From 88a4f2baf1de736b6f95f5e9c493d8fdd4d829a3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 16:27:43 -0800 Subject: [PATCH 001/685] Onboarding: no buttons if no frame (#36909) no buttons if no frame --- selfdrive/ui/mici/layouts/onboarding.py | 27 +++++++++++++------------ 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index d3588134111..5d441222149 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -237,19 +237,20 @@ def _render(self, _): ring_color, ) - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) - - self._good_button.render(rl.Rectangle( - self._rect.x + self._rect.width - self._good_button.rect.width - 8, - self._rect.y + self._rect.height - self._good_button.rect.height, - self._good_button.rect.width, - self._good_button.rect.height, - )) + if self._dialog._camera_view.frame: + self._back_button.render(rl.Rectangle( + self._rect.x + 8, + self._rect.y + self._rect.height - self._back_button.rect.height, + self._back_button.rect.width, + self._back_button.rect.height, + )) + + self._good_button.render(rl.Rectangle( + self._rect.x + self._rect.width - self._good_button.rect.width - 8, + self._rect.y + self._rect.height - self._good_button.rect.height, + self._good_button.rect.width, + self._good_button.rect.height, + )) # rounded border rl.draw_rectangle_rounded_lines_ex(self._rect, 0.2 * 1.02, 10, 50, rl.BLACK) From 4e855683701949c5da290ac646de837e5301b14e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 17:02:19 -0800 Subject: [PATCH 002/685] MultiOptionDialog: support no default (#36912) fix --- selfdrive/ui/mici/widgets/dialog.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 3d9aa3f9e24..3cd9f8b2c21 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -323,8 +323,8 @@ def __init__(self, options: list[str], default: str | None, if default is not None: assert default in options - self._default_option: str = default or (options[0] if len(options) > 0 else "") - self._selected_option: str = self._default_option + self._default_option: str | None = default + self._selected_option: str = self._default_option or (options[0] if len(options) > 0 else "") self._last_selected_option: str = self._selected_option self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) @@ -344,7 +344,8 @@ def click_callback(_btn=button): def show_event(self): super().show_event() self._scroller.show_event() - self._on_option_selected(self._default_option) + if self._default_option is not None: + self._on_option_selected(self._default_option) def get_selected_option(self) -> str: return self._selected_option From 2213f8f8a4002745ae67557ee518a8d374a9d701 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 17:10:12 -0800 Subject: [PATCH 003/685] MultiOptionDialog: tap activates current option (#36911) * works * clean up --- .../mici/layouts/settings/network/wifi_ui.py | 11 ++++----- selfdrive/ui/mici/widgets/dialog.py | 24 +++++++++++-------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 374539c4ce6..06347b5657a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -388,7 +388,7 @@ def _update_buttons(self): else: network_button = WifiItem(network) - self.add_button(network_button) + self._scroller.add_widget(network_button) # remove networks no longer present self._scroller._items[:] = [btn for btn in self._scroller._items if btn.option in self._networks] @@ -402,11 +402,10 @@ def _connect_with_password(self, ssid: str, password: str): self._wifi_manager.connect_to_network(ssid, password) self._update_buttons() - def _on_option_selected(self, option: str, smooth_scroll: bool = True): - super()._on_option_selected(option, smooth_scroll) + def _on_option_selected(self, option: str): + super()._on_option_selected(option) - # only open if button is already selected - if option in self._networks and option == self._selected_option: + if option in self._networks: self._network_info_page.set_current_network(self._networks[option]) self._open_network_manage_page() @@ -453,7 +452,7 @@ def _render(self, _): current_selection = self.get_selected_option() if self._restore_selection and current_selection in self._networks: self._scroller._layout() - BigMultiOptionDialog._on_option_selected(self, current_selection, smooth_scroll=False) + BigMultiOptionDialog._on_option_selected(self, current_selection) self._restore_selection = None super()._render(_) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 3cd9f8b2c21..eb36494ad48 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -328,18 +328,12 @@ def __init__(self, options: list[str], default: str | None, self._last_selected_option: str = self._selected_option self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) + self.set_touch_valid_callback(self._scroller.scroll_panel.is_touch_valid) if self._right_btn is not None: self._scroller.set_enabled(lambda: not cast(Widget, self._right_btn).is_pressed) for option in options: - self.add_button(BigDialogOptionButton(option)) - - def add_button(self, button: BigDialogOptionButton): - def click_callback(_btn=button): - self._on_option_selected(_btn.option) - - button.set_click_callback(click_callback) - self._scroller.add_widget(button) + self._scroller.add_widget(BigDialogOptionButton(option)) def show_event(self): super().show_event() @@ -350,7 +344,7 @@ def show_event(self): def get_selected_option(self) -> str: return self._selected_option - def _on_option_selected(self, option: str, smooth_scroll: bool = True): + def _on_option_selected(self, option: str): y_pos = 0.0 for btn in self._scroller._items: btn = cast(BigDialogOptionButton, btn) @@ -366,11 +360,21 @@ def _on_option_selected(self, option: str, smooth_scroll: bool = True): y_pos = rect_center_y - (btn.rect.y + height / 2) break - self._scroller.scroll_to(-y_pos, smooth=smooth_scroll) + self._scroller.scroll_to(-y_pos) def _selected_option_changed(self): pass + def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + + # select current option + for btn in self._scroller._items: + btn = cast(BigDialogOptionButton, btn) + if btn.option == self._selected_option: + self._on_option_selected(btn.option) + break + def _update_state(self): super()._update_state() From 6249211745d6cc68fd7446c78e2c9b9a64ca1393 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 19:57:57 -0800 Subject: [PATCH 004/685] Update canError alert texts (#36918) * hmm * hmm it's small * can * ? * variant? * unknown --- selfdrive/selfdrived/events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index c6dbcccbee4..1acfa2964e7 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -932,13 +932,13 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin # - CAN data is received, but some message are not received at the right frequency # If you're not writing a new car port, this is usually cause by faulty wiring EventName.canError: { - ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Error"), + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Unknown Vehicle Variant"), ET.PERMANENT: Alert( - "CAN Error: Check Connections", + "Unknown Vehicle Variant", "", AlertStatus.normal, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlert.none, 1., creation_delay=1.), - ET.NO_ENTRY: NoEntryAlert("CAN Error: Check Connections"), + ET.NO_ENTRY: NoEntryAlert("Unknown Vehicle Variant"), }, EventName.canBusMissing: { From 631d6d9ef4f8c375843c64c6b3552e32adf7b363 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 21:26:52 -0800 Subject: [PATCH 005/685] Widget: mouse handlers don't use bool (#36921) * not used * lint --- selfdrive/ui/layouts/settings/settings.py | 8 +++----- system/ui/widgets/__init__.py | 6 ++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/layouts/settings/settings.py b/selfdrive/ui/layouts/settings/settings.py index 72d3a4bafe3..68f45df77d0 100644 --- a/selfdrive/ui/layouts/settings/settings.py +++ b/selfdrive/ui/layouts/settings/settings.py @@ -145,20 +145,18 @@ def _draw_current_panel(self, rect: rl.Rectangle): if panel.instance: panel.instance.render(content_rect) - def _handle_mouse_release(self, mouse_pos: MousePos) -> bool: + def _handle_mouse_release(self, mouse_pos: MousePos) -> None: # Check close button if rl.check_collision_point_rec(mouse_pos, self._close_btn_rect): if self._close_callback: self._close_callback() - return True + return # Check navigation buttons for panel_type, panel_info in self._panels.items(): if rl.check_collision_point_rec(mouse_pos, panel_info.button_rect): self.set_current_panel(panel_type) - return True - - return False + return def set_current_panel(self, panel_type: PanelType): if panel_type != self._current_panel: diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index a3fed6d9621..8a46a3e43df 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -165,15 +165,13 @@ def _render(self, rect: rl.Rectangle) -> bool | int | None: def _update_layout_rects(self) -> None: """Optionally update any layout rects on Widget rect change.""" - def _handle_mouse_press(self, mouse_pos: MousePos) -> bool: + def _handle_mouse_press(self, mouse_pos: MousePos) -> None: """Optionally handle mouse press events.""" - return False - def _handle_mouse_release(self, mouse_pos: MousePos) -> bool: + def _handle_mouse_release(self, mouse_pos: MousePos) -> None: """Optionally handle mouse release events.""" if self._click_callback: self._click_callback() - return False def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: """Optionally handle mouse events. This is called before rendering.""" From 792a9b715c1c577f0adaf41c6fcc925884b2dc79 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 21:30:40 -0800 Subject: [PATCH 006/685] Widget: track mouse events (#36922) * track * fix * rm --- system/ui/widgets/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 8a46a3e43df..37683634865 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -26,6 +26,7 @@ def __init__(self): self.__is_pressed = [False] * MAX_TOUCH_SLOTS # if current mouse/touch down started within the widget's rectangle self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS + self.__mouse_events: list[MouseEvent | None] = [None] * MAX_TOUCH_SLOTS self._enabled: bool | Callable[[], bool] = True self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None @@ -125,12 +126,14 @@ def _process_mouse_events(self) -> None: if mouse_event.left_pressed and touch_valid: if mouse_in_rect: self._handle_mouse_press(mouse_event.pos) + self.__mouse_events[mouse_event.slot] = mouse_event self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True self._handle_mouse_event(mouse_event) # Callback such as scroll panel signifies user is scrolling elif not touch_valid: + self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False @@ -138,17 +141,20 @@ def _process_mouse_events(self) -> None: self._handle_mouse_event(mouse_event) if self.__is_pressed[mouse_event.slot] and mouse_in_rect: self._handle_mouse_release(mouse_event.pos) + self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False # Mouse/touch is still within our rect elif mouse_in_rect: if self.__tracking_is_pressed[mouse_event.slot]: + self.__mouse_events[mouse_event.slot] = mouse_event self.__is_pressed[mouse_event.slot] = True self._handle_mouse_event(mouse_event) # Mouse/touch left our rect but may come back into focus later elif not mouse_in_rect: + self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self._handle_mouse_event(mouse_event) From b3c2daf9e58c5fe174d9d402aca80215e9763806 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 21:32:06 -0800 Subject: [PATCH 007/685] Revert "Widget: track mouse events (#36922)" https://github.com/commaai/openpilot/pull/36920#issuecomment-3668453692 This reverts commit 792a9b715c1c577f0adaf41c6fcc925884b2dc79. --- system/ui/widgets/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 37683634865..8a46a3e43df 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -26,7 +26,6 @@ def __init__(self): self.__is_pressed = [False] * MAX_TOUCH_SLOTS # if current mouse/touch down started within the widget's rectangle self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS - self.__mouse_events: list[MouseEvent | None] = [None] * MAX_TOUCH_SLOTS self._enabled: bool | Callable[[], bool] = True self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None @@ -126,14 +125,12 @@ def _process_mouse_events(self) -> None: if mouse_event.left_pressed and touch_valid: if mouse_in_rect: self._handle_mouse_press(mouse_event.pos) - self.__mouse_events[mouse_event.slot] = mouse_event self.__is_pressed[mouse_event.slot] = True self.__tracking_is_pressed[mouse_event.slot] = True self._handle_mouse_event(mouse_event) # Callback such as scroll panel signifies user is scrolling elif not touch_valid: - self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False @@ -141,20 +138,17 @@ def _process_mouse_events(self) -> None: self._handle_mouse_event(mouse_event) if self.__is_pressed[mouse_event.slot] and mouse_in_rect: self._handle_mouse_release(mouse_event.pos) - self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self.__tracking_is_pressed[mouse_event.slot] = False # Mouse/touch is still within our rect elif mouse_in_rect: if self.__tracking_is_pressed[mouse_event.slot]: - self.__mouse_events[mouse_event.slot] = mouse_event self.__is_pressed[mouse_event.slot] = True self._handle_mouse_event(mouse_event) # Mouse/touch left our rect but may come back into focus later elif not mouse_in_rect: - self.__mouse_events[mouse_event.slot] = None self.__is_pressed[mouse_event.slot] = False self._handle_mouse_event(mouse_event) From a478b64ff3c0ac8356dbac31f907e6cf4090ca8e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 21:55:31 -0800 Subject: [PATCH 008/685] WifiUi: fix clicking (#36919) * fix * rm * rename * need this in case user swipes back to starting pos * better * better * clean up * cmt --- selfdrive/ui/mici/widgets/dialog.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index eb36494ad48..34760925a13 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -4,17 +4,17 @@ from typing import Union from collections.abc import Callable from typing import cast -from openpilot.selfdrive.ui.mici.widgets.side_button import SideButton from openpilot.system.ui.widgets import Widget, NavWidget, DialogResult from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, MouseEvent from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.widgets.side_button import SideButton DEBUG = False @@ -327,8 +327,10 @@ def __init__(self, options: list[str], default: str | None, self._selected_option: str = self._default_option or (options[0] if len(options) > 0 else "") self._last_selected_option: str = self._selected_option + # Widget doesn't differentiate between click and drag + self._can_click = True + self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) - self.set_touch_valid_callback(self._scroller.scroll_panel.is_touch_valid) if self._right_btn is not None: self._scroller.set_enabled(lambda: not cast(Widget, self._right_btn).is_pressed) @@ -365,9 +367,23 @@ def _on_option_selected(self, option: str): def _selected_option_changed(self): pass + def _handle_mouse_press(self, mouse_pos: MousePos): + super()._handle_mouse_press(mouse_pos) + self._can_click = True + + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: + super()._handle_mouse_event(mouse_event) + + # # TODO: add generic _handle_mouse_click handler to Widget + if not self._scroller.scroll_panel.is_touch_valid(): + self._can_click = False + def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) + if not self._can_click: + return + # select current option for btn in self._scroller._items: btn = cast(BigDialogOptionButton, btn) From 89d9fdca82aab17bb85ee1c63db4c06f2dd262a9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 17 Dec 2025 23:15:22 -0800 Subject: [PATCH 009/685] WifiUi: tune wifi strengths (#36923) * tune wifi strengths * android nor ios show none or slash. it's always 1, 2, or 3 bars * match here * clean up --- .../ui/mici/layouts/settings/network/wifi_ui.py | 14 ++++---------- system/ui/lib/wifi_manager.py | 2 +- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 06347b5657a..565fef5af3f 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -36,8 +36,6 @@ def __init__(self): super().__init__() self.set_rect(rl.Rectangle(0, 0, 89, 64)) - self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 89, 64) - self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 89, 64) self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 89, 64) self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 89, 64) self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 89, 64) @@ -57,17 +55,13 @@ def _render(self, _): return # Determine which wifi strength icon to use - strength = round(self._network.strength / 100 * 4) - if strength == 4: + strength = round(self._network.strength / 100 * 2) + if strength == 2: strength_icon = self._wifi_full_txt - elif strength == 3: + elif strength == 1: strength_icon = self._wifi_medium_txt - elif strength == 2: - strength_icon = self._wifi_low_txt - elif self._network.strength < 0: - strength_icon = self._wifi_slash_txt else: - strength_icon = self._wifi_none_txt + strength_icon = self._wifi_low_txt icon_x = int(self._rect.x + (self._rect.width - strength_icon.width * self._scale) // 2) icon_y = int(self._rect.y + (self._rect.height - strength_icon.height * self._scale) // 2) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 7e5f04ef6f7..bd66b8e03ab 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -631,7 +631,7 @@ def _update_networks(self): known_connections = self._get_connections() networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()] # sort with quantized strength to reduce jumping - networks.sort(key=lambda n: (-n.is_connected, -round(n.strength / 100 * 4), n.ssid.lower())) + networks.sort(key=lambda n: (-n.is_connected, -round(n.strength / 100 * 2), n.ssid.lower())) self._networks = networks self._update_ipv4_address() From 13b8a67ae27210f0a8b7544afca219ea1c0a641c Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 18 Dec 2025 16:03:45 -0800 Subject: [PATCH 010/685] reset: fix button overlap (#36929) fix --- system/ui/mici_reset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index d9bb45d99ad..925afd7d10a 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -132,7 +132,7 @@ def _get_body_text(self): if self._reset_state == ResetState.FAILED: return "Reset failed. Reboot to try again." if self._mode == ResetMode.RECOVER: - return "Unable to mount data partition. Partition may be corrupted." + return "Unable to mount data partition. It may be corrupted." return "All content and settings will be erased." From e4359e9acb76586e99434592cdf3ee3ccf9aaecd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 Dec 2025 17:15:47 -0800 Subject: [PATCH 011/685] api_get: use session (#36930) * use session * prob better for now * todo * same for firehose * no more stutters! * cmt --- common/api.py | 6 ++++-- selfdrive/ui/lib/prime_state.py | 4 +++- selfdrive/ui/mici/layouts/settings/firehose.py | 4 +++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/common/api.py b/common/api.py index 7ea278038d5..ebf0290d154 100644 --- a/common/api.py +++ b/common/api.py @@ -42,14 +42,16 @@ def get_token(self, payload_extra=None, expiry_hours=1): return token -def api_get(endpoint, method='GET', timeout=None, access_token=None, **params): +def api_get(endpoint, method='GET', timeout=None, access_token=None, session=None, **params): headers = {} if access_token is not None: headers['Authorization'] = "JWT " + access_token headers['User-Agent'] = "openpilot-" + get_version() - return requests.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params) + # TODO: add session to Api + req = requests if session is None else session + return req.request(method, API_HOST + "/" + endpoint, timeout=timeout, headers=headers, params=params) def get_key_pair() -> tuple[str, str, str] | tuple[None, None, None]: diff --git a/selfdrive/ui/lib/prime_state.py b/selfdrive/ui/lib/prime_state.py index e1ef387bf77..1aed949bee9 100644 --- a/selfdrive/ui/lib/prime_state.py +++ b/selfdrive/ui/lib/prime_state.py @@ -1,5 +1,6 @@ from enum import IntEnum import os +import requests import threading import time @@ -29,6 +30,7 @@ class PrimeState: def __init__(self): self._params = Params() self._lock = threading.Lock() + self._session = requests.Session() # reuse session to reduce SSL handshake overhead self.prime_type: PrimeType = self._load_initial_state() self._running = False @@ -50,7 +52,7 @@ def _fetch_prime_status(self) -> None: try: identity_token = get_token(dongle_id) - response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token) + response = api_get(f"v1.1/devices/{dongle_id}", timeout=self.API_TIMEOUT, access_token=identity_token, session=self._session) if response.status_code == 200: data = response.json() is_paired = data.get("is_paired", False) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index 10e52bb3b4e..d305906e13d 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -1,3 +1,4 @@ +import requests import threading import time import pyray as rl @@ -44,6 +45,7 @@ class FirehoseLayoutBase(Widget): def __init__(self): super().__init__() self._params = Params() + self._session = requests.Session() # reuse session to reduce SSL handshake overhead self._segment_count = self._get_segment_count() self._scroll_panel = GuiScrollPanel2(horizontal=False) @@ -203,7 +205,7 @@ def _fetch_firehose_stats(self): if not dongle_id or dongle_id == UNREGISTERED_DONGLE_ID: return identity_token = get_token(dongle_id) - response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token) + response = api_get(f"v1/devices/{dongle_id}/firehose_stats", access_token=identity_token, session=self._session) if response.status_code == 200: data = response.json() self._segment_count = data.get("firehose", 0) From bb8a5bd476a98d61788c78959903b89d3e81440a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 18 Dec 2025 20:50:34 -0800 Subject: [PATCH 012/685] Fix slow DM onboarding (#36932) * slow * interesting * check * clean up --- selfdrive/ui/mici/layouts/onboarding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 5d441222149..16e96d6f7df 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -166,8 +166,8 @@ def show_event(self): def _update_state(self): super()._update_state() - if device.awake: - ui_state.params.put_bool("IsDriverViewEnabled", True) + if device.awake and not ui_state.params.get_bool("IsDriverViewEnabled"): + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", True) sm = ui_state.sm if sm.recv_frame.get("driverMonitoringState", 0) == 0: From dfd7a8c8d74b822a15738a2afedc8159990cd042 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 18 Dec 2025 22:02:46 -0800 Subject: [PATCH 013/685] AGNOS 16 (#36915) * stage * prod * bump reset --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 20 +++++------ system/hardware/tici/all-partitions.json | 44 ++++++++++++------------ 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index fcbee2ff8d3..314366f429a 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="15.1" + export AGNOS_VERSION="16" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 5a2a092aa8a..e33a26bb2e4 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c.img.xz", - "hash": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", - "hash_raw": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", + "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", + "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", + "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", "size": 17496064, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "35014c39b55010ac955c10f808b088e74259147c7a8cbf989b3dff7d95a1e8ae" + "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img.xz", - "hash": "a068d4d692ec770884f0a15e1a6d7aba52385ecae138f6d43fb0a9b1643ed5cd", - "hash_raw": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", + "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", + "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", + "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "6ffa02f7113badc122742f33efebc5d17f1cd61dd6358f3e130c162707dbfaf4", + "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", "alt": { - "hash": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", - "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img", + "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 3abf66cdd4e..b6718fe97db 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,62 +339,62 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c.img.xz", - "hash": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", - "hash_raw": "90bd687e9e407834d4ee1b07f3d05527dfae0ff09c0cacd64cfd6097f6b10e2c", + "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", + "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", + "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", "size": 17496064, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "35014c39b55010ac955c10f808b088e74259147c7a8cbf989b3dff7d95a1e8ae" + "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img.xz", - "hash": "a068d4d692ec770884f0a15e1a6d7aba52385ecae138f6d43fb0a9b1643ed5cd", - "hash_raw": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", + "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", + "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", + "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "6ffa02f7113badc122742f33efebc5d17f1cd61dd6358f3e130c162707dbfaf4", + "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", "alt": { - "hash": "d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818", - "url": "https://commadist.azureedge.net/agnosupdate/system-d9d476b466186014e7ae4b8232bc6fc5e79b122421bdc12ff4eb02d1c3f37818.img", + "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-f9ea618ac97a86da49733ce66cd5e3aa19aa917666ee90de301cd746664e4d22.img.xz", - "hash": "dfc6812e76bd1583ed77a86eedf48cafdc306037d2a85c5d0aa7cdb23033b736", - "hash_raw": "f9ea618ac97a86da49733ce66cd5e3aa19aa917666ee90de301cd746664e4d22", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb.img.xz", + "hash": "aa0f0fe32187493e6135aee9e984d3f9705fc58560d537b34687bb6b51a38428", + "hash_raw": "ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "ff95f994e9ed6504632f4b7c6daecef582f0a4e5261b8240d4474f16059faef4" + "ondevice_hash": "9c916b7d05543d4608b0401bc867639f44ce9671639a1a6da83b6d58b4eaa1b4" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-393956e255c277b895bdb98bf65cfa3907e4b57822740ff82f857ac4e1a2f11e.img.xz", - "hash": "b5e2f05d31fc18fff18e82dcebfc2bf04de624baeca0511b93e50b3198b8a9ab", - "hash_raw": "393956e255c277b895bdb98bf65cfa3907e4b57822740ff82f857ac4e1a2f11e", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11.img.xz", + "hash": "fa83d4b7096857136820b0b0a8785c90677256b054c5c14039cd7b9b1065a90b", + "hash_raw": "7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "db64c6abc72bfcddc1682c73cc73c7230ed2f6e835d292fd38d054a9d242b8fc" + "ondevice_hash": "1699e38de769eb32c21dfa6a5ac21eb3ad620a362c7b8abf1a2c0afe0f717530" }, { "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-a4b3e2a2fc3612a37322b7b1a4c5737765841dc3b8d6d3bb58b1e5a271023068.img.xz", - "hash": "ecec713cf7d8f1f616f122a16b138931f818290447e36a5925da6a4fc0fc7bf3", - "hash_raw": "a4b3e2a2fc3612a37322b7b1a4c5737765841dc3b8d6d3bb58b1e5a271023068", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed.img.xz", + "hash": "890910f20b1ad88a728ee822a47b1234eb3d70cab28ca8a935679c8c2d33cbe9", + "hash_raw": "3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed", "size": 32212254720, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "48fefa5a1880a4fd3dd50e1f9ddee297122053556816baca310d495129bc8893" + "ondevice_hash": "8e7cb392dd6e49c7d59fa850be7d1f44901314c86ba9c88be5bb27a0cd1123c9" } ] \ No newline at end of file From 654338f9c7c096377ad39afc39122fdd207ce3d1 Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 18 Dec 2025 22:52:14 -0800 Subject: [PATCH 014/685] update release instructions --- release/README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/release/README.md b/release/README.md index fb651fa05ad..7aeea9fe4af 100644 --- a/release/README.md +++ b/release/README.md @@ -4,18 +4,17 @@ ## release checklist ### Go to staging -- [ ] make a GitHub issue to track release +- [ ] make a GitHub issue to track release with this checklist - [ ] create release master branch -- [ ] update RELEASES.md + - [ ] create a branch from upstream master named `zerotentwo` for release `v0.10.2` + - [ ] revert risky commits (double check with autonomy team) + - [ ] push the new branch +- [ ] push to staging: + - [ ] make sure you are on the newly created release master branch (`zerotentwo`) + - [ ] run `BRANCH=devel-staging release/build_stripped.sh`. Jenkins will then automatically build staging on device, run `test_onroad` and update the staging branch - [ ] bump version on master: `common/version.h` and `RELEASES.md` -- [ ] build new userdata partition from `release3-staging` - [ ] post on Discord, tag `@release crew` -Updating staging: -1. either rebase on master or cherry-pick changes -2. run this to update: `BRANCH=devel-staging release/build_devel.sh` -3. build new userdata partition from `release3-staging` - ### Go to release - [ ] before going to release, test the following: - [ ] update from previous release -> new release @@ -26,7 +25,7 @@ Updating staging: - [ ] check sentry, MTBF, etc. - [ ] stress test passes in production - [ ] publish the blog post -- [ ] `git reset --hard origin/release3-staging` +- [ ] `git reset --hard origin/release-mici-staging` - [ ] tag the release: `git tag v0.X.X && git push origin v0.X.X` - [ ] create GitHub release - [ ] final test install on `openpilot.comma.ai` From 1c2f9e6190210a870191ffb39c719f73b069b24c Mon Sep 17 00:00:00 2001 From: Maxime Desroches Date: Thu, 18 Dec 2025 23:26:06 -0800 Subject: [PATCH 015/685] bump version to 0.10.4 --- RELEASES.md | 3 +++ common/version.h | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 30283514009..186f1bc0470 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,6 @@ +Version 0.10.4 (2026-02-17) +======================== + Version 0.10.3 (2025-12-17) ======================== * New driving model #36249 diff --git a/common/version.h b/common/version.h index c489ecc578e..7e78d64b223 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.10.3" +#define COMMA_VERSION "0.10.4" From c3143f3833a1ca6f1b949e70df54d9753e8195a9 Mon Sep 17 00:00:00 2001 From: eFini Date: Sat, 20 Dec 2025 01:38:23 +0800 Subject: [PATCH 016/685] Multilang: update zh translation (#36933) --- selfdrive/ui/translations/app_zh-CHS.po | 6 +++--- selfdrive/ui/translations/app_zh-CHT.po | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/translations/app_zh-CHS.po b/selfdrive/ui/translations/app_zh-CHS.po index 16e4369476c..2400b6f44aa 100644 --- a/selfdrive/ui/translations/app_zh-CHS.po +++ b/selfdrive/ui/translations/app_zh-CHS.po @@ -200,7 +200,7 @@ msgstr "CONNECT" #: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." -msgstr "CONNECTING..." +msgstr "连接中..." #: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 #: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 @@ -1001,7 +1001,7 @@ msgstr "用于“{}”" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" -msgstr "公里/时" +msgstr "km/h" #: system/ui/widgets/network.py:204 #, python-format @@ -1021,7 +1021,7 @@ msgstr "计量" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" -msgstr "英里/时" +msgstr "mph" #: selfdrive/ui/layouts/settings/software.py:20 #, python-format diff --git a/selfdrive/ui/translations/app_zh-CHT.po b/selfdrive/ui/translations/app_zh-CHT.po index 85cfb774014..f4d5e0a4ed2 100644 --- a/selfdrive/ui/translations/app_zh-CHT.po +++ b/selfdrive/ui/translations/app_zh-CHT.po @@ -200,7 +200,7 @@ msgstr "CONNECT" #: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." -msgstr "CONNECTING..." +msgstr "連線中..." #: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 #: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 @@ -1000,7 +1000,7 @@ msgstr "適用於「{}」" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" -msgstr "公里/時" +msgstr "km/h" #: system/ui/widgets/network.py:204 #, python-format @@ -1020,7 +1020,7 @@ msgstr "計量" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" -msgstr "英里/時" +msgstr "mph" #: selfdrive/ui/layouts/settings/software.py:20 #, python-format From de975d5af9413a36fcb8c3b28be03b85a4296278 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 Dec 2025 15:04:13 -0800 Subject: [PATCH 017/685] card: remove MockCarstate (#36941) --- selfdrive/car/car_specific.py | 16 ---------------- selfdrive/car/card.py | 4 ---- 2 files changed, 20 deletions(-) diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 6210983d972..307132e7806 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -1,5 +1,4 @@ from cereal import car, log -import cereal.messaging as messaging from opendbc.car import DT_CTRL, structs from opendbc.car.interfaces import MAX_CTRL_SPEED @@ -11,21 +10,6 @@ NetworkLocation = structs.CarParams.NetworkLocation -# TODO: the goal is to abstract this file into the CarState struct and make events generic -class MockCarState: - def __init__(self): - self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal']) - - def update(self, CS: car.CarState): - self.sm.update(0) - gps_sock = 'gpsLocationExternal' if self.sm.recv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' - - CS.vEgo = self.sm[gps_sock].speed - CS.vEgoRaw = self.sm[gps_sock].speed - - return CS - - BRAND_EXTRA_GEARS = { 'ford': [GearShifter.low, GearShifter.manumatic], 'nissan': [GearShifter.brake], diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 27b04ae65e7..aa0d12e9608 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -19,7 +19,6 @@ from opendbc.car.interfaces import CarInterfaceBase, RadarInterfaceBase from openpilot.selfdrive.pandad import can_capnp_to_list, can_list_to_can_capnp from openpilot.selfdrive.car.cruise import VCruiseHelper -from openpilot.selfdrive.car.car_specific import MockCarState REPLAY = "REPLAY" in os.environ @@ -150,7 +149,6 @@ def __init__(self, CI=None, RI=None) -> None: self.params.put_nonblocking("CarParamsCache", cp_bytes) self.params.put_nonblocking("CarParamsPersistent", cp_bytes) - self.mock_carstate = MockCarState() self.v_cruise_helper = VCruiseHelper(self.CP) self.is_metric = self.params.get_bool("IsMetric") @@ -167,8 +165,6 @@ def state_update(self) -> tuple[car.CarState, structs.RadarDataT | None]: # Update carState from CAN CS = self.CI.update(can_list) - if self.CP.brand == 'mock': - CS = self.mock_carstate.update(CS) # Update radar tracks from CAN RD: structs.RadarDataT | None = self.RI.update(can_list) From 67742699ccd76fe864e46758c3aa1a67334fc464 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 20 Dec 2025 15:22:30 -0800 Subject: [PATCH 018/685] card: move drivable gears to opendbc (#36942) * card: move drivable gears to opendbc * it's not none * bump opendbc * mypy --- opendbc_repo | 2 +- selfdrive/car/car_specific.py | 20 ++++---------------- 2 files changed, 5 insertions(+), 17 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 39773a987ea..4aa7ca97229 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 39773a987eaefd680c6befa390d7898945daf2e7 +Subproject commit 4aa7ca972290af6c6bbd314d1975eab46a888b6f diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 307132e7806..90e4a6986a2 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -1,5 +1,6 @@ from cereal import car, log from opendbc.car import DT_CTRL, structs +from opendbc.car.car_helpers import interfaces from opendbc.car.interfaces import MAX_CTRL_SPEED from openpilot.selfdrive.selfdrived.events import Events @@ -10,18 +11,6 @@ NetworkLocation = structs.CarParams.NetworkLocation -BRAND_EXTRA_GEARS = { - 'ford': [GearShifter.low, GearShifter.manumatic], - 'nissan': [GearShifter.brake], - 'chrysler': [GearShifter.low], - 'honda': [GearShifter.sport], - 'toyota': [GearShifter.sport], - 'gm': [GearShifter.sport, GearShifter.low, GearShifter.eco, GearShifter.manumatic], - 'volkswagen': [GearShifter.eco, GearShifter.sport, GearShifter.manumatic], - 'hyundai': [GearShifter.sport, GearShifter.manumatic] -} - - class CarSpecificEvents: def __init__(self, CP: structs.CarParams): self.CP = CP @@ -32,7 +21,7 @@ def __init__(self, CP: structs.CarParams): self.silent_steer_warning = True def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): - extra_gears = BRAND_EXTRA_GEARS.get(self.CP.brand, None) + extra_gears = interfaces[self.CP.carFingerprint].DRIVABLE_GEARS if self.CP.brand in ('body', 'mock'): events = Events() @@ -118,7 +107,7 @@ def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): return events - def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: list | None = None, pcm_enable=True, + def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: tuple = (), pcm_enable=True, allow_button_cancel=True): events = Events() @@ -126,8 +115,7 @@ def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extr events.add(EventName.doorOpen) if CS.seatbeltUnlatched: events.add(EventName.seatbeltNotLatched) - if CS.gearShifter != GearShifter.drive and (extra_gears is None or - CS.gearShifter not in extra_gears): + if CS.gearShifter != GearShifter.drive and CS.gearShifter not in extra_gears: events.add(EventName.wrongGear) if CS.gearShifter == GearShifter.reverse: events.add(EventName.reverseGear) From e9a37d99c3d715e1b44d85da5c48c49136a5eb90 Mon Sep 17 00:00:00 2001 From: Dean Lee Date: Sun, 21 Dec 2025 08:33:52 +0800 Subject: [PATCH 019/685] Fix incorrect msgq path in prefix.h (#36943) --- common/prefix.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/prefix.h b/common/prefix.h index 2612c05d4fc..7d0265c2599 100644 --- a/common/prefix.h +++ b/common/prefix.h @@ -13,7 +13,7 @@ class OpenpilotPrefix { if (prefix.empty()) { prefix = util::random_string(15); } - msgq_path = Path::shm_path() + "/" + prefix; + msgq_path = Path::shm_path() + "/msgq_" + prefix; bool ret = util::create_directories(msgq_path, 0777); assert(ret); setenv("OPENPILOT_PREFIX", prefix.c_str(), 1); From 8728c7dde38be4a10f3044312e12195ebe4d57f4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 15:42:53 -0800 Subject: [PATCH 020/685] killing car_specific.py, part 3 (#36946) * mv that * lil more * todo * cleanup --- selfdrive/car/car_specific.py | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index 90e4a6986a2..cdeeebdcd57 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -21,14 +21,12 @@ def __init__(self, CP: structs.CarParams): self.silent_steer_warning = True def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): - extra_gears = interfaces[self.CP.carFingerprint].DRIVABLE_GEARS - if self.CP.brand in ('body', 'mock'): - events = Events() + return Events() - elif self.CP.brand == 'chrysler': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) + events = self.create_common_events(CS, CS_prev) + if self.CP.brand == 'chrysler': # Low speed steer alert hysteresis logic if self.CP.minSteerSpeed > 0. and CS.vEgo < (self.CP.minSteerSpeed + 0.5): self.low_speed_alert = True @@ -38,8 +36,6 @@ def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): events.add(EventName.belowSteerSpeed) elif self.CP.brand == 'honda': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=False) - if self.CP.pcmCruise and CS.vEgo < self.CP.minEnableSpeed: events.add(EventName.belowEngageSpeed) @@ -60,8 +56,6 @@ def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): elif self.CP.brand == 'toyota': # TODO: when we check for unexpected disengagement, check gear not S1, S2, S3 - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) - if self.CP.openpilotLongitudinalControl: # Only can leave standstill when planner wants to move if CS.cruiseState.standstill and not CS.brakePressed and CC.cruiseControl.resume: @@ -76,8 +70,6 @@ def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): events.add(EventName.manualRestart) elif self.CP.brand == 'gm': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise) - # Enabling at a standstill with brake is allowed # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs if CS.vEgo < self.CP.minEnableSpeed and not (CS.standstill and CS.brake >= 20 and @@ -87,8 +79,6 @@ def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): events.add(EventName.resumeRequired) elif self.CP.brand == 'volkswagen': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise) - if self.CP.openpilotLongitudinalControl: if CS.vEgo < self.CP.minEnableSpeed + 0.5: events.add(EventName.belowEngageSpeed) @@ -99,23 +89,23 @@ def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): # if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined] # events.add(EventName.steerTimeLimit) - elif self.CP.brand == 'hyundai': - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears, pcm_enable=self.CP.pcmCruise, allow_button_cancel=False) - - else: - events = self.create_common_events(CS, CS_prev, extra_gears=extra_gears) - return events - def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState, extra_gears: tuple = (), pcm_enable=True, - allow_button_cancel=True): + def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState): events = Events() + CI = interfaces[self.CP.carFingerprint] + # TODO: cleanup the honda-specific logic + pcm_enable = self.CP.pcmCruise and self.CP.brand != 'honda' + # TODO: on some hyundai cars, the cancel button is also the pause/resume button, + # so only use it for cancel when running openpilot longitudinal + allow_button_cancel = self.CP.brand != 'hyundai' + if CS.doorOpen: events.add(EventName.doorOpen) if CS.seatbeltUnlatched: events.add(EventName.seatbeltNotLatched) - if CS.gearShifter != GearShifter.drive and CS.gearShifter not in extra_gears: + if CS.gearShifter != GearShifter.drive and CS.gearShifter not in CI.DRIVABLE_GEARS: events.add(EventName.wrongGear) if CS.gearShifter == GearShifter.reverse: events.add(EventName.reverseGear) From 31ec0096e46ad2ac310d40a5f99ecf22243511ad Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 16:21:35 -0800 Subject: [PATCH 021/685] ui: fix raylib init spam (#36947) * lil more * not none * don't need that either * too spammy now? --- Jenkinsfile | 2 +- system/ui/lib/application.py | 9 +++++---- system/ui/lib/multilang.py | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 73fa74c1cde..c095eda8a91 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,7 +22,7 @@ shopt -s huponexit # kill all child processes when the shell exits export CI=1 export PYTHONWARNINGS=error -export LOGPRINT=debug +#export LOGPRINT=debug # this has gotten too spammy... export TEST_DIR=${env.TEST_DIR} export SOURCE_DIR=${env.SOURCE_DIR} export GIT_BRANCH=${env.GIT_BRANCH} diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 501a7ff371d..04cd37af3ec 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -192,6 +192,8 @@ def _handle_mouse_event(self): class GuiApplication: def __init__(self, width: int | None = None, height: int | None = None): + self._set_log_callback() + self._fonts: dict[FontWeight, rl.Font] = {} self._width = width if width is not None else GuiApplication._default_width() self._height = height if height is not None else GuiApplication._default_height() @@ -215,7 +217,6 @@ def __init__(self, width: int | None = None, height: int | None = None): self._last_fps_log_time: float = time.monotonic() self._frame = 0 self._window_close_requested = False - self._trace_log_callback = None self._modal_overlay = ModalOverlay() self._modal_overlay_shown = False self._modal_overlay_tick: Callable[[], None] | None = None @@ -260,9 +261,6 @@ def _close(sig, frame): signal.signal(signal.SIGINT, _close) atexit.register(self.close) - self._set_log_callback() - rl.set_trace_log_level(rl.TraceLogLevel.LOG_WARNING) - flags = rl.ConfigFlags.FLAG_MSAA_4X_HINT if ENABLE_VSYNC: flags |= rl.ConfigFlags.FLAG_VSYNC_HINT @@ -640,6 +638,9 @@ def trace_log_callback(log_level, text, args): else: cloudlog.error(f"raylib: Unknown level {log_level}: {text_str}") + # ensure we get all the logs forwarded to us + rl.set_trace_log_level(rl.TraceLogLevel.LOG_DEBUG) + # Store callback reference self._trace_log_callback = trace_log_callback rl.set_trace_log_callback(self._trace_log_callback) diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 9b10a8bdc71..70de1e3d5c8 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -48,7 +48,7 @@ def setup(self): translation = gettext.GNUTranslations(fh) translation.install() self._translation = translation - cloudlog.warning(f"Loaded translations for language: {self._language}") + cloudlog.debug(f"Loaded translations for language: {self._language}") except FileNotFoundError: cloudlog.error(f"No translation file found for language: {self._language}, using default.") gettext.install('app') From 29a2f576f5929b2efe6761e3996a926e3b9e62d5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 16:49:15 -0800 Subject: [PATCH 022/685] unpin raylib-python-cffi (#36948) --- pyproject.toml | 4 ++-- uv.lock | 40 +++++++++++++++++++++++----------------- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d5dc95e1b1a..fc80488c693 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dependencies = [ # logging "pyzmq", "sentry-sdk", - "xattr", # used in place of 'os.getxattr' for macos compatibility + "xattr", # used in place of 'os.getxattr' for macOS compatibility # athena "PyJWT", @@ -72,7 +72,7 @@ dependencies = [ "zstandard", # ui - "raylib < 5.5.0.3", # TODO: unpin when they fix https://github.com/electronstudio/raylib-python-cffi/issues/186 + "raylib > 5.5.0.3", "qrcode", "mapbox-earcut", ] diff --git a/uv.lock b/uv.lock index b179517e0b2..53cb108c610 100644 --- a/uv.lock +++ b/uv.lock @@ -1463,7 +1463,7 @@ requires-dist = [ { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", specifier = "<5.5.0.3" }, + { name = "raylib", specifier = ">5.5.0.3" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -4639,26 +4639,32 @@ wheels = [ [[package]] name = "raylib" -version = "5.5.0.2" +version = "5.5.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8c/35/9bf3a2af73c55fd4310dcaec4f997c739888e0db9b4dfac71b7680810852/raylib-5.5.0.2.tar.gz", hash = "sha256:83c108ae3b4af40b53c93d1de2afbe309e986dd5efeb280ebe2e61c79956edb0", size = 181172, upload-time = "2024-11-26T11:12:02.791Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c4/ce21721b474eb8f65379f7315b382ccfe1d5df728eea4dcf287b874e7461/raylib-5.5.0.2-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:37eb0ec97fc6b08f989489a50e09b5dde519e1bb8eb17e4033ac82227b0e5eda", size = 1703742, upload-time = "2024-11-26T11:09:31.115Z" }, - { url = "https://files.pythonhosted.org/packages/23/61/138e305c82549869bb8cd41abe75571559eafbeab6aed1ce7d8fbe3ffd58/raylib-5.5.0.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bb9e506ecd3dbec6dba868eb036269837a8bde68220690842c3238239ee887ef", size = 1247449, upload-time = "2024-11-26T11:09:34.182Z" }, - { url = "https://files.pythonhosted.org/packages/85/e0/dc638c42d1a505f0992263d48e1434d82c21afdf376b06f549d2e281dfd4/raylib-5.5.0.2-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:70aa8bed67875a8cf25191f35263ef92d646bdfcb1f507915c81562a321f4931", size = 2184315, upload-time = "2024-11-26T11:09:36.715Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1a/49db57283a28fdc1ff0e4604911b7fff085128c2ac8bdd9efa8c5c47439d/raylib-5.5.0.2-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:0365e8c578f72f598795d9377fc70342f0d62aa193c2f304ca048b3e28866752", size = 2278139, upload-time = "2024-11-26T11:09:39.475Z" }, - { url = "https://files.pythonhosted.org/packages/f0/8a/e1a690ab6889d4cb67346a2d32bad8b8e8b0f85ec826b00f76b0ad7e6ad6/raylib-5.5.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:5219be70e7fca03e9c4fddebf7e60e885d77137125c7a13f3800a947f8562a13", size = 1693944, upload-time = "2024-11-26T11:09:41.596Z" }, - { url = "https://files.pythonhosted.org/packages/69/2b/49bfa6833ad74ddf318d54ecafe73d535f583531469ecbd5b009d79667d1/raylib-5.5.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5233c529d9a0cfd469d88239c2182e55c5215a7755d83cc3d611148d3b9c9e67", size = 1706157, upload-time = "2024-11-26T11:09:43.6Z" }, - { url = "https://files.pythonhosted.org/packages/58/9c/8a3f4de0c81ad1228bf26410cfe3ecdc73011c59f18e542685ffc92c0120/raylib-5.5.0.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:1f76204ffbc492722b571b12dbdc0dca89b10da76ddf48c12a3968d2db061dff", size = 1248027, upload-time = "2025-01-04T20:21:46.269Z" }, - { url = "https://files.pythonhosted.org/packages/7f/16/63baf1aae94832b9f5d15cafcee67bb6dd07a20cf64d40bac09903b79274/raylib-5.5.0.2-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:f8cc2e39f1d6b29211a97ec0ac818a5b04c43a40e747e4b4622101d48c711f9e", size = 2195374, upload-time = "2024-11-26T11:09:46.114Z" }, - { url = "https://files.pythonhosted.org/packages/70/bd/61a006b4e3ce4a6ca974cb0ceeb19f3816815ebabac650e9bf82767e65f6/raylib-5.5.0.2-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:f12da578a28da7f48481f46323e5aab8dd25461982b0e80d045782d6e69649f5", size = 2299593, upload-time = "2024-11-26T11:09:48.963Z" }, - { url = "https://files.pythonhosted.org/packages/f4/4f/59d554cc495bea8235b17cebfc76ed57aaa602c613b870159e31282fd4c1/raylib-5.5.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:b40234bbad9523fd6a2049640c76a98b4d6f0b8f4bd19bd33eaee55faf5e050d", size = 1696780, upload-time = "2024-11-26T11:09:50.787Z" }, - { url = "https://files.pythonhosted.org/packages/4a/22/2e02e3738ad041f5ec2830aecdfab411fc2960bfc3400e03b477284bfaf7/raylib-5.5.0.2-pp311-pypy311_pp73-macosx_10_13_x86_64.whl", hash = "sha256:bc45fe1c0aac50aa319a9a66d44bb2bd0dcd038a44d95978191ae7bfeb4a06d8", size = 1216231, upload-time = "2025-02-12T04:21:59.38Z" }, - { url = "https://files.pythonhosted.org/packages/fe/7d/b29afedc4a706b12143f74f322cb32ad5a6f43e56aaca2a9fb89b0d94eee/raylib-5.5.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.whl", hash = "sha256:2242fd6079da5137e9863a447224f800adef6386ca8f59013a5d62cc5cadab2b", size = 1394928, upload-time = "2025-02-12T04:22:03.021Z" }, - { url = "https://files.pythonhosted.org/packages/b6/fa/2daf36d78078c6871b241168a36156169cfc8ea089faba5abe8edad304be/raylib-5.5.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e475a40764c9f83f9e66406bd86d85587eb923329a61ade463c3c59e1e880b16", size = 1564224, upload-time = "2025-02-12T04:22:05.911Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/98a78b819d7374dab309525ce45cd591d0d62db7f6ed2d5ed32b8f55d62b/raylib-5.5.0.4-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:09717ed32c9ec1c574370e2e2d30e9bc13876f7e2f2dd6e04dc366dae23e0994", size = 1632797, upload-time = "2025-12-11T15:27:15.429Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f4/ec949f45274cf266875b30b67f8cb7243ecced05080cec54bf65ec73a8b2/raylib-5.5.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cef7b0e238eafc80a3be7e3c656a3ddc94cc523790758b7130df1957ba4ad4ad", size = 1550301, upload-time = "2025-12-11T15:27:17.429Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5a/8f60d367147019acef342746f20121b2341ec6596acd5c7941cb36bda02e/raylib-5.5.0.4-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:bdaa119b767f380caf6dd4f9d42ab3bf8596d8fb98737d2951b36924a5a83ac0", size = 2036797, upload-time = "2025-12-11T15:27:20.044Z" }, + { url = "https://files.pythonhosted.org/packages/dd/ad/97dd93c389263c61a3057065f0f70db5fdc3c5768fa383a9b3e989ddb6a7/raylib-5.5.0.4-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:6a5cdeeb803d081342961eb1f7c4161af27e951d9ecf2b56d469d5730fcc6213", size = 2188009, upload-time = "2025-12-11T18:50:05.612Z" }, + { url = "https://files.pythonhosted.org/packages/42/6a/55be04012f3459842389689326910204f985cffcb8989a92475221f5660a/raylib-5.5.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4067fa8a6ed3eb78a1162fc2d40ce8c26c26c5ee544019d1902accf21ec22add", size = 2187633, upload-time = "2025-12-11T15:27:22.345Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b7/702ea311fcb1b82064a1c50f32fe86fce1f21caa39c54ca1d598a9862444/raylib-5.5.0.4-cp311-cp311-manylinux_2_35_aarch64.whl", hash = "sha256:faa403252fd0a692dd2f37d9dd4e79fa293ec05deae8a3b086b063cd725a48f9", size = 2247484, upload-time = "2025-12-11T15:27:24.718Z" }, + { url = "https://files.pythonhosted.org/packages/6b/18/b69d9ad9f4064785ad29c73672d40b36c59c3b3efd1dee264cdff4b48bf6/raylib-5.5.0.4-cp311-cp311-win32.whl", hash = "sha256:f01a769bb0797ab4f6e1efc950d5d8aca53548e97da7f527190a1ca5f671c389", size = 1456775, upload-time = "2025-12-11T15:27:26.776Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7a/4025d9ceeee8e3ae4748b0f6c356c5ce97628bd5da8a056b6782c87f7e65/raylib-5.5.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:34771dea34a30fa4657f35b344d5ebf9eb11d9b62b23d9349742db5c5f3992bd", size = 1705555, upload-time = "2025-12-11T15:27:28.888Z" }, + { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, + { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f1/a9bde00b01956bbccc82c9112c8a6a64d50d44d7e4752c04dc59e59bde7e/raylib-5.5.0.4-cp312-cp312-manylinux_2_35_aarch64.whl", hash = "sha256:fb80c638f74a3a59af6a702078da3152a0013b959a225fc76cc6cc8fd0d91e36", size = 2259080, upload-time = "2025-12-11T15:27:41.862Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, + { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ba/fee7e6ae0be850f6581d4084ea97825b7895c8866fa8b2df347d408c8293/raylib-5.5.0.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c318357ce721c62a6848b6d84b26574cd77267e5758cfa2dbc01d4deb2a2b0b8", size = 1211520, upload-time = "2025-12-11T15:28:30.266Z" }, + { url = "https://files.pythonhosted.org/packages/80/a0/847066c6d824f535068112ed362d41c499f9a4aca52b82b74d9dfb1bdfc7/raylib-5.5.0.4-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82a0ea2859d04f3b5b441910881ec48789484463856168fa8f35c7165e11d44c", size = 1433828, upload-time = "2025-12-11T15:28:32.204Z" }, + { url = "https://files.pythonhosted.org/packages/40/c6/a2cfb01d63246602ce49111f08d8716e1c7c2994efe4e14d87450176393c/raylib-5.5.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:871e77547cd3f78d98a47bef491821cd25c879b3b3b79f1973d8fb3f8841cdfb", size = 1572456, upload-time = "2025-12-11T15:28:34.333Z" }, ] [[package]] From c8eed435383147fc18b0627d87a5b9c56ae06ba3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 17:02:39 -0800 Subject: [PATCH 023/685] misc system/hardware/ cleanup (#36949) * misc system/hardware/ cleanup * lil more * pc is kinda base * lil more * lil more * lil more * int * lil more? --- system/hardware/.gitignore | 1 - system/hardware/base.py | 66 ++++++---------- system/hardware/fan_controller.py | 29 ++----- system/hardware/hardwared.py | 13 +--- system/hardware/pc/hardware.h | 1 - system/hardware/pc/hardware.py | 70 +---------------- system/hardware/tests/test_fan_controller.py | 6 +- system/hardware/tici/amplifier.py | 80 +++++++------------- system/hardware/tici/hardware.py | 6 +- system/hardware/tici/tests/test_amplifier.py | 5 +- 10 files changed, 72 insertions(+), 205 deletions(-) delete mode 100644 system/hardware/.gitignore diff --git a/system/hardware/.gitignore b/system/hardware/.gitignore deleted file mode 100644 index 980f09abfa7..00000000000 --- a/system/hardware/.gitignore +++ /dev/null @@ -1 +0,0 @@ -eon/rat diff --git a/system/hardware/base.py b/system/hardware/base.py index 17d0ec16140..05e8eb015f3 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -5,6 +5,7 @@ from cereal import log NetworkType = log.DeviceState.NetworkType +NetworkStrength = log.DeviceState.NetworkStrength class LPAError(RuntimeError): pass @@ -114,68 +115,57 @@ def read_param_file(path, parser, default=0): def booted(self) -> bool: return True - @abstractmethod def reboot(self, reason=None): - pass + print("REBOOT!") - @abstractmethod def uninstall(self): - pass + print("uninstall") - @abstractmethod def get_os_version(self): - pass + return None @abstractmethod def get_device_type(self): pass - @abstractmethod def get_imei(self, slot) -> str: - pass + return "" - @abstractmethod def get_serial(self): - pass + return "" - @abstractmethod def get_network_info(self): - pass + return None - @abstractmethod def get_network_type(self): - pass + return NetworkType.none - @abstractmethod def get_sim_info(self): - pass + return { + 'sim_id': '', + 'mcc_mnc': None, + 'network_type': ["Unknown"], + 'sim_state': ["ABSENT"], + 'data_connected': False + } - @abstractmethod def get_sim_lpa(self) -> LPABase: - pass + raise NotImplementedError("SIM LPA not available") - @abstractmethod def get_network_strength(self, network_type): - pass + return NetworkStrength.unknown def get_network_metered(self, network_type) -> bool: return network_type not in (NetworkType.none, NetworkType.wifi, NetworkType.ethernet) - @staticmethod - def set_bandwidth_limit(upload_speed_kbps: int, download_speed_kbps: int) -> None: - pass - - @abstractmethod def get_current_power_draw(self): - pass + return 0 - @abstractmethod def get_som_power_draw(self): - pass + return 0 - @abstractmethod def shutdown(self): - pass + print("SHUTDOWN!") def get_thermal_config(self): return ThermalConfig() @@ -183,31 +173,24 @@ def get_thermal_config(self): def set_display_power(self, on: bool): pass - @abstractmethod def set_screen_brightness(self, percentage): pass - @abstractmethod def get_screen_brightness(self): - pass + return 0 - @abstractmethod def set_power_save(self, powersave_enabled): pass - @abstractmethod def get_gpu_usage_percent(self): - pass + return 0 def get_modem_version(self): return None - @abstractmethod def get_modem_temperatures(self): - pass + return [] - - @abstractmethod def initialize_hardware(self): pass @@ -217,9 +200,8 @@ def configure_modem(self): def reboot_modem(self): pass - @abstractmethod def get_networks(self): - pass + return None def has_internal_panda(self) -> bool: return False diff --git a/system/hardware/fan_controller.py b/system/hardware/fan_controller.py index 365688429ae..b2140d33d41 100755 --- a/system/hardware/fan_controller.py +++ b/system/hardware/fan_controller.py @@ -1,24 +1,13 @@ #!/usr/bin/env python3 import numpy as np -from abc import ABC, abstractmethod -from openpilot.common.realtime import DT_HW -from openpilot.common.swaglog import cloudlog from openpilot.common.pid import PIDController -class BaseFanController(ABC): - @abstractmethod - def update(self, cur_temp: float, ignition: bool) -> int: - pass - - -class TiciFanController(BaseFanController): - def __init__(self) -> None: - super().__init__() - cloudlog.info("Setting up TICI fan handler") +class FanController: + def __init__(self, rate: int) -> None: self.last_ignition = False - self.controller = PIDController(k_p=0, k_i=4e-3, rate=(1 / DT_HW)) + self.controller = PIDController(k_p=0, k_i=4e-3, rate=rate) def update(self, cur_temp: float, ignition: bool) -> int: self.controller.pos_limit = 100 if ignition else 30 @@ -26,13 +15,9 @@ def update(self, cur_temp: float, ignition: bool) -> int: if ignition != self.last_ignition: self.controller.reset() - - error = cur_temp - 75 - fan_pwr_out = int(self.controller.update( - error=error, - feedforward=np.interp(cur_temp, [60.0, 100.0], [0, 100]) - )) - self.last_ignition = ignition - return fan_pwr_out + return int(self.controller.update( + error=(cur_temp - 75), # temperature setpoint in C + feedforward=np.interp(cur_temp, [60.0, 100.0], [0, 100]) + )) diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index 8ddc4da2f24..aad30f77b2b 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -22,7 +22,7 @@ from openpilot.system.statsd import statlog from openpilot.common.swaglog import cloudlog from openpilot.system.hardware.power_monitoring import PowerMonitoring -from openpilot.system.hardware.fan_controller import TiciFanController +from openpilot.system.hardware.fan_controller import FanController from openpilot.system.version import terms_version, training_version from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID @@ -210,14 +210,13 @@ def hardware_thread(end_event, hw_queue) -> None: HARDWARE.initialize_hardware() thermal_config = HARDWARE.get_thermal_config() - fan_controller = None + fan_controller = FanController(int(1./DT_HW)) while not end_event.is_set(): sm.update(PANDA_STATES_TIMEOUT) pandaStates = sm['pandaStates'] peripheralState = sm['peripheralState'] - peripheral_panda_present = peripheralState.pandaType != log.PandaState.PandaType.unknown # handle requests to cycle system started state if params.get_bool("OnroadCycleRequested"): @@ -234,11 +233,6 @@ def hardware_thread(end_event, hw_queue) -> None: in_car = pandaState.harnessStatus != log.PandaState.HarnessStatus.notConnected - # Setup fan handler on first connect to panda - if fan_controller is None and peripheral_panda_present: - if TICI: - fan_controller = TiciFanController() - elif (time.monotonic() - sm.recv_time['pandaStates']) > DISCONNECT_TIMEOUT: if onroad_conditions["ignition"]: onroad_conditions["ignition"] = False @@ -289,8 +283,7 @@ def hardware_thread(end_event, hw_queue) -> None: all_comp_temp = all_temp_filter.update(max(temp_sources)) msg.deviceState.maxTempC = all_comp_temp - if fan_controller is not None: - msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"]) + msg.deviceState.fanSpeedPercentDesired = fan_controller.update(all_comp_temp, onroad_conditions["ignition"]) is_offroad_for_5_min = (started_ts is None) and ((not started_seen) or (off_ts is None) or (time.monotonic() - off_ts > 60 * 5)) if is_offroad_for_5_min and offroad_comp_temp > OFFROAD_DANGER_TEMP: diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h index 978dd771c8a..71f58b188be 100644 --- a/system/hardware/pc/hardware.h +++ b/system/hardware/pc/hardware.h @@ -6,7 +6,6 @@ class HardwarePC : public HardwareNone { public: - static std::string get_os_version() { return "openpilot for PC"; } static std::string get_name() { return "pc"; } static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; } static bool PC() { return true; } diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index 6c118963906..f3d527429ce 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -1,78 +1,12 @@ -import random - from cereal import log -from openpilot.system.hardware.base import HardwareBase, LPABase +from openpilot.system.hardware.base import HardwareBase NetworkType = log.DeviceState.NetworkType -NetworkStrength = log.DeviceState.NetworkStrength -class Pc(HardwareBase): - def get_os_version(self): - return None +class Pc(HardwareBase): def get_device_type(self): return "pc" - def reboot(self, reason=None): - print("REBOOT!") - - def uninstall(self): - print("uninstall") - - def get_imei(self, slot): - return f"{random.randint(0, 1 << 32):015d}" - - def get_serial(self): - return "cccccccc" - - def get_network_info(self): - return None - def get_network_type(self): return NetworkType.wifi - - def get_sim_info(self): - return { - 'sim_id': '', - 'mcc_mnc': None, - 'network_type': ["Unknown"], - 'sim_state': ["ABSENT"], - 'data_connected': False - } - - def get_sim_lpa(self) -> LPABase: - raise NotImplementedError("SIM LPA not implemented for PC") - - def get_network_strength(self, network_type): - return NetworkStrength.unknown - - def get_current_power_draw(self): - return 0 - - def get_som_power_draw(self): - return 0 - - def shutdown(self): - print("SHUTDOWN!") - - def set_screen_brightness(self, percentage): - pass - - def get_screen_brightness(self): - return 0 - - def set_power_save(self, powersave_enabled): - pass - - def get_gpu_usage_percent(self): - return 0 - - def get_modem_temperatures(self): - return [] - - - def initialize_hardware(self): - pass - - def get_networks(self): - return None diff --git a/system/hardware/tests/test_fan_controller.py b/system/hardware/tests/test_fan_controller.py index 002c1edfda5..ee39a24f874 100644 --- a/system/hardware/tests/test_fan_controller.py +++ b/system/hardware/tests/test_fan_controller.py @@ -1,12 +1,12 @@ import pytest -from openpilot.system.hardware.fan_controller import TiciFanController +from openpilot.system.hardware.fan_controller import FanController -ALL_CONTROLLERS = [TiciFanController] +ALL_CONTROLLERS = [FanController] def patched_controller(mocker, controller_class): mocker.patch("os.system", new=mocker.Mock()) - return controller_class() + return controller_class(2) class TestFanController: def wind_up(self, controller, ignition=True): diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py index bfdcc6ddaf7..d714837bb3e 100755 --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -6,23 +6,8 @@ # https://datasheets.maximintegrated.com/en/ds/MAX98089.pdf AmpConfig = namedtuple('AmpConfig', ['name', 'value', 'register', 'offset', 'mask']) -EQParams = namedtuple('EQParams', ['K', 'k1', 'k2', 'c1', 'c2']) - -def configs_from_eq_params(base, eq_params): - return [ - AmpConfig("K (high)", (eq_params.K >> 8), base, 0, 0xFF), - AmpConfig("K (low)", (eq_params.K & 0xFF), base + 1, 0, 0xFF), - AmpConfig("k1 (high)", (eq_params.k1 >> 8), base + 2, 0, 0xFF), - AmpConfig("k1 (low)", (eq_params.k1 & 0xFF), base + 3, 0, 0xFF), - AmpConfig("k2 (high)", (eq_params.k2 >> 8), base + 4, 0, 0xFF), - AmpConfig("k2 (low)", (eq_params.k2 & 0xFF), base + 5, 0, 0xFF), - AmpConfig("c1 (high)", (eq_params.c1 >> 8), base + 6, 0, 0xFF), - AmpConfig("c1 (low)", (eq_params.c1 & 0xFF), base + 7, 0, 0xFF), - AmpConfig("c2 (high)", (eq_params.c2 >> 8), base + 8, 0, 0xFF), - AmpConfig("c2 (low)", (eq_params.c2 & 0xFF), base + 9, 0, 0xFF), - ] - -BASE_CONFIG = [ + +CONFIG = [ AmpConfig("MCLK prescaler", 0b01, 0x10, 4, 0b00110000), AmpConfig("PM: enable speakers", 0b11, 0x4D, 4, 0b00110000), AmpConfig("PM: enable DACs", 0b11, 0x4D, 0, 0b00000011), @@ -58,34 +43,30 @@ def configs_from_eq_params(base, eq_params): AmpConfig("Enhanced volume smoothing disabled", 0b0, 0x49, 7, 0b10000000), AmpConfig("Volume adjustment smoothing disabled", 0b0, 0x49, 6, 0b01000000), AmpConfig("Zero-crossing detection disabled", 0b0, 0x49, 5, 0b00100000), -] -CONFIGS = { - "tizi": [ - AmpConfig("Left speaker output from left DAC", 0b1, 0x2B, 0, 0b11111111), - AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111), - AmpConfig("Left Speaker Mixer Gain", 0b00, 0x2D, 0, 0b00000011), - AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100), - AmpConfig("Left speaker output volume", 0x17, 0x3D, 0, 0b00011111), - AmpConfig("Right speaker output volume", 0x17, 0x3E, 0, 0b00011111), - - AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010), - AmpConfig("DAI2: DC blocking", 0b0, 0x20, 0, 0b00000001), - AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000), - AmpConfig("DAI2 EQ attenuation", 0x2, 0x32, 0, 0b00001111), - AmpConfig("Excursion limiter upper corner freq", 0b001, 0x41, 4, 0b01110000), - AmpConfig("Excursion limiter threshold", 0b100, 0x42, 0, 0b00001111), - AmpConfig("Distortion limit (THDCLP)", 0x0, 0x46, 4, 0b11110000), - AmpConfig("Distortion limiter release time constant", 0b1, 0x46, 0, 0b00000001), - AmpConfig("Left DAC input mixer: DAI1 left", 0b0, 0x22, 7, 0b10000000), - AmpConfig("Left DAC input mixer: DAI1 right", 0b0, 0x22, 6, 0b01000000), - AmpConfig("Left DAC input mixer: DAI2 left", 0b1, 0x22, 5, 0b00100000), - AmpConfig("Left DAC input mixer: DAI2 right", 0b0, 0x22, 4, 0b00010000), - AmpConfig("Right DAC input mixer: DAI2 left", 0b0, 0x22, 1, 0b00000010), - AmpConfig("Right DAC input mixer: DAI2 right", 0b1, 0x22, 0, 0b00000001), - AmpConfig("Volume adjustment smoothing disabled", 0b1, 0x49, 6, 0b01000000), - ], -} + AmpConfig("Left speaker output from left DAC", 0b1, 0x2B, 0, 0b11111111), + AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111), + AmpConfig("Left Speaker Mixer Gain", 0b00, 0x2D, 0, 0b00000011), + AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100), + AmpConfig("Left speaker output volume", 0x17, 0x3D, 0, 0b00011111), + AmpConfig("Right speaker output volume", 0x17, 0x3E, 0, 0b00011111), + + AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010), + AmpConfig("DAI2: DC blocking", 0b0, 0x20, 0, 0b00000001), + AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000), + AmpConfig("DAI2 EQ attenuation", 0x2, 0x32, 0, 0b00001111), + AmpConfig("Excursion limiter upper corner freq", 0b001, 0x41, 4, 0b01110000), + AmpConfig("Excursion limiter threshold", 0b100, 0x42, 0, 0b00001111), + AmpConfig("Distortion limit (THDCLP)", 0x0, 0x46, 4, 0b11110000), + AmpConfig("Distortion limiter release time constant", 0b1, 0x46, 0, 0b00000001), + AmpConfig("Left DAC input mixer: DAI1 left", 0b0, 0x22, 7, 0b10000000), + AmpConfig("Left DAC input mixer: DAI1 right", 0b0, 0x22, 6, 0b01000000), + AmpConfig("Left DAC input mixer: DAI2 left", 0b1, 0x22, 5, 0b00100000), + AmpConfig("Left DAC input mixer: DAI2 right", 0b0, 0x22, 4, 0b00010000), + AmpConfig("Right DAC input mixer: DAI2 left", 0b0, 0x22, 1, 0b00000010), + AmpConfig("Right DAC input mixer: DAI2 right", 0b1, 0x22, 0, 0b00000001), + AmpConfig("Volume adjustment smoothing disabled", 0b1, 0x49, 6, 0b01000000), +] class Amplifier: AMP_I2C_BUS = 0 @@ -127,20 +108,15 @@ def set_configs(self, configs: list[AmpConfig]) -> bool: def set_global_shutdown(self, amp_disabled: bool) -> bool: return self.set_configs([self._get_shutdown_config(amp_disabled), ]) - def initialize_configuration(self, model: str) -> bool: + def initialize_configuration(self) -> bool: cfgs = [ self._get_shutdown_config(True), - *BASE_CONFIG, - *CONFIGS[model], + *CONFIG, self._get_shutdown_config(False), ] return self.set_configs(cfgs) if __name__ == "__main__": - with open("/sys/firmware/devicetree/base/model") as f: - model = f.read().strip('\x00') - model = model.split('comma ')[-1] - amp = Amplifier() - amp.initialize_configuration(model) + amp.initialize_configuration() diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 9791f15f34b..3cdb336086a 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -125,7 +125,7 @@ def get_current(self): return int(f.read()) def set_ir_power(self, percent: int): - if self.get_device_type() in ("tici", "tizi"): + if self.get_device_type() == "tizi": return value = int((percent / 100) * 300) @@ -369,7 +369,7 @@ def set_power_save(self, powersave_enabled): if self.amplifier is not None: self.amplifier.set_global_shutdown(amp_disabled=powersave_enabled) if not powersave_enabled: - self.amplifier.initialize_configuration(self.get_device_type()) + self.amplifier.initialize_configuration() # *** CPU config *** @@ -404,7 +404,7 @@ def get_gpu_usage_percent(self): def initialize_hardware(self): if self.amplifier is not None: - self.amplifier.initialize_configuration(self.get_device_type()) + self.amplifier.initialize_configuration() # Allow hardwared to write engagement status to kmsg os.system("sudo chmod a+w /dev/kmsg") diff --git a/system/hardware/tici/tests/test_amplifier.py b/system/hardware/tici/tests/test_amplifier.py index 3f75436db18..9ce00c3ff26 100644 --- a/system/hardware/tici/tests/test_amplifier.py +++ b/system/hardware/tici/tests/test_amplifier.py @@ -5,7 +5,6 @@ from panda import Panda from openpilot.system.hardware import TICI, HARDWARE -from openpilot.system.hardware.tici.hardware import Tici from openpilot.system.hardware.tici.amplifier import Amplifier @@ -39,7 +38,7 @@ def _check_for_i2c_errors(self, expected): def test_init(self): amp = Amplifier(debug=True) - r = amp.initialize_configuration(Tici().get_device_type()) + r = amp.initialize_configuration() assert r assert self._check_for_i2c_errors(False) @@ -61,7 +60,7 @@ def test_init_while_siren_play(self): time.sleep(random.randint(0, 5)) amp = Amplifier(debug=True) - r = amp.initialize_configuration(Tici().get_device_type()) + r = amp.initialize_configuration() assert r if self._check_for_i2c_errors(True): From a8cfa2e2feaf63fde0f34d6e036c0ad5498d02eb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 21 Dec 2025 17:22:57 -0800 Subject: [PATCH 024/685] bump msgq (#36950) --- msgq_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msgq_repo b/msgq_repo index 6abe47bc98b..f3f440102ef 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 6abe47bc98b83338b6ea04a87a6b2b5c65d09630 +Subproject commit f3f440102ef0a22e0ab1c7400b8c8a98eb6275c5 From 3f1f7ad89c785070ffac84a3e19cd6d07f16859a Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 24 Dec 2025 09:04:34 -0800 Subject: [PATCH 025/685] feat(esim): remove bootstrap (#36954) feat: remove bootstrap --- system/hardware/base.py | 4 ---- system/hardware/esim.py | 27 +-------------------------- system/hardware/tici/esim.py | 29 ----------------------------- 3 files changed, 1 insertion(+), 59 deletions(-) diff --git a/system/hardware/base.py b/system/hardware/base.py index 05e8eb015f3..c12c0758f50 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -66,10 +66,6 @@ def get_msg(self): return ret class LPABase(ABC): - @abstractmethod - def bootstrap(self) -> None: - pass - @abstractmethod def list_profiles(self) -> list[Profile]: pass diff --git a/system/hardware/esim.py b/system/hardware/esim.py index 909ad41e031..58ead6593f6 100755 --- a/system/hardware/esim.py +++ b/system/hardware/esim.py @@ -3,32 +3,10 @@ import argparse import time from openpilot.system.hardware import HARDWARE -from openpilot.system.hardware.base import LPABase - - -def bootstrap(lpa: LPABase) -> None: - print('┌──────────────────────────────────────────────────────────────────────────────┐') - print('│ WARNING, PLEASE READ BEFORE PROCEEDING │') - print('│ │') - print('│ this is an irreversible operation that will remove the comma-provisioned │') - print('│ profile. │') - print('│ │') - print('│ after this operation, you must purchase a new eSIM from comma in order to │') - print('│ use the comma prime subscription again. │') - print('└──────────────────────────────────────────────────────────────────────────────┘') - print() - for severity in ('sure', '100% sure'): - print(f'are you {severity} you want to proceed? (y/N) ', end='') - confirm = input() - if confirm != 'y': - print('aborting') - exit(0) - lpa.bootstrap() if __name__ == '__main__': parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') - parser.add_argument('--bootstrap', action='store_true', help='bootstrap the eUICC (required before downloading profiles)') parser.add_argument('--backend', choices=['qmi', 'at'], default='qmi', help='use the specified backend, defaults to qmi') parser.add_argument('--switch', metavar='iccid', help='switch to profile') parser.add_argument('--delete', metavar='iccid', help='delete profile (warning: this cannot be undone)') @@ -38,10 +16,7 @@ def bootstrap(lpa: LPABase) -> None: mutated = False lpa = HARDWARE.get_sim_lpa() - if args.bootstrap: - bootstrap(lpa) - mutated = True - elif args.switch: + if args.switch: lpa.switch_profile(args.switch) mutated = True elif args.delete: diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py index 391ba45531b..b489286f50b 100644 --- a/system/hardware/tici/esim.py +++ b/system/hardware/tici/esim.py @@ -40,7 +40,6 @@ def delete_profile(self, iccid: str) -> None: self._process_notifications() def download_profile(self, qr: str, nickname: str | None = None) -> None: - self._check_bootstrapped() msgs = self._invoke('profile', 'download', '-a', qr) self._validate_successful(msgs) new_profile = next((m for m in msgs if m['payload']['message'] == 'es8p_meatadata_parse'), None) @@ -55,7 +54,6 @@ def nickname_profile(self, iccid: str, nickname: str) -> None: self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname)) def switch_profile(self, iccid: str) -> None: - self._check_bootstrapped() self._validate_profile_exists(iccid) latest = self.get_active_profile() if latest and latest.iccid == iccid: @@ -63,33 +61,6 @@ def switch_profile(self, iccid: str) -> None: self._validate_successful(self._invoke('profile', 'enable', iccid)) self._process_notifications() - def bootstrap(self) -> None: - """ - find all comma-provisioned profiles and delete them. they conflict with user-provisioned profiles - and must be deleted. - - **note**: this is a **very** destructive operation. you **must** purchase a new comma SIM in order - to use comma prime again. - """ - if self._is_bootstrapped(): - return - - for p in self.list_profiles(): - if self.is_comma_profile(p.iccid): - self._disable_profile(p.iccid) - self.delete_profile(p.iccid) - - def _disable_profile(self, iccid: str) -> None: - self._validate_successful(self._invoke('profile', 'disable', iccid)) - self._process_notifications() - - def _check_bootstrapped(self) -> None: - assert self._is_bootstrapped(), 'eUICC is not bootstrapped, please bootstrap before performing this operation' - - def _is_bootstrapped(self) -> bool: - """ check if any comma provisioned profiles are on the eUICC """ - return not any(self.is_comma_profile(iccid) for iccid in (p.iccid for p in self.list_profiles())) - def _invoke(self, *cmd: str): proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) try: From 368947c88cb7556089312008c1322f7c12e4e04e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 27 Dec 2025 16:49:30 -0800 Subject: [PATCH 026/685] msgq on macOS (#36958) * msgq on macOS * clean that up --- common/prefix.h | 6 +++++- common/prefix.py | 4 +++- msgq_repo | 2 +- tools/install_python_dependencies.sh | 2 -- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/common/prefix.h b/common/prefix.h index 7d0265c2599..30ee18f6375 100644 --- a/common/prefix.h +++ b/common/prefix.h @@ -13,7 +13,11 @@ class OpenpilotPrefix { if (prefix.empty()) { prefix = util::random_string(15); } - msgq_path = Path::shm_path() + "/msgq_" + prefix; +#ifdef __APPLE__ + msgq_path = "/tmp/msgq_" + prefix; +#else + msgq_path = "/dev/shm/msgq_" + prefix; +#endif bool ret = util::create_directories(msgq_path, 0777); assert(ret); setenv("OPENPILOT_PREFIX", prefix.c_str(), 1); diff --git a/common/prefix.py b/common/prefix.py index b19ce1472b1..86f69671d83 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -1,4 +1,5 @@ import os +import platform import shutil import uuid @@ -11,7 +12,8 @@ class OpenpilotPrefix: def __init__(self, prefix: str = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15]) - self.msgq_path = os.path.join(Paths.shm_path(), "msgq_" + self.prefix) + shm_path = "/tmp" if platform.system() == "Darwin" else "/dev/shm" + self.msgq_path = os.path.join(shm_path, "msgq_" + self.prefix) self.create_dirs_on_enter = create_dirs_on_enter self.clean_dirs_on_exit = clean_dirs_on_exit self.shared_download_cache = shared_download_cache diff --git a/msgq_repo b/msgq_repo index f3f440102ef..349bf449b73 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit f3f440102ef0a22e0ab1c7400b8c8a98eb6275c5 +Subproject commit 349bf449b7329527162598de357bfecf232abc3f diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh index c2db249cf23..4454845fcd0 100755 --- a/tools/install_python_dependencies.sh +++ b/tools/install_python_dependencies.sh @@ -25,7 +25,5 @@ source .venv/bin/activate if [[ "$(uname)" == 'Darwin' ]]; then touch "$ROOT"/.env - echo "# msgq doesn't work on mac" >> "$ROOT"/.env - echo "export ZMQ=1" >> "$ROOT"/.env echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> "$ROOT"/.env fi From 85cdb2ed9abc5153aa0dd377c83a84126cf80334 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 27 Dec 2025 23:15:18 -0800 Subject: [PATCH 027/685] fix(url_file): ensure seek position is always an integer (#36960) --- tools/lib/url_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index c791444f74d..c7ccab1ae38 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -164,7 +164,7 @@ def get_multi_range(self, ranges: list[tuple[int, int]]) -> list[bytes]: return parts def seek(self, pos: int) -> None: - self._pos = pos + self._pos = int(pos) @property def name(self) -> str: From ea01a53711a01d4d88c1e4e8ae9cb7e93b7a070e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 10:42:49 -0800 Subject: [PATCH 028/685] switch from mypy to ty (#36961) --- common/git.py | 12 +-- common/prefix.py | 2 +- pyproject.toml | 78 ++++++++++--------- scripts/lint/lint.sh | 12 +-- selfdrive/debug/cycle_alerts.py | 6 +- selfdrive/debug/fingerprint_from_route.py | 11 +-- selfdrive/locationd/calibrationd.py | 2 +- selfdrive/locationd/helpers.py | 2 +- selfdrive/selfdrived/alertmanager.py | 2 +- selfdrive/test/fuzzy_generation.py | 2 +- selfdrive/test/process_replay/migration.py | 3 +- .../test/process_replay/process_replay.py | 6 +- selfdrive/test/process_replay/regen.py | 2 +- selfdrive/test/update_ci_routes.py | 2 +- selfdrive/ui/layouts/home.py | 2 +- selfdrive/ui/mici/widgets/button.py | 16 ++-- selfdrive/ui/mici/widgets/dialog.py | 4 +- selfdrive/ui/translations/auto_translate.py | 2 +- system/athena/athenad.py | 4 +- system/athena/tests/test_athenad.py | 2 +- system/loggerd/tests/loggerd_tests_common.py | 4 +- system/loggerd/tests/test_uploader.py | 2 +- system/loggerd/uploader.py | 2 +- system/manager/process.py | 2 +- system/ui/mici_updater.py | 17 ++-- system/ui/tici_updater.py | 17 ++-- system/ui/widgets/__init__.py | 6 +- system/ui/widgets/label.py | 4 +- system/ui/widgets/list_view.py | 11 +-- system/updated/casync/casync.py | 4 +- system/updated/updated.py | 2 +- system/webrtc/device/audio.py | 4 +- tools/jotpluggler/data.py | 2 +- tools/jotpluggler/views.py | 2 +- tools/lib/logreader.py | 5 +- tools/lib/vidindex.py | 2 +- uv.lock | 64 +++++++-------- 37 files changed, 160 insertions(+), 162 deletions(-) diff --git a/common/git.py b/common/git.py index 2296fa7088b..6b662e57191 100644 --- a/common/git.py +++ b/common/git.py @@ -4,27 +4,27 @@ @cache -def get_commit(cwd: str = None, branch: str = "HEAD") -> str: +def get_commit(cwd: str | None = None, branch: str = "HEAD") -> str: return run_cmd_default(["git", "rev-parse", branch], cwd=cwd) @cache -def get_commit_date(cwd: str = None, commit: str = "HEAD") -> str: +def get_commit_date(cwd: str | None = None, commit: str = "HEAD") -> str: return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit], cwd=cwd) @cache -def get_short_branch(cwd: str = None) -> str: +def get_short_branch(cwd: str | None = None) -> str: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], cwd=cwd) @cache -def get_branch(cwd: str = None) -> str: +def get_branch(cwd: str | None = None) -> str: return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], cwd=cwd) @cache -def get_origin(cwd: str = None) -> str: +def get_origin(cwd: str | None = None) -> str: try: local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"], cwd=cwd) tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"], cwd=cwd) @@ -34,7 +34,7 @@ def get_origin(cwd: str = None) -> str: @cache -def get_normalized_origin(cwd: str = None) -> str: +def get_normalized_origin(cwd: str | None = None) -> str: return get_origin(cwd) \ .replace("git@", "", 1) \ .replace(".git", "", 1) \ diff --git a/common/prefix.py b/common/prefix.py index 86f69671d83..d0a5f926286 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -10,7 +10,7 @@ from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT class OpenpilotPrefix: - def __init__(self, prefix: str = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): + def __init__(self, prefix: str | None = None, create_dirs_on_enter: bool = True, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15]) shm_path = "/tmp" if platform.system() == "Darwin" else "/dev/shm" self.msgq_path = os.path.join(shm_path, "msgq_" + self.prefix) diff --git a/pyproject.toml b/pyproject.toml index fc80488c693..bb4ac0e8d8b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ docs = [ testing = [ "coverage", "hypothesis ==6.47.*", - "mypy", + "ty", "pytest", "pytest-cpp", "pytest-subtests", @@ -180,42 +180,6 @@ ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,w builtin = "clear,rare,informal,code,names,en-GB_to_en-US" skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" -[tool.mypy] -python_version = "3.11" -exclude = [ - "cereal/", - "msgq/", - "msgq_repo/", - "opendbc/", - "opendbc_repo/", - "panda/", - "rednose/", - "rednose_repo/", - "tinygrad/", - "tinygrad_repo/", - "teleoprtc/", - "teleoprtc_repo/", - "third_party/", -] - -# third-party packages -ignore_missing_imports=true - -# helpful warnings -warn_redundant_casts=true -warn_unreachable=true -warn_unused_ignores=true - -# restrict dynamic typing -warn_return_any=true - -# allow implicit optionals for default args -implicit_optional = true - -local_partial_types=true -explicit_package_bases=true -disable_error_code = "annotation-unchecked" - # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] indent-width = 2 @@ -274,3 +238,43 @@ lint.flake8-implicit-str-concat.allow-multiline = false [tool.ruff.format] quote-style = "preserve" + +[tool.ty.src] +exclude = [ + "cereal/", + "msgq/", + "msgq_repo/", + "opendbc/", + "opendbc_repo/", + "panda/", + "rednose/", + "rednose_repo/", + "tinygrad/", + "tinygrad_repo/", + "teleoprtc/", + "teleoprtc_repo/", + "third_party/", +] + +[tool.ty.rules] +# Ignore unresolved imports for Cython-compiled modules (.pyx) +unresolved-import = "ignore" +# Ignore unresolved attributes - many from capnp and Cython modules +unresolved-attribute = "ignore" +# Ignore invalid method overrides - signature variance issues +invalid-method-override = "ignore" +# Ignore possibly-missing-attribute - too many false positives +possibly-missing-attribute = "ignore" +# Ignore invalid assignment - often intentional monkey-patching +invalid-assignment = "ignore" +# Ignore no-matching-overload - numpy/ctypes overload matching issues +no-matching-overload = "ignore" +# Ignore invalid-argument-type - many false positives from raylib, ctypes, numpy +invalid-argument-type = "ignore" +# Ignore call-non-callable - false positives from dynamic types +call-non-callable = "ignore" +# Ignore unsupported-operator - false positives from dynamic types +unsupported-operator = "ignore" +# Ignore non-subscriptable - false positives from dynamic types +non-subscriptable = "ignore" +# not-iterable errors are now fixed diff --git a/scripts/lint/lint.sh b/scripts/lint/lint.sh index 578c63cd189..5581171e8fe 100755 --- a/scripts/lint/lint.sh +++ b/scripts/lint/lint.sh @@ -55,7 +55,7 @@ function run_tests() { run "check_nomerge_comments" $DIR/check_nomerge_comments.sh $ALL_FILES if [[ -z "$FAST" ]]; then - run "mypy" mypy $PYTHON_FILES + run "ty" ty check run "codespell" codespell $ALL_FILES fi @@ -69,7 +69,7 @@ function help() { echo "" echo -e "${BOLD}${UNDERLINE}Tests:${NC}" echo -e " ${BOLD}ruff${NC}" - echo -e " ${BOLD}mypy${NC}" + echo -e " ${BOLD}ty${NC}" echo -e " ${BOLD}codespell${NC}" echo -e " ${BOLD}check_added_large_files${NC}" echo -e " ${BOLD}check_shebang_scripts_are_executable${NC}" @@ -81,11 +81,11 @@ function help() { echo " Specify tests to skip separated by spaces" echo "" echo -e "${BOLD}${UNDERLINE}Examples:${NC}" - echo " op lint mypy ruff" - echo " Only run the mypy and ruff tests" + echo " op lint ty ruff" + echo " Only run the ty and ruff tests" echo "" - echo " op lint --skip mypy ruff" - echo " Skip the mypy and ruff tests" + echo " op lint --skip ty ruff" + echo " Skip the ty and ruff tests" echo "" echo " op lint" echo " Run all the tests" diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 11e75a7a8e3..00fa33ac63a 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -99,8 +99,7 @@ def cycle_alerts(duration=200, is_metric=False): alert = AM.process_alerts(frame, []) print(alert) for _ in range(duration): - dat = messaging.new_message() - dat.init('selfdriveState') + dat = messaging.new_message('selfdriveState') dat.selfdriveState.enabled = False if alert: @@ -112,8 +111,7 @@ def cycle_alerts(duration=200, is_metric=False): dat.selfdriveState.alertSound = alert.audible_alert pm.send('selfdriveState', dat) - dat = messaging.new_message() - dat.init('deviceState') + dat = messaging.new_message('deviceState') dat.deviceState.started = True pm.send('deviceState', dat) diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index 179ff4c8383..5fd46f5b767 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -28,11 +28,12 @@ def get_fingerprint(lr): # TODO: also print the fw fingerprint merged with the existing ones # show FW fingerprint - print("\nFW fingerprint:\n") - for f in fw: - print(f" (Ecu.{f.ecu}, {hex(f.address)}, {None if f.subAddress == 0 else f.subAddress}): [") - print(f" {f.fwVersion},") - print(" ],") + if fw: + print("\nFW fingerprint:\n") + for f in fw: + print(f" (Ecu.{f.ecu}, {hex(f.address)}, {None if f.subAddress == 0 else f.subAddress}): [") + print(f" {f.fwVersion},") + print(" ],") print() print(f"VIN: {vin}") diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 03c044982e3..3843cf37f30 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -92,7 +92,7 @@ def reset(self, rpy_init: np.ndarray = RPY_INIT, valid_blocks: int = 0, wide_from_device_euler_init: np.ndarray = WIDE_FROM_DEVICE_EULER_INIT, height_init: np.ndarray = HEIGHT_INIT, - smooth_from: np.ndarray = None) -> None: + smooth_from: np.ndarray | None = None) -> None: if not np.isfinite(rpy_init).all(): self.rpy = RPY_INIT.copy() else: diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py index 2a3ac8b8610..73c4d8bf352 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -94,7 +94,7 @@ def is_calculable(self) -> bool: def add_point(self, x: float, y: float) -> None: raise NotImplementedError - def get_points(self, num_points: int = None) -> Any: + def get_points(self, num_points: int | None = None) -> Any: points = np.vstack([x.arr for x in self.buckets.values()]) if num_points is None: return points diff --git a/selfdrive/selfdrived/alertmanager.py b/selfdrive/selfdrived/alertmanager.py index 251d32ba9a2..385c276a948 100644 --- a/selfdrive/selfdrived/alertmanager.py +++ b/selfdrive/selfdrived/alertmanager.py @@ -13,7 +13,7 @@ OFFROAD_ALERTS = json.load(f) -def set_offroad_alert(alert: str, show_alert: bool, extra_text: str = None) -> None: +def set_offroad_alert(alert: str, show_alert: bool, extra_text: str | None = None) -> None: if show_alert: a = copy.copy(OFFROAD_ALERTS[alert]) a['extra'] = extra_text or '' diff --git a/selfdrive/test/fuzzy_generation.py b/selfdrive/test/fuzzy_generation.py index 94eb0dfaa62..131dab47b23 100644 --- a/selfdrive/test/fuzzy_generation.py +++ b/selfdrive/test/fuzzy_generation.py @@ -44,7 +44,7 @@ def rec(field_type: capnp.lib.capnp._DynamicStructReader) -> st.SearchStrategy: except capnp.lib.capnp.KjException: return self.generate_struct(field.schema) - def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str = None) -> st.SearchStrategy[dict[str, Any]]: + def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str | None = None) -> st.SearchStrategy[dict[str, Any]]: single_fill: tuple[str, ...] = (event,) if event else (self.draw(st.sampled_from(schema.union_fields)),) if schema.union_fields else () fields_to_generate = schema.non_union_fields + single_fill return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate if not field.endswith('DEPRECATED')}) diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 33b363cfd94..232722d1b1e 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -1,5 +1,6 @@ from collections import defaultdict from collections.abc import Callable +from typing import cast import capnp import functools import traceback @@ -67,7 +68,7 @@ def migrate(lr: LogIterable, migration_funcs: list[MigrationFunc]): if migration.product in grouped: # skip if product already exists continue - sorted_indices = sorted(ii for i in migration.inputs for ii in grouped[i]) + sorted_indices = sorted(ii for i in cast(list[str], migration.inputs) for ii in grouped.get(i, [])) msg_gen = [(i, lr[i]) for i in sorted_indices] r_ops, a_ops, d_ops = migration(msg_gen) replace_ops.extend(r_ops) diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 1144b7955e7..8af72e5f4e7 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -610,9 +610,9 @@ def replay_process_with_name(name: str | Iterable[str], lr: LogIterable, *args, def replay_process( - cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] = None, - fingerprint: str = None, return_all_logs: bool = False, custom_params: dict[str, Any] = None, - captured_output_store: dict[str, dict[str, str]] = None, disable_progress: bool = False + cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, FrameReader] | None = None, + fingerprint: str | None = None, return_all_logs: bool = False, custom_params: dict[str, Any] | None = None, + captured_output_store: dict[str, dict[str, str]] | None = None, disable_progress: bool = False ) -> list[capnp._DynamicStructReader]: if isinstance(cfg, Iterable): cfgs = list(cfg) diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index ec35a5c3acf..c501a4b2508 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -16,7 +16,7 @@ def regen_segment( - lr: LogIterable, frs: dict[str, Any] = None, + lr: LogIterable, frs: dict[str, Any] | None = None, processes: Iterable[ProcessConfig] = CONFIGS, disable_tqdm: bool = False ) -> list[capnp._DynamicStructReader]: all_msgs = sorted(lr, key=lambda m: m.logMonoTime) diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 2bf06b48603..54e1c88718f 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -19,7 +19,7 @@ DEST = OpenpilotCIContainer -def upload_route(path: str, exclude_patterns: Iterable[str] = None) -> None: +def upload_route(path: str, exclude_patterns: Iterable[str] | None = None) -> None: if exclude_patterns is None: exclude_patterns = [r'dcamera\.hevc'] diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index cd6ae600ef3..a8404a20c3b 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -39,7 +39,7 @@ def __init__(self): self.current_state = HomeLayoutState.HOME self.last_refresh = 0 - self.settings_callback: callable | None = None + self.settings_callback: Callable[[], None] | None = None self.update_available = False self.alert_count = 0 diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index be08e0fee37..82310577b0a 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -71,7 +71,7 @@ def _render(self, _): class BigCircleToggle(BigCircleButton): - def __init__(self, icon: str, toggle_callback: Callable = None): + def __init__(self, icon: str, toggle_callback: Callable | None = None): super().__init__(icon, False) self._toggle_callback = toggle_callback @@ -251,7 +251,7 @@ def _render(self, _): class BigToggle(BigButton): - def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable = None): + def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable | None = None): super().__init__(text, value, "") self._checked = initial_state self._toggle_callback = toggle_callback @@ -288,8 +288,8 @@ def _render(self, _): class BigMultiToggle(BigToggle): - def __init__(self, text: str, options: list[str], toggle_callback: Callable = None, - select_callback: Callable = None): + def __init__(self, text: str, options: list[str], toggle_callback: Callable | None = None, + select_callback: Callable | None = None): super().__init__(text, "", toggle_callback=toggle_callback) assert len(options) > 0 self._options = options @@ -327,8 +327,8 @@ def _render(self, _): class BigMultiParamToggle(BigMultiToggle): - def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable = None, - select_callback: Callable = None): + def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None, + select_callback: Callable | None = None): super().__init__(text, options, toggle_callback, select_callback) self._param = param @@ -345,7 +345,7 @@ def _handle_mouse_release(self, mouse_pos: MousePos): class BigParamControl(BigToggle): - def __init__(self, text: str, param: str, toggle_callback: Callable = None): + def __init__(self, text: str, param: str, toggle_callback: Callable | None = None): super().__init__(text, "", toggle_callback=toggle_callback) self.param = param self.params = Params() @@ -361,7 +361,7 @@ def refresh(self): # TODO: param control base class class BigCircleParamControl(BigCircleToggle): - def __init__(self, icon: str, param: str, toggle_callback: Callable = None): + def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None): super().__init__(icon, toggle_callback) self._param = param self.params = Params() diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 34760925a13..abd558aa8db 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -137,7 +137,7 @@ def __init__(self, hint: str, default_text: str = "", minimum_length: int = 1, - confirm_callback: Callable[[str], None] = None): + confirm_callback: Callable[[str], None] | None = None): super().__init__(None, None) self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)), font_weight=FontWeight.MEDIUM) @@ -317,7 +317,7 @@ class BigMultiOptionDialog(BigDialogBase): BACK_TOUCH_AREA_PERCENTAGE = 0.1 def __init__(self, options: list[str], default: str | None, - right_btn: str | None = 'check', right_btn_callback: Callable[[], None] = None): + right_btn: str | None = 'check', right_btn_callback: Callable[[], None] | None = None): super().__init__(right_btn, right_btn_callback=right_btn_callback) self._options = options if default is not None: diff --git a/selfdrive/ui/translations/auto_translate.py b/selfdrive/ui/translations/auto_translate.py index 6251e033971..9354790f94f 100755 --- a/selfdrive/ui/translations/auto_translate.py +++ b/selfdrive/ui/translations/auto_translate.py @@ -18,7 +18,7 @@ "The following sentence or word is in the GUI of a software called openpilot, translate it accordingly." -def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]: +def get_language_files(languages: list[str] | None = None) -> dict[str, pathlib.Path]: files = {} with open(TRANSLATIONS_LANGUAGES) as fp: diff --git a/system/athena/athenad.py b/system/athena/athenad.py index 3b71a9c31f5..b52ef21ba63 100755 --- a/system/athena/athenad.py +++ b/system/athena/athenad.py @@ -314,7 +314,7 @@ def upload_handler(end_event: threading.Event) -> None: cloudlog.exception("athena.upload_handler.exception") -def _do_upload(upload_item: UploadItem, callback: Callable = None) -> requests.Response: +def _do_upload(upload_item: UploadItem, callback: Callable | None = None) -> requests.Response: path = upload_item.path compress = False @@ -805,7 +805,7 @@ def backoff(retries: int) -> int: return random.randrange(0, min(128, int(2 ** retries))) -def main(exit_event: threading.Event = None): +def main(exit_event: threading.Event | None = None): try: set_core_affinity([0, 1, 2, 3]) except Exception: diff --git a/system/athena/tests/test_athenad.py b/system/athena/tests/test_athenad.py index 99ac3b1c6b6..5b3e0b41f48 100644 --- a/system/athena/tests/test_athenad.py +++ b/system/athena/tests/test_athenad.py @@ -97,7 +97,7 @@ def _wait_for_upload(): break @staticmethod - def _create_file(file: str, parent: str = None, data: bytes = b'') -> str: + def _create_file(file: str, parent: str | None = None, data: bytes = b'') -> str: fn = os.path.join(Paths.log_root() if parent is None else parent, file) os.makedirs(os.path.dirname(fn), exist_ok=True) with open(fn, 'wb') as f: diff --git a/system/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py index 87c3da65c2f..8bf609ae8d9 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -10,7 +10,7 @@ from openpilot.system.loggerd.xattr_cache import setxattr -def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: bytes = None) -> None: +def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: bytes | None = None) -> None: file_path.parent.mkdir(parents=True, exist_ok=True) if lock: @@ -80,7 +80,7 @@ def setup_method(self): self.params.put("DongleId", "0000000000000000") def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False, - upload_xattr: bytes = None, preserve_xattr: bytes = None) -> Path: + upload_xattr: bytes | None = None, preserve_xattr: bytes | None = None) -> Path: file_path = Path(Paths.log_root()) / f_dir / fn create_random_file(file_path, size_mb, lock, upload_xattr) diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index 961a8aa36f8..562bc068eb8 100644 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -50,7 +50,7 @@ def join_thread(self): self.end_event.set() self.up_thread.join() - def gen_files(self, lock=False, xattr: bytes = None, boot=True) -> list[Path]: + def gen_files(self, lock=False, xattr: bytes | None = None, boot=True) -> list[Path]: f_paths = [] for t in ["qlog", "rlog", "dcamera.hevc", "fcamera.hevc"]: f_paths.append(self.make_file_with_data(self.seg_dir, t, 1, lock=lock, upload_xattr=xattr)) diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 5b6234e1d55..8ac38b6df6a 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -226,7 +226,7 @@ def step(self, network_type: int, metered: bool) -> bool | None: return self.upload(name, key, fn, network_type, metered) -def main(exit_event: threading.Event = None) -> None: +def main(exit_event: threading.Event | None = None) -> None: if exit_event is None: exit_event = threading.Event() diff --git a/system/manager/process.py b/system/manager/process.py index 1e24198267b..36e1ba77b28 100644 --- a/system/manager/process.py +++ b/system/manager/process.py @@ -81,7 +81,7 @@ def restart(self) -> None: self.stop(sig=signal.SIGKILL) self.start() - def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals = None) -> int | None: + def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals | None = None) -> int | None: if self.proc is None: return None diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 2ae2f7cc192..7ebb4262ff9 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -102,14 +102,15 @@ def _run_update_process(self): self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) - for line in self.process.stdout: - parts = line.strip().split(":") - if len(parts) == 2: - self.progress_text = parts[0].lower() - try: - self.progress_value = int(float(parts[1])) - except ValueError: - pass + if self.process.stdout is not None: + for line in self.process.stdout: + parts = line.strip().split(":") + if len(parts) == 2: + self.progress_text = parts[0].lower() + try: + self.progress_value = int(float(parts[1])) + except ValueError: + pass exit_code = self.process.wait() if exit_code == 0: diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index 2e1a8687e1a..ebf4b3bec39 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -71,14 +71,15 @@ def _run_update_process(self): self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) - for line in self.process.stdout: - parts = line.strip().split(":") - if len(parts) == 2: - self.progress_text = parts[0] - try: - self.progress_value = int(float(parts[1])) - except ValueError: - pass + if self.process.stdout is not None: + for line in self.process.stdout: + parts = line.strip().split(":") + if len(parts) == 2: + self.progress_text = parts[0] + try: + self.progress_value = int(float(parts[1])) + except ValueError: + pass exit_code = self.process.wait() if exit_code == 0: diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 8a46a3e43df..4f3c977d2e3 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import abc import pyray as rl from enum import IntEnum @@ -91,7 +93,7 @@ def _hit_rect(self) -> rl.Rectangle: return self._rect return rl.get_collision_rec(self._rect, self._parent_rect) - def render(self, rect: rl.Rectangle = None) -> bool | int | None: + def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: if rect is not None: self.set_rect(rect) @@ -359,7 +361,7 @@ def _update_state(self): self.set_position(self._rect.x, new_y) - def render(self, rect: rl.Rectangle = None) -> bool | int | None: + def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: ret = super().render(rect) if self.back_enabled: diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 97b293083d0..cb0cf66b144 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -31,13 +31,13 @@ class MiciLabel(Widget): def __init__(self, text: str, font_size: int = DEFAULT_TEXT_SIZE, - width: int = None, + width: int | None = None, color: rl.Color = DEFAULT_TEXT_COLOR, font_weight: FontWeight = FontWeight.NORMAL, alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, spacing: int = 0, - line_height: int = None, + line_height: int | None = None, elide_right: bool = True, wrap_text: bool = False, scroll: bool = False): diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index cfb8ab58a35..32bf01cfc86 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -174,8 +174,8 @@ def set_text(self, text: str | Callable[[], str]): class DualButtonAction(ItemAction): - def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, - right_callback: Callable = None, enabled: bool | Callable[[], bool] = True): + def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable | None = None, + right_callback: Callable | None = None, enabled: bool | Callable[[], bool] = True): super().__init__(width=0, enabled=enabled) # Width 0 means use full width self.left_button = Button(left_text, click_callback=left_callback, button_style=ButtonStyle.NORMAL, text_padding=0) self.right_button = Button(right_text, click_callback=right_callback, button_style=ButtonStyle.DANGER, text_padding=0) @@ -207,7 +207,7 @@ def _render(self, rect: rl.Rectangle): class MultipleButtonAction(ItemAction): - def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None): + def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable | None = None): super().__init__(width=len(buttons) * button_width + (len(buttons) - 1) * RIGHT_ITEM_PADDING, enabled=True) self.buttons = buttons self.button_width = button_width @@ -454,13 +454,14 @@ def text_item(title: str | Callable[[], str], value: str | Callable[[], str], de return ListItem(title=title, description=description, action_item=action, callback=callback) -def dual_button_item(left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, right_callback: Callable = None, +def dual_button_item(left_text: str | Callable[[], str], right_text: str | Callable[[], str], + left_callback: Callable | None = None, right_callback: Callable | None = None, description: str | Callable[[], str] | None = None, enabled: bool | Callable[[], bool] = True) -> ListItem: action = DualButtonAction(left_text, right_text, left_callback, right_callback, enabled) return ListItem(title="", description=description, action_item=action) def multiple_button_item(title: str | Callable[[], str], description: str | Callable[[], str], buttons: list[str | Callable[[], str]], selected_index: int, - button_width: int = BUTTON_WIDTH, callback: Callable = None, icon: str = ""): + button_width: int = BUTTON_WIDTH, callback: Callable | None = None, icon: str = ""): action = MultipleButtonAction(buttons, button_width, selected_index, callback=callback) return ListItem(title=title, description=description, icon=icon, action_item=action) diff --git a/system/updated/casync/casync.py b/system/updated/casync/casync.py index 79ac26f1c6e..2bd46a1ffba 100755 --- a/system/updated/casync/casync.py +++ b/system/updated/casync/casync.py @@ -168,7 +168,7 @@ def build_chunk_dict(chunks: list[Chunk]) -> ChunkDict: def extract(target: list[Chunk], sources: list[tuple[str, ChunkReader, ChunkDict]], out_path: str, - progress: Callable[[int], None] = None): + progress: Callable[[int], None] | None = None): stats: dict[str, int] = defaultdict(int) mode = 'rb+' if os.path.exists(out_path) else 'wb' @@ -208,7 +208,7 @@ def extract_directory(target: list[Chunk], sources: list[tuple[str, ChunkReader, ChunkDict]], out_path: str, tmp_file: str, - progress: Callable[[int], None] = None): + progress: Callable[[int], None] | None = None): """extract a directory stored as a casync tar archive""" stats = extract(target, sources, tmp_file, progress) diff --git a/system/updated/updated.py b/system/updated/updated.py index a4a1f8f34f8..ffd10e038db 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -68,7 +68,7 @@ def write_time_to_param(params, param) -> None: t = datetime.datetime.now(datetime.UTC).replace(tzinfo=None) params.put(param, t) -def run(cmd: list[str], cwd: str = None) -> str: +def run(cmd: list[str], cwd: str | None = None) -> str: return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT, encoding='utf8') diff --git a/system/webrtc/device/audio.py b/system/webrtc/device/audio.py index 4b22033e03e..b1859518a17 100644 --- a/system/webrtc/device/audio.py +++ b/system/webrtc/device/audio.py @@ -16,7 +16,7 @@ class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): pyaudio.paFloat32: 'flt', } - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: int = None): + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: int | None = None): super().__init__() self.p = pyaudio.PyAudio() @@ -48,7 +48,7 @@ async def recv(self): class AudioOutputSpeaker: - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: int = None): + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: int | None = None): chunk_size = int(packet_time * rate) self.p = pyaudio.PyAudio() diff --git a/tools/jotpluggler/data.py b/tools/jotpluggler/data.py index cf27857d1f1..756b87bd20c 100644 --- a/tools/jotpluggler/data.py +++ b/tools/jotpluggler/data.py @@ -9,7 +9,7 @@ from openpilot.tools.lib.logreader import _LogFileReader, LogReader -def flatten_dict(d: dict, sep: str = "/", prefix: str = None) -> dict: +def flatten_dict(d: dict, sep: str = "/", prefix: str | None = None) -> dict: result = {} stack: list[tuple] = [(d, prefix)] diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py index 1c4d9a8f3c0..cd723d161e9 100644 --- a/tools/jotpluggler/views.py +++ b/tools/jotpluggler/views.py @@ -9,7 +9,7 @@ class ViewPanel(ABC): """Abstract base class for all view panels that can be displayed in a plot container""" - def __init__(self, panel_id: str = None): + def __init__(self, panel_id: str | None = None): self.panel_id = panel_id or str(uuid.uuid4()) self.title = "Untitled Panel" diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index f9a90490b91..9696c8524d1 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -13,7 +13,6 @@ import zstandard as zstd from collections.abc import Iterable, Iterator -from typing import cast from urllib.parse import parse_qs, urlparse from cereal import log as capnp_log @@ -180,7 +179,7 @@ def auto_source(identifier: str, sources: list[Source], default_mode: ReadMode) # We've found all files, return them if len(needed_seg_idxs) == 0: - return cast(list[str], list(valid_files.values())) + return list(valid_files.values()) else: raise FileNotFoundError(f"Did not find {fn} for seg idxs {needed_seg_idxs} of {sr.route_name}") @@ -245,7 +244,7 @@ def _parse_identifier(self, identifier: str) -> list[str]: return identifiers def __init__(self, identifier: str | list[str], default_mode: ReadMode = ReadMode.RLOG, - sources: list[Source] = None, sort_by_time=False, only_union_types=False): + sources: list[Source] | None = None, sort_by_time=False, only_union_types=False): if sources is None: sources = [internal_source, comma_api_source, openpilotci_source, comma_car_segments_source] diff --git a/tools/lib/vidindex.py b/tools/lib/vidindex.py index f2e4e9ca45e..4aef6fb4d5a 100755 --- a/tools/lib/vidindex.py +++ b/tools/lib/vidindex.py @@ -140,7 +140,7 @@ def get_ue(dat: bytes, start_idx: int, skip_bits: int) -> tuple[int, int]: j -= 1 if prefix_val == 1 and prefix_len - 1 == suffix_len: - val = 2**(prefix_len-1) - 1 + suffix_val + val = int(2**(prefix_len-1) - 1 + suffix_val) size = prefix_len + suffix_len return val, size i += 1 diff --git a/uv.lock b/uv.lock index 53cb108c610..d8e3d826961 100644 --- a/uv.lock +++ b/uv.lock @@ -1188,41 +1188,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, ] -[[package]] -name = "mypy" -version = "1.18.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mypy-extensions" }, - { name = "pathspec" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" }, - { url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" }, - { url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" }, - { url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" }, - { url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" }, - { url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" }, - { url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" }, - { url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" }, - { url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" }, - { url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" }, - { url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" }, -] - -[[package]] -name = "mypy-extensions" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" }, -] - [[package]] name = "natsort" version = "8.4.0" @@ -1386,7 +1351,6 @@ testing = [ { name = "codespell" }, { name = "coverage" }, { name = "hypothesis" }, - { name = "mypy" }, { name = "pre-commit-hooks" }, { name = "pytest" }, { name = "pytest-asyncio" }, @@ -1398,6 +1362,7 @@ testing = [ { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "ruff" }, + { name = "ty" }, ] tools = [ { name = "dearpygui", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, @@ -1432,7 +1397,6 @@ requires-dist = [ { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" }, { name = "mkdocs", marker = "extra == 'docs'" }, - { name = "mypy", marker = "extra == 'testing'" }, { name = "natsort", marker = "extra == 'docs'" }, { name = "numpy", specifier = ">=2.0" }, { name = "onnx", specifier = ">=1.14.0" }, @@ -1476,6 +1440,7 @@ requires-dist = [ { name = "sympy" }, { name = "tabulate", marker = "extra == 'dev'" }, { name = "tqdm" }, + { name = "ty", marker = "extra == 'testing'" }, { name = "types-requests", marker = "extra == 'dev'" }, { name = "types-tabulate", marker = "extra == 'dev'" }, { name = "websocket-client" }, @@ -4945,6 +4910,31 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] +[[package]] +name = "ty" +version = "0.0.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/43/8be3ec2e2ce6119cff9ee3a207fae0cb4f2b4f8ed6534175130a32be24a7/ty-0.0.7.tar.gz", hash = "sha256:90e53b20b86c418ee41a8385f17da44cc7f916f96f9eee87593423ce8292ca72", size = 4826677, upload-time = "2025-12-24T21:28:49.136Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6e/56/fafa123acf955089306372add312f16e97aba61f7c4daf74e2bb9c350d23/ty-0.0.7-py3-none-linux_armv6l.whl", hash = "sha256:b30105bd9a0b064497111c50c206d5b6a032f29bcf39f09a12085c3009d72784", size = 9862360, upload-time = "2025-12-24T21:28:36.762Z" }, + { url = "https://files.pythonhosted.org/packages/71/f4/9c30ff498d9a60e24f16d26c0cf93cd03a119913ffa720a77149f02df06e/ty-0.0.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b4df20889115f3d5611a9d9cdedc222e3fd82b5fe87bb0a9f7246e53a23becc7", size = 9712866, upload-time = "2025-12-24T21:28:25.926Z" }, + { url = "https://files.pythonhosted.org/packages/43/84/e06a4a6e4011890027ffee41efbf261b1335103d09009d625ace7f1a60eb/ty-0.0.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f699589d8511e1e17c5a7edfc5f4a4e80f2a6d4a3932a0e9e3422fd32d731472", size = 9221692, upload-time = "2025-12-24T21:28:29.649Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e9/ebb4192d3627730125d40ee403a17dc91bab59d69c3eff286453b3218d01/ty-0.0.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eaec2d8aa153ee4bcc43b17a384d0f9e66177c8c8127be3358b6b8348b9e3b", size = 9710340, upload-time = "2025-12-24T21:28:55.148Z" }, + { url = "https://files.pythonhosted.org/packages/8f/4a/ec144458a9cfb324d5cb471483094e62e74d73179343dff262a5cca1a1e1/ty-0.0.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:177d160295e6a56bdf0b61f6120bc4502fff301d4d10855ba711c109aa7f37fb", size = 9670317, upload-time = "2025-12-24T21:28:43.096Z" }, + { url = "https://files.pythonhosted.org/packages/b6/94/fe7106fd5e2ac06b81fba7b785a6216774618edc3fda9e17f58efe3cede6/ty-0.0.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30518b95ab5cc83615794cca765a5fb86df39a0d9c3dadc0ab2d787ab7830008", size = 10096517, upload-time = "2025-12-24T21:28:23.667Z" }, + { url = "https://files.pythonhosted.org/packages/45/d9/db96ccfd663c96bdd4bb63db72899198c01445012f939477a5318a563f14/ty-0.0.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7867b3f75c2d9602cc6fb3b6d462580b707c2d112d4b27037142b0d01f8bfd03", size = 10996406, upload-time = "2025-12-24T21:28:39.134Z" }, + { url = "https://files.pythonhosted.org/packages/94/da/103915c08c3e6a14f95959614646fcdc9a240cd9a039fadbdcd086c819ee/ty-0.0.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878d45858e209b7904753fbc5155f4cb75dadc20a26bbb77614bfef31580f9ae", size = 10712829, upload-time = "2025-12-24T21:28:27.745Z" }, + { url = "https://files.pythonhosted.org/packages/47/c0/d9be417bc8e459e13e9698978579eec9868f91f4c5d6ef663249967fec8b/ty-0.0.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:651820b193901825afce40ae68f6a51cd64dbfa4b81a45db90061401261f25e4", size = 10486541, upload-time = "2025-12-24T21:28:45.17Z" }, + { url = "https://files.pythonhosted.org/packages/ad/09/d1858c66620d8ae566e021ad0d7168914b1568841f8fe9e439116ce6b440/ty-0.0.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f56a5a0c1c045863b1b70c358a392b3f73b8528c5c571d409f19dd465525e116", size = 10255312, upload-time = "2025-12-24T21:28:53.17Z" }, + { url = "https://files.pythonhosted.org/packages/b6/0a/78f75089db491fd5fcc13d2845a0b2771b7f7d377450c64c6616e9c227bc/ty-0.0.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:748218fbc1f7b7f1b9d14e77d4f3d7fec72af794417e26b0185bdb94153afe1c", size = 9696201, upload-time = "2025-12-24T21:28:57.345Z" }, + { url = "https://files.pythonhosted.org/packages/01/9e/b26e94832fd563fef6f77a4487affc77a027b0e53106422c66aafb37fa01/ty-0.0.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1ff80f3985a52a7358b9069b4a8d223e92cf312544a934a062d6d3a4fb6876b3", size = 9688907, upload-time = "2025-12-24T21:28:59.485Z" }, + { url = "https://files.pythonhosted.org/packages/5a/8f/cc48601fb92c964cf6c34277e0d947076146b7de47aa11b5dbae45e01ce7/ty-0.0.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a808910ce672ba4446699f4c021283208f58f988bcfc3bdbdfc6e005819d9ee0", size = 9829982, upload-time = "2025-12-24T21:28:34.429Z" }, + { url = "https://files.pythonhosted.org/packages/b5/af/7fa9c2bfa25865968bded637f7e71f1a712f4fbede88f487b6a9101ab936/ty-0.0.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2718fea5f314eda01703fb406ec89b1fc8710b3fc6a09bbd6f7a4f3502ddc889", size = 10361037, upload-time = "2025-12-24T21:28:47.027Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5b/1a6ff1495975cd1c02aa8d03bc5c9d8006eaeb8bf354446f88d70f0518fd/ty-0.0.7-py3-none-win32.whl", hash = "sha256:ae89bb8dc50deb66f34eab3113aa61ac5d7f85ecf16279e5918548085a89021c", size = 9295092, upload-time = "2025-12-24T21:28:51.041Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f6/47e9364635d048002354f84d2d0d6dfc9eb166dc67850739f88e1fec4fc5/ty-0.0.7-py3-none-win_amd64.whl", hash = "sha256:25bd20e3d4d0f07b422f9b42711ba24d28116031273bd23dbda66cec14df1c06", size = 10162816, upload-time = "2025-12-24T21:28:41.006Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f4/c4fc28410c4493982b7481fb23f62bacb02fd2912ebec3b9bc7de18bebb8/ty-0.0.7-py3-none-win_arm64.whl", hash = "sha256:c87d27484dba9fca0053b6a9eee47eecc760aab2bbb8e6eab3d7f81531d1ad0c", size = 9653112, upload-time = "2025-12-24T21:28:31.562Z" }, +] + [[package]] name = "types-requests" version = "2.32.4.20250913" From ce1491df9c6bfcc45113de3a974973d6057933fc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 11:45:19 -0800 Subject: [PATCH 029/685] tools: add LRU eviction for log cache (#36959) * tools: add LRU for log cache * lil more * cleanup: * less syscall * manifest * cleanup * cleanup * lil more * cleanup * lil more * simpler * lil more --- tools/lib/tests/test_caching.py | 36 ++++++++++++++++++++++++++++- tools/lib/url_file.py | 41 ++++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/tools/lib/tests/test_caching.py b/tools/lib/tests/test_caching.py index 2bb63b4dce5..6e70ef90b05 100644 --- a/tools/lib/tests/test_caching.py +++ b/tools/lib/tests/test_caching.py @@ -2,11 +2,13 @@ import os import shutil import socket +import tempfile import pytest from openpilot.selfdrive.test.helpers import http_server_context from openpilot.system.hardware.hw import Paths -from openpilot.tools.lib.url_file import URLFile +from openpilot.tools.lib.url_file import URLFile, prune_cache +import openpilot.tools.lib.url_file as url_file_module class CachingTestRequestHandler(http.server.BaseHTTPRequestHandler): @@ -128,3 +130,35 @@ def test_recover_from_missing_file(self, host, cache_enabled): CachingTestRequestHandler.FILE_EXISTS = True length = URLFile(file_url).get_length() assert length == 4 + + +class TestCache: + def test_prune_cache(self, monkeypatch): + with tempfile.TemporaryDirectory() as tmpdir: + monkeypatch.setattr(Paths, 'download_cache_root', staticmethod(lambda: tmpdir + "/")) + + # setup test files and manifest + manifest_lines = [] + for i in range(3): + fname = f"hash_{i}" + with open(tmpdir + "/" + fname, "wb") as f: + f.truncate(1000) + manifest_lines.append(f"{fname} {1000 + i}") + with open(tmpdir + "/manifest.txt", "w") as f: + f.write('\n'.join(manifest_lines)) + + # under limit, shouldn't prune + assert len(os.listdir(tmpdir)) == 4 + prune_cache() + assert len(os.listdir(tmpdir)) == 4 + + # set a tiny cache limit to force eviction (1.5 chunks worth) + monkeypatch.setattr(url_file_module, 'CACHE_SIZE', url_file_module.CHUNK_SIZE + url_file_module.CHUNK_SIZE // 2) + + # prune_cache should evict oldest files to get under limit + prune_cache() + remaining = os.listdir(tmpdir) + # should have evicted at least one file + manifest + assert len(remaining) < 4 + # newest file should remain + assert manifest_lines[2].split()[0] in remaining diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index c7ccab1ae38..790fa7e8fb1 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -1,8 +1,9 @@ -import re import logging import os +import re import socket -from hashlib import sha256 +import time +from hashlib import md5 from urllib3 import PoolManager, Retry from urllib3.response import BaseHTTPResponse from urllib3.util import Timeout @@ -14,13 +15,40 @@ # Cache chunk size K = 1000 CHUNK_SIZE = 1000 * K +CACHE_SIZE = 10 * 1024 * 1024 * 1024 # total cache size in GB logging.getLogger("urllib3").setLevel(logging.WARNING) -def hash_256(link: str) -> str: - return sha256((link.split("?")[0]).encode('utf-8')).hexdigest() +def hash_url(link: str) -> str: + return md5((link.split("?")[0]).encode('utf-8')).hexdigest() + + +def prune_cache(new_entry: str | None = None) -> None: + """Evicts oldest cache files (LRU) until cache is under the size limit.""" + # we use a manifest to avoid tons of os.stat syscalls (slow) + manifest = {} + manifest_path = Paths.download_cache_root() + "manifest.txt" + if os.path.exists(manifest_path): + with open(manifest_path) as f: + manifest = {parts[0]: int(parts[1]) for line in f if (parts := line.strip().split()) and len(parts) == 2} + + if new_entry: + manifest[new_entry] = int(time.time()) # noqa: TID251 + + # evict the least recently used files until under limit + sorted_items = sorted(manifest.items(), key=lambda x: x[1]) + while len(manifest) * CHUNK_SIZE > CACHE_SIZE and sorted_items: + key, _ = sorted_items.pop(0) + try: + os.remove(Paths.download_cache_root() + key) + except OSError: + pass + manifest.pop(key, None) + # write out manifest + with atomic_write(manifest_path, mode="w", overwrite=True) as f: + f.write('\n'.join(f"{k} {v}" for k, v in manifest.items())) class URLFileException(Exception): pass @@ -77,7 +105,7 @@ def get_length(self) -> int: if self._length is not None: return self._length - file_length_path = os.path.join(Paths.download_cache_root(), hash_256(self._url) + "_length") + file_length_path = os.path.join(Paths.download_cache_root(), hash_url(self._url) + "_length") if not self._force_download and os.path.exists(file_length_path): with open(file_length_path) as file_length: content = file_length.read() @@ -103,7 +131,7 @@ def read(self, ll: int | None = None) -> bytes: while True: self._pos = position chunk_number = self._pos / CHUNK_SIZE - file_name = hash_256(self._url) + "_" + str(chunk_number) + file_name = hash_url(self._url) + "_" + str(chunk_number) full_path = os.path.join(Paths.download_cache_root(), str(file_name)) data = None # If we don't have a file, download it @@ -111,6 +139,7 @@ def read(self, ll: int | None = None) -> bytes: data = self.read_aux(ll=CHUNK_SIZE) with atomic_write(full_path, mode="wb", overwrite=True) as new_cached_file: new_cached_file.write(data) + prune_cache(file_name) else: with open(full_path, "rb") as cached_file: data = cached_file.read() From b58fddb83eab27504e0ba351d567a91e0d62e7e6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 12:20:45 -0800 Subject: [PATCH 030/685] replay: add --benchmark mode (#36957) --- tools/replay/main.cc | 27 +++++++++++++++ tools/replay/replay.cc | 76 ++++++++++++++++++++++++++++++++++++++--- tools/replay/replay.h | 17 ++++++++- tools/replay/seg_mgr.cc | 6 ++++ tools/replay/seg_mgr.h | 2 ++ 5 files changed, 123 insertions(+), 5 deletions(-) diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 46090121945..30f85b1700f 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -1,11 +1,13 @@ #include +#include #include #include #include #include #include "common/prefix.h" +#include "common/timing.h" #include "tools/replay/consoleui.h" #include "tools/replay/replay.h" #include "tools/replay/util.h" @@ -31,6 +33,7 @@ R"(Usage: replay [options] [route] --no-hw-decoder Disable HW video decoding --no-vipc Do not output video --all Output all messages including bookmarkButton, uiDebug, userBookmark + --benchmark Run in benchmark mode (process all events then exit with stats) -h, --help Show this help message )"; @@ -66,6 +69,7 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) { {"no-hw-decoder", no_argument, nullptr, 0}, {"no-vipc", no_argument, nullptr, 0}, {"all", no_argument, nullptr, 0}, + {"benchmark", no_argument, nullptr, 0}, {"help", no_argument, nullptr, 'h'}, {nullptr, 0, nullptr, 0}, // Terminating entry }; @@ -79,6 +83,7 @@ bool parseArgs(int argc, char *argv[], ReplayConfig &config) { {"no-hw-decoder", REPLAY_FLAG_NO_HW_DECODER}, {"no-vipc", REPLAY_FLAG_NO_VIPC}, {"all", REPLAY_FLAG_ALL_SERVICES}, + {"benchmark", REPLAY_FLAG_BENCHMARK}, }; if (argc == 1) { @@ -149,6 +154,28 @@ int main(int argc, char *argv[]) { return 1; } + if (config.flags & REPLAY_FLAG_BENCHMARK) { + replay.start(config.start_seconds); + replay.waitForFinished(); + + const auto &stats = replay.getBenchmarkStats(); + uint64_t process_start = stats.process_start_ts; + + std::cout << "\n===== REPLAY BENCHMARK RESULTS =====\n"; + std::cout << "Route: " << replay.route().name() << "\n\n"; + + std::cout << "TIMELINE:\n"; + std::cout << " t=0 ms process start\n"; + for (const auto &[ts, event] : stats.timeline) { + double ms = (ts - process_start) / 1e6; + std::cout << " t=" << std::fixed << std::setprecision(0) << ms << " ms" + << std::string(std::max(1, 8 - static_cast(std::to_string(static_cast(ms)).length())), ' ') + << event << "\n"; + } + + return 0; + } + ConsoleUI console_ui(&replay); replay.start(config.start_seconds); return console_ui.exec(); diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index cc105dd10e6..d8d59e41a43 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -2,6 +2,8 @@ #include #include +#include +#include #include "cereal/services.h" #include "common/params.h" #include "tools/replay/util.h" @@ -19,6 +21,14 @@ Replay::Replay(const std::string &route, std::vector allow, std::ve : sm_(sm), flags_(flags), seg_mgr_(std::make_unique(route, flags, data_dir, auto_source)) { std::signal(SIGUSR1, interrupt_sleep_handler); + if (flags_ & REPLAY_FLAG_BENCHMARK) { + benchmark_stats_.process_start_ts = nanos_since_boot(); + seg_mgr_->setBenchmarkCallback([this](int seg_num, const std::string& event) { + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), + "segment " + std::to_string(seg_num) + " " + event); + }); + } + if (!(flags_ & REPLAY_FLAG_ALL_SERVICES)) { block.insert(block.end(), {"bookmarkButton", "uiDebug", "userBookmark"}); } @@ -78,8 +88,13 @@ Replay::~Replay() { bool Replay::load() { rInfo("loading route %s", seg_mgr_->route_.name().c_str()); + if (!seg_mgr_->load()) return false; + if (hasFlag(REPLAY_FLAG_BENCHMARK)) { + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), "route metadata loaded"); + } + min_seconds_ = seg_mgr_->route_.segments().begin()->first * 60; max_seconds_ = (seg_mgr_->route_.segments().rbegin()->first + 1) * 60; return true; @@ -257,8 +272,13 @@ void Replay::streamThread() { stream_thread_id = pthread_self(); std::unique_lock lk(stream_lock_); + int last_processed_segment = -1; + uint64_t segment_start_time = 0; + bool streaming_started = false; + while (true) { stream_cv_.wait(lk, [this]() { return exit_ || (events_ready_ && !interrupt_requested_); }); + if (exit_) break; event_data_ = seg_mgr_->getEventData(); @@ -270,14 +290,19 @@ void Replay::streamThread() { continue; } - auto it = publishEvents(first, events.cend()); + if (!streaming_started && hasFlag(REPLAY_FLAG_BENCHMARK)) { + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), "streaming started"); + streaming_started = true; + } + + auto it = publishEvents(first, events.cend(), last_processed_segment, segment_start_time); // Ensure frames are sent before unlocking to prevent race conditions if (camera_server_) { camera_server_->waitForSent(); } - if (it == events.cend() && !hasFlag(REPLAY_FLAG_NO_LOOP)) { + if (it == events.cend() && !hasFlag(REPLAY_FLAG_NO_LOOP) && !hasFlag(REPLAY_FLAG_BENCHMARK)) { int last_segment = seg_mgr_->route_.segments().rbegin()->first; if (event_data_->isSegmentLoaded(last_segment)) { rInfo("reaches the end of route, restart from beginning"); @@ -285,12 +310,28 @@ void Replay::streamThread() { seekTo(minSeconds(), false); stream_lock_.lock(); } + } else if (it == events.cend() && hasFlag(REPLAY_FLAG_BENCHMARK)) { + // Exit benchmark mode after first segment completes + exit_ = true; + break; } } + + if (hasFlag(REPLAY_FLAG_BENCHMARK)) { + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), "benchmark done"); + + { + std::unique_lock lock(benchmark_lock_); + benchmark_done_ = true; + } + benchmark_cv_.notify_one(); + } } std::vector::const_iterator Replay::publishEvents(std::vector::const_iterator first, - std::vector::const_iterator last) { + std::vector::const_iterator last, + int &last_processed_segment, + uint64_t &segment_start_time) { uint64_t evt_start_ts = cur_mono_time_; uint64_t loop_start_ts = nanos_since_boot(); double prev_replay_speed = speed_; @@ -304,6 +345,23 @@ std::vector::const_iterator Replay::publishEvents(std::vector::con seg_mgr_->setCurrentSegment(segment); } + // Track segment completion for benchmark timeline + if (hasFlag(REPLAY_FLAG_BENCHMARK) && segment != last_processed_segment) { + if (last_processed_segment >= 0 && segment_start_time > 0) { + uint64_t processing_time_ns = nanos_since_boot() - segment_start_time; + double processing_time_ms = processing_time_ns / 1e6; + double realtime_factor = 60.0 / (processing_time_ns / 1e9); // 60s per segment + + std::ostringstream oss; + oss << "segment " << last_processed_segment << " done publishing (" + << std::fixed << std::setprecision(0) << processing_time_ms << " ms, " + << std::fixed << std::setprecision(0) << realtime_factor << "x realtime)"; + benchmark_stats_.timeline.emplace_back(nanos_since_boot(), oss.str()); + } + segment_start_time = nanos_since_boot(); + last_processed_segment = segment; + } + cur_mono_time_ = evt.mono_time; cur_which_ = evt.which; @@ -320,7 +378,8 @@ std::vector::const_iterator Replay::publishEvents(std::vector::con evt_start_ts = evt.mono_time; loop_start_ts = current_nanos; prev_replay_speed = speed_; - } else if (time_diff > 0) { + } else if (time_diff > 0 && !hasFlag(REPLAY_FLAG_BENCHMARK)) { + // Skip sleep in benchmark mode for maximum throughput precise_nano_sleep(time_diff, interrupt_requested_); } @@ -338,3 +397,12 @@ std::vector::const_iterator Replay::publishEvents(std::vector::con return first; } + +void Replay::waitForFinished() { + if (!hasFlag(REPLAY_FLAG_BENCHMARK)) { + return; + } + + std::unique_lock lock(benchmark_lock_); + benchmark_cv_.wait(lock, [this]() { return benchmark_done_; }); +} diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 5e868d2427a..58c1b71b8a8 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -24,6 +24,12 @@ enum REPLAY_FLAGS { REPLAY_FLAG_NO_HW_DECODER = 0x0100, REPLAY_FLAG_NO_VIPC = 0x0400, REPLAY_FLAG_ALL_SERVICES = 0x0800, + REPLAY_FLAG_BENCHMARK = 0x1000, +}; + +struct BenchmarkStats { + uint64_t process_start_ts = 0; + std::vector> timeline; }; class Replay { @@ -57,6 +63,8 @@ class Replay { inline const std::optional findAlertAtTime(double sec) const { return timeline_.findAlertAtTime(sec); } const std::shared_ptr getEventData() const { return seg_mgr_->getEventData(); } void installEventFilter(std::function filter) { event_filter_ = filter; } + void waitForFinished(); + const BenchmarkStats &getBenchmarkStats() const { return benchmark_stats_; } // Event callback functions std::function onSegmentsMerged = nullptr; @@ -72,7 +80,9 @@ class Replay { void handleSegmentMerge(); void interruptStream(const std::function& update_fn); std::vector::const_iterator publishEvents(std::vector::const_iterator first, - std::vector::const_iterator last); + std::vector::const_iterator last, + int &last_processed_segment, + uint64_t &segment_start_time); void publishMessage(const Event *e); void publishFrame(const Event *e); void checkSeekProgress(); @@ -107,4 +117,9 @@ class Replay { std::function event_filter_ = nullptr; std::shared_ptr event_data_ = std::make_shared(); + + BenchmarkStats benchmark_stats_; + std::condition_variable benchmark_cv_; + std::mutex benchmark_lock_; + bool benchmark_done_ = false; }; diff --git a/tools/replay/seg_mgr.cc b/tools/replay/seg_mgr.cc index f4e865d4768..0778cacbc1b 100644 --- a/tools/replay/seg_mgr.cc +++ b/tools/replay/seg_mgr.cc @@ -118,9 +118,15 @@ void SegmentManager::loadSegmentsInRange(SegmentMap::iterator begin, SegmentMap: for (auto it = first; it != last; ++it) { auto &segment_ptr = it->second; if (!segment_ptr) { + if (onBenchmarkEvent_) { + onBenchmarkEvent_(it->first, "loading"); + } segment_ptr = std::make_shared( it->first, route_.at(it->first), flags_, filters_, [this](int seg_num, bool success) { + if (onBenchmarkEvent_) { + onBenchmarkEvent_(seg_num, success ? "loaded" : "load failed"); + } std::unique_lock lock(mutex_); needs_update_ = true; cv_.notify_one(); diff --git a/tools/replay/seg_mgr.h b/tools/replay/seg_mgr.h index 640169749e2..54e156fb60a 100644 --- a/tools/replay/seg_mgr.h +++ b/tools/replay/seg_mgr.h @@ -27,6 +27,7 @@ class SegmentManager { bool load(); void setCurrentSegment(int seg_num); void setCallback(const std::function &callback) { onSegmentMergedCallback_ = callback; } + void setBenchmarkCallback(const std::function &callback) { onBenchmarkEvent_ = callback; } void setFilters(const std::vector &filters) { filters_ = filters; } const std::shared_ptr getEventData() const { return std::atomic_load(&event_data_); } bool hasSegment(int n) const { return segments_.find(n) != segments_.end(); } @@ -52,5 +53,6 @@ class SegmentManager { SegmentMap segments_; std::shared_ptr event_data_; std::function onSegmentMergedCallback_ = nullptr; + std::function onBenchmarkEvent_ = nullptr; std::set merged_segments_; }; From 883d1232d31bad9318d31d08ff5302f303192296 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Sun, 28 Dec 2025 13:00:35 -0800 Subject: [PATCH 031/685] [bot] Update Python packages (#36606) update Co-authored-by: Adeeb Shihadeh --- msgq_repo | 2 +- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 2675 +++++++++++++++++++++++++------------------------ 5 files changed, 1368 insertions(+), 1315 deletions(-) diff --git a/msgq_repo b/msgq_repo index 349bf449b73..20f2493855e 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 349bf449b7329527162598de357bfecf232abc3f +Subproject commit 20f2493855ef32339b80f0ad76b3cb82210dc474 diff --git a/opendbc_repo b/opendbc_repo index 4aa7ca97229..edf19be8efe 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 4aa7ca972290af6c6bbd314d1975eab46a888b6f +Subproject commit edf19be8efe06a5387265adc4650725bb56b5c46 diff --git a/panda b/panda index 1ffad74f88e..e42367df97f 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 1ffad74f88e5683d9cd7c472e823928e28037e9e +Subproject commit e42367df97f1a5ea4ddb152566022c3ae4672e58 diff --git a/tinygrad_repo b/tinygrad_repo index 547304c471b..f5090192c84 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 547304c471b26ada0b34f400ccba67f3e1eb5965 +Subproject commit f5090192c84760be1227f7e3c4f99ad0603117ae diff --git a/uv.lock b/uv.lock index d8e3d826961..a5b529e52eb 100644 --- a/uv.lock +++ b/uv.lock @@ -21,7 +21,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.12.15" +version = "3.13.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -32,55 +32,55 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, - { url = "https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, - { url = "https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, - { url = "https://files.pythonhosted.org/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, - { url = "https://files.pythonhosted.org/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, - { url = "https://files.pythonhosted.org/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, - { url = "https://files.pythonhosted.org/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, - { url = "https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, - { url = "https://files.pythonhosted.org/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, - { url = "https://files.pythonhosted.org/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, - { url = "https://files.pythonhosted.org/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, - { url = "https://files.pythonhosted.org/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, - { url = "https://files.pythonhosted.org/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, - { url = "https://files.pythonhosted.org/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, - { url = "https://files.pythonhosted.org/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, - { url = "https://files.pythonhosted.org/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, - { url = "https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, - { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, - { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, - { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, - { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, - { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, - { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, - { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, - { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, - { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, - { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, - { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, - { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, - { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, - { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/35/74/b321e7d7ca762638cdf8cdeceb39755d9c745aff7a64c8789be96ddf6e96/aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0", size = 743409, upload-time = "2025-10-28T20:56:00.354Z" }, + { url = "https://files.pythonhosted.org/packages/99/3d/91524b905ec473beaf35158d17f82ef5a38033e5809fe8742e3657cdbb97/aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb", size = 497006, upload-time = "2025-10-28T20:56:01.85Z" }, + { url = "https://files.pythonhosted.org/packages/eb/d3/7f68bc02a67716fe80f063e19adbd80a642e30682ce74071269e17d2dba1/aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9", size = 493195, upload-time = "2025-10-28T20:56:03.314Z" }, + { url = "https://files.pythonhosted.org/packages/98/31/913f774a4708775433b7375c4f867d58ba58ead833af96c8af3621a0d243/aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613", size = 1747759, upload-time = "2025-10-28T20:56:04.904Z" }, + { url = "https://files.pythonhosted.org/packages/e8/63/04efe156f4326f31c7c4a97144f82132c3bb21859b7bb84748d452ccc17c/aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead", size = 1704456, upload-time = "2025-10-28T20:56:06.986Z" }, + { url = "https://files.pythonhosted.org/packages/8e/02/4e16154d8e0a9cf4ae76f692941fd52543bbb148f02f098ca73cab9b1c1b/aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780", size = 1807572, upload-time = "2025-10-28T20:56:08.558Z" }, + { url = "https://files.pythonhosted.org/packages/34/58/b0583defb38689e7f06798f0285b1ffb3a6fb371f38363ce5fd772112724/aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a", size = 1895954, upload-time = "2025-10-28T20:56:10.545Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f3/083907ee3437425b4e376aa58b2c915eb1a33703ec0dc30040f7ae3368c6/aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592", size = 1747092, upload-time = "2025-10-28T20:56:12.118Z" }, + { url = "https://files.pythonhosted.org/packages/ac/61/98a47319b4e425cc134e05e5f3fc512bf9a04bf65aafd9fdcda5d57ec693/aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab", size = 1606815, upload-time = "2025-10-28T20:56:14.191Z" }, + { url = "https://files.pythonhosted.org/packages/97/4b/e78b854d82f66bb974189135d31fce265dee0f5344f64dd0d345158a5973/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30", size = 1723789, upload-time = "2025-10-28T20:56:16.101Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fc/9d2ccc794fc9b9acd1379d625c3a8c64a45508b5091c546dea273a41929e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40", size = 1718104, upload-time = "2025-10-28T20:56:17.655Z" }, + { url = "https://files.pythonhosted.org/packages/66/65/34564b8765ea5c7d79d23c9113135d1dd3609173da13084830f1507d56cf/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948", size = 1785584, upload-time = "2025-10-28T20:56:19.238Z" }, + { url = "https://files.pythonhosted.org/packages/30/be/f6a7a426e02fc82781afd62016417b3948e2207426d90a0e478790d1c8a4/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf", size = 1595126, upload-time = "2025-10-28T20:56:20.836Z" }, + { url = "https://files.pythonhosted.org/packages/e5/c7/8e22d5d28f94f67d2af496f14a83b3c155d915d1fe53d94b66d425ec5b42/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782", size = 1800665, upload-time = "2025-10-28T20:56:22.922Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/91133c8b68b1da9fc16555706aa7276fdf781ae2bb0876c838dd86b8116e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8", size = 1739532, upload-time = "2025-10-28T20:56:25.924Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/3747644d26a998774b21a616016620293ddefa4d63af6286f389aedac844/aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec", size = 431876, upload-time = "2025-10-28T20:56:27.524Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/688462108c1a00eb9f05765331c107f95ae86f6b197b865d29e930b7e462/aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c", size = 456205, upload-time = "2025-10-28T20:56:29.062Z" }, + { url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b", size = 737623, upload-time = "2025-10-28T20:56:30.797Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc", size = 492664, upload-time = "2025-10-28T20:56:32.708Z" }, + { url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7", size = 491808, upload-time = "2025-10-28T20:56:34.57Z" }, + { url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb", size = 1738863, upload-time = "2025-10-28T20:56:36.377Z" }, + { url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3", size = 1700586, upload-time = "2025-10-28T20:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f", size = 1768625, upload-time = "2025-10-28T20:56:39.75Z" }, + { url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6", size = 1867281, upload-time = "2025-10-28T20:56:41.471Z" }, + { url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e", size = 1752431, upload-time = "2025-10-28T20:56:43.162Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7", size = 1562846, upload-time = "2025-10-28T20:56:44.85Z" }, + { url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d", size = 1699606, upload-time = "2025-10-28T20:56:46.519Z" }, + { url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b", size = 1720663, upload-time = "2025-10-28T20:56:48.528Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8", size = 1737939, upload-time = "2025-10-28T20:56:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16", size = 1555132, upload-time = "2025-10-28T20:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169", size = 1764802, upload-time = "2025-10-28T20:56:54.292Z" }, + { url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248", size = 1719512, upload-time = "2025-10-28T20:56:56.428Z" }, + { url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e", size = 426690, upload-time = "2025-10-28T20:56:58.736Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45", size = 453465, upload-time = "2025-10-28T20:57:00.795Z" }, ] [[package]] name = "aioice" -version = "0.10.1" +version = "0.10.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "dnspython" }, { name = "ifaddr" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/a2/45dfab1d5a7f96c48595a5770379acf406cdf02a2cd1ac1729b599322b08/aioice-0.10.1.tar.gz", hash = "sha256:5c8e1422103448d171925c678fb39795e5fe13d79108bebb00aa75a899c2094a", size = 44304, upload-time = "2025-04-13T08:15:25.629Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/04/df7286233f468e19e9bedff023b6b246182f0b2ccb04ceeb69b2994021c6/aioice-0.10.2.tar.gz", hash = "sha256:bf236c6829ee33c8e540535d31cd5a066b531cb56de2be94c46be76d68b1a806", size = 44307, upload-time = "2025-11-28T15:56:48.836Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/58/af07dda649c22a1ae954ffb7aaaf4d4a57f1bf00ebdf62307affc0b8552f/aioice-0.10.1-py3-none-any.whl", hash = "sha256:f31ae2abc8608b1283ed5f21aebd7b6bd472b152ff9551e9b559b2d8efed79e9", size = 24872, upload-time = "2025-04-13T08:15:24.044Z" }, + { url = "https://files.pythonhosted.org/packages/c7/e3/0d23b1f930c17d371ce1ec36ee529f22fd19ebc2a07fe3418e3d1d884ce2/aioice-0.10.2-py3-none-any.whl", hash = "sha256:14911c15ab12d096dd14d372ebb4aecbb7420b52c9b76fdfcf54375dec17fcbf", size = 24875, upload-time = "2025-11-28T15:56:47.847Z" }, ] [[package]] @@ -123,11 +123,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.3.0" +version = "25.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" }, + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, ] [[package]] @@ -152,21 +152,20 @@ wheels = [ [[package]] name = "azure-core" -version = "1.35.1" +version = "1.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, - { name = "six" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/6b/2653adc0f33adba8f11b1903701e6b1c10d34ce5d8e25dfa13a422f832b0/azure_core-1.35.1.tar.gz", hash = "sha256:435d05d6df0fff2f73fb3c15493bb4721ede14203f1ff1382aa6b6b2bdd7e562", size = 345290, upload-time = "2025-09-11T22:58:04.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/83/41c9371c8298999c67b007e308a0a3c4d6a59c6908fa9c62101f031f886f/azure_core-1.37.0.tar.gz", hash = "sha256:7064f2c11e4b97f340e8e8c6d923b822978be3016e46b7bc4aa4b337cfb48aee", size = 357620, upload-time = "2025-12-11T20:05:13.518Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/52/805980aa1ba18282077c484dba634ef0ede1e84eec8be9c92b2e162d0ed6/azure_core-1.35.1-py3-none-any.whl", hash = "sha256:12da0c9e08e48e198f9158b56ddbe33b421477e1dc98c2e1c8f9e254d92c468b", size = 211800, upload-time = "2025-09-11T22:58:06.281Z" }, + { url = "https://files.pythonhosted.org/packages/ee/34/a9914e676971a13d6cc671b1ed172f9804b50a3a80a143ff196e52f4c7ee/azure_core-1.37.0-py3-none-any.whl", hash = "sha256:b3abe2c59e7d6bb18b38c275a5029ff80f98990e7c90a5e646249a56630fcc19", size = 214006, upload-time = "2025-12-11T20:05:14.96Z" }, ] [[package]] name = "azure-identity" -version = "1.25.0" +version = "1.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -175,14 +174,14 @@ dependencies = [ { name = "msal-extensions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/9e/4c9682a286c3c89e437579bd9f64f311020e5125c1321fd3a653166b5716/azure_identity-1.25.0.tar.gz", hash = "sha256:4177df34d684cddc026e6cf684e1abb57767aa9d84e7f2129b080ec45eee7733", size = 278507, upload-time = "2025-09-12T01:30:04.418Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/8d/1a6c41c28a37eab26dc85ab6c86992c700cd3f4a597d9ed174b0e9c69489/azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456", size = 279826, upload-time = "2025-10-06T20:30:02.194Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/54/81683b6756676a22e037b209695b08008258e603f7e47c56834029c5922a/azure_identity-1.25.0-py3-none-any.whl", hash = "sha256:becaec086bbdf8d1a6aa4fb080c2772a0f824a97d50c29637ec8cc4933f1e82d", size = 190861, upload-time = "2025-09-12T01:30:06.474Z" }, + { url = "https://files.pythonhosted.org/packages/83/7b/5652771e24fff12da9dde4c20ecf4682e606b104f26419d139758cc935a6/azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651", size = 191317, upload-time = "2025-10-06T20:30:04.251Z" }, ] [[package]] name = "azure-storage-blob" -version = "12.26.0" +version = "12.27.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -190,9 +189,9 @@ dependencies = [ { name = "isodate" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/95/3e3414491ce45025a1cde107b6ae72bf72049e6021597c201cd6a3029b9a/azure_storage_blob-12.26.0.tar.gz", hash = "sha256:5dd7d7824224f7de00bfeb032753601c982655173061e242f13be6e26d78d71f", size = 583332, upload-time = "2025-07-16T21:34:07.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/7c/2fd872e11a88163f208b9c92de273bf64bb22d0eef9048cc6284d128a77a/azure_storage_blob-12.27.1.tar.gz", hash = "sha256:a1596cc4daf5dac9be115fcb5db67245eae894cf40e4248243754261f7b674a6", size = 597579, upload-time = "2025-10-29T12:27:16.185Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/64/63dbfdd83b31200ac58820a7951ddfdeed1fbee9285b0f3eae12d1357155/azure_storage_blob-12.26.0-py3-none-any.whl", hash = "sha256:8c5631b8b22b4f53ec5fff2f3bededf34cfef111e2af613ad42c9e6de00a77fe", size = 412907, upload-time = "2025-07-16T21:34:09.367Z" }, + { url = "https://files.pythonhosted.org/packages/3d/9e/1c90a122ea6180e8c72eb7294adc92531b0e08eb3d2324c2ba70d37f4802/azure_storage_blob-12.27.1-py3-none-any.whl", hash = "sha256:65d1e25a4628b7b6acd20ff7902d8da5b4fde8e46e19c8f6d213a3abc3ece272", size = 428954, upload-time = "2025-10-29T12:27:18.072Z" }, ] [[package]] @@ -220,11 +219,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.8.3" +version = "2025.11.12" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, + { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, ] [[package]] @@ -265,54 +264,64 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, - { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, - { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, - { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, - { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, - { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, - { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, - { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, - { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, - { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, - { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, - { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, - { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, - { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, - { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, - { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, - { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, - { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, - { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, - { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, - { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, - { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, + { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, + { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, + { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, + { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, + { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, + { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, + { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, + { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, + { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, + { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, + { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, + { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, ] [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/61/de6cd827efad202d7057d93e0fed9294b96952e188f7384832791c7b2254/click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4", size = 276943, upload-time = "2025-09-18T17:32:23.696Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/d3/9dcc0f5797f070ec8edf30fbadfb200e71d9db6b84d211e3b2085a7589a0/click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc", size = 107295, upload-time = "2025-09-18T17:32:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] [[package]] name = "cloudpickle" -version = "3.1.1" +version = "3.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/39/069100b84d7418bc358d81669d5748efb14b9cceacd2f9c75f550424132f/cloudpickle-3.1.1.tar.gz", hash = "sha256:b216fa8ae4019d5482a8ac3c95d8f6346115d8835911fd4aefd1a445e4242c64", size = 22113, upload-time = "2025-01-14T17:02:05.085Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/e8/64c37fadfc2816a7701fa8a6ed8d87327c7d54eacfbfb6edab14a2f2be75/cloudpickle-3.1.1-py3-none-any.whl", hash = "sha256:c8c5a44295039331ee9dad40ba100a9c7297b6f988e50e87ccdf3765a668350e", size = 20992, upload-time = "2025-01-14T17:02:02.417Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, ] [[package]] @@ -373,37 +382,37 @@ wheels = [ [[package]] name = "coverage" -version = "7.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" }, - { url = "https://files.pythonhosted.org/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" }, - { url = "https://files.pythonhosted.org/packages/f4/36/2d93fbf6a04670f3874aed397d5a5371948a076e3249244a9e84fb0e02d6/coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7", size = 250361, upload-time = "2025-11-18T13:32:13.852Z" }, - { url = "https://files.pythonhosted.org/packages/5d/49/66dc65cc456a6bfc41ea3d0758c4afeaa4068a2b2931bf83be6894cf1058/coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245", size = 252472, upload-time = "2025-11-18T13:32:15.068Z" }, - { url = "https://files.pythonhosted.org/packages/35/1f/ebb8a18dffd406db9fcd4b3ae42254aedcaf612470e8712f12041325930f/coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b", size = 248592, upload-time = "2025-11-18T13:32:16.328Z" }, - { url = "https://files.pythonhosted.org/packages/da/a8/67f213c06e5ea3b3d4980df7dc344d7fea88240b5fe878a5dcbdfe0e2315/coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64", size = 250167, upload-time = "2025-11-18T13:32:17.687Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/e52aef68154164ea40cc8389c120c314c747fe63a04b013a5782e989b77f/coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742", size = 248238, upload-time = "2025-11-18T13:32:19.2Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a4/4d88750bcf9d6d66f77865e5a05a20e14db44074c25fd22519777cb69025/coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c", size = 247964, upload-time = "2025-11-18T13:32:21.027Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6b/b74693158899d5b47b0bf6238d2c6722e20ba749f86b74454fac0696bb00/coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984", size = 248862, upload-time = "2025-11-18T13:32:22.304Z" }, - { url = "https://files.pythonhosted.org/packages/18/de/6af6730227ce0e8ade307b1cc4a08e7f51b419a78d02083a86c04ccceb29/coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6", size = 220033, upload-time = "2025-11-18T13:32:23.714Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/e7f63021a7c4fe20994359fcdeae43cbef4a4d0ca36a5a1639feeea5d9e1/coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4", size = 220966, upload-time = "2025-11-18T13:32:25.599Z" }, - { url = "https://files.pythonhosted.org/packages/77/e8/deae26453f37c20c3aa0c4433a1e32cdc169bf415cce223a693117aa3ddd/coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc", size = 219637, upload-time = "2025-11-18T13:32:27.265Z" }, - { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, - { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, - { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, - { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, - { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, - { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, - { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, - { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, +version = "7.13.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, + { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, + { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, + { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, + { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, + { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, + { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, + { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, + { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, + { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, + { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, + { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, + { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, + { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, + { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, + { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, + { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, + { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, + { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, + { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, + { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, + { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, ] [[package]] @@ -452,31 +461,28 @@ wheels = [ [[package]] name = "cython" -version = "3.1.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/f6/d762df1f436a0618455d37f4e4c4872a7cd0dcfc8dec3022ee99e4389c69/cython-3.1.4.tar.gz", hash = "sha256:9aefefe831331e2d66ab31799814eae4d0f8a2d246cbaaaa14d1be29ef777683", size = 3190778, upload-time = "2025-09-16T07:20:33.531Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/ab/0a568bac7c4c052db4ae27edf01e16f3093cdfef04a2dfd313ef1b3c478a/cython-3.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d1d7013dba5fb0506794d4ef8947ff5ed021370614950a8d8d04e57c8c84499e", size = 3026389, upload-time = "2025-09-16T07:22:02.212Z" }, - { url = "https://files.pythonhosted.org/packages/cb/b7/51f5566e1309215a7fef744975b2fabb56d3fdc5fa1922fd7e306c14f523/cython-3.1.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:eed989f5c139d6550ef2665b783d86fab99372590c97f10a3c26c4523c5fce9e", size = 2955954, upload-time = "2025-09-16T07:22:03.782Z" }, - { url = "https://files.pythonhosted.org/packages/28/fd/ad8314520000fe96292fb8208c640fa862baa3053d2f3453a2acb50cafb8/cython-3.1.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3df3beb8b024dfd73cfddb7f2f7456751cebf6e31655eed3189c209b634bc2f2", size = 3412005, upload-time = "2025-09-16T07:22:05.483Z" }, - { url = "https://files.pythonhosted.org/packages/0c/3b/e570f8bcb392e7943fc9a25d1b2d1646ef0148ff017d3681511acf6bbfdc/cython-3.1.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f8354703f1168e1aaa01348940f719734c1f11298be333bdb5b94101d49677c0", size = 3191100, upload-time = "2025-09-16T07:22:07.144Z" }, - { url = "https://files.pythonhosted.org/packages/78/81/f1ea09f563ebab732542cb11bf363710e53f3842458159ea2c160788bc8e/cython-3.1.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a928bd7d446247855f54f359057ab4a32c465219c8c1e299906a483393a59a9e", size = 3313786, upload-time = "2025-09-16T07:22:09.15Z" }, - { url = "https://files.pythonhosted.org/packages/ca/17/06575eb6175a926523bada7dac1cd05cc74add96cebbf2e8b492a2494291/cython-3.1.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c233bfff4cc7b9d629eecb7345f9b733437f76dc4441951ec393b0a6e29919fc", size = 3205775, upload-time = "2025-09-16T07:22:10.745Z" }, - { url = "https://files.pythonhosted.org/packages/10/ba/61a8cf56a76ab21ddf6476b70884feff2a2e56b6d9010e1e1b1e06c46f70/cython-3.1.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e9691a2cbc2faf0cd819108bceccf9bfc56c15a06d172eafe74157388c44a601", size = 3428423, upload-time = "2025-09-16T07:22:12.404Z" }, - { url = "https://files.pythonhosted.org/packages/c4/c2/42cf9239088d6b4b62c1c017c36e0e839f64c8d68674ce4172d0e0168d3b/cython-3.1.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ada319207432ea7c6691c70b5c112d261637d79d21ba086ae3726fedde79bfbf", size = 3330489, upload-time = "2025-09-16T07:22:14.576Z" }, - { url = "https://files.pythonhosted.org/packages/b5/08/36a619d6b1fc671a11744998e5cdd31790589e3cb4542927c97f3f351043/cython-3.1.4-cp311-cp311-win32.whl", hash = "sha256:dae81313c28222bf7be695f85ae1d16625aac35a0973a3af1e001f63379440c5", size = 2482410, upload-time = "2025-09-16T07:22:17.373Z" }, - { url = "https://files.pythonhosted.org/packages/6d/58/7d9ae7944bcd32e6f02d1a8d5d0c3875125227d050e235584127f2c64ffd/cython-3.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:60d2f192059ac34c5c26527f2beac823d34aaa766ef06792a3b7f290c18ac5e2", size = 2713755, upload-time = "2025-09-16T07:22:18.949Z" }, - { url = "https://files.pythonhosted.org/packages/f0/51/2939c739cfdc67ab94935a2c4fcc75638afd15e1954552655503a4112e92/cython-3.1.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d26af46505d0e54fe0f05e7ad089fd0eed8fa04f385f3ab88796f554467bcb9", size = 3062976, upload-time = "2025-09-16T07:22:20.517Z" }, - { url = "https://files.pythonhosted.org/packages/eb/bd/a84de57fd01017bf5dba84a49aeee826db21112282bf8d76ab97567ee15d/cython-3.1.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:66ac8bb5068156c92359e3f0eefa138c177d59d1a2e8a89467881fa7d06aba3b", size = 2970701, upload-time = "2025-09-16T07:22:22.644Z" }, - { url = "https://files.pythonhosted.org/packages/71/79/a09004c8e42f5be188c7636b1be479cdb244a6d8837e1878d062e4e20139/cython-3.1.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2e42714faec723d2305607a04bafb49a48a8d8f25dd39368d884c058dbcfbc", size = 3387730, upload-time = "2025-09-16T07:22:24.271Z" }, - { url = "https://files.pythonhosted.org/packages/fb/bd/979f8c59e247f562642f3eb98a1b453530e1f7954ef071835c08ed2bf6ba/cython-3.1.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0fd655b27997a209a574873304ded9629de588f021154009e8f923475e2c677", size = 3167289, upload-time = "2025-09-16T07:22:26.35Z" }, - { url = "https://files.pythonhosted.org/packages/34/f8/0b98537f0b4e8c01f76d2a6cf75389987538e4d4ac9faf25836fd18c9689/cython-3.1.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9def7c41f4dc339003b1e6875f84edf059989b9c7f5e9a245d3ce12c190742d9", size = 3321099, upload-time = "2025-09-16T07:22:27.957Z" }, - { url = "https://files.pythonhosted.org/packages/f3/39/437968a2e7c7f57eb6e1144f6aca968aa15fbbf169b2d4da5d1ff6c21442/cython-3.1.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:196555584a8716bf7e017e23ca53e9f632ed493f9faa327d0718e7551588f55d", size = 3179897, upload-time = "2025-09-16T07:22:30.014Z" }, - { url = "https://files.pythonhosted.org/packages/2c/04/b3f42915f034d133f1a34e74a2270bc2def02786f9b40dc9028fbb968814/cython-3.1.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7fff0e739e07a20726484b8898b8628a7b87acb960d0fc5486013c6b77b7bb97", size = 3400936, upload-time = "2025-09-16T07:22:31.705Z" }, - { url = "https://files.pythonhosted.org/packages/21/eb/2ad9fa0896ab6cf29875a09a9f4aaea37c28b79b869a013bf9b58e4e652e/cython-3.1.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c2754034fa10f95052949cd6b07eb2f61d654c1b9cfa0b17ea53a269389422e8", size = 3332131, upload-time = "2025-09-16T07:22:33.32Z" }, - { url = "https://files.pythonhosted.org/packages/3c/bf/f19283f8405e7e564c3353302a8665ea2c589be63a8e1be1b503043366a9/cython-3.1.4-cp312-cp312-win32.whl", hash = "sha256:2e0808ff3614a1dbfd1adfcbff9b2b8119292f1824b3535b4a173205109509f8", size = 2487672, upload-time = "2025-09-16T07:22:35.227Z" }, - { url = "https://files.pythonhosted.org/packages/30/bf/32150a2e6c7b50b81c5dc9e942d41969400223a9c49d04e2ed955709894c/cython-3.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:f262b32327b6bce340cce5d45bbfe3972cb62543a4930460d8564a489f3aea12", size = 2705348, upload-time = "2025-09-16T07:22:37.922Z" }, - { url = "https://files.pythonhosted.org/packages/7c/24/f7351052cf9db771fe4f32fca47fd66e6d9b53d8613b17faf7d130a9d553/cython-3.1.4-py3-none-any.whl", hash = "sha256:d194d95e4fa029a3f6c7d46bdd16d973808c7ea4797586911fdb67cb98b1a2c6", size = 1227541, upload-time = "2025-09-16T07:20:29.595Z" }, +version = "3.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/39/e1/c0d92b1258722e1bc62a12e630c33f1f842fdab53fd8cd5de2f75c6449a9/cython-3.2.3.tar.gz", hash = "sha256:f13832412d633376ffc08d751cc18ed0d7d00a398a4065e2871db505258748a6", size = 3276650, upload-time = "2025-12-14T07:50:34.691Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/85/77315c92d29d782bee1b36e30b8d76ad1e731cb7ea0af17e285885f3bb68/cython-3.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c041f7e338cca2422e0924716b04fabeda57636214324fc1941396acce99e7c7", size = 2951618, upload-time = "2025-12-14T07:50:53.883Z" }, + { url = "https://files.pythonhosted.org/packages/cb/dd/a8209e0d424a0207ddb4a3097a97b667027af3cfada762d85f3bed08ccf8/cython-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:283262b8f902323ceb6ed3b643f275a2a963e7ab059f0714a467933383cbc56d", size = 3243636, upload-time = "2025-12-14T07:50:56.346Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2d/bc1927fd7174f7928b86cc9b83589d39592b9273c8b1d2295ca0c0071984/cython-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22a624290c2883387b2c2cfb5224c15bff21432c6a2cf0c23ac8df3dcbd45e96", size = 3378528, upload-time = "2025-12-14T07:50:57.988Z" }, + { url = "https://files.pythonhosted.org/packages/ad/10/5add6a6e1721f9c36b5d5b4f3b75fa7af43196e4f2a474921a7277e31b7a/cython-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:26404441f733fd1cfb0dd9c45477f501437e7d51fad05bb402bd2feb4e127aa3", size = 2769341, upload-time = "2025-12-14T07:50:59.581Z" }, + { url = "https://files.pythonhosted.org/packages/b4/14/d16282d17c9eb2f78ca9ccd5801fed22f6c3360f5a55dbcce3c93cc70352/cython-3.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf210228c15b5c625824d8e31d43b6fea25f9e13c81dac632f2f7d838e0229a5", size = 2968471, upload-time = "2025-12-14T07:51:01.207Z" }, + { url = "https://files.pythonhosted.org/packages/d0/3c/46304a942dac5a636701c55f5b05ec00ad151e6722cd068fe3d0993349bb/cython-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5bf0cebeb4147e172a114437d3fce5a507595d8fdd821be792b1bb25c691514", size = 3223581, upload-time = "2025-12-14T07:51:04.336Z" }, + { url = "https://files.pythonhosted.org/packages/29/ad/15da606d71f40bcf2c405f84ca3d4195cb252f4eaa2f551fe6b2e630ee7c/cython-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1f8700ba89c977438744f083890d87187f15709507a5489e0f6d682053b7fa0", size = 3391391, upload-time = "2025-12-14T07:51:05.998Z" }, + { url = "https://files.pythonhosted.org/packages/51/9e/045b35eb678682edc3e2d57112cf5ac3581a9ef274eb220b638279195678/cython-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:25732f3981a93407826297f4423206e5e22c3cfccfc74e37bf444453bbdc076f", size = 2756814, upload-time = "2025-12-14T07:51:07.759Z" }, + { url = "https://files.pythonhosted.org/packages/43/49/afe1e3df87a770861cf17ba39f4a91f6d22a2571010fc1890b3708360630/cython-3.2.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:74f482da8b605c61b4df6ff716d013f20131949cb2fa59b03e63abd36ef5bac0", size = 2874467, upload-time = "2025-12-14T07:51:31.568Z" }, + { url = "https://files.pythonhosted.org/packages/c7/da/044f725a083e28fb4de5bd33d13ec13f0753734b6ae52d4bc07434610cc8/cython-3.2.3-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a75a04688875b275a6c875565e672325bae04327dd6ec2fc25aeb5c6cf82fce", size = 3211272, upload-time = "2025-12-14T07:51:33.673Z" }, + { url = "https://files.pythonhosted.org/packages/95/14/af02ba6e2e03279f2ca2956e3024a44faed4c8496bda8170b663dc3ba6e8/cython-3.2.3-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b01b36c9eb1b68c25bddbeef7379f7bfc37f7c9afc044e71840ffab761a2dd0", size = 2856058, upload-time = "2025-12-14T07:51:36.015Z" }, + { url = "https://files.pythonhosted.org/packages/69/16/d254359396c2f099ab154f89b2b35f5b8b0dd21a8102c2c96a7e00291434/cython-3.2.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3829f99d611412288f44ff543e9d2b5c0c83274998b2a6680bbe5cca3539c1fd", size = 2993276, upload-time = "2025-12-14T07:51:37.863Z" }, + { url = "https://files.pythonhosted.org/packages/51/0e/1a071381923e896f751f8fbff2a01c5dc8860a8b9a90066f6ec8df561dc4/cython-3.2.3-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c2365a0c79ab9c0fa86d30a4a6ba7e37fc1be9537c48b79b9d63ee7e08bf2fef", size = 2890843, upload-time = "2025-12-14T07:51:40.409Z" }, + { url = "https://files.pythonhosted.org/packages/f4/46/1e93e10766db988e6bb8e5c6f7e2e90b9e62f1ac8dee4c1a6cf1fc170773/cython-3.2.3-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3141734fb15f8b5e9402b9240f8da8336edecae91742b41c85678c31ab68f66d", size = 3225339, upload-time = "2025-12-14T07:51:42.09Z" }, + { url = "https://files.pythonhosted.org/packages/d4/ae/c284b06ae6a9c95d5883bf8744d10466cf0df64cef041a4c80ccf9fd07bd/cython-3.2.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9a24cc653fad3adbd9cbaa638d80df3aa08a1fe27f62eb35850971c70be680df", size = 3114751, upload-time = "2025-12-14T07:51:44.088Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d6/7795a4775c70256217134195f06b07233cf17b00f8905d5b3d782208af64/cython-3.2.3-cp39-abi3-win32.whl", hash = "sha256:b39dff92db70cbd95528f3b81d70e06bd6d3fc9c1dd91321e4d3b999ece3bceb", size = 2435616, upload-time = "2025-12-14T07:51:46.063Z" }, + { url = "https://files.pythonhosted.org/packages/18/9e/2a3edcb858ad74e6274448dccf32150c532bc6e423f112a71f65ff3b5680/cython-3.2.3-cp39-abi3-win_arm64.whl", hash = "sha256:18edc858e6a52de47fe03ffa97ea14dadf450e20069de0a8aef531006c4bbd93", size = 2440952, upload-time = "2025-12-14T07:51:47.943Z" }, + { url = "https://files.pythonhosted.org/packages/e5/41/54fd429ff8147475fc24ca43246f85d78fb4e747c27f227e68f1594648f1/cython-3.2.3-py3-none-any.whl", hash = "sha256:06a1317097f540d3bb6c7b81ed58a0d8b9dbfa97abf39dfd4c22ee87a6c7241e", size = 1255561, upload-time = "2025-12-14T07:50:31.217Z" }, ] [[package]] @@ -490,17 +496,17 @@ wheels = [ [[package]] name = "dearpygui" -version = "2.1.0" +version = "2.1.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/fe/66293fc40254a29f060efd3398f2b1001ed79263ae1837db9ec42caa8f1d/dearpygui-2.1.0-cp311-cp311-macosx_10_6_x86_64.whl", hash = "sha256:03e5dc0b3dd2f7965e50bbe41f3316a814408064b582586de994d93afedb125c", size = 2100924, upload-time = "2025-07-07T14:20:00.602Z" }, - { url = "https://files.pythonhosted.org/packages/c4/4d/9fa1c3156ba7bbf4dc89e2e322998752fccfdc3575923a98dd6a4da48911/dearpygui-2.1.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b5b37710c3fa135c48e2347f39ecd1f415146e86db5d404707a0bf72d16bd304", size = 1874441, upload-time = "2025-07-07T14:20:09.165Z" }, - { url = "https://files.pythonhosted.org/packages/5a/3c/af5673b50699e1734296a0b5bcef39bb6989175b001ad1f9b0e7888ad90d/dearpygui-2.1.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:b0cfd7ac7eaa090fc22d6aa60fc4b527fc631cee10c348e4d8df92bb39af03d2", size = 2636574, upload-time = "2025-07-07T14:20:14.951Z" }, - { url = "https://files.pythonhosted.org/packages/7f/db/ed4db0bb3d88e7a8c405472641419086bef9632c4b8b0489dc0c43519c0d/dearpygui-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:a9af54f96d3ef30c5db9d12cdf3266f005507396fb0da2e12e6b22b662161070", size = 1810266, upload-time = "2025-07-07T14:19:51.565Z" }, - { url = "https://files.pythonhosted.org/packages/55/9d/20a55786cc9d9266395544463d5db3be3528f7d5244bc52ba760de5dcc2d/dearpygui-2.1.0-cp312-cp312-macosx_10_6_x86_64.whl", hash = "sha256:1270ceb9cdb8ecc047c42477ccaa075b7864b314a5d09191f9280a24c8aa90a0", size = 2101499, upload-time = "2025-07-07T14:20:01.701Z" }, - { url = "https://files.pythonhosted.org/packages/a7/b2/39d820796b7ac4d0ebf93306c1f031bf3516b159408286f1fb495c6babeb/dearpygui-2.1.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:ce9969eb62057b9d4c88a8baaed13b5fbe4058caa9faf5b19fec89da75aece3d", size = 1874385, upload-time = "2025-07-07T14:20:11.226Z" }, - { url = "https://files.pythonhosted.org/packages/fc/26/c29998ffeb5eb8d638f307851e51a81c8bd4aeaf89ad660fc67ea4d1ac1a/dearpygui-2.1.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:a3ca8cf788db63ef7e2e8d6f277631b607d548b37606f080ca1b42b1f0a9b183", size = 2635863, upload-time = "2025-07-07T14:20:17.186Z" }, - { url = "https://files.pythonhosted.org/packages/28/9c/3ab33927f1d8c839c5b7033a33d44fc9f0aeb00c264fc9772cb7555a03c4/dearpygui-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:43f0e4db9402f44fc3683a1f5c703564819de18cc15a042de7f1ed1c8cb5d148", size = 1810460, upload-time = "2025-07-07T14:19:53.13Z" }, + { url = "https://files.pythonhosted.org/packages/57/b5/d5bae262633d05f82aeeb3f84ae6240fe3f2955ab6fb4509ebf8048a0b9e/dearpygui-2.1.1-cp311-cp311-macosx_10_6_x86_64.whl", hash = "sha256:8a7c8608b365f4b380b7326679023595fecd04b78c514f2cfd349b0a1108bd0e", size = 2100934, upload-time = "2025-11-14T14:47:38.172Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/75fa9a0f0a7b4b62810cc6f1e8ebaea3df0a825c0adf27d2024aaac2d178/dearpygui-2.1.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:ee87153fdd494ccead8c2345553acd7a9ee61c031e3223f4160aa560709248a3", size = 1895473, upload-time = "2025-11-14T14:47:47.064Z" }, + { url = "https://files.pythonhosted.org/packages/ab/7a/e109e06f8f4379d41a4e672c49aba42e7fcf0eec88056fa06185f4e52c98/dearpygui-2.1.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:964fbb3735017b919efa58104b2d7e9b84a168ff5c1031ae0652d5bc0a48bf5b", size = 2640408, upload-time = "2025-11-14T14:47:53.124Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b5/2ec29d9b47c30ecee96c6f6a0cf229f2898ce3e133a1a0e5b0cd5db82e6b/dearpygui-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:6141184ff59fa4b8df1b81b077cb8cc2b2ef9c0ff92e69c6063062b6d251f426", size = 1808736, upload-time = "2025-11-14T14:47:26.46Z" }, + { url = "https://files.pythonhosted.org/packages/79/41/2146e8d03d28b5a66d5282beb26ffd9ab68a729a29d31e2fe91809271bf5/dearpygui-2.1.1-cp312-cp312-macosx_10_6_x86_64.whl", hash = "sha256:238aea7b4be7376f564dae8edd563b280ec1483a03786022969938507691e017", size = 2101529, upload-time = "2025-11-14T14:47:39.646Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c5/fcc37ef834fe225241aa4f18d77aaa2903134f283077978d65a901c624c6/dearpygui-2.1.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:c27ca6ecd4913555b717f3bb341c0b6a27d6c9fdc9932f0b3c31ae2ef893ae35", size = 1895555, upload-time = "2025-11-14T14:47:48.149Z" }, + { url = "https://files.pythonhosted.org/packages/74/66/19f454ba02d5f03a847cc1dfee4a849cd2307d97add5ba26fecdca318adb/dearpygui-2.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:8c071e9c165d89217bdcdaf769c6069252fcaee50bf369489add524107932273", size = 2641509, upload-time = "2025-11-14T14:47:54.581Z" }, + { url = "https://files.pythonhosted.org/packages/5e/58/d01538556103d544a5a5b4cbcb00646ff92d8a97f0a6283a56bede4307c8/dearpygui-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f2291313d2035f8a4108e13f60d8c1a0e7c19af7554a7739a3fd15b3d5af8f7", size = 1808971, upload-time = "2025-11-14T14:47:28.15Z" }, ] [[package]] @@ -535,11 +541,11 @@ wheels = [ [[package]] name = "execnet" -version = "2.1.1" +version = "2.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/ff/b4c0dc78fbe20c3e59c0c7334de0c27eb4001a2b2017999af398bf730817/execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3", size = 166524, upload-time = "2024-04-08T09:04:19.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/89/780e11f9588d9e7128a3f87788354c7946a9cbb1401ad38a48c4db9a4f07/execnet-2.1.2.tar.gz", hash = "sha256:63d83bfdd9a23e35b9c6a3261412324f964c2ec8dcd8d3c6916ee9373e0befcd", size = 166622, upload-time = "2025-11-12T09:56:37.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/09/2aea36ff60d16dd8879bdb2f5b3ee0ba8d08cbbdcdfe870e695ce3784385/execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc", size = 40612, upload-time = "2024-04-08T09:04:17.414Z" }, + { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] [[package]] @@ -553,79 +559,77 @@ wheels = [ [[package]] name = "filelock" -version = "3.19.1" +version = "3.20.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, ] [[package]] name = "fonttools" -version = "4.60.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/d9/4eabd956fe123651a1f0efe29d9758b3837b5ae9a98934bdb571117033bb/fonttools-4.60.0.tar.gz", hash = "sha256:8f5927f049091a0ca74d35cce7f78e8f7775c83a6901a8fbe899babcc297146a", size = 3553671, upload-time = "2025-09-17T11:34:01.504Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/da/3d/c57731fbbf204ef1045caca28d5176430161ead73cd9feac3e9d9ef77ee6/fonttools-4.60.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a9106c202d68ff5f9b4a0094c4d7ad2eaa7e9280f06427b09643215e706eb016", size = 2830883, upload-time = "2025-09-17T11:32:10.552Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/b7a6ebaed464ce441c755252cc222af11edc651d17c8f26482f429cc2c0e/fonttools-4.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9da3a4a3f2485b156bb429b4f8faa972480fc01f553f7c8c80d05d48f17eec89", size = 2356005, upload-time = "2025-09-17T11:32:13.248Z" }, - { url = "https://files.pythonhosted.org/packages/ee/c2/ea834e921324e2051403e125c1fe0bfbdde4951a7c1784e4ae6bdbd286cc/fonttools-4.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1f84de764c6057b2ffd4feb50ddef481d92e348f0c70f2c849b723118d352bf3", size = 5041201, upload-time = "2025-09-17T11:32:15.373Z" }, - { url = "https://files.pythonhosted.org/packages/93/3c/1c64a338e9aa410d2d0728827d5bb1301463078cb225b94589f27558b427/fonttools-4.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800b3fa0d5c12ddff02179d45b035a23989a6c597a71c8035c010fff3b2ef1bb", size = 4977696, upload-time = "2025-09-17T11:32:17.674Z" }, - { url = "https://files.pythonhosted.org/packages/07/cc/c8c411a0d9732bb886b870e052f20658fec9cf91118314f253950d2c1d65/fonttools-4.60.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd68f60b030277f292a582d31c374edfadc60bb33d51ec7b6cd4304531819ba", size = 5020386, upload-time = "2025-09-17T11:32:20.089Z" }, - { url = "https://files.pythonhosted.org/packages/13/01/1d3bc07cf92e7f4fc27f06d4494bf6078dc595b2e01b959157a4fd23df12/fonttools-4.60.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:53328e3ca9e5c8660ef6de07c35f8f312c189b757535e12141be7a8ec942de6e", size = 5131575, upload-time = "2025-09-17T11:32:22.582Z" }, - { url = "https://files.pythonhosted.org/packages/5a/16/08db3917ee19e89d2eb0ee637d37cd4136c849dc421ff63f406b9165c1a1/fonttools-4.60.0-cp311-cp311-win32.whl", hash = "sha256:d493c175ddd0b88a5376e61163e3e6fde3be8b8987db9b092e0a84650709c9e7", size = 2229297, upload-time = "2025-09-17T11:32:24.834Z" }, - { url = "https://files.pythonhosted.org/packages/d2/0b/76764da82c0dfcea144861f568d9e83f4b921e84f2be617b451257bb25a7/fonttools-4.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cc2770c9dc49c2d0366e9683f4d03beb46c98042d7ccc8ddbadf3459ecb051a7", size = 2277193, upload-time = "2025-09-17T11:32:27.094Z" }, - { url = "https://files.pythonhosted.org/packages/2a/9b/706ebf84b55ab03439c1f3a94d6915123c0d96099f4238b254fdacffe03a/fonttools-4.60.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c68928a438d60dfde90e2f09aa7f848ed201176ca6652341744ceec4215859f", size = 2831953, upload-time = "2025-09-17T11:32:29.39Z" }, - { url = "https://files.pythonhosted.org/packages/76/40/782f485be450846e4f3aecff1f10e42af414fc6e19d235c70020f64278e1/fonttools-4.60.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b7133821249097cffabf0624eafd37f5a3358d5ce814febe9db688e3673e724e", size = 2351716, upload-time = "2025-09-17T11:32:31.46Z" }, - { url = "https://files.pythonhosted.org/packages/39/77/ad8d2a6ecc19716eb488c8cf118de10f7802e14bdf61d136d7b52358d6b1/fonttools-4.60.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:d3638905d3d77ac8791127ce181f7cb434f37e4204d8b2e31b8f1e154320b41f", size = 4922729, upload-time = "2025-09-17T11:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/6b/48/aa543037c6e7788e1bc36b3f858ac70a59d32d0f45915263d0b330a35140/fonttools-4.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7968a26ef010ae89aabbb2f8e9dec1e2709a2541bb8620790451ee8aeb4f6fbf", size = 4967188, upload-time = "2025-09-17T11:32:35.74Z" }, - { url = "https://files.pythonhosted.org/packages/ac/58/e407d2028adc6387947eff8f2940b31f4ed40b9a83c2c7bbc8b9255126e2/fonttools-4.60.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1ef01ca7847c356b0fe026b7b92304bc31dc60a4218689ee0acc66652c1a36b2", size = 4910043, upload-time = "2025-09-17T11:32:38.054Z" }, - { url = "https://files.pythonhosted.org/packages/16/ef/e78519b3c296ef757a21b792fc6a785aa2ef9a2efb098083d8ed5f6ee2ba/fonttools-4.60.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f3482d7ed7867edfcf785f77c1dffc876c4b2ddac19539c075712ff2a0703cf5", size = 5061980, upload-time = "2025-09-17T11:32:40.457Z" }, - { url = "https://files.pythonhosted.org/packages/00/4c/ad72444d1e3ef704ee90af8d5abf198016a39908d322bf41235562fb01a0/fonttools-4.60.0-cp312-cp312-win32.whl", hash = "sha256:8c937c4fe8addff575a984c9519433391180bf52cf35895524a07b520f376067", size = 2217750, upload-time = "2025-09-17T11:32:42.586Z" }, - { url = "https://files.pythonhosted.org/packages/46/55/3e8ac21963e130242f5a9ea2ebc57f5726d704bf4dcca89088b5b637b2d3/fonttools-4.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:99b06d5d6f29f32e312adaed0367112f5ff2d300ea24363d377ec917daf9e8c5", size = 2266025, upload-time = "2025-09-17T11:32:44.8Z" }, - { url = "https://files.pythonhosted.org/packages/f9/a4/247d3e54eb5ed59e94e09866cfc4f9567e274fbf310ba390711851f63b3b/fonttools-4.60.0-py3-none-any.whl", hash = "sha256:496d26e4d14dcccdd6ada2e937e4d174d3138e3d73f5c9b6ec6eb2fd1dab4f66", size = 1142186, upload-time = "2025-09-17T11:33:59.287Z" }, +version = "4.61.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, + { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, + { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, + { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, + { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, + { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, + { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, + { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, + { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, + { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, + { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, + { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, + { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, + { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, ] [[package]] name = "frozenlist" -version = "1.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/b1/b64018016eeb087db503b038296fd782586432b9c077fc5c7839e9cb6ef6/frozenlist-1.7.0.tar.gz", hash = "sha256:2e310d81923c2437ea8670467121cc3e9b0f76d3043cc1d2331d56c7fb7a3a8f", size = 45078, upload-time = "2025-06-09T23:02:35.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/7e/803dde33760128acd393a27eb002f2020ddb8d99d30a44bfbaab31c5f08a/frozenlist-1.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aa51e147a66b2d74de1e6e2cf5921890de6b0f4820b257465101d7f37b49fb5a", size = 82251, upload-time = "2025-06-09T23:00:16.279Z" }, - { url = "https://files.pythonhosted.org/packages/75/a9/9c2c5760b6ba45eae11334db454c189d43d34a4c0b489feb2175e5e64277/frozenlist-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b35db7ce1cd71d36ba24f80f0c9e7cff73a28d7a74e91fe83e23d27c7828750", size = 48183, upload-time = "2025-06-09T23:00:17.698Z" }, - { url = "https://files.pythonhosted.org/packages/47/be/4038e2d869f8a2da165f35a6befb9158c259819be22eeaf9c9a8f6a87771/frozenlist-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34a69a85e34ff37791e94542065c8416c1afbf820b68f720452f636d5fb990cd", size = 47107, upload-time = "2025-06-09T23:00:18.952Z" }, - { url = "https://files.pythonhosted.org/packages/79/26/85314b8a83187c76a37183ceed886381a5f992975786f883472fcb6dc5f2/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a646531fa8d82c87fe4bb2e596f23173caec9185bfbca5d583b4ccfb95183e2", size = 237333, upload-time = "2025-06-09T23:00:20.275Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fd/e5b64f7d2c92a41639ffb2ad44a6a82f347787abc0c7df5f49057cf11770/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:79b2ffbba483f4ed36a0f236ccb85fbb16e670c9238313709638167670ba235f", size = 231724, upload-time = "2025-06-09T23:00:21.705Z" }, - { url = "https://files.pythonhosted.org/packages/20/fb/03395c0a43a5976af4bf7534759d214405fbbb4c114683f434dfdd3128ef/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a26f205c9ca5829cbf82bb2a84b5c36f7184c4316617d7ef1b271a56720d6b30", size = 245842, upload-time = "2025-06-09T23:00:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/d0/15/c01c8e1dffdac5d9803507d824f27aed2ba76b6ed0026fab4d9866e82f1f/frozenlist-1.7.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bcacfad3185a623fa11ea0e0634aac7b691aa925d50a440f39b458e41c561d98", size = 239767, upload-time = "2025-06-09T23:00:25.103Z" }, - { url = "https://files.pythonhosted.org/packages/14/99/3f4c6fe882c1f5514b6848aa0a69b20cb5e5d8e8f51a339d48c0e9305ed0/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72c1b0fe8fe451b34f12dce46445ddf14bd2a5bcad7e324987194dc8e3a74c86", size = 224130, upload-time = "2025-06-09T23:00:27.061Z" }, - { url = "https://files.pythonhosted.org/packages/4d/83/220a374bd7b2aeba9d0725130665afe11de347d95c3620b9b82cc2fcab97/frozenlist-1.7.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:61d1a5baeaac6c0798ff6edfaeaa00e0e412d49946c53fae8d4b8e8b3566c4ae", size = 235301, upload-time = "2025-06-09T23:00:29.02Z" }, - { url = "https://files.pythonhosted.org/packages/03/3c/3e3390d75334a063181625343e8daab61b77e1b8214802cc4e8a1bb678fc/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7edf5c043c062462f09b6820de9854bf28cc6cc5b6714b383149745e287181a8", size = 234606, upload-time = "2025-06-09T23:00:30.514Z" }, - { url = "https://files.pythonhosted.org/packages/23/1e/58232c19608b7a549d72d9903005e2d82488f12554a32de2d5fb59b9b1ba/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:d50ac7627b3a1bd2dcef6f9da89a772694ec04d9a61b66cf87f7d9446b4a0c31", size = 248372, upload-time = "2025-06-09T23:00:31.966Z" }, - { url = "https://files.pythonhosted.org/packages/c0/a4/e4a567e01702a88a74ce8a324691e62a629bf47d4f8607f24bf1c7216e7f/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce48b2fece5aeb45265bb7a58259f45027db0abff478e3077e12b05b17fb9da7", size = 229860, upload-time = "2025-06-09T23:00:33.375Z" }, - { url = "https://files.pythonhosted.org/packages/73/a6/63b3374f7d22268b41a9db73d68a8233afa30ed164c46107b33c4d18ecdd/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:fe2365ae915a1fafd982c146754e1de6ab3478def8a59c86e1f7242d794f97d5", size = 245893, upload-time = "2025-06-09T23:00:35.002Z" }, - { url = "https://files.pythonhosted.org/packages/6d/eb/d18b3f6e64799a79673c4ba0b45e4cfbe49c240edfd03a68be20002eaeaa/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:45a6f2fdbd10e074e8814eb98b05292f27bad7d1883afbe009d96abdcf3bc898", size = 246323, upload-time = "2025-06-09T23:00:36.468Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f5/720f3812e3d06cd89a1d5db9ff6450088b8f5c449dae8ffb2971a44da506/frozenlist-1.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:21884e23cffabb157a9dd7e353779077bf5b8f9a58e9b262c6caad2ef5f80a56", size = 233149, upload-time = "2025-06-09T23:00:37.963Z" }, - { url = "https://files.pythonhosted.org/packages/69/68/03efbf545e217d5db8446acfd4c447c15b7c8cf4dbd4a58403111df9322d/frozenlist-1.7.0-cp311-cp311-win32.whl", hash = "sha256:284d233a8953d7b24f9159b8a3496fc1ddc00f4db99c324bd5fb5f22d8698ea7", size = 39565, upload-time = "2025-06-09T23:00:39.753Z" }, - { url = "https://files.pythonhosted.org/packages/58/17/fe61124c5c333ae87f09bb67186d65038834a47d974fc10a5fadb4cc5ae1/frozenlist-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:387cbfdcde2f2353f19c2f66bbb52406d06ed77519ac7ee21be0232147c2592d", size = 44019, upload-time = "2025-06-09T23:00:40.988Z" }, - { url = "https://files.pythonhosted.org/packages/ef/a2/c8131383f1e66adad5f6ecfcce383d584ca94055a34d683bbb24ac5f2f1c/frozenlist-1.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3dbf9952c4bb0e90e98aec1bd992b3318685005702656bc6f67c1a32b76787f2", size = 81424, upload-time = "2025-06-09T23:00:42.24Z" }, - { url = "https://files.pythonhosted.org/packages/4c/9d/02754159955088cb52567337d1113f945b9e444c4960771ea90eb73de8db/frozenlist-1.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1f5906d3359300b8a9bb194239491122e6cf1444c2efb88865426f170c262cdb", size = 47952, upload-time = "2025-06-09T23:00:43.481Z" }, - { url = "https://files.pythonhosted.org/packages/01/7a/0046ef1bd6699b40acd2067ed6d6670b4db2f425c56980fa21c982c2a9db/frozenlist-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3dabd5a8f84573c8d10d8859a50ea2dec01eea372031929871368c09fa103478", size = 46688, upload-time = "2025-06-09T23:00:44.793Z" }, - { url = "https://files.pythonhosted.org/packages/d6/a2/a910bafe29c86997363fb4c02069df4ff0b5bc39d33c5198b4e9dd42d8f8/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa57daa5917f1738064f302bf2626281a1cb01920c32f711fbc7bc36111058a8", size = 243084, upload-time = "2025-06-09T23:00:46.125Z" }, - { url = "https://files.pythonhosted.org/packages/64/3e/5036af9d5031374c64c387469bfcc3af537fc0f5b1187d83a1cf6fab1639/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c193dda2b6d49f4c4398962810fa7d7c78f032bf45572b3e04dd5249dff27e08", size = 233524, upload-time = "2025-06-09T23:00:47.73Z" }, - { url = "https://files.pythonhosted.org/packages/06/39/6a17b7c107a2887e781a48ecf20ad20f1c39d94b2a548c83615b5b879f28/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe2b675cf0aaa6d61bf8fbffd3c274b3c9b7b1623beb3809df8a81399a4a9c4", size = 248493, upload-time = "2025-06-09T23:00:49.742Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/711d1337c7327d88c44d91dd0f556a1c47fb99afc060ae0ef66b4d24793d/frozenlist-1.7.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8fc5d5cda37f62b262405cf9652cf0856839c4be8ee41be0afe8858f17f4c94b", size = 244116, upload-time = "2025-06-09T23:00:51.352Z" }, - { url = "https://files.pythonhosted.org/packages/24/fe/74e6ec0639c115df13d5850e75722750adabdc7de24e37e05a40527ca539/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0d5ce521d1dd7d620198829b87ea002956e4319002ef0bc8d3e6d045cb4646e", size = 224557, upload-time = "2025-06-09T23:00:52.855Z" }, - { url = "https://files.pythonhosted.org/packages/8d/db/48421f62a6f77c553575201e89048e97198046b793f4a089c79a6e3268bd/frozenlist-1.7.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:488d0a7d6a0008ca0db273c542098a0fa9e7dfaa7e57f70acef43f32b3f69dca", size = 241820, upload-time = "2025-06-09T23:00:54.43Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fa/cb4a76bea23047c8462976ea7b7a2bf53997a0ca171302deae9d6dd12096/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:15a7eaba63983d22c54d255b854e8108e7e5f3e89f647fc854bd77a237e767df", size = 236542, upload-time = "2025-06-09T23:00:56.409Z" }, - { url = "https://files.pythonhosted.org/packages/5d/32/476a4b5cfaa0ec94d3f808f193301debff2ea42288a099afe60757ef6282/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1eaa7e9c6d15df825bf255649e05bd8a74b04a4d2baa1ae46d9c2d00b2ca2cb5", size = 249350, upload-time = "2025-06-09T23:00:58.468Z" }, - { url = "https://files.pythonhosted.org/packages/8d/ba/9a28042f84a6bf8ea5dbc81cfff8eaef18d78b2a1ad9d51c7bc5b029ad16/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4389e06714cfa9d47ab87f784a7c5be91d3934cd6e9a7b85beef808297cc025", size = 225093, upload-time = "2025-06-09T23:01:00.015Z" }, - { url = "https://files.pythonhosted.org/packages/bc/29/3a32959e68f9cf000b04e79ba574527c17e8842e38c91d68214a37455786/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:73bd45e1488c40b63fe5a7df892baf9e2a4d4bb6409a2b3b78ac1c6236178e01", size = 245482, upload-time = "2025-06-09T23:01:01.474Z" }, - { url = "https://files.pythonhosted.org/packages/80/e8/edf2f9e00da553f07f5fa165325cfc302dead715cab6ac8336a5f3d0adc2/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99886d98e1643269760e5fe0df31e5ae7050788dd288947f7f007209b8c33f08", size = 249590, upload-time = "2025-06-09T23:01:02.961Z" }, - { url = "https://files.pythonhosted.org/packages/1c/80/9a0eb48b944050f94cc51ee1c413eb14a39543cc4f760ed12657a5a3c45a/frozenlist-1.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:290a172aae5a4c278c6da8a96222e6337744cd9c77313efe33d5670b9f65fc43", size = 237785, upload-time = "2025-06-09T23:01:05.095Z" }, - { url = "https://files.pythonhosted.org/packages/f3/74/87601e0fb0369b7a2baf404ea921769c53b7ae00dee7dcfe5162c8c6dbf0/frozenlist-1.7.0-cp312-cp312-win32.whl", hash = "sha256:426c7bc70e07cfebc178bc4c2bf2d861d720c4fff172181eeb4a4c41d4ca2ad3", size = 39487, upload-time = "2025-06-09T23:01:06.54Z" }, - { url = "https://files.pythonhosted.org/packages/0b/15/c026e9a9fc17585a9d461f65d8593d281fedf55fbf7eb53f16c6df2392f9/frozenlist-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:563b72efe5da92e02eb68c59cb37205457c977aa7a449ed1b37e6939e5c47c6a", size = 43874, upload-time = "2025-06-09T23:01:07.752Z" }, - { url = "https://files.pythonhosted.org/packages/ee/45/b82e3c16be2182bff01179db177fe144d58b5dc787a7d4492c6ed8b9317f/frozenlist-1.7.0-py3-none-any.whl", hash = "sha256:9a5af342e34f7e97caf8c995864c7a396418ae2859cc6fdf1b1073020d516a7e", size = 13106, upload-time = "2025-06-09T23:02:34.204Z" }, +version = "1.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, + { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, + { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, + { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, + { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, + { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, + { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, + { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, + { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, + { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, + { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, + { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, + { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, + { url = "https://files.pythonhosted.org/packages/6a/bd/d91c5e39f490a49df14320f4e8c80161cfcce09f1e2cde1edd16a551abb3/frozenlist-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:494a5952b1c597ba44e0e78113a7266e656b9794eec897b19ead706bd7074383", size = 242411, upload-time = "2025-10-06T05:36:09.801Z" }, + { url = "https://files.pythonhosted.org/packages/8f/83/f61505a05109ef3293dfb1ff594d13d64a2324ac3482be2cedc2be818256/frozenlist-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:96f423a119f4777a4a056b66ce11527366a8bb92f54e541ade21f2374433f6d4", size = 243014, upload-time = "2025-10-06T05:36:11.394Z" }, + { url = "https://files.pythonhosted.org/packages/d8/cb/cb6c7b0f7d4023ddda30cf56b8b17494eb3a79e3fda666bf735f63118b35/frozenlist-1.8.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3462dd9475af2025c31cc61be6652dfa25cbfb56cbbf52f4ccfe029f38decaf8", size = 234909, upload-time = "2025-10-06T05:36:12.598Z" }, + { url = "https://files.pythonhosted.org/packages/31/c5/cd7a1f3b8b34af009fb17d4123c5a778b44ae2804e3ad6b86204255f9ec5/frozenlist-1.8.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c4c800524c9cd9bac5166cd6f55285957fcfc907db323e193f2afcd4d9abd69b", size = 250049, upload-time = "2025-10-06T05:36:14.065Z" }, + { url = "https://files.pythonhosted.org/packages/c0/01/2f95d3b416c584a1e7f0e1d6d31998c4a795f7544069ee2e0962a4b60740/frozenlist-1.8.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d6a5df73acd3399d893dafc71663ad22534b5aa4f94e8a2fabfe856c3c1b6a52", size = 256485, upload-time = "2025-10-06T05:36:15.39Z" }, + { url = "https://files.pythonhosted.org/packages/ce/03/024bf7720b3abaebcff6d0793d73c154237b85bdf67b7ed55e5e9596dc9a/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:405e8fe955c2280ce66428b3ca55e12b3c4e9c336fb2103a4937e891c69a4a29", size = 237619, upload-time = "2025-10-06T05:36:16.558Z" }, + { url = "https://files.pythonhosted.org/packages/69/fa/f8abdfe7d76b731f5d8bd217827cf6764d4f1d9763407e42717b4bed50a0/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:908bd3f6439f2fef9e85031b59fd4f1297af54415fb60e4254a95f75b3cab3f3", size = 250320, upload-time = "2025-10-06T05:36:17.821Z" }, + { url = "https://files.pythonhosted.org/packages/f5/3c/b051329f718b463b22613e269ad72138cc256c540f78a6de89452803a47d/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:294e487f9ec720bd8ffcebc99d575f7eff3568a08a253d1ee1a0378754b74143", size = 246820, upload-time = "2025-10-06T05:36:19.046Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ae/58282e8f98e444b3f4dd42448ff36fa38bef29e40d40f330b22e7108f565/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:74c51543498289c0c43656701be6b077f4b265868fa7f8a8859c197006efb608", size = 250518, upload-time = "2025-10-06T05:36:20.763Z" }, + { url = "https://files.pythonhosted.org/packages/8f/96/007e5944694d66123183845a106547a15944fbbb7154788cbf7272789536/frozenlist-1.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:776f352e8329135506a1d6bf16ac3f87bc25b28e765949282dcc627af36123aa", size = 239096, upload-time = "2025-10-06T05:36:22.129Z" }, + { url = "https://files.pythonhosted.org/packages/66/bb/852b9d6db2fa40be96f29c0d1205c306288f0684df8fd26ca1951d461a56/frozenlist-1.8.0-cp312-cp312-win32.whl", hash = "sha256:433403ae80709741ce34038da08511d4a77062aa924baf411ef73d1146e74faf", size = 39985, upload-time = "2025-10-06T05:36:23.661Z" }, + { url = "https://files.pythonhosted.org/packages/b8/af/38e51a553dd66eb064cdf193841f16f077585d4d28394c2fa6235cb41765/frozenlist-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:34187385b08f866104f0c0617404c8eb08165ab1272e884abc89c112e9c00746", size = 44591, upload-time = "2025-10-06T05:36:24.958Z" }, + { url = "https://files.pythonhosted.org/packages/a7/06/1dc65480ab147339fecc70797e9c2f69d9cea9cf38934ce08df070fdb9cb/frozenlist-1.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:fe3c58d2f5db5fbd18c2987cba06d51b0529f52bc3a6cdc33d3f4eab725104bd", size = 40102, upload-time = "2025-10-06T05:36:26.333Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] [[package]] @@ -651,37 +655,37 @@ wheels = [ [[package]] name = "google-crc32c" -version = "1.7.1" +version = "1.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495, upload-time = "2025-03-26T14:29:13.32Z" } +sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/94/220139ea87822b6fdfdab4fb9ba81b3fff7ea2c82e2af34adc726085bffc/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:6fbab4b935989e2c3610371963ba1b86afb09537fd0c633049be82afe153ac06", size = 30468, upload-time = "2025-03-26T14:32:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/94/97/789b23bdeeb9d15dc2904660463ad539d0318286d7633fe2760c10ed0c1c/google_crc32c-1.7.1-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:ed66cbe1ed9cbaaad9392b5259b3eba4a9e565420d734e6238813c428c3336c9", size = 30313, upload-time = "2025-03-26T14:57:38.758Z" }, - { url = "https://files.pythonhosted.org/packages/81/b8/976a2b843610c211e7ccb3e248996a61e87dbb2c09b1499847e295080aec/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee6547b657621b6cbed3562ea7826c3e11cab01cd33b74e1f677690652883e77", size = 33048, upload-time = "2025-03-26T14:41:30.679Z" }, - { url = "https://files.pythonhosted.org/packages/c9/16/a3842c2cf591093b111d4a5e2bfb478ac6692d02f1b386d2a33283a19dc9/google_crc32c-1.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d68e17bad8f7dd9a49181a1f5a8f4b251c6dbc8cc96fb79f1d321dfd57d66f53", size = 32669, upload-time = "2025-03-26T14:41:31.432Z" }, - { url = "https://files.pythonhosted.org/packages/04/17/ed9aba495916fcf5fe4ecb2267ceb851fc5f273c4e4625ae453350cfd564/google_crc32c-1.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:6335de12921f06e1f774d0dd1fbea6bf610abe0887a1638f64d694013138be5d", size = 33476, upload-time = "2025-03-26T14:29:10.211Z" }, - { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470, upload-time = "2025-03-26T14:34:31.655Z" }, - { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315, upload-time = "2025-03-26T15:01:54.634Z" }, - { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180, upload-time = "2025-03-26T14:41:32.168Z" }, - { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794, upload-time = "2025-03-26T14:41:33.264Z" }, - { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477, upload-time = "2025-03-26T14:29:10.94Z" }, - { url = "https://files.pythonhosted.org/packages/16/1b/1693372bf423ada422f80fd88260dbfd140754adb15cbc4d7e9a68b1cb8e/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85fef7fae11494e747c9fd1359a527e5970fc9603c90764843caabd3a16a0a48", size = 28241, upload-time = "2025-03-26T14:41:45.898Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3c/2a19a60a473de48717b4efb19398c3f914795b64a96cf3fbe82588044f78/google_crc32c-1.7.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6efb97eb4369d52593ad6f75e7e10d053cf00c48983f7a973105bc70b0ac4d82", size = 28048, upload-time = "2025-03-26T14:41:46.696Z" }, + { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, + { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, + { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, + { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, + { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, + { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, + { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, + { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, + { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, ] [[package]] name = "gymnasium" -version = "1.2.0" +version = "1.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "farama-notifications", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "cloudpickle" }, + { name = "farama-notifications" }, + { name = "numpy" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fd/17/c2a0e15c2cd5a8e788389b280996db927b923410de676ec5c7b2695e9261/gymnasium-1.2.0.tar.gz", hash = "sha256:344e87561012558f603880baf264ebc97f8a5c997a957b0c9f910281145534b0", size = 821142, upload-time = "2025-06-27T08:21:20.262Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/59/653a9417d98ed3e29ef9734ba52c3495f6c6823b8d5c0c75369f25111708/gymnasium-1.2.3.tar.gz", hash = "sha256:2b2cb5b5fbbbdf3afb9f38ca952cc48aa6aa3e26561400d940747fda3ad42509", size = 829230, upload-time = "2025-12-18T16:51:10.234Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e2/a111dbb8625af467ea4760a1373d6ef27aac3137931219902406ccc05423/gymnasium-1.2.0-py3-none-any.whl", hash = "sha256:fc4a1e4121a9464c29b4d7dc6ade3fbeaa36dea448682f5f71a6d2c17489ea76", size = 944301, upload-time = "2025-06-27T08:21:18.83Z" }, + { url = "https://files.pythonhosted.org/packages/56/d3/ea5f088e3638dbab12e5c20d6559d5b3bdaeaa1f2af74e526e6815836285/gymnasium-1.2.3-py3-none-any.whl", hash = "sha256:e6314bba8f549c7fdcc8677f7cd786b64908af6e79b57ddaa5ce1825bffb5373", size = 952113, upload-time = "2025-12-18T16:51:08.445Z" }, ] [[package]] @@ -699,11 +703,11 @@ wheels = [ [[package]] name = "idna" -version = "3.10" +version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload-time = "2024-09-15T18:07:39.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] [[package]] @@ -717,11 +721,11 @@ wheels = [ [[package]] name = "iniconfig" -version = "2.1.0" +version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] @@ -881,67 +885,75 @@ wheels = [ [[package]] name = "mapbox-earcut" -version = "1.0.3" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/70/0a322197c1178f47941e5e6e13b0a4adeaaa7c465c18e3b4ead3eba49860/mapbox_earcut-1.0.3.tar.gz", hash = "sha256:b6bac5d519d9947a6321a699c15d58e0b5740da61b9210ed229e05ad207c1c04", size = 24029, upload-time = "2024-12-25T12:49:09.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/d7/b37a45c248100e7285a40de87a8b1808ca4ca10228e265f2d0c320702d96/mapbox_earcut-1.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bbf24029e7447eb0351000f4fd3185327a00dac5ed756b07330b0bdaed6932db", size = 71057, upload-time = "2024-12-25T12:48:09.131Z" }, - { url = "https://files.pythonhosted.org/packages/1b/df/2b63eb0d3a24e14f67adc816de18c2e09f3eb0997c512ace84dd59c3ed96/mapbox_earcut-1.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:998e2f1e3769538f7656a34296d08a37cb71ce57aa8cf4387572bc00029b52ce", size = 65300, upload-time = "2024-12-25T12:48:11.677Z" }, - { url = "https://files.pythonhosted.org/packages/87/37/9dd9575f5c00e35d480e7150e5bb315a35d9cf5642bfb75ca628a31e1341/mapbox_earcut-1.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2382d84d6d168f73479673d297753e37440772f233cc03ebb54d150e37b174", size = 96965, upload-time = "2024-12-25T12:48:12.968Z" }, - { url = "https://files.pythonhosted.org/packages/3b/91/5708233941b5bf73149ba35f7aa32c6ee2cf4a33cd33069e7dba69d4129f/mapbox_earcut-1.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ccddb4bb04f11beab62943eb5a1bcd52c5a71d236bfce0ecc03e45e97fdb24b", size = 1070953, upload-time = "2024-12-25T12:48:15.495Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fe/b35b999ba786aa17ddc47bc04231de076665eb511e1cd58cf6fef3581172/mapbox_earcut-1.0.3-cp311-cp311-win32.whl", hash = "sha256:f19b2bcf6475bc591f48437d3214691a6730f39b1f6dfd7505b69c4345485b0c", size = 65245, upload-time = "2024-12-25T12:48:17.826Z" }, - { url = "https://files.pythonhosted.org/packages/11/81/18ac08b0bb0c22dd9028c7ecb31ae4086d31128b13fb3903e717331072ac/mapbox_earcut-1.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:811a64ad5e6ecf09b96af533e5c169299ba173e53eb4ff0209de1adcfae314be", size = 72356, upload-time = "2024-12-25T12:48:20.164Z" }, - { url = "https://files.pythonhosted.org/packages/96/7c/707a4ce96e078f7d382cc32b4a6c2326eca68d77ead5e990f5f940d16140/mapbox_earcut-1.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5be71b7ec2180a27ce1178d53933430a3292b6ac3f94f2144513ee51d9034007", size = 70333, upload-time = "2024-12-25T12:48:22.565Z" }, - { url = "https://files.pythonhosted.org/packages/fb/47/ba2a14732f6e197b0ed879a1992b4d85054294b23627ad681b4fb1251d16/mapbox_earcut-1.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:eb874f7562a49ae0fb7bd47bcc9b4854cc53e3e4f7f26674f02f3cadb006ce16", size = 64697, upload-time = "2024-12-25T12:48:25.025Z" }, - { url = "https://files.pythonhosted.org/packages/e7/68/59a514811da76c3c801207bd6d7094ea5ba75648c2e7f15d4cb98b08216f/mapbox_earcut-1.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b9f06f2f8a795d835342aa80e021cfceda78fdca7bc07dc1a0b4aca90239f3", size = 96182, upload-time = "2024-12-25T12:48:26.316Z" }, - { url = "https://files.pythonhosted.org/packages/3f/79/97bf509ade0f9aeb5b5f94b1aff86393c2f584379a80e392fdfcbea434ae/mapbox_earcut-1.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdc55574ef7b613004874a459d2d59c07e1ef45cebb83f86c4958f7d3e2d6069", size = 1070584, upload-time = "2024-12-25T12:48:29.065Z" }, - { url = "https://files.pythonhosted.org/packages/de/7a/5a6e205bab9ff49d1dae392f6179a444f820880d8985f26080816fa6c7ba/mapbox_earcut-1.0.3-cp312-cp312-win32.whl", hash = "sha256:790f52c67a0bd81032eaf61ebc181b1825b8b6daf01cb69e9eaa38521dd07aeb", size = 65375, upload-time = "2024-12-25T12:48:30.618Z" }, - { url = "https://files.pythonhosted.org/packages/7a/59/674a67f92772563d5a943ce2c4ed834ed341e3a0fd77b8eb4b79057f5193/mapbox_earcut-1.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:cc1bbf35be0d9853dd448374330684ddbd0112497dee7d21b7417b0ab6236ac7", size = 72575, upload-time = "2024-12-25T12:48:33.544Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/bc/7b/bbf6b00488662be5d2eb7a188222c264b6f713bac10dc4a77bf37a4cb4b6/mapbox_earcut-2.0.0.tar.gz", hash = "sha256:81eab6b86cf99551deb698b98e3f7502c57900e5c479df15e1bdaf1a57f0f9d6", size = 39934, upload-time = "2025-11-16T18:41:27.251Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/9f/fbd15d9e348e75e986d6912c4eab99888106b7e5fb0a01e765422f7cd464/mapbox_earcut-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:9b5040e79e3783295e99c90277f31c1cbaddd3335297275331995ba5680e3649", size = 55773, upload-time = "2025-11-16T18:40:20.045Z" }, + { url = "https://files.pythonhosted.org/packages/72/40/be761298704fbbaa81c5618bb306f1510fb068e482f6a1c8b3b6c1b31479/mapbox_earcut-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf43baafec3ef1e967319d9b5da96bc6ddf3dbb204b6f3535275eda4b519a72", size = 52444, upload-time = "2025-11-16T18:40:21.501Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0b/0c0c08db9663238ffb82c48259582dc0047a3255d98c0ac83c48026b7544/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a283531847f603dd9d69afb75b21bd009d385ca9485fcd3e5a7fa5db1ccd913", size = 56803, upload-time = "2025-11-16T18:40:22.891Z" }, + { url = "https://files.pythonhosted.org/packages/f0/4a/86796859383d7d11fa5d4bcf1983f94c6cbb9eeb60fb3bab527fec4b32fa/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab697676f4cec4572d4e941b7a3429a6687bf2ac6e8db3f3781024e3239ae3a0", size = 59403, upload-time = "2025-11-16T18:40:24.021Z" }, + { url = "https://files.pythonhosted.org/packages/6c/db/adaf981ab3bcfcf993ef317636b1f27210d6834bb1e8d63db6ad7c08214a/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1bdac76e048f4299accf4eaf797079ddfc330442e7231c15535ed198100d6c5", size = 152876, upload-time = "2025-11-16T18:40:25.588Z" }, + { url = "https://files.pythonhosted.org/packages/d2/83/86417974039e7554c9e1e55c852a7e9c2a1390d64675eb85d70e5fa7eb37/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a6945b23f859bef11ce3194303d17bd371c86b637e7029f81b1feaff3db3758", size = 157548, upload-time = "2025-11-16T18:40:27.202Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4c/c82a292bb21e5c651d81334123db2d654c5c9d19b2197080d3429dc1e49a/mapbox_earcut-2.0.0-cp311-cp311-win32.whl", hash = "sha256:8e119524c29406afb5eaa15e933f297d35679293a3ca62ced22f97a14c484cb5", size = 51424, upload-time = "2025-11-16T18:40:28.415Z" }, + { url = "https://files.pythonhosted.org/packages/30/57/6c39d7db81f72a3e4814ef152c8fb8dfe275dc4b03c9bfa073d251e3755f/mapbox_earcut-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:378bbbb3304e446023752db8f44ecd6e7ef965bcbda36541d2ae64442ba94254", size = 56662, upload-time = "2025-11-16T18:40:29.863Z" }, + { url = "https://files.pythonhosted.org/packages/f4/d6/a1ef6e196b3d6968bf6546d4f7e54c559f9cff8991fdb880df0ba1618f52/mapbox_earcut-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:6d249a431abd6bbff36f1fd0493247a86de962244cc4081b4d5050b02ed48fb1", size = 50505, upload-time = "2025-11-16T18:40:30.992Z" }, + { url = "https://files.pythonhosted.org/packages/8d/93/846804029d955c3c841d8efff77c2b0e8d9aab057d3a077dc8e3f88b5ea4/mapbox_earcut-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db55ce18e698bc9d90914ee7d4f8c3e4d23827456ece7c5d7a1ec91e90c7122b", size = 55623, upload-time = "2025-11-16T18:40:32.113Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f6/cc9ece104bc3876b350dba6fef7f34fb7b20ecc028d2cdbdbecb436b1ed1/mapbox_earcut-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01dd6099d16123baf582a11b2bd1d59ce848498cf0cdca3812fd1f8b20ff33b7", size = 52028, upload-time = "2025-11-16T18:40:33.516Z" }, + { url = "https://files.pythonhosted.org/packages/88/6e/230da4aabcc56c99e9bddb4c43ce7d4ba3609c0caf2d316fb26535d7c60c/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5a098aae26a52282bc981a38e7bf6b889d2ea7442f2cd1903d2ba842f4ff07", size = 56351, upload-time = "2025-11-16T18:40:35.217Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f7/5cdd3752526e91d91336c7263af7767b291d21e63c89d7190a60051f0f87/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de35f241d0b9110ad9260f295acedd9d7cc0d7acfe30d36b1b3ee8419c2caba1", size = 59209, upload-time = "2025-11-16T18:40:36.634Z" }, + { url = "https://files.pythonhosted.org/packages/7b/a2/b7781416cb93b37b95d0444e03f87184de8815e57ff202ce4105fa921325/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cb63ab85e2e430c350f93e75c13f8b91cb8c8a045f3cd714c390b69a720368a", size = 152316, upload-time = "2025-11-16T18:40:38.147Z" }, + { url = "https://files.pythonhosted.org/packages/c1/74/396338e3d345e4e36fb23a0380921098b6a95ce7fb19c4777f4185a5974e/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb3c9f069fc3795306db87f8139f70c4f047532f897a3de05f54dc1faebc97f6", size = 157268, upload-time = "2025-11-16T18:40:39.753Z" }, + { url = "https://files.pythonhosted.org/packages/56/2c/66fd137ea86c508f6cd7247f7f6e2d1dabffc9f0e9ccf14c71406b197af1/mapbox_earcut-2.0.0-cp312-cp312-win32.whl", hash = "sha256:eb290e6676217707ed238dd55e07b0a8ca3ab928f6a27c4afefb2ff3af08d7cb", size = 51226, upload-time = "2025-11-16T18:40:41.018Z" }, + { url = "https://files.pythonhosted.org/packages/b8/84/7b78e37b0c2109243c0dad7d9ba9774b02fcee228bf61cf727a5aa1702e2/mapbox_earcut-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ef5b3319a43375272ad2cad9333ed16e569b5102e32a4241451358897e6f6ee", size = 56417, upload-time = "2025-11-16T18:40:42.173Z" }, + { url = "https://files.pythonhosted.org/packages/75/7f/cd7195aa27c1c8f2b9d38025a5a8663f32cd01c07b648a54b1308ab26c15/mapbox_earcut-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4a3706feb5cc8c782d8f68bb0110c8d551304043f680a87a54b0651a2c208c3", size = 50111, upload-time = "2025-11-16T18:40:43.334Z" }, ] [[package]] name = "markdown" -version = "3.9" +version = "3.10" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8d/37/02347f6d6d8279247a5837082ebc26fc0d5aaeaf75aa013fcbb433c777ab/markdown-3.9.tar.gz", hash = "sha256:d2900fe1782bd33bdbbd56859defef70c2e78fc46668f8eb9df3128138f2cb6a", size = 364585, upload-time = "2025-09-04T20:25:22.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/ae/44c4a6a4cbb496d93c6257954260fe3a6e91b7bed2240e5dad2a717f5111/markdown-3.9-py3-none-any.whl", hash = "sha256:9f4d91ed810864ea88a6f32c07ba8bee1346c0cc1f6b1f9f6c822f2a9667d280", size = 107441, upload-time = "2025-09-04T20:25:21.784Z" }, + { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, ] [[package]] name = "markupsafe" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/28/bbf83e3f76936960b850435576dd5e67034e200469571be53f69174a2dfd/MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d", size = 14353, upload-time = "2024-10-18T15:21:02.187Z" }, - { url = "https://files.pythonhosted.org/packages/6c/30/316d194b093cde57d448a4c3209f22e3046c5bb2fb0820b118292b334be7/MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93", size = 12392, upload-time = "2024-10-18T15:21:02.941Z" }, - { url = "https://files.pythonhosted.org/packages/f2/96/9cdafba8445d3a53cae530aaf83c38ec64c4d5427d975c974084af5bc5d2/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832", size = 23984, upload-time = "2024-10-18T15:21:03.953Z" }, - { url = "https://files.pythonhosted.org/packages/f1/a4/aefb044a2cd8d7334c8a47d3fb2c9f328ac48cb349468cc31c20b539305f/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84", size = 23120, upload-time = "2024-10-18T15:21:06.495Z" }, - { url = "https://files.pythonhosted.org/packages/8d/21/5e4851379f88f3fad1de30361db501300d4f07bcad047d3cb0449fc51f8c/MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca", size = 23032, upload-time = "2024-10-18T15:21:07.295Z" }, - { url = "https://files.pythonhosted.org/packages/00/7b/e92c64e079b2d0d7ddf69899c98842f3f9a60a1ae72657c89ce2655c999d/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798", size = 24057, upload-time = "2024-10-18T15:21:08.073Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ac/46f960ca323037caa0a10662ef97d0a4728e890334fc156b9f9e52bcc4ca/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e", size = 23359, upload-time = "2024-10-18T15:21:09.318Z" }, - { url = "https://files.pythonhosted.org/packages/69/84/83439e16197337b8b14b6a5b9c2105fff81d42c2a7c5b58ac7b62ee2c3b1/MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4", size = 23306, upload-time = "2024-10-18T15:21:10.185Z" }, - { url = "https://files.pythonhosted.org/packages/9a/34/a15aa69f01e2181ed8d2b685c0d2f6655d5cca2c4db0ddea775e631918cd/MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d", size = 15094, upload-time = "2024-10-18T15:21:11.005Z" }, - { url = "https://files.pythonhosted.org/packages/da/b8/3a3bd761922d416f3dc5d00bfbed11f66b1ab89a0c2b6e887240a30b0f6b/MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b", size = 15521, upload-time = "2024-10-18T15:21:12.911Z" }, - { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" }, - { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" }, - { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" }, - { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" }, - { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" }, - { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" }, - { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" }, - { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" }, - { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" }, +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, ] [[package]] name = "matplotlib" -version = "3.10.6" +version = "3.10.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "contourpy" }, @@ -954,25 +966,25 @@ dependencies = [ { name = "pyparsing" }, { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/59/c3e6453a9676ffba145309a73c462bb407f4400de7de3f2b41af70720a3c/matplotlib-3.10.6.tar.gz", hash = "sha256:ec01b645840dd1996df21ee37f208cd8ba57644779fa20464010638013d3203c", size = 34804264, upload-time = "2025-08-30T00:14:25.137Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/d6/5d3665aa44c49005aaacaa68ddea6fcb27345961cd538a98bb0177934ede/matplotlib-3.10.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:905b60d1cb0ee604ce65b297b61cf8be9f4e6cfecf95a3fe1c388b5266bc8f4f", size = 8257527, upload-time = "2025-08-30T00:12:45.31Z" }, - { url = "https://files.pythonhosted.org/packages/8c/af/30ddefe19ca67eebd70047dabf50f899eaff6f3c5e6a1a7edaecaf63f794/matplotlib-3.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7bac38d816637343e53d7185d0c66677ff30ffb131044a81898b5792c956ba76", size = 8119583, upload-time = "2025-08-30T00:12:47.236Z" }, - { url = "https://files.pythonhosted.org/packages/d3/29/4a8650a3dcae97fa4f375d46efcb25920d67b512186f8a6788b896062a81/matplotlib-3.10.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:942a8de2b5bfff1de31d95722f702e2966b8a7e31f4e68f7cd963c7cd8861cf6", size = 8692682, upload-time = "2025-08-30T00:12:48.781Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d3/b793b9cb061cfd5d42ff0f69d1822f8d5dbc94e004618e48a97a8373179a/matplotlib-3.10.6-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3276c85370bc0dfca051ec65c5817d1e0f8f5ce1b7787528ec8ed2d524bbc2f", size = 9521065, upload-time = "2025-08-30T00:12:50.602Z" }, - { url = "https://files.pythonhosted.org/packages/f7/c5/53de5629f223c1c66668d46ac2621961970d21916a4bc3862b174eb2a88f/matplotlib-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9df5851b219225731f564e4b9e7f2ac1e13c9e6481f941b5631a0f8e2d9387ce", size = 9576888, upload-time = "2025-08-30T00:12:52.92Z" }, - { url = "https://files.pythonhosted.org/packages/fc/8e/0a18d6d7d2d0a2e66585032a760d13662e5250c784d53ad50434e9560991/matplotlib-3.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:abb5d9478625dd9c9eb51a06d39aae71eda749ae9b3138afb23eb38824026c7e", size = 8115158, upload-time = "2025-08-30T00:12:54.863Z" }, - { url = "https://files.pythonhosted.org/packages/07/b3/1a5107bb66c261e23b9338070702597a2d374e5aa7004b7adfc754fbed02/matplotlib-3.10.6-cp311-cp311-win_arm64.whl", hash = "sha256:886f989ccfae63659183173bb3fced7fd65e9eb793c3cc21c273add368536951", size = 7992444, upload-time = "2025-08-30T00:12:57.067Z" }, - { url = "https://files.pythonhosted.org/packages/ea/1a/7042f7430055d567cc3257ac409fcf608599ab27459457f13772c2d9778b/matplotlib-3.10.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31ca662df6a80bd426f871105fdd69db7543e28e73a9f2afe80de7e531eb2347", size = 8272404, upload-time = "2025-08-30T00:12:59.112Z" }, - { url = "https://files.pythonhosted.org/packages/a9/5d/1d5f33f5b43f4f9e69e6a5fe1fb9090936ae7bc8e2ff6158e7a76542633b/matplotlib-3.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1678bb61d897bb4ac4757b5ecfb02bfb3fddf7f808000fb81e09c510712fda75", size = 8128262, upload-time = "2025-08-30T00:13:01.141Z" }, - { url = "https://files.pythonhosted.org/packages/67/c3/135fdbbbf84e0979712df58e5e22b4f257b3f5e52a3c4aacf1b8abec0d09/matplotlib-3.10.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:56cd2d20842f58c03d2d6e6c1f1cf5548ad6f66b91e1e48f814e4fb5abd1cb95", size = 8697008, upload-time = "2025-08-30T00:13:03.24Z" }, - { url = "https://files.pythonhosted.org/packages/9c/be/c443ea428fb2488a3ea7608714b1bd85a82738c45da21b447dc49e2f8e5d/matplotlib-3.10.6-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:662df55604a2f9a45435566d6e2660e41efe83cd94f4288dfbf1e6d1eae4b0bb", size = 9530166, upload-time = "2025-08-30T00:13:05.951Z" }, - { url = "https://files.pythonhosted.org/packages/a9/35/48441422b044d74034aea2a3e0d1a49023f12150ebc58f16600132b9bbaf/matplotlib-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:08f141d55148cd1fc870c3387d70ca4df16dee10e909b3b038782bd4bda6ea07", size = 9593105, upload-time = "2025-08-30T00:13:08.356Z" }, - { url = "https://files.pythonhosted.org/packages/45/c3/994ef20eb4154ab84cc08d033834555319e4af970165e6c8894050af0b3c/matplotlib-3.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:590f5925c2d650b5c9d813c5b3b5fc53f2929c3f8ef463e4ecfa7e052044fb2b", size = 8122784, upload-time = "2025-08-30T00:13:10.367Z" }, - { url = "https://files.pythonhosted.org/packages/57/b8/5c85d9ae0e40f04e71bedb053aada5d6bab1f9b5399a0937afb5d6b02d98/matplotlib-3.10.6-cp312-cp312-win_arm64.whl", hash = "sha256:f44c8d264a71609c79a78d50349e724f5d5fc3684ead7c2a473665ee63d868aa", size = 7992823, upload-time = "2025-08-30T00:13:12.24Z" }, - { url = "https://files.pythonhosted.org/packages/12/bb/02c35a51484aae5f49bd29f091286e7af5f3f677a9736c58a92b3c78baeb/matplotlib-3.10.6-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f2d684c3204fa62421bbf770ddfebc6b50130f9cad65531eeba19236d73bb488", size = 8252296, upload-time = "2025-08-30T00:14:19.49Z" }, - { url = "https://files.pythonhosted.org/packages/7d/85/41701e3092005aee9a2445f5ee3904d9dbd4a7df7a45905ffef29b7ef098/matplotlib-3.10.6-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:6f4a69196e663a41d12a728fab8751177215357906436804217d6d9cf0d4d6cf", size = 8116749, upload-time = "2025-08-30T00:14:21.344Z" }, - { url = "https://files.pythonhosted.org/packages/16/53/8d8fa0ea32a8c8239e04d022f6c059ee5e1b77517769feccd50f1df43d6d/matplotlib-3.10.6-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d6ca6ef03dfd269f4ead566ec6f3fb9becf8dab146fb999022ed85ee9f6b3eb", size = 8693933, upload-time = "2025-08-30T00:14:22.942Z" }, + { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, + { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, + { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, + { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, + { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, + { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, + { url = "https://files.pythonhosted.org/packages/00/f9/7638f5cc82ec8a7aa005de48622eecc3ed7c9854b96ba15bd76b7fd27574/matplotlib-3.10.8-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24d50994d8c5816ddc35411e50a86ab05f575e2530c02752e02538122613371f", size = 9550099, upload-time = "2025-12-10T22:55:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, + { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, + { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, + { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, + { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, + { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, ] [[package]] @@ -989,22 +1001,22 @@ name = "metadrive-simulator" version = "0.4.2.4" source = { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" } dependencies = [ - { name = "filelock", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "gymnasium", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "lxml", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "matplotlib", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "opencv-python-headless", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "panda3d-gltf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "pillow", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "progressbar", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "psutil", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "pygments", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "requests", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "shapely", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "tqdm", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "yapf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "filelock" }, + { name = "gymnasium" }, + { name = "lxml" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "panda3d" }, + { name = "panda3d-gltf" }, + { name = "pillow" }, + { name = "progressbar" }, + { name = "psutil" }, + { name = "pygments" }, + { name = "requests" }, + { name = "shapely" }, + { name = "tqdm" }, + { name = "yapf" }, ] wheels = [ { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, @@ -1078,23 +1090,23 @@ wheels = [ [[package]] name = "ml-dtypes" -version = "0.5.3" +version = "0.5.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/a7/aad060393123cfb383956dca68402aff3db1e1caffd5764887ed5153f41b/ml_dtypes-0.5.3.tar.gz", hash = "sha256:95ce33057ba4d05df50b1f3cfefab22e351868a843b3b15a46c65836283670c9", size = 692316, upload-time = "2025-07-29T18:39:19.454Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/f1/720cb1409b5d0c05cff9040c0e9fba73fa4c67897d33babf905d5d46a070/ml_dtypes-0.5.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4a177b882667c69422402df6ed5c3428ce07ac2c1f844d8a1314944651439458", size = 667412, upload-time = "2025-07-29T18:38:25.275Z" }, - { url = "https://files.pythonhosted.org/packages/6a/d5/05861ede5d299f6599f86e6bc1291714e2116d96df003cfe23cc54bcc568/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9849ce7267444c0a717c80c6900997de4f36e2815ce34ac560a3edb2d9a64cd2", size = 4964606, upload-time = "2025-07-29T18:38:27.045Z" }, - { url = "https://files.pythonhosted.org/packages/db/dc/72992b68de367741bfab8df3b3fe7c29f982b7279d341aa5bf3e7ef737ea/ml_dtypes-0.5.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c3f5ae0309d9f888fd825c2e9d0241102fadaca81d888f26f845bc8c13c1e4ee", size = 4938435, upload-time = "2025-07-29T18:38:29.193Z" }, - { url = "https://files.pythonhosted.org/packages/81/1c/d27a930bca31fb07d975a2d7eaf3404f9388114463b9f15032813c98f893/ml_dtypes-0.5.3-cp311-cp311-win_amd64.whl", hash = "sha256:58e39349d820b5702bb6f94ea0cb2dc8ec62ee81c0267d9622067d8333596a46", size = 206334, upload-time = "2025-07-29T18:38:30.687Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d8/6922499effa616012cb8dc445280f66d100a7ff39b35c864cfca019b3f89/ml_dtypes-0.5.3-cp311-cp311-win_arm64.whl", hash = "sha256:66c2756ae6cfd7f5224e355c893cfd617fa2f747b8bbd8996152cbdebad9a184", size = 157584, upload-time = "2025-07-29T18:38:32.187Z" }, - { url = "https://files.pythonhosted.org/packages/0d/eb/bc07c88a6ab002b4635e44585d80fa0b350603f11a2097c9d1bfacc03357/ml_dtypes-0.5.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:156418abeeda48ea4797db6776db3c5bdab9ac7be197c1233771e0880c304057", size = 663864, upload-time = "2025-07-29T18:38:33.777Z" }, - { url = "https://files.pythonhosted.org/packages/cf/89/11af9b0f21b99e6386b6581ab40fb38d03225f9de5f55cf52097047e2826/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1db60c154989af253f6c4a34e8a540c2c9dce4d770784d426945e09908fbb177", size = 4951313, upload-time = "2025-07-29T18:38:36.45Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a9/b98b86426c24900b0c754aad006dce2863df7ce0bb2bcc2c02f9cc7e8489/ml_dtypes-0.5.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1b255acada256d1fa8c35ed07b5f6d18bc21d1556f842fbc2d5718aea2cd9e55", size = 4928805, upload-time = "2025-07-29T18:38:38.29Z" }, - { url = "https://files.pythonhosted.org/packages/50/c1/85e6be4fc09c6175f36fb05a45917837f30af9a5146a5151cb3a3f0f9e09/ml_dtypes-0.5.3-cp312-cp312-win_amd64.whl", hash = "sha256:da65e5fd3eea434ccb8984c3624bc234ddcc0d9f4c81864af611aaebcc08a50e", size = 208182, upload-time = "2025-07-29T18:38:39.72Z" }, - { url = "https://files.pythonhosted.org/packages/9e/17/cf5326d6867be057f232d0610de1458f70a8ce7b6290e4b4a277ea62b4cd/ml_dtypes-0.5.3-cp312-cp312-win_arm64.whl", hash = "sha256:8bb9cd1ce63096567f5f42851f5843b5a0ea11511e50039a7649619abfb4ba6d", size = 161560, upload-time = "2025-07-29T18:38:41.072Z" }, + { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" }, + { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" }, + { url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742, upload-time = "2025-11-17T22:31:44.068Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709, upload-time = "2025-11-17T22:31:46.557Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, ] [[package]] @@ -1119,16 +1131,16 @@ wheels = [ [[package]] name = "msal" -version = "1.33.0" +version = "1.34.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "pyjwt", extra = ["crypto"] }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/da/81acbe0c1fd7e9e4ec35f55dadeba9833a847b9a6ba2e2d1e4432da901dd/msal-1.33.0.tar.gz", hash = "sha256:836ad80faa3e25a7d71015c990ce61f704a87328b1e73bcbb0623a18cbf17510", size = 153801, upload-time = "2025-07-22T19:36:33.693Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/5b/fbc73e91f7727ae1e79b21ed833308e99dc11cc1cd3d4717f579775de5e9/msal-1.33.0-py3-none-any.whl", hash = "sha256:c0cd41cecf8eaed733ee7e3be9e040291eba53b0f262d3ae9c58f38b04244273", size = 116853, upload-time = "2025-07-22T19:36:32.403Z" }, + { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" }, ] [[package]] @@ -1145,47 +1157,47 @@ wheels = [ [[package]] name = "multidict" -version = "6.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/7f/90a7f01e2d005d6653c689039977f6856718c75c5579445effb7e60923d1/multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c", size = 76472, upload-time = "2025-08-11T12:06:29.006Z" }, - { url = "https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb", size = 44634, upload-time = "2025-08-11T12:06:30.374Z" }, - { url = "https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e", size = 44282, upload-time = "2025-08-11T12:06:31.958Z" }, - { url = "https://files.pythonhosted.org/packages/03/35/436a5da8702b06866189b69f655ffdb8f70796252a8772a77815f1812679/multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded", size = 229696, upload-time = "2025-08-11T12:06:33.087Z" }, - { url = "https://files.pythonhosted.org/packages/b6/0e/915160be8fecf1fca35f790c08fb74ca684d752fcba62c11daaf3d92c216/multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683", size = 246665, upload-time = "2025-08-11T12:06:34.448Z" }, - { url = "https://files.pythonhosted.org/packages/08/ee/2f464330acd83f77dcc346f0b1a0eaae10230291450887f96b204b8ac4d3/multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a", size = 225485, upload-time = "2025-08-11T12:06:35.672Z" }, - { url = "https://files.pythonhosted.org/packages/71/cc/9a117f828b4d7fbaec6adeed2204f211e9caf0a012692a1ee32169f846ae/multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9", size = 257318, upload-time = "2025-08-11T12:06:36.98Z" }, - { url = "https://files.pythonhosted.org/packages/25/77/62752d3dbd70e27fdd68e86626c1ae6bccfebe2bb1f84ae226363e112f5a/multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50", size = 254689, upload-time = "2025-08-11T12:06:38.233Z" }, - { url = "https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52", size = 246709, upload-time = "2025-08-11T12:06:39.517Z" }, - { url = "https://files.pythonhosted.org/packages/01/ef/4698d6842ef5e797c6db7744b0081e36fb5de3d00002cc4c58071097fac3/multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6", size = 243185, upload-time = "2025-08-11T12:06:40.796Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c9/d82e95ae1d6e4ef396934e9b0e942dfc428775f9554acf04393cce66b157/multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e", size = 237838, upload-time = "2025-08-11T12:06:42.595Z" }, - { url = "https://files.pythonhosted.org/packages/57/cf/f94af5c36baaa75d44fab9f02e2a6bcfa0cd90acb44d4976a80960759dbc/multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3", size = 246368, upload-time = "2025-08-11T12:06:44.304Z" }, - { url = "https://files.pythonhosted.org/packages/4a/fe/29f23460c3d995f6a4b678cb2e9730e7277231b981f0b234702f0177818a/multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c", size = 253339, upload-time = "2025-08-11T12:06:45.597Z" }, - { url = "https://files.pythonhosted.org/packages/29/b6/fd59449204426187b82bf8a75f629310f68c6adc9559dc922d5abe34797b/multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b", size = 246933, upload-time = "2025-08-11T12:06:46.841Z" }, - { url = "https://files.pythonhosted.org/packages/19/52/d5d6b344f176a5ac3606f7a61fb44dc746e04550e1a13834dff722b8d7d6/multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f", size = 242225, upload-time = "2025-08-11T12:06:48.588Z" }, - { url = "https://files.pythonhosted.org/packages/ec/d3/5b2281ed89ff4d5318d82478a2a2450fcdfc3300da48ff15c1778280ad26/multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2", size = 41306, upload-time = "2025-08-11T12:06:49.95Z" }, - { url = "https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e", size = 46029, upload-time = "2025-08-11T12:06:51.082Z" }, - { url = "https://files.pythonhosted.org/packages/0f/5e/553d67d24432c5cd52b49047f2d248821843743ee6d29a704594f656d182/multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf", size = 43017, upload-time = "2025-08-11T12:06:52.243Z" }, - { url = "https://files.pythonhosted.org/packages/05/f6/512ffd8fd8b37fb2680e5ac35d788f1d71bbaf37789d21a820bdc441e565/multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8", size = 76516, upload-time = "2025-08-11T12:06:53.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/58/45c3e75deb8855c36bd66cc1658007589662ba584dbf423d01df478dd1c5/multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3", size = 45394, upload-time = "2025-08-11T12:06:54.555Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/e8c4472a93a26e4507c0b8e1f0762c0d8a32de1328ef72fd704ef9cc5447/multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b", size = 43591, upload-time = "2025-08-11T12:06:55.672Z" }, - { url = "https://files.pythonhosted.org/packages/05/51/edf414f4df058574a7265034d04c935aa84a89e79ce90fcf4df211f47b16/multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287", size = 237215, upload-time = "2025-08-11T12:06:57.213Z" }, - { url = "https://files.pythonhosted.org/packages/c8/45/8b3d6dbad8cf3252553cc41abea09ad527b33ce47a5e199072620b296902/multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138", size = 258299, upload-time = "2025-08-11T12:06:58.946Z" }, - { url = "https://files.pythonhosted.org/packages/3c/e8/8ca2e9a9f5a435fc6db40438a55730a4bf4956b554e487fa1b9ae920f825/multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6", size = 242357, upload-time = "2025-08-11T12:07:00.301Z" }, - { url = "https://files.pythonhosted.org/packages/0f/84/80c77c99df05a75c28490b2af8f7cba2a12621186e0a8b0865d8e745c104/multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9", size = 268369, upload-time = "2025-08-11T12:07:01.638Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e9/920bfa46c27b05fb3e1ad85121fd49f441492dca2449c5bcfe42e4565d8a/multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c", size = 269341, upload-time = "2025-08-11T12:07:02.943Z" }, - { url = "https://files.pythonhosted.org/packages/af/65/753a2d8b05daf496f4a9c367fe844e90a1b2cac78e2be2c844200d10cc4c/multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402", size = 256100, upload-time = "2025-08-11T12:07:04.564Z" }, - { url = "https://files.pythonhosted.org/packages/09/54/655be13ae324212bf0bc15d665a4e34844f34c206f78801be42f7a0a8aaa/multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7", size = 253584, upload-time = "2025-08-11T12:07:05.914Z" }, - { url = "https://files.pythonhosted.org/packages/5c/74/ab2039ecc05264b5cec73eb018ce417af3ebb384ae9c0e9ed42cb33f8151/multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f", size = 251018, upload-time = "2025-08-11T12:07:08.301Z" }, - { url = "https://files.pythonhosted.org/packages/af/0a/ccbb244ac848e56c6427f2392741c06302bbfba49c0042f1eb3c5b606497/multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d", size = 251477, upload-time = "2025-08-11T12:07:10.248Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b0/0ed49bba775b135937f52fe13922bc64a7eaf0a3ead84a36e8e4e446e096/multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7", size = 263575, upload-time = "2025-08-11T12:07:11.928Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d9/7fb85a85e14de2e44dfb6a24f03c41e2af8697a6df83daddb0e9b7569f73/multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802", size = 259649, upload-time = "2025-08-11T12:07:13.244Z" }, - { url = "https://files.pythonhosted.org/packages/03/9e/b3a459bcf9b6e74fa461a5222a10ff9b544cb1cd52fd482fb1b75ecda2a2/multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24", size = 251505, upload-time = "2025-08-11T12:07:14.57Z" }, - { url = "https://files.pythonhosted.org/packages/86/a2/8022f78f041dfe6d71e364001a5cf987c30edfc83c8a5fb7a3f0974cff39/multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793", size = 41888, upload-time = "2025-08-11T12:07:15.904Z" }, - { url = "https://files.pythonhosted.org/packages/c7/eb/d88b1780d43a56db2cba24289fa744a9d216c1a8546a0dc3956563fd53ea/multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e", size = 46072, upload-time = "2025-08-11T12:07:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/9f/16/b929320bf5750e2d9d4931835a4c638a19d2494a5b519caaaa7492ebe105/multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364", size = 43222, upload-time = "2025-08-11T12:07:18.328Z" }, - { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, +version = "6.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, + { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, + { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, + { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, + { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, + { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, + { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, + { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, + { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, + { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, + { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, + { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, + { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, + { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, + { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, + { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, + { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, + { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, + { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, + { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, + { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, + { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, + { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, + { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, + { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, + { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, + { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, ] [[package]] @@ -1199,44 +1211,44 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" }, - { url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" }, - { url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" }, - { url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" }, - { url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" }, - { url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" }, - { url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" }, - { url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" }, - { url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" }, - { url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" }, - { url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" }, - { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, - { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, - { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, - { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, - { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, - { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, - { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, - { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, - { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, - { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, - { url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" }, - { url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" }, - { url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" }, - { url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" }, - { url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" }, - { url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" }, +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a4/7a/6a3d14e205d292b738db449d0de649b373a59edb0d0b4493821d0a3e8718/numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934", size = 20685720, upload-time = "2025-12-20T16:18:19.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/7e/7bae7cbcc2f8132271967aa03e03954fc1e48aa1f3bf32b29ca95fbef352/numpy-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:316b2f2584682318539f0bcaca5a496ce9ca78c88066579ebd11fd06f8e4741e", size = 16940166, upload-time = "2025-12-20T16:15:43.434Z" }, + { url = "https://files.pythonhosted.org/packages/0f/27/6c13f5b46776d6246ec884ac5817452672156a506d08a1f2abb39961930a/numpy-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2718c1de8504121714234b6f8241d0019450353276c88b9453c9c3d92e101db", size = 12641781, upload-time = "2025-12-20T16:15:45.701Z" }, + { url = "https://files.pythonhosted.org/packages/14/1c/83b4998d4860d15283241d9e5215f28b40ac31f497c04b12fa7f428ff370/numpy-2.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:21555da4ec4a0c942520ead42c3b0dc9477441e085c42b0fbdd6a084869a6f6b", size = 5470247, upload-time = "2025-12-20T16:15:47.943Z" }, + { url = "https://files.pythonhosted.org/packages/54/08/cbce72c835d937795571b0464b52069f869c9e78b0c076d416c5269d2718/numpy-2.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:413aa561266a4be2d06cd2b9665e89d9f54c543f418773076a76adcf2af08bc7", size = 6799807, upload-time = "2025-12-20T16:15:49.795Z" }, + { url = "https://files.pythonhosted.org/packages/ff/be/2e647961cd8c980591d75cdcd9e8f647d69fbe05e2a25613dc0a2ea5fb1a/numpy-2.4.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0feafc9e03128074689183031181fac0897ff169692d8492066e949041096548", size = 14701992, upload-time = "2025-12-20T16:15:51.615Z" }, + { url = "https://files.pythonhosted.org/packages/a2/fb/e1652fb8b6fd91ce6ed429143fe2e01ce714711e03e5b762615e7b36172c/numpy-2.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8fdfed3deaf1928fb7667d96e0567cdf58c2b370ea2ee7e586aa383ec2cb346", size = 16646871, upload-time = "2025-12-20T16:15:54.129Z" }, + { url = "https://files.pythonhosted.org/packages/62/23/d841207e63c4322842f7cd042ae981cffe715c73376dcad8235fb31debf1/numpy-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06a922a469cae9a57100864caf4f8a97a1026513793969f8ba5b63137a35d25", size = 16487190, upload-time = "2025-12-20T16:15:56.147Z" }, + { url = "https://files.pythonhosted.org/packages/bc/a0/6a842c8421ebfdec0a230e65f61e0dabda6edbef443d999d79b87c273965/numpy-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:927ccf5cd17c48f801f4ed43a7e5673a2724bd2171460be3e3894e6e332ef83a", size = 18580762, upload-time = "2025-12-20T16:15:58.524Z" }, + { url = "https://files.pythonhosted.org/packages/0a/d1/c79e0046641186f2134dde05e6181825b911f8bdcef31b19ddd16e232847/numpy-2.4.0-cp311-cp311-win32.whl", hash = "sha256:882567b7ae57c1b1a0250208cc21a7976d8cbcc49d5a322e607e6f09c9e0bd53", size = 6233359, upload-time = "2025-12-20T16:16:00.938Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f0/74965001d231f28184d6305b8cdc1b6fcd4bf23033f6cb039cfe76c9fca7/numpy-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b986403023c8f3bf8f487c2e6186afda156174d31c175f747d8934dfddf3479", size = 12601132, upload-time = "2025-12-20T16:16:02.484Z" }, + { url = "https://files.pythonhosted.org/packages/65/32/55408d0f46dfebce38017f5bd931affa7256ad6beac1a92a012e1fbc67a7/numpy-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:3f3096405acc48887458bbf9f6814d43785ac7ba2a57ea6442b581dedbc60ce6", size = 10573977, upload-time = "2025-12-20T16:16:04.77Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ff/f6400ffec95de41c74b8e73df32e3fff1830633193a7b1e409be7fb1bb8c/numpy-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a8b6bb8369abefb8bd1801b054ad50e02b3275c8614dc6e5b0373c305291037", size = 16653117, upload-time = "2025-12-20T16:16:06.709Z" }, + { url = "https://files.pythonhosted.org/packages/fd/28/6c23e97450035072e8d830a3c411bf1abd1f42c611ff9d29e3d8f55c6252/numpy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e284ca13d5a8367e43734148622caf0b261b275673823593e3e3634a6490f83", size = 12369711, upload-time = "2025-12-20T16:16:08.758Z" }, + { url = "https://files.pythonhosted.org/packages/bc/af/acbef97b630ab1bb45e6a7d01d1452e4251aa88ce680ac36e56c272120ec/numpy-2.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:49ff32b09f5aa0cd30a20c2b39db3e669c845589f2b7fc910365210887e39344", size = 5198355, upload-time = "2025-12-20T16:16:10.902Z" }, + { url = "https://files.pythonhosted.org/packages/c1/c8/4e0d436b66b826f2e53330adaa6311f5cac9871a5b5c31ad773b27f25a74/numpy-2.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:36cbfb13c152b1c7c184ddac43765db8ad672567e7bafff2cc755a09917ed2e6", size = 6545298, upload-time = "2025-12-20T16:16:12.607Z" }, + { url = "https://files.pythonhosted.org/packages/ef/27/e1f5d144ab54eac34875e79037011d511ac57b21b220063310cb96c80fbc/numpy-2.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ddc8f4914466e6fc954c76527aa91aa763682a4f6d73249ef20b418fe6effb", size = 14398387, upload-time = "2025-12-20T16:16:14.257Z" }, + { url = "https://files.pythonhosted.org/packages/67/64/4cb909dd5ab09a9a5d086eff9586e69e827b88a5585517386879474f4cf7/numpy-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc578891de1db95b2a35001b695451767b580bb45753717498213c5ff3c41d63", size = 16363091, upload-time = "2025-12-20T16:16:17.32Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9c/8efe24577523ec6809261859737cf117b0eb6fdb655abdfdc81b2e468ce4/numpy-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98e81648e0b36e325ab67e46b5400a7a6d4a22b8a7c8e8bbfe20e7db7906bf95", size = 16176394, upload-time = "2025-12-20T16:16:19.524Z" }, + { url = "https://files.pythonhosted.org/packages/61/f0/1687441ece7b47a62e45a1f82015352c240765c707928edd8aef875d5951/numpy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d57b5046c120561ba8fa8e4030fbb8b822f3063910fa901ffadf16e2b7128ad6", size = 18287378, upload-time = "2025-12-20T16:16:22.866Z" }, + { url = "https://files.pythonhosted.org/packages/d3/6f/f868765d44e6fc466467ed810ba9d8d6db1add7d4a748abfa2a4c99a3194/numpy-2.4.0-cp312-cp312-win32.whl", hash = "sha256:92190db305a6f48734d3982f2c60fa30d6b5ee9bff10f2887b930d7b40119f4c", size = 5955432, upload-time = "2025-12-20T16:16:25.06Z" }, + { url = "https://files.pythonhosted.org/packages/d4/b5/94c1e79fcbab38d1ca15e13777477b2914dd2d559b410f96949d6637b085/numpy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:680060061adb2d74ce352628cb798cfdec399068aa7f07ba9fb818b2b3305f98", size = 12306201, upload-time = "2025-12-20T16:16:26.979Z" }, + { url = "https://files.pythonhosted.org/packages/70/09/c39dadf0b13bb0768cd29d6a3aaff1fb7c6905ac40e9aaeca26b1c086e06/numpy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:39699233bc72dd482da1415dcb06076e32f60eddc796a796c5fb6c5efce94667", size = 10308234, upload-time = "2025-12-20T16:16:29.417Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ef/088e7c7342f300aaf3ee5f2c821c4b9996a1bef2aaf6a49cc8ab4883758e/numpy-2.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b54c83f1c0c0f1d748dca0af516062b8829d53d1f0c402be24b4257a9c48ada6", size = 16819003, upload-time = "2025-12-20T16:18:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ce/a53017b5443b4b84517182d463fc7bcc2adb4faa8b20813f8e5f5aeb5faa/numpy-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:aabb081ca0ec5d39591fc33018cd4b3f96e1a2dd6756282029986d00a785fba4", size = 12567105, upload-time = "2025-12-20T16:18:05.594Z" }, + { url = "https://files.pythonhosted.org/packages/77/58/5ff91b161f2ec650c88a626c3905d938c89aaadabd0431e6d9c1330c83e2/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:8eafe7c36c8430b7794edeab3087dec7bf31d634d92f2af9949434b9d1964cba", size = 5395590, upload-time = "2025-12-20T16:18:08.031Z" }, + { url = "https://files.pythonhosted.org/packages/1d/4e/f1a084106df8c2df8132fc437e56987308e0524836aa7733721c8429d4fe/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2f585f52b2baf07ff3356158d9268ea095e221371f1074fadea2f42544d58b4d", size = 6709947, upload-time = "2025-12-20T16:18:09.836Z" }, + { url = "https://files.pythonhosted.org/packages/63/09/3d8aeb809c0332c3f642da812ac2e3d74fc9252b3021f8c30c82e99e3f3d/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ed06d0fe9cae27d8fb5f400c63ccee72370599c75e683a6358dd3a4fb50aaf", size = 14535119, upload-time = "2025-12-20T16:18:12.105Z" }, + { url = "https://files.pythonhosted.org/packages/fd/7f/68f0fc43a2cbdc6bb239160c754d87c922f60fbaa0fa3cd3d312b8a7f5ee/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:57c540ed8fb1f05cb997c6761cd56db72395b0d6985e90571ff660452ade4f98", size = 16475815, upload-time = "2025-12-20T16:18:14.433Z" }, + { url = "https://files.pythonhosted.org/packages/11/73/edeacba3167b1ca66d51b1a5a14697c2c40098b5ffa01811c67b1785a5ab/numpy-2.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a39fb973a726e63223287adc6dafe444ce75af952d711e400f3bf2b36ef55a7b", size = 12489376, upload-time = "2025-12-20T16:18:16.524Z" }, ] [[package]] name = "onnx" -version = "1.19.0" +version = "1.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ml-dtypes" }, @@ -1244,20 +1256,20 @@ dependencies = [ { name = "protobuf" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/bf/b0a63ee9f3759dcd177b28c6f2cb22f2aecc6d9b3efecaabc298883caa5f/onnx-1.19.0.tar.gz", hash = "sha256:aa3f70b60f54a29015e41639298ace06adf1dd6b023b9b30f1bca91bb0db9473", size = 11949859, upload-time = "2025-08-27T02:34:27.107Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bd/bf/824b13b7ea14c2d374b48a296cfa412442e5559326fbab5441a4fcb68924/onnx-1.20.0.tar.gz", hash = "sha256:1a93ec69996b4556062d552ed1aa0671978cfd3c17a40bf4c89a1ae169c6a4ad", size = 12049527, upload-time = "2025-12-01T18:14:34.679Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/db/5c/b959b17608cfb6ccf6359b39fe56a5b0b7d965b3d6e6a3c0add90812c36e/onnx-1.19.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:206f00c47b85b5c7af79671e3307147407991a17994c26974565aadc9e96e4e4", size = 18312580, upload-time = "2025-08-27T02:33:03.081Z" }, - { url = "https://files.pythonhosted.org/packages/2c/ee/ac052bbbc832abe0debb784c2c57f9582444fb5f51d63c2967fd04432444/onnx-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4d7bee94abaac28988b50da675ae99ef8dd3ce16210d591fbd0b214a5930beb3", size = 18029165, upload-time = "2025-08-27T02:33:05.771Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c9/8687ba0948d46fd61b04e3952af9237883bbf8f16d716e7ed27e688d73b8/onnx-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7730b96b68c0c354bbc7857961bb4909b9aaa171360a8e3708d0a4c749aaadeb", size = 18202125, upload-time = "2025-08-27T02:33:09.325Z" }, - { url = "https://files.pythonhosted.org/packages/e2/16/6249c013e81bd689f46f96c7236d7677f1af5dd9ef22746716b48f10e506/onnx-1.19.0-cp311-cp311-win32.whl", hash = "sha256:7cb7a3ad8059d1a0dfdc5e0a98f71837d82002e441f112825403b137227c2c97", size = 16332738, upload-time = "2025-08-27T02:33:12.448Z" }, - { url = "https://files.pythonhosted.org/packages/6a/28/34a1e2166e418c6a78e5c82e66f409d9da9317832f11c647f7d4e23846a6/onnx-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:d75452a9be868bd30c3ef6aa5991df89bbfe53d0d90b2325c5e730fbd91fff85", size = 16452303, upload-time = "2025-08-27T02:33:15.176Z" }, - { url = "https://files.pythonhosted.org/packages/e6/b7/639664626e5ba8027860c4d2a639ee02b37e9c322215c921e9222513c3aa/onnx-1.19.0-cp311-cp311-win_arm64.whl", hash = "sha256:23c7959370d7b3236f821e609b0af7763cff7672a758e6c1fc877bac099e786b", size = 16425340, upload-time = "2025-08-27T02:33:17.78Z" }, - { url = "https://files.pythonhosted.org/packages/0d/94/f56f6ca5e2f921b28c0f0476705eab56486b279f04e1d568ed64c14e7764/onnx-1.19.0-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:61d94e6498ca636756f8f4ee2135708434601b2892b7c09536befb19bc8ca007", size = 18322331, upload-time = "2025-08-27T02:33:20.373Z" }, - { url = "https://files.pythonhosted.org/packages/c8/00/8cc3f3c40b54b28f96923380f57c9176872e475face726f7d7a78bd74098/onnx-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:224473354462f005bae985c72028aaa5c85ab11de1b71d55b06fdadd64a667dd", size = 18027513, upload-time = "2025-08-27T02:33:23.44Z" }, - { url = "https://files.pythonhosted.org/packages/61/90/17c4d2566fd0117a5e412688c9525f8950d467f477fbd574e6b32bc9cb8d/onnx-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae475c85c89bc4d1f16571006fd21a3e7c0e258dd2c091f6e8aafb083d1ed9b", size = 18202278, upload-time = "2025-08-27T02:33:26.103Z" }, - { url = "https://files.pythonhosted.org/packages/bc/6e/a9383d9cf6db4ac761a129b081e9fa5d0cd89aad43cf1e3fc6285b915c7d/onnx-1.19.0-cp312-cp312-win32.whl", hash = "sha256:323f6a96383a9cdb3960396cffea0a922593d221f3929b17312781e9f9b7fb9f", size = 16333080, upload-time = "2025-08-27T02:33:28.559Z" }, - { url = "https://files.pythonhosted.org/packages/a7/2e/3ff480a8c1fa7939662bdc973e41914add2d4a1f2b8572a3c39c2e4982e5/onnx-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:50220f3499a499b1a15e19451a678a58e22ad21b34edf2c844c6ef1d9febddc2", size = 16453927, upload-time = "2025-08-27T02:33:31.177Z" }, - { url = "https://files.pythonhosted.org/packages/57/37/ad500945b1b5c154fe9d7b826b30816ebd629d10211ea82071b5bcc30aa4/onnx-1.19.0-cp312-cp312-win_arm64.whl", hash = "sha256:efb768299580b786e21abe504e1652ae6189f0beed02ab087cd841cb4bb37e43", size = 16426022, upload-time = "2025-08-27T02:33:33.515Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9a/125ad5ed919d1782b26b0b4404e51adc44afd029be30d5a81b446dccd9c5/onnx-1.20.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:00dc8ae2c7b283f79623961f450b5515bd2c4b47a7027e7a1374ba49cef27768", size = 18341929, upload-time = "2025-12-01T18:13:43.79Z" }, + { url = "https://files.pythonhosted.org/packages/4d/3c/85280dd05396493f3e1b4feb7a3426715e344b36083229437f31d9788a01/onnx-1.20.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f62978ecfb8f320faba6704abd20253a5a79aacc4e5d39a9c061dd63d3b7574f", size = 17899362, upload-time = "2025-12-01T18:13:46.496Z" }, + { url = "https://files.pythonhosted.org/packages/26/db/e11cf9aaa6ccbcd27ea94d321020fef3207cba388bff96111e6431f97d1a/onnx-1.20.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71177f8fd5c0dd90697bc281f5035f73707bdac83257a5c54d74403a1100ace9", size = 18119129, upload-time = "2025-12-01T18:13:49.662Z" }, + { url = "https://files.pythonhosted.org/packages/ef/0b/1b99e7ba5ccfa8ecb3509ec579c8520098d09b903ccd520026d60faa7c75/onnx-1.20.0-cp311-cp311-win32.whl", hash = "sha256:1d3d0308e2c194f4b782f51e78461b567fac8ce6871c0cf5452ede261683cc8f", size = 16364604, upload-time = "2025-12-01T18:13:52.691Z" }, + { url = "https://files.pythonhosted.org/packages/51/ab/7399817821d0d18ff67292ac183383e41f4f4ddff2047902f1b7b51d2d40/onnx-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a6de7dda77926c323b0e5a830dc9c2866ce350c1901229e193be1003a076c25", size = 16488019, upload-time = "2025-12-01T18:13:55.776Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/23059c11d9c0fb1951acec504a5cc86e1dd03d2eef3a98cf1941839f5322/onnx-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:afc4cf83ce5d547ebfbb276dae8eb0ec836254a8698d462b4ba5f51e717fd1ae", size = 16446841, upload-time = "2025-12-01T18:13:58.091Z" }, + { url = "https://files.pythonhosted.org/packages/5e/19/2caa972a31014a8cb4525f715f2a75d93caef9d4b9da2809cc05d0489e43/onnx-1.20.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:31efe37d7d1d659091f34ddd6a31780334acf7c624176832db9a0a8ececa8fb5", size = 18340913, upload-time = "2025-12-01T18:14:00.477Z" }, + { url = "https://files.pythonhosted.org/packages/78/bb/b98732309f2f6beb4cdcf7b955d7bbfd75a191185370ee21233373db381e/onnx-1.20.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d75da05e743eb9a11ff155a775cae5745e71f1cd0ca26402881b8f20e8d6e449", size = 17896118, upload-time = "2025-12-01T18:14:03.239Z" }, + { url = "https://files.pythonhosted.org/packages/84/a7/38aa564871d062c11538d65c575af9c7e057be880c09ecbd899dd1abfa83/onnx-1.20.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02e0d72ab09a983fce46686b155a5049898558d9f3bc6e8515120d6c40666318", size = 18115415, upload-time = "2025-12-01T18:14:06.261Z" }, + { url = "https://files.pythonhosted.org/packages/3b/17/a600b62cf4ad72976c66f83ce9e324205af434706ad5ec0e35129e125aef/onnx-1.20.0-cp312-abi3-win32.whl", hash = "sha256:392ca68b34b97e172d33b507e1e7bfdf2eea96603e6e7ff109895b82ff009dc7", size = 16363019, upload-time = "2025-12-01T18:14:09.16Z" }, + { url = "https://files.pythonhosted.org/packages/9c/3b/5146ba0a89f73c026bb468c49612bab8d005aa28155ebf06cf5f2eb8d36c/onnx-1.20.0-cp312-abi3-win_amd64.whl", hash = "sha256:259b05758d41645f5545c09f887187662b350d40db8d707c33c94a4f398e1733", size = 16485934, upload-time = "2025-12-01T18:14:13.046Z" }, + { url = "https://files.pythonhosted.org/packages/f3/bc/d251b97395e721b3034e9578d4d4d9fb33aac4197ae16ce8c7ed79a26dce/onnx-1.20.0-cp312-abi3-win_arm64.whl", hash = "sha256:2d25a9e1fde44bc69988e50e2211f62d6afcd01b0fd6dfd23429fd978a35d32f", size = 16444946, upload-time = "2025-12-01T18:14:15.801Z" }, ] [[package]] @@ -1481,8 +1493,8 @@ name = "panda3d-gltf" version = "0.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "panda3d-simplepbr", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d" }, + { name = "panda3d-simplepbr" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/7f/9f18fc3fa843a080acb891af6bcc12262e7bdf1d194a530f7042bebfc81f/panda3d-gltf-0.13.tar.gz", hash = "sha256:d06d373bdd91cf530909b669f43080e599463bbf6d3ef00c3558bad6c6b19675", size = 25573, upload-time = "2021-05-21T05:46:32.738Z" } wheels = [ @@ -1494,8 +1506,8 @@ name = "panda3d-simplepbr" version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/be/c4d1ded04c22b357277cf6e6a44c1ab4abb285a700bd1991460460e05b99/panda3d_simplepbr-0.13.1.tar.gz", hash = "sha256:c83766d7c8f47499f365a07fe1dff078fc8b3054c2689bdc8dceabddfe7f1a35", size = 6216055, upload-time = "2025-03-30T16:57:41.087Z" } wheels = [ @@ -1522,48 +1534,48 @@ wheels = [ [[package]] name = "pillow" -version = "11.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/d0d6dea55cd152ce3d6767bb38a8fc10e33796ba4ba210cbab9354b6d238/pillow-11.3.0.tar.gz", hash = "sha256:3828ee7586cd0b2091b6209e5ad53e20d0649bbe87164a459d0676e035e8f523", size = 47113069, upload-time = "2025-07-01T09:16:30.666Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/26/77f8ed17ca4ffd60e1dcd220a6ec6d71210ba398cfa33a13a1cd614c5613/pillow-11.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1cd110edf822773368b396281a2293aeb91c90a2db00d78ea43e7e861631b722", size = 5316531, upload-time = "2025-07-01T09:13:59.203Z" }, - { url = "https://files.pythonhosted.org/packages/cb/39/ee475903197ce709322a17a866892efb560f57900d9af2e55f86db51b0a5/pillow-11.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c412fddd1b77a75aa904615ebaa6001f169b26fd467b4be93aded278266b288", size = 4686560, upload-time = "2025-07-01T09:14:01.101Z" }, - { url = "https://files.pythonhosted.org/packages/d5/90/442068a160fd179938ba55ec8c97050a612426fae5ec0a764e345839f76d/pillow-11.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1aa4de119a0ecac0a34a9c8bde33f34022e2e8f99104e47a3ca392fd60e37d", size = 5870978, upload-time = "2025-07-03T13:09:55.638Z" }, - { url = "https://files.pythonhosted.org/packages/13/92/dcdd147ab02daf405387f0218dcf792dc6dd5b14d2573d40b4caeef01059/pillow-11.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:91da1d88226663594e3f6b4b8c3c8d85bd504117d043740a8e0ec449087cc494", size = 7641168, upload-time = "2025-07-03T13:10:00.37Z" }, - { url = "https://files.pythonhosted.org/packages/6e/db/839d6ba7fd38b51af641aa904e2960e7a5644d60ec754c046b7d2aee00e5/pillow-11.3.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:643f189248837533073c405ec2f0bb250ba54598cf80e8c1e043381a60632f58", size = 5973053, upload-time = "2025-07-01T09:14:04.491Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2f/d7675ecae6c43e9f12aa8d58b6012683b20b6edfbdac7abcb4e6af7a3784/pillow-11.3.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:106064daa23a745510dabce1d84f29137a37224831d88eb4ce94bb187b1d7e5f", size = 6640273, upload-time = "2025-07-01T09:14:06.235Z" }, - { url = "https://files.pythonhosted.org/packages/45/ad/931694675ede172e15b2ff03c8144a0ddaea1d87adb72bb07655eaffb654/pillow-11.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd8ff254faf15591e724dc7c4ddb6bf4793efcbe13802a4ae3e863cd300b493e", size = 6082043, upload-time = "2025-07-01T09:14:07.978Z" }, - { url = "https://files.pythonhosted.org/packages/3a/04/ba8f2b11fc80d2dd462d7abec16351b45ec99cbbaea4387648a44190351a/pillow-11.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:932c754c2d51ad2b2271fd01c3d121daaa35e27efae2a616f77bf164bc0b3e94", size = 6715516, upload-time = "2025-07-01T09:14:10.233Z" }, - { url = "https://files.pythonhosted.org/packages/48/59/8cd06d7f3944cc7d892e8533c56b0acb68399f640786313275faec1e3b6f/pillow-11.3.0-cp311-cp311-win32.whl", hash = "sha256:b4b8f3efc8d530a1544e5962bd6b403d5f7fe8b9e08227c6b255f98ad82b4ba0", size = 6274768, upload-time = "2025-07-01T09:14:11.921Z" }, - { url = "https://files.pythonhosted.org/packages/f1/cc/29c0f5d64ab8eae20f3232da8f8571660aa0ab4b8f1331da5c2f5f9a938e/pillow-11.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:1a992e86b0dd7aeb1f053cd506508c0999d710a8f07b4c791c63843fc6a807ac", size = 6986055, upload-time = "2025-07-01T09:14:13.623Z" }, - { url = "https://files.pythonhosted.org/packages/c6/df/90bd886fabd544c25addd63e5ca6932c86f2b701d5da6c7839387a076b4a/pillow-11.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:30807c931ff7c095620fe04448e2c2fc673fcbb1ffe2a7da3fb39613489b1ddd", size = 2423079, upload-time = "2025-07-01T09:14:15.268Z" }, - { url = "https://files.pythonhosted.org/packages/40/fe/1bc9b3ee13f68487a99ac9529968035cca2f0a51ec36892060edcc51d06a/pillow-11.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdae223722da47b024b867c1ea0be64e0df702c5e0a60e27daad39bf960dd1e4", size = 5278800, upload-time = "2025-07-01T09:14:17.648Z" }, - { url = "https://files.pythonhosted.org/packages/2c/32/7e2ac19b5713657384cec55f89065fb306b06af008cfd87e572035b27119/pillow-11.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:921bd305b10e82b4d1f5e802b6850677f965d8394203d182f078873851dada69", size = 4686296, upload-time = "2025-07-01T09:14:19.828Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1e/b9e12bbe6e4c2220effebc09ea0923a07a6da1e1f1bfbc8d7d29a01ce32b/pillow-11.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb76541cba2f958032d79d143b98a3a6b3ea87f0959bbe256c0b5e416599fd5d", size = 5871726, upload-time = "2025-07-03T13:10:04.448Z" }, - { url = "https://files.pythonhosted.org/packages/8d/33/e9200d2bd7ba00dc3ddb78df1198a6e80d7669cce6c2bdbeb2530a74ec58/pillow-11.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:67172f2944ebba3d4a7b54f2e95c786a3a50c21b88456329314caaa28cda70f6", size = 7644652, upload-time = "2025-07-03T13:10:10.391Z" }, - { url = "https://files.pythonhosted.org/packages/41/f1/6f2427a26fc683e00d985bc391bdd76d8dd4e92fac33d841127eb8fb2313/pillow-11.3.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f07ed9f56a3b9b5f49d3661dc9607484e85c67e27f3e8be2c7d28ca032fec7", size = 5977787, upload-time = "2025-07-01T09:14:21.63Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c9/06dd4a38974e24f932ff5f98ea3c546ce3f8c995d3f0985f8e5ba48bba19/pillow-11.3.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:676b2815362456b5b3216b4fd5bd89d362100dc6f4945154ff172e206a22c024", size = 6645236, upload-time = "2025-07-01T09:14:23.321Z" }, - { url = "https://files.pythonhosted.org/packages/40/e7/848f69fb79843b3d91241bad658e9c14f39a32f71a301bcd1d139416d1be/pillow-11.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3e184b2f26ff146363dd07bde8b711833d7b0202e27d13540bfe2e35a323a809", size = 6086950, upload-time = "2025-07-01T09:14:25.237Z" }, - { url = "https://files.pythonhosted.org/packages/0b/1a/7cff92e695a2a29ac1958c2a0fe4c0b2393b60aac13b04a4fe2735cad52d/pillow-11.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6be31e3fc9a621e071bc17bb7de63b85cbe0bfae91bb0363c893cbe67247780d", size = 6723358, upload-time = "2025-07-01T09:14:27.053Z" }, - { url = "https://files.pythonhosted.org/packages/26/7d/73699ad77895f69edff76b0f332acc3d497f22f5d75e5360f78cbcaff248/pillow-11.3.0-cp312-cp312-win32.whl", hash = "sha256:7b161756381f0918e05e7cb8a371fff367e807770f8fe92ecb20d905d0e1c149", size = 6275079, upload-time = "2025-07-01T09:14:30.104Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ce/e7dfc873bdd9828f3b6e5c2bbb74e47a98ec23cc5c74fc4e54462f0d9204/pillow-11.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a6444696fce635783440b7f7a9fc24b3ad10a9ea3f0ab66c5905be1c19ccf17d", size = 6986324, upload-time = "2025-07-01T09:14:31.899Z" }, - { url = "https://files.pythonhosted.org/packages/16/8f/b13447d1bf0b1f7467ce7d86f6e6edf66c0ad7cf44cf5c87a37f9bed9936/pillow-11.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:2aceea54f957dd4448264f9bf40875da0415c83eb85f55069d89c0ed436e3542", size = 2423067, upload-time = "2025-07-01T09:14:33.709Z" }, - { url = "https://files.pythonhosted.org/packages/9e/e3/6fa84033758276fb31da12e5fb66ad747ae83b93c67af17f8c6ff4cc8f34/pillow-11.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7c8ec7a017ad1bd562f93dbd8505763e688d388cde6e4a010ae1486916e713e6", size = 5270566, upload-time = "2025-07-01T09:16:19.801Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ee/e8d2e1ab4892970b561e1ba96cbd59c0d28cf66737fc44abb2aec3795a4e/pillow-11.3.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9ab6ae226de48019caa8074894544af5b53a117ccb9d3b3dcb2871464c829438", size = 4654618, upload-time = "2025-07-01T09:16:21.818Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6d/17f80f4e1f0761f02160fc433abd4109fa1548dcfdca46cfdadaf9efa565/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe27fb049cdcca11f11a7bfda64043c37b30e6b91f10cb5bab275806c32f6ab3", size = 4874248, upload-time = "2025-07-03T13:11:20.738Z" }, - { url = "https://files.pythonhosted.org/packages/de/5f/c22340acd61cef960130585bbe2120e2fd8434c214802f07e8c03596b17e/pillow-11.3.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:465b9e8844e3c3519a983d58b80be3f668e2a7a5db97f2784e7079fbc9f9822c", size = 6583963, upload-time = "2025-07-03T13:11:26.283Z" }, - { url = "https://files.pythonhosted.org/packages/31/5e/03966aedfbfcbb4d5f8aa042452d3361f325b963ebbadddac05b122e47dd/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5418b53c0d59b3824d05e029669efa023bbef0f3e92e75ec8428f3799487f361", size = 4957170, upload-time = "2025-07-01T09:16:23.762Z" }, - { url = "https://files.pythonhosted.org/packages/cc/2d/e082982aacc927fc2cab48e1e731bdb1643a1406acace8bed0900a61464e/pillow-11.3.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:504b6f59505f08ae014f724b6207ff6222662aab5cc9542577fb084ed0676ac7", size = 5581505, upload-time = "2025-07-01T09:16:25.593Z" }, - { url = "https://files.pythonhosted.org/packages/34/e7/ae39f538fd6844e982063c3a5e4598b8ced43b9633baa3a85ef33af8c05c/pillow-11.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c84d689db21a1c397d001aa08241044aa2069e7587b398c8cc63020390b1c1b8", size = 6984598, upload-time = "2025-07-01T09:16:27.732Z" }, +version = "12.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, + { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, + { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, + { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, + { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, + { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, + { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, + { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, + { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, + { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, + { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, + { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, + { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, + { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, + { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, + { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, + { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, + { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, + { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, + { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, ] [[package]] name = "platformdirs" -version = "4.4.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/e8/21db9c9987b0e728855bd57bff6984f67952bea55d6f75e055c46b5383e8/platformdirs-4.4.0.tar.gz", hash = "sha256:ca753cf4d81dc309bc67b0ea38fd15dc97bc30ce419a7f58d13eb3bf14c4febf", size = 21634, upload-time = "2025-08-26T14:32:04.268Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/4b/2028861e724d3bd36227adfa20d3fd24c3fc6d52032f4a93c133be5d17ce/platformdirs-4.4.0-py3-none-any.whl", hash = "sha256:abd01743f24e5287cd7a5db3752faf1a2d65353f38ec26d98e25a6db65958c85", size = 18654, upload-time = "2025-08-26T14:32:02.735Z" }, + { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] [[package]] @@ -1595,73 +1607,72 @@ sdist = { url = "https://files.pythonhosted.org/packages/a3/a6/b8e451f6cff1c99b4 [[package]] name = "propcache" -version = "0.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a6/16/43264e4a779dd8588c21a70f0709665ee8f611211bdd2c87d952cfa7c776/propcache-0.3.2.tar.gz", hash = "sha256:20d7d62e4e7ef05f221e0db2856b979540686342e7dd9973b815599c7057e168", size = 44139, upload-time = "2025-06-09T22:56:06.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/80/8d/e8b436717ab9c2cfc23b116d2c297305aa4cd8339172a456d61ebf5669b8/propcache-0.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b8d2f607bd8f80ddc04088bc2a037fdd17884a6fcadc47a96e334d72f3717be", size = 74207, upload-time = "2025-06-09T22:54:05.399Z" }, - { url = "https://files.pythonhosted.org/packages/d6/29/1e34000e9766d112171764b9fa3226fa0153ab565d0c242c70e9945318a7/propcache-0.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:06766d8f34733416e2e34f46fea488ad5d60726bb9481d3cddf89a6fa2d9603f", size = 43648, upload-time = "2025-06-09T22:54:08.023Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/1ad5af0df781e76988897da39b5f086c2bf0f028b7f9bd1f409bb05b6874/propcache-0.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2dc1f4a1df4fecf4e6f68013575ff4af84ef6f478fe5344317a65d38a8e6dc9", size = 43496, upload-time = "2025-06-09T22:54:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ce/e96392460f9fb68461fabab3e095cb00c8ddf901205be4eae5ce246e5b7e/propcache-0.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be29c4f4810c5789cf10ddf6af80b041c724e629fa51e308a7a0fb19ed1ef7bf", size = 217288, upload-time = "2025-06-09T22:54:10.466Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2a/866726ea345299f7ceefc861a5e782b045545ae6940851930a6adaf1fca6/propcache-0.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59d61f6970ecbd8ff2e9360304d5c8876a6abd4530cb752c06586849ac8a9dc9", size = 227456, upload-time = "2025-06-09T22:54:11.828Z" }, - { url = "https://files.pythonhosted.org/packages/de/03/07d992ccb6d930398689187e1b3c718339a1c06b8b145a8d9650e4726166/propcache-0.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62180e0b8dbb6b004baec00a7983e4cc52f5ada9cd11f48c3528d8cfa7b96a66", size = 225429, upload-time = "2025-06-09T22:54:13.823Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e6/116ba39448753b1330f48ab8ba927dcd6cf0baea8a0ccbc512dfb49ba670/propcache-0.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c144ca294a204c470f18cf4c9d78887810d04a3e2fbb30eea903575a779159df", size = 213472, upload-time = "2025-06-09T22:54:15.232Z" }, - { url = "https://files.pythonhosted.org/packages/a6/85/f01f5d97e54e428885a5497ccf7f54404cbb4f906688a1690cd51bf597dc/propcache-0.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c5c2a784234c28854878d68978265617aa6dc0780e53d44b4d67f3651a17a9a2", size = 204480, upload-time = "2025-06-09T22:54:17.104Z" }, - { url = "https://files.pythonhosted.org/packages/e3/79/7bf5ab9033b8b8194cc3f7cf1aaa0e9c3256320726f64a3e1f113a812dce/propcache-0.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5745bc7acdafa978ca1642891b82c19238eadc78ba2aaa293c6863b304e552d7", size = 214530, upload-time = "2025-06-09T22:54:18.512Z" }, - { url = "https://files.pythonhosted.org/packages/31/0b/bd3e0c00509b609317df4a18e6b05a450ef2d9a963e1d8bc9c9415d86f30/propcache-0.3.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c0075bf773d66fa8c9d41f66cc132ecc75e5bb9dd7cce3cfd14adc5ca184cb95", size = 205230, upload-time = "2025-06-09T22:54:19.947Z" }, - { url = "https://files.pythonhosted.org/packages/7a/23/fae0ff9b54b0de4e819bbe559508da132d5683c32d84d0dc2ccce3563ed4/propcache-0.3.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5f57aa0847730daceff0497f417c9de353c575d8da3579162cc74ac294c5369e", size = 206754, upload-time = "2025-06-09T22:54:21.716Z" }, - { url = "https://files.pythonhosted.org/packages/b7/7f/ad6a3c22630aaa5f618b4dc3c3598974a72abb4c18e45a50b3cdd091eb2f/propcache-0.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:eef914c014bf72d18efb55619447e0aecd5fb7c2e3fa7441e2e5d6099bddff7e", size = 218430, upload-time = "2025-06-09T22:54:23.17Z" }, - { url = "https://files.pythonhosted.org/packages/5b/2c/ba4f1c0e8a4b4c75910742f0d333759d441f65a1c7f34683b4a74c0ee015/propcache-0.3.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a4092e8549031e82facf3decdbc0883755d5bbcc62d3aea9d9e185549936dcf", size = 223884, upload-time = "2025-06-09T22:54:25.539Z" }, - { url = "https://files.pythonhosted.org/packages/88/e4/ebe30fc399e98572019eee82ad0caf512401661985cbd3da5e3140ffa1b0/propcache-0.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:85871b050f174bc0bfb437efbdb68aaf860611953ed12418e4361bc9c392749e", size = 211480, upload-time = "2025-06-09T22:54:26.892Z" }, - { url = "https://files.pythonhosted.org/packages/96/0a/7d5260b914e01d1d0906f7f38af101f8d8ed0dc47426219eeaf05e8ea7c2/propcache-0.3.2-cp311-cp311-win32.whl", hash = "sha256:36c8d9b673ec57900c3554264e630d45980fd302458e4ac801802a7fd2ef7897", size = 37757, upload-time = "2025-06-09T22:54:28.241Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2d/89fe4489a884bc0da0c3278c552bd4ffe06a1ace559db5ef02ef24ab446b/propcache-0.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53af8cb6a781b02d2ea079b5b853ba9430fcbe18a8e3ce647d5982a3ff69f39", size = 41500, upload-time = "2025-06-09T22:54:29.4Z" }, - { url = "https://files.pythonhosted.org/packages/a8/42/9ca01b0a6f48e81615dca4765a8f1dd2c057e0540f6116a27dc5ee01dfb6/propcache-0.3.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8de106b6c84506b31c27168582cd3cb3000a6412c16df14a8628e5871ff83c10", size = 73674, upload-time = "2025-06-09T22:54:30.551Z" }, - { url = "https://files.pythonhosted.org/packages/af/6e/21293133beb550f9c901bbece755d582bfaf2176bee4774000bd4dd41884/propcache-0.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:28710b0d3975117239c76600ea351934ac7b5ff56e60953474342608dbbb6154", size = 43570, upload-time = "2025-06-09T22:54:32.296Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c8/0393a0a3a2b8760eb3bde3c147f62b20044f0ddac81e9d6ed7318ec0d852/propcache-0.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce26862344bdf836650ed2487c3d724b00fbfec4233a1013f597b78c1cb73615", size = 43094, upload-time = "2025-06-09T22:54:33.929Z" }, - { url = "https://files.pythonhosted.org/packages/37/2c/489afe311a690399d04a3e03b069225670c1d489eb7b044a566511c1c498/propcache-0.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bca54bd347a253af2cf4544bbec232ab982f4868de0dd684246b67a51bc6b1db", size = 226958, upload-time = "2025-06-09T22:54:35.186Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ca/63b520d2f3d418c968bf596839ae26cf7f87bead026b6192d4da6a08c467/propcache-0.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55780d5e9a2ddc59711d727226bb1ba83a22dd32f64ee15594b9392b1f544eb1", size = 234894, upload-time = "2025-06-09T22:54:36.708Z" }, - { url = "https://files.pythonhosted.org/packages/11/60/1d0ed6fff455a028d678df30cc28dcee7af77fa2b0e6962ce1df95c9a2a9/propcache-0.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e631be25d6975ed87ab23153db6a73426a48db688070d925aa27e996fe93c", size = 233672, upload-time = "2025-06-09T22:54:38.062Z" }, - { url = "https://files.pythonhosted.org/packages/37/7c/54fd5301ef38505ab235d98827207176a5c9b2aa61939b10a460ca53e123/propcache-0.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee6f22b6eaa39297c751d0e80c0d3a454f112f5c6481214fcf4c092074cecd67", size = 224395, upload-time = "2025-06-09T22:54:39.634Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1a/89a40e0846f5de05fdc6779883bf46ba980e6df4d2ff8fb02643de126592/propcache-0.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ca3aee1aa955438c4dba34fc20a9f390e4c79967257d830f137bd5a8a32ed3b", size = 212510, upload-time = "2025-06-09T22:54:41.565Z" }, - { url = "https://files.pythonhosted.org/packages/5e/33/ca98368586c9566a6b8d5ef66e30484f8da84c0aac3f2d9aec6d31a11bd5/propcache-0.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4f30862869fa2b68380d677cc1c5fcf1e0f2b9ea0cf665812895c75d0ca3b8", size = 222949, upload-time = "2025-06-09T22:54:43.038Z" }, - { url = "https://files.pythonhosted.org/packages/ba/11/ace870d0aafe443b33b2f0b7efdb872b7c3abd505bfb4890716ad7865e9d/propcache-0.3.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b77ec3c257d7816d9f3700013639db7491a434644c906a2578a11daf13176251", size = 217258, upload-time = "2025-06-09T22:54:44.376Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d2/86fd6f7adffcfc74b42c10a6b7db721d1d9ca1055c45d39a1a8f2a740a21/propcache-0.3.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cab90ac9d3f14b2d5050928483d3d3b8fb6b4018893fc75710e6aa361ecb2474", size = 213036, upload-time = "2025-06-09T22:54:46.243Z" }, - { url = "https://files.pythonhosted.org/packages/07/94/2d7d1e328f45ff34a0a284cf5a2847013701e24c2a53117e7c280a4316b3/propcache-0.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0b504d29f3c47cf6b9e936c1852246c83d450e8e063d50562115a6be6d3a2535", size = 227684, upload-time = "2025-06-09T22:54:47.63Z" }, - { url = "https://files.pythonhosted.org/packages/b7/05/37ae63a0087677e90b1d14710e532ff104d44bc1efa3b3970fff99b891dc/propcache-0.3.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce2ac2675a6aa41ddb2a0c9cbff53780a617ac3d43e620f8fd77ba1c84dcfc06", size = 234562, upload-time = "2025-06-09T22:54:48.982Z" }, - { url = "https://files.pythonhosted.org/packages/a4/7c/3f539fcae630408d0bd8bf3208b9a647ccad10976eda62402a80adf8fc34/propcache-0.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:62b4239611205294cc433845b914131b2a1f03500ff3c1ed093ed216b82621e1", size = 222142, upload-time = "2025-06-09T22:54:50.424Z" }, - { url = "https://files.pythonhosted.org/packages/7c/d2/34b9eac8c35f79f8a962546b3e97e9d4b990c420ee66ac8255d5d9611648/propcache-0.3.2-cp312-cp312-win32.whl", hash = "sha256:df4a81b9b53449ebc90cc4deefb052c1dd934ba85012aa912c7ea7b7e38b60c1", size = 37711, upload-time = "2025-06-09T22:54:52.072Z" }, - { url = "https://files.pythonhosted.org/packages/19/61/d582be5d226cf79071681d1b46b848d6cb03d7b70af7063e33a2787eaa03/propcache-0.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7046e79b989d7fe457bb755844019e10f693752d169076138abf17f31380800c", size = 41479, upload-time = "2025-06-09T22:54:53.234Z" }, - { url = "https://files.pythonhosted.org/packages/cc/35/cc0aaecf278bb4575b8555f2b137de5ab821595ddae9da9d3cd1da4072c7/propcache-0.3.2-py3-none-any.whl", hash = "sha256:98f1ec44fb675f5052cccc8e609c46ed23a35a1cfd18545ad4e29002d858a43f", size = 12663, upload-time = "2025-06-09T22:56:04.484Z" }, +version = "0.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, + { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, + { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, + { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, + { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, + { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, + { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, + { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, + { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, + { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, + { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, + { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, + { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, + { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/6c7ee328b39a81ee877c962469f1e795f9db87f925251efeb0545e0020d0/propcache-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ec17c65562a827bba85e3872ead335f95405ea1674860d96483a02f5c698fa72", size = 225505, upload-time = "2025-10-08T19:46:50.055Z" }, + { url = "https://files.pythonhosted.org/packages/01/5d/1c53f4563490b1d06a684742cc6076ef944bc6457df6051b7d1a877c057b/propcache-0.4.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:405aac25c6394ef275dee4c709be43745d36674b223ba4eb7144bf4d691b7367", size = 230242, upload-time = "2025-10-08T19:46:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/e1/ce4620633b0e2422207c3cb774a0ee61cac13abc6217763a7b9e2e3f4a12/propcache-0.4.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0013cb6f8dde4b2a2f66903b8ba740bdfe378c943c4377a200551ceb27f379e4", size = 238474, upload-time = "2025-10-08T19:46:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/3aae6835b8e5f44ea6a68348ad90f78134047b503765087be2f9912140ea/propcache-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:15932ab57837c3368b024473a525e25d316d8353016e7cc0e5ba9eb343fbb1cf", size = 221575, upload-time = "2025-10-08T19:46:54.511Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a5/8a5e8678bcc9d3a1a15b9a29165640d64762d424a16af543f00629c87338/propcache-0.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:031dce78b9dc099f4c29785d9cf5577a3faf9ebf74ecbd3c856a7b92768c3df3", size = 216736, upload-time = "2025-10-08T19:46:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/f1/63/b7b215eddeac83ca1c6b934f89d09a625aa9ee4ba158338854c87210cc36/propcache-0.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ab08df6c9a035bee56e31af99be621526bd237bea9f32def431c656b29e41778", size = 213019, upload-time = "2025-10-08T19:46:57.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/74/f580099a58c8af587cac7ba19ee7cb418506342fbbe2d4a4401661cca886/propcache-0.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4d7af63f9f93fe593afbf104c21b3b15868efb2c21d07d8732c0c4287e66b6a6", size = 220376, upload-time = "2025-10-08T19:46:59.067Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ee/542f1313aff7eaf19c2bb758c5d0560d2683dac001a1c96d0774af799843/propcache-0.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cfc27c945f422e8b5071b6e93169679e4eb5bf73bbcbf1ba3ae3a83d2f78ebd9", size = 226988, upload-time = "2025-10-08T19:47:00.544Z" }, + { url = "https://files.pythonhosted.org/packages/8f/18/9c6b015dd9c6930f6ce2229e1f02fb35298b847f2087ea2b436a5bfa7287/propcache-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:35c3277624a080cc6ec6f847cbbbb5b49affa3598c4535a0a4682a697aaa5c75", size = 215615, upload-time = "2025-10-08T19:47:01.968Z" }, + { url = "https://files.pythonhosted.org/packages/80/9e/e7b85720b98c45a45e1fca6a177024934dc9bc5f4d5dd04207f216fc33ed/propcache-0.4.1-cp312-cp312-win32.whl", hash = "sha256:671538c2262dadb5ba6395e26c1731e1d52534bfe9ae56d0b5573ce539266aa8", size = 38066, upload-time = "2025-10-08T19:47:03.503Z" }, + { url = "https://files.pythonhosted.org/packages/54/09/d19cff2a5aaac632ec8fc03737b223597b1e347416934c1b3a7df079784c/propcache-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:cb2d222e72399fcf5890d1d5cc1060857b9b236adff2792ff48ca2dfd46c81db", size = 41655, upload-time = "2025-10-08T19:47:04.973Z" }, + { url = "https://files.pythonhosted.org/packages/68/ab/6b5c191bb5de08036a8c697b265d4ca76148efb10fa162f14af14fb5f076/propcache-0.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:204483131fb222bdaaeeea9f9e6c6ed0cac32731f75dfc1d4a567fc1926477c1", size = 37789, upload-time = "2025-10-08T19:47:06.077Z" }, + { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] [[package]] name = "protobuf" -version = "6.32.1" +version = "6.33.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/a4/cc17347aa2897568beece2e674674359f911d6fe21b0b8d6268cd42727ac/protobuf-6.32.1.tar.gz", hash = "sha256:ee2469e4a021474ab9baafea6cd070e5bf27c7d29433504ddea1a4ee5850f68d", size = 440635, upload-time = "2025-09-11T21:38:42.935Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/98/645183ea03ab3995d29086b8bf4f7562ebd3d10c9a4b14ee3f20d47cfe50/protobuf-6.32.1-cp310-abi3-win32.whl", hash = "sha256:a8a32a84bc9f2aad712041b8b366190f71dde248926da517bde9e832e4412085", size = 424411, upload-time = "2025-09-11T21:38:27.427Z" }, - { url = "https://files.pythonhosted.org/packages/8c/f3/6f58f841f6ebafe076cebeae33fc336e900619d34b1c93e4b5c97a81fdfa/protobuf-6.32.1-cp310-abi3-win_amd64.whl", hash = "sha256:b00a7d8c25fa471f16bc8153d0e53d6c9e827f0953f3c09aaa4331c718cae5e1", size = 435738, upload-time = "2025-09-11T21:38:30.959Z" }, - { url = "https://files.pythonhosted.org/packages/10/56/a8a3f4e7190837139e68c7002ec749190a163af3e330f65d90309145a210/protobuf-6.32.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d8c7e6eb619ffdf105ee4ab76af5a68b60a9d0f66da3ea12d1640e6d8dab7281", size = 426454, upload-time = "2025-09-11T21:38:34.076Z" }, - { url = "https://files.pythonhosted.org/packages/3f/be/8dd0a927c559b37d7a6c8ab79034fd167dcc1f851595f2e641ad62be8643/protobuf-6.32.1-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:2f5b80a49e1eb7b86d85fcd23fe92df154b9730a725c3b38c4e43b9d77018bf4", size = 322874, upload-time = "2025-09-11T21:38:35.509Z" }, - { url = "https://files.pythonhosted.org/packages/5c/f6/88d77011b605ef979aace37b7703e4eefad066f7e84d935e5a696515c2dd/protobuf-6.32.1-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:b1864818300c297265c83a4982fd3169f97122c299f56a56e2445c3698d34710", size = 322013, upload-time = "2025-09-11T21:38:37.017Z" }, - { url = "https://files.pythonhosted.org/packages/97/b7/15cc7d93443d6c6a84626ae3258a91f4c6ac8c0edd5df35ea7658f71b79c/protobuf-6.32.1-py3-none-any.whl", hash = "sha256:2601b779fc7d32a866c6b4404f9d42a3f67c5b9f3f15b4db3cccabe06b95c346", size = 169289, upload-time = "2025-09-11T21:38:41.234Z" }, + { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, + { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, + { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, + { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, + { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, + { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, + { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, ] [[package]] name = "psutil" -version = "7.1.0" +version = "7.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/31/4723d756b59344b643542936e37a31d1d3204bcdc42a7daa8ee9eb06fb50/psutil-7.1.0.tar.gz", hash = "sha256:655708b3c069387c8b77b072fc429a57d0e214221d01c0a772df7dfedcb3bcd2", size = 497660, upload-time = "2025-09-17T20:14:52.902Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/7c/31d1c3ceb1260301f87565f50689dc6da3db427ece1e1e012af22abca54e/psutil-7.2.0.tar.gz", hash = "sha256:2e4f8e1552f77d14dc96fb0f6240c5b34a37081c0889f0853b3b29a496e5ef64", size = 489863, upload-time = "2025-12-23T20:26:24.616Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/62/ce4051019ee20ce0ed74432dd73a5bb087a6704284a470bb8adff69a0932/psutil-7.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76168cef4397494250e9f4e73eb3752b146de1dd950040b29186d0cce1d5ca13", size = 245242, upload-time = "2025-09-17T20:14:56.126Z" }, - { url = "https://files.pythonhosted.org/packages/38/61/f76959fba841bf5b61123fbf4b650886dc4094c6858008b5bf73d9057216/psutil-7.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:5d007560c8c372efdff9e4579c2846d71de737e4605f611437255e81efcca2c5", size = 246682, upload-time = "2025-09-17T20:14:58.25Z" }, - { url = "https://files.pythonhosted.org/packages/88/7a/37c99d2e77ec30d63398ffa6a660450b8a62517cabe44b3e9bae97696e8d/psutil-7.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22e4454970b32472ce7deaa45d045b34d3648ce478e26a04c7e858a0a6e75ff3", size = 287994, upload-time = "2025-09-17T20:14:59.901Z" }, - { url = "https://files.pythonhosted.org/packages/9d/de/04c8c61232f7244aa0a4b9a9fbd63a89d5aeaf94b2fc9d1d16e2faa5cbb0/psutil-7.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c70e113920d51e89f212dd7be06219a9b88014e63a4cec69b684c327bc474e3", size = 291163, upload-time = "2025-09-17T20:15:01.481Z" }, - { url = "https://files.pythonhosted.org/packages/f4/58/c4f976234bf6d4737bc8c02a81192f045c307b72cf39c9e5c5a2d78927f6/psutil-7.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d4a113425c037300de3ac8b331637293da9be9713855c4fc9d2d97436d7259d", size = 293625, upload-time = "2025-09-17T20:15:04.492Z" }, - { url = "https://files.pythonhosted.org/packages/79/87/157c8e7959ec39ced1b11cc93c730c4fb7f9d408569a6c59dbd92ceb35db/psutil-7.1.0-cp37-abi3-win32.whl", hash = "sha256:09ad740870c8d219ed8daae0ad3b726d3bf9a028a198e7f3080f6a1888b99bca", size = 244812, upload-time = "2025-09-17T20:15:07.462Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e9/b44c4f697276a7a95b8e94d0e320a7bf7f3318521b23de69035540b39838/psutil-7.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:57f5e987c36d3146c0dd2528cd42151cf96cd359b9d67cfff836995cc5df9a3d", size = 247965, upload-time = "2025-09-17T20:15:09.673Z" }, - { url = "https://files.pythonhosted.org/packages/26/65/1070a6e3c036f39142c2820c4b52e9243246fcfc3f96239ac84472ba361e/psutil-7.1.0-cp37-abi3-win_arm64.whl", hash = "sha256:6937cb68133e7c97b6cc9649a570c9a18ba0efebed46d8c5dae4c07fa1b67a07", size = 244971, upload-time = "2025-09-17T20:15:12.262Z" }, + { url = "https://files.pythonhosted.org/packages/40/c5/a49160bf3e165b7b93a60579a353cf5d939d7f878fe5fd369110f1d18043/psutil-7.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:977a2fcd132d15cb05b32b2d85b98d087cad039b0ce435731670ba74da9e6133", size = 128116, upload-time = "2025-12-23T20:26:53.516Z" }, + { url = "https://files.pythonhosted.org/packages/10/a1/c75feb480f60cd768fb6ed00ac362a16a33e5076ec8475a22d8162fb2659/psutil-7.2.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:24151011c21fadd94214d7139d7c6c54569290d7e553989bdf0eab73b13beb8c", size = 128925, upload-time = "2025-12-23T20:26:55.573Z" }, + { url = "https://files.pythonhosted.org/packages/12/ff/e93136587c00a543f4bc768b157fac2c47cd77b180d4f4e5c6efb6ea53a2/psutil-7.2.0-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91f211ba9279e7c61d9d8f84b713cfc38fa161cb0597d5cb3f1ca742f6848254", size = 154666, upload-time = "2025-12-23T20:26:57.312Z" }, + { url = "https://files.pythonhosted.org/packages/b8/dd/4c2de9c3827c892599d277a69d2224136800870a8a88a80981de905de28d/psutil-7.2.0-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f37415188b7ea98faf90fed51131181646c59098b077550246e2e092e127418b", size = 156109, upload-time = "2025-12-23T20:26:58.851Z" }, + { url = "https://files.pythonhosted.org/packages/81/3f/090943c682d3629968dd0b04826ddcbc760ee1379021dbe316e2ddfcd01b/psutil-7.2.0-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d12c7ce6ed1128cd81fd54606afa054ac7dbb9773469ebb58cf2f171c49f2ac", size = 148081, upload-time = "2025-12-23T20:27:01.318Z" }, + { url = "https://files.pythonhosted.org/packages/c4/88/c39648ebb8ec182d0364af53cdefe6eddb5f3872ba718b5855a8ff65d6d4/psutil-7.2.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ca0faef7976530940dcd39bc5382d0d0d5eb023b186a4901ca341bd8d8684151", size = 147376, upload-time = "2025-12-23T20:27:03.347Z" }, + { url = "https://files.pythonhosted.org/packages/01/a2/5b39e08bd9b27476bc7cce7e21c71a481ad60b81ffac49baf02687a50d7f/psutil-7.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:abdb74137ca232d20250e9ad471f58d500e7743bc8253ba0bfbf26e570c0e437", size = 136910, upload-time = "2025-12-23T20:27:05.289Z" }, + { url = "https://files.pythonhosted.org/packages/59/54/53839db1258c1eaeb4ded57ff202144ebc75b23facc05a74fd98d338b0c6/psutil-7.2.0-cp37-abi3-win_arm64.whl", hash = "sha256:284e71038b3139e7ab3834b63b3eb5aa5565fcd61a681ec746ef9a0a8c457fd2", size = 133807, upload-time = "2025-12-23T20:27:06.825Z" }, ] [[package]] @@ -1820,23 +1831,24 @@ crypto = [ [[package]] name = "pylibsrtp" -version = "0.12.0" +version = "1.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/c8/a59e61f5dd655f5f21033bd643dd31fe980a537ed6f373cdfb49d3a3bd32/pylibsrtp-0.12.0.tar.gz", hash = "sha256:f5c3c0fb6954e7bb74dc7e6398352740ca67327e6759a199fe852dbc7b84b8ac", size = 10878, upload-time = "2025-04-06T12:35:51.804Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/a6/6e532bec974aaecbf9fe4e12538489fb1c28456e65088a50f305aeab9f89/pylibsrtp-1.0.0.tar.gz", hash = "sha256:b39dff075b263a8ded5377f2490c60d2af452c9f06c4d061c7a2b640612b34d4", size = 10858, upload-time = "2025-10-13T16:12:31.552Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/f0/b818395c4cae2d5cc5a0c78fc47d694eae78e6a0d678baeb52a381a26327/pylibsrtp-0.12.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:5adde3cf9a5feef561d0eb7ed99dedb30b9bf1ce9a0c1770b2bf19fd0b98bc9a", size = 1727918, upload-time = "2025-04-06T12:35:36.456Z" }, - { url = "https://files.pythonhosted.org/packages/05/1a/ee553abe4431b7bd9bab18f078c0ad2298b94ea55e664da6ecb8700b1052/pylibsrtp-0.12.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:d2c81d152606721331ece87c80ed17159ba6da55c7c61a6b750cff67ab7f63a5", size = 2057900, upload-time = "2025-04-06T12:35:38.253Z" }, - { url = "https://files.pythonhosted.org/packages/7f/a2/2dd0188be58d3cba48c5eb4b3c787e5743c111cd0c9289de4b6f2798382a/pylibsrtp-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:242fa3d44219846bf1734d5df595563a2c8fbb0fb00ccc79ab0f569fc0af2c1b", size = 2567047, upload-time = "2025-04-06T12:35:39.797Z" }, - { url = "https://files.pythonhosted.org/packages/6c/3a/4bdab9fc1d78f2efa02c8a8f3e9c187bfa278e89481b5123f07c8dd69310/pylibsrtp-0.12.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b74aaf8fac1b119a3c762f54751c3d20e77227b84c26d85aae57c2c43129b49c", size = 2168775, upload-time = "2025-04-06T12:35:41.422Z" }, - { url = "https://files.pythonhosted.org/packages/d0/fc/0b1e1bfed420d79427d50aff84c370dcd78d81af9500c1e86fbcc5bf95e1/pylibsrtp-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33e3e223102989b71f07e1deeb804170ed53fb4e1b283762eb031bd45bb425d4", size = 2225033, upload-time = "2025-04-06T12:35:43.03Z" }, - { url = "https://files.pythonhosted.org/packages/39/7b/e1021d27900315c2c077ec7d45f50274cedbdde067ff679d44df06f01a8a/pylibsrtp-0.12.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:36d07de64dbc82dbbb99fd77f36c8e23d6730bdbcccf09701945690a9a9a422a", size = 2606093, upload-time = "2025-04-06T12:35:44.587Z" }, - { url = "https://files.pythonhosted.org/packages/eb/c2/0fae6687a06fcde210a778148ec808af49e431c36fe9908503a695c35479/pylibsrtp-0.12.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:ef03b4578577690f716fd023daed8914eee6de9a764fa128eda19a0e645cc032", size = 2193213, upload-time = "2025-04-06T12:35:46.167Z" }, - { url = "https://files.pythonhosted.org/packages/67/c2/2ed7a4a5c38b999fd34298f76b93d29f5ba8c06f85cfad3efd9468343715/pylibsrtp-0.12.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:0a8421e9fe4d20ce48d439430e55149f12b1bca1b0436741972c362c49948c0a", size = 2256774, upload-time = "2025-04-06T12:35:47.704Z" }, - { url = "https://files.pythonhosted.org/packages/48/d7/f13fedce3b21d24f6f154d1dee7287464a34728dcb3b0c50f687dbad5765/pylibsrtp-0.12.0-cp39-abi3-win32.whl", hash = "sha256:cbc9bfbfb2597e993a1aa16b832ba16a9dd4647f70815421bb78484f8b50b924", size = 1156186, upload-time = "2025-04-06T12:35:48.78Z" }, - { url = "https://files.pythonhosted.org/packages/9b/26/3a20b638a3a3995368f856eeb10701dd6c0e9ace9fb6665eeb1b95ccce19/pylibsrtp-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:061ef1dbb5f08079ac6d7515b7e67ca48a3163e16e5b820beea6b01cb31d7e54", size = 1485072, upload-time = "2025-04-06T12:35:50.312Z" }, + { url = "https://files.pythonhosted.org/packages/aa/af/89e61a62fa3567f1b7883feb4d19e19564066c2fcd41c37e08d317b51881/pylibsrtp-1.0.0-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:822c30ea9e759b333dc1f56ceac778707c51546e97eb874de98d7d378c000122", size = 1865017, upload-time = "2025-10-13T16:12:15.62Z" }, + { url = "https://files.pythonhosted.org/packages/8d/0e/8d215484a9877adcf2459a8b28165fc89668b034565277fd55d666edd247/pylibsrtp-1.0.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:aaad74e5c8cbc1c32056c3767fea494c1e62b3aea2c908eda2a1051389fdad76", size = 2182739, upload-time = "2025-10-13T16:12:17.121Z" }, + { url = "https://files.pythonhosted.org/packages/57/3f/76a841978877ae13eac0d4af412c13bbd5d83b3df2c1f5f2175f2e0f68e5/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9209b86e662ebbd17c8a9e8549ba57eca92a3e87fb5ba8c0e27b8c43cd08a767", size = 2732922, upload-time = "2025-10-13T16:12:18.348Z" }, + { url = "https://files.pythonhosted.org/packages/0e/14/cf5d2a98a66fdfe258f6b036cda570f704a644fa861d7883a34bc359501e/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:293c9f2ac21a2bd689c477603a1aa235d85cf252160e6715f0101e42a43cbedc", size = 2434534, upload-time = "2025-10-13T16:12:20.074Z" }, + { url = "https://files.pythonhosted.org/packages/bd/08/a3f6e86c04562f7dce6717cd2206a0f84ca85c5e38121d998e0e330194c3/pylibsrtp-1.0.0-cp310-abi3-manylinux_2_28_i686.whl", hash = "sha256:81fb8879c2e522021a7cbd3f4bda1b37c192e1af939dfda3ff95b4723b329663", size = 2345818, upload-time = "2025-10-13T16:12:21.439Z" }, + { url = "https://files.pythonhosted.org/packages/8e/d5/130c2b5b4b51df5631684069c6f0a6761c59d096a33d21503ac207cf0e47/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4ddb562e443cf2e557ea2dfaeef0d7e6b90e96dd38eb079b4ab2c8e34a79f50b", size = 2774490, upload-time = "2025-10-13T16:12:22.659Z" }, + { url = "https://files.pythonhosted.org/packages/91/e3/715a453bfee3bea92a243888ad359094a7727cc6d393f21281320fe7798c/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:f02e616c9dfab2b03b32d8cc7b748f9d91814c0211086f987629a60f05f6e2cc", size = 2372603, upload-time = "2025-10-13T16:12:24.036Z" }, + { url = "https://files.pythonhosted.org/packages/e3/56/52fa74294254e1f53a4ff170ee2006e57886cf4bb3db46a02b4f09e1d99f/pylibsrtp-1.0.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c134fa09e7b80a5b7fed626230c5bc257fd771bd6978e754343e7a61d96bc7e6", size = 2451269, upload-time = "2025-10-13T16:12:25.475Z" }, + { url = "https://files.pythonhosted.org/packages/1e/51/2e9b34f484cbdd3bac999bf1f48b696d7389433e900639089e8fc4e0da0d/pylibsrtp-1.0.0-cp310-abi3-win32.whl", hash = "sha256:bae377c3b402b17b9bbfbfe2534c2edba17aa13bea4c64ce440caacbe0858b55", size = 1247503, upload-time = "2025-10-13T16:12:27.39Z" }, + { url = "https://files.pythonhosted.org/packages/c3/70/43db21af194580aba2d9a6d4c7bd8c1a6e887fa52cd810b88f89096ecad2/pylibsrtp-1.0.0-cp310-abi3-win_amd64.whl", hash = "sha256:8d6527c4a78a39a8d397f8862a8b7cdad4701ee866faf9de4ab8c70be61fd34d", size = 1601659, upload-time = "2025-10-13T16:12:29.037Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ec/6e02b2561d056ea5b33046e3cad21238e6a9097b97d6ccc0fbe52b50c858/pylibsrtp-1.0.0-cp310-abi3-win_arm64.whl", hash = "sha256:2696bdb2180d53ac55d0eb7b58048a2aa30cd4836dd2ca683669889137a94d2a", size = 1159246, upload-time = "2025-10-13T16:12:30.285Z" }, ] [[package]] @@ -1865,7 +1877,7 @@ wheels = [ [[package]] name = "pyobjc" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -1878,6 +1890,7 @@ dependencies = [ { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-applicationservices", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-arkit", marker = "platform_release >= '25.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, @@ -1898,6 +1911,7 @@ dependencies = [ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-compositorservices", marker = "platform_release >= '25.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, @@ -1938,6 +1952,7 @@ dependencies = [ { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, + { name = "pyobjc-framework-gamesave", marker = "platform_release >= '25.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, @@ -2027,118 +2042,118 @@ dependencies = [ { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/5e/16bc372806790d295c76b5c7851767cc9ee3787b3e581f5d7cc44158e4e0/pyobjc-11.1.tar.gz", hash = "sha256:a71b14389657811d658526ba4d5faba4ef7eadbddcf9fe8bf4fb3a6261effba3", size = 11161, upload-time = "2025-06-14T20:56:32.819Z" } +sdist = { url = "https://files.pythonhosted.org/packages/17/06/d77639ba166cc09aed2d32ae204811b47bc5d40e035cdc9bff7fff72ec5f/pyobjc-12.1.tar.gz", hash = "sha256:686d6db3eb3182fac9846b8ce3eedf4c7d2680b21b8b8d6e6df054a17e92a12d", size = 11345, upload-time = "2025-11-14T10:07:28.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/32/ad08b45fc0ad9850054ffe66fb0cb2ff7af3d2007c192dda14cf9a3ea893/pyobjc-11.1-py3-none-any.whl", hash = "sha256:903f822cba40be53d408b8eaf834514937ec0b4e6af1c5ecc24fcb652812dd85", size = 4164, upload-time = "2025-06-14T20:44:42.659Z" }, + { url = "https://files.pythonhosted.org/packages/ef/00/1085de7b73abf37ec27ad59f7a1d7a406e6e6da45720bced2e198fdf1ddf/pyobjc-12.1-py3-none-any.whl", hash = "sha256:6f8c36cf87b1159d2ca1aa387ffc3efcd51cc3da13ef47c65f45e6d9fbccc729", size = 4226, upload-time = "2025-11-14T09:30:25.185Z" }, ] [[package]] name = "pyobjc-core" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/e9/0b85c81e2b441267bca707b5d89f56c2f02578ef8f3eafddf0e0c0b8848c/pyobjc_core-11.1.tar.gz", hash = "sha256:b63d4d90c5df7e762f34739b39cc55bc63dbcf9fb2fb3f2671e528488c7a87fe", size = 974602, upload-time = "2025-06-14T20:56:34.189Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/a7/55afc166d89e3fcd87966f48f8bca3305a3a2d7c62100715b9ffa7153a90/pyobjc_core-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ec36680b5c14e2f73d432b03ba7c1457dc6ca70fa59fd7daea1073f2b4157d33", size = 671075, upload-time = "2025-06-14T20:44:46.594Z" }, - { url = "https://files.pythonhosted.org/packages/c0/09/e83228e878e73bf756749939f906a872da54488f18d75658afa7f1abbab1/pyobjc_core-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:765b97dea6b87ec4612b3212258024d8496ea23517c95a1c5f0735f96b7fd529", size = 677985, upload-time = "2025-06-14T20:44:48.375Z" }, + { url = "https://files.pythonhosted.org/packages/95/df/d2b290708e9da86d6e7a9a2a2022b91915cf2e712a5a82e306cb6ee99792/pyobjc_core-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c918ebca280925e7fcb14c5c43ce12dcb9574a33cccb889be7c8c17f3bcce8b6", size = 671263, upload-time = "2025-11-14T09:31:35.231Z" }, + { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, ] [[package]] name = "pyobjc-framework-accessibility" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/b4/10c16e9d48568a68da2f61866b19468d4ac7129c377d4b1333ee936ae5d0/pyobjc_framework_accessibility-11.1.tar.gz", hash = "sha256:c0fa5f1e00906ec002f582c7d3d80463a46d19f672bf5ec51144f819eeb40656", size = 45098, upload-time = "2025-06-14T20:56:35.287Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/87/8ca40428d05a668fecc638f2f47dba86054dbdc35351d247f039749de955/pyobjc_framework_accessibility-12.1.tar.gz", hash = "sha256:5ff362c3425edc242d49deec11f5f3e26e565cefb6a2872eda59ab7362149772", size = 29800, upload-time = "2025-11-14T10:08:31.949Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/c5/8803e4f9c3f2d3f5672097438e305be9ccfb87ad092c68cbf02b172bf1d2/pyobjc_framework_accessibility-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:332263153d829b946b311ddc8b9a4402b52d40a572b44c69c3242451ced1b008", size = 11135, upload-time = "2025-06-14T20:44:58.339Z" }, - { url = "https://files.pythonhosted.org/packages/5d/bd/087d511e0ea356434399609a38e8819978943cbeaca3ca7cc5f35c93d0b2/pyobjc_framework_accessibility-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a049b63b32514da68aaaeef0d6c00a125e0618e4042aa6dbe3867b74fb2a8b2b", size = 11158, upload-time = "2025-06-14T20:44:59.032Z" }, + { url = "https://files.pythonhosted.org/packages/76/00/182c57584ad8e5946a82dacdc83c9791567e10bffdea1fe92272b3fdec14/pyobjc_framework_accessibility-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e29dac0ce8327cd5a8b9a5a8bd8aa83e4070018b93699e97ac0c3af09b42a9a", size = 11301, upload-time = "2025-11-14T09:35:28.678Z" }, + { url = "https://files.pythonhosted.org/packages/cc/95/9ea0d1c16316b4b5babf4b0515e9a133ac64269d3ec031f15ee9c7c2a8c1/pyobjc_framework_accessibility-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:537691a0b28fedb8385cd093df069a6e5d7e027629671fc47b50210404eca20b", size = 11335, upload-time = "2025-11-14T09:35:30.81Z" }, ] [[package]] name = "pyobjc-framework-accounts" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/45/ca21003f68ad0f13b5a9ac1761862ad2ddd83224b4314a2f7d03ca437c8d/pyobjc_framework_accounts-11.1.tar.gz", hash = "sha256:384fec156e13ff75253bb094339013f4013464f6dfd47e2f7de3e2ae7441c030", size = 17086, upload-time = "2025-06-14T20:56:36.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/10/f6fe336c7624d6753c1f6edac102310ce4434d49b548c479e8e6420d4024/pyobjc_framework_accounts-12.1.tar.gz", hash = "sha256:76d62c5e7b831eb8f4c9ca6abaf79d9ed961dfffe24d89a041fb1de97fe56a3e", size = 15202, upload-time = "2025-11-14T10:08:33.995Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/db/fa1c4a964fb9f390af8fce1d82c053f9d4467ffe6acdaab464bb3220e673/pyobjc_framework_accounts-11.1-py2.py3-none-any.whl", hash = "sha256:9c3fe342be7b8e73cba735e5a38affbe349cf8bc19091aa4fd788eabf2074b72", size = 5117, upload-time = "2025-06-14T20:45:04.696Z" }, + { url = "https://files.pythonhosted.org/packages/ac/70/5f9214250f92fbe2e07f35778875d2771d612f313af2a0e4bacba80af28e/pyobjc_framework_accounts-12.1-py2.py3-none-any.whl", hash = "sha256:e1544ad11a2f889a7aaed649188d0e76d58595a27eec07ca663847a7adb21ae5", size = 5104, upload-time = "2025-11-14T09:35:40.246Z" }, ] [[package]] name = "pyobjc-framework-addressbook" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/d3/f5bb5c72be5c6e52224f43e23e5a44e86d2c35ee9af36939e5514c6c7a0f/pyobjc_framework_addressbook-11.1.tar.gz", hash = "sha256:ce2db3be4a3128bf79d5c41319a6d16b73754785ce75ac694d0d658c690922fc", size = 97609, upload-time = "2025-06-14T20:56:37.324Z" } +sdist = { url = "https://files.pythonhosted.org/packages/18/28/0404af2a1c6fa8fd266df26fb6196a8f3fb500d6fe3dab94701949247bea/pyobjc_framework_addressbook-12.1.tar.gz", hash = "sha256:c48b740cf981103cef1743d0804a226d86481fcb839bd84b80e9a586187e8000", size = 44359, upload-time = "2025-11-14T10:08:37.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/46/27ade210b0bcf2903540c37e96f5e88ec5303e98dc12b255148f12ef9c04/pyobjc_framework_addressbook-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d1d69330b5a87a29d26feea95dcf40681fd00ba3b40ac89579072ce536b6b647", size = 13156, upload-time = "2025-06-14T20:45:06.788Z" }, - { url = "https://files.pythonhosted.org/packages/c2/de/e1ba5f113c05b543a097040add795fa4b85fdd5ad850b56d83cd6ce8afff/pyobjc_framework_addressbook-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fb3d0a710f8342a0c63a8e4caf64a044b4d7e42d6d242c8e1b54470238b938cb", size = 13173, upload-time = "2025-06-14T20:45:07.755Z" }, + { url = "https://files.pythonhosted.org/packages/9f/5a/2ecaa94e5f56c6631f0820ec4209f8075c1b7561fe37495e2d024de1c8df/pyobjc_framework_addressbook-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:681755ada6c95bd4a096bc2b9f9c24661ffe6bff19a96963ee3fad34f3d61d2b", size = 12879, upload-time = "2025-11-14T09:35:45.21Z" }, + { url = "https://files.pythonhosted.org/packages/b6/33/da709c69cbb60df9522cd614d5c23c15b649b72e5d62fed1048e75c70e7b/pyobjc_framework_addressbook-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7893dd784322f4674299fb3ca40cb03385e5eddb78defd38f08c0b730813b56c", size = 12894, upload-time = "2025-11-14T09:35:47.498Z" }, ] [[package]] name = "pyobjc-framework-adservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/3f/af76eab6eee0a405a4fdee172e7181773040158476966ecd757b0a98bfc5/pyobjc_framework_adservices-11.1.tar.gz", hash = "sha256:44c72f8163705c9aa41baca938fdb17dde257639e5797e6a5c3a2b2d8afdade9", size = 12473, upload-time = "2025-06-14T20:56:38.147Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/04/1c3d3e0a1ac981664f30b33407dcdf8956046ecde6abc88832cf2aa535f4/pyobjc_framework_adservices-12.1.tar.gz", hash = "sha256:7a31fc8d5c6fd58f012db87c89ba581361fc905114bfb912e0a3a87475c02183", size = 11793, upload-time = "2025-11-14T10:08:39.56Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/11/a63a171ce86c25a6ae85ebff6a9ab92b0d0cb1fd66ddc7d7b0d803f36191/pyobjc_framework_adservices-11.1-py2.py3-none-any.whl", hash = "sha256:1744f59a75b2375e139c39f3e85658e62cd10cc0f12b158a80421f18734e9ffc", size = 3474, upload-time = "2025-06-14T20:45:13.263Z" }, + { url = "https://files.pythonhosted.org/packages/ad/13/f7796469b25f50750299c4b0e95dc2f75c7c7fc4c93ef2c644f947f10529/pyobjc_framework_adservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ca3c55e35b2abb3149a0bce5de9a1f7e8ee4f8642036910ca8586ab2e161538", size = 3492, upload-time = "2025-11-14T09:35:57.344Z" }, ] [[package]] name = "pyobjc-framework-adsupport" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7f/03/9c51edd964796a97def4e1433d76a128dd7059b685fb4366081bf4e292ba/pyobjc_framework_adsupport-11.1.tar.gz", hash = "sha256:78b9667c275785df96219d205bd4309731869c3298d0931e32aed83bede29096", size = 12556, upload-time = "2025-06-14T20:56:38.741Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/77/f26a2e9994d4df32e9b3680c8014e350b0f1c78d7673b3eba9de2e04816f/pyobjc_framework_adsupport-12.1.tar.gz", hash = "sha256:9a68480e76de567c339dca29a8c739d6d7b5cad30e1cd585ff6e49ec2fc283dd", size = 11645, upload-time = "2025-11-14T10:08:41.439Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/b8/ad895efb24311cab2b9d6f7f7f6a833b7f354f80fec606e6c7893da9349b/pyobjc_framework_adsupport-11.1-py2.py3-none-any.whl", hash = "sha256:c3e009612778948910d3a7135b9d77b9b7c06aab29d40957770834c083acf825", size = 3387, upload-time = "2025-06-14T20:45:14.394Z" }, + { url = "https://files.pythonhosted.org/packages/cd/1a/3e90d5a09953bde7b60946cd09cca1411aed05dea855cb88cb9e944c7006/pyobjc_framework_adsupport-12.1-py2.py3-none-any.whl", hash = "sha256:97dcd8799dd61f047bb2eb788bbde81f86e95241b5e5173a3a61cfc05b5598b1", size = 3401, upload-time = "2025-11-14T09:35:59.039Z" }, ] [[package]] name = "pyobjc-framework-applescriptkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/63/1bcfcdca53bf5bba3a7b4d73d24232ae1721a378a32fd4ebc34a35549df2/pyobjc_framework_applescriptkit-11.1.tar.gz", hash = "sha256:477707352eaa6cc4a5f8c593759dc3227a19d5958481b1482f0d59394a4601c3", size = 12392, upload-time = "2025-06-14T20:56:39.331Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/f1/e0c07b2a9eb98f1a2050f153d287a52a92f873eeddb41b74c52c144d8767/pyobjc_framework_applescriptkit-12.1.tar.gz", hash = "sha256:cb09f88cf0ad9753dedc02720065818f854b50e33eb4194f0ea34de6d7a3eb33", size = 11451, upload-time = "2025-11-14T10:08:43.328Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/0e/68ac4ce71e613697a087c262aefacc9ed54eaf0cf1d9ffcd89134bfdab9b/pyobjc_framework_applescriptkit-11.1-py2.py3-none-any.whl", hash = "sha256:e22cbc9d1a25a4a713f21aa94dd017c311186b02062fc7ffbde3009495fb0067", size = 4334, upload-time = "2025-06-14T20:45:15.205Z" }, + { url = "https://files.pythonhosted.org/packages/3b/70/6c399c6ebc37a4e48acf63967e0a916878aedfe420531f6d739215184c0c/pyobjc_framework_applescriptkit-12.1-py2.py3-none-any.whl", hash = "sha256:b955fc017b524027f635d92a8a45a5fd9fbae898f3e03de16ecd94aa4c4db987", size = 4352, upload-time = "2025-11-14T09:36:00.705Z" }, ] [[package]] name = "pyobjc-framework-applescriptobjc" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/27/687b55b575367df045879b786f358355e40e41f847968e557d0718a6c4a4/pyobjc_framework_applescriptobjc-11.1.tar.gz", hash = "sha256:c8a0ec975b64411a4f16a1280c5ea8dbe949fd361e723edd343102f0f95aba6e", size = 12445, upload-time = "2025-06-14T20:56:39.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/4b/e4d1592207cbe17355e01828bdd11dd58f31356108f6a49f5e0484a5df50/pyobjc_framework_applescriptobjc-12.1.tar.gz", hash = "sha256:dce080ed07409b0dda2fee75d559bd312ea1ef0243a4338606440f282a6a0f5f", size = 11588, upload-time = "2025-11-14T10:08:45.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/33/ceb6a512b41fbf3458b9a281997ebb3056cc354981215261f0a2bf7d15d6/pyobjc_framework_applescriptobjc-11.1-py2.py3-none-any.whl", hash = "sha256:ac22526fd1f0a3b07ac1d77f90046b77f10ec9549182114f2428ee1e96d3de2b", size = 4433, upload-time = "2025-06-14T20:45:16.061Z" }, + { url = "https://files.pythonhosted.org/packages/3e/5f/9ce6706399706930eb29c5308037109c30cfb36f943a6df66fdf38cc842a/pyobjc_framework_applescriptobjc-12.1-py2.py3-none-any.whl", hash = "sha256:79068f982cc22471712ce808c0a8fd5deea11258fc8d8c61968a84b1962a3d10", size = 4454, upload-time = "2025-11-14T09:36:02.276Z" }, ] [[package]] name = "pyobjc-framework-applicationservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2146,84 +2161,97 @@ dependencies = [ { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/3f/b33ce0cecc3a42f6c289dcbf9ff698b0d9e85f5796db2e9cb5dadccffbb9/pyobjc_framework_applicationservices-11.1.tar.gz", hash = "sha256:03fcd8c0c600db98fa8b85eb7b3bc31491701720c795e3f762b54e865138bbaf", size = 224842, upload-time = "2025-06-14T20:56:40.648Z" } +sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/2d/9fde6de0b2a95fbb3d77ba11b3cc4f289dd208f38cb3a28389add87c0f44/pyobjc_framework_applicationservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cf45d15eddae36dec2330a9992fc852476b61c8f529874b9ec2805c768a75482", size = 30991, upload-time = "2025-06-14T20:45:18.169Z" }, - { url = "https://files.pythonhosted.org/packages/38/ec/46a5c710e2d7edf55105223c34fed5a7b7cc7aba7d00a3a7b0405d6a2d1a/pyobjc_framework_applicationservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f4a85ccd78bab84f7f05ac65ff9be117839dfc09d48c39edd65c617ed73eb01c", size = 31056, upload-time = "2025-06-14T20:45:18.925Z" }, + { url = "https://files.pythonhosted.org/packages/17/86/d07eff705ff909a0ffa96d14fc14026e9fc9dd716233648c53dfd5056b8e/pyobjc_framework_applicationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bdddd492eeac6d14ff2f5bd342aba29e30dffa72a2d358c08444da22129890e2", size = 32784, upload-time = "2025-11-14T09:36:08.755Z" }, + { url = "https://files.pythonhosted.org/packages/37/a7/55fa88def5c02732c4b747606ff1cbce6e1f890734bbd00f5596b21eaa02/pyobjc_framework_applicationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8f6e2fb3b3e9214ab4864ef04eee18f592b46a986c86ea0113448b310520532", size = 32835, upload-time = "2025-11-14T09:36:11.855Z" }, ] [[package]] name = "pyobjc-framework-apptrackingtransparency" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/68/7aa3afffd038dd6e5af764336bca734eb910121013ca71030457b61e5b99/pyobjc_framework_apptrackingtransparency-11.1.tar.gz", hash = "sha256:796cc5f83346c10973806cfb535d4200b894a5d2626ff2eeb1972d594d14fed4", size = 13135, upload-time = "2025-06-14T20:56:41.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/de/f24348982ecab0cb13067c348fc5fbc882c60d704ca290bada9a2b3e594b/pyobjc_framework_apptrackingtransparency-12.1.tar.gz", hash = "sha256:e25bf4e4dfa2d929993ee8e852b28fdf332fa6cde0a33328fdc3b2f502fa50ec", size = 12407, upload-time = "2025-11-14T10:08:54.118Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/37/22cc0293c911a98a49c5fc007b968d82797101dd06e89c4c3266564ff443/pyobjc_framework_apptrackingtransparency-11.1-py2.py3-none-any.whl", hash = "sha256:e25c3eae25d24ee8b523b7ecc4d2b07af37c7733444b80c4964071dea7b0cb19", size = 3862, upload-time = "2025-06-14T20:45:23.851Z" }, + { url = "https://files.pythonhosted.org/packages/19/b2/90120b93ecfb099b6af21696c26356ad0f2182bdef72b6cba28aa6472ca6/pyobjc_framework_apptrackingtransparency-12.1-py2.py3-none-any.whl", hash = "sha256:23a98ade55495f2f992ecf62c3cbd8f648cbd68ba5539c3f795bf66de82e37ca", size = 3879, upload-time = "2025-11-14T09:36:26.425Z" }, +] + +[[package]] +name = "pyobjc-framework-arkit" +version = "12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/8b/843fe08e696bca8e7fc129344965ab6280f8336f64f01ba0a8862d219c3f/pyobjc_framework_arkit-12.1.tar.gz", hash = "sha256:0c5c6b702926179700b68ba29b8247464c3b609fd002a07a3308e72cfa953adf", size = 35814, upload-time = "2025-11-14T10:08:57.55Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/21/1e/64c55b409243b3eb9abc7a99e7b27ad4e16b9e74bc4b507fb7e7b81fd41a/pyobjc_framework_arkit-12.1-py2.py3-none-any.whl", hash = "sha256:f6d39e28d858ee03f052d6780a552247e682204382dbc090f1d3192fa1b21493", size = 8302, upload-time = "2025-11-14T09:36:28.127Z" }, ] [[package]] name = "pyobjc-framework-audiovideobridging" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/25/6c5a7b1443d30139cc722029880284ea9dfa575f0436471b9364fcd499f5/pyobjc_framework_audiovideobridging-11.1.tar.gz", hash = "sha256:12756b3aa35083b8ad5c9139b6a0e2f4792e217096b5bf6b702d499038203991", size = 72913, upload-time = "2025-06-14T20:56:42.128Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/51/f81581e7a3c5cb6c9254c6f1e1ee1d614930493761dec491b5b0d49544b9/pyobjc_framework_audiovideobridging-12.1.tar.gz", hash = "sha256:6230ace6bec1f38e8a727c35d054a7be54e039b3053f98e6dd8d08d6baee2625", size = 38457, upload-time = "2025-11-14T10:09:01.122Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/d0/952ccd59944f98f10f39c061ef7c3dceecbcd2654910e763c0ad2fd1c910/pyobjc_framework_audiovideobridging-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:db570433910d1df49cc45d25f7a966227033c794fb41133d59212689b86b1ac6", size = 11021, upload-time = "2025-06-14T20:45:25.498Z" }, - { url = "https://files.pythonhosted.org/packages/1d/69/3e8e3da4db835168d18155a2c90fcca441047fc9c2e021d2ea01b4c6eb8c/pyobjc_framework_audiovideobridging-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:591e80ff6973ea51a12f7c1a2e3fd59496633a51d5a1bf73f4fb989a43e23681", size = 11032, upload-time = "2025-06-14T20:45:26.196Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f8/c614630fa382720bbd42a0ff567378630c36d10f114476d6c70b73f73b49/pyobjc_framework_audiovideobridging-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6bc24a7063b08c7d9f1749a4641430d363b6dba642c04d09b58abcee7a5260cb", size = 11037, upload-time = "2025-11-14T09:36:32.583Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8e/a28badfcc6c731696e3d3a8a83927bd844d992f9152f903c2fee355702ca/pyobjc_framework_audiovideobridging-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:010021502649e2cca4e999a7c09358d48c6b0ed83530bbc0b85bba6834340e4b", size = 11052, upload-time = "2025-11-14T09:36:34.475Z" }, ] [[package]] name = "pyobjc-framework-authenticationservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8f/b7/3e9ad0ed3625dc02e495615ea5dbf55ca95cbd25b3e31f25092f5caad640/pyobjc_framework_authenticationservices-11.1.tar.gz", hash = "sha256:8fd801cdb53d426b4e678b0a8529c005d0c44f5a17ccd7052a7c3a1a87caed6a", size = 115266, upload-time = "2025-06-14T20:56:42.889Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/18/86218de3bf67fc1d810065f353d9df70c740de567ebee8550d476cb23862/pyobjc_framework_authenticationservices-12.1.tar.gz", hash = "sha256:cef71faeae2559f5c0ff9a81c9ceea1c81108e2f4ec7de52a98c269feff7a4b6", size = 58683, upload-time = "2025-11-14T10:09:06.003Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/99/0a9d2b9c1aa3b9713d322ddb90a59537013afdae5661af233409e7a24dc9/pyobjc_framework_authenticationservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3987b7fc9493c2ba77b773df99f6631bff1ee9b957d99e34afa6b4e1c9d48bfb", size = 20280, upload-time = "2025-06-14T20:45:32.617Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2d/cbb5e88c3713fb68cda7d76d37737076c1653bf1ac95418c30d4b614f4be/pyobjc_framework_authenticationservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6655dd53d9135ef85265a4297da5e7459ed7836973f2796027fdfbfd7f08e433", size = 20385, upload-time = "2025-06-14T20:45:33.359Z" }, + { url = "https://files.pythonhosted.org/packages/c2/16/2f19d8a95f0cf8e940f7b7fb506ced805d5522b4118336c8e640c34517ae/pyobjc_framework_authenticationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c15bb81282356f3f062ac79ff4166c93097448edc44b17dcf686e1dac78cc832", size = 20636, upload-time = "2025-11-14T09:36:48.35Z" }, + { url = "https://files.pythonhosted.org/packages/f1/1d/e9f296fe1ee9a074ff6c45ce9eb109fc3b45696de000f373265c8e42fd47/pyobjc_framework_authenticationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6fd5ce10fe5359cbbfe03eb12cab3e01992b32ab65653c579b00ac93cf674985", size = 20738, upload-time = "2025-11-14T09:36:51.094Z" }, ] [[package]] name = "pyobjc-framework-automaticassessmentconfiguration" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/39/d4c94e0245d290b83919854c4f205851cc0b2603f843448fdfb8e74aad71/pyobjc_framework_automaticassessmentconfiguration-11.1.tar.gz", hash = "sha256:70eadbf8600101901a56fcd7014d8941604e14f3b3728bc4fb0178a9a9420032", size = 24933, upload-time = "2025-06-14T20:56:43.984Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/24/080afe8189c47c4bb3daa191ccfd962400ca31a67c14b0f7c2d002c2e249/pyobjc_framework_automaticassessmentconfiguration-12.1.tar.gz", hash = "sha256:2b732c02d9097682ca16e48f5d3b10056b740bc091e217ee4d5715194c8970b1", size = 21895, upload-time = "2025-11-14T10:09:08.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/ca/f4ee1c9c274e0a41f8885f842fc78e520a367437edf9ca86eca46709e62d/pyobjc_framework_automaticassessmentconfiguration-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:50cc5466bec1f58f79921d49544b525b56897cb985dfcfabf825ee231c27bcfc", size = 9167, upload-time = "2025-06-14T20:45:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/5e/e0/5a67f8ee0393447ca8251cbd06788cb7f3a1f4b9b052afd2e1b2cdfcb504/pyobjc_framework_automaticassessmentconfiguration-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:55d1684dd676730fb1afbc7c67e0669e3a7159f18c126fea7453fe6182c098f9", size = 9193, upload-time = "2025-06-14T20:45:40.52Z" }, + { url = "https://files.pythonhosted.org/packages/fc/c9/4d2785565cc470daa222f93f3d332af97de600aef6bd23507ec07501999d/pyobjc_framework_automaticassessmentconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d94a4a3beb77b3b2ab7b610c4b41e28593d15571724a9e6ab196b82acc98dc13", size = 9316, upload-time = "2025-11-14T09:37:05.052Z" }, + { url = "https://files.pythonhosted.org/packages/f2/b2/fbec3d649bf275d7a9604e5f56015be02ef8dcf002f4ae4d760436b8e222/pyobjc_framework_automaticassessmentconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c2e22ea67d7e6d6a84d968169f83d92b59857a49ab12132de07345adbfea8a62", size = 9332, upload-time = "2025-11-14T09:37:07.083Z" }, ] [[package]] name = "pyobjc-framework-automator" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/63/9f/097ed9f4de9e9491a1b08bb7d85d35a95d726c9e9f5f5bf203b359a436b6/pyobjc_framework_automator-11.1.tar.gz", hash = "sha256:9b46c55a4f9ae2b3c39ff560f42ced66bdd18c093188f0b5fc4060ad911838e4", size = 201439, upload-time = "2025-06-14T20:56:44.767Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/08/362bf6ac2bba393c46cf56078d4578b692b56857c385e47690637a72f0dd/pyobjc_framework_automator-12.1.tar.gz", hash = "sha256:7491a99347bb30da3a3f744052a03434ee29bee3e2ae520576f7e796740e4ba7", size = 186068, upload-time = "2025-11-14T10:09:20.82Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/c0/ebcc5a041440625ca984cde4ff96bc3e2cac4e5a37ca5bf4506ef4a98c54/pyobjc_framework_automator-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bf675a19edd97de9c19dcfd0fea9af9ebbd3409786c162670d1d71cb2738e341", size = 10004, upload-time = "2025-06-14T20:45:46.111Z" }, - { url = "https://files.pythonhosted.org/packages/0e/1e/3ed1df2168e596151da2329258951dae334e194d7de3b117c7e29a768ffc/pyobjc_framework_automator-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af5941f8d90167244209b352512b7779e5590d17dc1e703e087a6cfe79ee3d64", size = 10029, upload-time = "2025-06-14T20:45:46.823Z" }, + { url = "https://files.pythonhosted.org/packages/e7/99/480e07eef053a2ad2a5cf1e15f71982f21d7f4119daafac338fa0352309c/pyobjc_framework_automator-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f3d96da10d28c5c197193a9d805a13157b1cb694b6c535983f8572f5f8746ea", size = 10016, upload-time = "2025-11-14T09:37:18.621Z" }, + { url = "https://files.pythonhosted.org/packages/e3/36/2e8c36ddf20d501f9d344ed694e39021190faffc44b596f3a430bf437174/pyobjc_framework_automator-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4df9aec77f0fbca66cd3534d1b8398fe6f3e3c2748c0fc12fec2546c7f2e3ffd", size = 10034, upload-time = "2025-11-14T09:37:20.293Z" }, ] [[package]] name = "pyobjc-framework-avfoundation" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2232,58 +2260,58 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/1f/90cdbce1d3b4861cbb17c12adf57daeec32477eb1df8d3f9ab8551bdadfb/pyobjc_framework_avfoundation-11.1.tar.gz", hash = "sha256:6663056cc6ca49af8de6d36a7fff498f51e1a9a7f1bde7afba718a8ceaaa7377", size = 832178, upload-time = "2025-06-14T20:56:46.329Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/42/c026ab308edc2ed5582d8b4b93da6b15d1b6557c0086914a4aabedd1f032/pyobjc_framework_avfoundation-12.1.tar.gz", hash = "sha256:eda0bb60be380f9ba2344600c4231dd58a3efafa99fdc65d3673ecfbb83f6fcb", size = 310047, upload-time = "2025-11-14T10:09:40.069Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/48/31286b2b09a619d8047256d7180e0d511be71ab598e5f54f034977b59bbf/pyobjc_framework_avfoundation-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8a0ccbdba46b69dec1d12eea52eef56fcd63c492f73e41011bb72508b2aa2d0e", size = 70711, upload-time = "2025-06-14T20:45:52.461Z" }, - { url = "https://files.pythonhosted.org/packages/43/30/d5d03dd4a508bdaa2156ff379e9e109020de23cbb6316c5865d341aa6db1/pyobjc_framework_avfoundation-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94f065db4e87b1baebb5cf9f464cf9d82c5f903fff192001ebc974d9e3132c7e", size = 70746, upload-time = "2025-06-14T20:45:53.253Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5a/4ef36b309138840ff8cd85364f66c29e27023f291004c335a99f6e87e599/pyobjc_framework_avfoundation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82cc2c2d9ab6cc04feeb4700ff251d00f1fcafff573c63d4e87168ff80adb926", size = 83328, upload-time = "2025-11-14T09:37:40.808Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/ca471e5dd33f040f69320832e45415d00440260bf7f8221a9df4c4662659/pyobjc_framework_avfoundation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bf634f89265b4d93126153200d885b6de4859ed6b3bc65e69ff75540bc398406", size = 83375, upload-time = "2025-11-14T09:37:47.262Z" }, ] [[package]] name = "pyobjc-framework-avkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/61/ff/9f41f2b8de786871184b48c4e5052cb7c9fcc204e7fee06687fa32b08bed/pyobjc_framework_avkit-11.1.tar.gz", hash = "sha256:d948204a7b94e0e878b19a909f9b33342e19d9ea519571d66a21fce8f72e3263", size = 46825, upload-time = "2025-06-14T20:56:47.494Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/a9/e44db1a1f26e2882c140f1d502d508b1f240af9048909dcf1e1a687375b4/pyobjc_framework_avkit-12.1.tar.gz", hash = "sha256:a5c0ddb0cb700f9b09c8afeca2c58952d554139e9bb078236d2355b1fddfb588", size = 28473, upload-time = "2025-11-14T10:09:43.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/6c/ee7504367f4a9337d3e78cd34beb9fcb58ad30e274c2a9f1d8058b9837f2/pyobjc_framework_avkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:88f70e2a399e43ce7bc3124b3b35d65537daddb358ea542fbb0146fa6406be8a", size = 11517, upload-time = "2025-06-14T20:45:59.676Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2f/6ec6a4ec7eb9ca329f36bbd2a51750fe5064d44dd437d8615abb7121ec93/pyobjc_framework_avkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef9cd9fe37c6199bfde7ee5cd6e76ede23a6797932882785c53ef3070e209afb", size = 11539, upload-time = "2025-06-14T20:46:00.375Z" }, + { url = "https://files.pythonhosted.org/packages/8c/68/409ee30f3418b76573c70aa05fa4c38e9b8b1d4864093edcc781d66019c2/pyobjc_framework_avkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:78bd31a8aed48644e5407b444dec8b1e15ff77af765607b52edf88b8f1213ac7", size = 11583, upload-time = "2025-11-14T09:38:17.569Z" }, + { url = "https://files.pythonhosted.org/packages/75/34/e77b18f7ed0bd707afd388702e910bdf2d0acee39d1139e8619c916d3eb4/pyobjc_framework_avkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eef2c0a51465de025a4509db05ef18ca2b678bb00ee0a8fbad7fd470edfd58f9", size = 11613, upload-time = "2025-11-14T09:38:19.78Z" }, ] [[package]] name = "pyobjc-framework-avrouting" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cf/42/94bc18b968a4ee8b6427257f907ffbfc97f8ba6a6202953da149b649d638/pyobjc_framework_avrouting-11.1.tar.gz", hash = "sha256:7db1291d9f53cc58d34b2a826feb721a85f50ceb5e71952e8762baacd3db3fc0", size = 21069, upload-time = "2025-06-14T20:56:48.57Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/83/15bf6c28ec100dae7f92d37c9e117b3b4ee6b4873db062833e16f1cfd6c4/pyobjc_framework_avrouting-12.1.tar.gz", hash = "sha256:6a6c5e583d14f6501df530a9d0559a32269a821fc8140e3646015f097155cd1c", size = 20031, upload-time = "2025-11-14T10:09:45.701Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/d4/0d17fd5a761d8a3d7dab0e096315de694b47dd48d2bb9655534e44399385/pyobjc_framework_avrouting-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45cbabbf69764b2467d78adb8f3b7f209d1a8ee690e19f9a32d05c62a9c3a131", size = 8192, upload-time = "2025-06-14T20:46:05.479Z" }, - { url = "https://files.pythonhosted.org/packages/01/17/ce199bc7fb3ba1f7b0474554bd71d1bdd3d5a141e1d9722ff9f46c104e1d/pyobjc_framework_avrouting-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc309e175abf3961f933f8b341c0504b17f4717931242ebb121a83256b8b5c13", size = 8212, upload-time = "2025-06-14T20:46:06.17Z" }, + { url = "https://files.pythonhosted.org/packages/69/a7/5c5725db9c91b492ffbd4ae3e40025deeb9e60fcc7c8fbd5279b52280b95/pyobjc_framework_avrouting-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a79f05fb66e337cabc19a9d949c8b29a5145c879f42e29ba02b601b7700d1bb", size = 8431, upload-time = "2025-11-14T09:38:33.018Z" }, + { url = "https://files.pythonhosted.org/packages/68/54/fa24f666525c1332a11b2de959c9877b0fe08f00f29ecf96964b24246c13/pyobjc_framework_avrouting-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c0fb0d3d260527320377a70c87688ca5e4a208b09fddcae2b4257d7fe9b1e18", size = 8450, upload-time = "2025-11-14T09:38:34.941Z" }, ] [[package]] name = "pyobjc-framework-backgroundassets" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/76/21e1632a212f997d7a5f26d53eb997951978916858039b79f43ebe3d10b2/pyobjc_framework_backgroundassets-11.1.tar.gz", hash = "sha256:2e14b50539d96d5fca70c49f21b69fdbad81a22549e3630f5e4f20d5c0204fc2", size = 24803, upload-time = "2025-06-14T20:56:49.566Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/d1/e917fba82790495152fd3508c5053827658881cf7e9887ba60def5e3f221/pyobjc_framework_backgroundassets-12.1.tar.gz", hash = "sha256:8da34df9ae4519c360c429415477fdaf3fbba5addbc647b3340b8783454eb419", size = 26210, upload-time = "2025-11-14T10:09:48.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/ac/b1cb5c0ec2691ea225d53c2b9411d5ea1896f8f72eb5ca92978664443bb0/pyobjc_framework_backgroundassets-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd371ce08d1b79f540d5994139898097b83b1d4e4471c264892433d448b24de0", size = 9691, upload-time = "2025-06-14T20:46:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/ad/77/a6ad2df35fd71b3c26f52698d25174899ba1be134766022f5bf804ebf12d/pyobjc_framework_backgroundassets-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:13bf451c59b409b6ce1ac0e717a970a1b03bca7a944a7f19219da0d46ab7c561", size = 9707, upload-time = "2025-06-14T20:46:12.88Z" }, + { url = "https://files.pythonhosted.org/packages/c1/49/33c1c3eaf26a7d89dd414e14939d4f02063d66252d0f51c02082350223e0/pyobjc_framework_backgroundassets-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17de7990b5ea8047d447339f9e9e6f54b954ffc06647c830932a1688c4743fea", size = 10763, upload-time = "2025-11-14T09:38:46.671Z" }, + { url = "https://files.pythonhosted.org/packages/de/34/bbba61f0e8ecb0fe0da7aa2c9ea15f7cb0dca2fb2914fcdcd77b782b5c11/pyobjc_framework_backgroundassets-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2c11cb98650c1a4bc68eeb4b040541ba96613434c5957e98e9bb363413b23c91", size = 10786, upload-time = "2025-11-14T09:38:48.341Z" }, ] [[package]] name = "pyobjc-framework-browserenginekit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2292,82 +2320,82 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/30/75/087270d9f81e913b57c7db58eaff8691fa0574b11faf9302340b3b8320f1/pyobjc_framework_browserenginekit-11.1.tar.gz", hash = "sha256:918440cefb10480024f645169de3733e30ede65e41267fa12c7b90c264a0a479", size = 31944, upload-time = "2025-06-14T20:56:50.195Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/b9/39f9de1730e6f8e73be0e4f0c6087cd9439cbe11645b8052d22e1fb8e69b/pyobjc_framework_browserenginekit-12.1.tar.gz", hash = "sha256:6a1a34a155778ab55ab5f463e885f2a3b4680231264e1fe078e62ddeccce49ed", size = 29120, upload-time = "2025-11-14T10:09:51.582Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/29/ec0a0cc6fb15911769cb8e5ad8ada85e3f5cf4889fafbb90d936c6b7053b/pyobjc_framework_browserenginekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29b5f5949170af0235485e79aa465a7af2b2e0913d0c2c9ab1ac033224a90edb", size = 11088, upload-time = "2025-06-14T20:46:18.696Z" }, - { url = "https://files.pythonhosted.org/packages/89/90/a50bb66a5e041ace99b6c8b1df43b38d5f2e1bf771f57409e4aebf1dfae5/pyobjc_framework_browserenginekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9b815b167533015d62832b956e9cfb962bd2026f5a4ccd66718cf3bb2e15ab27", size = 11115, upload-time = "2025-06-14T20:46:19.401Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a4/2d576d71b2e4b3e1a9aa9fd62eb73167d90cdc2e07b425bbaba8edd32ff5/pyobjc_framework_browserenginekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41229c766fb3e5bba2de5e580776388297303b4d63d3065fef3f67b77ec46c3f", size = 11526, upload-time = "2025-11-14T09:38:58.861Z" }, + { url = "https://files.pythonhosted.org/packages/46/e0/8d2cebbfcfd6aacb805ae0ae7ba931f6a39140540b2e1e96719e7be28359/pyobjc_framework_browserenginekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d15766bb841b081447015c9626e2a766febfe651f487893d29c5d72bef976b94", size = 11545, upload-time = "2025-11-14T09:39:00.988Z" }, ] [[package]] name = "pyobjc-framework-businesschat" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/85/be/9d9d9d9383c411a58323ea510d768443287ca21610af652b815b3205ea80/pyobjc_framework_businesschat-11.1.tar.gz", hash = "sha256:69589d2f0cb4e7892e5ecc6aed79b1abd1ec55c099a7faacae6a326bc921259d", size = 12698, upload-time = "2025-06-14T20:56:51.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/da/bc09b6ed19e9ea38ecca9387c291ca11fa680a8132d82b27030f82551c23/pyobjc_framework_businesschat-12.1.tar.gz", hash = "sha256:f6fa3a8369a1a51363e1757530128741d9d09ed90692a1d6777a4c0fbad25868", size = 12055, upload-time = "2025-11-14T10:09:53.436Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/a4/5b8bb268b263678c0908cdaa8bed2534a6caac5862d05236f6c361d130ba/pyobjc_framework_businesschat-11.1-py2.py3-none-any.whl", hash = "sha256:7fdc1219b988ce3ae896bffd01f547c06cec3b4e4b2d0aa04d251444d7f1c2db", size = 3458, upload-time = "2025-06-14T20:46:24.651Z" }, + { url = "https://files.pythonhosted.org/packages/53/88/4c727424b05efa33ed7f6c45e40333e5a8a8dc5bb238e34695addd68463b/pyobjc_framework_businesschat-12.1-py2.py3-none-any.whl", hash = "sha256:f66ce741507b324de3c301d72ba0cfa6aaf7093d7235972332807645c118cc29", size = 3474, upload-time = "2025-11-14T09:39:10.771Z" }, ] [[package]] name = "pyobjc-framework-calendarstore" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/df/7ca8ee65b16d5fc862d7e8664289472eed918cf4d76921de6bdaa1461c65/pyobjc_framework_calendarstore-11.1.tar.gz", hash = "sha256:858ee00e6a380d9c086c2d7db82c116a6c406234038e0ec8fc2ad02e385dc437", size = 68215, upload-time = "2025-06-14T20:56:51.799Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/41/ae955d1c44dcc18b5b9df45c679e9a08311a0f853b9d981bca760cf1eef2/pyobjc_framework_calendarstore-12.1.tar.gz", hash = "sha256:f9a798d560a3c99ad4c0d2af68767bc5695d8b1aabef04d8377861cd1d6d1670", size = 52272, upload-time = "2025-11-14T10:09:58.48Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/94/69cb863bd88349df0f6cf491fd3ca4d674816c4d66270f9e2620cc6e16ed/pyobjc_framework_calendarstore-11.1-py2.py3-none-any.whl", hash = "sha256:bf066e17392c978becf17a61863eb81727bf593a2bfdab261177126072557e24", size = 5265, upload-time = "2025-06-14T20:46:25.457Z" }, + { url = "https://files.pythonhosted.org/packages/fa/70/f68aebdb7d3fa2dec2e9da9e9cdaa76d370de326a495917dbcde7bb7711e/pyobjc_framework_calendarstore-12.1-py2.py3-none-any.whl", hash = "sha256:18533e0fcbcdd29ee5884dfbd30606710f65df9b688bf47daee1438ee22e50cc", size = 5285, upload-time = "2025-11-14T09:39:12.473Z" }, ] [[package]] name = "pyobjc-framework-callkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/d5/4f0b62ab35be619e8c8d96538a03cf56fde6fd53540e1837e0fa588b3f6c/pyobjc_framework_callkit-11.1.tar.gz", hash = "sha256:b84d5ea38dff0cbe0754f5f9f6f33c742e216f12e7166179a8ec2cf4b0bfca94", size = 46648, upload-time = "2025-06-14T20:56:52.579Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c0/1859d4532d39254df085309aff55b85323576f00a883626325af40da4653/pyobjc_framework_callkit-12.1.tar.gz", hash = "sha256:fd6dc9688b785aab360139d683be56f0844bf68bf5e45d0eb770cb68221083cc", size = 29171, upload-time = "2025-11-14T10:10:01.336Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/f8/6e368225634cad9e457c4f8f0580ed318cb2f2c8110f2e56935fc12502f3/pyobjc_framework_callkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1db8b74abd6489d73c8619972730bea87a7d1f55d47649150fc1a30fdc6840fb", size = 11211, upload-time = "2025-06-14T20:46:27.146Z" }, - { url = "https://files.pythonhosted.org/packages/18/2a/209572a6dba6768a57667e1f87a83ce8cadf18de5d6b1a91b95ce548d0f8/pyobjc_framework_callkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:554e09ca3dab44d93a89927d9e300f004d2ef0db020b10425a4622b432e7b684", size = 11269, upload-time = "2025-06-14T20:46:28.164Z" }, + { url = "https://files.pythonhosted.org/packages/2b/f6/aafd14b31e00d59d830f9a8e8e46c4f41a249f0370499d5b017599362cf1/pyobjc_framework_callkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e73beae08e6a32bcced8d5bdb45b52d6a0866dd1485eaaddba6063f17d41fcb0", size = 11273, upload-time = "2025-11-14T09:39:16.837Z" }, + { url = "https://files.pythonhosted.org/packages/2e/b7/b3a498b14751b4be6af5272c9be9ded718aa850ebf769b052c7d610a142a/pyobjc_framework_callkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12adc0ace464a057f8908187698e1d417c6c53619797a69d096f4329bffb1089", size = 11334, upload-time = "2025-11-14T09:39:18.622Z" }, ] [[package]] name = "pyobjc-framework-carbon" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/a4/d751851865d9a78405cfec0c8b2931b1e96b9914e9788cd441fa4e8290d0/pyobjc_framework_carbon-11.1.tar.gz", hash = "sha256:047f098535479efa3ab89da1ebdf3cf9ec0b439a33a4f32806193886e9fcea71", size = 37291, upload-time = "2025-06-14T20:56:53.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/0f/9ab8e518a4e5ac4a1e2fdde38a054c32aef82787ff7f30927345c18b7765/pyobjc_framework_carbon-12.1.tar.gz", hash = "sha256:57a72807db252d5746caccc46da4bd20ff8ea9e82109af9f72735579645ff4f0", size = 37293, upload-time = "2025-11-14T10:10:04.464Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/44/f1a20b5aa3833af4d461074c479263a410ef90d17dbec11f78ad9c34dbab/pyobjc_framework_carbon-11.1-py2.py3-none-any.whl", hash = "sha256:1bf66853e939315ad7ee968170b16dd12cb838c42b80dfcd5354687760998825", size = 4753, upload-time = "2025-06-14T20:46:33.141Z" }, + { url = "https://files.pythonhosted.org/packages/a4/9e/91853c8f98b9d5bccf464113908620c94cc12c2a3e4625f3ce172e3ea4bc/pyobjc_framework_carbon-12.1-py2.py3-none-any.whl", hash = "sha256:f8b719b3c7c5cf1d61ac7c45a8a70b5e5e5a83fa02f5194c2a48a7e81a3d1b7f", size = 4625, upload-time = "2025-11-14T09:39:27.937Z" }, ] [[package]] name = "pyobjc-framework-cfnetwork" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/49/7b24172e3d6eb0ddffc33a7498a2bea264aa2958c3fecaeb463bef88f0b8/pyobjc_framework_cfnetwork-11.1.tar.gz", hash = "sha256:ad600163eeadb7bf71abc51a9b6f2b5462a018d3f9bb1510c5ce3fdf2f22959d", size = 79069, upload-time = "2025-06-14T20:56:54.615Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/6a/f5f0f191956e187db85312cbffcc41bf863670d121b9190b4a35f0d36403/pyobjc_framework_cfnetwork-12.1.tar.gz", hash = "sha256:2d16e820f2d43522c793f55833fda89888139d7a84ca5758548ba1f3a325a88d", size = 44383, upload-time = "2025-11-14T10:10:08.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/61/74b0d0430807615b7f91a688a871ffd94a61d4764a101e2a53e0c95dd05e/pyobjc_framework_cfnetwork-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d7a24746d0754b3a0042def2cd64aa205e5614f12ea0de9461c8e26d97633c72", size = 18953, upload-time = "2025-06-14T20:46:35.409Z" }, - { url = "https://files.pythonhosted.org/packages/c2/31/05b4fb79e7f738f7f7d7a58734de2fab47d9a1fb219c2180e8c07efe2550/pyobjc_framework_cfnetwork-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70beb8095df76e0e8eb7ab218be1e69ae180e01a4d77f7cad73c97b4eb7a296a", size = 19141, upload-time = "2025-06-14T20:46:36.134Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7e/82aca783499b690163dd19d5ccbba580398970874a3431bfd7c14ceddbb3/pyobjc_framework_cfnetwork-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bf93c0f3d262f629e72f8dd43384d0930ed8e610b3fc5ff555c0c1a1e05334a", size = 18949, upload-time = "2025-11-14T09:39:32.924Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/28034e63f3a25b30ede814469c3f57d44268cbced19664c84a8664200f9d/pyobjc_framework_cfnetwork-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:92760da248c757085fc39bce4388a0f6f0b67540e51edf60a92ad60ca907d071", size = 19135, upload-time = "2025-11-14T09:39:36.382Z" }, ] [[package]] name = "pyobjc-framework-cinematic" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2376,28 +2404,28 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/6f/c2d0b49e01e654496a1781bafb9da72a6fbd00f5abb39dc4a3a0045167c7/pyobjc_framework_cinematic-11.1.tar.gz", hash = "sha256:efde39a6a2379e1738dbc5434b2470cd187cf3114ffb81390b3b1abda470b382", size = 25522, upload-time = "2025-06-14T20:56:55.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/67/4e/f4cc7f9f7f66df0290c90fe445f1ff5aa514c6634f5203fe049161053716/pyobjc_framework_cinematic-12.1.tar.gz", hash = "sha256:795068c30447548c0e8614e9c432d4b288b13d5614622ef2f9e3246132329b06", size = 21215, upload-time = "2025-11-14T10:10:10.795Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/bd/a9b51c770bd96546a101c9e9994f851b87336f168a77048241517ca4db8c/pyobjc_framework_cinematic-11.1-py2.py3-none-any.whl", hash = "sha256:b62c024c1a9c7890481bc2fdfaf0cd3c251a4a08357d57dc1795d98920fcdbd1", size = 4562, upload-time = "2025-06-14T20:46:40.989Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a0/cd85c827ce5535c08d936e5723c16ee49f7ff633f2e9881f4f58bf83e4ce/pyobjc_framework_cinematic-12.1-py2.py3-none-any.whl", hash = "sha256:c003543bb6908379680a93dfd77a44228686b86c118cf3bc930f60241d0cd141", size = 5031, upload-time = "2025-11-14T09:39:49.003Z" }, ] [[package]] name = "pyobjc-framework-classkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7a/8b/5150b4faddd15d5dd795bc62b2256c4f7dafc983cfa694fcf88121ea0016/pyobjc_framework_classkit-11.1.tar.gz", hash = "sha256:ee1e26395eb00b3ed5442e3234cdbfe925d2413185af38eca0477d7166651df4", size = 39831, upload-time = "2025-06-14T20:56:56.036Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/ef/67815278023b344a79c7e95f748f647245d6f5305136fc80615254ad447c/pyobjc_framework_classkit-12.1.tar.gz", hash = "sha256:8d1e9dd75c3d14938ff533d88b72bca2d34918e4461f418ea323bfb2498473b4", size = 26298, upload-time = "2025-11-14T10:10:13.406Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/89/86/5b9ef1d5aa3f4835d164c9be46afae634911db56c6ad7795e212ef9bb50b/pyobjc_framework_classkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:018da363d06f3615c07a8623cbdb024a31b1f8b96a933ff2656c0e903063842c", size = 8895, upload-time = "2025-06-14T20:46:42.689Z" }, - { url = "https://files.pythonhosted.org/packages/75/79/2552fd5e1da73dffb35589469b3cd8c0928e3100462761350d19ea922e59/pyobjc_framework_classkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:161dcb9b718649e6331a5eab5a76c2b43a9b322b15b37b3f8f9c5faad12ee6d1", size = 8911, upload-time = "2025-06-14T20:46:43.714Z" }, + { url = "https://files.pythonhosted.org/packages/14/e2/67bd062fbc9761c34b9911ed099ee50ccddc3032779ce420ca40083ee15c/pyobjc_framework_classkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd90aacc68eff3412204a9040fa81eb18348cbd88ed56d33558349f3e51bff52", size = 8857, upload-time = "2025-11-14T09:39:53.283Z" }, + { url = "https://files.pythonhosted.org/packages/87/5e/cf43c647af872499fc8e80cc6ac6e9ad77d9c77861dc2e62bdd9b01473ce/pyobjc_framework_classkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c027a3cd9be5fee3f605589118b8b278297c384a271f224c1a98b224e0c087e6", size = 8877, upload-time = "2025-11-14T09:39:54.979Z" }, ] [[package]] name = "pyobjc-framework-cloudkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -2406,880 +2434,907 @@ dependencies = [ { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/58/a6/bfe5be55ed95704efca0e86b218155a9c801735107cedba3af8ea4580a05/pyobjc_framework_cloudkit-11.1.tar.gz", hash = "sha256:40d2dc4bf28c5be9b836b01e4d267a15d847d756c2a65530e1fcd79b2825e86d", size = 122778, upload-time = "2025-06-14T20:56:56.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/09/762ee4f3ae8568b8e0e5392c705bc4aa1929aa454646c124ca470f1bf9fc/pyobjc_framework_cloudkit-12.1.tar.gz", hash = "sha256:1dddd38e60863f88adb3d1d37d3b4ccb9cbff48c4ef02ab50e36fa40c2379d2f", size = 53730, upload-time = "2025-11-14T10:10:17.831Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/d9/5570a217cef8130708e860b86f4f22bb5827247c97121523a9dfd4784148/pyobjc_framework_cloudkit-11.1-py2.py3-none-any.whl", hash = "sha256:c583e40c710cf85ebe34173d1d2995e832a20127edc8899b2f35b13f98498af1", size = 10870, upload-time = "2025-06-14T20:46:48.781Z" }, + { url = "https://files.pythonhosted.org/packages/35/71/cbef7179bf1a594558ea27f1e5ad18f5c17ef71a8a24192aae16127bc849/pyobjc_framework_cloudkit-12.1-py2.py3-none-any.whl", hash = "sha256:875e37bf1a2ce3d05c2492692650104f2d908b56b71a0aedf6620bc517c6c9ca", size = 11090, upload-time = "2025-11-14T09:40:04.207Z" }, ] [[package]] name = "pyobjc-framework-cocoa" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4b/c5/7a866d24bc026f79239b74d05e2cf3088b03263da66d53d1b4cf5207f5ae/pyobjc_framework_cocoa-11.1.tar.gz", hash = "sha256:87df76b9b73e7ca699a828ff112564b59251bb9bbe72e610e670a4dc9940d038", size = 5565335, upload-time = "2025-06-14T20:56:59.683Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/43/6841046aa4e257b6276cd23e53cacedfb842ecaf3386bb360fa9cc319aa1/pyobjc_framework_cocoa-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b9a9b8ba07f5bf84866399e3de2aa311ed1c34d5d2788a995bdbe82cc36cfa0", size = 388177, upload-time = "2025-06-14T20:46:51.454Z" }, - { url = "https://files.pythonhosted.org/packages/68/da/41c0f7edc92ead461cced7e67813e27fa17da3c5da428afdb4086c69d7ba/pyobjc_framework_cocoa-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:806de56f06dfba8f301a244cce289d54877c36b4b19818e3b53150eb7c2424d0", size = 388983, upload-time = "2025-06-14T20:46:52.591Z" }, + { url = "https://files.pythonhosted.org/packages/3f/07/5760735c0fffc65107e648eaf7e0991f46da442ac4493501be5380e6d9d4/pyobjc_framework_cocoa-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52228bcf38da64b77328787967d464e28b981492b33a7675585141e1b0a01e6", size = 383812, upload-time = "2025-11-14T09:40:53.169Z" }, + { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, ] [[package]] name = "pyobjc-framework-collaboration" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/49/9dbe8407d5dd663747267c1234d1b914bab66e1878d22f57926261a3063b/pyobjc_framework_collaboration-11.1.tar.gz", hash = "sha256:4564e3931bfc51773623d4f57f2431b58a39b75cb964ae5c48d27ee4dde2f4ea", size = 16839, upload-time = "2025-06-14T20:57:01.101Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/21/77fe64b39eae98412de1a0d33e9c735aa9949d53fff6b2d81403572b410b/pyobjc_framework_collaboration-12.1.tar.gz", hash = "sha256:2afa264d3233fc0a03a56789c6fefe655ffd81a2da4ba1dc79ea0c45931ad47b", size = 14299, upload-time = "2025-11-14T10:13:04.631Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/24/4c9deedcc62d223a45d4b4fa16162729923d2b3e2231467de6ecd079f3f8/pyobjc_framework_collaboration-11.1-py2.py3-none-any.whl", hash = "sha256:3629ea5b56c513fb330d43952afabb2df2a2ac2f9048b8ec6e8ab4486191390a", size = 4891, upload-time = "2025-06-14T20:46:59.734Z" }, + { url = "https://files.pythonhosted.org/packages/2a/66/1507de01f1e2b309f8e11553a52769e4e2e9939ed770b5b560ef5bc27bc1/pyobjc_framework_collaboration-12.1-py2.py3-none-any.whl", hash = "sha256:182d6e6080833b97f9bef61738ae7bacb509714538f0d7281e5f0814c804b315", size = 4907, upload-time = "2025-11-14T09:42:55.781Z" }, ] [[package]] name = "pyobjc-framework-colorsync" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b5/97/7613b6041f62c52f972e42dd5d79476b56b84d017a8b5e4add4d9cfaca36/pyobjc_framework_colorsync-11.1.tar.gz", hash = "sha256:7a346f71f34b2ccd1b020a34c219b85bf8b6f6e05283d503185aeb7767a269dd", size = 38999, upload-time = "2025-06-14T20:57:01.761Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/b4/706e4cc9db25b400201fc90f3edfaa1ab2d51b400b19437b043a68532078/pyobjc_framework_colorsync-12.1.tar.gz", hash = "sha256:d69dab7df01245a8c1bd536b9231c97993a5d1a2765d77692ce40ebbe6c1b8e9", size = 25269, upload-time = "2025-11-14T10:13:07.522Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/e1/82e45c712f43905ee1e6d585180764e8fa6b6f1377feb872f9f03c8c1fb8/pyobjc_framework_colorsync-12.1-py2.py3-none-any.whl", hash = "sha256:41e08d5b9a7af4b380c9adab24c7ff59dfd607b3073ae466693a3e791d8ffdc9", size = 6020, upload-time = "2025-11-14T09:42:57.504Z" }, +] + +[[package]] +name = "pyobjc-framework-compositorservices" +version = "12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/c5/0ba31d7af7e464b7f7ece8c2bd09112bdb0b7260848402e79ba6aacc622c/pyobjc_framework_compositorservices-12.1.tar.gz", hash = "sha256:028e357bbee7fbd3723339a321bbe14e6da5a772708a661a13eea5f17c89e4ab", size = 23292, upload-time = "2025-11-14T10:13:10.392Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/d5/c8fc7c47cbb9865058094dc9cf3f57879156ff55fb261cf199e7081d1db7/pyobjc_framework_colorsync-11.1-py2.py3-none-any.whl", hash = "sha256:d19d6da2c7175a3896a63c9b40a8ab98ade0779a5b40062789681501c33efd5c", size = 5971, upload-time = "2025-06-14T20:47:00.547Z" }, + { url = "https://files.pythonhosted.org/packages/f9/34/5a2de8d531dbb88023898e0b5d2ce8edee14751af6c70e6103f6aa31a669/pyobjc_framework_compositorservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ef22d4eacd492e13099b9b8936db892cdbbef1e3d23c3484e0ed749f83c4984", size = 5910, upload-time = "2025-11-14T09:42:59.154Z" }, ] [[package]] name = "pyobjc-framework-contacts" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a6/85/34868b6447d552adf8674bac226b55c2baacacee0d67ee031e33805d6faa/pyobjc_framework_contacts-11.1.tar.gz", hash = "sha256:752036e7d8952a4122296d7772f274170a5f35a53ee6454a27f3e1d9603222cc", size = 84814, upload-time = "2025-06-14T20:57:02.582Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/a0/ce0542d211d4ea02f5cbcf72ee0a16b66b0d477a4ba5c32e00117703f2f0/pyobjc_framework_contacts-12.1.tar.gz", hash = "sha256:89bca3c5cf31404b714abaa1673577e1aaad6f2ef49d4141c6dbcc0643a789ad", size = 42378, upload-time = "2025-11-14T10:13:14.203Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/e1/27715ef476441cb05d4442b93fe6380a57a946cda008f70399cadb4ff1fd/pyobjc_framework_contacts-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:68148653f27c1eaeff2ad4831b5e68393071a382aab773629cd047ce55556726", size = 12067, upload-time = "2025-06-14T20:47:02.178Z" }, - { url = "https://files.pythonhosted.org/packages/30/c8/0d47af11112bf382e059cfe2dd03be98914f0621ddff8858bb9af864f8c5/pyobjc_framework_contacts-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:576ee4aec05d755444bff10b45833f73083b5b3d1b2740e133b92111f7765e54", size = 12141, upload-time = "2025-06-14T20:47:02.884Z" }, + { url = "https://files.pythonhosted.org/packages/94/f5/5d2c03cf5219f2e35f3f908afa11868e9096aff33b29b41d63f2de3595f2/pyobjc_framework_contacts-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8ab86070895a005239256d207e18209b1a79d35335b6604db160e8375a7165e6", size = 12086, upload-time = "2025-11-14T09:43:03.225Z" }, + { url = "https://files.pythonhosted.org/packages/32/c8/2c4638c0d06447886a34070eebb9ba57407d4dd5f0fcb7ab642568272b88/pyobjc_framework_contacts-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2e5ce33b686eb9c0a39351938a756442ea8dea88f6ae2f16bff5494a8569c687", size = 12165, upload-time = "2025-11-14T09:43:05.119Z" }, ] [[package]] name = "pyobjc-framework-contactsui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-contacts", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3f/57/8765b54a30edaa2a56df62e11e7c32e41b6ea300513256adffa191689368/pyobjc_framework_contactsui-11.1.tar.gz", hash = "sha256:5bc29ea2b10a342018e1b96be6b140c10ebe3cfb6417278770feef5e88026a1f", size = 20031, upload-time = "2025-06-14T20:57:03.603Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/0c/7bb7f898456a81d88d06a1084a42e374519d2e40a668a872b69b11f8c1f9/pyobjc_framework_contactsui-12.1.tar.gz", hash = "sha256:aaeca7c9e0c9c4e224d73636f9a558f9368c2c7422155a41fd4d7a13613a77c1", size = 18769, upload-time = "2025-11-14T10:13:16.301Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/38/02/f65f2eb6e2ad91c95e5a6b532fe8dd5cd0c190fbaff71e4a85346e16c0f6/pyobjc_framework_contactsui-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c0f03c71e63daf5dbf760bf0e45620618a6f1ea62f8c17e288463c1fd4d2685", size = 7858, upload-time = "2025-06-14T20:47:08.346Z" }, - { url = "https://files.pythonhosted.org/packages/46/b6/50ec09f1bb18c422b8c079e02328689f32e977b43ab7651c05e8274854dc/pyobjc_framework_contactsui-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c34a6f27ef5aa4742cc44fd5b4d16fe1e1745ff839578b4c059faf2c58eee3ca", size = 7875, upload-time = "2025-06-14T20:47:09.041Z" }, + { url = "https://files.pythonhosted.org/packages/04/e3/8d330640bf0337289834334c54c599fec2dad38a8a3b736d40bcb5d8db6e/pyobjc_framework_contactsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:10e7ce3b105795919605be89ebeecffd656e82dbf1bafa5db6d51d6def2265ee", size = 7871, upload-time = "2025-11-14T09:43:16.973Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ab/319aa52dfe6f836f4dc542282c2c13996222d4f5c9ea7ff8f391b12dac83/pyobjc_framework_contactsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:057f40d2f6eb1b169a300675ec75cc7a747cddcbcee8ece133e652a7086c5ab5", size = 7888, upload-time = "2025-11-14T09:43:18.502Z" }, ] [[package]] name = "pyobjc-framework-coreaudio" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/39/c0/4ab6005cf97e534725b0c14b110d4864b367c282b1c5b0d8f42aad74a83f/pyobjc_framework_coreaudio-11.1.tar.gz", hash = "sha256:b7b89540ae7efc6c1e3208ac838ef2acfc4d2c506dd629d91f6b3b3120e55c1b", size = 141032, upload-time = "2025-06-14T20:57:04.348Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/d1/0b884c5564ab952ff5daa949128c64815300556019c1bba0cf2ca752a1a0/pyobjc_framework_coreaudio-12.1.tar.gz", hash = "sha256:a9e72925fcc1795430496ce0bffd4ddaa92c22460a10308a7283ade830089fe1", size = 75077, upload-time = "2025-11-14T10:13:22.345Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/1d/81339c1087519a9f125396c717b85a05b49c2c54137bdf4ca01c1ccb6239/pyobjc_framework_coreaudio-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:73a46f0db2fa8ca2e8c47c3ddcc2751e67a0f8600246a6718553b15ee0dbbdb6", size = 35383, upload-time = "2025-06-14T20:47:14.234Z" }, - { url = "https://files.pythonhosted.org/packages/3d/fe/c43521642db98a4ec29fa535781c1316342bb52d5fc709696cbb1e8ca6cd/pyobjc_framework_coreaudio-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2538d1242dab4e27efb346eafbad50594e7e95597fa7220f0bab2099c825da55", size = 36765, upload-time = "2025-06-14T20:47:15.344Z" }, + { url = "https://files.pythonhosted.org/packages/9e/25/491ff549fd9a40be4416793d335bff1911d3d1d1e1635e3b0defbd2cf585/pyobjc_framework_coreaudio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a452de6b509fa4a20160c0410b72330ac871696cd80237883955a5b3a4de8f2a", size = 35327, upload-time = "2025-11-14T09:43:32.523Z" }, + { url = "https://files.pythonhosted.org/packages/a9/48/05b5192122e23140cf583eac99ccc5bf615591d6ff76483ba986c38ee750/pyobjc_framework_coreaudio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a5ad6309779663f846ab36fe6c49647e470b7e08473c3e48b4f004017bdb68a4", size = 36908, upload-time = "2025-11-14T09:43:36.108Z" }, ] [[package]] name = "pyobjc-framework-coreaudiokit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/4e/c49b26c60047c511727efe994b412276c487dfe90f1ee0fced0bddbdf8a3/pyobjc_framework_coreaudiokit-11.1.tar.gz", hash = "sha256:0b461c3d6123fda4da6b6aaa022efc918c1de2e126a5cf07d2189d63fa54ba40", size = 21955, upload-time = "2025-06-14T20:57:05.218Z" } +sdist = { url = "https://files.pythonhosted.org/packages/41/1c/5c7e39b9361d4eec99b9115b593edd9825388acd594cb3b4519f8f1ac12c/pyobjc_framework_coreaudiokit-12.1.tar.gz", hash = "sha256:b83624f8de3068ab2ca279f786be0804da5cf904ff9979d96007b69ef4869e1e", size = 20137, upload-time = "2025-11-14T10:13:24.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/44/0de5d26e383d0b00f2f44394db74e0953bc1e35b74072a67fde916e8e31e/pyobjc_framework_coreaudiokit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4743fbd210159cffffb0a7b8e06bf8b8527ba4bf01e76806fae2696fd6990e77", size = 7234, upload-time = "2025-06-14T20:47:21.271Z" }, - { url = "https://files.pythonhosted.org/packages/18/27/d8ff6293851a7d9665724fa5c324d28200776ec10a04b850ba21ad1f9be1/pyobjc_framework_coreaudiokit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:20440a2926b1d91da8efc8bc060e77c7a195cb0443dbf3770eaca9e597276748", size = 7266, upload-time = "2025-06-14T20:47:22.136Z" }, + { url = "https://files.pythonhosted.org/packages/c2/53/e4233fbe5b94b124f5612e1edc130a9280c4674a1d1bf42079ea14b816e1/pyobjc_framework_coreaudiokit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e1144c272f8d6429a34a6757700048f4631eb067c4b08d4768ddc28c371a7014", size = 7250, upload-time = "2025-11-14T09:43:53.208Z" }, + { url = "https://files.pythonhosted.org/packages/19/d7/f171c04c6496afeaad2ab658b0c810682c8407127edc94d4b3f3b90c2bb1/pyobjc_framework_coreaudiokit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:97d5dd857e73d5b597cfc980972b021314b760e2f5bdde7bbba0334fbf404722", size = 7273, upload-time = "2025-11-14T09:43:55.411Z" }, ] [[package]] name = "pyobjc-framework-corebluetooth" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fe/2081dfd9413b7b4d719935c33762fbed9cce9dc06430f322d1e2c9dbcd91/pyobjc_framework_corebluetooth-11.1.tar.gz", hash = "sha256:1deba46e3fcaf5e1c314f4bbafb77d9fe49ec248c493ad00d8aff2df212d6190", size = 60337, upload-time = "2025-06-14T20:57:05.919Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4b/25/d21d6cb3fd249c2c2aa96ee54279f40876a0c93e7161b3304bf21cbd0bfe/pyobjc_framework_corebluetooth-12.1.tar.gz", hash = "sha256:8060c1466d90bbb9100741a1091bb79975d9ba43911c9841599879fc45c2bbe0", size = 33157, upload-time = "2025-11-14T10:13:28.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/75/3318e85b7328c99c752e40592a907fc5c755cddc6d73beacbb432f6aa2d0/pyobjc_framework_corebluetooth-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:433b8593eb1ea8b6262b243ec903e1de4434b768ce103ebe15aac249b890cc2a", size = 13143, upload-time = "2025-06-14T20:47:28.889Z" }, - { url = "https://files.pythonhosted.org/packages/8a/bc/083ea1ae57a31645df7fad59921528f6690995f7b7c84a203399ded7e7fe/pyobjc_framework_corebluetooth-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:36bef95a822c68b72f505cf909913affd61a15b56eeaeafea7302d35a82f4f05", size = 13163, upload-time = "2025-06-14T20:47:29.624Z" }, + { url = "https://files.pythonhosted.org/packages/57/7a/26ae106beb97e9c4745065edb3ce3c2bdd91d81f5b52b8224f82ce9d5fb9/pyobjc_framework_corebluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:37e6456c8a076bd5a2bdd781d0324edd5e7397ef9ac9234a97433b522efb13cf", size = 13189, upload-time = "2025-11-14T09:44:06.229Z" }, + { url = "https://files.pythonhosted.org/packages/2a/56/01fef62a479cdd6ff9ee40b6e062a205408ff386ce5ba56d7e14a71fcf73/pyobjc_framework_corebluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe72c9732ee6c5c793b9543f08c1f5bdd98cd95dfc9d96efd5708ec9d6eeb213", size = 13209, upload-time = "2025-11-14T09:44:08.203Z" }, ] [[package]] name = "pyobjc-framework-coredata" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/e3/af497da7a7c895b6ff529d709d855a783f34afcc4b87ab57a1a2afb3f876/pyobjc_framework_coredata-11.1.tar.gz", hash = "sha256:fe9fd985f8e06c70c0fb1e6bbea5b731461f9e76f8f8d8e89c7c72667cdc6adf", size = 260628, upload-time = "2025-06-14T20:57:06.729Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/c5/8cd46cd4f1b7cf88bdeed3848f830ea9cdcc4e55cd0287a968a2838033fb/pyobjc_framework_coredata-12.1.tar.gz", hash = "sha256:1e47d3c5e51fdc87a90da62b97cae1bc49931a2bb064db1305827028e1fc0ffa", size = 124348, upload-time = "2025-11-14T10:13:36.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/d9/7f12bdba0503d0ef7b1ac26e05429aecc33b4aaf190155a6bec8b576850d/pyobjc_framework_coredata-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c66ae04cc658eafdfb987f9705e21f9782edee6773a8adb6bfa190500e4e7e29", size = 16428, upload-time = "2025-06-14T20:47:34.981Z" }, - { url = "https://files.pythonhosted.org/packages/5b/ac/77935aa9891bd6be952b1e6780df2bae748971dd0fe0b5155894004840bd/pyobjc_framework_coredata-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c9b2374784e67694a18fc8c120a12f11b355a20b643c01f23ae2ce87330a75e0", size = 16443, upload-time = "2025-06-14T20:47:35.711Z" }, + { url = "https://files.pythonhosted.org/packages/6b/a8/4c694c85365071baef36013a7460850dcf6ebfea0ba239e52d7293cdcb93/pyobjc_framework_coredata-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c861dc42b786243cbd96d9ea07d74023787d03637ef69a2f75a1191a2f16d9d6", size = 16395, upload-time = "2025-11-14T09:44:21.105Z" }, + { url = "https://files.pythonhosted.org/packages/a3/29/fe24dc81e0f154805534923a56fe572c3b296092f086cf5a239fccc2d46a/pyobjc_framework_coredata-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3ee3581ca23ead0b152257e98622fe0bf7e7948f30a62a25a17cafe28fe015e", size = 16409, upload-time = "2025-11-14T09:44:23.582Z" }, ] [[package]] name = "pyobjc-framework-corehaptics" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5f/83/cc997ec4687a68214dd3ad1bdf64353305f5c7e827fad211adac4c28b39f/pyobjc_framework_corehaptics-11.1.tar.gz", hash = "sha256:e5da3a97ed6aca9b7268c8c5196c0a339773a50baa72d1502d3435dc1a2a80f1", size = 42722, upload-time = "2025-06-14T20:57:08.019Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/2f/74a3da79d9188b05dd4be4428a819ea6992d4dfaedf7d629027cf1f57bfc/pyobjc_framework_corehaptics-12.1.tar.gz", hash = "sha256:521dd2986c8a4266d583dd9ed9ae42053b11ae7d3aa89bf53fbee88307d8db10", size = 22164, upload-time = "2025-11-14T10:13:38.941Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/d0/0fb20c0f19beae53c905653ffdcbf32e3b4119420c737ff4733f7ebb3b29/pyobjc_framework_corehaptics-11.1-py2.py3-none-any.whl", hash = "sha256:8f8c47ccca5052d07f95d2f35e6e399c5ac1f2072ba9d9eaae902edf4e3a7af4", size = 5363, upload-time = "2025-06-14T20:47:40.582Z" }, + { url = "https://files.pythonhosted.org/packages/25/f4/f469d6a9cac7c195f3d08fa65f94c32dd1dcf97a54b481be648fb3a7a5f3/pyobjc_framework_corehaptics-12.1-py2.py3-none-any.whl", hash = "sha256:a3b07d36ddf5c86a9cdaa411ab53d09553d26ea04fc7d4f82d21a84f0fc05fc0", size = 5382, upload-time = "2025-11-14T09:44:34.725Z" }, ] [[package]] name = "pyobjc-framework-corelocation" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/ef/fbd2e01ec137208af7bfefe222773748d27f16f845b0efa950d65e2bd719/pyobjc_framework_corelocation-11.1.tar.gz", hash = "sha256:46a67b99925ee3d53914331759c6ee110b31bb790b74b05915acfca41074c206", size = 104508, upload-time = "2025-06-14T20:57:08.731Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/79/b75885e0d75397dc2fe1ed9ca80be2b64c18b817f5fb924277cb1bf7b163/pyobjc_framework_corelocation-12.1.tar.gz", hash = "sha256:3674e9353f949d91dde6230ad68f6d5748a7f0424751e08a2c09d06050d66231", size = 53511, upload-time = "2025-11-14T10:13:43.384Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/f9/8137e8bf86f8e6350298217dcc635fd6577d64b484f9d250ddb85a84efa0/pyobjc_framework_corelocation-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ea261e7d87c6f62f1b03c252c273ea7fd6f314e3e73c69c6fb3fe807bf183462", size = 12741, upload-time = "2025-06-14T20:47:42.583Z" }, - { url = "https://files.pythonhosted.org/packages/95/cb/282d59421cdb89a5e5fcce72fc37d6eeace98a2a86d71f3be3cd47801298/pyobjc_framework_corelocation-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:562e31124f80207becfd8df01868f73fa5aa70169cc4460e1209fb16916e4fb4", size = 12752, upload-time = "2025-06-14T20:47:43.273Z" }, + { url = "https://files.pythonhosted.org/packages/34/ac/44b6cb414ce647da8328d0ed39f0a8b6eb54e72189ce9049678ce2cb04c3/pyobjc_framework_corelocation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ffc96b9ba504b35fe3e0fcfb0153e68fdfca6fe71663d240829ceab2d7122588", size = 12700, upload-time = "2025-11-14T09:44:38.717Z" }, + { url = "https://files.pythonhosted.org/packages/71/57/1b670890fbf650f1a00afe5ee897ea3856a4a1417c2304c633ee2e978ed0/pyobjc_framework_corelocation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c35ad29a062fea7d417fd8997a9309660ba7963f2847c004e670efbe6bb5b00", size = 12721, upload-time = "2025-11-14T09:44:41.185Z" }, ] [[package]] name = "pyobjc-framework-coremedia" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/5d/81513acd219df77a89176f1574d936b81ad6f6002225cabb64d55efb7e8d/pyobjc_framework_coremedia-11.1.tar.gz", hash = "sha256:82cdc087f61e21b761e677ea618a575d4c0dbe00e98230bf9cea540cff931db3", size = 216389, upload-time = "2025-06-14T20:57:09.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/7d/5ad600ff7aedfef8ba8f51b11d9aaacdf247b870bd14045d6e6f232e3df9/pyobjc_framework_coremedia-12.1.tar.gz", hash = "sha256:166c66a9c01e7a70103f3ca44c571431d124b9070612ef63a1511a4e6d9d84a7", size = 89566, upload-time = "2025-11-14T10:13:49.788Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/48/811ccea77d2c0d8156a489e2900298502eb6648d9c041c7f0c514c8f8a29/pyobjc_framework_coremedia-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:aacf47006e1c6bf6124fb2b5016a8d5fd5cf504b6b488f9eba4e389ab0f0a051", size = 29118, upload-time = "2025-06-14T20:47:48.895Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d1/b3d004d6a2d2188d196779d92fe8cfa2533f5722cd216fbc4f0cffc63b24/pyobjc_framework_coremedia-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ea5055298af91e463ffa7977d573530f9bada57b8f2968dcc76a75e339b9a598", size = 29015, upload-time = "2025-06-14T20:47:49.655Z" }, + { url = "https://files.pythonhosted.org/packages/c8/bc/e66de468b3777d8fece69279cf6d2af51d2263e9a1ccad21b90c35c74b1b/pyobjc_framework_coremedia-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ee7b822c9bb674b5b0a70bfb133410acae354e9241b6983f075395f3562f3c46", size = 29503, upload-time = "2025-11-14T09:44:54.716Z" }, + { url = "https://files.pythonhosted.org/packages/c8/ae/f773cdc33c34a3f9ce6db829dbf72661b65c28ea9efaec8940364185b977/pyobjc_framework_coremedia-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:161a627f5c8cd30a5ebb935189f740e21e6cd94871a9afd463efdb5d51e255fa", size = 29396, upload-time = "2025-11-14T09:44:57.563Z" }, ] [[package]] name = "pyobjc-framework-coremediaio" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/68/9cef2aefba8e69916049ff43120e8794df8051bdf1f690a55994bbe4eb57/pyobjc_framework_coremediaio-11.1.tar.gz", hash = "sha256:bccd69712578b177144ded398f4695d71a765ef61204da51a21f0c90b4ad4c64", size = 108326, upload-time = "2025-06-14T20:57:10.435Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/8e/23baee53ccd6c011c965cff62eb55638b4088c3df27d2bf05004105d6190/pyobjc_framework_coremediaio-12.1.tar.gz", hash = "sha256:880b313b28f00b27775d630174d09e0b53d1cdbadb74216618c9dd5b3eb6806a", size = 51100, upload-time = "2025-11-14T10:13:54.277Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/38/6bcddd7d57fa19173621aa29b46342756ed1a081103d24e4bdac1cf882fe/pyobjc_framework_coremediaio-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4438713ee4611d5310f4f2e71e557b6138bc79c0363e3d45ecb8c09227dfa58e", size = 17203, upload-time = "2025-06-14T20:47:55.781Z" }, - { url = "https://files.pythonhosted.org/packages/4b/b5/5dd941c1d7020a78b563a213fb8be7c6c3c1073c488914e158cd5417f4f7/pyobjc_framework_coremediaio-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:39ad2518de9943c474e5ca0037e78f92423c3352deeee6c513a489016dac1266", size = 17250, upload-time = "2025-06-14T20:47:56.505Z" }, + { url = "https://files.pythonhosted.org/packages/46/6c/88514f8938719f74aa13abb9fd5492499f1834391133809b4e125c3e7150/pyobjc_framework_coremediaio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3da79c5b9785c5ccc1f5982de61d4d0f1ba29717909eb6720734076ccdc0633c", size = 17218, upload-time = "2025-11-14T09:45:15.294Z" }, + { url = "https://files.pythonhosted.org/packages/d4/0c/9425c53c9a8c26e468e065ba12ef076bab20197ff7c82052a6dddd46d42b/pyobjc_framework_coremediaio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1108f8a278928fbca465f95123ea4a56456bd6571c1dc8b91793e6c61d624517", size = 17277, upload-time = "2025-11-14T09:45:17.457Z" }, ] [[package]] name = "pyobjc-framework-coremidi" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/ca/2ae5149966ccd78290444f88fa62022e2b96ed2fddd47e71d9fd249a9f82/pyobjc_framework_coremidi-11.1.tar.gz", hash = "sha256:095030c59d50c23aa53608777102bc88744ff8b10dfb57afe24b428dcd12e376", size = 107817, upload-time = "2025-06-14T20:57:11.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/75/96/2d583060a71a73c8a7e6d92f2a02675621b63c1f489f2639e020fae34792/pyobjc_framework_coremidi-12.1.tar.gz", hash = "sha256:3c6f1fd03997c3b0f20ab8545126b1ce5f0cddcc1587dffacad876c161da8c54", size = 55587, upload-time = "2025-11-14T10:13:58.903Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/fc/db75c55e492e5e34be637da2eeeaadbb579655b6d17159de419237bc9bdf/pyobjc_framework_coremidi-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5f8c2fdc9d1b7967e2a5ec0d5281eaddc00477bed9753aa14d5b881dc3a9ad8f", size = 24256, upload-time = "2025-06-14T20:48:02.448Z" }, - { url = "https://files.pythonhosted.org/packages/c2/2d/57c279dd74a9073d1416b10b05ebb9598f4868cad010d87f46ef4b517324/pyobjc_framework_coremidi-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:deb9120478a831a898f22f68737fc683bb9b937e77556e78b75986aebd349c55", size = 24277, upload-time = "2025-06-14T20:48:03.184Z" }, + { url = "https://files.pythonhosted.org/packages/76/d5/49b8720ec86f64e3dc3c804bd7e16fabb2a234a9a8b1b6753332ed343b4e/pyobjc_framework_coremidi-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af3cdf195e8d5e30d1203889cc4107bebc6eb901aaa81bf3faf15e9ffaca0735", size = 24282, upload-time = "2025-11-14T09:45:32.288Z" }, + { url = "https://files.pythonhosted.org/packages/e3/2d/99520f6f1685e4cad816e55cbf6d85f8ce6ea908107950e2d37dc17219d8/pyobjc_framework_coremidi-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e84ffc1de59691c04201b0872e184fe55b5589f3a14876bd14460f3b5f3cd109", size = 24317, upload-time = "2025-11-14T09:45:34.92Z" }, ] [[package]] name = "pyobjc-framework-coreml" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/5d/4309f220981d769b1a2f0dcb2c5c104490d31389a8ebea67e5595ce1cb74/pyobjc_framework_coreml-11.1.tar.gz", hash = "sha256:775923eefb9eac2e389c0821b10564372de8057cea89f1ea1cdaf04996c970a7", size = 82005, upload-time = "2025-06-14T20:57:12.004Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/9c/2218a8f457f56075a8a3f2490bd9d01c8e69c807139eaa0a6ac570531ca5/pyobjc_framework_coreml-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b5be7889ad99da1aca040238fd99af9ee87ea8a6628f24d33e2e4890b88dd139", size = 11414, upload-time = "2025-06-14T20:48:09.267Z" }, - { url = "https://files.pythonhosted.org/packages/3e/9e/a1b6d30b4f91c7cc4780e745e1e73a322bd3524a773f66f5eac0b1600d85/pyobjc_framework_coreml-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c768b03d72488b964d753392e9c587684961d8237b69cca848b3a5a00aea79c9", size = 11436, upload-time = "2025-06-14T20:48:10.048Z" }, + { url = "https://files.pythonhosted.org/packages/34/0f/f55369da4a33cfe1db38a3512aac4487602783d3a1d572d2c8c4ccce6abc/pyobjc_framework_coreml-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16dafcfb123f022e62f47a590a7eccf7d0cb5957a77fd5f062b5ee751cb5a423", size = 11331, upload-time = "2025-11-14T09:45:50.445Z" }, + { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356, upload-time = "2025-11-14T09:45:52.271Z" }, ] [[package]] name = "pyobjc-framework-coremotion" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/95/e469dc7100ea6b9c29a074a4f713d78b32a78d7ec5498c25c83a56744fc2/pyobjc_framework_coremotion-11.1.tar.gz", hash = "sha256:5884a568521c0836fac39d46683a4dea3d259a23837920897042ffb922d9ac3e", size = 67050, upload-time = "2025-06-14T20:57:12.705Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/eb/abef7d405670cf9c844befc2330a46ee59f6ff7bac6f199bf249561a2ca6/pyobjc_framework_coremotion-12.1.tar.gz", hash = "sha256:8e1b094d34084cc8cf07bedc0630b4ee7f32b0215011f79c9e3cd09d205a27c7", size = 33851, upload-time = "2025-11-14T10:14:05.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/3f/137c983dbccbdbc4a07fb2453e494af938078969bcde9252fbbad0ba939d/pyobjc_framework_coremotion-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:501248a726816e05552d1c1f7e2be2c7305cda792c46905d9aee7079dfad2eea", size = 10353, upload-time = "2025-06-14T20:48:15.365Z" }, - { url = "https://files.pythonhosted.org/packages/e9/17/ffa3cf9fde9df31f3d6ecb38507c61c6d8d81276d0a9116979cafd5a0ab7/pyobjc_framework_coremotion-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c3b33228a170bf8495508a8923451ec600435c7bff93d7614f19c913baeafd1", size = 10368, upload-time = "2025-06-14T20:48:16.066Z" }, + { url = "https://files.pythonhosted.org/packages/77/fd/0d24796779e4d8187abbce5d06cfd7614496d57a68081c5ff1e978b398f9/pyobjc_framework_coremotion-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed8cb67927985d97b1dd23ab6a4a1b716fc7c409c35349816108781efdcbb5b6", size = 10382, upload-time = "2025-11-14T09:46:03.438Z" }, + { url = "https://files.pythonhosted.org/packages/bc/75/89fa4aab818aeca21ac0a60b7ceb89a9e685df0ddd3828d36a6f84a0cff0/pyobjc_framework_coremotion-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a77908ab83c422030f913a2a761d196359ab47f6d1e7c76f21de2c6c05ea2f5f", size = 10406, upload-time = "2025-11-14T09:46:05.076Z" }, ] [[package]] name = "pyobjc-framework-coreservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-fsevents", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/a9/141d18019a25776f507992f9e7ffc051ca5a734848d8ea8d848f7c938efc/pyobjc_framework_coreservices-11.1.tar.gz", hash = "sha256:cf8eb5e272c60a96d025313eca26ff2487dcd02c47034cc9db39f6852d077873", size = 1245086, upload-time = "2025-06-14T20:57:13.914Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/b3/52338a3ff41713f7d7bccaf63bef4ba4a8f2ce0c7eaff39a3629d022a79a/pyobjc_framework_coreservices-12.1.tar.gz", hash = "sha256:fc6a9f18fc6da64c166fe95f2defeb7ac8a9836b3b03bb6a891d36035260dbaa", size = 366150, upload-time = "2025-11-14T10:14:28.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/35/a984b9aace173e92b3509f82afe5e0f8ecddf5cf43bf0c01c803f60a19ce/pyobjc_framework_coreservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7260e09a0550d57756ad655f3d3815f21fc3f0386aed014be4b46194c346941", size = 30243, upload-time = "2025-06-14T20:48:21.563Z" }, - { url = "https://files.pythonhosted.org/packages/fa/0f/52827197a1fa1dabefd77803920eaf340f25e0c81944844ab329d511cade/pyobjc_framework_coreservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6bd313ec326efd715b4b10c3ebcc9f054e3ee3178be407b97ea225cd871351d2", size = 30252, upload-time = "2025-06-14T20:48:22.657Z" }, + { url = "https://files.pythonhosted.org/packages/55/56/c905deb5ab6f7f758faac3f2cbc6f62fde89f8364837b626801bba0975c3/pyobjc_framework_coreservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b6ef07bcf99e941395491f1efcf46e99e5fb83eb6bfa12ae5371135d83f731e1", size = 30196, upload-time = "2025-11-14T09:46:19.356Z" }, + { url = "https://files.pythonhosted.org/packages/61/6c/33984caaf497fc5a6f86350d7ca4fac8abeb2bc33203edc96955a21e8c05/pyobjc_framework_coreservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8751dc2edcb7cfa248bf8a274c4d6493e8d53ef28a843827a4fc9a0a8b04b8be", size = 30206, upload-time = "2025-11-14T09:46:22.732Z" }, ] [[package]] name = "pyobjc-framework-corespotlight" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/c7/b67ebfb63b7ccbfda780d583056d1fd4b610ba3839c8ebe3435b86122c61/pyobjc_framework_corespotlight-11.1.tar.gz", hash = "sha256:4dd363c8d3ff7619659b63dd31400f135b03e32435b5d151459ecdacea14e0f2", size = 87161, upload-time = "2025-06-14T20:57:14.934Z" } +sdist = { url = "https://files.pythonhosted.org/packages/99/d0/88ca73b0cf23847af463334989dd8f98e44f801b811e7e1d8a5627ec20b4/pyobjc_framework_corespotlight-12.1.tar.gz", hash = "sha256:57add47380cd0bbb9793f50a4a4b435a90d4ebd2a33698e058cb353ddfb0d068", size = 38002, upload-time = "2025-11-14T10:14:31.948Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/d4/87a87384bbb2e27864d527eb00973a056bae72603e6c581711231f2479fc/pyobjc_framework_corespotlight-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3c571289ce9107f1ade92ad036633f81355f22f70e8ba82d7335f1757381b89", size = 9954, upload-time = "2025-06-14T20:48:28.065Z" }, - { url = "https://files.pythonhosted.org/packages/b9/f8/06b7edfeabe5b3874485b6e5bbe4a39d9f2e1f44348faa7cb320fbc6f21a/pyobjc_framework_corespotlight-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7cedd3792fe1fe2a8dc65a8ff1f70baf12415a5dc9dc4d88f987059567d7e694", size = 9977, upload-time = "2025-06-14T20:48:28.757Z" }, + { url = "https://files.pythonhosted.org/packages/d4/37/1e7bacb9307a8df52234923e054b7303783e7a48a4637d44ce390b015921/pyobjc_framework_corespotlight-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:404a1e362fe19f0dff477edc1665d8ad90aada928246802da777399f7c06b22e", size = 9976, upload-time = "2025-11-14T09:46:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/f6/3b/d3031eddff8029859de6d92b1f741625b1c233748889141a6a5a89b96f0e/pyobjc_framework_corespotlight-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bfcea64ab3250e2886d202b8731be3817b5ac0c8c9f43e77d0d5a0b6602e71a7", size = 9996, upload-time = "2025-11-14T09:46:47.157Z" }, ] [[package]] name = "pyobjc-framework-coretext" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/65/e9/d3231c4f87d07b8525401fd6ad3c56607c9e512c5490f0a7a6abb13acab6/pyobjc_framework_coretext-11.1.tar.gz", hash = "sha256:a29bbd5d85c77f46a8ee81d381b847244c88a3a5a96ac22f509027ceceaffaf6", size = 274702, upload-time = "2025-06-14T20:57:16.059Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4c/59/d6cc5470157cfd328b2d1ee2c1b6f846a5205307fce17291b57236d9f46e/pyobjc_framework_coretext-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b4f4d2d2a6331fa64465247358d7aafce98e4fb654b99301a490627a073d021e", size = 30072, upload-time = "2025-06-14T20:48:34.248Z" }, - { url = "https://files.pythonhosted.org/packages/32/67/9cc5189c366e67dc3e5b5976fac73cc6405841095f795d3fa0d5fc43d76a/pyobjc_framework_coretext-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1597bf7234270ee1b9963bf112e9061050d5fb8e1384b3f50c11bde2fe2b1570", size = 30175, upload-time = "2025-06-14T20:48:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/f0/81/7b8efc41e743adfa2d74b92dec263c91bcebfb188d2a8f5eea1886a195ff/pyobjc_framework_coretext-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f6742ba5b0bb7629c345e99eff928fbfd9e9d3d667421ac1a2a43bdb7ba9833", size = 29990, upload-time = "2025-11-14T09:47:01.206Z" }, + { url = "https://files.pythonhosted.org/packages/cd/0f/ddf45bf0e3ba4fbdc7772de4728fd97ffc34a0b5a15e1ab1115b202fe4ae/pyobjc_framework_coretext-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d246fa654bdbf43bae3969887d58f0b336c29b795ad55a54eb76397d0e62b93c", size = 30108, upload-time = "2025-11-14T09:47:04.228Z" }, ] [[package]] name = "pyobjc-framework-corewlan" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/d8/03aff3c75485fc999e260946ef1e9adf17640a6e08d7bf603d31cfcf73fc/pyobjc_framework_corewlan-11.1.tar.gz", hash = "sha256:4a8afea75393cc0a6fe696e136233aa0ed54266f35a47b55a3583f4cb078e6ce", size = 65792, upload-time = "2025-06-14T20:57:16.931Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/71/739a5d023566b506b3fd3d2412983faa95a8c16226c0dcd0f67a9294a342/pyobjc_framework_corewlan-12.1.tar.gz", hash = "sha256:a9d82ec71ef61f37e1d611caf51a4203f3dbd8caf827e98128a1afaa0fd2feb5", size = 32417, upload-time = "2025-11-14T10:14:41.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/ba/e73152fc1beee1bf75489d4a6f89ebd9783340e50ca1948cde029d7b0411/pyobjc_framework_corewlan-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e12f127b37a7ab8f349167332633392f2d6d29b87c9b98137a289d0fc1e07b5b", size = 9993, upload-time = "2025-06-14T20:48:41.081Z" }, - { url = "https://files.pythonhosted.org/packages/09/8a/74feabaad1225eb2c44d043924ed8caea31683e6760cd9b918b8d965efea/pyobjc_framework_corewlan-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7bd0775d2466ad500aad4747d8a889993db3a14240239f30ef53c087745e9c8e", size = 10016, upload-time = "2025-06-14T20:48:41.792Z" }, + { url = "https://files.pythonhosted.org/packages/f5/74/4d8a52b930a276f6f9b4f3b1e07cd518cb6d923cb512e39c935e3adb0b86/pyobjc_framework_corewlan-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e3f2614eb37dfd6860d6a0683877c2f3b909758ef78b68e5f6b7ea9c858cc51", size = 9931, upload-time = "2025-11-14T09:47:20.849Z" }, + { url = "https://files.pythonhosted.org/packages/4e/31/3e9cf2c0ac3c979062958eae7a275b602515c9c76fd30680e1ee0fea82ae/pyobjc_framework_corewlan-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cba04c0550fc777767cd3a5471e4ed837406ab182d7d5c273bc5ce6ea237bfe", size = 9958, upload-time = "2025-11-14T09:47:22.474Z" }, ] [[package]] name = "pyobjc-framework-cryptotokenkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/92/7fab6fcc6bb659d6946cfb2f670058180bcc4ca1626878b0f7c95107abf0/pyobjc_framework_cryptotokenkit-11.1.tar.gz", hash = "sha256:5f82f44d9ab466c715a7c8ad4d5ec47c68aacd78bd67b5466a7b8215a2265328", size = 59223, upload-time = "2025-06-14T20:57:17.658Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/7c/d03ff4f74054578577296f33bc669fce16c7827eb1a553bb372b5aab30ca/pyobjc_framework_cryptotokenkit-12.1.tar.gz", hash = "sha256:c95116b4b7a41bf5b54aff823a4ef6f4d9da4d0441996d6d2c115026a42d82f5", size = 32716, upload-time = "2025-11-14T10:14:45.024Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/b6/783495dc440277a330930bac7b560cf54d5e1838fc30fdc3162722db8a62/pyobjc_framework_cryptotokenkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2b76fb928bc398091141dc52b26e02511065afd0b6de5533fa0e71ab13c51589", size = 12515, upload-time = "2025-06-14T20:48:47.346Z" }, - { url = "https://files.pythonhosted.org/packages/76/f1/4cb9c90a55ec13301d60ac1c4d774c37b4ebc6db6331d3853021c933fcc8/pyobjc_framework_cryptotokenkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6384cb1d86fc586e2da934a5a37900825bd789e3a5df97517691de9af354af0c", size = 12543, upload-time = "2025-06-14T20:48:48.079Z" }, + { url = "https://files.pythonhosted.org/packages/2c/90/1623b60d6189db08f642777374fd32287b06932c51dfeb1e9ed5bbf67f35/pyobjc_framework_cryptotokenkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d84b75569054fa0886e3e341c00d7179d5fe287e6d1509630dd698ee60ec5af1", size = 12598, upload-time = "2025-11-14T09:47:33.798Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c7/aecba253cf21303b2c9f3ce03fc0e987523609d7839ea8e0a688ae816c96/pyobjc_framework_cryptotokenkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef51a86c1d0125fabdfad0b3efa51098fb03660d8dad2787d82e8b71c9f189de", size = 12633, upload-time = "2025-11-14T09:47:35.707Z" }, ] [[package]] name = "pyobjc-framework-datadetection" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/4d/65c61d8878b44689e28d5729be9edbb73e20b1b0500d1095172cfd24aea6/pyobjc_framework_datadetection-11.1.tar.gz", hash = "sha256:cbe0080b51e09b2f91eaf2a9babec3dcf2883d7966bc0abd8393ef7abfcfc5db", size = 13485, upload-time = "2025-06-14T20:57:18.829Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/97/9b03832695ec4d3008e6150ddfdc581b0fda559d9709a98b62815581259a/pyobjc_framework_datadetection-12.1.tar.gz", hash = "sha256:95539e46d3bc970ce890aa4a97515db10b2690597c5dd362996794572e5d5de0", size = 12323, upload-time = "2025-11-14T10:14:46.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/c4/ef2136e4e0cc69b02479295822aa33c8e26995b265c8a1184867b65a0a06/pyobjc_framework_datadetection-11.1-py2.py3-none-any.whl", hash = "sha256:5afd3dde7bba3324befb7a3133c9aeaa5088efd72dccc0804267a74799f4a12f", size = 3482, upload-time = "2025-06-14T20:48:54.301Z" }, + { url = "https://files.pythonhosted.org/packages/70/1c/5d2f941501e84da8fef8ef3fd378b5c083f063f083f97dd3e8a07f0404b3/pyobjc_framework_datadetection-12.1-py2.py3-none-any.whl", hash = "sha256:4dc8e1d386d655b44b2681a4a2341fb2fc9addbf3dda14cb1553cd22be6a5387", size = 3497, upload-time = "2025-11-14T09:47:45.826Z" }, ] [[package]] name = "pyobjc-framework-devicecheck" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f3/f2/b1d263f8231f815a9eeff15809f4b7428dacdc0a6aa267db5ed907445066/pyobjc_framework_devicecheck-11.1.tar.gz", hash = "sha256:8b05973eb2673571144d81346336e749a21cec90bd7fcaade76ffd3b147a0741", size = 13954, upload-time = "2025-06-14T20:57:19.782Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/af/c676107c40d51f55d0a42043865d7246db821d01241b518ea1d3b3ef1394/pyobjc_framework_devicecheck-12.1.tar.gz", hash = "sha256:567e85fc1f567b3fe64ac1cdc323d989509331f64ee54fbcbde2001aec5adbdb", size = 12885, upload-time = "2025-11-14T10:14:48.804Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/72/17698a0d68b1067b20b32b4afd74bcafb53a7c73ae8fc608addc7b9e7a37/pyobjc_framework_devicecheck-11.1-py2.py3-none-any.whl", hash = "sha256:8edb36329cdd5d55e2c2c57c379cb5ba1f500f74a08fe8d2612b1a69b7a26435", size = 3668, upload-time = "2025-06-14T20:48:55.098Z" }, + { url = "https://files.pythonhosted.org/packages/c5/d8/1f1b13fa4775b6474c9ad0f4b823953eaeb6c11bd6f03fa8479429b36577/pyobjc_framework_devicecheck-12.1-py2.py3-none-any.whl", hash = "sha256:ffd58148bdef4a1ee8548b243861b7d97a686e73808ca0efac5bef3c430e4a15", size = 3684, upload-time = "2025-11-14T09:47:47.25Z" }, ] [[package]] name = "pyobjc-framework-devicediscoveryextension" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9a/b8/102863bfa2f1e414c88bb9f51151a9a58b99c268a841b59d46e0dcc5fe6d/pyobjc_framework_devicediscoveryextension-11.1.tar.gz", hash = "sha256:ae160ea40f25d3ee5e7ce80ac9c1b315f94d0a4c7ccb86920396f71c6bf799a0", size = 14298, upload-time = "2025-06-14T20:57:20.738Z" } +sdist = { url = "https://files.pythonhosted.org/packages/91/b0/e6e2ed6a7f4b689746818000a003ff7ab9c10945df66398ae8d323ae9579/pyobjc_framework_devicediscoveryextension-12.1.tar.gz", hash = "sha256:60e12445fad97ff1f83472255c943685a8f3a9d95b3126d887cfe769b7261044", size = 14718, upload-time = "2025-11-14T10:14:50.723Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/67/89/fce0c0c89746f399d13e08b40fc12e29a2495f4dcebd30893336d047af18/pyobjc_framework_devicediscoveryextension-11.1-py2.py3-none-any.whl", hash = "sha256:96e5b13c718bd0e6c80fbd4e14b8073cffc88b3ab9bb1bbb4dab7893a62e4f11", size = 4249, upload-time = "2025-06-14T20:48:55.895Z" }, + { url = "https://files.pythonhosted.org/packages/7e/0c/005fe8db1e19135f493a3de8c8d38031e1ad2d626de4ef89f282acf4aff7/pyobjc_framework_devicediscoveryextension-12.1-py2.py3-none-any.whl", hash = "sha256:d6d6b606d27d4d88efc0bed4727c375e749149b360290c3ad2afc52337739a1b", size = 4321, upload-time = "2025-11-14T09:47:48.78Z" }, ] [[package]] name = "pyobjc-framework-dictionaryservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d6/13/c46f6db61133fee15e3471f33a679da2af10d63fa2b4369e0cd476988721/pyobjc_framework_dictionaryservices-11.1.tar.gz", hash = "sha256:39c24452d0ddd037afeb73a1742614c94535f15b1c024a8a6cc7ff081e1d22e7", size = 10578, upload-time = "2025-06-14T20:57:21.392Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/c0/daf03cdaf6d4e04e0cf164db358378c07facd21e4e3f8622505d72573e2c/pyobjc_framework_dictionaryservices-12.1.tar.gz", hash = "sha256:354158f3c55d66681fa903c7b3cb05a435b717fa78d0cef44d258d61156454a7", size = 10573, upload-time = "2025-11-14T10:14:53.961Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/86/4e757b4064a0feb8d60456672560adad0bb5df530ba6621fe65d175dbd90/pyobjc_framework_dictionaryservices-11.1-py2.py3-none-any.whl", hash = "sha256:92f4871066653f18e2394ac93b0a2ab50588d60020f6b3bd93e97b67cd511326", size = 3913, upload-time = "2025-06-14T20:48:56.806Z" }, + { url = "https://files.pythonhosted.org/packages/e7/13/ab308e934146cfd54691ddad87e572cd1edb6659d795903c4c75904e2d7d/pyobjc_framework_dictionaryservices-12.1-py2.py3-none-any.whl", hash = "sha256:578854eec17fa473ac17ab30050a7bbb2ab69f17c5c49b673695254c3e88ad4b", size = 3930, upload-time = "2025-11-14T09:47:50.782Z" }, ] [[package]] name = "pyobjc-framework-discrecording" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a5/b2/d8d1a28643c2ab681b517647bacb68496c98886336ffbd274f0b2ad28cdc/pyobjc_framework_discrecording-11.1.tar.gz", hash = "sha256:37585458e363b20bb28acdb5cc265dfca934d8a07b7baed2584953c11c927a87", size = 123004, upload-time = "2025-06-14T20:57:22.01Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/87/8bd4544793bfcdf507174abddd02b1f077b48fab0004b3db9a63142ce7e9/pyobjc_framework_discrecording-12.1.tar.gz", hash = "sha256:6defc8ea97fb33b4d43870c673710c04c3dc48be30cdf78ba28191a922094990", size = 55607, upload-time = "2025-11-14T10:14:58.276Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/8c/0ff85cc34218e54236eb866e71c35e3308a661f50aea090d400e9121d9c4/pyobjc_framework_discrecording-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc8a7820fc193c2bfcd843c31de945dc45e77e5413089eabbc72be16a4f52e53", size = 14558, upload-time = "2025-06-14T20:48:58.495Z" }, - { url = "https://files.pythonhosted.org/packages/5e/17/032fa44bb66b6a20c432f3311072f88478b42dcf39b21ebb6c3bbdf2954f/pyobjc_framework_discrecording-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e29bc8c3741ae52fae092f892de856dbab2363e71537a8ae6fd026ecb88e2252", size = 14581, upload-time = "2025-06-14T20:48:59.228Z" }, + { url = "https://files.pythonhosted.org/packages/0e/ce/89df4d53a0a5e3a590d6e735eca4f0ba4d1ccc0e0acfbc14164026a3c502/pyobjc_framework_discrecording-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7d815f28f781e20de0bf278aaa10b0de7e5ea1189aa17676c0bf5b99e9e0d52", size = 14540, upload-time = "2025-11-14T09:47:55.442Z" }, + { url = "https://files.pythonhosted.org/packages/c8/70/14a5aa348a5eba16e8773bb56698575cf114aa55aa303037b7000fc53959/pyobjc_framework_discrecording-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:865f1551e58459da6073360afc8f2cc452472c676ba83dcaa9b0c44e7775e4b5", size = 14566, upload-time = "2025-11-14T09:47:57.503Z" }, ] [[package]] name = "pyobjc-framework-discrecordingui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-discrecording", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/53/d71717f00332b8fc3d8a5c7234fdc270adadfeb5ca9318a55986f5c29c44/pyobjc_framework_discrecordingui-11.1.tar.gz", hash = "sha256:a9f10e2e7ee19582c77f0755ae11a64e3d61c652cbd8a5bf52756f599be24797", size = 19370, upload-time = "2025-06-14T20:57:22.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/30/63/8667f5bb1ecb556add04e86b278cb358dc1f2f03862705cae6f09097464c/pyobjc_framework_discrecordingui-12.1.tar.gz", hash = "sha256:6793d4a1a7f3219d063f39d87f1d4ebbbb3347e35d09194a193cfe16cba718a8", size = 16450, upload-time = "2025-11-14T10:15:00.254Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/a6/505af43f7a17e0ca3d45e099900764e8758e0ca65341e894b74ade513556/pyobjc_framework_discrecordingui-11.1-py2.py3-none-any.whl", hash = "sha256:33233b87d7b85ce277a51d27acca0f5b38485cf1d1dc8e28a065910047766ee2", size = 4721, upload-time = "2025-06-14T20:49:03.737Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4e/76016130c27b98943c5758a05beab3ba1bc9349ee881e1dfc509ea954233/pyobjc_framework_discrecordingui-12.1-py2.py3-none-any.whl", hash = "sha256:6544ef99cad3dee95716c83cb207088768b6ecd3de178f7e1b17df5997689dfd", size = 4702, upload-time = "2025-11-14T09:48:08.01Z" }, ] [[package]] name = "pyobjc-framework-diskarbitration" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/da/2a/68fa0c99e04ec1ec24b0b7d6f5b7ec735d5e8a73277c5c0671438a69a403/pyobjc_framework_diskarbitration-11.1.tar.gz", hash = "sha256:a933efc6624779a393fafe0313e43378bcae2b85d6d15cff95ac30048c1ef490", size = 19866, upload-time = "2025-06-14T20:57:23.435Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/42/f75fcabec1a0033e4c5235cc8225773f610321d565b63bf982c10c6bbee4/pyobjc_framework_diskarbitration-12.1.tar.gz", hash = "sha256:6703bc5a09b38a720c9ffca356b58f7e99fa76fc988c9ec4d87112344e63dfc2", size = 17121, upload-time = "2025-11-14T10:15:02.223Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/72/9534ca88effbf2897e07b722920b3f10890dbc780c6fff1ab4893ec1af10/pyobjc_framework_diskarbitration-11.1-py2.py3-none-any.whl", hash = "sha256:6a8e551e54df481a9081abba6fd680f6633babe5c7735f649731b22896bb6f08", size = 4849, upload-time = "2025-06-14T20:49:04.513Z" }, + { url = "https://files.pythonhosted.org/packages/48/65/c1f54c47af17cb6b923eab85e95f22396c52f90ee8f5b387acffad9a99ea/pyobjc_framework_diskarbitration-12.1-py2.py3-none-any.whl", hash = "sha256:54caf3079fe4ae5ac14466a9b68923ee260a1a88a8290686b4a2015ba14c2db6", size = 4877, upload-time = "2025-11-14T09:48:09.945Z" }, ] [[package]] name = "pyobjc-framework-dvdplayback" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b8/76/77046325b1957f0cbcdf4f96667496d042ed4758f3413f1d21df5b085939/pyobjc_framework_dvdplayback-11.1.tar.gz", hash = "sha256:b44c36a62c8479e649133216e22941859407cca5796b5f778815ef9340a838f4", size = 64558, upload-time = "2025-06-14T20:57:24.118Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/dd/7859a58e8dd336c77f83feb76d502e9623c394ea09322e29a03f5bc04d32/pyobjc_framework_dvdplayback-12.1.tar.gz", hash = "sha256:279345d4b5fb2c47dd8e5c2fd289e644b6648b74f5c25079805eeb61bfc4a9cd", size = 32332, upload-time = "2025-11-14T10:15:05.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/0c/f0fefa171b6938010d87194e26e63eea5c990c33d2d7828de66802f57c36/pyobjc_framework_dvdplayback-11.1-py2.py3-none-any.whl", hash = "sha256:6094e4651ea29540ac817294b27e1596b9d1883d30e78fb5f9619daf94ed30cb", size = 8221, upload-time = "2025-06-14T20:49:05.297Z" }, + { url = "https://files.pythonhosted.org/packages/29/7d/22c07c28fab1f15f0d364806e39a6ca63c737c645fe7e98e157878b5998c/pyobjc_framework_dvdplayback-12.1-py2.py3-none-any.whl", hash = "sha256:af911cc222272a55b46a1a02a46a355279aecfd8132231d8d1b279e252b8ad4c", size = 8243, upload-time = "2025-11-14T09:48:11.824Z" }, ] [[package]] name = "pyobjc-framework-eventkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/c4/cbba8f2dce13b9be37ecfd423ba2b92aa3f209dbb58ede6c4ce3b242feee/pyobjc_framework_eventkit-11.1.tar.gz", hash = "sha256:5643150f584243681099c5e9435efa833a913e93fe9ca81f62007e287349b561", size = 75177, upload-time = "2025-06-14T20:57:24.81Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/42/4ec97e641fdcf30896fe76476181622954cb017117b1429f634d24816711/pyobjc_framework_eventkit-12.1.tar.gz", hash = "sha256:7c1882be2f444b1d0f71e9a0cd1e9c04ad98e0261292ab741fc9de0b8bbbbae9", size = 28538, upload-time = "2025-11-14T10:15:07.878Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/0a/384b9ff4c6380cac310cb7b92c145896c20a690192dbfc07b38909787ded/pyobjc_framework_eventkit-11.1-py2.py3-none-any.whl", hash = "sha256:c303207610d9c742f4090799f60103cede466002f3c89cf66011c8bf1987750b", size = 6805, upload-time = "2025-06-14T20:49:06.147Z" }, + { url = "https://files.pythonhosted.org/packages/f4/35/142f43227627d6324993869d354b9e57eb1e88c4e229e2271592254daf25/pyobjc_framework_eventkit-12.1-py2.py3-none-any.whl", hash = "sha256:3d2d36d5bd9e0a13887a6ac7cdd36675985ebe2a9cb3cdf8cec0725670c92c60", size = 6820, upload-time = "2025-11-14T09:48:14.035Z" }, ] [[package]] name = "pyobjc-framework-exceptionhandling" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/0d/c72a885b40d28a99b586447f9ea6f400589f13d554fcd6f13a2c841bb6d2/pyobjc_framework_exceptionhandling-11.1.tar.gz", hash = "sha256:e010f56bf60ab4e9e3225954ebb53e9d7135d37097043ac6dd2a3f35770d4efa", size = 17890, upload-time = "2025-06-14T20:57:25.521Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/17/5c9d4164f7ccf6b9100be0ad597a7857395dd58ea492cba4f0e9c0b77049/pyobjc_framework_exceptionhandling-12.1.tar.gz", hash = "sha256:7f0719eeea6695197fce0e7042342daa462683dc466eb6a442aad897032ab00d", size = 16694, upload-time = "2025-11-14T10:15:10.173Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/81/dde9c73bf307b62c2d605fc818d3e49f857f39e0841766093dbc9ea47b08/pyobjc_framework_exceptionhandling-11.1-py2.py3-none-any.whl", hash = "sha256:31e6538160dfd7526ac0549bc0fce5d039932aea84c36abbe7b49c79ffc62437", size = 7078, upload-time = "2025-06-14T20:49:07.713Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ad/8e05acf3635f20ea7d878be30d58a484c8b901a8552c501feb7893472f86/pyobjc_framework_exceptionhandling-12.1-py2.py3-none-any.whl", hash = "sha256:2f1eae14cf0162e53a0888d9ffe63f047501fe583a23cdc9c966e89f48cf4713", size = 7113, upload-time = "2025-11-14T09:48:15.685Z" }, ] [[package]] name = "pyobjc-framework-executionpolicy" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/cf/54431846508c5d5bb114a415ebb96187da5847105918169e42f4ca3b00e6/pyobjc_framework_executionpolicy-11.1.tar.gz", hash = "sha256:3280ad2f4c5eaf45901f310cee0c52db940c0c63e959ad082efb8df41055d986", size = 13496, upload-time = "2025-06-14T20:57:26.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/11/db765e76e7b00e1521d7bb3a61ae49b59e7573ac108da174720e5d96b61b/pyobjc_framework_executionpolicy-12.1.tar.gz", hash = "sha256:682866589365cd01d3a724d8a2781794b5cba1e152411a58825ea52d7b972941", size = 12594, upload-time = "2025-11-14T10:15:12.077Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/d2/cb192d55786d0f881f2fb60d45b61862a1fcade945f6a7a549ed62f47e61/pyobjc_framework_executionpolicy-11.1-py2.py3-none-any.whl", hash = "sha256:7d4141e572cb916e73bb34bb74f6f976a8aa0a396a0bffd1cf66e5505f7c76c8", size = 3719, upload-time = "2025-06-14T20:49:08.521Z" }, + { url = "https://files.pythonhosted.org/packages/51/2c/f10352398f10f244401ab8f53cabd127dc3f5dbbfc8de83464661d716671/pyobjc_framework_executionpolicy-12.1-py2.py3-none-any.whl", hash = "sha256:c3a9eca3bd143cf202787dd5e3f40d954c198f18a5e0b8b3e2fcdd317bf33a52", size = 3739, upload-time = "2025-11-14T09:48:17.35Z" }, ] [[package]] name = "pyobjc-framework-extensionkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/7d/89adf16c7de4246477714dce8fcffae4242778aecd0c5f0ad9904725f42c/pyobjc_framework_extensionkit-11.1.tar.gz", hash = "sha256:c114a96f13f586dbbab8b6219a92fa4829896a645c8cd15652a6215bc8ff5409", size = 19766, upload-time = "2025-06-14T20:57:27.106Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/d4/e9b1f74d29ad9dea3d60468d59b80e14ed3a19f9f7a25afcbc10d29c8a1e/pyobjc_framework_extensionkit-12.1.tar.gz", hash = "sha256:773987353e8aba04223dbba3149253db944abfb090c35318b3a770195b75da6d", size = 18694, upload-time = "2025-11-14T10:15:14.104Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/90/e6607b779756e039c0a4725a37cf70dc5b13c54a8cedbcf01ec1608866b1/pyobjc_framework_extensionkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fd9f9758f95bcff2bf26fe475f679dfff9457d7130f114089e88fd5009675a", size = 7894, upload-time = "2025-06-14T20:49:10.593Z" }, - { url = "https://files.pythonhosted.org/packages/90/2a/93105b5452d2ff680a47e38a3ec6f2a37164babd95e0ab976c07984366de/pyobjc_framework_extensionkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d505a64617c9db4373eb386664d62a82ba9ffc909bffad42cb4da8ca8e244c66", size = 7914, upload-time = "2025-06-14T20:49:11.842Z" }, + { url = "https://files.pythonhosted.org/packages/4f/02/3d1df48f838dc9d64f03bedd29f0fdac6c31945251c9818c3e34083eb731/pyobjc_framework_extensionkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9139c064e1c7f21455411848eb39f092af6085a26cad322aa26309260e7929d9", size = 7919, upload-time = "2025-11-14T09:48:22.14Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/8064dad6114a489e5439cc20d9fb0dd64cfc406d875b4a3c87015b3f6266/pyobjc_framework_extensionkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e01d705c7ac6d080ae34a81db6d9b81875eabefa63fd6eafbfa30f676dd780b", size = 7932, upload-time = "2025-11-14T09:48:23.653Z" }, ] [[package]] name = "pyobjc-framework-externalaccessory" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/a3/519242e6822e1ddc9e64e21f717529079dbc28a353474420da8315d0a8b1/pyobjc_framework_externalaccessory-11.1.tar.gz", hash = "sha256:50887e948b78a1d94646422c243ac2a9e40761675e38b9184487870a31e83371", size = 23123, upload-time = "2025-06-14T20:57:27.845Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/35/86c097ae2fdf912c61c1276e80f3e090a3fc898c75effdf51d86afec456b/pyobjc_framework_externalaccessory-12.1.tar.gz", hash = "sha256:079f770a115d517a6ab87db1b8a62ca6cdf6c35ae65f45eecc21b491e78776c0", size = 20958, upload-time = "2025-11-14T10:15:16.419Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/54/d532badd43eba2db3fed2501b8e47a57cab233de2090ee97f4cff723e706/pyobjc_framework_externalaccessory-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a2b22f72b83721d841e5a3128df29fc41d785597357c6bbce84555a2b51a1e9d", size = 8887, upload-time = "2025-06-14T20:49:17.703Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1b/e2def12aca9162b0fe0bbf0790d35595d46b2ef12603749c42af9234ffca/pyobjc_framework_externalaccessory-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:00caf75b959db5d14118d78c04085e2148255498839cdee735a0b9f6ef86b6a2", size = 8903, upload-time = "2025-06-14T20:49:18.393Z" }, + { url = "https://files.pythonhosted.org/packages/18/01/2a83b63e82ce58722277a00521c3aeec58ac5abb3086704554e47f8becf3/pyobjc_framework_externalaccessory-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32208e05c9448c8f41b3efdd35dbea4a8b119af190f7a2db0d580be8a5cf962e", size = 8911, upload-time = "2025-11-14T09:48:35.349Z" }, + { url = "https://files.pythonhosted.org/packages/ec/52/984034396089766b6e5ff3be0f93470e721c420fa9d1076398557532234f/pyobjc_framework_externalaccessory-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dedbf7a09375ac19668156c1417bd7829565b164a246b714e225b9cbb6a351ad", size = 8932, upload-time = "2025-11-14T09:48:37.393Z" }, ] [[package]] name = "pyobjc-framework-fileprovider" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/80/3ebba2c1e5e3aeae989fe038c259a93e7e7e18fd56666ece514d000d38ea/pyobjc_framework_fileprovider-11.1.tar.gz", hash = "sha256:748ca1c75f84afdf5419346a24bf8eec44dca071986f31f00071dc191b3e9ca8", size = 91696, upload-time = "2025-06-14T20:57:28.546Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cc/9a/724b1fae5709f8860f06a6a2a46de568f9bb8bdb2e2aae45b4e010368f51/pyobjc_framework_fileprovider-12.1.tar.gz", hash = "sha256:45034e0d00ae153c991aa01cb1fd41874650a30093e77ba73401dcce5534c8ad", size = 43071, upload-time = "2025-11-14T10:15:19.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/e4/c7b985d1199e3697ab5c3247027fe488b9d81b1fb597c34350942dc5838c/pyobjc_framework_fileprovider-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:888d6fb3fd625889ce0e409320c3379330473a386095cb4eda2b4caf0198ff66", size = 19546, upload-time = "2025-06-14T20:49:23.436Z" }, - { url = "https://files.pythonhosted.org/packages/49/b2/859d733b0110e56511478ba837fd8a7ba43aa8f8c7e5231b9e3f0258bfbf/pyobjc_framework_fileprovider-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ce6092dfe74c78c0b2abc03bfc18a0f5d8ddc624fc6a1d8dfef26d7796653072", size = 19622, upload-time = "2025-06-14T20:49:24.162Z" }, + { url = "https://files.pythonhosted.org/packages/1d/37/2f56167e9f43d3b25a5ed073305ca0cfbfc66bedec7aae9e1f2c9c337265/pyobjc_framework_fileprovider-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d527c417f06d27c4908e51d4e6ccce0adcd80c054f19e709626e55c511dc963", size = 20970, upload-time = "2025-11-14T09:48:50.557Z" }, + { url = "https://files.pythonhosted.org/packages/2c/f5/56f0751a2988b2caca89d6800c8f29246828d1a7498bb676ef1ab28000b7/pyobjc_framework_fileprovider-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:89b140ea8369512ddf4164b007cbe35b4d97d1dcb8affa12a7264c0ab8d56e45", size = 21003, upload-time = "2025-11-14T09:48:53.128Z" }, ] [[package]] name = "pyobjc-framework-fileproviderui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-fileprovider", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/ed/0f5af06869661822c4a70aacd674da5d1e6b6661240e2883bbc7142aa525/pyobjc_framework_fileproviderui-11.1.tar.gz", hash = "sha256:162a23e67f59e1bb247e84dda88d513d7944d815144901a46be6fe051b6c7970", size = 13163, upload-time = "2025-06-14T20:57:29.568Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/00/234f9b93f75255845df81d9d5ea20cb83ecb5c0a4e59147168b622dd0b9d/pyobjc_framework_fileproviderui-12.1.tar.gz", hash = "sha256:15296429d9db0955abc3242b2920b7a810509a85118dbc185f3ac8234e5a6165", size = 12437, upload-time = "2025-11-14T10:15:22.044Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/01/667e139a0610494e181fccdce519f644166f3d8955b330674deba5876f0d/pyobjc_framework_fileproviderui-11.1-py2.py3-none-any.whl", hash = "sha256:f2765f114c2f4356aa41fb45c621fa8f0a4fae0b6d3c6b1a274366f5fe7fe829", size = 3696, upload-time = "2025-06-14T20:49:29.404Z" }, + { url = "https://files.pythonhosted.org/packages/e8/65/cc4397511bd0af91993d6302a2aed205296a9ad626146eefdfc8a9624219/pyobjc_framework_fileproviderui-12.1-py2.py3-none-any.whl", hash = "sha256:521a914055089e28631018bd78df4c4f7416e98b4150f861d4a5bc97d5b1ffe4", size = 3715, upload-time = "2025-11-14T09:49:04.213Z" }, ] [[package]] name = "pyobjc-framework-findersync" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/82/c6b670494ac0c4cf14cf2db0dfbe0df71925d20595404939383ddbcc56d3/pyobjc_framework_findersync-11.1.tar.gz", hash = "sha256:692364937f418f0e4e4abd395a09a7d4a0cdd55fd4e0184de85ee59642defb6e", size = 15045, upload-time = "2025-06-14T20:57:30.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/63/c8da472e0910238a905bc48620e005a1b8ae7921701408ca13e5fb0bfb4b/pyobjc_framework_findersync-12.1.tar.gz", hash = "sha256:c513104cef0013c233bf8655b527df665ce6f840c8bc0b3781e996933d4dcfa6", size = 13507, upload-time = "2025-11-14T10:15:24.161Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/10/748ff914c5b7fbae5fa2436cd44b11caeabb8d2f6f6f1b9ab581f70f32af/pyobjc_framework_findersync-11.1-py2.py3-none-any.whl", hash = "sha256:c72b0fd8b746b99cfa498da36c5bb333121b2080ad73fa8cbea05cd47db1fa82", size = 4873, upload-time = "2025-06-14T20:49:30.194Z" }, + { url = "https://files.pythonhosted.org/packages/6a/9f/ec7f393e3e2fd11cbdf930d884a0ba81078bdb61920b3cba4f264de8b446/pyobjc_framework_findersync-12.1-py2.py3-none-any.whl", hash = "sha256:e07abeca52c486cf14927f617afc27afa7a3828b99fab3ad02355105fb29203e", size = 4889, upload-time = "2025-11-14T09:49:05.763Z" }, ] [[package]] name = "pyobjc-framework-fsevents" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/83/ec0b9ba355dbc34f27ed748df9df4eb6dbfdd9bbd614b0f193752f36f419/pyobjc_framework_fsevents-11.1.tar.gz", hash = "sha256:d29157d04124503c4dfa9dcbbdc8c34d3bab134d3db3a48d96d93f26bd94c14d", size = 29587, upload-time = "2025-06-14T20:57:30.796Z" } +sdist = { url = "https://files.pythonhosted.org/packages/43/17/21f45d2bca2efc72b975f2dfeae7a163dbeabb1236c1f188578403fd4f09/pyobjc_framework_fsevents-12.1.tar.gz", hash = "sha256:a22350e2aa789dec59b62da869c1b494a429f8c618854b1383d6473f4c065a02", size = 26487, upload-time = "2025-11-14T10:15:26.796Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/6a/25118832a128db99a53be4c45f473192f72923d9b9690785539cee1a9858/pyobjc_framework_fsevents-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95cc5d839d298b8e95175fb72df8a8e1b08773fd2e0d031efe91eee23e0c8830", size = 13076, upload-time = "2025-06-14T20:49:32.269Z" }, - { url = "https://files.pythonhosted.org/packages/13/c7/378d78e0fd956370f2b120b209117384b5b98925c6d8210a33fd73db4a15/pyobjc_framework_fsevents-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8b51d120b8f12a1ca94e28cf74113bf2bfd4c5aee7035b452e895518f4df7630", size = 13147, upload-time = "2025-06-14T20:49:33.022Z" }, + { url = "https://files.pythonhosted.org/packages/a4/3f/a7fe5656b205ee3a9fd828e342157b91e643ee3e5c0d50b12bd4c737f683/pyobjc_framework_fsevents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:459cc0aac9850c489d238ba778379d09f073bbc3626248855e78c4bc4d97fe46", size = 13059, upload-time = "2025-11-14T09:49:09.814Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/2c5eeea390c0b053e2d73b223af3ec87a3e99a8106e8d3ee79942edb0822/pyobjc_framework_fsevents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2949358513fd7bc622fb362b5c4af4fc24fc6307320070ca410885e5e13d975", size = 13141, upload-time = "2025-11-14T09:49:11.947Z" }, ] [[package]] name = "pyobjc-framework-fskit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/46/47/d1f04c6115fa78936399a389cc5e0e443f8341c9a6c1c0df7f6fdbe51286/pyobjc_framework_fskit-11.1.tar.gz", hash = "sha256:9ded1eab19b4183cb04381e554bbbe679c1213fd58599d6fc6e135e93b51136f", size = 42091, upload-time = "2025-06-14T20:57:31.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/55/d00246d6e6d9756e129e1d94bc131c99eece2daa84b2696f6442b8a22177/pyobjc_framework_fskit-12.1.tar.gz", hash = "sha256:ec54e941cdb0b7d800616c06ca76a93685bd7119b8aa6eb4e7a3ee27658fc7ba", size = 42372, upload-time = "2025-11-14T10:15:30.411Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/16/76/1152bd8121ef2c9a0ccdf10624d647095ce944d34f654f001b458edef668/pyobjc_framework_fskit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59a939ac8442d648f73a3da75923aa3637ac4693850d995f1914260c8f4f7947", size = 19922, upload-time = "2025-06-14T20:49:38.424Z" }, - { url = "https://files.pythonhosted.org/packages/59/8f/db8f03688db77bfa4b78e89af1d89e910c5e877e94d58bdb3e93cc302e5d/pyobjc_framework_fskit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1e50b8f949f1386fada73b408463c87eb81ef7fd0b3482bacf0c206a73723013", size = 19948, upload-time = "2025-06-14T20:49:39.18Z" }, + { url = "https://files.pythonhosted.org/packages/e7/1a/5a0b6b8dc18b9dbcb7d1ef7bebdd93f12560097dafa6d7c4b3c15649afba/pyobjc_framework_fskit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95b9135eea81eeed319dcca32c9db04b38688301586180b86c4585fef6b0e9cd", size = 20228, upload-time = "2025-11-14T09:49:25.324Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a9/0c47469fe80fa14bc698bb0a5b772b44283cc3aca0f67e7f70ab45e09b24/pyobjc_framework_fskit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:50972897adea86508cfee33ec4c23aa91dede97e9da1640ea2fe74702b065be1", size = 20250, upload-time = "2025-11-14T09:49:28.065Z" }, ] [[package]] name = "pyobjc-framework-gamecenter" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1b/8e/b594fd1dc32a59462fc68ad502be2bd87c70e6359b4e879a99bcc4beaf5b/pyobjc_framework_gamecenter-11.1.tar.gz", hash = "sha256:a1c4ed54e11a6e4efba6f2a21ace92bcf186e3fe5c74a385b31f6b1a515ec20c", size = 31981, upload-time = "2025-06-14T20:57:32.192Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d2/f8/b5fd86f6b722d4259228922e125b50e0a6975120a1c4d957e990fb84e42c/pyobjc_framework_gamecenter-12.1.tar.gz", hash = "sha256:de4118f14c9cf93eb0316d49da410faded3609ce9cd63425e9ef878cebb7ea72", size = 31473, upload-time = "2025-11-14T10:15:33.38Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/21/a8/8d9c2d0ff9f42a0951063a9eaff1e39c46c15e89ce4e5e274114340ca976/pyobjc_framework_gamecenter-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:81abe136292ea157acb6c54871915fe6d386146a9386179ded0b974ac435045c", size = 18601, upload-time = "2025-06-14T20:49:44.946Z" }, - { url = "https://files.pythonhosted.org/packages/99/52/0e56f21a6660a4f43882ec641b9e19b7ea92dc7474cec48cda1c9bed9c49/pyobjc_framework_gamecenter-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:779cdf8f52348be7f64d16e3ea37fd621d5ee933c032db3a22a8ccad46d69c59", size = 18634, upload-time = "2025-06-14T20:49:45.737Z" }, + { url = "https://files.pythonhosted.org/packages/ca/17/6491f9e96664e05ec00af7942a6c2f69217771522c9d1180524273cac7cb/pyobjc_framework_gamecenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:30943512f2aa8cb129f8e1abf951bf06922ca20b868e918b26c19202f4ee5cc4", size = 18824, upload-time = "2025-11-14T09:49:42.543Z" }, + { url = "https://files.pythonhosted.org/packages/16/ee/b496cc4248c5b901e159d6d9a437da9b86a3105fc3999a66744ba2b2c884/pyobjc_framework_gamecenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8d6d10b868be7c00c2d5a0944cc79315945735dcf17eaa3fec1a7986d26be9b", size = 18868, upload-time = "2025-11-14T09:49:44.767Z" }, ] [[package]] name = "pyobjc-framework-gamecontroller" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/70/4c/1dd62103092a182f2ab8904c8a8e3922d2b0a80a7adab0c20e5fd0207d75/pyobjc_framework_gamecontroller-11.1.tar.gz", hash = "sha256:4d5346faf90e1ebe5602c0c480afbf528a35a7a1ad05f9b49991fdd2a97f105b", size = 115783, upload-time = "2025-06-14T20:57:32.879Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/14/353bb1fe448cd833839fd199ab26426c0248088753e63c22fe19dc07530f/pyobjc_framework_gamecontroller-12.1.tar.gz", hash = "sha256:64ed3cc4844b67f1faeb540c7cc8d512c84f70b3a4bafdb33d4663a2b2a2b1d8", size = 54554, upload-time = "2025-11-14T10:15:37.591Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/8e/09e73e03e9f57e77df58cf77f6069d3455a3c388a890ff815e86d036ae39/pyobjc_framework_gamecontroller-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:782779f080508acf869187c0cbd3a48c55ee059d3a14fe89ccd6349537923214", size = 20825, upload-time = "2025-06-14T20:49:51.565Z" }, - { url = "https://files.pythonhosted.org/packages/40/e3/e35bccb0284046ef716db4897b70d061b8b16c91fb2c434b1e782322ef56/pyobjc_framework_gamecontroller-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d2cbc0c6c7d9c63e6b5b0b124d0c2bad01bb4b136f3cbc305f27d31f8aab6083", size = 20850, upload-time = "2025-06-14T20:49:52.401Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/1d8bd4845a46cb5a5c1f860d85394e64729b2447bbe149bb33301bc99056/pyobjc_framework_gamecontroller-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2633c2703fb30ce068b2f5ce145edbd10fd574d2670b5cdee77a9a126f154fec", size = 20913, upload-time = "2025-11-14T09:49:58.863Z" }, + { url = "https://files.pythonhosted.org/packages/06/28/9f03d0ef7c78340441f78b19fb2d2c952af04a240da5ed30c7cf2d0d0f4e/pyobjc_framework_gamecontroller-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:878aa6590c1510e91bfc8710d6c880e7a8f3656a7b7b6f4f3af487a6f677ccd5", size = 20949, upload-time = "2025-11-14T09:50:01.608Z" }, ] [[package]] name = "pyobjc-framework-gamekit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/7b/ba141ec0f85ca816f493d1f6fe68c72d01092e5562e53c470a0111d9c34b/pyobjc_framework_gamekit-11.1.tar.gz", hash = "sha256:9b8db075da8866c4ef039a165af227bc29393dc11a617a40671bf6b3975ae269", size = 165397, upload-time = "2025-06-14T20:57:33.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/7b/d625c0937557f7e2e64200fdbeb867d2f6f86b2f148b8d6bfe085e32d872/pyobjc_framework_gamekit-12.1.tar.gz", hash = "sha256:014d032c3484093f1409f8f631ba8a0fd2ff7a3ae23fd9d14235340889854c16", size = 63833, upload-time = "2025-11-14T10:15:42.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/2a/f206682b9ff76983bae14a479a9c8a9098e58efc3db31f88211d6ad4fd42/pyobjc_framework_gamekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e07c25eab051905c6bd46f368d8b341ef8603dce588ff6dbd82d609dd4fbf71", size = 21932, upload-time = "2025-06-14T20:49:58.154Z" }, - { url = "https://files.pythonhosted.org/packages/1f/23/094e4fe38f2de029365604f0b7dffde7b0edfc57c3d388294c20ed663de2/pyobjc_framework_gamekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f945c7cfe53c4a349a03a1272f2736cc5cf88fe9e7a7a407abb03899635d860c", size = 21952, upload-time = "2025-06-14T20:49:58.933Z" }, + { url = "https://files.pythonhosted.org/packages/06/47/d3b78cf57bc2d62dc1408aaad226b776d167832063bbaa0c7cc7a9a6fa12/pyobjc_framework_gamekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb263e90a6af3d7294bc1b1ea5907f8e33bb77d62fb806696f8df7e14806ccad", size = 22463, upload-time = "2025-11-14T09:50:16.444Z" }, + { url = "https://files.pythonhosted.org/packages/c4/05/1c49e1030dc9f2812fa8049442158be76c32f271075f4571f94e4389ea86/pyobjc_framework_gamekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eee796d5781157f2c5684f7ef4c2a7ace9d9b408a26a9e7e92e8adf5a3f63d7", size = 22493, upload-time = "2025-11-14T09:50:19.129Z" }, ] [[package]] name = "pyobjc-framework-gameplaykit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-spritekit", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/07/f38b1d83eac10ea4f75c605ffc4850585740db89b90842d311e586ee36cd/pyobjc_framework_gameplaykit-11.1.tar.gz", hash = "sha256:9ae2bee69b0cc1afa0e210b4663c7cdbb3cc94be1374808df06f98f992e83639", size = 73399, upload-time = "2025-06-14T20:57:34.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/11/c310bbc2526f95cce662cc1f1359bb11e2458eab0689737b4850d0f6acb7/pyobjc_framework_gameplaykit-12.1.tar.gz", hash = "sha256:935ebd806d802888969357946245d35a304c530c86f1ffe584e2cf21f0a608a8", size = 41511, upload-time = "2025-11-14T10:15:46.529Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/29/df66f53f887990878b2b00b1336e451a15e360a384be74559acf47854bc3/pyobjc_framework_gameplaykit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ac9f50941988c30175149af481a49b2026c56a9a497c6dbf2974ffb50ffe0af8", size = 13065, upload-time = "2025-06-14T20:50:05.243Z" }, - { url = "https://files.pythonhosted.org/packages/e7/f5/65bdbefb9de7cbc2edf0b1f76286736536e31c216cfac1a5f84ea15f0fc1/pyobjc_framework_gameplaykit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e4f34db8177b8b4d89fd22a2a882a6c9f6e50cb438ea2fbbf96845481bcd80d", size = 13091, upload-time = "2025-06-14T20:50:05.962Z" }, + { url = "https://files.pythonhosted.org/packages/3b/84/7a4a2c358770f5ffdb6bdabb74dcefdfa248b17c250a7c0f9d16d3b8d987/pyobjc_framework_gameplaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2fb27f9f48c3279937e938a0456a5231b5c89e53e3199b9d54009a0bbd1228a", size = 13125, upload-time = "2025-11-14T09:50:34.384Z" }, + { url = "https://files.pythonhosted.org/packages/35/1f/e5fe404f92ec0f9c8c37b00d6cb3ba96ee396c7f91b0a41a39b64bfc2743/pyobjc_framework_gameplaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:309b0d7479f702830c9be92dbe5855ac2557a9d23f05f063caf9d9fdb85ff5f0", size = 13150, upload-time = "2025-11-14T09:50:36.884Z" }, +] + +[[package]] +name = "pyobjc-framework-gamesave" +version = "12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/1f/8d05585c844535e75dbc242dd6bdfecfc613d074dcb700362d1c908fb403/pyobjc_framework_gamesave-12.1.tar.gz", hash = "sha256:eb731c97aa644e78a87838ed56d0e5bdbaae125bdc8854a7772394877312cc2e", size = 12654, upload-time = "2025-11-14T10:15:48.344Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/ec/93d48cb048a1b35cea559cc9261b07f0d410078b3af029121302faa410d0/pyobjc_framework_gamesave-12.1-py2.py3-none-any.whl", hash = "sha256:432e69f8404be9290d42c89caba241a3156ed52013947978ac54f0f032a14ffd", size = 3689, upload-time = "2025-11-14T09:50:47.263Z" }, ] [[package]] name = "pyobjc-framework-healthkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/66/fa76f7c8e36e4c10677d42d91a8e220c135c610a06b759571db1abe26a32/pyobjc_framework_healthkit-11.1.tar.gz", hash = "sha256:20f59bd9e1ffafe5893b4eff5867fdfd20bd46c3d03bc4009219d82fc6815f76", size = 202009, upload-time = "2025-06-14T20:57:35.285Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/67/436630d00ba1028ea33cc9df2fc28e081481433e5075600f2ea1ff00f45e/pyobjc_framework_healthkit-12.1.tar.gz", hash = "sha256:29c5e5de54b41080b7a4b0207698ac6f600dcb9149becc9c6b3a69957e200e5c", size = 91802, upload-time = "2025-11-14T10:15:54.661Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/aa/c337d27dd98ffcbba2b1200126fcf624d1ccbeb7a4ed9205d48bfe2c1ca8/pyobjc_framework_healthkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:34bce3d144c461af7e577fcf6bbb7739d0537bf42f081960122923a7ef2e06c0", size = 20301, upload-time = "2025-06-14T20:50:12.158Z" }, - { url = "https://files.pythonhosted.org/packages/c7/08/12fca070ad2dc0b9c311df209b9b6d275ee192cb5ccbc94616d9ddd80d88/pyobjc_framework_healthkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab4350f9fe65909107dd7992b367a6c8aac7dc31ed3d5b52eeb2310367d0eb0b", size = 20311, upload-time = "2025-06-14T20:50:13.271Z" }, + { url = "https://files.pythonhosted.org/packages/1e/37/b23d3c04ee37cbb94ff92caedc3669cd259be0344fcf6bdf1ff75ff0a078/pyobjc_framework_healthkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67bce41f8f63c11000394c6ce1dc694655d9ff0458771340d2c782f9eafcc6e", size = 20785, upload-time = "2025-11-14T09:50:52.152Z" }, + { url = "https://files.pythonhosted.org/packages/65/87/bb1c438de51c4fa733a99ce4d3301e585f14d7efd94031a97707c0be2b46/pyobjc_framework_healthkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:15b6fc958ff5de42888b18dffdec839cb36d2dd8b82076ed2f21a51db5271109", size = 20799, upload-time = "2025-11-14T09:50:54.531Z" }, ] [[package]] name = "pyobjc-framework-imagecapturecore" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/3b/f4edbc58a8c7394393f8d00d0e764f655545e743ee4e33917f27b8c68e7b/pyobjc_framework_imagecapturecore-11.1.tar.gz", hash = "sha256:a610ceb6726e385b132a1481a68ce85ccf56f94667b6d6e1c45a2cfab806a624", size = 100398, upload-time = "2025-06-14T20:57:36.503Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/a1/39347381fc7d3cd5ab942d86af347b25c73f0ddf6f5227d8b4d8f5328016/pyobjc_framework_imagecapturecore-12.1.tar.gz", hash = "sha256:c4776c59f4db57727389d17e1ffd9c567b854b8db52198b3ccc11281711074e5", size = 46397, upload-time = "2025-11-14T10:15:58.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/50/72/465741d33757ef2162a1c9e12d6c8a41b5490949a92431c42a139c132303/pyobjc_framework_imagecapturecore-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ede4c15da909a4d819c732a5554b8282a7b56a1b73d82aef908124147921945a", size = 15999, upload-time = "2025-06-14T20:50:18.742Z" }, - { url = "https://files.pythonhosted.org/packages/61/62/54ed61e7cd3213549c8e98ca87a6b21afbb428d2c41948ae48ea019bf973/pyobjc_framework_imagecapturecore-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed296c23d3d8d1d9af96a6486d09fb8d294cc318e4a2152e6f134151c76065f8", size = 16021, upload-time = "2025-06-14T20:50:19.836Z" }, + { url = "https://files.pythonhosted.org/packages/b4/6b/b34d5c9041e90b8a82d87025a1854b60a8ec2d88d9ef9e715f3a40109ed5/pyobjc_framework_imagecapturecore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:64d1eb677fe5b658a6b6ed734b7120998ea738ca038ec18c4f9c776e90bd9402", size = 15983, upload-time = "2025-11-14T09:51:09.978Z" }, + { url = "https://files.pythonhosted.org/packages/50/13/632957b284dec3743d73fb30dbdf03793b3cf1b4c62e61e6484d870f3879/pyobjc_framework_imagecapturecore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2777e17ff71fb5a327a897e48c5c7b5a561723a80f990d26e6ed5a1b8748816", size = 16012, upload-time = "2025-11-14T09:51:12.058Z" }, ] [[package]] name = "pyobjc-framework-inputmethodkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/32/6a90bba682a31960ba1fc2d3b263e9be26043c4fb7aed273c13647c8b7d9/pyobjc_framework_inputmethodkit-11.1.tar.gz", hash = "sha256:7037579524041dcee71a649293c2660f9359800455a15e6a2f74a17b46d78496", size = 27203, upload-time = "2025-06-14T20:57:37.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/b8/d33dd8b7306029bbbd80525bf833fc547e6a223c494bf69a534487283a28/pyobjc_framework_inputmethodkit-12.1.tar.gz", hash = "sha256:f63b6fe2fa7f1412eae63baea1e120e7865e3b68ccfb7d8b0a4aadb309f2b278", size = 23054, upload-time = "2025-11-14T10:16:01.464Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/23/a4226040eec8ed930c81073776064f30d627db03e9db5b24720aad8fd14d/pyobjc_framework_inputmethodkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9b0e47c3bc7f1e628c906436c1735041ed2e9aa7cba3f70084b6311c63c508be", size = 9480, upload-time = "2025-06-14T20:50:25.184Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0d/8a570072096fe339702e4ae9d98e59ee7c6c14124d4437c9a8c4482dda6d/pyobjc_framework_inputmethodkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd0c591a9d26967018a781fa4638470147ef2a9af3ab4a28612f147573eeefba", size = 9489, upload-time = "2025-06-14T20:50:25.875Z" }, + { url = "https://files.pythonhosted.org/packages/a7/04/1315f84dba5704a4976ea0185f877f0f33f28781473a817010cee209a8f0/pyobjc_framework_inputmethodkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4e02f49816799a31d558866492048d69e8086178770b76f4c511295610e02ab", size = 9502, upload-time = "2025-11-14T09:51:24.708Z" }, + { url = "https://files.pythonhosted.org/packages/01/c2/59bea66405784b25f5d4e821467ba534a0b92dfc98e07257c971e2a8ed73/pyobjc_framework_inputmethodkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b7d813d46a060572fc0c14ef832e4fe538ebf64e5cab80ee955191792ce0110", size = 9506, upload-time = "2025-11-14T09:51:26.924Z" }, ] [[package]] name = "pyobjc-framework-installerplugins" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/89/9a881e466476ca21f3ff3e8e87ccfba1aaad9b88f7eea4be6d3f05b07107/pyobjc_framework_installerplugins-11.1.tar.gz", hash = "sha256:363e59c7e05553d881f0facd41884f17b489ff443d7856e33dd0312064c746d9", size = 27451, upload-time = "2025-06-14T20:57:37.915Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/60/ca4ab04eafa388a97a521db7d60a812e2f81a3c21c2372587872e6b074f9/pyobjc_framework_installerplugins-12.1.tar.gz", hash = "sha256:1329a193bd2e92a2320a981a9a421a9b99749bade3e5914358923e94fe995795", size = 25277, upload-time = "2025-11-14T10:16:04.379Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/01/45c3d159d671c5f488a40f70aa6791b8483a3ed32b461800990bb5ab4bb3/pyobjc_framework_installerplugins-11.1-py2.py3-none-any.whl", hash = "sha256:f92b06c9595f3c800b7aabf1c1a235bfb4b2de3f5406d5f604d8e2ddd0aecb4e", size = 4798, upload-time = "2025-06-14T20:50:30.799Z" }, + { url = "https://files.pythonhosted.org/packages/99/1f/31dca45db3342882a628aa1b27707a283d4dc7ef558fddd2533175a0661a/pyobjc_framework_installerplugins-12.1-py2.py3-none-any.whl", hash = "sha256:d2201c81b05bdbe0abf0af25db58dc230802573463bea322f8b2863e37b511d5", size = 4813, upload-time = "2025-11-14T09:51:37.836Z" }, ] [[package]] name = "pyobjc-framework-instantmessage" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/b9/5cec4dd0053b5f63c01211a60a286c47464d9f3e0c81bd682e6542dbff00/pyobjc_framework_instantmessage-11.1.tar.gz", hash = "sha256:c222aa61eb009704b333f6e63df01a0e690136e7e495907e5396882779bf9525", size = 33774, upload-time = "2025-06-14T20:57:38.553Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d4/67/66754e0d26320ba24a33608ca94d3f38e60ee6b2d2e094cb6269b346fdd4/pyobjc_framework_instantmessage-12.1.tar.gz", hash = "sha256:f453118d5693dc3c94554791bd2aaafe32a8b03b0e3d8ec3934b44b7fdd1f7e7", size = 31217, upload-time = "2025-11-14T10:16:07.693Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/34/acd618e90036822aaf01080d64558ba93e33e15ed91beb7d1d2aab290138/pyobjc_framework_instantmessage-11.1-py2.py3-none-any.whl", hash = "sha256:a70b716e279135eec5666af031f536c0f32dec57cfeae55cc9ff8457f10d4f3d", size = 5419, upload-time = "2025-06-14T20:50:31.993Z" }, + { url = "https://files.pythonhosted.org/packages/c1/38/6ae95b5c87d887c075bd5f4f7cca3d21dafd0a77cfdde870e87ca17579eb/pyobjc_framework_instantmessage-12.1-py2.py3-none-any.whl", hash = "sha256:cd91d38e8f356afd726b6ea8c235699316ea90edfd3472965c251efbf4150bc9", size = 5436, upload-time = "2025-11-14T09:51:39.557Z" }, ] [[package]] name = "pyobjc-framework-intents" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/af/d7f260d06b79acca8028e373c2fe30bf0be014388ba612f538f40597d929/pyobjc_framework_intents-11.1.tar.gz", hash = "sha256:13185f206493f45d6bd2d4903c2136b1c4f8b9aa37628309ace6ff4a906b4695", size = 448459, upload-time = "2025-06-14T20:57:39.589Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/a1/3bab6139e94b97eca098e1562f5d6840e3ff10ea1f7fd704a17111a97d5b/pyobjc_framework_intents-12.1.tar.gz", hash = "sha256:bd688c3ab34a18412f56e459e9dae29e1f4152d3c2048fcacdef5fc49dfb9765", size = 132262, upload-time = "2025-11-14T10:16:16.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/1d/10fdbf3b8dd6451465ae147143ba3159397a50ff81aed1eb86c153e987b5/pyobjc_framework_intents-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da2f11ee64c75cfbebb1c2be52a20b3618f32b6c47863809ff64c61e8a1dffb9", size = 32227, upload-time = "2025-06-14T20:50:34.303Z" }, - { url = "https://files.pythonhosted.org/packages/8a/37/e6fa5737da42fb1265041bd3bd4f2be96f09294018fabf07139dd9dbc7b9/pyobjc_framework_intents-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a663e2de1b7ae7b547de013f89773963f8180023e36f2cebfe8060395dc34c33", size = 32253, upload-time = "2025-06-14T20:50:35.028Z" }, + { url = "https://files.pythonhosted.org/packages/d0/25/648db47b9c3879fa50c65ab7cc5fbe0dd400cc97141ac2658ef2e196c0b6/pyobjc_framework_intents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc68dc49f1f8d9f8d2ffbc0f57ad25caac35312ddc444899707461e596024fec", size = 32134, upload-time = "2025-11-14T09:51:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/7a/90/e9489492ae90b4c1ffd02c1221c0432b8768d475787e7887f79032c2487a/pyobjc_framework_intents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ea9f3e79bf4baf6c7b0fd2d2797184ed51a372bf7f32974b4424f9bd067ef50", size = 32156, upload-time = "2025-11-14T09:51:49.438Z" }, ] [[package]] name = "pyobjc-framework-intentsui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-intents", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/46/20aae4a71efb514b096f36273a6129b48b01535bf501e5719d4a97fcb3a5/pyobjc_framework_intentsui-11.1.tar.gz", hash = "sha256:c8182155af4dce369c18d6e6ed9c25bbd8110c161ed5f1b4fb77cf5cdb99d135", size = 21305, upload-time = "2025-06-14T20:57:40.477Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/cf/f0e385b9cfbf153d68efe8d19e5ae672b59acbbfc1f9b58faaefc5ec8c9e/pyobjc_framework_intentsui-12.1.tar.gz", hash = "sha256:16bdf4b7b91c0d1ec9d5513a1182861f1b5b7af95d4f4218ff7cf03032d57f99", size = 19784, upload-time = "2025-11-14T10:16:18.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/31/e3/db74fc161bb85bc442dfddf50321924613b67cf49288e2a8b335bf6d546a/pyobjc_framework_intentsui-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:252f7833fabb036cd56d59b445922b25cda1561b54c0989702618a5561d8e748", size = 8936, upload-time = "2025-06-14T20:50:40.522Z" }, - { url = "https://files.pythonhosted.org/packages/43/7c/77fbd2a6f85eb905fbf27ba7540eaf2a026771ed5100fb1c01143cf47e9b/pyobjc_framework_intentsui-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:99a3ae40eb2a6ef1125955dd513c8acc88ce7d8d90130a8cdeaec8336e6fbec5", size = 8965, upload-time = "2025-06-14T20:50:41.281Z" }, + { url = "https://files.pythonhosted.org/packages/84/cc/7678f901cbf5bca8ccace568ae85ee7baddcd93d78754ac43a3bb5e5a7ac/pyobjc_framework_intentsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a877555e313d74ac3b10f7b4e738647eea9f744c00a227d1238935ac3f9d7968", size = 8961, upload-time = "2025-11-14T09:52:05.595Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/06812542a9028f5b2dcce56f52f25633c08b638faacd43bad862aad1b41d/pyobjc_framework_intentsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cb894fcc4c9ea613a424dcf6fb48142d51174559b82cfdafac8cb47555c842cf", size = 8983, upload-time = "2025-11-14T09:52:07.667Z" }, ] [[package]] name = "pyobjc-framework-iobluetooth" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/93/e0/74b7b10c567b66c5f38b45ab240336325a4c889f43072d90f2b90aaeb7c0/pyobjc_framework_iobluetooth-11.1.tar.gz", hash = "sha256:094fd4be60cd1371b17cb4b33a3894e0d88a11b36683912be0540a7d51de76f1", size = 300992, upload-time = "2025-06-14T20:57:41.256Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/aa/ca3944bbdfead4201b4ae6b51510942c5a7d8e5e2dc3139a071c74061fdf/pyobjc_framework_iobluetooth-12.1.tar.gz", hash = "sha256:8a434118812f4c01dfc64339d41fe8229516864a59d2803e9094ee4cbe2b7edd", size = 155241, upload-time = "2025-11-14T10:16:28.896Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/13/31a514e48bd54880aadb1aac3a042fca5f499780628c18f4f54f06d4ece2/pyobjc_framework_iobluetooth-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7d8858cf2e4b2ef5e8bf29b76c06d4f2e6a2264c325146d07dfab94c46633329", size = 40378, upload-time = "2025-06-14T20:50:46.298Z" }, - { url = "https://files.pythonhosted.org/packages/da/94/eef57045762e955795a4e3312674045c52f8c506133acf9efe1b3370b93f/pyobjc_framework_iobluetooth-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:883781e7223cb0c63fab029d640721ded747f2e2b067645bc8b695ef02a4a4dd", size = 40406, upload-time = "2025-06-14T20:50:47.101Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ab/ad6b36f574c3d52b5e935b1d57ab0f14f4e4cd328cc922d2b6ba6428c12d/pyobjc_framework_iobluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:77959f2ecf379aa41eb0848fdb25da7c322f9f4a82429965c87c4bc147137953", size = 40415, upload-time = "2025-11-14T09:52:22.069Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b6/933b56afb5e84c3c35c074c9e30d7b701c6038989d4867867bdaa7ab618b/pyobjc_framework_iobluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:111a6e54be9e9dcf77fa2bf84fdac09fae339aa33087d8647ea7ffbd34765d4c", size = 40439, upload-time = "2025-11-14T09:52:26.071Z" }, ] [[package]] name = "pyobjc-framework-iobluetoothui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dd/32/872272faeab6fe471eac6962c75db72ce65c3556e00b4edebdb41aaab7cb/pyobjc_framework_iobluetoothui-11.1.tar.gz", hash = "sha256:060c721f1cd8af4452493e8153b72b572edcd2a7e3b635d79d844f885afee860", size = 22835, upload-time = "2025-06-14T20:57:42.119Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/39/31d9a4e8565a4b1ec0a9ad81480dc0879f3df28799eae3bc22d1dd53705d/pyobjc_framework_iobluetoothui-12.1.tar.gz", hash = "sha256:81f8158bdfb2966a574b6988eb346114d6a4c277300c8c0a978c272018184e6f", size = 16495, upload-time = "2025-11-14T10:16:31.212Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/ed/35efed52ed3fa698480624e49ee5f3d859827aad5ff1c7334150c695e188/pyobjc_framework_iobluetoothui-11.1-py2.py3-none-any.whl", hash = "sha256:3c5a382d81f319a1ab9ab11b7ead04e53b758fdfeb604755d39c3039485eaac6", size = 4026, upload-time = "2025-06-14T20:50:52.018Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c9/69aeda0cdb5d25d30dc4596a1c5b464fc81b5c0c4e28efc54b7e11bde51c/pyobjc_framework_iobluetoothui-12.1-py2.py3-none-any.whl", hash = "sha256:a6d8ab98efa3029130577a57ee96b183c35c39b0f1c53a7534f8838260fab993", size = 4045, upload-time = "2025-11-14T09:52:42.201Z" }, ] [[package]] name = "pyobjc-framework-iosurface" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/ce/38ec17d860d0ee040bb737aad8ca7c7ff46bef6c9cffa47382d67682bb2d/pyobjc_framework_iosurface-11.1.tar.gz", hash = "sha256:a468b3a31e8cd70a2675a3ddc7176ab13aa521c035f11188b7a3af8fff8b148b", size = 20275, upload-time = "2025-06-14T20:57:42.742Z" } +sdist = { url = "https://files.pythonhosted.org/packages/07/61/0f12ad67a72d434e1c84b229ec760b5be71f53671ee9018593961c8bfeb7/pyobjc_framework_iosurface-12.1.tar.gz", hash = "sha256:4b9d0c66431aa296f3ca7c4f84c00dc5fc961194830ad7682fdbbc358fa0db55", size = 17690, upload-time = "2025-11-14T10:16:33.282Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/26/fa912d397b577ee318b20110a3c959e898514a1dce19b4f13f238a31a677/pyobjc_framework_iosurface-11.1-py2.py3-none-any.whl", hash = "sha256:0c36ad56f8ec675dd07616418a2bc29126412b54627655abd21de31bcafe2a79", size = 4948, upload-time = "2025-06-14T20:50:52.801Z" }, + { url = "https://files.pythonhosted.org/packages/88/ad/793d98a7ed9b775dc8cce54144cdab0df1808a1960ee017e46189291a8f3/pyobjc_framework_iosurface-12.1-py2.py3-none-any.whl", hash = "sha256:e784e248397cfebef4655d2c0025766d3eaa4a70474e363d084fc5ce2a4f2a3f", size = 4902, upload-time = "2025-11-14T09:52:43.899Z" }, ] [[package]] name = "pyobjc-framework-ituneslibrary" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/43/aebefed774b434965752f9001685af0b19c02353aa7a12d2918af0948181/pyobjc_framework_ituneslibrary-11.1.tar.gz", hash = "sha256:e2212a9340e4328056ade3c2f9d4305c71f3f6af050204a135f9fa9aa3ba9c5e", size = 47388, upload-time = "2025-06-14T20:57:43.383Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/46/d9bcec88675bf4ee887b9707bd245e2a793e7cb916cf310f286741d54b1f/pyobjc_framework_ituneslibrary-12.1.tar.gz", hash = "sha256:7f3aa76c4d05f6fa6015056b88986cacbda107c3f29520dd35ef0936c7367a6e", size = 23730, upload-time = "2025-11-14T10:16:36.127Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/57/a29150f734b45b7408cc06efb9e2156328ae74624e5c4a7fe95118e13e94/pyobjc_framework_ituneslibrary-11.1-py2.py3-none-any.whl", hash = "sha256:4e87d41f82acb6d98cf70ac3c932a568ceb3c2035383cbf177f54e63de6b815f", size = 5191, upload-time = "2025-06-14T20:50:53.637Z" }, + { url = "https://files.pythonhosted.org/packages/de/92/b598694a1713ee46f45c4bfb1a0425082253cbd2b1caf9f8fd50f292b017/pyobjc_framework_ituneslibrary-12.1-py2.py3-none-any.whl", hash = "sha256:fb678d7c3ff14c81672e09c015e25880dac278aa819971f4d5f75d46465932ef", size = 5205, upload-time = "2025-11-14T09:52:45.733Z" }, ] [[package]] name = "pyobjc-framework-kernelmanagement" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/b6/708f10ac16425834cb5f8b71efdbe39b42c3b1009ac0c1796a42fc98cd36/pyobjc_framework_kernelmanagement-11.1.tar.gz", hash = "sha256:e934d1638cd89e38d6c6c5d4d9901b4295acee2d39cbfe0bd91aae9832961b44", size = 12543, upload-time = "2025-06-14T20:57:44.046Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/7e/ecbac119866e8ac2cce700d7a48a4297946412ac7cbc243a7084a6582fb1/pyobjc_framework_kernelmanagement-12.1.tar.gz", hash = "sha256:488062893ac2074e0c8178667bf864a21f7909c11111de2f6a10d9bc579df59d", size = 11773, upload-time = "2025-11-14T10:16:38.216Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/cf/17ff988ad1a0e55a4be5336c64220aa620ad19bb2f487a1122e9a864b29e/pyobjc_framework_kernelmanagement-11.1-py2.py3-none-any.whl", hash = "sha256:ec74690bd3383a7945c4a038cc4e1553ec5c1d2408b60e2b0003a3564bff7c47", size = 3656, upload-time = "2025-06-14T20:50:54.484Z" }, + { url = "https://files.pythonhosted.org/packages/94/32/04325a20f39d88d6d712437e536961a9e6a4ec19f204f241de6ed54d1d84/pyobjc_framework_kernelmanagement-12.1-py2.py3-none-any.whl", hash = "sha256:926381bfbfbc985c3e6dfcb7004af21bb16ff66ecbc08912b925989a705944ff", size = 3704, upload-time = "2025-11-14T09:52:47.268Z" }, ] [[package]] name = "pyobjc-framework-latentsemanticmapping" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/db/8a/4e54ee2bc77d59d770b287daf73b629e2715a2b3b31264d164398131cbad/pyobjc_framework_latentsemanticmapping-11.1.tar.gz", hash = "sha256:c6c3142301e4d375c24a47dfaeebc2f3d0fc33128a1c0a755794865b9a371145", size = 17444, upload-time = "2025-06-14T20:57:44.643Z" } +sdist = { url = "https://files.pythonhosted.org/packages/88/3c/b621dac54ae8e77ac25ee75dd93e310e2d6e0faaf15b8da13513258d6657/pyobjc_framework_latentsemanticmapping-12.1.tar.gz", hash = "sha256:f0b1fa823313eefecbf1539b4ed4b32461534b7a35826c2cd9f6024411dc9284", size = 15526, upload-time = "2025-11-14T10:16:40.149Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/50/d62815b02968236eb46c33f0fb0f7293a32ef68d2ec50c397140846d4e42/pyobjc_framework_latentsemanticmapping-11.1-py2.py3-none-any.whl", hash = "sha256:57f3b183021759a100d2847a4d8aa314f4033be3d2845038b62e5e823d96e871", size = 5454, upload-time = "2025-06-14T20:50:55.658Z" }, + { url = "https://files.pythonhosted.org/packages/29/8e/74a7eb29b545f294485cd3cf70557b4a35616555fe63021edbb3e0ea4c20/pyobjc_framework_latentsemanticmapping-12.1-py2.py3-none-any.whl", hash = "sha256:7d760213b42bc8b1bc1472e1873c0f78ee80f987225978837b1fecdceddbdbf4", size = 5471, upload-time = "2025-11-14T09:52:48.939Z" }, ] [[package]] name = "pyobjc-framework-launchservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/0a/a76b13109b8ab563fdb2d7182ca79515f132f82ac6e1c52351a6b02896a8/pyobjc_framework_launchservices-11.1.tar.gz", hash = "sha256:80b55368b1e208d6c2c58395cc7bc12a630a2a402e00e4930493e9bace22b7bb", size = 20446, upload-time = "2025-06-14T20:57:45.258Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/d0/24673625922b0ad21546be5cf49e5ec1afaa4553ae92f222adacdc915907/pyobjc_framework_launchservices-12.1.tar.gz", hash = "sha256:4d2d34c9bd6fb7f77566155b539a2c70283d1f0326e1695da234a93ef48352dc", size = 20470, upload-time = "2025-11-14T10:16:42.499Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/30/a4de9021fdef7db0b224cdc1eae75811d889dc1debdfafdabf8be7bd0fb9/pyobjc_framework_launchservices-11.1-py2.py3-none-any.whl", hash = "sha256:8b58f1156651058b2905c87ce48468f4799db86a7edf760e1897fedd057a3908", size = 3889, upload-time = "2025-06-14T20:50:56.484Z" }, + { url = "https://files.pythonhosted.org/packages/08/af/9a0aebaab4c15632dc8fcb3669c68fa541a3278d99541d9c5f966fbc0909/pyobjc_framework_launchservices-12.1-py2.py3-none-any.whl", hash = "sha256:e63e78fceeed4d4dc807f9dabd5cf90407e4f552fab6a0d75a8d0af63094ad3c", size = 3905, upload-time = "2025-11-14T09:52:50.71Z" }, ] [[package]] name = "pyobjc-framework-libdispatch" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/be/89/7830c293ba71feb086cb1551455757f26a7e2abd12f360d375aae32a4d7d/pyobjc_framework_libdispatch-11.1.tar.gz", hash = "sha256:11a704e50a0b7dbfb01552b7d686473ffa63b5254100fdb271a1fe368dd08e87", size = 53942, upload-time = "2025-06-14T20:57:45.903Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/e8/75b6b9b3c88b37723c237e5a7600384ea2d84874548671139db02e76652b/pyobjc_framework_libdispatch-12.1.tar.gz", hash = "sha256:4035535b4fae1b5e976f3e0e38b6e3442ffea1b8aa178d0ca89faa9b8ecdea41", size = 38277, upload-time = "2025-11-14T10:16:46.235Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/cd/1010dee9f932a9686c27ce2e45e91d5b6875f5f18d2daafadea70090e111/pyobjc_framework_libdispatch-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ddca472c2cbc6bb192e05b8b501d528ce49333abe7ef0eef28df3133a8e18b7", size = 20441, upload-time = "2025-06-14T20:50:58.3Z" }, - { url = "https://files.pythonhosted.org/packages/ac/92/ff9ceb14e1604193dcdb50643f2578e1010c68556711cd1a00eb25489c2b/pyobjc_framework_libdispatch-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dc9a7b8c2e8a63789b7cf69563bb7247bde15353208ef1353fff0af61b281684", size = 15627, upload-time = "2025-06-14T20:50:59.055Z" }, + { url = "https://files.pythonhosted.org/packages/1f/75/c4aeab6ce7268373d4ceabbc5c406c4bbf557038649784384910932985f8/pyobjc_framework_libdispatch-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:954cc2d817b71383bd267cc5cd27d83536c5f879539122353ca59f1c945ac706", size = 20463, upload-time = "2025-11-14T09:52:55.703Z" }, + { url = "https://files.pythonhosted.org/packages/83/6f/96e15c7b2f7b51fc53252216cd0bed0c3541bc0f0aeb32756fefd31bed7d/pyobjc_framework_libdispatch-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e9570d7a9a3136f54b0b834683bf3f206acd5df0e421c30f8fd4f8b9b556789", size = 15650, upload-time = "2025-11-14T09:52:59.284Z" }, ] [[package]] name = "pyobjc-framework-libxpc" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6a/c9/7e15e38ac23f5bfb4e82bdf3b7ef88e2f56a8b4ad884009bc2d5267d2e1f/pyobjc_framework_libxpc-11.1.tar.gz", hash = "sha256:8fd7468aa520ff19915f6d793070b84be1498cb87224bee2bad1f01d8375273a", size = 49135, upload-time = "2025-06-14T20:57:46.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/16/e4/364db7dc26f235e3d7eaab2f92057f460b39800bffdec3128f113388ac9f/pyobjc_framework_libxpc-12.1.tar.gz", hash = "sha256:e46363a735f3ecc9a2f91637750623f90ee74f9938a4e7c833e01233174af44d", size = 35186, upload-time = "2025-11-14T10:16:49.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/01/f5fbc7627f838aea5960f3287b75cbda9233f76fc3ba82f088630d5d16cc/pyobjc_framework_libxpc-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4ec8a7df24d85a561fc21d0eb0db89e8cddefeedec71c69bccf17f99804068ed", size = 19466, upload-time = "2025-06-14T20:51:05.138Z" }, - { url = "https://files.pythonhosted.org/packages/be/8f/dfd8e1e1e461f857a1e50138e69b17c0e62a8dcaf7dea791cc158d2bf854/pyobjc_framework_libxpc-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c29b2df8d74ff6f489afa7c39f7c848c5f3d0531a6bbe704571782ee6c820084", size = 19573, upload-time = "2025-06-14T20:51:05.902Z" }, + { url = "https://files.pythonhosted.org/packages/7c/c9/701630d025407497b7af50a795ddb6202c184da7f12b46aa683dae3d3552/pyobjc_framework_libxpc-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8d7201db995e5dcd38775fd103641d8fb69b8577d8e6a405c5562e6c0bb72fd1", size = 19620, upload-time = "2025-11-14T09:53:12.529Z" }, + { url = "https://files.pythonhosted.org/packages/82/7f/fdec72430f90921b154517a6f9bbeefa7bacfb16b91320742eb16a5955c5/pyobjc_framework_libxpc-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ba93e91e9ca79603dd265382e9f80e9bd32309cd09c8ac3e6489fc5b233676c8", size = 19730, upload-time = "2025-11-14T09:53:17.113Z" }, ] [[package]] name = "pyobjc-framework-linkpresentation" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/76/22873be73f12a3a11ae57af13167a1d2379e4e7eef584de137156a00f5ef/pyobjc_framework_linkpresentation-11.1.tar.gz", hash = "sha256:a785f393b01fdaada6d7d6d8de46b7173babba205b13b44f1dc884b3695c2fc9", size = 14987, upload-time = "2025-06-14T20:57:47.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/58/c0c5919d883485ccdb6dccd8ecfe50271d2f6e6ab7c9b624789235ccec5a/pyobjc_framework_linkpresentation-12.1.tar.gz", hash = "sha256:84df6779591bb93217aa8bd82c10e16643441678547d2d73ba895475a02ade94", size = 13330, upload-time = "2025-11-14T10:16:52.169Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/59/23249e76e06e3c1a4f88acac7144999fae5a5a8ce4b90272d08cc0ac38ae/pyobjc_framework_linkpresentation-11.1-py2.py3-none-any.whl", hash = "sha256:018093469d780a45d98f4e159f1ea90771caec456b1599abcc6f3bf3c6873094", size = 3847, upload-time = "2025-06-14T20:51:10.817Z" }, + { url = "https://files.pythonhosted.org/packages/ad/51/226eb45f196f3bf93374713571aae6c8a4760389e1d9435c4a4cc3f38ea4/pyobjc_framework_linkpresentation-12.1-py2.py3-none-any.whl", hash = "sha256:853a84c7b525b77b114a7a8d798aef83f528ed3a6803bda12184fe5af4e79a47", size = 3865, upload-time = "2025-11-14T09:53:28.386Z" }, ] [[package]] name = "pyobjc-framework-localauthentication" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/27/9e3195f3561574140e9b9071a36f7e0ebd18f50ade9261d23b5b9df8fccd/pyobjc_framework_localauthentication-11.1.tar.gz", hash = "sha256:3cd48907c794bd414ac68b8ac595d83c7e1453b63fc2cfc2d2035b690d31eaa1", size = 40700, upload-time = "2025-06-14T20:57:47.931Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/0e/7e5d9a58bb3d5b79a75d925557ef68084171526191b1c0929a887a553d4f/pyobjc_framework_localauthentication-12.1.tar.gz", hash = "sha256:2284f587d8e1206166e4495b33f420c1de486c36c28c4921d09eec858a699d05", size = 29947, upload-time = "2025-11-14T10:16:54.923Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/9a/acc10d45041445db99a121950b0d4f4ff977dbe5e95ec154fe2e1740ff08/pyobjc_framework_localauthentication-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b6d52d07abd2240f7bc02b01ea1c630c280ed3fbc3fabe1e43b7444cfd41788", size = 10707, upload-time = "2025-06-14T20:51:12.436Z" }, - { url = "https://files.pythonhosted.org/packages/91/db/59f118cc2658814c6b501b7360ca4fe6a82fd289ced5897b99787130ceef/pyobjc_framework_localauthentication-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:aa3815f936612d78e51b53beed9115c57ae2fd49500bb52c4030a35856e6569e", size = 10730, upload-time = "2025-06-14T20:51:13.487Z" }, + { url = "https://files.pythonhosted.org/packages/6e/cb/cf9d13943e13dc868a68844448a7714c16f4ee6ecac384d21aaa5ac43796/pyobjc_framework_localauthentication-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d7e1b3f987dc387361517525c2c38550dc44dfb3ba42dec3a9fbf35015831a6", size = 10762, upload-time = "2025-11-14T09:53:32.035Z" }, + { url = "https://files.pythonhosted.org/packages/05/93/91761ad4e5fa1c3ec25819865d1ccfbee033987147087bff4fcce67a4dc4/pyobjc_framework_localauthentication-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3af1acd287d830cc7f912f46cde0dab054952bde0adaf66c8e8524311a68d279", size = 10773, upload-time = "2025-11-14T09:53:34.074Z" }, ] [[package]] name = "pyobjc-framework-localauthenticationembeddedui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-localauthentication", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/7b/08c1e52487b07e9aee4c24a78f7c82a46695fa883113e3eece40f8e32d40/pyobjc_framework_localauthenticationembeddedui-11.1.tar.gz", hash = "sha256:22baf3aae606e5204e194f02bb205f244e27841ea7b4a4431303955475b4fa56", size = 14076, upload-time = "2025-06-14T20:57:48.557Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/20/83ab4180e29b9a4a44d735c7f88909296c6adbe6250e8e00a156aff753e1/pyobjc_framework_localauthenticationembeddedui-12.1.tar.gz", hash = "sha256:a15ec44bf2769c872e86c6b550b6dd4f58d4eda40ad9ff00272a67d279d1d4e9", size = 13611, upload-time = "2025-11-14T10:16:57.145Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/3d/2aaa3a4f0e82f0ac95cc432a6079f6dc20aa18a66c9a87ac6128c70df9ef/pyobjc_framework_localauthenticationembeddedui-11.1-py2.py3-none-any.whl", hash = "sha256:3539a947b102b41ea6e40e7c145f27280d2f36a2a9a1211de32fa675d91585eb", size = 3973, upload-time = "2025-06-14T20:51:18.2Z" }, + { url = "https://files.pythonhosted.org/packages/30/7d/0d46639c7a26b6af928ab4c822cd28b733791e02ac28cc84c3014bcf7dc7/pyobjc_framework_localauthenticationembeddedui-12.1-py2.py3-none-any.whl", hash = "sha256:a7ce7b56346597b9f4768be61938cbc8fc5b1292137225b6c7f631b9cde97cd7", size = 3991, upload-time = "2025-11-14T09:53:42.958Z" }, ] [[package]] name = "pyobjc-framework-mailkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7e/7e/f22d733897e7618bd70a658b0353f5f897c583df04e7c5a2d68b99d43fbb/pyobjc_framework_mailkit-11.1.tar.gz", hash = "sha256:bf97dc44cb09b9eb9d591660dc0a41f077699976144b954caa4b9f0479211fd7", size = 32012, upload-time = "2025-06-14T20:57:49.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/98/3d9028620c1cd32ff4fb031155aba3b5511e980cdd114dd51383be9cb51b/pyobjc_framework_mailkit-12.1.tar.gz", hash = "sha256:d5574b7259baec17096410efcaacf5d45c7bb5f893d4c25cbb7072369799b652", size = 20996, upload-time = "2025-11-14T10:16:59.449Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/23/1897fc071e8e71bc0bef53bcb0d600eb1ed3bd6c4609f7257ddfe151d37a/pyobjc_framework_mailkit-11.1-py2.py3-none-any.whl", hash = "sha256:8e6026462567baba194468e710e83787f29d9e8c98ea0583f7b401ea9515966e", size = 4854, upload-time = "2025-06-14T20:51:18.978Z" }, + { url = "https://files.pythonhosted.org/packages/70/8d/3c968b736a3a8bd9d8e870b39b1c772a013eea1b81b89fc4efad9021a6cb/pyobjc_framework_mailkit-12.1-py2.py3-none-any.whl", hash = "sha256:536ac0c4ea3560364cd159a6512c3c18a744a12e4e0883c07df0f8a2ff21e3fe", size = 4871, upload-time = "2025-11-14T09:53:44.697Z" }, ] [[package]] name = "pyobjc-framework-mapkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -3287,28 +3342,28 @@ dependencies = [ { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/f0/505e074f49c783f2e65ca82174fd2d4348568f3f7281c1b81af816cf83bb/pyobjc_framework_mapkit-11.1.tar.gz", hash = "sha256:f3a5016f266091be313a118a42c0ea4f951c399b5259d93639eb643dacc626f1", size = 165614, upload-time = "2025-06-14T20:57:50.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/bb/2a668203c20e509a648c35e803d79d0c7f7816dacba74eb5ad8acb186790/pyobjc_framework_mapkit-12.1.tar.gz", hash = "sha256:dbc32dc48e821aaa9b4294402c240adbc1c6834e658a07677b7c19b7990533c5", size = 63520, upload-time = "2025-11-14T10:17:04.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/dc/a7e03a9066e6eed9d1707ae45453a5332057950e16de6665402c804ae7af/pyobjc_framework_mapkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:daee6bedc3acc23e62d1e7c3ab97e10425ca57e0c3cc47d2b212254705cc5c44", size = 22481, upload-time = "2025-06-14T20:51:20.694Z" }, - { url = "https://files.pythonhosted.org/packages/30/0a/50aa2fba57499ff657cacb9ef1730006442e4f42d9a822dae46239603ecc/pyobjc_framework_mapkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:91976c6dbc8cbb020e059a0ccdeab8933184712f77164dbad5a5526c1a49599d", size = 22515, upload-time = "2025-06-14T20:51:21.439Z" }, + { url = "https://files.pythonhosted.org/packages/d0/8f/411067e5c5cd23b9fe4c5edfb02ed94417b94eefe56562d36e244edc70ff/pyobjc_framework_mapkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8aa82d4aae81765c05dcd53fd362af615aa04159fc7a1df1d0eac9c252cb7d5", size = 22493, upload-time = "2025-11-14T09:53:50.112Z" }, + { url = "https://files.pythonhosted.org/packages/11/00/a3de41cdf3e6cd7a144e38999fe1ea9777ad19e19d863f2da862e7affe7b/pyobjc_framework_mapkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84ad7766271c114bdc423e4e2ff5433e5fc6771a3338b5f8e4b54d0340775800", size = 22518, upload-time = "2025-11-14T09:53:52.727Z" }, ] [[package]] name = "pyobjc-framework-mediaaccessibility" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/81/60412b423c121de0fa0aa3ef679825e1e2fe8b00fceddec7d72333ef564b/pyobjc_framework_mediaaccessibility-11.1.tar.gz", hash = "sha256:52479a998fec3d079d2d4590a945fc78c41fe7ac8c76f1964c9d8156880565a4", size = 18440, upload-time = "2025-06-14T20:57:51.126Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/10/dc1007e56944ed2e981e69e7b2fed2b2202c79b0d5b742b29b1081d1cbdd/pyobjc_framework_mediaaccessibility-12.1.tar.gz", hash = "sha256:cc4e3b1d45e84133d240318d53424eff55968f5c6873c2c53267598853445a3f", size = 16325, upload-time = "2025-11-14T10:17:07.454Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/a1/f4cbdf8478ad01859e2c8eef08e28b8a53b9aa4fe5d238a86bad29b73555/pyobjc_framework_mediaaccessibility-11.1-py2.py3-none-any.whl", hash = "sha256:cd07e7fc375ff1e8d225e0aa2bd9c2c1497a4d3aa5a80bfb13b08800fcd7f034", size = 4691, upload-time = "2025-06-14T20:51:26.596Z" }, + { url = "https://files.pythonhosted.org/packages/a2/0c/7fb5462561f59d739192c6d02ba0fd36ad7841efac5a8398a85a030ef7fc/pyobjc_framework_mediaaccessibility-12.1-py2.py3-none-any.whl", hash = "sha256:2ff8845c97dd52b0e5cf53990291e6d77c8a73a7aac0e9235d62d9a4256916d1", size = 4800, upload-time = "2025-11-14T09:54:05.04Z" }, ] [[package]] name = "pyobjc-framework-mediaextension" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -3316,276 +3371,276 @@ dependencies = [ { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/09/fd214dc0cf3f3bc3f528815af4799c0cb7b4bf4032703b19ea63486a132b/pyobjc_framework_mediaextension-11.1.tar.gz", hash = "sha256:85a1c8a94e9175fb364c453066ef99b95752343fd113f08a3805cad56e2fa709", size = 58489, upload-time = "2025-06-14T20:57:51.796Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/aa/1e8015711df1cdb5e4a0aa0ed4721409d39971ae6e1e71915e3ab72423a3/pyobjc_framework_mediaextension-12.1.tar.gz", hash = "sha256:44409d63cc7d74e5724a68e3f9252cb62fd0fd3ccf0ca94c6a33e5c990149953", size = 39425, upload-time = "2025-11-14T10:17:11.486Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/25/95315f730e9b73ef9e8936ed3ded636d3ac71b4d5653d4caf1d20a2314a8/pyobjc_framework_mediaextension-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:915c0cbb04913beb1f1ac8939dc0e615da8ddfba3927863a476af49f193415c5", size = 38858, upload-time = "2025-06-14T20:51:28.296Z" }, - { url = "https://files.pythonhosted.org/packages/56/78/2c2d8265851f6060dbf4434c21bd67bf569b8c3071ba1f257e43aae563a8/pyobjc_framework_mediaextension-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:06cb19004413a4b08dd75cf1e5dadea7f2df8d15feeeb7adb529d0cf947fa789", size = 38859, upload-time = "2025-06-14T20:51:29.102Z" }, + { url = "https://files.pythonhosted.org/packages/8e/6f/60b63edf5d27acf450e4937b7193c1a2bd6195fee18e15df6a5734dedb71/pyobjc_framework_mediaextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9555f937f2508bd2b6264cba088e2c2e516b2f94a6c804aee40e33fd89c2fb78", size = 38957, upload-time = "2025-11-14T09:54:13.22Z" }, + { url = "https://files.pythonhosted.org/packages/2b/ed/99038bcf72ec68e452709af10a087c1377c2d595ba4e66d7a2b0775145d2/pyobjc_framework_mediaextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:442bc3a759efb5c154cb75d643a5e182297093533fcdd1c24be6f64f68b93371", size = 38973, upload-time = "2025-11-14T09:54:16.701Z" }, ] [[package]] name = "pyobjc-framework-medialibrary" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2b/06/11ff622fb5fbdd557998a45cedd2b0a1c7ea5cc6c5cb015dd6e42ebd1c41/pyobjc_framework_medialibrary-11.1.tar.gz", hash = "sha256:102f4326f789734b7b2dfe689abd3840ca75a76fb8058bd3e4f85398ae2ce29d", size = 18706, upload-time = "2025-06-14T20:57:52.474Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/e9/848ebd02456f8fdb41b42298ec585bfed5899dbd30306ea5b0a7e4c4b341/pyobjc_framework_medialibrary-12.1.tar.gz", hash = "sha256:690dcca09b62511df18f58e8566cb33d9652aae09fe63a83f594bd018b5edfcd", size = 15995, upload-time = "2025-11-14T10:17:15.45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/2b/a4200080d97f88fdd406119bb8f00ccb7f32794f84735485510c14e87e76/pyobjc_framework_medialibrary-11.1-py2.py3-none-any.whl", hash = "sha256:779be84bd280f63837ce02028ca46b41b090902aa4205887ffd5777f49377669", size = 4340, upload-time = "2025-06-14T20:51:34.339Z" }, + { url = "https://files.pythonhosted.org/packages/c2/cd/eeaf8585a343fda5b8cf3b8f144c872d1057c845202098b9441a39b76cb0/pyobjc_framework_medialibrary-12.1-py2.py3-none-any.whl", hash = "sha256:1f03ad6802a5c6e19ee3208b065689d3ec79defe1052cb80e00f54e1eff5f2a0", size = 4361, upload-time = "2025-11-14T09:54:32.259Z" }, ] [[package]] name = "pyobjc-framework-mediaplayer" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/d5/daba26eb8c70af1f3823acfd7925356acc4dd75eeac4fc86dc95d94d0e15/pyobjc_framework_mediaplayer-11.1.tar.gz", hash = "sha256:d07a634b98e1b9eedd82d76f35e616525da096bd341051ea74f0971e0f2f2ddd", size = 93749, upload-time = "2025-06-14T20:57:53.165Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/f0/851f6f47e11acbd62d5f5dcb8274afc969135e30018591f75bf3cbf6417f/pyobjc_framework_mediaplayer-12.1.tar.gz", hash = "sha256:5ef3f669bdf837d87cdb5a486ec34831542360d14bcba099c7c2e0383380794c", size = 35402, upload-time = "2025-11-14T10:17:18.97Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/aa/b37aac80d821bd2fa347ddad1f6c7c75b23155e500edf1cb3b3740c27036/pyobjc_framework_mediaplayer-11.1-py2.py3-none-any.whl", hash = "sha256:b655cf537ea52d73209eb12935a047301c30239b318a366600f0f44335d51c9a", size = 6960, upload-time = "2025-06-14T20:51:35.171Z" }, + { url = "https://files.pythonhosted.org/packages/58/c0/038ee3efd286c0fbc89c1e0cb688f4670ed0e5803aa36e739e79ffc91331/pyobjc_framework_mediaplayer-12.1-py2.py3-none-any.whl", hash = "sha256:85d9baec131807bfdf0f4c24d4b943e83cce806ab31c95c7e19c78e3fb7eefc8", size = 7120, upload-time = "2025-11-14T09:54:33.901Z" }, ] [[package]] name = "pyobjc-framework-mediatoolbox" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/68/cc230d2dfdeb974fdcfa828de655a43ce2bf4962023fd55bbb7ab0970100/pyobjc_framework_mediatoolbox-11.1.tar.gz", hash = "sha256:97834addc5179b3165c0d8cd74cc97ad43ed4c89547724216426348aca3b822a", size = 23568, upload-time = "2025-06-14T20:57:53.913Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/71/be5879380a161f98212a336b432256f307d1dcbaaaeb8ec988aea2ada2cd/pyobjc_framework_mediatoolbox-12.1.tar.gz", hash = "sha256:385b48746a5f08756ee87afc14037e552954c427ed5745d7ece31a21a7bad5ab", size = 22305, upload-time = "2025-11-14T10:17:22.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/bc/6b69ca3c2bf1573b907be460c6a413ff2dfd1c037da53f46aec3bcdb3c73/pyobjc_framework_mediatoolbox-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da60c0409b18dfb9fa60a60589881e1382c007700b99722926270feadcf3bfc1", size = 12630, upload-time = "2025-06-14T20:51:36.873Z" }, - { url = "https://files.pythonhosted.org/packages/b5/23/6b5d999e1e71c42d5d116d992515955ac1bbc5cf4890072bb26f38eb9802/pyobjc_framework_mediatoolbox-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2867c91645a335ee29b47e9c0e9fd3ea8c9daad0c0719c50b8bf244d76998056", size = 12785, upload-time = "2025-06-14T20:51:37.593Z" }, + { url = "https://files.pythonhosted.org/packages/8f/7a/f20ebd3c590b2cc85cde3e608e49309bfccf9312e4aca7b7ea60908d36d7/pyobjc_framework_mediatoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74de0cb2d5aaa77e81f8b97eab0f39cd2fab5bf6fa7c6fb5546740cbfb1f8c1f", size = 12656, upload-time = "2025-11-14T09:54:39.215Z" }, + { url = "https://files.pythonhosted.org/packages/9c/94/d5ee221f2afbc64b2a7074efe25387cd8700e8116518904b28091ea6ad74/pyobjc_framework_mediatoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d7bcfeeff3fbf7e9e556ecafd8eaed2411df15c52baf134efa7480494e6faf6d", size = 12818, upload-time = "2025-11-14T09:54:41.251Z" }, ] [[package]] name = "pyobjc-framework-metal" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/cf/29fea96fd49bf72946c5dac4c43ef50f26c15e9f76edd6f15580d556aa23/pyobjc_framework_metal-11.1.tar.gz", hash = "sha256:f9fd3b7574a824632ee9b7602973da30f172d2b575dd0c0f5ef76b44cfe9f6f9", size = 446549, upload-time = "2025-06-14T20:57:54.731Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/06/a84f7eb8561d5631954b9458cfca04b690b80b5b85ce70642bc89335f52a/pyobjc_framework_metal-12.1.tar.gz", hash = "sha256:bb554877d5ee2bf3f340ad88e8fe1b85baab7b5ec4bd6ae0f4f7604147e3eae7", size = 181847, upload-time = "2025-11-14T10:17:34.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/e8/cd0621e246dc0dc06f55c50af3002573ad19208e30f6806ec997ac587886/pyobjc_framework_metal-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:157a0052be459ffb35a3687f77a96ea87b42caf4cdd0b9f7245242b100edb4f0", size = 58066, upload-time = "2025-06-14T20:51:44.243Z" }, - { url = "https://files.pythonhosted.org/packages/4c/94/3d5a8bed000dec4a13e72dde175898b488192716b7256a05cc253c77020d/pyobjc_framework_metal-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f3aae0f9a4192a7f4f158dbee126ab5ef63a81bf9165ec63bc50c353c8d0e6f", size = 57969, upload-time = "2025-06-14T20:51:45.051Z" }, + { url = "https://files.pythonhosted.org/packages/1d/cf/edbb8b6dd084df3d235b74dbeb1fc5daf4d063ee79d13fa3bc1cb1779177/pyobjc_framework_metal-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59e10f9b36d2e409f80f42b6175457a07b18a21ca57ff268f4bc519cd30db202", size = 75920, upload-time = "2025-11-14T09:55:01.048Z" }, + { url = "https://files.pythonhosted.org/packages/d0/48/9286d06e1b14c11b65d3fea1555edc0061d9ebe11898dff8a14089e3a4c9/pyobjc_framework_metal-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38ab566b5a2979a43e13593d3eb12000a45e574576fe76996a5e1eb75ad7ac78", size = 75841, upload-time = "2025-11-14T09:55:06.801Z" }, ] [[package]] name = "pyobjc-framework-metalfx" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/10/20/4c839a356b534c161fb97e06589f418fc78cc5a0808362bdecf4f9a61a8d/pyobjc_framework_metalfx-11.1.tar.gz", hash = "sha256:555c1b895d4ba31be43930f45e219a5d7bb0e531d148a78b6b75b677cc588fd8", size = 27002, upload-time = "2025-06-14T20:57:55.949Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/09/ce5c74565677fde66de3b9d35389066b19e5d1bfef9d9a4ad80f0c858c0c/pyobjc_framework_metalfx-12.1.tar.gz", hash = "sha256:1551b686fb80083a97879ce0331bdb1d4c9b94557570b7ecc35ebf40ff65c90b", size = 29470, upload-time = "2025-11-14T10:17:37.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/f5/df29eeaaf053cd931fb74204a5f8827f88875a81c456b1e0fa24ea0bbcee/pyobjc_framework_metalfx-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cbfca74f437fcde89de85d14de33c2e617d3084f5fc2b4d614a700e516324f55", size = 10091, upload-time = "2025-06-14T20:51:51.084Z" }, - { url = "https://files.pythonhosted.org/packages/36/73/a8df8fa445a09fbc917a495a30b13fbcf224b5576c1e464d5ece9824a493/pyobjc_framework_metalfx-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:60e1dcdf133d2504d810c3a9ba5a02781c9d54c2112a9238de8e3ca4e8debf31", size = 10107, upload-time = "2025-06-14T20:51:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/8b/e5/5494639c927085bbba1a310e73662e0bda44b90cdff67fa03a4e1c24d4c4/pyobjc_framework_metalfx-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ec3f7ab036eae45e067fbf209676f98075892aa307d73bb9394304960746cd2", size = 15026, upload-time = "2025-11-14T09:55:35.239Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0b/508e3af499694f4eec74cc3ab0530e38db76e43a27db9ecb98c50c68f5f9/pyobjc_framework_metalfx-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a4418ae5c2eb77ec00695fa720a547638dc252dfd77ecb6feb88f713f5a948fd", size = 15062, upload-time = "2025-11-14T09:55:37.352Z" }, ] [[package]] name = "pyobjc-framework-metalkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/45/cb/7e01bc61625c7a6fea9c9888c9ed35aa6bbc47cda2fcd02b6525757bc2b8/pyobjc_framework_metalkit-11.1.tar.gz", hash = "sha256:8811cd81ee9583b9330df4f2499a73dcc53f3359cb92767b409acaec9e4faa1e", size = 45135, upload-time = "2025-06-14T20:57:56.601Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/15/5091147aae12d4011a788b93971c3376aaaf9bf32aa935a2c9a06a71e18b/pyobjc_framework_metalkit-12.1.tar.gz", hash = "sha256:14cc5c256f0e3471b412a5b3582cb2a0d36d3d57401a8aa09e433252d1c34824", size = 25473, upload-time = "2025-11-14T10:17:39.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/eb/fd5640015fc91b16e23cafe3a84508775344cd13f621e62b9c32d1750a83/pyobjc_framework_metalkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95abb993d17be7a9d1174701594cc040e557983d0a0e9f49b1dfa9868ef20ed6", size = 8711, upload-time = "2025-06-14T20:51:56.765Z" }, - { url = "https://files.pythonhosted.org/packages/87/0c/516b6d7a67a170b7d2316701d5288797a19dd283fcc2f73b7b78973e1392/pyobjc_framework_metalkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4854cf74fccf6ce516b49bf7cf8fc7c22da9a3743914a2f4b00f336206ad47ec", size = 8730, upload-time = "2025-06-14T20:51:57.824Z" }, + { url = "https://files.pythonhosted.org/packages/10/c5/f72cbc3a5e83211cbfa33b60611abcebbe893854d0f2b28ff6f444f97549/pyobjc_framework_metalkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:28636454f222d9b20cb61f6e8dc1ebd48237903feb4d0dbdf9d7904c542475e5", size = 8735, upload-time = "2025-11-14T09:55:50.053Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c0/c8b5b060895cd51493afe3f09909b7e34893b1161cf4d93bc8e3cd306129/pyobjc_framework_metalkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c4869076571d94788fe539fabfdd568a5c8e340936c7726d2551196640bd152", size = 8755, upload-time = "2025-11-14T09:55:51.683Z" }, ] [[package]] name = "pyobjc-framework-metalperformanceshaders" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d0/11/5df398a158a6efe2c87ac5cae121ef2788242afe5d4302d703147b9fcd91/pyobjc_framework_metalperformanceshaders-11.1.tar.gz", hash = "sha256:8a312d090a0f51651e63d9001e6cc7c1aa04ceccf23b494cbf84b7fd3d122071", size = 302113, upload-time = "2025-06-14T20:57:57.407Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/68/58da38e54aa0d8c19f0d3084d8c84e92d54cc8c9254041f07119d86aa073/pyobjc_framework_metalperformanceshaders-12.1.tar.gz", hash = "sha256:b198e755b95a1de1525e63c3b14327ae93ef1d88359e6be1ce554a3493755b50", size = 137301, upload-time = "2025-11-14T10:17:49.554Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/ce/bbcf26f8aa94fb6edcf1a71ef23cd8df2afd4b5c2be451432211827c2ab0/pyobjc_framework_metalperformanceshaders-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:81ec1f85c55d11529008e6a0fb1329d5184620f04d89751c11bf14d7dd9798ee", size = 32650, upload-time = "2025-06-14T20:52:04.451Z" }, - { url = "https://files.pythonhosted.org/packages/89/df/f844516a54ef0fa1d047fe5fd94b63bc8b1218c09f7d4309b2a67a79708d/pyobjc_framework_metalperformanceshaders-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:06b2a4e446fe859e30f7efc7ccfbaefd443225a6ec53d949a113a6a4acc16c4c", size = 32888, upload-time = "2025-06-14T20:52:05.225Z" }, + { url = "https://files.pythonhosted.org/packages/00/0f/6dc06a08599a3bc211852a5e6dcb4ed65dfbf1066958feb367ba7702798a/pyobjc_framework_metalperformanceshaders-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0159a6f731dc0fd126481a26490683586864e9d47c678900049a8ffe0135f56", size = 32988, upload-time = "2025-11-14T09:56:05.323Z" }, + { url = "https://files.pythonhosted.org/packages/62/84/d505496fca9341e0cb11258ace7640cd986fe3e831f8b4749035e9f82109/pyobjc_framework_metalperformanceshaders-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c00e786c352b3ff5d86cf0cf3a830dc9f6fc32a03ae1a7539d20d11324adb2e8", size = 33242, upload-time = "2025-11-14T09:56:09.354Z" }, ] [[package]] name = "pyobjc-framework-metalperformanceshadersgraph" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-metalperformanceshaders", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/c3/8d98661f7eecd1f1b0d80a80961069081b88efd3a82fbbed2d7e6050c0ad/pyobjc_framework_metalperformanceshadersgraph-11.1.tar.gz", hash = "sha256:d25225aab4edc6f786b29fe3d9badc4f3e2d0caeab1054cd4f224258c1b6dbe2", size = 105098, upload-time = "2025-06-14T20:57:58.273Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/56/7ad0cd085532f7bdea9a8d4e9a2dfde376d26dd21e5eabdf1a366040eff8/pyobjc_framework_metalperformanceshadersgraph-12.1.tar.gz", hash = "sha256:b8fd017b47698037d7b172d41bed7a4835f4c4f2a288235819d200005f89ee35", size = 42992, upload-time = "2025-11-14T10:17:53.502Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/a1/2033cf8b0d9f059e3495a1d9a691751b242379c36dd5bcb96c8edb121c9e/pyobjc_framework_metalperformanceshadersgraph-11.1-py2.py3-none-any.whl", hash = "sha256:9b8b014e8301c2ae608a25f73bbf23c8f3f73a6f5fdbafddad509a21b84df681", size = 6461, upload-time = "2025-06-14T20:52:10.522Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c9/5e7fd0d4bc9bdf7b442f36e020677c721ba9b4c1dc1fa3180085f22a4ef9/pyobjc_framework_metalperformanceshadersgraph-12.1-py2.py3-none-any.whl", hash = "sha256:85a1c7a6114ada05c7924b3235a1a98c45359410d148097488f15aee5ebb6ab9", size = 6481, upload-time = "2025-11-14T09:56:23.66Z" }, ] [[package]] name = "pyobjc-framework-metrickit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/48/8ae969a51a91864000e39c1de74627b12ff587b1dbad9406f7a30dfe71f8/pyobjc_framework_metrickit-11.1.tar.gz", hash = "sha256:a79d37575489916c35840e6a07edd958be578d3be7a3d621684d028d721f0b85", size = 40952, upload-time = "2025-06-14T20:57:58.996Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/13/5576ddfbc0b174810a49171e2dbe610bdafd3b701011c6ecd9b3a461de8a/pyobjc_framework_metrickit-12.1.tar.gz", hash = "sha256:77841daf6b36ba0c19df88545fd910c0516acf279e6b7b4fa0a712a046eaa9f1", size = 27627, upload-time = "2025-11-14T10:17:56.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/cd/e459511c194d25c4acd31cbdb5c118215795785840861d55dbc8bd55cf35/pyobjc_framework_metrickit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a5d2b394f7acadd17d8947d188106424f59393b45dd4a842ac3cc50935170e3e", size = 8063, upload-time = "2025-06-14T20:52:12.696Z" }, - { url = "https://files.pythonhosted.org/packages/55/d1/aea4655e7eaa9ab19da8fe78ab363270443059c8a542b8f8a071b4988b57/pyobjc_framework_metrickit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a034e6b982e915da881edef87d71b063e596511d52aef7a32c683571f364156e", size = 8081, upload-time = "2025-06-14T20:52:13.72Z" }, + { url = "https://files.pythonhosted.org/packages/5e/b0/e57c60af3e9214e05309dca201abb82e10e8cf91952d90d572b641d62027/pyobjc_framework_metrickit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da6650afd9523cf7a9cae177f4bbd1ad45cc122d97784785fa1482847485142c", size = 8102, upload-time = "2025-11-14T09:56:27.194Z" }, + { url = "https://files.pythonhosted.org/packages/b7/04/8da5126e47306438c99750f1dfed430d7cc388f6b7f420ae748f3060ab96/pyobjc_framework_metrickit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3ec96e9ec7dc37fbce57dae277f0d89c66ffe1c3fa2feaca1b7125f8b2b29d87", size = 8120, upload-time = "2025-11-14T09:56:28.73Z" }, ] [[package]] name = "pyobjc-framework-mlcompute" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8b/e6/f064dec650fb1209f41aba0c3074416cb9b975a7cf4d05d93036e3d917f0/pyobjc_framework_mlcompute-11.1.tar.gz", hash = "sha256:f6c4c3ea6a62e4e3927abf9783c40495aa8bb9a8c89def744b0822da58c2354b", size = 89021, upload-time = "2025-06-14T20:57:59.997Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/69/15f8ce96c14383aa783c8e4bc1e6d936a489343bb197b8e71abb3ddc1cb8/pyobjc_framework_mlcompute-12.1.tar.gz", hash = "sha256:3281db120273dcc56e97becffd5cedf9c62042788289f7be6ea067a863164f1e", size = 40698, upload-time = "2025-11-14T10:17:59.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/23/cc/f47a4ac2d1a792b82206fdab58cc61b3aae15e694803ea2c81f3dfc16d9d/pyobjc_framework_mlcompute-11.1-py2.py3-none-any.whl", hash = "sha256:975150725e919f8d3d33f830898f3cd2fd19a440999faab320609487f4eae19d", size = 6778, upload-time = "2025-06-14T20:52:19.844Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f7/4614b9ccd0151795e328b9ed881fbcbb13e577a8ec4ae3507edb1a462731/pyobjc_framework_mlcompute-12.1-py2.py3-none-any.whl", hash = "sha256:4f0fc19551d710a03dfc4c7129299897544ff8ea76db6c7539ecc2f9b2571bde", size = 6744, upload-time = "2025-11-14T09:56:36.973Z" }, ] [[package]] name = "pyobjc-framework-modelio" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a0/27/140bf75706332729de252cc4141e8c8afe16a0e9e5818b5a23155aa3473c/pyobjc_framework_modelio-11.1.tar.gz", hash = "sha256:fad0fa2c09d468ac7e49848e144f7bbce6826f2178b3120add8960a83e5bfcb7", size = 123203, upload-time = "2025-06-14T20:58:01.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b4/11/32c358111b623b4a0af9e90470b198fffc068b45acac74e1ba711aee7199/pyobjc_framework_modelio-12.1.tar.gz", hash = "sha256:d041d7bca7c2a4526344d3e593347225b7a2e51a499b3aa548895ba516d1bdbb", size = 66482, upload-time = "2025-11-14T10:18:04.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/66/8109e52c7d97a108d4852a2032c9d7a7ecd27c6085bd7b2920b2ab575df4/pyobjc_framework_modelio-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4365fb96eb42b71c12efdfa2ff9d44755d5c292b8d1c78b947833d84271e359f", size = 20142, upload-time = "2025-06-14T20:52:21.582Z" }, - { url = "https://files.pythonhosted.org/packages/18/84/5f223b82894777388ef1aa09579d9c044044877a72075213741c97adc901/pyobjc_framework_modelio-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5d5e11389bde0852490b2a37896aaf9eb674b2a3586f2c572f9101cecb7bc576", size = 20172, upload-time = "2025-06-14T20:52:22.327Z" }, + { url = "https://files.pythonhosted.org/packages/35/c0/c67b806f3f2bb6264a4f7778a2aa82c7b0f50dfac40f6a60366ffc5afaf5/pyobjc_framework_modelio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c2c99d47a7e4956a75ce19bddbe2d8ada7d7ce9e2f56ff53fc2898367187749", size = 20180, upload-time = "2025-11-14T09:56:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/f6/0e/b8331100f0d658ecb3e87e75c108e2ae8ac7c78b521fd5ad0205b60a2584/pyobjc_framework_modelio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:68d971917c289fdddf69094c74915d2ccb746b42b150e0bdc16d8161e6164022", size = 20193, upload-time = "2025-11-14T09:56:44.296Z" }, ] [[package]] name = "pyobjc-framework-multipeerconnectivity" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/99/75bf6170e282d9e546b353b65af7859de8b1b27ddc431fc4afbf15423d01/pyobjc_framework_multipeerconnectivity-11.1.tar.gz", hash = "sha256:a3dacca5e6e2f1960dd2d1107d98399ff81ecf54a9852baa8ec8767dbfdbf54b", size = 26149, upload-time = "2025-06-14T20:58:01.793Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/35/0d0bb6881004cb238cfd7bf74f4b2e42601a1accdf27b2189ec61cf3a2dc/pyobjc_framework_multipeerconnectivity-12.1.tar.gz", hash = "sha256:7123f734b7174cacbe92a51a62b4645cc9033f6b462ff945b504b62e1b9e6c1c", size = 22816, upload-time = "2025-11-14T10:18:07.363Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/fc/a3fc2514879a39673202f7ea5e835135255c5e510d30c58a43239ec1d9e0/pyobjc_framework_multipeerconnectivity-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b3c9d4d36e0c142b4ce91033740ed5bca19fe7ec96870d90610d2942ecd3cd39", size = 11955, upload-time = "2025-06-14T20:52:28.392Z" }, - { url = "https://files.pythonhosted.org/packages/b4/fe/5c29c227f6ed81147ec6ec3e681fc680a7ffe0360f96901371435ea68570/pyobjc_framework_multipeerconnectivity-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:970031deb3dbf8da1fcb04e785d4bd2eeedae8f6677db92881df6d92b05c31d6", size = 11981, upload-time = "2025-06-14T20:52:29.406Z" }, + { url = "https://files.pythonhosted.org/packages/12/eb/e3e4ba158167696498f6491f91a8ac7e24f1ebbab5042cd34318e5d2035c/pyobjc_framework_multipeerconnectivity-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7372e505ed050286aeb83d7e158fda65ad379eae12e1526f32da0a260a8b7d06", size = 11981, upload-time = "2025-11-14T09:56:58.858Z" }, + { url = "https://files.pythonhosted.org/packages/33/8d/0646ff7db36942829f0e84be18ba44bc5cd96d6a81651f8e7dc0974821c1/pyobjc_framework_multipeerconnectivity-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c3bd254a16debed321debf4858f9c9b7d41572ddf1058a4bacf6a5bcfedeeff", size = 12001, upload-time = "2025-11-14T09:57:01.027Z" }, ] [[package]] name = "pyobjc-framework-naturallanguage" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a2/e9/5352fbf09c5d5360405dea49fb77e53ed55acd572a94ce9a0d05f64d2b70/pyobjc_framework_naturallanguage-11.1.tar.gz", hash = "sha256:ab1fc711713aa29c32719774fc623bf2d32168aed21883970d4896e901ff4b41", size = 46120, upload-time = "2025-06-14T20:58:02.808Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/d1/c81c0cdbb198d498edc9bc5fbb17e79b796450c17bb7541adbf502f9ad65/pyobjc_framework_naturallanguage-12.1.tar.gz", hash = "sha256:cb27a1e1e5b2913d308c49fcd2fd04ab5ea87cb60cac4a576a91ebf6a50e52f6", size = 23524, upload-time = "2025-11-14T10:18:09.883Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/f2/de86665d48737c74756b016c0f3bf93c99ca4151b48b14e2fbe7233283f8/pyobjc_framework_naturallanguage-11.1-py2.py3-none-any.whl", hash = "sha256:65a780273d2cdd12a3fa304e9c9ad822cb71facd9281f1b35a71640c53826f7c", size = 5306, upload-time = "2025-06-14T20:52:34.024Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d8/715a11111f76c80769cb267a19ecf2a4ac76152a6410debb5a4790422256/pyobjc_framework_naturallanguage-12.1-py2.py3-none-any.whl", hash = "sha256:a02ef383ec88948ca28f03ab8995523726b3bc75c49f593b5c89c218bcbce7ce", size = 5320, upload-time = "2025-11-14T09:57:10.294Z" }, ] [[package]] name = "pyobjc-framework-netfs" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/68/5d/d68cc59a1c1ea61f227ed58e7b185a444d560655320b53ced155076f5b78/pyobjc_framework_netfs-11.1.tar.gz", hash = "sha256:9c49f050c8171dc37e54d05dd12a63979c8b6b565c10f05092923a2250446f50", size = 15910, upload-time = "2025-06-14T20:58:03.811Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/68/4bf0e5b8cc0780cf7acf0aec54def58c8bcf8d733db0bd38f5a264d1af06/pyobjc_framework_netfs-12.1.tar.gz", hash = "sha256:e8d0c25f41d7d9ced1aa2483238d0a80536df21f4b588640a72e1bdb87e75c1e", size = 14799, upload-time = "2025-11-14T10:18:11.85Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/cc/199b06f214f8a2db26eb47e3ab7015a306597a1bca25dcb4d14ddc65bd4a/pyobjc_framework_netfs-11.1-py2.py3-none-any.whl", hash = "sha256:f202e8e0c2e73516d3eac7a43b1c66f9911cdbb37ea32750ed197d82162c994a", size = 4143, upload-time = "2025-06-14T20:52:35.428Z" }, + { url = "https://files.pythonhosted.org/packages/7e/6b/8c2f223879edd3e3f030d0a9c9ba812775519c6d0c257e3e7255785ca6e7/pyobjc_framework_netfs-12.1-py2.py3-none-any.whl", hash = "sha256:0021f8b141e693d3821524c170e9c645090eb320e80c2935ddb978a6e8b8da81", size = 4163, upload-time = "2025-11-14T09:57:11.845Z" }, ] [[package]] name = "pyobjc-framework-network" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/ee/5ea93e48eca341b274027e1532bd8629fd55d609cd9c39c2c3acf26158c3/pyobjc_framework_network-11.1.tar.gz", hash = "sha256:f6df7a58a1279bbc976fd7e2efe813afbbb18427df40463e6e2ee28fba07d2df", size = 124670, upload-time = "2025-06-14T20:58:05.491Z" } +sdist = { url = "https://files.pythonhosted.org/packages/38/13/a71270a1b0a9ec979e68b8ec84b0f960e908b17b51cb3cac246a74d52b6b/pyobjc_framework_network-12.1.tar.gz", hash = "sha256:dbf736ff84d1caa41224e86ff84d34b4e9eb6918ae4e373a44d3cb597648a16a", size = 56990, upload-time = "2025-11-14T10:18:16.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/e9/a54f32daa0365bf000b739fc386d4783432273a9075337aa57a3808af65d/pyobjc_framework_network-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e56691507584c09cdb50f1cd69b5f57b42fd55c396e8c34fab8c5b81b44d36ed", size = 19499, upload-time = "2025-06-14T20:52:37.158Z" }, - { url = "https://files.pythonhosted.org/packages/15/c2/3c6626fdb3616fde2c173d313d15caea22d141abcc2fbf3b615f8555abe3/pyobjc_framework_network-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8cdc9be8ec3b0ae95e5c649e4bbcdf502cffd357dacc566223be707bdd5ac271", size = 19513, upload-time = "2025-06-14T20:52:38.423Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7c/4f9fc6b94be3e949b7579128cbb9171943e27d1d7841db12d66b76aeadc3/pyobjc_framework_network-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d1ad948b9b977f432bf05363381d7d85a7021246ebf9d50771b35bf8d4548d2b", size = 19593, upload-time = "2025-11-14T09:57:17.027Z" }, + { url = "https://files.pythonhosted.org/packages/9d/ef/a53f04f43e93932817f2ea71689dcc8afe3b908d631c21d11ec30c7b2e87/pyobjc_framework_network-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e53aad64eae2933fe12d49185d66aca62fb817abf8a46f86b01e436ce1b79e4", size = 19613, upload-time = "2025-11-14T09:57:19.571Z" }, ] [[package]] name = "pyobjc-framework-networkextension" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/30/d1eee738d702bbca78effdaa346a2b05359ab8a96d961b7cb44838e236ca/pyobjc_framework_networkextension-11.1.tar.gz", hash = "sha256:2b74b430ca651293e5aa90a1e7571b200d0acbf42803af87306ac8a1c70b0d4b", size = 217252, upload-time = "2025-06-14T20:58:06.311Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bf/3e/ac51dbb2efa16903e6af01f3c1f5a854c558661a7a5375c3e8767ac668e8/pyobjc_framework_networkextension-12.1.tar.gz", hash = "sha256:36abc339a7f214ab6a05cb2384a9df912f247163710741e118662bd049acfa2e", size = 62796, upload-time = "2025-11-14T10:18:21.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/d7/b10aa191d37900ade78f1b7806d17ff29fa95f40ce7aeecce6f15ec94ac9/pyobjc_framework_networkextension-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:55e5ca70c81a864896b603cfcabf4c065783f64395460d16fe16db2bf0866d60", size = 14101, upload-time = "2025-06-14T20:52:44.527Z" }, - { url = "https://files.pythonhosted.org/packages/b6/26/526cd9f63e390e9c2153c41dc0982231b0b1ca88865deb538b77e1c3513d/pyobjc_framework_networkextension-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:853458aae8b43634461f6c44759750e2dc784c9aba561f9468ab14529b5a7fbe", size = 14114, upload-time = "2025-06-14T20:52:45.274Z" }, + { url = "https://files.pythonhosted.org/packages/6e/4e/aa34fc983f001cdb1afbbb4d08b42fd019fc9816caca0bf0b166db1688c1/pyobjc_framework_networkextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c3082c29f94ca3a05cd1f3219999ca3af9b6dece1302ccf789f347e612bb9303", size = 14368, upload-time = "2025-11-14T09:57:33.748Z" }, + { url = "https://files.pythonhosted.org/packages/f6/14/4934b10ade5ad0518001bfc25260d926816b9c7d08d85ef45e8a61fdef1b/pyobjc_framework_networkextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:adc9baacfc532944d67018e381c7645f66a9fa0064939a5a841476d81422cdcc", size = 14376, upload-time = "2025-11-14T09:57:36.132Z" }, ] [[package]] name = "pyobjc-framework-notificationcenter" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4a/d3529b9bd7aae2c89d258ebc234673c5435e217a5136abd8c0aba37b916b/pyobjc_framework_notificationcenter-11.1.tar.gz", hash = "sha256:0b938053f2d6b1cea9db79313639d7eb9ddd5b2a5436a346be0887e75101e717", size = 23389, upload-time = "2025-06-14T20:58:07.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/12/ae0fe82fb1e02365c9fe9531c9de46322f7af09e3659882212c6bf24d75e/pyobjc_framework_notificationcenter-12.1.tar.gz", hash = "sha256:2d09f5ab9dc39770bae4fa0c7cfe961e6c440c8fc465191d403633dccc941094", size = 21282, upload-time = "2025-11-14T10:18:24.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/ed/3beb825e2b80de45b90e7cd510ad52890ac4a5a4de88cd9a5291235519fb/pyobjc_framework_notificationcenter-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d44413818e7fa3662f784cdcf0730c86676dd7333b7d24a7da13d4ffcde491b", size = 9859, upload-time = "2025-06-14T20:52:51.744Z" }, - { url = "https://files.pythonhosted.org/packages/6d/92/cd00fe5e54a191fb77611fe728a8c8a0a6edb229857d32f27806582406ca/pyobjc_framework_notificationcenter-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:65fc67374a471890245c7a1d60cf67dcf160075a9c048a5d89608a8290f33b03", size = 9880, upload-time = "2025-06-14T20:52:52.406Z" }, + { url = "https://files.pythonhosted.org/packages/47/aa/03526fc0cc285c0f8cf31c74ce3a7a464011cc8fa82a35a1637d9878c788/pyobjc_framework_notificationcenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e254f2a56ff5372793dea938a2b2683dd0bc40c5107fede76f9c2c1f6641a2", size = 9871, upload-time = "2025-11-14T09:57:49.208Z" }, + { url = "https://files.pythonhosted.org/packages/d8/05/3168637dd425257df5693c2ceafecf92d2e6833c0aaa6594d894a528d797/pyobjc_framework_notificationcenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82a735bd63f315f0a56abd206373917b7d09a0ae35fd99f1639a0fac4c525c0a", size = 9895, upload-time = "2025-11-14T09:57:51.151Z" }, ] [[package]] name = "pyobjc-framework-opendirectory" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9d/02/ac56c56fdfbc24cdf87f4a624f81bbe2e371d0983529b211a18c6170e932/pyobjc_framework_opendirectory-11.1.tar.gz", hash = "sha256:319ac3424ed0350be458b78148914468a8fc13a069d62e7869e3079108e4f118", size = 188880, upload-time = "2025-06-14T20:58:08.003Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/11/bc2f71d3077b3bd078dccad5c0c5c57ec807fefe3d90c97b97dd0ed3d04b/pyobjc_framework_opendirectory-12.1.tar.gz", hash = "sha256:2c63ce5dd179828ef2d8f9e3961da3bfa971a57db07a6c34eedc296548a928bb", size = 61049, upload-time = "2025-11-14T10:18:29.336Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/56/f0f5b7222d5030192c44010ab7260681e349efea2f1b1b9f116ba1951d6d/pyobjc_framework_opendirectory-11.1-py2.py3-none-any.whl", hash = "sha256:bb4219b0d98dff4a952c50a79b1855ce74e1defd0d241f3013def5b09256fd7b", size = 11829, upload-time = "2025-06-14T20:52:56.715Z" }, + { url = "https://files.pythonhosted.org/packages/d6/e7/3c2dece9c5b28af28a44d72a27b35ea5ffac31fed7cbd8d696ea75dc4a81/pyobjc_framework_opendirectory-12.1-py2.py3-none-any.whl", hash = "sha256:b5b5a5cf3cc2fb25147b16b79f046b90e3982bf3ded1b210a993d8cfdba737c4", size = 11845, upload-time = "2025-11-14T09:58:00.175Z" }, ] [[package]] name = "pyobjc-framework-osakit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/22/f9cdfb5de255b335f99e61a3284be7cb1552a43ed1dfe7c22cc868c23819/pyobjc_framework_osakit-11.1.tar.gz", hash = "sha256:920987da78b67578367c315d208f87e8fab01dd35825d72242909f29fb43c820", size = 22290, upload-time = "2025-06-14T20:58:09.103Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/b9/bf52c555c75a83aa45782122432fa06066bb76469047f13d06fb31e585c4/pyobjc_framework_osakit-12.1.tar.gz", hash = "sha256:36ea6acf03483dc1e4344a0cce7250a9656f44277d12bc265fa86d4cbde01f23", size = 17102, upload-time = "2025-11-14T10:18:31.354Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/65/c6531ce0792d5035d87f054b0ccf22e453328fda2e68e11a7f70486da23a/pyobjc_framework_osakit-11.1-py2.py3-none-any.whl", hash = "sha256:1b0c0cc537ffb8a8365ef9a8b46f717a7cc2906414b6a3983777a6c0e4d53d5a", size = 4143, upload-time = "2025-06-14T20:52:57.555Z" }, + { url = "https://files.pythonhosted.org/packages/99/10/30a15d7b23e6fcfa63d41ca4c7356c39ff81300249de89c3ff28216a9790/pyobjc_framework_osakit-12.1-py2.py3-none-any.whl", hash = "sha256:c49165336856fd75113d2e264a98c6deb235f1bd033eae48f661d4d832d85e6b", size = 4162, upload-time = "2025-11-14T09:58:01.953Z" }, ] [[package]] name = "pyobjc-framework-oslog" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -3593,583 +3648,583 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/93/3feb7f6150b50165524750a424f5434448392123420cb4673db766c3f54a/pyobjc_framework_oslog-11.1.tar.gz", hash = "sha256:b2af409617e6b68fa1f1467c5a5679ebf59afd0cdc4b4528e1616059959a7979", size = 24689, upload-time = "2025-06-14T20:58:09.739Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/42/805c9b4ac6ad25deb4215989d8fc41533d01e07ffd23f31b65620bade546/pyobjc_framework_oslog-12.1.tar.gz", hash = "sha256:d0ec6f4e3d1689d5e4341bc1130c6f24cb4ad619939f6c14d11a7e80c0ac4553", size = 21193, upload-time = "2025-11-14T10:18:33.645Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/7a/2db26fc24e16c84312a0de432bab16ca586223fd6c5ba08e49c192ae95f6/pyobjc_framework_oslog-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5dab25ef1cde4237cd2957c1f61c2888968e924304f7b9d9699eceeb330e9817", size = 7793, upload-time = "2025-06-14T20:52:59.132Z" }, - { url = "https://files.pythonhosted.org/packages/40/da/fd3bd62899cd679743056aa2c28bc821c2688682a17ddde1a08d6d9d67fc/pyobjc_framework_oslog-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7ae29c31ce51c476d3a37ca303465dd8bdfa98df2f6f951cf14c497e984a1ba9", size = 7799, upload-time = "2025-06-14T20:52:59.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d5/8d37c2e733bd8a9a16546ceca07809d14401a059f8433cdc13579cd6a41a/pyobjc_framework_oslog-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8dd03386331fbb6b39df8941d99071da0bfeda7d10f6434d1daa1c69f0e7bb14", size = 7802, upload-time = "2025-11-14T09:58:05.619Z" }, + { url = "https://files.pythonhosted.org/packages/ee/60/0b742347d484068e9d6867cd95dedd1810c790b6aca45f6ef1d0f089f1f5/pyobjc_framework_oslog-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:072a41d36fcf780a070f13ac2569f8bafbb5ae4792fab4136b1a4d602dd9f5b4", size = 7813, upload-time = "2025-11-14T09:58:07.768Z" }, ] [[package]] name = "pyobjc-framework-passkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/05/063db500e7df70e39cbb5518a5a03c2acc06a1ca90b057061daea00129f3/pyobjc_framework_passkit-11.1.tar.gz", hash = "sha256:d2408b58960fca66607b483353c1ffbd751ef0bef394a1853ec414a34029566f", size = 144859, upload-time = "2025-06-14T20:58:10.761Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/d4/2afb59fb0f99eb2f03888850887e536f1ef64b303fd756283679471a5189/pyobjc_framework_passkit-12.1.tar.gz", hash = "sha256:d8c27c352e86a3549bf696504e6b25af5f2134b173d9dd60d66c6d3da53bb078", size = 53835, upload-time = "2025-11-14T10:18:37.906Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/18/343eb846e62704fbd64e178e0cbf75b121955c1973bf51ddd0871a42910a/pyobjc_framework_passkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:67b7b1ee9454919c073c2cba7bdba444a766a4e1dd15a5e906f4fa0c61525347", size = 13949, upload-time = "2025-06-14T20:53:04.98Z" }, - { url = "https://files.pythonhosted.org/packages/9d/ba/9e52213e0c0100079e4ef397cf4fd5ba8939fa4de19339755d1a373407a8/pyobjc_framework_passkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:779eaea4e1931cfda4c8701e1111307b14bf9067b359a319fc992b6848a86932", size = 13959, upload-time = "2025-06-14T20:53:05.694Z" }, + { url = "https://files.pythonhosted.org/packages/25/e6/dabd6b99bdadc50aa0306495d8d0afe4b9b3475c2bafdad182721401a724/pyobjc_framework_passkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb5c8f0fdc46db6b91c51ee1f41a2b81e9a482c96a0c91c096dcb78a012b740a", size = 14087, upload-time = "2025-11-14T09:58:18.991Z" }, + { url = "https://files.pythonhosted.org/packages/d8/dc/9cb27e8b7b00649af5e802815ffa8928bd8a619f2984a1bea7dabd28f741/pyobjc_framework_passkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e95a484ec529dbf1d44f5f7f1406502a77bda733511e117856e3dca9fa29c5c", size = 14102, upload-time = "2025-11-14T09:58:20.903Z" }, ] [[package]] name = "pyobjc-framework-pencilkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/75/d0/bbbe9dadcfc37e33a63d43b381a8d9a64eca27559df38efb74d524fa6260/pyobjc_framework_pencilkit-11.1.tar.gz", hash = "sha256:9c173e0fe70179feadc3558de113a8baad61b584fe70789b263af202bfa4c6be", size = 22570, upload-time = "2025-06-14T20:58:11.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/43/859068016bcbe7d80597d5c579de0b84b0da62c5c55cdf9cc940e9f9c0f8/pyobjc_framework_pencilkit-12.1.tar.gz", hash = "sha256:d404982d1f7a474369f3e7fea3fbd6290326143fa4138d64b6753005a6263dc4", size = 17664, upload-time = "2025-11-14T10:18:40.045Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/f6/59ffc3f26ea9cfda4d40409f9afc2a38e5c0c6a68a3a8c9202e8b98b03b1/pyobjc_framework_pencilkit-11.1-py2.py3-none-any.whl", hash = "sha256:b7824907bbcf28812f588dda730e78f662313baf40befd485c6f2fcb49018019", size = 4026, upload-time = "2025-06-14T20:53:10.449Z" }, + { url = "https://files.pythonhosted.org/packages/e8/26/daf47dcfced8f7326218dced5c68ed2f3b522ec113329218ce1305809535/pyobjc_framework_pencilkit-12.1-py2.py3-none-any.whl", hash = "sha256:33b88e5ed15724a12fd8bf27a68614b654ff739d227e81161298bc0d03acca4f", size = 4206, upload-time = "2025-11-14T09:58:30.814Z" }, ] [[package]] name = "pyobjc-framework-phase" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/d2/e9384b5b3fbcc79e8176cb39fcdd48b77f60cd1cb64f9ee4353762b037dc/pyobjc_framework_phase-11.1.tar.gz", hash = "sha256:a940d81ac5c393ae3da94144cf40af33932e0a9731244e2cfd5c9c8eb851e3fc", size = 58986, upload-time = "2025-06-14T20:58:12.196Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/51/3b25eaf7ca85f38ceef892fdf066b7faa0fec716f35ea928c6ffec6ae311/pyobjc_framework_phase-12.1.tar.gz", hash = "sha256:3a69005c572f6fd777276a835115eb8359a33673d4a87e754209f99583534475", size = 32730, upload-time = "2025-11-14T10:18:43.102Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/9e/55782f02b3bfb58f030b062176e8b0dba5f8fbd6e50d27a687f559c4179d/pyobjc_framework_phase-11.1-py2.py3-none-any.whl", hash = "sha256:cfa61f9c6c004161913946501538258aed48c448b886adbf9ed035957d93fa15", size = 6822, upload-time = "2025-06-14T20:53:11.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/9f/1ae45db731e8d6dd3e0b408c3accd0cf3236849e671f95c7c8cf95687240/pyobjc_framework_phase-12.1-py2.py3-none-any.whl", hash = "sha256:99a1c1efc6644f5312cce3693117d4e4482538f65ad08fe59e41e2579b67ab17", size = 6902, upload-time = "2025-11-14T09:58:32.436Z" }, ] [[package]] name = "pyobjc-framework-photos" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/78/b0/576652ecd05c26026ab4e75e0d81466edd570d060ce7df3d6bd812eb90d0/pyobjc_framework_photos-11.1.tar.gz", hash = "sha256:c8c3b25b14a2305047f72c7c081ff3655b3d051f7ed531476c03246798f8156d", size = 92569, upload-time = "2025-06-14T20:58:12.939Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b8/53/f8a3dc7f711034d2283e289cd966fb7486028ea132a24260290ff32d3525/pyobjc_framework_photos-12.1.tar.gz", hash = "sha256:adb68aaa29e186832d3c36a0b60b0592a834e24c5263e9d78c956b2b77dce563", size = 47034, upload-time = "2025-11-14T10:18:47.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/df/25/ec3b0234d20948816791399e580f6dd83c0d50a24219c954708f755742c4/pyobjc_framework_photos-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:959dfc82f20513366b85cd37d8541bb0a6ab4f3bfa2f8094e9758a5245032d67", size = 12198, upload-time = "2025-06-14T20:53:13.563Z" }, - { url = "https://files.pythonhosted.org/packages/fa/24/2400e6b738d3ed622c61a7cc6604eec769f398071a1eb6a16dfdf3a9ceea/pyobjc_framework_photos-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8dbfffd29cfa63a8396ede0030785c15a5bc36065d3dd98fc6176a59e7abb3d3", size = 12224, upload-time = "2025-06-14T20:53:14.793Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e0/8824f7cb167934a8aa1c088b7e6f1b5a9342b14694e76eda95fc736282b2/pyobjc_framework_photos-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f28db92602daac9d760067449fc9bf940594536e65ad542aec47d52b56f51959", size = 12319, upload-time = "2025-11-14T09:58:36.324Z" }, + { url = "https://files.pythonhosted.org/packages/13/38/e6f25aec46a1a9d0a310795606cc43f9823d41c3e152114b814b597835a8/pyobjc_framework_photos-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eda8a584a851506a1ebbb2ee8de2cb1ed9e3431e6a642ef6a9543e32117d17b9", size = 12358, upload-time = "2025-11-14T09:58:38.131Z" }, ] [[package]] name = "pyobjc-framework-photosui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/bb/e6de720efde2e9718677c95c6ae3f97047be437cda7a0f050cd1d6d2a434/pyobjc_framework_photosui-11.1.tar.gz", hash = "sha256:1c7ffab4860ce3e2b50feeed4f1d84488a9e38546db0bec09484d8d141c650df", size = 48443, upload-time = "2025-06-14T20:58:13.626Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/a5/14c538828ed1a420e047388aedc4a2d7d9292030d81bf6b1ced2ec27b6e9/pyobjc_framework_photosui-12.1.tar.gz", hash = "sha256:9141234bb9d17687f1e8b66303158eccdd45132341fbe5e892174910035f029a", size = 29886, upload-time = "2025-11-14T10:18:50.238Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/c1/3d67c2af53fe91feb6f64dbc501bbcfd5d325b7f0f0ffffd5d033334cb03/pyobjc_framework_photosui-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d93722aeb8c134569035fd7e6632d0247e1bcb18c3cc4e0a288664218f241b85", size = 11667, upload-time = "2025-06-14T20:53:20.464Z" }, - { url = "https://files.pythonhosted.org/packages/f8/c1/a5c84c1695e7a066743d63d10b219d94f3c07d706871682e42f7db389f5c/pyobjc_framework_photosui-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b2f278f569dfd596a32468351411518a651d12cb91e60620094e852c525a5f10", size = 11682, upload-time = "2025-06-14T20:53:21.162Z" }, + { url = "https://files.pythonhosted.org/packages/64/6c/d678767bbeafa932b91c88bc8bb3a586a1b404b5564b0dc791702eb376c3/pyobjc_framework_photosui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:02ca941187b2a2dcbbd4964d7b2a05de869653ed8484dc059a51cc70f520cd07", size = 11688, upload-time = "2025-11-14T09:58:51.84Z" }, + { url = "https://files.pythonhosted.org/packages/16/a2/b5afca8039b1a659a2a979bb1bdbdddfdf9b1d2724a2cc4633dca2573d5f/pyobjc_framework_photosui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:713e3bb25feb5ea891e67260c2c0769cab44a7f11b252023bfcf9f8c29dd1206", size = 11714, upload-time = "2025-11-14T09:58:53.674Z" }, ] [[package]] name = "pyobjc-framework-preferencepanes" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/ac/9324602daf9916308ebf1935b8a4b91c93b9ae993dcd0da731c0619c2836/pyobjc_framework_preferencepanes-11.1.tar.gz", hash = "sha256:6e4a55195ec9fc921e0eaad6b3038d0ab91f0bb2f39206aa6fccd24b14a0f1d8", size = 26212, upload-time = "2025-06-14T20:58:14.361Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/bc/e87df041d4f7f6b7721bf7996fa02aa0255939fb0fac0ecb294229765f92/pyobjc_framework_preferencepanes-12.1.tar.gz", hash = "sha256:b2a02f9049f136bdeca7642b3307637b190850e5853b74b5c372bc7d88ef9744", size = 24543, upload-time = "2025-11-14T10:18:53.259Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/51/75c7e32272241f706ce8168e04a32be02c4b0c244358330f730fc85695c3/pyobjc_framework_preferencepanes-11.1-py2.py3-none-any.whl", hash = "sha256:6ee5f5a7eb294e03ea3bac522ac4b69e6dc83ceceff627a0a2d289afe1e01ad9", size = 4786, upload-time = "2025-06-14T20:53:25.603Z" }, + { url = "https://files.pythonhosted.org/packages/36/7b/8ceec1ab0446224d685e243e2770c5a5c92285bcab0b9324dbe7a893ae5a/pyobjc_framework_preferencepanes-12.1-py2.py3-none-any.whl", hash = "sha256:1b3af9db9e0cfed8db28c260b2cf9a22c15fda5f0ff4c26157b17f99a0e29bbf", size = 4797, upload-time = "2025-11-14T09:59:03.998Z" }, ] [[package]] name = "pyobjc-framework-pushkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/f0/92d0eb26bf8af8ebf6b5b88df77e70b807de11f01af0162e0a429fcfb892/pyobjc_framework_pushkit-11.1.tar.gz", hash = "sha256:540769a4aadc3c9f08beca8496fe305372501eb28fdbca078db904a07b8e10f4", size = 21362, upload-time = "2025-06-14T20:58:15.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/45/de756b62709add6d0615f86e48291ee2bee40223e7dde7bbe68a952593f0/pyobjc_framework_pushkit-12.1.tar.gz", hash = "sha256:829a2fc8f4780e75fc2a41217290ee0ff92d4ade43c42def4d7e5af436d8ae82", size = 19465, upload-time = "2025-11-14T10:18:57.727Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/dc/415d6d7e3ed04d8b2f8dc6d458e7c6db3f503737b092d71b4856bf1607f7/pyobjc_framework_pushkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e2f08b667035df6b11a0a26f038610df1eebbedf9f3f111c241b5afaaf7c5fd", size = 8149, upload-time = "2025-06-14T20:53:28.096Z" }, - { url = "https://files.pythonhosted.org/packages/31/65/260014c5d13c54bd359221b0a890cbffdb99eecff3703f253cf648e45036/pyobjc_framework_pushkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:21993b7e9127b05575a954faa68e85301c6a4c04e34e38aff9050f67a05c562a", size = 8174, upload-time = "2025-06-14T20:53:28.805Z" }, + { url = "https://files.pythonhosted.org/packages/a1/b2/d92045e0d4399feda83ee56a9fd685b5c5c175f7ac8423e2cd9b3d52a9da/pyobjc_framework_pushkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:03f41be8b27d06302ea487a6b250aaf811917a0e7d648cd4043fac759d027210", size = 8158, upload-time = "2025-11-14T09:59:09.593Z" }, + { url = "https://files.pythonhosted.org/packages/b9/01/74cf1dd0764c590de05dc1e87d168031e424f834721940b7bb02c67fe821/pyobjc_framework_pushkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7bdf472a55ac65154e03f54ae0bcad64c4cf45e9b1acba62f15107f2bc994d69", size = 8177, upload-time = "2025-11-14T09:59:11.155Z" }, ] [[package]] name = "pyobjc-framework-quartz" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/ac/6308fec6c9ffeda9942fef72724f4094c6df4933560f512e63eac37ebd30/pyobjc_framework_quartz-11.1.tar.gz", hash = "sha256:a57f35ccfc22ad48c87c5932818e583777ff7276605fef6afad0ac0741169f75", size = 3953275, upload-time = "2025-06-14T20:58:17.924Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/cb/38172fdb350b3f47e18d87c5760e50f4efbb4da6308182b5e1310ff0cde4/pyobjc_framework_quartz-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d501fe95ef15d8acf587cb7dc4ab4be3c5a84e2252017da8dbb7df1bbe7a72a", size = 215565, upload-time = "2025-06-14T20:53:35.262Z" }, - { url = "https://files.pythonhosted.org/packages/9b/37/ee6e0bdd31b3b277fec00e5ee84d30eb1b5b8b0e025095e24ddc561697d0/pyobjc_framework_quartz-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9ac806067541917d6119b98d90390a6944e7d9bd737f5c0a79884202327c9204", size = 216410, upload-time = "2025-06-14T20:53:36.346Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ef/dcd22b743e38b3c430fce4788176c2c5afa8bfb01085b8143b02d1e75201/pyobjc_framework_quartz-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19f99ac49a0b15dd892e155644fe80242d741411a9ed9c119b18b7466048625a", size = 217795, upload-time = "2025-11-14T09:59:46.922Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, ] [[package]] name = "pyobjc-framework-quicklookthumbnailing" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/98/6e87f360c2dfc870ae7870b8a25fdea8ddf1d62092c755686cebe7ec1a07/pyobjc_framework_quicklookthumbnailing-11.1.tar.gz", hash = "sha256:1614dc108c1d45bbf899ea84b8691288a5b1d25f2d6f0c57dfffa962b7a478c3", size = 16527, upload-time = "2025-06-14T20:58:20.811Z" } +sdist = { url = "https://files.pythonhosted.org/packages/97/1a/b90539500e9a27c2049c388d85a824fc0704009b11e33b05009f52a6dc67/pyobjc_framework_quicklookthumbnailing-12.1.tar.gz", hash = "sha256:4f7e09e873e9bda236dce6e2f238cab571baeb75eca2e0bc0961d5fcd85f3c8f", size = 14790, upload-time = "2025-11-14T10:21:26.442Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/65/4a/ddc35bdcd44278f22df2154a52025915dba6c80d94e458d92e9e7430d1e4/pyobjc_framework_quicklookthumbnailing-11.1-py2.py3-none-any.whl", hash = "sha256:4d1863c6c83c2a199c1dbe704b4f8b71287168f4090ed218d37dc59277f0d9c9", size = 4219, upload-time = "2025-06-14T20:53:43.198Z" }, + { url = "https://files.pythonhosted.org/packages/1e/22/7bd07b5b44bf8540514a9f24bc46da68812c1fd6c63bb2d3496e5ea44bf0/pyobjc_framework_quicklookthumbnailing-12.1-py2.py3-none-any.whl", hash = "sha256:5efe50b0318188b3a4147681788b47fce64709f6fe0e1b5d020e408ef40ab08e", size = 4234, upload-time = "2025-11-14T10:01:02.209Z" }, ] [[package]] name = "pyobjc-framework-replaykit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/4f/014e95f0fd6842d7fcc3d443feb6ee65ac69d06c66ffa9327fc33ceb7c27/pyobjc_framework_replaykit-11.1.tar.gz", hash = "sha256:6919baa123a6d8aad769769fcff87369e13ee7bae11b955a8185a406a651061b", size = 26132, upload-time = "2025-06-14T20:58:21.853Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/f8/b92af879734d91c1726227e7a03b9e68ab8d9d2bb1716d1a5c29254087f2/pyobjc_framework_replaykit-12.1.tar.gz", hash = "sha256:95801fd35c329d7302b2541f2754e6574bf36547ab869fbbf41e408dfa07268a", size = 23312, upload-time = "2025-11-14T10:21:29.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/97/2b4fbd52c6727977c0fdbde2b4a15226a9beb836248c289781e4129394e4/pyobjc_framework_replaykit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d88c3867349865d8a3a06ea064f15aed7e5be20d22882ac8a647d9b6959594e", size = 10066, upload-time = "2025-06-14T20:53:45.555Z" }, - { url = "https://files.pythonhosted.org/packages/b9/73/846cebb36fc279df18f10dc3a27cba8fe2e47e95350a3651147e4d454719/pyobjc_framework_replaykit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:22c6d09be9a6e758426d723a6c3658ad6bbb66f97ba9a1909bfcf29a91d99921", size = 10087, upload-time = "2025-06-14T20:53:46.242Z" }, + { url = "https://files.pythonhosted.org/packages/10/b1/fab264c6a82a78cd050a773c61dec397c5df7e7969eba3c57e17c8964ea3/pyobjc_framework_replaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3a2f9da6939d7695fa40de9c560c20948d31b0cc2f892fdd611fc566a6b83606", size = 10090, upload-time = "2025-11-14T10:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fc/c68d2111b2655148d88574959d3d8b21d3a003573013301d4d2a7254c1af/pyobjc_framework_replaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b0528c2a6188440fdc2017f0924c0a0f15d0a2f6aa295f1d1c2d6b3894c22f1d", size = 10120, upload-time = "2025-11-14T10:01:08.397Z" }, ] [[package]] name = "pyobjc-framework-safariservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/fc/c47d2abf3c1de6db21d685cace76a0931d594aa369e3d090260295273f6e/pyobjc_framework_safariservices-11.1.tar.gz", hash = "sha256:39a17df1a8e1c339457f3acbff0dc0eae4681d158f9d783a11995cf484aa9cd0", size = 34905, upload-time = "2025-06-14T20:58:22.492Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3e/4b/8f896bafbdbfa180a5ba1e21a6f5dc63150c09cba69d85f68708e02866ae/pyobjc_framework_safariservices-12.1.tar.gz", hash = "sha256:6a56f71c1e692bca1f48fe7c40e4c5a41e148b4e3c6cfb185fd80a4d4a951897", size = 25165, upload-time = "2025-11-14T10:21:32.041Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/aa/0c9f3456a57dbee711210a0ac3fe58aff9bf881ab7c65727b885193eb8af/pyobjc_framework_safariservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a441a2e99f7d6475bea00c3d53de924143b8f90052be226aee16f1f6d9cfdc8c", size = 7262, upload-time = "2025-06-14T20:53:52.057Z" }, - { url = "https://files.pythonhosted.org/packages/d7/13/9636e9d3dc362daaaa025b2aa4e28606a1e197dfc6506d3a246be8315f8a/pyobjc_framework_safariservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c92eb9e35f98368ea1bfaa8cdd41138ca8b004ea5a85833390a44e5626ca5061", size = 7275, upload-time = "2025-06-14T20:53:53.075Z" }, + { url = "https://files.pythonhosted.org/packages/f1/bb/da1059bfad021c417e090058c0a155419b735b4891a7eedc03177b376012/pyobjc_framework_safariservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae709cf7a72ac7b95d2f131349f852d5d7a1729a8d760ea3308883f8269a4c37", size = 7281, upload-time = "2025-11-14T10:01:19.294Z" }, + { url = "https://files.pythonhosted.org/packages/67/3a/8c525562fd782c88bc44e8c07fc2c073919f98dead08fffd50f280ef1afa/pyobjc_framework_safariservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b475abc82504fc1c0801096a639562d6a6d37370193e8e4a406de9199a7cea13", size = 7281, upload-time = "2025-11-14T10:01:21.238Z" }, ] [[package]] name = "pyobjc-framework-safetykit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/cc/f6aa5d6f45179bd084416511be4e5b0dd0752cb76daa93869e6edb806096/pyobjc_framework_safetykit-11.1.tar.gz", hash = "sha256:c6b44e0cf69e27584ac3ef3d8b771d19a7c2ccd9c6de4138d091358e036322d4", size = 21240, upload-time = "2025-06-14T20:58:23.132Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/bf/ad6bf60ceb61614c9c9f5758190971e9b90c45b1c7a244e45db64138b6c2/pyobjc_framework_safetykit-12.1.tar.gz", hash = "sha256:0cd4850659fb9b5632fd8ad21f2de6863e8303ff0d51c5cc9c0034aac5db08d8", size = 20086, upload-time = "2025-11-14T10:21:34.212Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/ad/1e9c661510cc4cd96f2beffc7ba39af36064c742e265303c689e85aaa0ad/pyobjc_framework_safetykit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3333e8e53a1e8c8133936684813a2254e5d1b4fe313333a3d0273e31b9158cf7", size = 8513, upload-time = "2025-06-14T20:53:58.413Z" }, - { url = "https://files.pythonhosted.org/packages/9c/8f/6f4c833e31526a81faef9bf19695b332ba8d2fa53d92640abd6fb3ac1d78/pyobjc_framework_safetykit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b76fccdb970d3d751a540c47712e9110afac9abea952cb9b7bc0d5867db896e3", size = 8523, upload-time = "2025-06-14T20:53:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/94/68/77f17fba082de7c65176e0d74aacbce5c9c9066d6d6edcde5a537c8c140a/pyobjc_framework_safetykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c63bcd5d571bba149e28c49c8db06073e54e073b08589e94b850b39a43e52b0", size = 8539, upload-time = "2025-11-14T10:01:31.201Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0c/08a20fb7516405186c0fe7299530edd4aa22c24f73290198312447f26c8c/pyobjc_framework_safetykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e4977f7069a23252053d1a42b1a053aefc19b85c960a5214b05daf3c037a6f16", size = 8550, upload-time = "2025-11-14T10:01:32.885Z" }, ] [[package]] name = "pyobjc-framework-scenekit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/64/cf/2d89777120d2812e7ee53c703bf6fc8968606c29ddc1351bc63f0a2a5692/pyobjc_framework_scenekit-11.1.tar.gz", hash = "sha256:82941f1e5040114d6e2c9fd35507244e102ef561c637686091b71a7ad0f31306", size = 214118, upload-time = "2025-06-14T20:58:24.003Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/8c/1f4005cf0cb68f84dd98b93bbc0974ee7851bb33d976791c85e042dc2278/pyobjc_framework_scenekit-12.1.tar.gz", hash = "sha256:1bd5b866f31fd829f26feac52e807ed942254fd248115c7c742cfad41d949426", size = 101212, upload-time = "2025-11-14T10:21:41.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/46/d011b5a88e45d78265f5df144759ff57e50d361d44c9adb68c2fb58b276d/pyobjc_framework_scenekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e777dacb563946ad0c2351e6cfe3f16b8587a65772ec0654e2be9f75764d234", size = 33490, upload-time = "2025-06-14T20:54:04.845Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f9/bdcd8a4bc6c387ef07f3e2190cea6a03d4f7ed761784f492b01323e8d900/pyobjc_framework_scenekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c803d95b30c4ce49f46ff7174806f5eb84e4c3a152f8f580c5da0313c5c67041", size = 33558, upload-time = "2025-06-14T20:54:05.59Z" }, + { url = "https://files.pythonhosted.org/packages/a0/7f/eda261013dc41cc70f3157d1a750712dc29b64fc05be84232006b5cd57e5/pyobjc_framework_scenekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:01bf1336a7a8bdc96fabde8f3506aa7a7d1905e20a5c46030a57daf0ce2cbd16", size = 33542, upload-time = "2025-11-14T10:01:47.613Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f1/4986bd96e0ba0f60bff482a6b135b9d6db65d56578d535751f18f88190f0/pyobjc_framework_scenekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:40aea10098893f0b06191f1e79d7b25e12e36a9265549d324238bdb25c7e6df0", size = 33597, upload-time = "2025-11-14T10:01:51.297Z" }, ] [[package]] name = "pyobjc-framework-screencapturekit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/32/a5/9bd1f1ad1773a1304ccde934ff39e0f0a0b0034441bf89166aea649606de/pyobjc_framework_screencapturekit-11.1.tar.gz", hash = "sha256:11443781a30ed446f2d892c9e6642ca4897eb45f1a1411136ca584997fa739e0", size = 53548, upload-time = "2025-06-14T20:58:24.837Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/7f/73458db1361d2cb408f43821a1e3819318a0f81885f833d78d93bdc698e0/pyobjc_framework_screencapturekit-12.1.tar.gz", hash = "sha256:50992c6128b35ab45d9e336f0993ddd112f58b8c8c8f0892a9cb42d61bd1f4c9", size = 32573, upload-time = "2025-11-14T10:21:44.497Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/e0/fd1957e962c4a1624171dbbda4e425615848a7bcc9b45a524018dc449874/pyobjc_framework_screencapturekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7203108d28d7373501c455cd4a8bbcd2eb7849906dbc7859ac17a350b141553c", size = 11280, upload-time = "2025-06-14T20:54:11.699Z" }, - { url = "https://files.pythonhosted.org/packages/98/37/840f306dcf01dd2bd092ae8dcf371a3bad3a0f88f0780d0840f899a8c047/pyobjc_framework_screencapturekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:641fa7834f54558859209e174c83551d5fa239ca6943ace52665f7d45e562ff2", size = 11308, upload-time = "2025-06-14T20:54:12.382Z" }, + { url = "https://files.pythonhosted.org/packages/79/92/fe66408f4bd74f6b6da75977d534a7091efe988301d13da4f009bf54ab71/pyobjc_framework_screencapturekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae412d397eedf189e763defe3497fcb8dffa5e0b54f62390cb33bf9b1cfb864a", size = 11473, upload-time = "2025-11-14T10:02:09.177Z" }, + { url = "https://files.pythonhosted.org/packages/05/a8/533acdbf26e0a908ff640d3a445481f3c948682ca887be6711b5fcf82682/pyobjc_framework_screencapturekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:27df138ce2dfa9d4aae5106d4877e9ed694b5a174643c058f1c48678ffc7001a", size = 11504, upload-time = "2025-11-14T10:02:11.36Z" }, ] [[package]] name = "pyobjc-framework-screensaver" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/f6/f2d48583b29fc67b64aa1f415fd51faf003d045cdb1f3acab039b9a3f59f/pyobjc_framework_screensaver-11.1.tar.gz", hash = "sha256:d5fbc9dc076cc574ead183d521840b56be0c160415e43cb8e01cfddd6d6372c2", size = 24302, upload-time = "2025-06-14T20:58:25.52Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/99/7cfbce880cea61253a44eed594dce66c2b2fbf29e37eaedcd40cffa949e9/pyobjc_framework_screensaver-12.1.tar.gz", hash = "sha256:c4ca111317c5a3883b7eace0a9e7dd72bc6ffaa2ca954bdec918c3ab7c65c96f", size = 22229, upload-time = "2025-11-14T10:21:47.299Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/8c/2236e5796f329a92ce7664036da91e91d63d86217972dc2939261ce88dde/pyobjc_framework_screensaver-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8b959761fddf06d9fb3fed6cd0cea6009d60473317e11490f66dcf0444011d5f", size = 8466, upload-time = "2025-06-14T20:54:18.329Z" }, - { url = "https://files.pythonhosted.org/packages/76/f9/4ae982c7a1387b64954130b72187e140329b73c647acb4d6b6eb3c033d8d/pyobjc_framework_screensaver-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f2d22293cf9d715e4692267a1678096afd6793c0519d9417cf77c8a6c706a543", size = 8402, upload-time = "2025-06-14T20:54:19.044Z" }, + { url = "https://files.pythonhosted.org/packages/2d/8d/87ca0fa0a9eda3097a0f4f2eef1544bf1d984697939fbef7cda7495fddb9/pyobjc_framework_screensaver-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bd10809005fbe0d68fe651f32a393ce059e90da38e74b6b3cd055ed5b23eaa9", size = 8480, upload-time = "2025-11-14T10:02:22.798Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/2481711f2e9557b90bac74fa8bf821162cf7b65835732ae560fd52e9037e/pyobjc_framework_screensaver-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3c90c2299eac6d01add81427ae2f90d7724f15d676261e838d7a7750f812322", size = 8422, upload-time = "2025-11-14T10:02:24.49Z" }, ] [[package]] name = "pyobjc-framework-screentime" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/82/33/ebed70a1de134de936bb9a12d5c76f24e1e335ff4964f9bb0af9b09607f1/pyobjc_framework_screentime-11.1.tar.gz", hash = "sha256:9bb8269456bbb674e1421182efe49f9168ceefd4e7c497047c7bf63e2f510a34", size = 14875, upload-time = "2025-06-14T20:58:26.179Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/11/ba18f905321895715dac3cae2071c2789745ae13605b283b8114b41e0459/pyobjc_framework_screentime-12.1.tar.gz", hash = "sha256:583de46b365543bbbcf27cd70eedd375d397441d64a2cf43c65286fd9c91af55", size = 13413, upload-time = "2025-11-14T10:21:49.17Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/20/783eccea7206ceeda42a09a4614e3da92889e4c54abe9dec2e5e53576e1a/pyobjc_framework_screentime-11.1-py2.py3-none-any.whl", hash = "sha256:50a4e4ab33d6643a52616e990aa1c697d5e3e8f9f9bdab8d631e6d42d8287b4f", size = 3949, upload-time = "2025-06-14T20:54:26.916Z" }, + { url = "https://files.pythonhosted.org/packages/27/06/904174de6170e11b53673cc5844e5f13394eeeed486e0bcdf5288c1b0853/pyobjc_framework_screentime-12.1-py2.py3-none-any.whl", hash = "sha256:d34a068ec8ba2704987fcd05c37c9a9392de61d92933e6e71c8e4eaa4dfce029", size = 3963, upload-time = "2025-11-14T10:02:32.577Z" }, ] [[package]] name = "pyobjc-framework-scriptingbridge" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8e/c1/5b1dd01ff173df4c6676f97405113458918819cb2064c1735b61948e8800/pyobjc_framework_scriptingbridge-11.1.tar.gz", hash = "sha256:604445c759210a35d86d3e0dfcde0aac8e5e3e9d9e35759e0723952138843699", size = 23155, upload-time = "2025-06-14T20:58:26.812Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/cb/adc0a09e8c4755c2281bd12803a87f36e0832a8fc853a2d663433dbb72ce/pyobjc_framework_scriptingbridge-12.1.tar.gz", hash = "sha256:0e90f866a7e6a8aeaf723d04c826657dd528c8c1b91e7a605f8bb947c74ad082", size = 20339, upload-time = "2025-11-14T10:21:51.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/76/e173ca0b121693bdc6ac5797b30fd5771f31a682d15fd46402dc6f9ca3d1/pyobjc_framework_scriptingbridge-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d6020c69c14872105852ff99aab7cd2b2671e61ded3faefb071dc40a8916c527", size = 8301, upload-time = "2025-06-14T20:54:29.082Z" }, - { url = "https://files.pythonhosted.org/packages/c1/64/31849063e3e81b4c312ce838dc98f0409c09eb33bc79dbb5261cb994a4c4/pyobjc_framework_scriptingbridge-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:226ba12d9cbd504411b702323b0507dd1690e81b4ce657c5f0d8b998c46cf374", size = 8323, upload-time = "2025-06-14T20:54:30.105Z" }, + { url = "https://files.pythonhosted.org/packages/42/de/0943ee8d7f1a7d8467df6e2ea017a6d5041caff2fb0283f37fea4c4ce370/pyobjc_framework_scriptingbridge-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e6e37e69760d6ac9d813decf135d107760d33e1cdf7335016522235607f6f31b", size = 8335, upload-time = "2025-11-14T10:02:36.654Z" }, + { url = "https://files.pythonhosted.org/packages/51/46/e0b07d2b3ff9effb8b1179a6cc681a953d3dfbf0eb8b1d6a0e54cef2e922/pyobjc_framework_scriptingbridge-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8083cd68c559c55a3787b2e74fc983c8665e5078571475aaeabf4f34add36b62", size = 8356, upload-time = "2025-11-14T10:02:38.559Z" }, ] [[package]] name = "pyobjc-framework-searchkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/20/61b73fddae0d1a94f5defb0cd4b4f391ec03bfcce7ebe830cb827d5e208a/pyobjc_framework_searchkit-11.1.tar.gz", hash = "sha256:13a194eefcf1359ce9972cd92f2aadddf103f3efb1b18fd578ba5367dff3c10c", size = 30918, upload-time = "2025-06-14T20:58:27.447Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/60/a38523198430e14fdef21ebe62a93c43aedd08f1f3a07ea3d96d9997db5d/pyobjc_framework_searchkit-12.1.tar.gz", hash = "sha256:ddd94131dabbbc2d7c3f17db3da87c1a712c431310eef16f07187771e7e85226", size = 30942, upload-time = "2025-11-14T10:21:55.483Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/a118d275a9132c8f5adcd353e4d9e844777068e33d51b195f46671161a7f/pyobjc_framework_searchkit-11.1-py2.py3-none-any.whl", hash = "sha256:9c9d6ca71cef637ccc3627225fb924a460b3d0618ed79bb0b3c12fcbe9270323", size = 3714, upload-time = "2025-06-14T20:54:34.329Z" }, + { url = "https://files.pythonhosted.org/packages/72/46/4f9cd3011f47b43b21b2924ab3770303c3f0a4d16f05550d38c5fcb42e78/pyobjc_framework_searchkit-12.1-py2.py3-none-any.whl", hash = "sha256:844ce62b7296b19da8db7dedd539d07f7b3fb3bb8b029c261f7bcf0e01a97758", size = 3733, upload-time = "2025-11-14T10:02:47.026Z" }, ] [[package]] name = "pyobjc-framework-security" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ee/6f/ba50ed2d9c1192c67590a7cfefa44fc5f85c776d1e25beb224dec32081f6/pyobjc_framework_security-11.1.tar.gz", hash = "sha256:dabcee6987c6bae575e2d1ef0fcbe437678c4f49f1c25a4b131a5e960f31a2da", size = 302291, upload-time = "2025-06-14T20:58:28.506Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/ae/1679770d9a1cf5f2fe532a3567a51f0c5ee09054ae2c4003ae8f3e11eea4/pyobjc_framework_security-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d361231697486e97cfdafadf56709190696ab26a6a086dbba5f170e042e13daa", size = 41202, upload-time = "2025-06-14T20:54:36.255Z" }, - { url = "https://files.pythonhosted.org/packages/35/16/7fc52ab1364ada5885bf9b4c9ea9da3ad892b847c9b86aa59e086b16fc11/pyobjc_framework_security-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eb4ba6d8b221b9ad5d010e026247e8aa26ee43dcaf327e848340ed227d22d7e", size = 41222, upload-time = "2025-06-14T20:54:37.032Z" }, + { url = "https://files.pythonhosted.org/packages/5e/3d/8d3a39cd292d7c76ab76233498189bc7170fc80f573b415308464f68c7ee/pyobjc_framework_security-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b2d8819f0fb7b619ec7627a0d8c1cac1a57c5143579ce8ac21548165680684b", size = 41287, upload-time = "2025-11-14T10:02:54.491Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" }, ] [[package]] name = "pyobjc-framework-securityfoundation" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5c/d4/19591dd0938a45b6d8711ef9ae5375b87c37a55b45d79c52d6f83a8d991f/pyobjc_framework_securityfoundation-11.1.tar.gz", hash = "sha256:b3c4cf70735a93e9df40f3a14478143959c415778f27be8c0dc9ae0c5b696b92", size = 13270, upload-time = "2025-06-14T20:58:29.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/d5/c2b77e83c1585ba43e5f00c917273ba4bf7ed548c1b691f6766eb0418d52/pyobjc_framework_securityfoundation-12.1.tar.gz", hash = "sha256:1f39f4b3db6e3bd3a420aaf4923228b88e48c90692cf3612b0f6f1573302a75d", size = 12669, upload-time = "2025-11-14T10:22:09.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/ab/23db6b1c09810d6bcc4eab96e62487fb4284b57e447eabe6c001cb41e36d/pyobjc_framework_securityfoundation-11.1-py2.py3-none-any.whl", hash = "sha256:25f2cf10f80c122f462e9d4d43efe9fd697299c194e0c357e76650e234e6d286", size = 3772, upload-time = "2025-06-14T20:54:41.732Z" }, + { url = "https://files.pythonhosted.org/packages/93/1e/349fb71a413b37b1b41e712c7ca180df82144478f8a9a59497d66d0f2ea2/pyobjc_framework_securityfoundation-12.1-py2.py3-none-any.whl", hash = "sha256:579cf23e63434226f78ffe0afb8426e971009588e4ad812c478d47dfd558201c", size = 3792, upload-time = "2025-11-14T10:03:14.459Z" }, ] [[package]] name = "pyobjc-framework-securityinterface" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/be/c846651c3e7f38a637c40ae1bcda9f14237c2395637c3a188df4f733c727/pyobjc_framework_securityinterface-11.1.tar.gz", hash = "sha256:e7aa6373e525f3ae05d71276e821a6348c53fec9f812b90eec1dbadfcb507bc9", size = 37648, upload-time = "2025-06-14T20:58:29.932Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/64/bf5b5d82655112a2314422ee649f1e1e73d4381afa87e1651ce7e8444694/pyobjc_framework_securityinterface-12.1.tar.gz", hash = "sha256:deef11ad03be8d9ff77db6e7ac40f6b641ee2d72eaafcf91040537942472e88b", size = 25552, upload-time = "2025-11-14T10:22:12.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/ec/8073f37f56870efb039970f1cc4536f279c5d476abab2e8654129789277f/pyobjc_framework_securityinterface-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e884620b22918d462764f0665f6ac0cbb8142bb160fcd27c4f4357f81da73b7", size = 10769, upload-time = "2025-06-14T20:54:43.344Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/48b8027a24f3f8924f5be5f97217961b4ed23e6be49b3bd94ee8a0d56a1e/pyobjc_framework_securityinterface-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:26056441b325029da06a7c7b8dd1a0c9a4ad7d980596c1b04d132a502b4cacc0", size = 10837, upload-time = "2025-06-14T20:54:44.052Z" }, + { url = "https://files.pythonhosted.org/packages/37/1c/a01fd56765792d1614eb5e8dc0a7d5467564be6a2056b417c9ec7efc648f/pyobjc_framework_securityinterface-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed599be750122376392e95c2407d57bd94644e8320ddef1d67660e16e96b0d06", size = 10719, upload-time = "2025-11-14T10:03:18.353Z" }, + { url = "https://files.pythonhosted.org/packages/59/3e/17889a6de03dc813606bb97887dc2c4c2d4e7c8f266bc439548bae756e90/pyobjc_framework_securityinterface-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cb5e79a73ea17663ebd29e350401162d93e42343da7d96c77efb38ae64ff01f", size = 10783, upload-time = "2025-11-14T10:03:20.202Z" }, ] [[package]] name = "pyobjc-framework-securityui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/5b/3b5585d56e0bcaba82e0661224bbc7aaf29fba6b10498971dbe08b2b490a/pyobjc_framework_securityui-11.1.tar.gz", hash = "sha256:e80c93e8a56bf89e4c0333047b9f8219752dd6de290681e9e2e2b2e26d69e92d", size = 12179, upload-time = "2025-06-14T20:58:30.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/83/3f/d870305f5dec58cd02966ca06ac29b69fb045d8b46dfb64e2da31f295345/pyobjc_framework_securityui-12.1.tar.gz", hash = "sha256:f1435fed85edc57533c334a4efc8032170424b759da184cb7a7a950ceea0e0b6", size = 12184, upload-time = "2025-11-14T10:22:14.323Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/a4/c9fcc42065b6aed73b14b9650c1dc0a4af26a30d418cbc1bab33621b461c/pyobjc_framework_securityui-11.1-py2.py3-none-any.whl", hash = "sha256:3cdb101b03459fcf8e4064b90021d06761003f669181e02f43ff585e6ba2403d", size = 3581, upload-time = "2025-06-14T20:54:49.474Z" }, + { url = "https://files.pythonhosted.org/packages/36/7f/eff9ffdd34511cc95a60e5bd62f1cfbcbcec1a5012ef1168161506628c87/pyobjc_framework_securityui-12.1-py2.py3-none-any.whl", hash = "sha256:3e988b83c9a2bb0393207eaa030fc023a8708a975ac5b8ea0508cdafc2b60705", size = 3594, upload-time = "2025-11-14T10:03:29.628Z" }, ] [[package]] name = "pyobjc-framework-sensitivecontentanalysis" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/7b/e28f6b30d99e9d464427a07ada82b33cd3292f310bf478a1824051d066b9/pyobjc_framework_sensitivecontentanalysis-11.1.tar.gz", hash = "sha256:5b310515c7386f7afaf13e4632d7d9590688182bb7b563f8026c304bdf317308", size = 12796, upload-time = "2025-06-14T20:58:31.488Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/17bf31753e14cb4d64fffaaba2377453c4977c2c5d3cf2ff0a3db30026c7/pyobjc_framework_sensitivecontentanalysis-12.1.tar.gz", hash = "sha256:2c615ac10e93eb547b32b214cd45092056bee0e79696426fd09978dc3e670f25", size = 13745, upload-time = "2025-11-14T10:22:16.447Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3c/63/76a939ecac74ca079702165330c692ad2c05ff9b2b446a72ddc8cdc63bb9/pyobjc_framework_sensitivecontentanalysis-11.1-py2.py3-none-any.whl", hash = "sha256:dbb78f5917f986a63878bb91263bceba28bd86fc381bad9461cf391646db369f", size = 3852, upload-time = "2025-06-14T20:54:50.75Z" }, + { url = "https://files.pythonhosted.org/packages/95/23/c99568a0d4e38bd8337d52e4ae25a0b0bd540577f2e06f3430c951d73209/pyobjc_framework_sensitivecontentanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:faf19d32d4599ac2b18fb1ccdc3e33b2b242bdf34c02e69978bd62d3643ad068", size = 4230, upload-time = "2025-11-14T10:03:31.26Z" }, ] [[package]] name = "pyobjc-framework-servicemanagement" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/c6/32e11599d9d232311607b79eb2d1d21c52eaaf001599ea85f8771a933fa2/pyobjc_framework_servicemanagement-11.1.tar.gz", hash = "sha256:90a07164da49338480e0e135b445acc6ae7c08549a2037d1e512d2605fedd80a", size = 16645, upload-time = "2025-06-14T20:58:32.062Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/d0/b26c83ae96ab55013df5fedf89337d4d62311b56ce3f520fc7597d223d82/pyobjc_framework_servicemanagement-12.1.tar.gz", hash = "sha256:08120981749a698033a1d7a6ab99dbbe412c5c0d40f2b4154014b52113511c1d", size = 14585, upload-time = "2025-11-14T10:22:18.735Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/f1/222462f5afcb6cb3c1fc9e6092dfcffcc7eb9db8bd2cef8c1743a22fbe95/pyobjc_framework_servicemanagement-11.1-py2.py3-none-any.whl", hash = "sha256:104f56557342a05ad68cd0c9daf63b7f4678957fe1f919f03a872f1607a50710", size = 5338, upload-time = "2025-06-14T20:54:51.614Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5d/1009c32189f9cb26da0124b4a60640ed26dd8ad453810594f0cbfab0ff70/pyobjc_framework_servicemanagement-12.1-py2.py3-none-any.whl", hash = "sha256:9a2941f16eeb71e55e1cd94f50197f91520778c7f48ad896761f5e78725cc08f", size = 5357, upload-time = "2025-11-14T10:03:32.928Z" }, ] [[package]] name = "pyobjc-framework-sharedwithyou" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-sharedwithyoucore", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fe/a5/e299fbd0c13d4fac9356459f21372f6eef4279d0fbc99ba316d88dfbbfb4/pyobjc_framework_sharedwithyou-11.1.tar.gz", hash = "sha256:ece3a28a3083d0bcad0ac95b01f0eb699b9d2d0c02c61305bfd402678753ff6e", size = 34216, upload-time = "2025-06-14T20:58:32.75Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/8b/8ab209a143c11575a857e2111acc5427fb4986b84708b21324cbcbf5591b/pyobjc_framework_sharedwithyou-12.1.tar.gz", hash = "sha256:167d84794a48f408ee51f885210c616fda1ec4bff3dd8617a4b5547f61b05caf", size = 24791, upload-time = "2025-11-14T10:22:21.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/23/7caefaddc58702da830d1cc4eb3c45ae82dcd605ea362126ab47ebd54f7d/pyobjc_framework_sharedwithyou-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce1c37d5f8cf5b0fe8a261e4e7256da677162fd5aa7b724e83532cdfe58d8f94", size = 8725, upload-time = "2025-06-14T20:54:53.179Z" }, - { url = "https://files.pythonhosted.org/packages/57/44/211e1f18676e85d3656671fc0c954ced2cd007e55f1b0b6b2e4d0a0852eb/pyobjc_framework_sharedwithyou-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:99e1749187ae370be7b9c55dd076d1b8143f0d8db3e83f52540586f32e7abb33", size = 8740, upload-time = "2025-06-14T20:54:53.879Z" }, + { url = "https://files.pythonhosted.org/packages/19/69/3ad9b344808c5619adc253b665f8677829dfb978888227e07233d120cfab/pyobjc_framework_sharedwithyou-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:359c03096a6988371ea89921806bb81483ea509c9aa7114f9cd20efd511b3576", size = 8739, upload-time = "2025-11-14T10:03:36.48Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ee/e5113ce985a480d13a0fa3d41a242c8068dc09b3c13210557cf5cc6a544a/pyobjc_framework_sharedwithyou-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a99a6ebc6b6de7bc8663b1f07332fab9560b984a57ce344dc5703f25258f258d", size = 8763, upload-time = "2025-11-14T10:03:38.467Z" }, ] [[package]] name = "pyobjc-framework-sharedwithyoucore" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/79/a3/1ca6ff1b785772c7c5a38a7c017c6f971b1eda638d6a0aab3bbde18ac086/pyobjc_framework_sharedwithyoucore-11.1.tar.gz", hash = "sha256:790050d25f47bda662a9f008b17ca640ac2460f2559a56b17995e53f2f44ed73", size = 29459, upload-time = "2025-06-14T20:58:33.422Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/ef/84059c5774fd5435551ab7ab40b51271cfb9997b0d21f491c6b429fe57a8/pyobjc_framework_sharedwithyoucore-12.1.tar.gz", hash = "sha256:0813149eeb755d718b146ec9365eb4ca3262b6af9ff9ba7db2f7b6f4fd104518", size = 22350, upload-time = "2025-11-14T10:22:23.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/df/08cfa01dcdb4655514b7a10eb7c40da2bdb7866078c761d6ed26c9f464f7/pyobjc_framework_sharedwithyoucore-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a7fe5ffcc65093ef7cd25903769ad557c3d3c5a59155a31f3f934cf555101e6", size = 8489, upload-time = "2025-06-14T20:54:59.631Z" }, - { url = "https://files.pythonhosted.org/packages/b9/70/3b2e13fcf393aa434b1cf5c29c6aaf65ee5b8361254df3a920ed436bb5e4/pyobjc_framework_sharedwithyoucore-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd18c588b29de322c25821934d6aa6d2bbbdbb89b6a4efacdb248b4115fc488d", size = 8512, upload-time = "2025-06-14T20:55:00.411Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a1/83e58eca8827a1a9975a9c5de7f8c0bdc73b5f53ee79768d1fdbec6747de/pyobjc_framework_sharedwithyoucore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4f9f7fed0768ebbbc2d24248365da2cf5f014b8822b2a1fbbce5fa920f410f1", size = 8512, upload-time = "2025-11-14T10:03:49.176Z" }, + { url = "https://files.pythonhosted.org/packages/dd/0e/0c2b0591ebc72d437dccca7a1e7164c5f11dde2189d4f4c707a132bab740/pyobjc_framework_sharedwithyoucore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed928266ae9d577ff73de72a03bebc66a751918eb59ca660a9eca157392f17be", size = 8530, upload-time = "2025-11-14T10:03:50.839Z" }, ] [[package]] name = "pyobjc-framework-shazamkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/08/ba739b97f1e441653bae8da5dd1e441bbbfa43940018d21edb60da7dd163/pyobjc_framework_shazamkit-11.1.tar.gz", hash = "sha256:c6e3c9ab8744d9319a89b78ae6f185bb5704efb68509e66d77bcd1f84a9446d6", size = 25797, upload-time = "2025-06-14T20:58:34.086Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/2c/8d82c5066cc376de68ad8c1454b7c722c7a62215e5c2f9dac5b33a6c3d42/pyobjc_framework_shazamkit-12.1.tar.gz", hash = "sha256:71db2addd016874639a224ed32b2000b858802b0370c595a283cce27f76883fe", size = 22518, upload-time = "2025-11-14T10:22:25.996Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/b6/c03bc9aad7f15979b5d7f144baf5161c3c40e0bca194cce82e1bce0804a9/pyobjc_framework_shazamkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2fe6990d0ec1b40d4efd0d0e49c2deb65198f49b963e6215c608c140b3149151", size = 8540, upload-time = "2025-06-14T20:55:05.978Z" }, - { url = "https://files.pythonhosted.org/packages/89/b7/594b8bdc406603a7a07cdb33f2be483fed16aebc35aeb087385fc9eca844/pyobjc_framework_shazamkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b323f5409b01711aa2b6e2113306084fab2cc83fa57a0c3d55bd5876358b68d8", size = 8560, upload-time = "2025-06-14T20:55:07.564Z" }, + { url = "https://files.pythonhosted.org/packages/92/12/09d83a8ac51dc11a574449dea48ffa99b3a7c9baf74afeedb487394d110d/pyobjc_framework_shazamkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0c10ba22de524fbedf06270a71bb0a3dbd4a3853b7002ddf54394589c3be6939", size = 8555, upload-time = "2025-11-14T10:04:02.552Z" }, + { url = "https://files.pythonhosted.org/packages/04/5e/7d60d8e7b036b20d0e94cd7c4563e7414653344482e85fbc7facffabc95f/pyobjc_framework_shazamkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e184dd0f61a604b1cfcf44418eb95b943e7b8f536058a29e4b81acadd27a9420", size = 8577, upload-time = "2025-11-14T10:04:04.182Z" }, ] [[package]] name = "pyobjc-framework-social" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/2e/cc7707b7a40df392c579087947049f3e1f0e00597e7151ec411f654d8bef/pyobjc_framework_social-11.1.tar.gz", hash = "sha256:fbc09d7b00dad45b547f9b2329f4dcee3f5a50e2348de1870de0bd7be853a5b7", size = 14540, upload-time = "2025-06-14T20:58:35.116Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/21/afc6f37dfdd2cafcba0227e15240b5b0f1f4ad57621aeefda2985ac9560e/pyobjc_framework_social-12.1.tar.gz", hash = "sha256:1963db6939e92ae40dd9d68852e8f88111cbfd37a83a9fdbc9a0c08993ca7e60", size = 13184, upload-time = "2025-11-14T10:22:28.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/86/1d/e1026c082a66075dbb7e57983c0aaaed3ee09f06c346743e8af24d1dc21a/pyobjc_framework_social-11.1-py2.py3-none-any.whl", hash = "sha256:ab5878c47d7a0639704c191cee43eeb259e09688808f0905c42551b9f79e1d57", size = 4444, upload-time = "2025-06-14T20:55:12.536Z" }, + { url = "https://files.pythonhosted.org/packages/f6/fb/090867e332d49a1e492e4b8972ac6034d1c7d17cf39f546077f35be58c46/pyobjc_framework_social-12.1-py2.py3-none-any.whl", hash = "sha256:2f3b36ba5769503b1bc945f85fd7b255d42d7f6e417d78567507816502ff2b44", size = 4462, upload-time = "2025-11-14T10:04:14.578Z" }, ] [[package]] name = "pyobjc-framework-soundanalysis" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e0/d4/b9497dbb57afdf0d22f61bb6e776a6f46cf9294c890448acde5b46dd61f3/pyobjc_framework_soundanalysis-11.1.tar.gz", hash = "sha256:42cd25b7e0f343d8b59367f72b5dae96cf65696bdb8eeead8d7424ed37aa1434", size = 16539, upload-time = "2025-06-14T20:58:35.813Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/d6/5039b61edc310083425f87ce2363304d3a87617e941c1d07968c63b5638d/pyobjc_framework_soundanalysis-12.1.tar.gz", hash = "sha256:e2deead8b9a1c4513dbdcf703b21650dcb234b60a32d08afcec4895582b040b1", size = 14804, upload-time = "2025-11-14T10:22:29.998Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/b4/7e8cf3a02e615239568fdf12497233bbd5b58082615cd28a0c7cd4636309/pyobjc_framework_soundanalysis-11.1-py2.py3-none-any.whl", hash = "sha256:6cf983c24fb2ad2aa5e7499ab2d30ff134d887fe91fd2641acf7472e546ab4e5", size = 4161, upload-time = "2025-06-14T20:55:13.342Z" }, + { url = "https://files.pythonhosted.org/packages/53/d3/8df5183d52d20d459225d3f5d24f55e01b8cd9fe587ed972e3f20dd18709/pyobjc_framework_soundanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:8b2029ab48c1a9772f247f0aea995e8c3ff4706909002a9c1551722769343a52", size = 4188, upload-time = "2025-11-14T10:04:16.12Z" }, ] [[package]] name = "pyobjc-framework-speech" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/76/2a1fd7637b2c662349ede09806e159306afeebfba18fb062ad053b41d811/pyobjc_framework_speech-11.1.tar.gz", hash = "sha256:d382977208c3710eacea89e05eae4578f1638bb5a7b667c06971e3d34e96845c", size = 41179, upload-time = "2025-06-14T20:58:36.43Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8d/3d/194cf19fe7a56c2be5dfc28f42b3b597a62ebb1e1f52a7dd9c55b917ac6c/pyobjc_framework_speech-12.1.tar.gz", hash = "sha256:2a2a546ba6c52d5dd35ddcfee3fd9226a428043d1719597e8701851a6566afdd", size = 25218, upload-time = "2025-11-14T10:22:32.505Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/d3/c3b1d542c5ddc816924f02edf2ececcda226f35c91e95ed80f2632fbd91c/pyobjc_framework_speech-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3e0276a66d2fa4357959a6f6fb5def03f8e0fd3aa43711d6a81ab2573b9415f", size = 9171, upload-time = "2025-06-14T20:55:15.316Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/267f4699055beb39723ccbff70909ec3851e4adf17386f6ad85e5d983780/pyobjc_framework_speech-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7726eff52cfa9cc7178ddcd1285cbc23b5f89ee55b4b850b0d2e90bb4f8e044b", size = 9180, upload-time = "2025-06-14T20:55:16.556Z" }, + { url = "https://files.pythonhosted.org/packages/03/54/77e12e4c23a98fc49d874f9703c9f8fd0257d64bb0c6ae329b91fc7a99e3/pyobjc_framework_speech-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0301bfae5d0d09b6e69bd4dbabc5631209e291cc40bda223c69ed0c618f8f2dc", size = 9248, upload-time = "2025-11-14T10:04:19.73Z" }, + { url = "https://files.pythonhosted.org/packages/f9/1b/224cb98c9c32a6d5e68072f89d26444095be54c6f461efe4fefe9d1330a5/pyobjc_framework_speech-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cae4b88ef9563157a6c9e66b37778fc4022ee44dd1a2a53081c2adbb69698945", size = 9254, upload-time = "2025-11-14T10:04:21.361Z" }, ] [[package]] name = "pyobjc-framework-spritekit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/16/02/2e253ba4f7fad6efe05fd5fcf44aede093f6c438d608d67c6c6623a1846d/pyobjc_framework_spritekit-11.1.tar.gz", hash = "sha256:914da6e846573cac8db5e403dec9a3e6f6edf5211f9b7e429734924d00f65108", size = 130297, upload-time = "2025-06-14T20:58:37.113Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/78/d683ebe0afb49f46d2d21d38c870646e7cb3c2e83251f264e79d357b1b74/pyobjc_framework_spritekit-12.1.tar.gz", hash = "sha256:a851f4ef5aa65cc9e08008644a528e83cb31021a1c0f17ebfce4de343764d403", size = 64470, upload-time = "2025-11-14T10:22:37.569Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/83/1c874cffba691cf8c103e0fdf55b53d9749577794efb9fc30e4394ffef41/pyobjc_framework_spritekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c8c94d37c054b6e3c22c237f6458c12649776e5ac921d066ab99dee2e580909", size = 17718, upload-time = "2025-06-14T20:55:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fe/39d92bf40ec7a6116f89fd95053321f7c00c50c10d82b9adfa0f9ebdb10c/pyobjc_framework_spritekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8b470a890db69e70ef428dfff88da499500fca9b2d44da7120dc588d13a2dbdb", size = 17776, upload-time = "2025-06-14T20:55:23.639Z" }, + { url = "https://files.pythonhosted.org/packages/60/6a/e8e44fc690d898394093f3a1c5fe90110d1fbcc6e3f486764437c022b0f8/pyobjc_framework_spritekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26fd12944684713ae1e3cdd229348609c1142e60802624161ca0c3540eec3ffa", size = 17736, upload-time = "2025-11-14T10:04:33.202Z" }, + { url = "https://files.pythonhosted.org/packages/3b/38/97c3b6c3437e3e9267fb4e1cd86e0da4eff07e0abe7cd6923644d2dfc878/pyobjc_framework_spritekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1649e57c25145795d04bb6a1ec44c20ef7cf0af7c60a9f6f5bc7998dd269db1e", size = 17802, upload-time = "2025-11-14T10:04:35.346Z" }, ] [[package]] name = "pyobjc-framework-storekit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/a0/58cab9ebc9ac9282e1d4734b1987d1c3cd652b415ec3e678fcc5e735d279/pyobjc_framework_storekit-11.1.tar.gz", hash = "sha256:85acc30c0bfa120b37c3c5ac693fe9ad2c2e351ee7a1f9ea6f976b0c311ff164", size = 76421, upload-time = "2025-06-14T20:58:37.86Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/87/8a66a145feb026819775d44975c71c1c64df4e5e9ea20338f01456a61208/pyobjc_framework_storekit-12.1.tar.gz", hash = "sha256:818452e67e937a10b5c8451758274faa44ad5d4329df0fa85735115fb0608da9", size = 34574, upload-time = "2025-11-14T10:22:40.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/30/7549a7bd2b068cd460792e09a66d88465aab2ac6fb2ddcf77b7bf5712eee/pyobjc_framework_storekit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:624105bd26a9ce5a097b3f96653e2700d33bb095828ed65ee0f4679b34d9f1e1", size = 11841, upload-time = "2025-06-14T20:55:29.735Z" }, - { url = "https://files.pythonhosted.org/packages/ac/61/6404aac6857ea43798882333bcc26bfd3c9c3a1efc7a575cbf3e53538e2a/pyobjc_framework_storekit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5ca3373272b6989917c88571ca170ce6d771180fe1a2b44c7643fe084569b93e", size = 11868, upload-time = "2025-06-14T20:55:30.454Z" }, + { url = "https://files.pythonhosted.org/packages/d9/41/af2afc4d27bde026cfd3b725ee1b082b2838dcaa9880ab719226957bc7cd/pyobjc_framework_storekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a29f45bcba9dee4cf73dae05ab0f94d06a32fb052e31414d0c23791c1ec7931c", size = 12810, upload-time = "2025-11-14T10:04:48.693Z" }, + { url = "https://files.pythonhosted.org/packages/8a/9f/938985e506de0cc3a543e44e1f9990e9e2fb8980b8f3bcfc8f7921d09061/pyobjc_framework_storekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9fe2d65a2b644bb6b4fdd3002292cba153560917de3dd6cf969431fa32d21dd0", size = 12819, upload-time = "2025-11-14T10:04:50.945Z" }, ] [[package]] name = "pyobjc-framework-symbols" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/af/7191276204bd3e7db1d0a3e490a869956606f77f7a303a04d92a5d0c3f7b/pyobjc_framework_symbols-11.1.tar.gz", hash = "sha256:0e09b7813ef2ebdca7567d3179807444dd60f3f393202b35b755d4e1baf99982", size = 13377, upload-time = "2025-06-14T20:58:38.542Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/ce/a48819eb8524fa2dc11fb3dd40bb9c4dcad0596fe538f5004923396c2c6c/pyobjc_framework_symbols-12.1.tar.gz", hash = "sha256:7d8e999b8a59c97d38d1d343b6253b1b7d04bf50b665700957d89c8ac43b9110", size = 12782, upload-time = "2025-11-14T10:22:42.609Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/6a/c91f64ef9b8cd20245b88e392c66cb2279c511724f4ea2983d92584d6f3e/pyobjc_framework_symbols-11.1-py2.py3-none-any.whl", hash = "sha256:1de6fc3af15fc8d5fd4869663a3250311844ec33e99ec8a1991a352ab61d641d", size = 3312, upload-time = "2025-06-14T20:55:35.456Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ea/6e9af9c750d68109ac54fbffb5463e33a7b54ffe8b9901a5b6b603b7884b/pyobjc_framework_symbols-12.1-py2.py3-none-any.whl", hash = "sha256:c72eecbc25f6bfcd39c733067276270057c5aca684be20fdc56def645f2b6446", size = 3331, upload-time = "2025-11-14T10:05:01.333Z" }, ] [[package]] name = "pyobjc-framework-syncservices" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/45/cd9fa83ed1d75be7130fb8e41c375f05b5d6621737ec37e9d8da78676613/pyobjc_framework_syncservices-11.1.tar.gz", hash = "sha256:0f141d717256b98c17ec2eddbc983c4bd39dfa00dc0c31b4174742e73a8447fe", size = 57996, upload-time = "2025-06-14T20:58:39.146Z" } +sdist = { url = "https://files.pythonhosted.org/packages/21/91/6d03a988831ddb0fb001b13573560e9a5bcccde575b99350f98fe56a2dd4/pyobjc_framework_syncservices-12.1.tar.gz", hash = "sha256:6a213e93d9ce15128810987e4c5de8c73cfab1564ac8d273e6b437a49965e976", size = 31032, upload-time = "2025-11-14T10:22:45.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/7e/60e184beafca85571cfa68d46a8f453a54edbc7d2eceb18163cfec438438/pyobjc_framework_syncservices-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bc6159bda4597149c6999b052a35ffd9fc4817988293da6e54a1e073fa571653", size = 13464, upload-time = "2025-06-14T20:55:37.117Z" }, - { url = "https://files.pythonhosted.org/packages/01/2b/6d7d65c08a9c51eed12eb7f83eaa48deaed621036f77221b3b0346c3f6c2/pyobjc_framework_syncservices-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:03124c8c7c7ce837f51e1c9bdcf84c6f1d5201f92c8a1c172ec34908d5e57415", size = 13496, upload-time = "2025-06-14T20:55:37.83Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9b/25c117f8ffe15aa6cc447da7f5c179627ebafb2b5ec30dfb5e70fede2549/pyobjc_framework_syncservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e81a38c2eb7617cb0ecfc4406c1ae2a97c60e95af42e863b2b0f1f6facd9b0da", size = 13380, upload-time = "2025-11-14T10:05:05.814Z" }, + { url = "https://files.pythonhosted.org/packages/54/ac/a83cdd120e279ee905e9085afda90992159ed30c6a728b2c56fa2d36b6ea/pyobjc_framework_syncservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cd629bea95692aad2d26196657cde2fbadedae252c7846964228661a600b900", size = 13411, upload-time = "2025-11-14T10:05:07.741Z" }, ] [[package]] name = "pyobjc-framework-systemconfiguration" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/3d/41590c0afc72e93d911348fbde0c9c1071ff53c6f86df42df64b21174bb9/pyobjc_framework_systemconfiguration-11.1.tar.gz", hash = "sha256:f30ed0e9a8233fecb06522e67795918ab230ddcc4a18e15494eff7532f4c3ae1", size = 143410, upload-time = "2025-06-14T20:58:39.917Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/7d/50848df8e1c6b5e13967dee9fb91d3391fe1f2399d2d0797d2fc5edb32ba/pyobjc_framework_systemconfiguration-12.1.tar.gz", hash = "sha256:90fe04aa059876a21626931c71eaff742a27c79798a46347fd053d7008ec496e", size = 59158, upload-time = "2025-11-14T10:22:53.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/9b/8fe26a9ac85898fa58f6206f357745ec44cd95b63786503ce05c382344ce/pyobjc_framework_systemconfiguration-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d12d5078611c905162bc951dffbb2a989b0dfd156952ba1884736c8dcbe38f7f", size = 21732, upload-time = "2025-06-14T20:55:43.951Z" }, - { url = "https://files.pythonhosted.org/packages/b9/61/0e9841bf1c7597f380a6dcefcc9335b6a909f20d9bdf07910cddc8552b42/pyobjc_framework_systemconfiguration-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6881929b828a566bf1349f09db4943e96a2b33f42556e1f7f6f28b192420f6fc", size = 21639, upload-time = "2025-06-14T20:55:44.678Z" }, + { url = "https://files.pythonhosted.org/packages/1d/7b/9126a7af1b798998837027390a20b981e0298e51c4c55eed6435967145cb/pyobjc_framework_systemconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:796390a80500cc7fde86adc71b11cdc41d09507dd69103d3443fbb60e94fb438", size = 21663, upload-time = "2025-11-14T10:05:21.259Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d3/bb935c3d4bae9e6ce4a52638e30eea7039c480dd96bc4f0777c9fabda21b/pyobjc_framework_systemconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e5bb9103d39483964431db7125195c59001b7bff2961869cfe157b4c861e52d", size = 21578, upload-time = "2025-11-14T10:05:25.572Z" }, ] [[package]] name = "pyobjc-framework-systemextensions" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/57/4609fd9183383616b1e643c2489ad774335f679523a974b9ce346a6d4d5b/pyobjc_framework_systemextensions-11.1.tar.gz", hash = "sha256:8ff9f0aad14dcdd07dd47545c1dd20df7a286306967b0a0232c81fcc382babe6", size = 23062, upload-time = "2025-06-14T20:58:40.686Z" } +sdist = { url = "https://files.pythonhosted.org/packages/12/01/8a706cd3f7dfcb9a5017831f2e6f9e5538298e90052db3bb8163230cbc4f/pyobjc_framework_systemextensions-12.1.tar.gz", hash = "sha256:243e043e2daee4b5c46cd90af5fff46b34596aac25011bab8ba8a37099685eeb", size = 20701, upload-time = "2025-11-14T10:22:58.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/53/0fb6a200383fa98001ffa66b4f6344c68ccd092506699a353b30f18d7094/pyobjc_framework_systemextensions-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7e742ae51cdd86c0e609fe47189ea446de98d13b235b0a138a3f2e37e98cd359", size = 9125, upload-time = "2025-06-14T20:55:50.431Z" }, - { url = "https://files.pythonhosted.org/packages/76/40/d9be444b39ec12d68b5e4f712b71d6c00d654936ff5744ea380c1bfabf06/pyobjc_framework_systemextensions-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3a2b1e84e4a118bfe13efb9f2888b065dc937e2a7e60afd4d0a82b51b8301a10", size = 9130, upload-time = "2025-06-14T20:55:51.127Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a1/f8df6d59e06bc4b5989a76724e8551935e5b99aff6a21d3592e5ced91f1c/pyobjc_framework_systemextensions-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a4e82160e43c0b1aa17e6d4435e840a655737fbe534e00e37fc1961fbf3bebd", size = 9156, upload-time = "2025-11-14T10:05:39.744Z" }, + { url = "https://files.pythonhosted.org/packages/0a/cc/a42883d6ad0ae257a7fa62660b4dd13be15f8fa657922f9a5b6697f26e28/pyobjc_framework_systemextensions-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:01fac4f8d88c0956d9fc714d24811cd070e67200ba811904317d91e849e38233", size = 9166, upload-time = "2025-11-14T10:05:41.479Z" }, ] [[package]] name = "pyobjc-framework-threadnetwork" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/a4/5400a222ced0e4f077a8f4dd0188e08e2af4762e72ed0ed39f9d27feefc9/pyobjc_framework_threadnetwork-11.1.tar.gz", hash = "sha256:73a32782f44b61ca0f8a4a9811c36b1ca1cdcf96c8a3ba4de35d8e8e58a86ad5", size = 13572, upload-time = "2025-06-14T20:58:41.311Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/7e/f1816c3461e4121186f2f7750c58af083d1826bbd73f72728da3edcf4915/pyobjc_framework_threadnetwork-12.1.tar.gz", hash = "sha256:e071eedb41bfc1b205111deb54783ec5a035ccd6929e6e0076336107fdd046ee", size = 12788, upload-time = "2025-11-14T10:23:00.329Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/f0/b7a577d00bdb561efef82b046a75f627a60de53566ab2d9e9ddd5bd11b66/pyobjc_framework_threadnetwork-11.1-py2.py3-none-any.whl", hash = "sha256:55021455215a0d3ad4e40152f94154e29062e73655558c5f6e71ab097d90083e", size = 3751, upload-time = "2025-06-14T20:55:55.643Z" }, + { url = "https://files.pythonhosted.org/packages/4f/b8/94b37dd353302c051a76f1a698cf55b5ad50ca061db7f0f332aa9e195766/pyobjc_framework_threadnetwork-12.1-py2.py3-none-any.whl", hash = "sha256:07d937748fc54199f5ec04d5a408e8691a870481c11b641785c2adc279dd8e4b", size = 3771, upload-time = "2025-11-14T10:05:49.899Z" }, ] [[package]] name = "pyobjc-framework-uniformtypeidentifiers" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/4f/066ed1c69352ccc29165f45afb302f8c9c2b5c6f33ee3abfa41b873c07e5/pyobjc_framework_uniformtypeidentifiers-11.1.tar.gz", hash = "sha256:86c499bec8953aeb0c95af39b63f2592832384f09f12523405650b5d5f1ed5e9", size = 20599, upload-time = "2025-06-14T20:58:41.945Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/3b/b63b8137dd9f455d5abece6702c06c6b613fac6fda1319aaa2f79d00c380/pyobjc_framework_uniformtypeidentifiers-11.1-py2.py3-none-any.whl", hash = "sha256:6e2e8ea89eb8ca03bc2bc8e506fff901e71d916276475c8d81fbf0280059cb4c", size = 4891, upload-time = "2025-06-14T20:55:56.432Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5f/1f10f5275b06d213c9897850f1fca9c881c741c1f9190cea6db982b71824/pyobjc_framework_uniformtypeidentifiers-12.1-py2.py3-none-any.whl", hash = "sha256:ec5411e39152304d2a7e0e426c3058fa37a00860af64e164794e0bcffee813f2", size = 4901, upload-time = "2025-11-14T10:05:51.532Z" }, ] [[package]] name = "pyobjc-framework-usernotifications" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b4/4c/e7e180fcd06c246c37f218bcb01c40ea0213fde5ace3c09d359e60dcaafd/pyobjc_framework_usernotifications-11.1.tar.gz", hash = "sha256:38fc763afa7854b41ddfca8803f679a7305d278af8a7ad02044adc1265699996", size = 55428, upload-time = "2025-06-14T20:58:42.572Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/cd/e0253072f221fa89a42fe53f1a2650cc9bf415eb94ae455235bd010ee12e/pyobjc_framework_usernotifications-12.1.tar.gz", hash = "sha256:019ccdf2d400f9a428769df7dba4ea97c02453372bc5f8b75ce7ae54dfe130f9", size = 29749, upload-time = "2025-11-14T10:23:05.364Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/bb/ae9c9301a86b7c0c26583c59ac761374cb6928c3d34cae514939e93e44b1/pyobjc_framework_usernotifications-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7140d337dd9dc3635add2177086429fdd6ef24970935b22fffdc5ec7f02ebf60", size = 9599, upload-time = "2025-06-14T20:55:58.051Z" }, - { url = "https://files.pythonhosted.org/packages/03/af/a54e343a7226dc65a65f7a561c060f8c96cb9f92f41ce2242d20d82ae594/pyobjc_framework_usernotifications-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ce6006989fd4a59ec355f6797ccdc9946014ea5241ff7875854799934dbba901", size = 9606, upload-time = "2025-06-14T20:55:59.088Z" }, + { url = "https://files.pythonhosted.org/packages/d1/96/aa25bb0727e661a352d1c52e7288e25c12fe77047f988bb45557c17cf2d7/pyobjc_framework_usernotifications-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c62e8d7153d72c4379071e34258aa8b7263fa59212cfffd2f137013667e50381", size = 9632, upload-time = "2025-11-14T10:05:55.166Z" }, + { url = "https://files.pythonhosted.org/packages/61/ad/c95053a475246464cba686e16269b0973821601910d1947d088b855a8dac/pyobjc_framework_usernotifications-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:412afb2bf5fe0049f9c4e732e81a8a35d5ebf97c30a5a6abd276259d020c82ac", size = 9644, upload-time = "2025-11-14T10:05:56.801Z" }, ] [[package]] name = "pyobjc-framework-usernotificationsui" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-usernotifications", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/c4/03d97bd3adcee9b857533cb42967df0d019f6a034adcdbcfca2569d415b2/pyobjc_framework_usernotificationsui-11.1.tar.gz", hash = "sha256:18e0182bddd10381884530d6a28634ebb3280912592f8f2ad5bac2a9308c6a65", size = 14123, upload-time = "2025-06-14T20:58:43.267Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/03/73e29fd5e5973cb3800c9d56107c1062547ef7524cbcc757c3cbbd5465c6/pyobjc_framework_usernotificationsui-12.1.tar.gz", hash = "sha256:51381c97c7344099377870e49ed0871fea85ba50efe50ab05ccffc06b43ec02e", size = 13125, upload-time = "2025-11-14T10:23:07.259Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/2c/0bb489b5ac4daf83b113018701ce30a0cb4bf47c615c92c5844a16e0a012/pyobjc_framework_usernotificationsui-11.1-py2.py3-none-any.whl", hash = "sha256:b84d73d90ab319acf8fad5c59b7a5e2b6023fbb2efd68c58b532e3b3b52f647a", size = 3914, upload-time = "2025-06-14T20:56:03.978Z" }, + { url = "https://files.pythonhosted.org/packages/23/c8/52ac8a879079c1fbf25de8335ff506f7db87ff61e64838b20426f817f5d5/pyobjc_framework_usernotificationsui-12.1-py2.py3-none-any.whl", hash = "sha256:11af59dc5abfcb72c08769ab4d7ca32a628527a8ba341786431a0d2dacf31605", size = 3933, upload-time = "2025-11-14T10:06:05.478Z" }, ] [[package]] name = "pyobjc-framework-videosubscriberaccount" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/00/cd9d93d06204bbb7fe68fb97022b0dd4ecdf8af3adb6d70a41e22c860d55/pyobjc_framework_videosubscriberaccount-11.1.tar.gz", hash = "sha256:2dd78586260fcee51044e129197e8bf2e157176e02babeec2f873afa4235d8c6", size = 28856, upload-time = "2025-06-14T20:58:43.903Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/f8/27927a9c125c622656ee5aada4596ccb8e5679da0260742360f193df6dcf/pyobjc_framework_videosubscriberaccount-12.1.tar.gz", hash = "sha256:750459fa88220ab83416f769f2d5d210a1f77b8938fa4d119aad0002fc32846b", size = 18793, upload-time = "2025-11-14T10:23:09.33Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/dc/b409dee6dd58a5db2e9a681bde8894c9715468689f18e040f7d252794c3d/pyobjc_framework_videosubscriberaccount-11.1-py2.py3-none-any.whl", hash = "sha256:d5a95ae9f2a6f0180a5bbb10e76c064f0fd327aae00a2fe90aa7b65ed4dad7ef", size = 4695, upload-time = "2025-06-14T20:56:06.027Z" }, + { url = "https://files.pythonhosted.org/packages/41/ca/e2f982916267508c1594f1e50d27bf223a24f55a5e175ab7d7822a00997c/pyobjc_framework_videosubscriberaccount-12.1-py2.py3-none-any.whl", hash = "sha256:381a5e8a3016676e52b88e38b706559fa09391d33474d8a8a52f20a883104a7b", size = 4825, upload-time = "2025-11-14T10:06:07.027Z" }, ] [[package]] name = "pyobjc-framework-videotoolbox" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -4177,29 +4232,29 @@ dependencies = [ { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/e3/df9096f54ae1f27cab8f922ee70cbda5d80f8c1d12734c38580829858133/pyobjc_framework_videotoolbox-11.1.tar.gz", hash = "sha256:a27985656e1b639cdb102fcc727ebc39f71bb1a44cdb751c8c80cc9fe938f3a9", size = 88551, upload-time = "2025-06-14T20:58:44.566Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/5f/6995ee40dc0d1a3460ee183f696e5254c0ad14a25b5bc5fd9bd7266c077b/pyobjc_framework_videotoolbox-12.1.tar.gz", hash = "sha256:7adc8670f3b94b086aed6e86c3199b388892edab4f02933c2e2d9b1657561bef", size = 57825, upload-time = "2025-11-14T10:23:13.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/41/fda951f1c734a68d7bf46ecc03bfff376a690ad771029c4289ba0423a52e/pyobjc_framework_videotoolbox-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:94c17bffe0f4692db2e7641390dfdcd0f73ddbb0afa6c81ef504219be0777930", size = 17325, upload-time = "2025-06-14T20:56:07.719Z" }, - { url = "https://files.pythonhosted.org/packages/1f/cf/569babadbf1f9598f62c400ee02da19d4ab5f36276978c81080999399df9/pyobjc_framework_videotoolbox-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c55285c3c78183fd2a092d582e30b562777a82985cccca9e7e99a0aff2601591", size = 17432, upload-time = "2025-06-14T20:56:08.457Z" }, + { url = "https://files.pythonhosted.org/packages/1e/42/53d57b09fd4879988084ec0d9b74c645c9fdd322be594c9601f6cf265dd0/pyobjc_framework_videotoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1eb1eb41c0ffdd8dcc6a9b68ab2b5bc50824a85820c8a7802a94a22dfbb4f91", size = 18781, upload-time = "2025-11-14T10:06:11.89Z" }, + { url = "https://files.pythonhosted.org/packages/94/a5/91c6c95416f41c412c2079950527cb746c0712ec319c51a6c728c8d6b231/pyobjc_framework_videotoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eb6ce6837344ee319122066c16ada4beb913e7bfd62188a8d14b1ecbb5a89234", size = 18908, upload-time = "2025-11-14T10:06:14.087Z" }, ] [[package]] name = "pyobjc-framework-virtualization" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f1/ff/57214e8f42755eeaad516a7e673dae4341b8742005d368ecc22c7a790b0b/pyobjc_framework_virtualization-11.1.tar.gz", hash = "sha256:4221ee5eb669e43a2ff46e04178bec149af2d65205deb5d4db5fa62ea060e022", size = 78633, upload-time = "2025-06-14T20:58:45.358Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/6a/9d110b5521d9b898fad10928818c9f55d66a4af9ac097426c65a9878b095/pyobjc_framework_virtualization-12.1.tar.gz", hash = "sha256:e96afd8e801e92c6863da0921e40a3b68f724804f888bce43791330658abdb0f", size = 40682, upload-time = "2025-11-14T10:23:17.456Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/8b/5eeabfd08d5e6801010496969c1b67517bbda348ff0578ca5f075aa58926/pyobjc_framework_virtualization-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c2a812da4c995e1f8076678130d0b0a63042aa48219f8fb43b70e13eabcbdbc2", size = 13054, upload-time = "2025-06-14T20:56:13.866Z" }, - { url = "https://files.pythonhosted.org/packages/c8/4f/fe1930f4ce2c7d2f4c34bb53adf43f412bc91364e8e4cb450a7c8a6b8b59/pyobjc_framework_virtualization-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:59df6702b3e63200752be7d9c0dc590cb4c3b699c886f9a8634dd224c74b3c3c", size = 13084, upload-time = "2025-06-14T20:56:14.617Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ee/e18d0d9014c42758d7169144acb2d37eb5ff19bf959db74b20eac706bd8c/pyobjc_framework_virtualization-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a88a307dc96885afc227ceda4067f1af787f024063f4ccf453d59e7afd47cda8", size = 13099, upload-time = "2025-11-14T10:06:27.403Z" }, + { url = "https://files.pythonhosted.org/packages/c6/f2/0da47e91f3f8eeda9a8b4bb0d3a0c54a18925009e99b66a8226b9e06ce1e/pyobjc_framework_virtualization-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d5724b38e64b39ab5ec3b45993afa29fc88b307d99ee2c7a1c0fd770e9b4b21", size = 13131, upload-time = "2025-11-14T10:06:29.337Z" }, ] [[package]] name = "pyobjc-framework-vision" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, @@ -4207,47 +4262,48 @@ dependencies = [ { name = "pyobjc-framework-coreml", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/a8/7128da4d0a0103cabe58910a7233e2f98d18c590b1d36d4b3efaaedba6b9/pyobjc_framework_vision-11.1.tar.gz", hash = "sha256:26590512ee7758da3056499062a344b8a351b178be66d4b719327884dde4216b", size = 133721, upload-time = "2025-06-14T20:58:46.095Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/69/a745a5491d7af6034ac9e0d627e7b41b42978df0a469b86cdf372ba8917f/pyobjc_framework_vision-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bfbde43c9d4296e1d26548b6d30ae413e2029425968cd8bce96d3c5a735e8f2c", size = 21657, upload-time = "2025-06-14T20:56:20.265Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b5/54c0227a695557ea3065bc035b20a5c256f6f3b861e095eee1ec4b4d8cee/pyobjc_framework_vision-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df076c3e3e672887182953efc934c1f9683304737e792ec09a29bfee90d2e26a", size = 16829, upload-time = "2025-06-14T20:56:21.355Z" }, + { url = "https://files.pythonhosted.org/packages/bd/37/e30cf4eef2b4c7e20ccadc1249117c77305fbc38b2e5904eb42e3753f63c/pyobjc_framework_vision-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1edbf2fc18ce3b31108f845901a88f2236783ae6bf0bc68438d7ece572dc2a29", size = 21432, upload-time = "2025-11-14T10:06:42.373Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625, upload-time = "2025-11-14T10:06:44.422Z" }, ] [[package]] name = "pyobjc-framework-webkit" -version = "11.1" +version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/92/04/fb3d0b68994f7e657ef00c1ac5fc1c04ae2fc7ea581d647f5ae1f6739b14/pyobjc_framework_webkit-11.1.tar.gz", hash = "sha256:27e701c7aaf4f24fc7e601a128e2ef14f2773f4ab071b9db7438dc5afb5053ae", size = 717102, upload-time = "2025-06-14T20:58:47.461Z" } +sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/b6/d62c01a83c22619edf2379a6941c9f6b7aee01c565b9c1170696f85cba95/pyobjc_framework_webkit-11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:10ec89d727af8f216ba5911ff5553f84a5b660f5ddf75b07788e3a439c281165", size = 51406, upload-time = "2025-06-14T20:56:26.845Z" }, - { url = "https://files.pythonhosted.org/packages/8a/7e/fa2c18c0c0f9321e5036e54b9da7a196956b531e50fe1a76e7dfdbe8fac2/pyobjc_framework_webkit-11.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1a6e6f64ca53c4953f17e808ecac11da288d9a6ade738156ba161732a5e0c96a", size = 51464, upload-time = "2025-06-14T20:56:27.653Z" }, + { url = "https://files.pythonhosted.org/packages/e5/37/5082a0bbe12e48d4ffa53b0c0f09c77a4a6ffcfa119e26fa8dd77c08dc1c/pyobjc_framework_webkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3db734877025614eaef4504fadc0fbbe1279f68686a6f106f2e614e89e0d1a9d", size = 49970, upload-time = "2025-11-14T10:07:01.413Z" }, + { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, ] [[package]] name = "pyopencl" -version = "2025.1" +version = "2025.2.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "pytools", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy" }, + { name = "platformdirs" }, + { name = "pytools" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/88/0ac460d3e2def08b2ad6345db6a13613815f616bbbd60c6f4bdf774f4c41/pyopencl-2025.1.tar.gz", hash = "sha256:0116736d7f7920f87b8db4b66a03f27b1d930d2e37ddd14518407cc22dd24779", size = 422510, upload-time = "2025-01-22T00:16:58.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/cb/8927052160bc0d3bd1123a645aaf57f696da364216b57b49f92ba0777bcc/pyopencl-2025.2.7.tar.gz", hash = "sha256:a68d92eb2970418b1a7ca45aff71984c02d2e4261e01402b273f257b5d6d7511", size = 444787, upload-time = "2025-10-28T14:23:15.497Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/ce/c40c6248b29195397a6e176615c24a8047cdd3afe847932a1f27603c1b14/pyopencl-2025.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:a302e4ee1bb19ff244f5ae2b5a83a98977daa13f31929a85f23723020a4bec01", size = 424117, upload-time = "2025-01-22T00:16:08.957Z" }, - { url = "https://files.pythonhosted.org/packages/71/dd/8dd4e18396c705567be7eda65234932f8eb7e975cc15ae167265ed9c7d20/pyopencl-2025.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:48527fc5a250b9e89f2eaaa1c9423e1bc15ff181bd41ffa1e29e31890dc1d3b7", size = 408327, upload-time = "2025-01-22T00:16:10.42Z" }, - { url = "https://files.pythonhosted.org/packages/47/7b/270c4e6765b675eaa97af8ff0c964e21dc74ac2a2f04e2c24431c91ce382/pyopencl-2025.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95490199c490e47b245b88e64e06f95272502d13810da172ac3b0f0340cf8715", size = 724636, upload-time = "2025-01-22T00:16:12.881Z" }, - { url = "https://files.pythonhosted.org/packages/0d/55/996d7877793acfc340678a71dc98151a8c39dbb289cf24ecae08c0af68eb/pyopencl-2025.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cd2cb3c031beeec93cf660c8809d6ad58a2cde7dcd95ae12b4b2da6ac8e2da41", size = 1173429, upload-time = "2025-01-22T00:16:14.466Z" }, - { url = "https://files.pythonhosted.org/packages/3d/7c/d2a89b1c24c318375856e8b7611bc03ddf687134f68ddbb387496453eda8/pyopencl-2025.1-cp311-cp311-win_amd64.whl", hash = "sha256:d8d3cdb02dc8750a9cc11c5b37f219da1f61b4216af4a830eb52b451a0e9f9a7", size = 457877, upload-time = "2025-01-22T00:16:17.21Z" }, - { url = "https://files.pythonhosted.org/packages/02/c0/d9536211ecfddd3bdf7eaa1658d085fcd92120061ac6f4e662a5062660ff/pyopencl-2025.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:88c564d94a5067ab6b9429a7d92b655254da8b2b5a33c7e30e10c5a0cf3154e1", size = 425706, upload-time = "2025-01-22T00:16:18.771Z" }, - { url = "https://files.pythonhosted.org/packages/63/b9/3e6dd574cc9ffb2271be135ecdb36cd6aea70a1f74e80539b048072a256a/pyopencl-2025.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f204cd6974ca19e7ffe14f21afac9967b06a6718f3aecbbc4a4df313e8e7ebc2", size = 408163, upload-time = "2025-01-22T00:16:20.282Z" }, - { url = "https://files.pythonhosted.org/packages/a4/4d/7f6f2e24b12585b81fd49f689d21ba62187656d061e3cb43840f12097dad/pyopencl-2025.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce8d1b3fd2046e89377b754117fdb3505f45edacfda6ad2b3a29670c0526ad1", size = 719348, upload-time = "2025-01-22T00:16:22.15Z" }, - { url = "https://files.pythonhosted.org/packages/f0/45/3c93510819859e047d494dd8f4ed80c26378bb964a8e5e850cc079cc1f6e/pyopencl-2025.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbb0b7b775424f0c4c929a00f09eb351075ea9e4d2b1c80afe37d09bec218ee9", size = 1170733, upload-time = "2025-01-22T00:16:24.575Z" }, - { url = "https://files.pythonhosted.org/packages/91/ba/b745715085ef893fa7d1c7e04c95e3e554b6b27b2fb0800d6bbca563b43c/pyopencl-2025.1-cp312-cp312-win_amd64.whl", hash = "sha256:78f2a58d2e177793fb5a7b9c8a574e3a5f1d178c4df713969d1b08341c817d60", size = 457762, upload-time = "2025-01-22T00:16:27.334Z" }, + { url = "https://files.pythonhosted.org/packages/48/ec/5e511d47fa0d5ad576cd259ceffe4662d6e617dc716044f1ead0d1ba29da/pyopencl-2025.2.7-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:359b484989e9caf532d73206d83422bc0cb8a0e4708c7feeb1f9115752ecda1c", size = 445658, upload-time = "2025-10-28T14:22:41.047Z" }, + { url = "https://files.pythonhosted.org/packages/bd/04/bba1d1ba36ba57eed24ee5407cd2172414792c0a6e89fbf9cc0591e0ba9d/pyopencl-2025.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3749a54e124ab72f2cb656aa4049befd300ac31f2125d03c8a1ec42c10f6c22e", size = 427841, upload-time = "2025-10-28T14:22:42.378Z" }, + { url = "https://files.pythonhosted.org/packages/93/cd/ae3ee576c3f296be713e63b2088fdf217ea3c90d7957bbe5bf2ee3165d6a/pyopencl-2025.2.7-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5244cd402f4411832599f3891b557a4be4c3ce98d0165111ab8f8f3638898b94", size = 734543, upload-time = "2025-10-28T14:22:43.614Z" }, + { url = "https://files.pythonhosted.org/packages/d7/30/321d4d174af5e58122e381afac9ce851e9f84f9b7d6502708913a6a1ef8b/pyopencl-2025.2.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c25a874f94cd2126bc526e22e94a2e4def7c82ebd095fed7e3b5708332d2391", size = 1227048, upload-time = "2025-10-28T14:22:44.947Z" }, + { url = "https://files.pythonhosted.org/packages/20/28/7363bc9b124ad5b4fa6c4bcb0b4fc2a8e213e85e3c192af8c31c320796f2/pyopencl-2025.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:0c9989080124f27f44df86f048e55ecd3e376e20933e470cc9c14506c82ee5dd", size = 472884, upload-time = "2025-10-28T14:22:47.232Z" }, + { url = "https://files.pythonhosted.org/packages/f9/04/e3a17fa79266140246c6193934c0526185f31ffbe45f4ecd6a41ba58c42d/pyopencl-2025.2.7-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:3525c7e35369d505b8a6141f4dfcdcc78a9714c0c30f37e82834906a678c992a", size = 447013, upload-time = "2025-10-28T14:22:48.442Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3b/b2d3c4e69063b53ea6728f09dee7220c780e040b159b21e5d5a369c877fe/pyopencl-2025.2.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:91ab33d7844c2712c65f1881dbf10755e76010db01b1b9d041eb3b947a29f994", size = 427543, upload-time = "2025-10-28T14:22:49.956Z" }, + { url = "https://files.pythonhosted.org/packages/24/fd/8f1042308f3369c653443e9b461156cb28f805da41889a34788dbcaf6470/pyopencl-2025.2.7-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:386c1cdad727df58b34ad6026cf3fe1a2254474977a8afdf8d40eb6daf8806bc", size = 732584, upload-time = "2025-10-28T14:22:51.613Z" }, + { url = "https://files.pythonhosted.org/packages/24/2f/7413a39eb56e3f9130232a2964e50ef72b5d6a351af7624225d185c1ed11/pyopencl-2025.2.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce3643e6367d2c7362a33df4f1d676068ae45db3721bf8aee7d7c60c73065e7", size = 1223000, upload-time = "2025-10-28T14:22:52.976Z" }, + { url = "https://files.pythonhosted.org/packages/08/70/66b371eb8fa3867d139df5e6b7107b611eb1cc8231b7755101f589dda66f/pyopencl-2025.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:0791a7303236f53bcc99ae87515452b2e9cb0303949212cd5e115c4889a44f06", size = 472938, upload-time = "2025-10-28T14:22:54.689Z" }, ] [[package]] @@ -4264,20 +4320,20 @@ wheels = [ [[package]] name = "pyparsing" -version = "3.2.5" +version = "3.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, ] [[package]] name = "pyperclip" -version = "1.10.0" +version = "1.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/99/25f4898cf420efb6f45f519de018f4faea5391114a8618b16736ef3029f1/pyperclip-1.10.0.tar.gz", hash = "sha256:180c8346b1186921c75dfd14d9048a6b5d46bfc499778811952c6dd6eb1ca6be", size = 12193, upload-time = "2025-09-18T00:54:00.384Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/bc/22540e73c5f5ae18f02924cd3954a6c9a4aa6b713c841a94c98335d333a1/pyperclip-1.10.0-py3-none-any.whl", hash = "sha256:596fbe55dc59263bff26e61d2afbe10223e2fccb5210c9c96a28d6887cfcc7ec", size = 11062, upload-time = "2025-09-18T00:53:59.252Z" }, + { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, ] [[package]] @@ -4312,7 +4368,7 @@ wheels = [ [[package]] name = "pytest" -version = "8.4.2" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -4321,22 +4377,22 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] name = "pytest-asyncio" -version = "1.2.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/86/9e3c5f48f7b7b638b216e4b9e645f54d199d7abbbab7a64a13b4e12ba10f/pytest_asyncio-1.2.0.tar.gz", hash = "sha256:c609a64a2a8768462d0c99811ddb8bd2583c33fd33cf7f21af1c142e824ffb57", size = 50119, upload-time = "2025-09-12T07:33:53.816Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/93/2fa34714b7a4ae72f2f8dad66ba17dd9a2c793220719e736dda28b7aec27/pytest_asyncio-1.2.0-py3-none-any.whl", hash = "sha256:8e17ae5e46d8e7efe51ab6494dd2010f4ca8dae51652aa3c8d55acf50bfb2e99", size = 15095, upload-time = "2025-09-12T07:33:52.639Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] @@ -4389,15 +4445,15 @@ wheels = [ [[package]] name = "pytest-subtests" -version = "0.14.2" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/59/30/6ec8dfc678ddfd1c294212bbd7088c52d3f7fbf3f05e6d8a440c13b9741a/pytest_subtests-0.14.2.tar.gz", hash = "sha256:7154a8665fd528ee70a76d00216a44d139dc3c9c83521a0f779f7b0ad4f800de", size = 18083, upload-time = "2025-06-13T10:50:01.636Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/d9/20097971a8d315e011e055d512fa120fd6be3bdb8f4b3aa3e3c6bf77bebc/pytest_subtests-0.15.0.tar.gz", hash = "sha256:cb495bde05551b784b8f0b8adfaa27edb4131469a27c339b80fd8d6ba33f887c", size = 18525, upload-time = "2025-10-20T16:26:18.358Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/d4/9bf12e59fb882b0cf4f993871e1adbee094802224c429b00861acee1a169/pytest_subtests-0.14.2-py3-none-any.whl", hash = "sha256:8da0787c994ab372a13a0ad7d390533ad2e4385cac167b3ac501258c885d0b66", size = 9115, upload-time = "2025-06-13T10:50:00.543Z" }, + { url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" }, ] [[package]] @@ -4456,9 +4512,9 @@ name = "pytools" version = "2025.2.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "siphash24", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "platformdirs" }, + { name = "siphash24" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/7b/f885a57e61ded45b5b10ca60f0b7575c9fb9a282e7513d0e23a33ee647e1/pytools-2025.2.5.tar.gz", hash = "sha256:a7f5350644d46d98ee9c7e67b4b41693308aa0f5e9b188d8f0694b27dc94e3a2", size = 85594, upload-time = "2025-10-07T15:53:30.49Z" } wheels = [ @@ -4518,28 +4574,29 @@ wheels = [ [[package]] name = "pyyaml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload-time = "2024-08-06T20:32:03.408Z" }, - { url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload-time = "2024-08-06T20:32:04.926Z" }, - { url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload-time = "2024-08-06T20:32:06.459Z" }, - { url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload-time = "2024-08-06T20:32:08.338Z" }, - { url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload-time = "2024-08-06T20:32:14.124Z" }, - { url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload-time = "2024-08-06T20:32:16.17Z" }, - { url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload-time = "2024-08-06T20:32:18.555Z" }, - { url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload-time = "2024-08-06T20:32:19.889Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload-time = "2024-08-06T20:32:21.273Z" }, - { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" }, - { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" }, - { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" }, - { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" }, - { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" }, - { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" }, - { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" }, - { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" }, +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] [[package]] @@ -4649,97 +4706,99 @@ wheels = [ [[package]] name = "ruamel-yaml" -version = "0.18.15" +version = "0.18.17" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3e/db/f3950f5e5031b618aae9f423a39bf81a55c148aecd15a34527898e752cf4/ruamel.yaml-0.18.15.tar.gz", hash = "sha256:dbfca74b018c4c3fba0b9cc9ee33e53c371194a9000e694995e620490fd40700", size = 146865, upload-time = "2025-08-19T11:15:10.694Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/2b/7a1f1ebcd6b3f14febdc003e658778d81e76b40df2267904ee6b13f0c5c6/ruamel_yaml-0.18.17.tar.gz", hash = "sha256:9091cd6e2d93a3a4b157ddb8fabf348c3de7f1fb1381346d985b6b247dcd8d3c", size = 149602, upload-time = "2025-12-17T20:02:55.757Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/e5/f2a0621f1781b76a38194acae72f01e37b1941470407345b6e8653ad7640/ruamel.yaml-0.18.15-py3-none-any.whl", hash = "sha256:148f6488d698b7a5eded5ea793a025308b25eca97208181b6a026037f391f701", size = 119702, upload-time = "2025-08-19T11:15:07.696Z" }, + { url = "https://files.pythonhosted.org/packages/af/fe/b6045c782f1fd1ae317d2a6ca1884857ce5c20f59befe6ab25a8603c43a7/ruamel_yaml-0.18.17-py3-none-any.whl", hash = "sha256:9c8ba9eb3e793efdf924b60d521820869d5bf0cb9c6f1b82d82de8295e290b9d", size = 121594, upload-time = "2025-12-17T20:02:07.657Z" }, ] [[package]] name = "ruamel-yaml-clib" -version = "0.2.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3c/fd/32c91c4d301ebe7931a3c44f7593650613afe14854e3cc9d2764321b7a46/ruamel.yaml.clib-0.2.13.tar.gz", hash = "sha256:8d4f8d7853053a5a19171a0f515f1e14f503bd3e772c4da6bafe7d0893d4f299", size = 201264, upload-time = "2025-09-22T13:33:47.268Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/86/fa6be53f11d1c663a261d137d616bbb326cb937b361a55a00cacf9ba76aa/ruamel.yaml.clib-0.2.13-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:825a7483da63b9448fdad9778269a5d02e5f64040aa8326fec071e830c2fc49c", size = 136894, upload-time = "2025-09-22T13:32:56.684Z" }, - { url = "https://files.pythonhosted.org/packages/70/f1/d26dbf3c53befd01d8b341cb29c555c1e910ef29478b4ebd50d1d67fbc64/ruamel.yaml.clib-0.2.13-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:54707e2200b9e1c34a62af09edd3ea3469a722951365311398458abe36ff45d6", size = 640033, upload-time = "2025-09-22T13:33:00.01Z" }, - { url = "https://files.pythonhosted.org/packages/a8/80/119ce9e40690b7e0dfc6bec41369333593f8738fa4f58dbbffdb9b80339b/ruamel.yaml.clib-0.2.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:958b1fc6334cd745db7099a69da4f60503cfbbc05ce1412b1d06c5922cd8314b", size = 738065, upload-time = "2025-09-22T13:32:57.712Z" }, - { url = "https://files.pythonhosted.org/packages/0b/85/09625df6b3ccf0ae0ddc93c0ab4d89f785debc0ae1e7efa541696b788b6a/ruamel.yaml.clib-0.2.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d50c415df5ba918c483a2486a90dd5a3e6df634fca688f71d266e15a618d643f", size = 700721, upload-time = "2025-09-22T13:32:58.881Z" }, - { url = "https://files.pythonhosted.org/packages/e0/2e/c05050760b20fe10cb17b3fe7efd5d5653baefa862121f5b0825418f3d8b/ruamel.yaml.clib-0.2.13-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bbdb57e57a5bb3510bfb43496bab44857546c56c7f04bb7bfd0b248388d41c2e", size = 641589, upload-time = "2025-09-22T13:33:01.482Z" }, - { url = "https://files.pythonhosted.org/packages/d7/e0/6cfd5c61070f0e5556a724c753919b8758b86fe89f5b53eb9f53d0506b27/ruamel.yaml.clib-0.2.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df8ef03eb60fc2e1c0570612363091898b97a7b3181422e1b34c3ee22d7e0a9e", size = 743806, upload-time = "2025-09-22T13:33:02.682Z" }, - { url = "https://files.pythonhosted.org/packages/85/01/14964bd94bbe809497d1affc0625e428803cc9fd21e145b1b2edcbbc3c92/ruamel.yaml.clib-0.2.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:89168de27694f7903a1317381525efdf7ea2c377b5b1eeeb927940870a7e8945", size = 769530, upload-time = "2025-09-22T13:33:04.044Z" }, - { url = "https://files.pythonhosted.org/packages/1d/c0/a39cc7de38b5acc81241dda9888ecabc1ee90fb3ebf9189ddfc65c08555d/ruamel.yaml.clib-0.2.13-cp311-cp311-win32.whl", hash = "sha256:8cdb4c720137bb7834cf2c09753d5f3468023eebb7f6ad2604ddf920328753dd", size = 100254, upload-time = "2025-09-22T13:33:06.243Z" }, - { url = "https://files.pythonhosted.org/packages/1f/7f/d5c1e0279df8dd9ca92138bec8387a07112d97598f668ccb3190d0bc06aa/ruamel.yaml.clib-0.2.13-cp311-cp311-win_amd64.whl", hash = "sha256:670d8a9c5b22af152863236b7a7fec471aafefc5e89b637b8f98d280cb92a0ce", size = 118235, upload-time = "2025-09-22T13:33:05.202Z" }, - { url = "https://files.pythonhosted.org/packages/50/ae/b6b3ed185206af04c779eec77a4a57587278b713b72034957d127307bbef/ruamel.yaml.clib-0.2.13-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:810500056a0e294131042eca6687a786e82aad817e8e0bc5ce059d2f95b67fef", size = 137955, upload-time = "2025-09-22T13:33:07.585Z" }, - { url = "https://files.pythonhosted.org/packages/6f/2b/b0307fc587cebabd8faaaeb1476c825fe4dbb5ad2b5c152dfbcc31b258c4/ruamel.yaml.clib-0.2.13-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:f8e44b1b583f93a8f4d00e5285d9ea8ccd9524cdd4c54788db2880a11f21287d", size = 645916, upload-time = "2025-09-22T13:33:11.619Z" }, - { url = "https://files.pythonhosted.org/packages/74/a9/2ddad6eb03195206aca765b4ff7bd7098c46fbc65957d5ac53f96ef8d6f4/ruamel.yaml.clib-0.2.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d1ab027be86f7d5ccc9b777a22194fd3850012df413f216bf1608768b4daa2f", size = 753115, upload-time = "2025-09-22T13:33:08.793Z" }, - { url = "https://files.pythonhosted.org/packages/42/a0/ded38d60fc34f69afef6f3e379f896a80d34532bb6b4828e95f7003be610/ruamel.yaml.clib-0.2.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:55f01d82d7d96213f0963609876ae841c81e217985c668100c5fb95cc9a564b3", size = 703451, upload-time = "2025-09-22T13:33:10.111Z" }, - { url = "https://files.pythonhosted.org/packages/3e/49/121067d5621a77913dc0fd473bdf2ec4a515c564806b107d3b1257cd5b14/ruamel.yaml.clib-0.2.13-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:031c7cdd5751cda19ddf90e42756ec2ee72a30791c2f65f5f800f9ee929862f0", size = 647403, upload-time = "2025-09-22T13:33:12.778Z" }, - { url = "https://files.pythonhosted.org/packages/15/fc/0845929db1840eec6aa6133b8c89941e251c3bc67180aee7d71a30bc1c80/ruamel.yaml.clib-0.2.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba745411caabc994963729663a7c2d09398795c36dc4b5672ca4fdc445ab6544", size = 745860, upload-time = "2025-09-22T13:33:13.99Z" }, - { url = "https://files.pythonhosted.org/packages/d6/0a/dd0007d41a321edf64c4bda0aab70936281ee213406e6913105499f7d62a/ruamel.yaml.clib-0.2.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb90267cc3748858236b9cedb580191be1d0d218f6cde64e954fa759d90fb08d", size = 770202, upload-time = "2025-09-22T13:33:15.181Z" }, - { url = "https://files.pythonhosted.org/packages/93/0d/eeed0a9824b14fa089a82247fca96de6b202af84e4093ba2a15afc0b4cb9/ruamel.yaml.clib-0.2.13-cp312-cp312-win32.whl", hash = "sha256:5188c3212c10fcd14a6de8ce787c0713a49fc5972054703f790cfd9f1d27a90b", size = 98831, upload-time = "2025-09-22T13:33:17.362Z" }, - { url = "https://files.pythonhosted.org/packages/25/45/c882a32e4b5c0ed6a5b4e0933af427fef48f3aad7259b87f38e33ee20536/ruamel.yaml.clib-0.2.13-cp312-cp312-win_amd64.whl", hash = "sha256:518b13f9fe601a559a759f3d21cdb2590cb85611934a7c0f09c09dc1a869ec83", size = 115567, upload-time = "2025-09-22T13:33:16.358Z" }, +version = "0.2.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/80/8ce7b9af532aa94dd83360f01ce4716264db73de6bc8efd22c32341f6658/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd", size = 147998, upload-time = "2025-11-16T16:13:13.241Z" }, + { url = "https://files.pythonhosted.org/packages/53/09/de9d3f6b6701ced5f276d082ad0f980edf08ca67114523d1b9264cd5e2e0/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137", size = 132743, upload-time = "2025-11-16T16:13:14.265Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f7/73a9b517571e214fe5c246698ff3ed232f1ef863c8ae1667486625ec688a/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401", size = 731459, upload-time = "2025-11-16T20:22:44.338Z" }, + { url = "https://files.pythonhosted.org/packages/9b/a2/0dc0013169800f1c331a6f55b1282c1f4492a6d32660a0cf7b89e6684919/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262", size = 749289, upload-time = "2025-11-16T16:13:15.633Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ed/3fb20a1a96b8dc645d88c4072df481fe06e0289e4d528ebbdcc044ebc8b3/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f", size = 777630, upload-time = "2025-11-16T16:13:16.898Z" }, + { url = "https://files.pythonhosted.org/packages/60/50/6842f4628bc98b7aa4733ab2378346e1441e150935ad3b9f3c3c429d9408/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d", size = 744368, upload-time = "2025-11-16T16:13:18.117Z" }, + { url = "https://files.pythonhosted.org/packages/d3/b0/128ae8e19a7d794c2e36130a72b3bb650ce1dd13fb7def6cf10656437dcf/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922", size = 745233, upload-time = "2025-11-16T20:22:45.833Z" }, + { url = "https://files.pythonhosted.org/packages/75/05/91130633602d6ba7ce3e07f8fc865b40d2a09efd4751c740df89eed5caf9/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490", size = 770963, upload-time = "2025-11-16T16:13:19.344Z" }, + { url = "https://files.pythonhosted.org/packages/fd/4b/fd4542e7f33d7d1bc64cc9ac9ba574ce8cf145569d21f5f20133336cdc8c/ruamel_yaml_clib-0.2.15-cp311-cp311-win32.whl", hash = "sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c", size = 102640, upload-time = "2025-11-16T16:13:20.498Z" }, + { url = "https://files.pythonhosted.org/packages/bb/eb/00ff6032c19c7537371e3119287999570867a0eafb0154fccc80e74bf57a/ruamel_yaml_clib-0.2.15-cp311-cp311-win_amd64.whl", hash = "sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e", size = 121996, upload-time = "2025-11-16T16:13:21.855Z" }, + { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, + { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, + { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, + { url = "https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60", size = 753349, upload-time = "2025-11-16T16:13:26.269Z" }, + { url = "https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9", size = 788211, upload-time = "2025-11-16T16:13:27.441Z" }, + { url = "https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642", size = 743203, upload-time = "2025-11-16T16:13:28.671Z" }, + { url = "https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690", size = 747292, upload-time = "2025-11-16T20:22:48.584Z" }, + { url = "https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a", size = 771624, upload-time = "2025-11-16T16:13:29.853Z" }, + { url = "https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144", size = 100342, upload-time = "2025-11-16T16:13:31.067Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc", size = 119013, upload-time = "2025-11-16T16:13:32.164Z" }, ] [[package]] name = "rubicon-objc" -version = "0.5.2" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/2f/612049b8601e810080f63f4b85acbf2ed0784349088d3c944eb288e1d487/rubicon_objc-0.5.2.tar.gz", hash = "sha256:1180593935f6a8a39c23b5f4b7baa24aedf9f7285e80804a1d9d6b50a50572f5", size = 175710, upload-time = "2025-08-07T06:12:25.194Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/d2/d39ecd205661a5c14c90dbd92a722a203848a3621785c9783716341de427/rubicon_objc-0.5.3.tar.gz", hash = "sha256:74c25920c5951a05db9d3a1aac31d23816ec7dacc841a5b124d911b99ea71b9a", size = 171512, upload-time = "2025-12-03T03:51:10.264Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/a4/11ad29100060af56408ed084dca76a16d2ce8eb31b75081bfc0eec45d755/rubicon_objc-0.5.2-py3-none-any.whl", hash = "sha256:829b253c579e51fc34f4bb6587c34806e78960dcc1eb24e62b38141a1fe02b39", size = 63512, upload-time = "2025-08-07T06:12:23.766Z" }, + { url = "https://files.pythonhosted.org/packages/93/ab/e834c01138c272fb2e37d2f3c7cba708bc694dbc7b3f03b743f29ceb92d5/rubicon_objc-0.5.3-py3-none-any.whl", hash = "sha256:31dedcda9be38435f5ec067906e1eea5d0ddb790330e98a22e94ff424758b415", size = 64414, upload-time = "2025-12-03T03:51:09.082Z" }, ] [[package]] name = "ruff" -version = "0.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ab/33/c8e89216845615d14d2d42ba2bee404e7206a8db782f33400754f3799f05/ruff-0.13.1.tar.gz", hash = "sha256:88074c3849087f153d4bb22e92243ad4c1b366d7055f98726bc19aa08dc12d51", size = 5397987, upload-time = "2025-09-18T19:52:44.33Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/41/ca37e340938f45cfb8557a97a5c347e718ef34702546b174e5300dbb1f28/ruff-0.13.1-py3-none-linux_armv6l.whl", hash = "sha256:b2abff595cc3cbfa55e509d89439b5a09a6ee3c252d92020bd2de240836cf45b", size = 12304308, upload-time = "2025-09-18T19:51:56.253Z" }, - { url = "https://files.pythonhosted.org/packages/ff/84/ba378ef4129415066c3e1c80d84e539a0d52feb250685091f874804f28af/ruff-0.13.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4ee9f4249bf7f8bb3984c41bfaf6a658162cdb1b22e3103eabc7dd1dc5579334", size = 12937258, upload-time = "2025-09-18T19:52:00.184Z" }, - { url = "https://files.pythonhosted.org/packages/8d/b6/ec5e4559ae0ad955515c176910d6d7c93edcbc0ed1a3195a41179c58431d/ruff-0.13.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5c5da4af5f6418c07d75e6f3224e08147441f5d1eac2e6ce10dcce5e616a3bae", size = 12214554, upload-time = "2025-09-18T19:52:02.753Z" }, - { url = "https://files.pythonhosted.org/packages/70/d6/cb3e3b4f03b9b0c4d4d8f06126d34b3394f6b4d764912fe80a1300696ef6/ruff-0.13.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80524f84a01355a59a93cef98d804e2137639823bcee2931f5028e71134a954e", size = 12448181, upload-time = "2025-09-18T19:52:05.279Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ea/bf60cb46d7ade706a246cd3fb99e4cfe854efa3dfbe530d049c684da24ff/ruff-0.13.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff7f5ce8d7988767dd46a148192a14d0f48d1baea733f055d9064875c7d50389", size = 12104599, upload-time = "2025-09-18T19:52:07.497Z" }, - { url = "https://files.pythonhosted.org/packages/2d/3e/05f72f4c3d3a69e65d55a13e1dd1ade76c106d8546e7e54501d31f1dc54a/ruff-0.13.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c55d84715061f8b05469cdc9a446aa6c7294cd4bd55e86a89e572dba14374f8c", size = 13791178, upload-time = "2025-09-18T19:52:10.189Z" }, - { url = "https://files.pythonhosted.org/packages/81/e7/01b1fc403dd45d6cfe600725270ecc6a8f8a48a55bc6521ad820ed3ceaf8/ruff-0.13.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:ac57fed932d90fa1624c946dc67a0a3388d65a7edc7d2d8e4ca7bddaa789b3b0", size = 14814474, upload-time = "2025-09-18T19:52:12.866Z" }, - { url = "https://files.pythonhosted.org/packages/fa/92/d9e183d4ed6185a8df2ce9faa3f22e80e95b5f88d9cc3d86a6d94331da3f/ruff-0.13.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c366a71d5b4f41f86a008694f7a0d75fe409ec298685ff72dc882f882d532e36", size = 14217531, upload-time = "2025-09-18T19:52:15.245Z" }, - { url = "https://files.pythonhosted.org/packages/3b/4a/6ddb1b11d60888be224d721e01bdd2d81faaf1720592858ab8bac3600466/ruff-0.13.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4ea9d1b5ad3e7a83ee8ebb1229c33e5fe771e833d6d3dcfca7b77d95b060d38", size = 13265267, upload-time = "2025-09-18T19:52:17.649Z" }, - { url = "https://files.pythonhosted.org/packages/81/98/3f1d18a8d9ea33ef2ad508f0417fcb182c99b23258ec5e53d15db8289809/ruff-0.13.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0f70202996055b555d3d74b626406476cc692f37b13bac8828acff058c9966a", size = 13243120, upload-time = "2025-09-18T19:52:20.332Z" }, - { url = "https://files.pythonhosted.org/packages/8d/86/b6ce62ce9c12765fa6c65078d1938d2490b2b1d9273d0de384952b43c490/ruff-0.13.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:f8cff7a105dad631085d9505b491db33848007d6b487c3c1979dd8d9b2963783", size = 13443084, upload-time = "2025-09-18T19:52:23.032Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/af7943466a41338d04503fb5a81b2fd07251bd272f546622e5b1599a7976/ruff-0.13.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9761e84255443316a258dd7dfbd9bfb59c756e52237ed42494917b2577697c6a", size = 12295105, upload-time = "2025-09-18T19:52:25.263Z" }, - { url = "https://files.pythonhosted.org/packages/3f/97/0249b9a24f0f3ebd12f007e81c87cec6d311de566885e9309fcbac5b24cc/ruff-0.13.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:3d376a88c3102ef228b102211ef4a6d13df330cb0f5ca56fdac04ccec2a99700", size = 12072284, upload-time = "2025-09-18T19:52:27.478Z" }, - { url = "https://files.pythonhosted.org/packages/f6/85/0b64693b2c99d62ae65236ef74508ba39c3febd01466ef7f354885e5050c/ruff-0.13.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cbefd60082b517a82c6ec8836989775ac05f8991715d228b3c1d86ccc7df7dae", size = 12970314, upload-time = "2025-09-18T19:52:30.212Z" }, - { url = "https://files.pythonhosted.org/packages/96/fc/342e9f28179915d28b3747b7654f932ca472afbf7090fc0c4011e802f494/ruff-0.13.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dd16b9a5a499fe73f3c2ef09a7885cb1d97058614d601809d37c422ed1525317", size = 13422360, upload-time = "2025-09-18T19:52:32.676Z" }, - { url = "https://files.pythonhosted.org/packages/37/54/6177a0dc10bce6f43e392a2192e6018755473283d0cf43cc7e6afc182aea/ruff-0.13.1-py3-none-win32.whl", hash = "sha256:55e9efa692d7cb18580279f1fbb525146adc401f40735edf0aaeabd93099f9a0", size = 12178448, upload-time = "2025-09-18T19:52:35.545Z" }, - { url = "https://files.pythonhosted.org/packages/64/51/c6a3a33d9938007b8bdc8ca852ecc8d810a407fb513ab08e34af12dc7c24/ruff-0.13.1-py3-none-win_amd64.whl", hash = "sha256:3a3fb595287ee556de947183489f636b9f76a72f0fa9c028bdcabf5bab2cc5e5", size = 13286458, upload-time = "2025-09-18T19:52:38.198Z" }, - { url = "https://files.pythonhosted.org/packages/fd/04/afc078a12cf68592345b1e2d6ecdff837d286bac023d7a22c54c7a698c5b/ruff-0.13.1-py3-none-win_arm64.whl", hash = "sha256:c0bae9ffd92d54e03c2bf266f466da0a65e145f298ee5b5846ed435f6a00518a", size = 12437893, upload-time = "2025-09-18T19:52:41.283Z" }, +version = "0.14.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, + { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, + { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, + { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, + { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, + { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, + { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, + { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, + { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, + { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, + { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, + { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, + { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, + { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, ] [[package]] name = "scons" -version = "4.9.1" +version = "4.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/c1/30176c76c1ef723fab62e5cdb15d3c972427a146cb6f868748613d7b25af/scons-4.9.1.tar.gz", hash = "sha256:bacac880ba2e86d6a156c116e2f8f2bfa82b257046f3ac2666c85c53c615c338", size = 3252106, upload-time = "2025-03-27T20:42:19.765Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/c9/2f430bb39e4eccba32ce8008df4a3206df651276422204e177a09e12b30b/scons-4.10.1.tar.gz", hash = "sha256:99c0e94a42a2c1182fa6859b0be697953db07ba936ecc9817ae0d218ced20b15", size = 3258403, upload-time = "2025-11-16T22:43:39.258Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/45/92/50b739021983b131dcacd57aa8b04d31c5acc2e7e0eb4ed4a362f438c6b7/scons-4.9.1-py3-none-any.whl", hash = "sha256:d2db1a22eb6039e97cbbb0106f18f435af033d0aad190299c9688378e2810a5e", size = 4131331, upload-time = "2025-03-27T20:42:16.665Z" }, + { url = "https://files.pythonhosted.org/packages/ce/bf/931fb9fbb87234c32b8b1b1c15fba23472a10777c12043336675633809a7/scons-4.10.1-py3-none-any.whl", hash = "sha256:bd9d1c52f908d874eba92a8c0c0a8dcf2ed9f3b88ab956d0fce1da479c4e7126", size = 4136069, upload-time = "2025-11-16T22:43:35.933Z" }, ] [[package]] name = "sentry-sdk" -version = "2.38.0" +version = "2.48.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b2/22/60fd703b34d94d216b2387e048ac82de3e86b63bc28869fb076f8bb0204a/sentry_sdk-2.38.0.tar.gz", hash = "sha256:792d2af45e167e2f8a3347143f525b9b6bac6f058fb2014720b40b84ccbeb985", size = 348116, upload-time = "2025-09-15T15:00:37.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/f0/0e9dc590513d5e742d7799e2038df3a05167cba084c6ca4f3cdd75b55164/sentry_sdk-2.48.0.tar.gz", hash = "sha256:5213190977ff7fdff8a58b722fb807f8d5524a80488626ebeda1b5676c0c1473", size = 384828, upload-time = "2025-12-16T14:55:41.722Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/84/bde4c4bbb269b71bc09316af8eb00da91f67814d40337cc12ef9c8742541/sentry_sdk-2.38.0-py2.py3-none-any.whl", hash = "sha256:2324aea8573a3fa1576df7fb4d65c4eb8d9929c8fa5939647397a07179eef8d0", size = 370346, upload-time = "2025-09-15T15:00:35.821Z" }, + { url = "https://files.pythonhosted.org/packages/4d/19/8d77f9992e5cbfcaa9133c3bf63b4fbbb051248802e1e803fed5c552fbb2/sentry_sdk-2.48.0-py2.py3-none-any.whl", hash = "sha256:6b12ac256769d41825d9b7518444e57fa35b5642df4c7c5e322af4d2c8721172", size = 414555, upload-time = "2025-12-16T14:55:40.152Z" }, ] [[package]] @@ -4784,29 +4843,29 @@ wheels = [ [[package]] name = "shapely" -version = "2.1.1" +version = "2.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ca/3c/2da625233f4e605155926566c0e7ea8dda361877f48e8b1655e53456f252/shapely-2.1.1.tar.gz", hash = "sha256:500621967f2ffe9642454808009044c21e5b35db89ce69f8a2042c2ffd0e2772", size = 315422, upload-time = "2025-05-19T11:04:41.265Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/97/2df985b1e03f90c503796ad5ecd3d9ed305123b64d4ccb54616b30295b29/shapely-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587a1aa72bc858fab9b8c20427b5f6027b7cbc92743b8e2c73b9de55aa71c7a7", size = 1819368, upload-time = "2025-05-19T11:03:55.937Z" }, - { url = "https://files.pythonhosted.org/packages/56/17/504518860370f0a28908b18864f43d72f03581e2b6680540ca668f07aa42/shapely-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fa5c53b0791a4b998f9ad84aad456c988600757a96b0a05e14bba10cebaaaea", size = 1625362, upload-time = "2025-05-19T11:03:57.06Z" }, - { url = "https://files.pythonhosted.org/packages/36/a1/9677337d729b79fce1ef3296aac6b8ef4743419086f669e8a8070eff8f40/shapely-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aabecd038841ab5310d23495253f01c2a82a3aedae5ab9ca489be214aa458aa7", size = 2999005, upload-time = "2025-05-19T11:03:58.692Z" }, - { url = "https://files.pythonhosted.org/packages/a2/17/e09357274699c6e012bbb5a8ea14765a4d5860bb658df1931c9f90d53bd3/shapely-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:586f6aee1edec04e16227517a866df3e9a2e43c1f635efc32978bb3dc9c63753", size = 3108489, upload-time = "2025-05-19T11:04:00.059Z" }, - { url = "https://files.pythonhosted.org/packages/17/5d/93a6c37c4b4e9955ad40834f42b17260ca74ecf36df2e81bb14d12221b90/shapely-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b9878b9e37ad26c72aada8de0c9cfe418d9e2ff36992a1693b7f65a075b28647", size = 3945727, upload-time = "2025-05-19T11:04:01.786Z" }, - { url = "https://files.pythonhosted.org/packages/a3/1a/ad696648f16fd82dd6bfcca0b3b8fbafa7aacc13431c7fc4c9b49e481681/shapely-2.1.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9a531c48f289ba355e37b134e98e28c557ff13965d4653a5228d0f42a09aed0", size = 4109311, upload-time = "2025-05-19T11:04:03.134Z" }, - { url = "https://files.pythonhosted.org/packages/d4/38/150dd245beab179ec0d4472bf6799bf18f21b1efbef59ac87de3377dbf1c/shapely-2.1.1-cp311-cp311-win32.whl", hash = "sha256:4866de2673a971820c75c0167b1f1cd8fb76f2d641101c23d3ca021ad0449bab", size = 1522982, upload-time = "2025-05-19T11:04:05.217Z" }, - { url = "https://files.pythonhosted.org/packages/93/5b/842022c00fbb051083c1c85430f3bb55565b7fd2d775f4f398c0ba8052ce/shapely-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:20a9d79958b3d6c70d8a886b250047ea32ff40489d7abb47d01498c704557a93", size = 1703872, upload-time = "2025-05-19T11:04:06.791Z" }, - { url = "https://files.pythonhosted.org/packages/fb/64/9544dc07dfe80a2d489060791300827c941c451e2910f7364b19607ea352/shapely-2.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2827365b58bf98efb60affc94a8e01c56dd1995a80aabe4b701465d86dcbba43", size = 1833021, upload-time = "2025-05-19T11:04:08.022Z" }, - { url = "https://files.pythonhosted.org/packages/07/aa/fb5f545e72e89b6a0f04a0effda144f5be956c9c312c7d4e00dfddbddbcf/shapely-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9c551f7fa7f1e917af2347fe983f21f212863f1d04f08eece01e9c275903fad", size = 1643018, upload-time = "2025-05-19T11:04:09.343Z" }, - { url = "https://files.pythonhosted.org/packages/03/46/61e03edba81de729f09d880ce7ae5c1af873a0814206bbfb4402ab5c3388/shapely-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78dec4d4fbe7b1db8dc36de3031767e7ece5911fb7782bc9e95c5cdec58fb1e9", size = 2986417, upload-time = "2025-05-19T11:04:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/1f/1e/83ec268ab8254a446b4178b45616ab5822d7b9d2b7eb6e27cf0b82f45601/shapely-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:872d3c0a7b8b37da0e23d80496ec5973c4692920b90de9f502b5beb994bbaaef", size = 3098224, upload-time = "2025-05-19T11:04:11.903Z" }, - { url = "https://files.pythonhosted.org/packages/f1/44/0c21e7717c243e067c9ef8fa9126de24239f8345a5bba9280f7bb9935959/shapely-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2e2b9125ebfbc28ecf5353511de62f75a8515ae9470521c9a693e4bb9fbe0cf1", size = 3925982, upload-time = "2025-05-19T11:04:13.224Z" }, - { url = "https://files.pythonhosted.org/packages/15/50/d3b4e15fefc103a0eb13d83bad5f65cd6e07a5d8b2ae920e767932a247d1/shapely-2.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4b96cea171b3d7f6786976a0520f178c42792897653ecca0c5422fb1e6946e6d", size = 4089122, upload-time = "2025-05-19T11:04:14.477Z" }, - { url = "https://files.pythonhosted.org/packages/bd/05/9a68f27fc6110baeedeeebc14fd86e73fa38738c5b741302408fb6355577/shapely-2.1.1-cp312-cp312-win32.whl", hash = "sha256:39dca52201e02996df02e447f729da97cfb6ff41a03cb50f5547f19d02905af8", size = 1522437, upload-time = "2025-05-19T11:04:16.203Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e9/a4560e12b9338842a1f82c9016d2543eaa084fce30a1ca11991143086b57/shapely-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:13d643256f81d55a50013eff6321142781cf777eb6a9e207c2c9e6315ba6044a", size = 1703479, upload-time = "2025-05-19T11:04:18.497Z" }, + { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038, upload-time = "2025-09-24T13:50:15.628Z" }, + { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039, upload-time = "2025-09-24T13:50:16.881Z" }, + { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519, upload-time = "2025-09-24T13:50:18.606Z" }, + { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842, upload-time = "2025-09-24T13:50:21.77Z" }, + { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316, upload-time = "2025-09-24T13:50:23.626Z" }, + { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586, upload-time = "2025-09-24T13:50:25.443Z" }, + { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961, upload-time = "2025-09-24T13:50:26.968Z" }, + { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856, upload-time = "2025-09-24T13:50:28.497Z" }, + { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, + { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, + { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, + { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" }, + { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" }, + { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, ] [[package]] @@ -4840,11 +4899,11 @@ wheels = [ [[package]] name = "smbus2" -version = "0.5.0" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/c9/6d85aa809e107adf85303010a59b340be109c8f815cbedc5c08c73bcffef/smbus2-0.5.0.tar.gz", hash = "sha256:4a5946fd82277870c2878befdb1a29bb28d15cda14ea4d8d2d54cf3d4bdcb035", size = 16950, upload-time = "2024-10-19T09:20:56.746Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/36/afafd43770caae69f04e21402552a8f94a072def46a002fab9357f4852ce/smbus2-0.6.0.tar.gz", hash = "sha256:9b5ff1e998e114730f9dfe0c4babbef06c92468cfb61eaa684e30f225661b95b", size = 17403, upload-time = "2025-12-20T09:02:52.017Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/9f/2235ba9001e3c29fc342eeb222104420bcb7bac51555f0c034376a744075/smbus2-0.5.0-py2.py3-none-any.whl", hash = "sha256:1a15c3b9fa69357beb038cc0b5d37939702f8bfde1ddc89ca9f17d8461dbe949", size = 11527, upload-time = "2024-10-19T09:20:55.202Z" }, + { url = "https://files.pythonhosted.org/packages/a5/cf/2e1d6805da6f9c9b3a4358076ff2e072d828ba7fed124edc1b729e210c55/smbus2-0.6.0-py2.py3-none-any.whl", hash = "sha256:03d83d2a9a4afc5ddca0698ccabf101cb3de52bc5aefd7b76778ffb27ff654e0", size = 11849, upload-time = "2025-12-20T09:02:51.219Z" }, ] [[package]] @@ -4858,17 +4917,17 @@ wheels = [ [[package]] name = "sounddevice" -version = "0.5.2" +version = "0.5.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/91/a6/91e9f08ed37c7c9f56b5227c6aea7f2ae63ba2d59520eefb24e82cbdd589/sounddevice-0.5.2.tar.gz", hash = "sha256:c634d51bd4e922d6f0fa5e1a975cc897c947f61d31da9f79ba7ea34dff448b49", size = 53150, upload-time = "2025-05-16T18:12:27.339Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/4f/28e734898b870db15b6474453f19813d3c81b91c806d9e6f867bd6e4dd03/sounddevice-0.5.3.tar.gz", hash = "sha256:cbac2b60198fbab84533697e7c4904cc895ec69d5fb3973556c9eb74a4629b2c", size = 53465, upload-time = "2025-10-19T13:23:57.922Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/2d/582738fc01352a5bc20acac9221e58538365cecb3bb264838f66419df219/sounddevice-0.5.2-py3-none-any.whl", hash = "sha256:82375859fac2e73295a4ab3fc60bd4782743157adc339561c1f1142af472f505", size = 32450, upload-time = "2025-05-16T18:12:21.919Z" }, - { url = "https://files.pythonhosted.org/packages/3f/6f/e3dd751face4fcb5be25e8abba22f25d8e6457ebd7e9ed79068b768dc0e5/sounddevice-0.5.2-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:943f27e66037d41435bdd0293454072cdf657b594c9cde63cd01ee3daaac7ab3", size = 108088, upload-time = "2025-05-16T18:12:23.146Z" }, - { url = "https://files.pythonhosted.org/packages/45/0b/bfad79af0b380aa7c0bfe73e4b03e0af45354a48ad62549489bd7696c5b0/sounddevice-0.5.2-py3-none-win32.whl", hash = "sha256:3a113ce614a2c557f14737cb20123ae6298c91fc9301eb014ada0cba6d248c5f", size = 312665, upload-time = "2025-05-16T18:12:24.726Z" }, - { url = "https://files.pythonhosted.org/packages/e1/3e/61d88e6b0a7383127cdc779195cb9d83ebcf11d39bc961de5777e457075e/sounddevice-0.5.2-py3-none-win_amd64.whl", hash = "sha256:e18944b767d2dac3771a7771bdd7ff7d3acd7d334e72c4bedab17d1aed5dbc22", size = 363808, upload-time = "2025-05-16T18:12:26Z" }, + { url = "https://files.pythonhosted.org/packages/73/e7/9020e9f0f3df00432728f4c4044387468a743e3d9a4f91123d77be10010e/sounddevice-0.5.3-py3-none-any.whl", hash = "sha256:ea7738baa0a9f9fef7390f649e41c9f2c8ada776180e56c2ffd217133c92a806", size = 32670, upload-time = "2025-10-19T13:23:51.779Z" }, + { url = "https://files.pythonhosted.org/packages/2f/39/714118f8413e0e353436914f2b976665161f1be2b6483ac15a8f61484c14/sounddevice-0.5.3-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:278dc4451fff70934a176df048b77d80d7ce1623a6ec9db8b34b806f3112f9c2", size = 108306, upload-time = "2025-10-19T13:23:53.277Z" }, + { url = "https://files.pythonhosted.org/packages/f5/74/52186e3e5c833d00273f7949a9383adff93692c6e02406bf359cb4d3e921/sounddevice-0.5.3-py3-none-win32.whl", hash = "sha256:845d6927bcf14e84be5292a61ab3359cf8e6b9145819ec6f3ac2619ff089a69c", size = 312882, upload-time = "2025-10-19T13:23:54.829Z" }, + { url = "https://files.pythonhosted.org/packages/66/c7/16123d054aef6d445176c9122bfbe73c11087589b2413cab22aff5a7839a/sounddevice-0.5.3-py3-none-win_amd64.whl", hash = "sha256:f55ad20082efc2bdec06928e974fbcae07bc6c405409ae1334cefe7d377eb687", size = 364025, upload-time = "2025-10-19T13:23:56.362Z" }, ] [[package]] @@ -4967,11 +5026,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload-time = "2025-06-18T14:07:41.644Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload-time = "2025-06-18T14:07:40.39Z" }, + { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, ] [[package]] @@ -5000,40 +5059,36 @@ wheels = [ [[package]] name = "websocket-client" -version = "1.8.0" +version = "1.9.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, + { url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" }, ] [[package]] name = "xattr" -version = "1.2.0" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/65/14438ae55acf7f8fc396ee8340d740a3e1d6ef382bf25bf24156cfb83563/xattr-1.2.0.tar.gz", hash = "sha256:a64c8e21eff1be143accf80fd3b8fde3e28a478c37da298742af647ac3e5e0a7", size = 17293, upload-time = "2025-07-14T03:15:44.884Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/e2/bf74df197a415f25e07378bfa301788e3bf2ac029c3a6c7bd56a900934ff/xattr-1.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:00c26c14c90058338993bb2d3e1cebf562e94ec516cafba64a8f34f74b9d18b4", size = 24246, upload-time = "2025-07-14T03:14:34.873Z" }, - { url = "https://files.pythonhosted.org/packages/a5/51/922df424556ff35b20ca043da5e4dcf0f99cbcb674f59046d08ceff3ebc7/xattr-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b4f43dc644db87d5eb9484a9518c34a864cb2e588db34cffc42139bf55302a1c", size = 19212, upload-time = "2025-07-14T03:14:35.905Z" }, - { url = "https://files.pythonhosted.org/packages/7c/72/1ed37812e8285c8002b8834395c53cc89a2d83aa088db642b217be439017/xattr-1.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c7602583fc643ca76576498e2319c7cef0b72aef1936701678589da6371b731b", size = 19546, upload-time = "2025-07-14T03:14:37.242Z" }, - { url = "https://files.pythonhosted.org/packages/d4/b8/ec75db23d81beec68e3be20ea176c11f125697d3bbb5e118b9de9ea7a9ab/xattr-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90c3ad4a9205cceb64ec54616aa90aa42d140c8ae3b9710a0aaa2843a6f1aca7", size = 39426, upload-time = "2025-07-14T03:14:38.264Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/c24950641b138072eda7f34d86966dd15cfe3af9a111b5e77b85ee55f99c/xattr-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83d87cfe19cd606fc0709d45a4d6efc276900797deced99e239566926a5afedf", size = 37311, upload-time = "2025-07-14T03:14:39.347Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d5/3b7e0dab706d09c6cdb2f05384610e6c5693c72e3794d54a4cad8c838373/xattr-1.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67dabd9ddc04ead63fbc85aed459c9afcc24abfc5bb3217fff7ec9a466faacb", size = 39222, upload-time = "2025-07-14T03:14:40.768Z" }, - { url = "https://files.pythonhosted.org/packages/0e/16/80cf8ec7d92d20b2860c96a1eca18d25e27fa4770f32c9e8250ff32e7386/xattr-1.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9a18ee82d8ba2c17f1e8414bfeb421fa763e0fb4acbc1e124988ca1584ad32d5", size = 38694, upload-time = "2025-07-14T03:14:41.93Z" }, - { url = "https://files.pythonhosted.org/packages/38/c0/b154b254e6e4596aed3210dd48b2e82d958b16d9a7f65346b9154968d2d0/xattr-1.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:38de598c47b85185e745986a061094d2e706e9c2d9022210d2c738066990fe91", size = 37055, upload-time = "2025-07-14T03:14:43.435Z" }, - { url = "https://files.pythonhosted.org/packages/dc/1d/3a615660849ef9bdf46d04f9c6d40ee082f7427678013ff85452ed9497db/xattr-1.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:15e754e854bdaac366ad3f1c8fbf77f6668e8858266b4246e8c5f487eeaf1179", size = 38275, upload-time = "2025-07-14T03:14:45.18Z" }, - { url = "https://files.pythonhosted.org/packages/37/e5/b048a5f6c5a489915026b70b9133242a2a368383ddab24e4e3a5bdba7ebd/xattr-1.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:daff0c1f5c5e4eaf758c56259c4f72631fa9619875e7a25554b6077dc73da964", size = 24240, upload-time = "2025-07-14T03:14:46.173Z" }, - { url = "https://files.pythonhosted.org/packages/cc/f5/d795774f719a0be6137041d4833ca00b178f816e538948548dff79530f34/xattr-1.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:109b11fb3f73a0d4e199962f11230ab5f462e85a8021874f96c1732aa61148d5", size = 19218, upload-time = "2025-07-14T03:14:47.412Z" }, - { url = "https://files.pythonhosted.org/packages/cb/8b/65f3bed09ca9ced27bbba8d4a3326f14a58b98ac102163d85b545f81d9c2/xattr-1.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7c7c12968ce0bf798d8ba90194cef65de768bee9f51a684e022c74cab4218305", size = 19539, upload-time = "2025-07-14T03:14:48.413Z" }, - { url = "https://files.pythonhosted.org/packages/96/2d/01ecfdf41ce70f7e29c8a21e730de3c157fb1cb84391923581af81a44c45/xattr-1.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37989dabf25ff18773e4aaeebcb65604b9528f8645f43e02bebaa363e3ae958", size = 39631, upload-time = "2025-07-14T03:14:49.665Z" }, - { url = "https://files.pythonhosted.org/packages/c9/e9/15cbf9c59cf1117e3c45dd429c52f9dab25d95e65ac245c5ad9532986bec/xattr-1.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:165de92b0f2adafb336f936931d044619b9840e35ba01079f4dd288747b73714", size = 37552, upload-time = "2025-07-14T03:14:50.718Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f5/cb4dad87843fe79d605cf5d10caad80e2c338a06f0363f1443449185f489/xattr-1.2.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82191c006ae4c609b22b9aea5f38f68fff022dc6884c4c0e1dba329effd4b288", size = 39472, upload-time = "2025-07-14T03:14:51.74Z" }, - { url = "https://files.pythonhosted.org/packages/5a/d9/012df7b814cc4a0ae41afb59ac31d0469227397b29f58c1377e8db0f34ba/xattr-1.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2b2e9c87dc643b09d86befad218e921f6e65b59a4668d6262b85308de5dbd1dd", size = 38802, upload-time = "2025-07-14T03:14:52.801Z" }, - { url = "https://files.pythonhosted.org/packages/d8/08/e107a5d294a816586f274c33aea480fe740fd446276efc84c067e6c82de2/xattr-1.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:14edd5d47d0bb92b23222c0bb6379abbddab01fb776b2170758e666035ecf3aa", size = 37125, upload-time = "2025-07-14T03:14:54.313Z" }, - { url = "https://files.pythonhosted.org/packages/3e/6c/a6f9152e10543af67ea277caae7c5a6400a581e407c42156ffce71dd8242/xattr-1.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:12183d5eb104d4da787638c7dadf63b718472d92fec6dbe12994ea5d094d7863", size = 38456, upload-time = "2025-07-14T03:14:55.383Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/08/d5/25f7b19af3a2cb4000cac4f9e5525a40bec79f4f5d0ac9b517c0544586a0/xattr-1.3.0.tar.gz", hash = "sha256:30439fabd7de0787b27e9a6e1d569c5959854cb322f64ce7380fedbfa5035036", size = 17148, upload-time = "2025-10-13T22:16:47.353Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/64/292426ad5653e72c6e1325bbff22868a20077290d967cebb9c0624ad08b6/xattr-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:331a51bf8f20c27822f44054b0d760588462d3ed472d5e52ba135cf0bea510e8", size = 23448, upload-time = "2025-10-13T22:15:59.229Z" }, + { url = "https://files.pythonhosted.org/packages/63/84/6539fbe620da8e5927406e76b9c8abad8953025d5f578d792747c38a8c0e/xattr-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:196360f068b74fa0132a8c6001ce1333f095364b8f43b6fd8cdaf2f18741ef89", size = 18553, upload-time = "2025-10-13T22:16:00.151Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bb/c1c2e24a49f8d13ff878fb85aabc42ea1b2f98ce08d8205b9661d517a9cc/xattr-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:405d2e4911d37f2b9400fa501acd920fe0c97fe2b2ec252cb23df4b59c000811", size = 18848, upload-time = "2025-10-13T22:16:01.046Z" }, + { url = "https://files.pythonhosted.org/packages/02/c2/a60aad150322b217dfe33695d8d9f32bc01e8f300641b6ba4b73f4b3c03f/xattr-1.3.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ae3a66ae1effd40994f64defeeaa97da369406485e60bfb421f2d781be3b75d", size = 38547, upload-time = "2025-10-13T22:16:01.973Z" }, + { url = "https://files.pythonhosted.org/packages/c6/58/2eca142bad4ea0a2be6b58d3122d0acce310c4e53fa7defd168202772178/xattr-1.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:69cd3bfe779f7ba87abe6473fdfa428460cf9e78aeb7e390cfd737b784edf1b5", size = 38753, upload-time = "2025-10-13T22:16:03.244Z" }, + { url = "https://files.pythonhosted.org/packages/2b/50/d032e5254c2c27d36bdb02abdf2735db6768a441f0e3d0f139e0f9f56638/xattr-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c5742ca61761a99ae0c522f90a39d5fb8139280f27b254e3128482296d1df2db", size = 38054, upload-time = "2025-10-13T22:16:04.656Z" }, + { url = "https://files.pythonhosted.org/packages/04/24/458a306439aabe0083ca0a7b14c3e6a800ab9782b5ec0bdcec4ec9f3dc6c/xattr-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a04ada131e9bdfd32db3ab1efa9f852646f4f7c9d6fde0596c3825c67161be3", size = 37562, upload-time = "2025-10-13T22:16:05.97Z" }, + { url = "https://files.pythonhosted.org/packages/bf/78/00bdc9290066173e53e1e734d8d8e1a84a6faa9c66aee9df81e4d9aeec1c/xattr-1.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd4e63614722d183e81842cb237fd1cc978d43384166f9fe22368bfcb187ebe5", size = 23476, upload-time = "2025-10-13T22:16:06.942Z" }, + { url = "https://files.pythonhosted.org/packages/53/16/5243722294eb982514fa7b6b87a29dfb7b29b8e5e1486500c5babaf6e4b3/xattr-1.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:995843ef374af73e3370b0c107319611f3cdcdb6d151d629449efecad36be4c4", size = 18556, upload-time = "2025-10-13T22:16:08.209Z" }, + { url = "https://files.pythonhosted.org/packages/d6/5c/d7ab0e547bea885b55f097206459bd612cefb652c5fc1f747130cbc0d42c/xattr-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa23a25220e29d956cedf75746e3df6cc824cc1553326d6516479967c540e386", size = 18869, upload-time = "2025-10-13T22:16:10.319Z" }, + { url = "https://files.pythonhosted.org/packages/98/25/25cc7d64f07de644b7e9057842227adf61017e5bcfe59a79df79f768874c/xattr-1.3.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b4345387087fffcd28f709eb45aae113d911e1a1f4f0f70d46b43ba81e69ccdd", size = 38797, upload-time = "2025-10-13T22:16:11.624Z" }, + { url = "https://files.pythonhosted.org/packages/a9/24/cc350bcdbed006dfcc6ade0ac817693b8b3d4b2787f20e427fd0697042e4/xattr-1.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fe92bb05eb849ab468fe13e942be0f8d7123f15d074f3aba5223fad0c4b484de", size = 38956, upload-time = "2025-10-13T22:16:13.121Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b2/9416317ac89e2ed759a861857cda0d5e284c3691e6f460d36cc2bd5ce4d1/xattr-1.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6c42ef5bdac3febbe28d3db14d3a8a159d84ba5daca2b13deae6f9f1fc0d4092", size = 38214, upload-time = "2025-10-13T22:16:14.389Z" }, + { url = "https://files.pythonhosted.org/packages/38/63/188f7cb41ab35d795558325d5cc8ab552171d5498cfb178fd14409651e18/xattr-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2aaa5d66af6523332189108f34e966ca120ff816dfa077ca34b31e6263f8a236", size = 37754, upload-time = "2025-10-13T22:16:15.306Z" }, ] [[package]] @@ -5041,7 +5096,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } wheels = [ @@ -5050,50 +5105,48 @@ wheels = [ [[package]] name = "yarl" -version = "1.20.1" +version = "1.22.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3c/fb/efaa23fa4e45537b827620f04cf8f3cd658b76642205162e072703a5b963/yarl-1.20.1.tar.gz", hash = "sha256:d017a4997ee50c91fd5466cef416231bb82177b93b029906cefc542ce14c35ac", size = 186428, upload-time = "2025-06-10T00:46:09.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/18/893b50efc2350e47a874c5c2d67e55a0ea5df91186b2a6f5ac52eff887cd/yarl-1.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:47ee6188fea634bdfaeb2cc420f5b3b17332e6225ce88149a17c413c77ff269e", size = 133833, upload-time = "2025-06-10T00:43:07.393Z" }, - { url = "https://files.pythonhosted.org/packages/89/ed/b8773448030e6fc47fa797f099ab9eab151a43a25717f9ac043844ad5ea3/yarl-1.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d0f6500f69e8402d513e5eedb77a4e1818691e8f45e6b687147963514d84b44b", size = 91070, upload-time = "2025-06-10T00:43:09.538Z" }, - { url = "https://files.pythonhosted.org/packages/e3/e3/409bd17b1e42619bf69f60e4f031ce1ccb29bd7380117a55529e76933464/yarl-1.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a8900a42fcdaad568de58887c7b2f602962356908eedb7628eaf6021a6e435b", size = 89818, upload-time = "2025-06-10T00:43:11.575Z" }, - { url = "https://files.pythonhosted.org/packages/f8/77/64d8431a4d77c856eb2d82aa3de2ad6741365245a29b3a9543cd598ed8c5/yarl-1.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bad6d131fda8ef508b36be3ece16d0902e80b88ea7200f030a0f6c11d9e508d4", size = 347003, upload-time = "2025-06-10T00:43:14.088Z" }, - { url = "https://files.pythonhosted.org/packages/8d/d2/0c7e4def093dcef0bd9fa22d4d24b023788b0a33b8d0088b51aa51e21e99/yarl-1.20.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:df018d92fe22aaebb679a7f89fe0c0f368ec497e3dda6cb81a567610f04501f1", size = 336537, upload-time = "2025-06-10T00:43:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/f0/f3/fc514f4b2cf02cb59d10cbfe228691d25929ce8f72a38db07d3febc3f706/yarl-1.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8f969afbb0a9b63c18d0feecf0db09d164b7a44a053e78a7d05f5df163e43833", size = 362358, upload-time = "2025-06-10T00:43:18.704Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6d/a313ac8d8391381ff9006ac05f1d4331cee3b1efaa833a53d12253733255/yarl-1.20.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:812303eb4aa98e302886ccda58d6b099e3576b1b9276161469c25803a8db277d", size = 357362, upload-time = "2025-06-10T00:43:20.888Z" }, - { url = "https://files.pythonhosted.org/packages/00/70/8f78a95d6935a70263d46caa3dd18e1f223cf2f2ff2037baa01a22bc5b22/yarl-1.20.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98c4a7d166635147924aa0bf9bfe8d8abad6fffa6102de9c99ea04a1376f91e8", size = 348979, upload-time = "2025-06-10T00:43:23.169Z" }, - { url = "https://files.pythonhosted.org/packages/cb/05/42773027968968f4f15143553970ee36ead27038d627f457cc44bbbeecf3/yarl-1.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12e768f966538e81e6e7550f9086a6236b16e26cd964cf4df35349970f3551cf", size = 337274, upload-time = "2025-06-10T00:43:27.111Z" }, - { url = "https://files.pythonhosted.org/packages/05/be/665634aa196954156741ea591d2f946f1b78ceee8bb8f28488bf28c0dd62/yarl-1.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe41919b9d899661c5c28a8b4b0acf704510b88f27f0934ac7a7bebdd8938d5e", size = 363294, upload-time = "2025-06-10T00:43:28.96Z" }, - { url = "https://files.pythonhosted.org/packages/eb/90/73448401d36fa4e210ece5579895731f190d5119c4b66b43b52182e88cd5/yarl-1.20.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8601bc010d1d7780592f3fc1bdc6c72e2b6466ea34569778422943e1a1f3c389", size = 358169, upload-time = "2025-06-10T00:43:30.701Z" }, - { url = "https://files.pythonhosted.org/packages/c3/b0/fce922d46dc1eb43c811f1889f7daa6001b27a4005587e94878570300881/yarl-1.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:daadbdc1f2a9033a2399c42646fbd46da7992e868a5fe9513860122d7fe7a73f", size = 362776, upload-time = "2025-06-10T00:43:32.51Z" }, - { url = "https://files.pythonhosted.org/packages/f1/0d/b172628fce039dae8977fd22caeff3eeebffd52e86060413f5673767c427/yarl-1.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:03aa1e041727cb438ca762628109ef1333498b122e4c76dd858d186a37cec845", size = 381341, upload-time = "2025-06-10T00:43:34.543Z" }, - { url = "https://files.pythonhosted.org/packages/6b/9b/5b886d7671f4580209e855974fe1cecec409aa4a89ea58b8f0560dc529b1/yarl-1.20.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:642980ef5e0fa1de5fa96d905c7e00cb2c47cb468bfcac5a18c58e27dbf8d8d1", size = 379988, upload-time = "2025-06-10T00:43:36.489Z" }, - { url = "https://files.pythonhosted.org/packages/73/be/75ef5fd0fcd8f083a5d13f78fd3f009528132a1f2a1d7c925c39fa20aa79/yarl-1.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:86971e2795584fe8c002356d3b97ef6c61862720eeff03db2a7c86b678d85b3e", size = 371113, upload-time = "2025-06-10T00:43:38.592Z" }, - { url = "https://files.pythonhosted.org/packages/50/4f/62faab3b479dfdcb741fe9e3f0323e2a7d5cd1ab2edc73221d57ad4834b2/yarl-1.20.1-cp311-cp311-win32.whl", hash = "sha256:597f40615b8d25812f14562699e287f0dcc035d25eb74da72cae043bb884d773", size = 81485, upload-time = "2025-06-10T00:43:41.038Z" }, - { url = "https://files.pythonhosted.org/packages/f0/09/d9c7942f8f05c32ec72cd5c8e041c8b29b5807328b68b4801ff2511d4d5e/yarl-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:26ef53a9e726e61e9cd1cda6b478f17e350fb5800b4bd1cd9fe81c4d91cfeb2e", size = 86686, upload-time = "2025-06-10T00:43:42.692Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9a/cb7fad7d73c69f296eda6815e4a2c7ed53fc70c2f136479a91c8e5fbdb6d/yarl-1.20.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdcc4cd244e58593a4379fe60fdee5ac0331f8eb70320a24d591a3be197b94a9", size = 133667, upload-time = "2025-06-10T00:43:44.369Z" }, - { url = "https://files.pythonhosted.org/packages/67/38/688577a1cb1e656e3971fb66a3492501c5a5df56d99722e57c98249e5b8a/yarl-1.20.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b29a2c385a5f5b9c7d9347e5812b6f7ab267193c62d282a540b4fc528c8a9d2a", size = 91025, upload-time = "2025-06-10T00:43:46.295Z" }, - { url = "https://files.pythonhosted.org/packages/50/ec/72991ae51febeb11a42813fc259f0d4c8e0507f2b74b5514618d8b640365/yarl-1.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1112ae8154186dfe2de4732197f59c05a83dc814849a5ced892b708033f40dc2", size = 89709, upload-time = "2025-06-10T00:43:48.22Z" }, - { url = "https://files.pythonhosted.org/packages/99/da/4d798025490e89426e9f976702e5f9482005c548c579bdae792a4c37769e/yarl-1.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90bbd29c4fe234233f7fa2b9b121fb63c321830e5d05b45153a2ca68f7d310ee", size = 352287, upload-time = "2025-06-10T00:43:49.924Z" }, - { url = "https://files.pythonhosted.org/packages/1a/26/54a15c6a567aac1c61b18aa0f4b8aa2e285a52d547d1be8bf48abe2b3991/yarl-1.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:680e19c7ce3710ac4cd964e90dad99bf9b5029372ba0c7cbfcd55e54d90ea819", size = 345429, upload-time = "2025-06-10T00:43:51.7Z" }, - { url = "https://files.pythonhosted.org/packages/d6/95/9dcf2386cb875b234353b93ec43e40219e14900e046bf6ac118f94b1e353/yarl-1.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a979218c1fdb4246a05efc2cc23859d47c89af463a90b99b7c56094daf25a16", size = 365429, upload-time = "2025-06-10T00:43:53.494Z" }, - { url = "https://files.pythonhosted.org/packages/91/b2/33a8750f6a4bc224242a635f5f2cff6d6ad5ba651f6edcccf721992c21a0/yarl-1.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255b468adf57b4a7b65d8aad5b5138dce6a0752c139965711bdcb81bc370e1b6", size = 363862, upload-time = "2025-06-10T00:43:55.766Z" }, - { url = "https://files.pythonhosted.org/packages/98/28/3ab7acc5b51f4434b181b0cee8f1f4b77a65919700a355fb3617f9488874/yarl-1.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a97d67108e79cfe22e2b430d80d7571ae57d19f17cda8bb967057ca8a7bf5bfd", size = 355616, upload-time = "2025-06-10T00:43:58.056Z" }, - { url = "https://files.pythonhosted.org/packages/36/a3/f666894aa947a371724ec7cd2e5daa78ee8a777b21509b4252dd7bd15e29/yarl-1.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8570d998db4ddbfb9a590b185a0a33dbf8aafb831d07a5257b4ec9948df9cb0a", size = 339954, upload-time = "2025-06-10T00:43:59.773Z" }, - { url = "https://files.pythonhosted.org/packages/f1/81/5f466427e09773c04219d3450d7a1256138a010b6c9f0af2d48565e9ad13/yarl-1.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:97c75596019baae7c71ccf1d8cc4738bc08134060d0adfcbe5642f778d1dca38", size = 365575, upload-time = "2025-06-10T00:44:02.051Z" }, - { url = "https://files.pythonhosted.org/packages/2e/e3/e4b0ad8403e97e6c9972dd587388940a032f030ebec196ab81a3b8e94d31/yarl-1.20.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:1c48912653e63aef91ff988c5432832692ac5a1d8f0fb8a33091520b5bbe19ef", size = 365061, upload-time = "2025-06-10T00:44:04.196Z" }, - { url = "https://files.pythonhosted.org/packages/ac/99/b8a142e79eb86c926f9f06452eb13ecb1bb5713bd01dc0038faf5452e544/yarl-1.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4c3ae28f3ae1563c50f3d37f064ddb1511ecc1d5584e88c6b7c63cf7702a6d5f", size = 364142, upload-time = "2025-06-10T00:44:06.527Z" }, - { url = "https://files.pythonhosted.org/packages/34/f2/08ed34a4a506d82a1a3e5bab99ccd930a040f9b6449e9fd050320e45845c/yarl-1.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c5e9642f27036283550f5f57dc6156c51084b458570b9d0d96100c8bebb186a8", size = 381894, upload-time = "2025-06-10T00:44:08.379Z" }, - { url = "https://files.pythonhosted.org/packages/92/f8/9a3fbf0968eac704f681726eff595dce9b49c8a25cd92bf83df209668285/yarl-1.20.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2c26b0c49220d5799f7b22c6838409ee9bc58ee5c95361a4d7831f03cc225b5a", size = 383378, upload-time = "2025-06-10T00:44:10.51Z" }, - { url = "https://files.pythonhosted.org/packages/af/85/9363f77bdfa1e4d690957cd39d192c4cacd1c58965df0470a4905253b54f/yarl-1.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:564ab3d517e3d01c408c67f2e5247aad4019dcf1969982aba3974b4093279004", size = 374069, upload-time = "2025-06-10T00:44:12.834Z" }, - { url = "https://files.pythonhosted.org/packages/35/99/9918c8739ba271dcd935400cff8b32e3cd319eaf02fcd023d5dcd487a7c8/yarl-1.20.1-cp312-cp312-win32.whl", hash = "sha256:daea0d313868da1cf2fac6b2d3a25c6e3a9e879483244be38c8e6a41f1d876a5", size = 81249, upload-time = "2025-06-10T00:44:14.731Z" }, - { url = "https://files.pythonhosted.org/packages/eb/83/5d9092950565481b413b31a23e75dd3418ff0a277d6e0abf3729d4d1ce25/yarl-1.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:48ea7d7f9be0487339828a4de0360d7ce0efc06524a48e1810f945c45b813698", size = 86710, upload-time = "2025-06-10T00:44:16.716Z" }, - { url = "https://files.pythonhosted.org/packages/b4/2d/2345fce04cfd4bee161bf1e7d9cdc702e3e16109021035dbb24db654a622/yarl-1.20.1-py3-none-any.whl", hash = "sha256:83b8eb083fe4683c6115795d9fc1cfaf2cbbefb19b3a1cb68f6527460f483a77", size = 46542, upload-time = "2025-06-10T00:46:07.521Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, + { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, + { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, + { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, + { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, + { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, + { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, + { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, + { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, + { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, + { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, + { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, + { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, + { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, + { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, + { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, + { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, + { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, + { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, + { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, + { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, + { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, + { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, + { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, + { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, + { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, + { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, + { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, + { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] [[package]] From adf9ec5360154b9238fc7a91b8722005e17a32a7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 15:39:20 -0800 Subject: [PATCH 032/685] tools: speed up Route() (#36963) * tools: speed up Route() * cleanup --- tools/lib/route.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tools/lib/route.py b/tools/lib/route.py index 1fc26fb9962..98334a06c86 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -23,7 +23,6 @@ class FileName: class Route: def __init__(self, name, data_dir=None): - self._metadata = None self._name = RouteName(name) self.files = None if data_dir is not None: @@ -32,13 +31,6 @@ def __init__(self, name, data_dir=None): self._segments = self._get_segments_remote() self.max_seg_number = self._segments[-1].name.segment_num - @property - def metadata(self): - if not self._metadata: - api = CommaApi(get_token()) - self._metadata = api.get('v1/route/' + self.name.canonical_name) - return self._metadata - @property def name(self): return self._name @@ -90,7 +82,6 @@ def _get_segments_remote(self): url if fn in FileName.DCAMERA else segments[segment_name].dcamera_path, url if fn in FileName.ECAMERA else segments[segment_name].ecamera_path, url if fn in FileName.QCAMERA else segments[segment_name].qcamera_path, - self.metadata['url'], ) else: segments[segment_name] = Segment( @@ -101,7 +92,6 @@ def _get_segments_remote(self): url if fn in FileName.DCAMERA else None, url if fn in FileName.ECAMERA else None, url if fn in FileName.QCAMERA else None, - self.metadata['url'], ) return sorted(segments.values(), key=lambda seg: seg.name.segment_num) @@ -167,7 +157,7 @@ def _get_segments_local(self, data_dir): except StopIteration: qcamera_path = None - segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path, self.metadata['url'])) + segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path)) if len(segments) == 0: raise ValueError(f'Could not find segments for route {self.name.canonical_name} in data directory {data_dir}') @@ -175,10 +165,9 @@ def _get_segments_local(self, data_dir): class Segment: - def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path, url): + def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path): self._events = None self._name = SegmentName(name) - self.url = f'{url}/{self._name.segment_num}' self.log_path = log_path self.qlog_path = qlog_path self.camera_path = camera_path @@ -190,6 +179,18 @@ def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera def name(self): return self._name + @staticmethod + @cache + def _get_route_metadata(route_name: str): + api = CommaApi(get_token()) + return api.get(f'v1/route/{route_name}') + + @property + def url(self): + route_name = self._name.route_name.canonical_name + metadata = self._get_route_metadata(route_name) + return f'{metadata["url"]}/{self._name.segment_num}' + @property def events(self): if not self._events: From 63c9a85c6a15785d9c5182c54b770b4822cfc435 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 28 Dec 2025 21:38:54 -0800 Subject: [PATCH 033/685] FrameReader: use HW accel if available (#36964) * FrameReader: add macOS hw accel * sys * more platforms * logging --- tools/lib/framereader.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index 30ce1f80fec..64e2f9f4348 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -1,6 +1,8 @@ import os import subprocess import json +import logging +from functools import cache from collections.abc import Iterator from collections import OrderedDict @@ -9,11 +11,24 @@ from openpilot.tools.lib.exceptions import DataUnreadableError from openpilot.tools.lib.vidindex import hevc_index +logger = logging.getLogger("tools") HEVC_SLICE_B = 0 HEVC_SLICE_P = 1 HEVC_SLICE_I = 2 +@cache +def get_hw_accel() -> list[str]: + """Detect and return the best available ffmpeg hardware acceleration.""" + priority = ("videotoolbox", "cuda", "vaapi", "d3d11va") + result = subprocess.run(["ffmpeg", "-hwaccels"], capture_output=True, text=True, timeout=5) + for accel in priority: + if accel in result.stdout.lower(): + logger.info(f"HW accelerated video decode found, using ffmpeg's {accel}") + return ["-hwaccel", accel] + logger.warning("no HW accelerated video found with `ffmpeg -hwaccels`. falling back to ffmpeg CPU decode") + return [] + class LRUCache: def __init__(self, capacity: int): @@ -46,6 +61,7 @@ def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc') -> np.n threads = os.getenv("FFMPEG_THREADS", "0") args = ["ffmpeg", "-v", "quiet", "-threads", threads, + *get_hw_accel(), "-c:v", "hevc", "-vsync", "0", "-f", vid_fmt, @@ -171,3 +187,7 @@ def get(self, fidx:int): self.fidx, frame = next(self.it) self._cache[self.fidx] = frame return self._cache[fidx] + + +if __name__ == "__main__": + get_hw_accel() From a5348b8679c45fd3e903bae6fe093bcc9fcbd971 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 29 Dec 2025 16:41:52 -0800 Subject: [PATCH 034/685] crcmod -> crcmod-plus (#36968) --- pyproject.toml | 2 +- uv.lock | 90 +++++++++++++++++++++++++++++--------------------- 2 files changed, 53 insertions(+), 39 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bb4ac0e8d8b..e048894494e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "pyserial", # pigeond + qcomgpsd "requests", # many one-off uses "sympy", # rednose + friends - "crcmod", # cars + qcomgpsd + "crcmod-plus", # cars + qcomgpsd "tqdm", # cars (fw_versions.py) on start + many one-off uses # hardwared diff --git a/uv.lock b/uv.lock index a5b529e52eb..a40167bb92c 100644 --- a/uv.lock +++ b/uv.lock @@ -416,10 +416,24 @@ wheels = [ ] [[package]] -name = "crcmod" -version = "1.7" +name = "crcmod-plus" +version = "2.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/b0/e595ce2a2527e169c3bcd6c33d2473c1918e0b7f6826a043ca1245dd4e5b/crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e", size = 89670, upload-time = "2010-06-27T14:35:29.538Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/0c/71733bbaf38e9f1eaecfdf7f8e350993f3dcac208a5297c41503ae66e513/crcmod_plus-2.3.1.tar.gz", hash = "sha256:732ffe3c3ce3ef9b272e1827d8fb894590c4d6ff553f2a2b41ae30f4f94b0f5d", size = 22319, upload-time = "2025-10-10T22:14:21.691Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/e0/2dad2e6f0cd4914b4144496d9785780ec820e200816c080df785cfa34da6/crcmod_plus-2.3.1-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:b7e35e0f7d93d7571c2c9c3d6760e456999ea4c1eae5ead6acac247b5a79e469", size = 23279, upload-time = "2025-10-10T22:13:47.281Z" }, + { url = "https://files.pythonhosted.org/packages/66/76/53c0b65b9679b903f98fc54efa32b0e5a19634712a45200c7a80674aa6f5/crcmod_plus-2.3.1-cp311-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6853243120db84677b94b625112116f0ef69cd581741d20de58dce4c34242654", size = 20185, upload-time = "2025-10-10T22:13:48.06Z" }, + { url = "https://files.pythonhosted.org/packages/98/79/2b4dc9bb26394873d7699737124408b5106264ae33053fdec600e9a9fa65/crcmod_plus-2.3.1-cp311-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:17735bc4e944d552ea18c8609fc6d08a5e64ee9b29cc216ba4d623754029cc3a", size = 26999, upload-time = "2025-10-10T22:13:48.854Z" }, + { url = "https://files.pythonhosted.org/packages/bb/e8/f5d66778b5a1bff915807016561a02b5cebf6b3840fb8a2be40bbb0c8575/crcmod_plus-2.3.1-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ac755040a2a35f43ab331978c48a9acb4ff64b425f282a296be467a410f00c3", size = 27536, upload-time = "2025-10-10T22:13:49.956Z" }, + { url = "https://files.pythonhosted.org/packages/f3/2c/0113ad30cadad40c22eef08c0f2618f2446dd282f02268fecbcfc9fda3c1/crcmod_plus-2.3.1-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bdcfb838ca093ca673a3bbb37f62d1e5ec7182e00cc5ee2d00759f9f9f8ab11", size = 27385, upload-time = "2025-10-10T22:13:50.765Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ba/501ef1b02119402cf1a31c01eb2cb8399660bca863c2f4dd3dc060220284/crcmod_plus-2.3.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9166bc3c9b5e7b07b4e6854cac392b4a451b31d58d3950e48c140ab7b5d05394", size = 27135, upload-time = "2025-10-10T22:13:51.889Z" }, + { url = "https://files.pythonhosted.org/packages/49/90/d4556c9db69c83e726c5b88da3d656fdaac7d60c4d27b43cb939bed80069/crcmod_plus-2.3.1-cp311-abi3-win32.whl", hash = "sha256:cb99b694cce5c862560cf332a8b5e793620e28f0de3726995608bbd6f9b6e09a", size = 22384, upload-time = "2025-10-10T22:13:53.016Z" }, + { url = "https://files.pythonhosted.org/packages/4d/7e/57bb97a8c7b4e19900744f58b67dc83bc9c83aaac670deeede9fb3bfab6a/crcmod_plus-2.3.1-cp311-abi3-win_amd64.whl", hash = "sha256:82b0f7e968c430c5a80fe0fc59e75cb54f2e84df2ed0cee5a3ff9cadfbf8a220", size = 22912, upload-time = "2025-10-10T22:13:53.849Z" }, + { url = "https://files.pythonhosted.org/packages/76/66/419ae3991bb68943cb752e2f4d317c555e3f02a298dd498f26113874ee59/crcmod_plus-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9397324da1be2729f894744d9031a21ed97584c17fb0289e69e0c3c60916fc5f", size = 19880, upload-time = "2025-10-10T22:14:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/18/f0/d10c9b859927b2cdc38eafc33c8b66e4ede02eaa174df4575681dab5a0f1/crcmod_plus-2.3.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:073c7a3b832652e66c41c8b8705eaecda704d1cbe850b9fa05fdee36cd50745a", size = 21120, upload-time = "2025-10-10T22:14:18.117Z" }, + { url = "https://files.pythonhosted.org/packages/6c/68/cbd8f1707b37b80f9a0bf643e04747b0196f69cf065b52ed56639afbecef/crcmod_plus-2.3.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e5f4c62553f772ea7ae12d9484801b752622c9c288e49ee7ea34a20b94e4920", size = 21698, upload-time = "2025-10-10T22:14:20.044Z" }, + { url = "https://files.pythonhosted.org/packages/41/1b/4ab1681ecbfc48d7e4641fb178c97374eb475ae4109255bdd832110cbbe2/crcmod_plus-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5e80a9860f66f339956f540d86a768f4fe8c8bfcb139811f14be864425c48d64", size = 23289, upload-time = "2025-10-10T22:14:20.875Z" }, +] [[package]] name = "cryptography" @@ -678,10 +692,10 @@ name = "gymnasium" version = "1.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle" }, - { name = "farama-notifications" }, - { name = "numpy" }, - { name = "typing-extensions" }, + { name = "cloudpickle", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "farama-notifications", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/59/653a9417d98ed3e29ef9734ba52c3495f6c6823b8d5c0c75369f25111708/gymnasium-1.2.3.tar.gz", hash = "sha256:2b2cb5b5fbbbdf3afb9f38ca952cc48aa6aa3e26561400d940747fda3ad42509", size = 829230, upload-time = "2025-12-18T16:51:10.234Z" } wheels = [ @@ -1001,22 +1015,22 @@ name = "metadrive-simulator" version = "0.4.2.4" source = { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" } dependencies = [ - { name = "filelock" }, - { name = "gymnasium" }, - { name = "lxml" }, - { name = "matplotlib" }, - { name = "numpy" }, - { name = "opencv-python-headless" }, - { name = "panda3d" }, - { name = "panda3d-gltf" }, - { name = "pillow" }, - { name = "progressbar" }, - { name = "psutil" }, - { name = "pygments" }, - { name = "requests" }, - { name = "shapely" }, - { name = "tqdm" }, - { name = "yapf" }, + { name = "filelock", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "gymnasium", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "lxml", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "matplotlib", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "opencv-python-headless", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d-gltf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "pillow", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "progressbar", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "psutil", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "pygments", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "requests", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "shapely", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "tqdm", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "yapf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] wheels = [ { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, @@ -1298,7 +1312,7 @@ dependencies = [ { name = "aiortc" }, { name = "casadi" }, { name = "cffi" }, - { name = "crcmod" }, + { name = "crcmod-plus" }, { name = "cython" }, { name = "future-fstrings" }, { name = "inputs" }, @@ -1392,7 +1406,7 @@ requires-dist = [ { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, { name = "coverage", marker = "extra == 'testing'" }, - { name = "crcmod" }, + { name = "crcmod-plus" }, { name = "cython" }, { name = "dbus-next", marker = "extra == 'dev'" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, @@ -1493,8 +1507,8 @@ name = "panda3d-gltf" version = "0.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "panda3d-simplepbr" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d-simplepbr", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/7f/9f18fc3fa843a080acb891af6bcc12262e7bdf1d194a530f7042bebfc81f/panda3d-gltf-0.13.tar.gz", hash = "sha256:d06d373bdd91cf530909b669f43080e599463bbf6d3ef00c3558bad6c6b19675", size = 25573, upload-time = "2021-05-21T05:46:32.738Z" } wheels = [ @@ -1506,8 +1520,8 @@ name = "panda3d-simplepbr" version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d" }, - { name = "typing-extensions" }, + { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/be/c4d1ded04c22b357277cf6e6a44c1ab4abb285a700bd1991460460e05b99/panda3d_simplepbr-0.13.1.tar.gz", hash = "sha256:c83766d7c8f47499f365a07fe1dff078fc8b3054c2689bdc8dceabddfe7f1a35", size = 6216055, upload-time = "2025-03-30T16:57:41.087Z" } wheels = [ @@ -4287,10 +4301,10 @@ name = "pyopencl" version = "2025.2.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, - { name = "platformdirs" }, - { name = "pytools" }, - { name = "typing-extensions" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "pytools", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/cb/8927052160bc0d3bd1123a645aaf57f696da364216b57b49f92ba0777bcc/pyopencl-2025.2.7.tar.gz", hash = "sha256:a68d92eb2970418b1a7ca45aff71984c02d2e4261e01402b273f257b5d6d7511", size = 444787, upload-time = "2025-10-28T14:23:15.497Z" } wheels = [ @@ -4512,9 +4526,9 @@ name = "pytools" version = "2025.2.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, - { name = "siphash24" }, - { name = "typing-extensions" }, + { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "siphash24", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/7b/f885a57e61ded45b5b10ca60f0b7575c9fb9a282e7513d0e23a33ee647e1/pytools-2025.2.5.tar.gz", hash = "sha256:a7f5350644d46d98ee9c7e67b4b41693308aa0f5e9b188d8f0694b27dc94e3a2", size = 85594, upload-time = "2025-10-07T15:53:30.49Z" } wheels = [ @@ -4846,7 +4860,7 @@ name = "shapely" version = "2.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy" }, + { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ @@ -5096,7 +5110,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs" }, + { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } wheels = [ From 84b1f363e42293104b4d56ed5b07f549fce381cf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 31 Dec 2025 12:43:36 -0800 Subject: [PATCH 035/685] Alert for stock LKAS (#36969) * alert stock lkas * high * i didn't do this --- cereal/log.capnp | 1 + selfdrive/car/car_specific.py | 2 ++ selfdrive/selfdrived/events.py | 9 +++++++++ 3 files changed, 12 insertions(+) diff --git a/cereal/log.capnp b/cereal/log.capnp index 3a6432c84df..2f300881b1e 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -87,6 +87,7 @@ struct OnroadEvent @0xc4fa6047f024e718 { laneChange @50; lowMemory @51; stockAeb @52; + stockLkas @98; ldw @53; carUnrecognized @54; invalidLkasSetting @55; diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index cdeeebdcd57..bc903ec0831 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -119,6 +119,8 @@ def create_common_events(self, CS: structs.CarState, CS_prev: car.CarState): events.add(EventName.stockFcw) if CS.stockAeb: events.add(EventName.stockAeb) + if CS.stockLkas: + events.add(EventName.stockLkas) if CS.vEgo > MAX_CTRL_SPEED: events.add(EventName.speedTooHigh) if CS.cruiseState.nonAdaptive: diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 1acfa2964e7..35d4bda42f3 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -477,6 +477,15 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin ET.NO_ENTRY: NoEntryAlert("Stock AEB: Risk of Collision"), }, + EventName.stockLkas: { + ET.PERMANENT: Alert( + "TAKE CONTROL", + "Stock LKAS: Lane Departure Detected", + AlertStatus.critical, AlertSize.full, + Priority.HIGH, VisualAlert.fcw, AudibleAlert.none, 2.), + ET.NO_ENTRY: NoEntryAlert("Stock LKAS: Lane Departure Detected"), + }, + EventName.fcw: { ET.PERMANENT: Alert( "BRAKE!", From bb40d161e8d24f3e4c4835603610ea669ab00154 Mon Sep 17 00:00:00 2001 From: Alexandre Nobuharu Sato <66435071+AlexandreSato@users.noreply.github.com> Date: Thu, 1 Jan 2026 20:13:18 -0300 Subject: [PATCH 036/685] Add back badges for keep multilanguage support (#36967) --- selfdrive/ui/translations/README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 selfdrive/ui/translations/README.md diff --git a/selfdrive/ui/translations/README.md b/selfdrive/ui/translations/README.md new file mode 100644 index 00000000000..433eb7d64a0 --- /dev/null +++ b/selfdrive/ui/translations/README.md @@ -0,0 +1,3 @@ +# Multilanguage + +[![languages](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge.svg)](#) From f62177a8274ac75b82011d1a7ae57f8458c85091 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 1 Jan 2026 19:06:31 -0800 Subject: [PATCH 037/685] FrameReader: use hwaccel auto (#36973) * FrameReader: use hwaccel auto * rm main block --- tools/lib/framereader.py | 46 ++++++++++++---------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index 64e2f9f4348..7f557187765 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -2,7 +2,6 @@ import subprocess import json import logging -from functools import cache from collections.abc import Iterator from collections import OrderedDict @@ -17,19 +16,6 @@ HEVC_SLICE_P = 1 HEVC_SLICE_I = 2 -@cache -def get_hw_accel() -> list[str]: - """Detect and return the best available ffmpeg hardware acceleration.""" - priority = ("videotoolbox", "cuda", "vaapi", "d3d11va") - result = subprocess.run(["ffmpeg", "-hwaccels"], capture_output=True, text=True, timeout=5) - for accel in priority: - if accel in result.stdout.lower(): - logger.info(f"HW accelerated video decode found, using ffmpeg's {accel}") - return ["-hwaccel", accel] - logger.warning("no HW accelerated video found with `ffmpeg -hwaccels`. falling back to ffmpeg CPU decode") - return [] - - class LRUCache: def __init__(self, capacity: int): self._cache: OrderedDict = OrderedDict() @@ -47,7 +33,6 @@ def __setitem__(self, key, value): def __contains__(self, key): return key in self._cache - def assert_hvec(fn: str) -> None: with FileReader(fn) as f: header = f.read(4) @@ -57,11 +42,11 @@ def assert_hvec(fn: str) -> None: if 'hevc' not in fn: raise NotImplementedError(fn) -def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc') -> np.ndarray: +def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc', loglevel="info") -> np.ndarray: threads = os.getenv("FFMPEG_THREADS", "0") - args = ["ffmpeg", "-v", "quiet", + args = ["ffmpeg", "-v", loglevel, "-threads", threads, - *get_hw_accel(), + "-hwaccel", "auto", "-c:v", "hevc", "-vsync", "0", "-f", vid_fmt, @@ -114,15 +99,15 @@ def get_video_index(fn): 'probe': probe } - class FfmpegDecoder: def __init__(self, fn: str, index_data: dict|None = None, - pix_fmt: str = "rgb24"): + pix_fmt: str = "rgb24", loglevel="quiet"): self.fn = fn self.index, self.prefix, self.w, self.h = get_index_data(fn, index_data) self.frame_count = len(self.index) - 1 # sentinel row at the end self.iframes = np.where(self.index[:, 0] == HEVC_SLICE_I)[0] self.pix_fmt = pix_fmt + self.loglevel = loglevel def _gop_bounds(self, frame_idx: int): f_b = frame_idx @@ -134,7 +119,7 @@ def _gop_bounds(self, frame_idx: int): return f_b, f_e, self.index[f_b, 1], self.index[f_e, 1] def _decode_gop(self, raw: bytes) -> Iterator[np.ndarray]: - yield from decompress_video_data(raw, self.w, self.h, self.pix_fmt) + yield from decompress_video_data(raw, self.w, self.h, self.pix_fmt, loglevel=self.loglevel) def get_gop_start(self, frame_idx: int): return self.iframes[np.searchsorted(self.iframes, frame_idx, side="right") - 1] @@ -149,7 +134,7 @@ def get_iterator(self, start_fidx: int = 0, end_fidx: int|None = None, f.seek(off_b) raw = self.prefix + f.read(off_e - off_b) # number of frames to discard inside this GOP before the wanted one - for i, frm in enumerate(decompress_video_data(raw, self.w, self.h, self.pix_fmt)): + for i, frm in enumerate(decompress_video_data(raw, self.w, self.h, self.pix_fmt, loglevel=self.loglevel)): fidx = f_b + i if fidx >= end_fidx: return @@ -157,17 +142,16 @@ def get_iterator(self, start_fidx: int = 0, end_fidx: int|None = None, yield fidx, frm fidx += 1 -def FrameIterator(fn: str, index_data: dict|None=None, - pix_fmt: str = "rgb24", - start_fidx:int=0, end_fidx=None, frame_skip:int=1) -> Iterator[np.ndarray]: - dec = FfmpegDecoder(fn, pix_fmt=pix_fmt, index_data=index_data) +def FrameIterator(fn: str, index_data: dict|None=None, pix_fmt: str = "rgb24", + start_fidx:int=0, end_fidx=None, frame_skip:int=1, loglevel="quiet") -> Iterator[np.ndarray]: + dec = FfmpegDecoder(fn, pix_fmt=pix_fmt, index_data=index_data, loglevel=loglevel) for _, frame in dec.get_iterator(start_fidx=start_fidx, end_fidx=end_fidx, frame_skip=frame_skip): yield frame class FrameReader: - def __init__(self, fn: str, index_data: dict|None = None, - cache_size: int = 30, pix_fmt: str = "rgb24"): - self.decoder = FfmpegDecoder(fn, index_data, pix_fmt) + def __init__(self, fn: str, index_data: dict|None = None, cache_size: int = 30, + pix_fmt: str = "rgb24", loglevel="quiet"): + self.decoder = FfmpegDecoder(fn, index_data, pix_fmt, loglevel=loglevel) self.iframes = self.decoder.iframes self._cache: LRUCache = LRUCache(cache_size) self.w, self.h, self.frame_count, = self.decoder.w, self.decoder.h, self.decoder.frame_count @@ -187,7 +171,3 @@ def get(self, fidx:int): self.fidx, frame = next(self.it) self._cache[self.fidx] = frame return self._cache[fidx] - - -if __name__ == "__main__": - get_hw_accel() From adbf68f77120b4568de68c37b8bed4ed433e01fe Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Fri, 2 Jan 2026 11:29:45 -0800 Subject: [PATCH 038/685] FrameReader: add hwaccel arg and clear frames_cache (#36974) --- tools/lib/framereader.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index 7f557187765..c13f7fd3134 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -42,11 +42,11 @@ def assert_hvec(fn: str) -> None: if 'hevc' not in fn: raise NotImplementedError(fn) -def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc', loglevel="info") -> np.ndarray: +def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc', hwaccel="auto", loglevel="info") -> np.ndarray: threads = os.getenv("FFMPEG_THREADS", "0") args = ["ffmpeg", "-v", loglevel, "-threads", threads, - "-hwaccel", "auto", + "-hwaccel", hwaccel, "-c:v", "hevc", "-vsync", "0", "-f", vid_fmt, @@ -101,13 +101,13 @@ def get_video_index(fn): class FfmpegDecoder: def __init__(self, fn: str, index_data: dict|None = None, - pix_fmt: str = "rgb24", loglevel="quiet"): + pix_fmt: str = "rgb24", hwaccel="auto", loglevel="quiet"): self.fn = fn self.index, self.prefix, self.w, self.h = get_index_data(fn, index_data) self.frame_count = len(self.index) - 1 # sentinel row at the end self.iframes = np.where(self.index[:, 0] == HEVC_SLICE_I)[0] self.pix_fmt = pix_fmt - self.loglevel = loglevel + self.loglevel, self.hwaccel = loglevel, hwaccel def _gop_bounds(self, frame_idx: int): f_b = frame_idx @@ -119,7 +119,7 @@ def _gop_bounds(self, frame_idx: int): return f_b, f_e, self.index[f_b, 1], self.index[f_e, 1] def _decode_gop(self, raw: bytes) -> Iterator[np.ndarray]: - yield from decompress_video_data(raw, self.w, self.h, self.pix_fmt, loglevel=self.loglevel) + yield from decompress_video_data(raw, self.w, self.h, pix_fmt=self.pix_fmt, hwaccel=self.hwaccel, loglevel=self.loglevel) def get_gop_start(self, frame_idx: int): return self.iframes[np.searchsorted(self.iframes, frame_idx, side="right") - 1] @@ -134,7 +134,7 @@ def get_iterator(self, start_fidx: int = 0, end_fidx: int|None = None, f.seek(off_b) raw = self.prefix + f.read(off_e - off_b) # number of frames to discard inside this GOP before the wanted one - for i, frm in enumerate(decompress_video_data(raw, self.w, self.h, self.pix_fmt, loglevel=self.loglevel)): + for i, frm in enumerate(decompress_video_data(raw, self.w, self.h, self.pix_fmt, hwaccel=self.hwaccel, loglevel=self.loglevel)): fidx = f_b + i if fidx >= end_fidx: return @@ -143,15 +143,15 @@ def get_iterator(self, start_fidx: int = 0, end_fidx: int|None = None, fidx += 1 def FrameIterator(fn: str, index_data: dict|None=None, pix_fmt: str = "rgb24", - start_fidx:int=0, end_fidx=None, frame_skip:int=1, loglevel="quiet") -> Iterator[np.ndarray]: - dec = FfmpegDecoder(fn, pix_fmt=pix_fmt, index_data=index_data, loglevel=loglevel) + start_fidx:int=0, end_fidx=None, frame_skip:int=1, hwaccel="auto", loglevel="quiet") -> Iterator[np.ndarray]: + dec = FfmpegDecoder(fn, pix_fmt=pix_fmt, index_data=index_data, hwaccel=hwaccel, loglevel=loglevel) for _, frame in dec.get_iterator(start_fidx=start_fidx, end_fidx=end_fidx, frame_skip=frame_skip): yield frame class FrameReader: def __init__(self, fn: str, index_data: dict|None = None, cache_size: int = 30, - pix_fmt: str = "rgb24", loglevel="quiet"): - self.decoder = FfmpegDecoder(fn, index_data, pix_fmt, loglevel=loglevel) + pix_fmt: str = "rgb24", hwaccel="auto", loglevel="quiet"): + self.decoder = FfmpegDecoder(fn, index_data=index_data, pix_fmt=pix_fmt, hwaccel=hwaccel, loglevel=loglevel) self.iframes = self.decoder.iframes self._cache: LRUCache = LRUCache(cache_size) self.w, self.h, self.frame_count, = self.decoder.w, self.decoder.h, self.decoder.frame_count From be854df32d438cbd0e7977f61f9d104de179d2bc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 4 Jan 2026 13:46:30 -0800 Subject: [PATCH 039/685] remove unused dbus-next package (#36979) --- pyproject.toml | 1 - uv.lock | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e048894494e..d6360a58ad1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,7 +107,6 @@ dev = [ "av", "azure-identity", "azure-storage-blob", - "dbus-next", # TODO: remove once we moved everything to jeepney "dictdiffer", "jeepney", "matplotlib", diff --git a/uv.lock b/uv.lock index a40167bb92c..f0f3cb35b9f 100644 --- a/uv.lock +++ b/uv.lock @@ -499,15 +499,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e5/41/54fd429ff8147475fc24ca43246f85d78fb4e747c27f227e68f1594648f1/cython-3.2.3-py3-none-any.whl", hash = "sha256:06a1317097f540d3bb6c7b81ed58a0d8b9dbfa97abf39dfd4c22ee87a6c7241e", size = 1255561, upload-time = "2025-12-14T07:50:31.217Z" }, ] -[[package]] -name = "dbus-next" -version = "0.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ce/45/6a40fbe886d60a8c26f480e7d12535502b5ba123814b3b9a0b002ebca198/dbus_next-0.2.3.tar.gz", hash = "sha256:f4eae26909332ada528c0a3549dda8d4f088f9b365153952a408e28023a626a5", size = 71112, upload-time = "2021-07-25T22:11:28.398Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/fc/c0a3f4c4eaa5a22fbef91713474666e13d0ea2a69c84532579490a9f2cc8/dbus_next-0.2.3-py3-none-any.whl", hash = "sha256:58948f9aff9db08316734c0be2a120f6dc502124d9642f55e90ac82ffb16a18b", size = 57885, upload-time = "2021-07-25T22:11:25.466Z" }, -] - [[package]] name = "dearpygui" version = "2.1.1" @@ -1352,7 +1343,6 @@ dev = [ { name = "av" }, { name = "azure-identity" }, { name = "azure-storage-blob" }, - { name = "dbus-next" }, { name = "dictdiffer" }, { name = "jeepney" }, { name = "matplotlib" }, @@ -1408,7 +1398,6 @@ requires-dist = [ { name = "coverage", marker = "extra == 'testing'" }, { name = "crcmod-plus" }, { name = "cython" }, - { name = "dbus-next", marker = "extra == 'dev'" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "future-fstrings" }, From 3c5974930aac69724a72aa895e2014807a9a0b52 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Sun, 4 Jan 2026 17:09:26 -0500 Subject: [PATCH 040/685] cleanup SecOC release gating (#36980) --- selfdrive/car/card.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index aa0d12e9608..12b83134710 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -117,7 +117,7 @@ def __init__(self, CI=None, RI=None) -> None: safety_config.safetyModel = structs.CarParams.SafetyModel.noOutput self.CP.safetyConfigs = [safety_config] - if self.CP.secOcRequired and not is_release: + if self.CP.secOcRequired: # Copy user key if available try: with open("/cache/params/SecOCKey") as f: From 84bce8ae020b0819cf41d31e832dd565a53e642d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 4 Jan 2026 17:52:10 -0800 Subject: [PATCH 041/685] rm pygame (#36982) * rm pygame * lil more * cleanup * lil more --- pyproject.toml | 1 - tools/replay/lib/ui_helpers.py | 91 +++++------- tools/replay/ui.py | 250 +++++++++++++++++++-------------- 3 files changed, 185 insertions(+), 157 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d6360a58ad1..903995cf645 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,7 +113,6 @@ dev = [ "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", - "pygame", "pyopencl; platform_machine != 'aarch64'", # broken on arm64 "pytools>=2025.1.6; platform_machine != 'aarch64'", "pywinctl", diff --git a/tools/replay/lib/ui_helpers.py b/tools/replay/lib/ui_helpers.py index b90cbd93b09..7c95e9cad40 100644 --- a/tools/replay/lib/ui_helpers.py +++ b/tools/replay/lib/ui_helpers.py @@ -1,9 +1,8 @@ import itertools -from typing import Any import matplotlib.pyplot as plt import numpy as np -import pygame +import pyray as rl from matplotlib.backends.backend_agg import FigureCanvasAgg @@ -18,21 +17,25 @@ BLACK = (0, 0, 0) WHITE = (255, 255, 255) + class UIParams: lidar_x, lidar_y, lidar_zoom = 384, 960, 6 - lidar_car_x, lidar_car_y = lidar_x / 2., lidar_y / 1.1 + lidar_car_x, lidar_car_y = lidar_x / 2.0, lidar_y / 1.1 car_hwidth = 1.7272 / 2 * lidar_zoom car_front = 2.6924 * lidar_zoom car_back = 1.8796 * lidar_zoom car_color = 110 + + UP = UIParams METER_WIDTH = 20 + class Calibration: def __init__(self, num_px, rpy, intrinsic, calib_scale): self.intrinsic = intrinsic - self.extrinsics_matrix = get_view_frame_from_calib_frame(rpy[0], rpy[1], rpy[2], 0.0)[:,:3] + self.extrinsics_matrix = get_view_frame_from_calib_frame(rpy[0], rpy[1], rpy[2], 0.0)[:, :3] self.zoom = calib_scale def car_space_to_ff(self, x, y, z): @@ -47,19 +50,18 @@ def car_space_to_bb(self, x, y, z): return pts / self.zoom -_COLOR_CACHE : dict[tuple[int, int, int], Any] = {} +_COLOR_CACHE: dict[tuple[int, int, int], int] = { + (255, 0, 0): 1, # RED + (0, 255, 0): 2, # GREEN + (0, 0, 255): 3, # BLUE + (255, 255, 0): 4, # YELLOW + (0, 0, 0): 0, # BLACK + (255, 255, 255): 255, # WHITE +} + + def find_color(lidar_surface, color): - if color in _COLOR_CACHE: - return _COLOR_CACHE[color] - tcolor = 0 - ret = 255 - for x in lidar_surface.get_palette(): - if x[0:3] == color: - ret = tcolor - break - tcolor += 1 - _COLOR_CACHE[color] = ret - return ret + return _COLOR_CACHE.get(color, 255) def to_topdown_pt(y, x): @@ -91,13 +93,7 @@ def draw_path(path, color, img, calibration, top_down, lid_color=None, z_off=0): def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles): - color_palette = { "r": (1, 0, 0), - "g": (0, 1, 0), - "b": (0, 0, 1), - "k": (0, 0, 0), - "y": (1, 1, 0), - "p": (0, 1, 1), - "m": (1, 0, 1)} + color_palette = {"r": (1, 0, 0), "g": (0, 1, 0), "b": (0, 0, 1), "k": (0, 0, 0), "y": (1, 1, 0), "p": (0, 1, 1), "m": (1, 0, 1)} dpi = 90 fig = plt.figure(figsize=(575 / dpi, 600 / dpi), dpi=dpi) @@ -107,7 +103,7 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co axs = [] for pn in range(len(plot_ylims)): - ax = fig.add_subplot(len(plot_ylims), 1, len(axs)+1) + ax = fig.add_subplot(len(plot_ylims), 1, len(axs) + 1) ax.set_xlim(plot_xlims[pn][0], plot_xlims[pn][1]) ax.set_ylim(plot_ylims[pn][0], plot_ylims[pn][1]) ax.patch.set_facecolor((0.4, 0.4, 0.4)) @@ -116,15 +112,11 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co plots, idxs, plot_select = [], [], [] for i, pl_list in enumerate(plot_names): for j, item in enumerate(pl_list): - plot, = axs[i].plot(arr[:, name_to_arr_idx[item]], - label=item, - color=color_palette[plot_colors[i][j]], - linestyle=plot_styles[i][j]) + (plot,) = axs[i].plot(arr[:, name_to_arr_idx[item]], label=item, color=color_palette[plot_colors[i][j]], linestyle=plot_styles[i][j]) plots.append(plot) idxs.append(name_to_arr_idx[item]) plot_select.append(i) - axs[i].set_title(", ".join(f"{nm} ({cl})" - for (nm, cl) in zip(pl_list, plot_colors[i], strict=False)), fontsize=10) + axs[i].set_title(", ".join(f"{nm} ({cl})" for (nm, cl) in zip(pl_list, plot_colors[i], strict=False)), fontsize=10) axs[i].tick_params(axis="x", colors="white") axs[i].tick_params(axis="y", colors="white") axs[i].title.set_color("white") @@ -134,6 +126,12 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co canvas.draw() + # Pre-create texture for plots (reuse each frame to avoid log spam) + w, h = canvas.get_width_height() + plot_image = rl.gen_image_color(w, h, rl.BLACK) + plot_texture = rl.load_texture_from_image(plot_image) + rl.unload_image(plot_image) + def draw_plots(arr): for ax in axs: ax.draw_artist(ax.patch) @@ -141,17 +139,13 @@ def draw_plots(arr): plots[i].set_ydata(arr[:, idxs[i]]) axs[plot_select[i]].draw_artist(plots[i]) - raw_data = canvas.buffer_rgba() - plot_surface = pygame.image.frombuffer(raw_data, canvas.get_width_height(), "RGBA").convert() - return plot_surface + raw_data = np.ascontiguousarray(canvas.buffer_rgba(), dtype=np.uint8) + rl.update_texture(plot_texture, rl.ffi.cast("void *", raw_data.ctypes.data)) + return plot_texture return draw_plots -def pygame_modules_have_loaded(): - return pygame.display.get_init() and pygame.font.get_init() - - def plot_model(m, img, calibration, top_down): if calibration is None or top_down is None: return @@ -166,7 +160,7 @@ def plot_model(m, img, calibration, top_down): _, py_top = to_topdown_pt(x + x_std, y) px, py_bottom = to_topdown_pt(x - x_std, y) - top_down[1][int(round(px - 4)):int(round(px + 4)), py_top:py_bottom] = find_color(top_down[0], YELLOW) + top_down[1][int(round(px - 4)) : int(round(px + 4)), py_top:py_bottom] = find_color(top_down[0], YELLOW) for path, prob, _ in zip(m.laneLines, m.laneLineProbs, m.laneLineStds, strict=True): color = (0, int(255 * prob), 0) @@ -202,22 +196,15 @@ def maybe_update_radar_points(lt, lid_overlay): # negative here since radar is left positive px, py = to_topdown_pt(pt[0], -pt[1]) if px != -1: - lid_overlay[px - 4:px + 4, py - 4:py + 4] = 0 - lid_overlay[px - 2:px + 2, py - 2:py + 2] = 255 + lid_overlay[px - 4 : px + 4, py - 4 : py + 4] = 0 + lid_overlay[px - 2 : px + 2, py - 2 : py + 2] = 255 + def get_blank_lid_overlay(UP): lid_overlay = np.zeros((UP.lidar_x, UP.lidar_y), 'uint8') # Draw the car. - lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int( - round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y - - UP.car_front))] = UP.car_color - lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)):int( - round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y + - UP.car_back))] = UP.car_color - lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)), int( - round(UP.lidar_car_y - UP.car_front)):int(round( - UP.lidar_car_y + UP.car_back))] = UP.car_color - lid_overlay[int(round(UP.lidar_car_x + UP.car_hwidth)), int( - round(UP.lidar_car_y - UP.car_front)):int(round( - UP.lidar_car_y + UP.car_back))] = UP.car_color + lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)) : int(round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y - UP.car_front))] = UP.car_color + lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)) : int(round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y + UP.car_back))] = UP.car_color + lid_overlay[int(round(UP.lidar_car_x - UP.car_hwidth)), int(round(UP.lidar_car_y - UP.car_front)) : int(round(UP.lidar_car_y + UP.car_back))] = UP.car_color + lid_overlay[int(round(UP.lidar_car_x + UP.car_hwidth)), int(round(UP.lidar_car_y - UP.car_front)) : int(round(UP.lidar_car_y + UP.car_back))] = UP.car_color return lid_overlay diff --git a/tools/replay/ui.py b/tools/replay/ui.py index d71d63d1ced..54667ebfe96 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -5,57 +5,88 @@ import cv2 import numpy as np -import pygame +import pyray as rl import cereal.messaging as messaging from openpilot.common.basedir import BASEDIR from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.tools.replay.lib.ui_helpers import (UP, - BLACK, GREEN, - YELLOW, Calibration, - get_blank_lid_overlay, init_plots, - maybe_update_radar_points, plot_lead, - plot_model, - pygame_modules_have_loaded) +from openpilot.tools.replay.lib.ui_helpers import ( + UP, + BLACK, + GREEN, + YELLOW, + Calibration, + get_blank_lid_overlay, + init_plots, + maybe_update_radar_points, + plot_lead, + plot_model, +) from msgq.visionipc import VisionIpcClient, VisionStreamType os.environ['BASEDIR'] = BASEDIR ANGLE_SCALE = 5.0 + def ui_thread(addr): cv2.setNumThreads(1) - pygame.init() - pygame.font.init() - assert pygame_modules_have_loaded() - disp_info = pygame.display.Info() - max_height = disp_info.current_h + # Get monitor info before creating window + rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT) + rl.init_window(1, 1, "") + max_height = rl.get_monitor_height(0) + rl.close_window() hor_mode = os.getenv("HORIZONTAL") is not None - hor_mode = True if max_height < 960+300 else hor_mode + hor_mode = True if max_height < 960 + 300 else hor_mode if hor_mode: - size = (640+384+640, 960) + size = (640 + 384 + 640, 960) write_x = 5 write_y = 680 else: - size = (640+384, 960+300) + size = (640 + 384, 960 + 300) write_x = 645 write_y = 970 - pygame.display.set_caption("openpilot debug UI") - screen = pygame.display.set_mode(size, pygame.DOUBLEBUF) - - alert1_font = pygame.font.SysFont("arial", 30) - alert2_font = pygame.font.SysFont("arial", 20) - info_font = pygame.font.SysFont("arial", 15) - - camera_surface = pygame.surface.Surface((640, 480), 0, 24).convert() - top_down_surface = pygame.surface.Surface((UP.lidar_x, UP.lidar_y), 0, 8) - - sm = messaging.SubMaster(['carState', 'longitudinalPlan', 'carControl', 'radarState', 'liveCalibration', 'controlsState', - 'selfdriveState', 'liveTracks', 'modelV2', 'liveParameters', 'roadCameraState'], addr=addr) + rl.set_trace_log_level(rl.TraceLogLevel.LOG_ERROR) + rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT) + rl.init_window(size[0], size[1], "openpilot debug UI") + rl.set_target_fps(60) + + # Load font + font_path = os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf") + font = rl.load_font_ex(font_path, 32, None, 0) + + # Create textures for camera and top-down view + camera_image = rl.gen_image_color(640, 480, rl.BLACK) + camera_texture = rl.load_texture_from_image(camera_image) + rl.unload_image(camera_image) + + # lid_overlay array is (lidar_x, lidar_y) = (384, 960) + # pygame treats first axis as width, so texture is 384 wide x 960 tall + # For raylib, we need to transpose to get (height, width) = (960, 384) for the RGBA array + top_down_image = rl.gen_image_color(UP.lidar_x, UP.lidar_y, rl.BLACK) + top_down_texture = rl.load_texture_from_image(top_down_image) + rl.unload_image(top_down_image) + + sm = messaging.SubMaster( + [ + 'carState', + 'longitudinalPlan', + 'carControl', + 'radarState', + 'liveCalibration', + 'controlsState', + 'selfdriveState', + 'liveTracks', + 'modelV2', + 'liveParameters', + 'roadCameraState', + ], + addr=addr, + ) img = np.zeros((480, 640, 3), dtype='uint8') imgff = None @@ -65,74 +96,78 @@ def ui_thread(addr): lid_overlay_blank = get_blank_lid_overlay(UP) # plots - name_to_arr_idx = { "gas": 0, - "computer_gas": 1, - "user_brake": 2, - "computer_brake": 3, - "v_ego": 4, - "v_pid": 5, - "angle_steers_des": 6, - "angle_steers": 7, - "angle_steers_k": 8, - "steer_torque": 9, - "v_override": 10, - "v_cruise": 11, - "a_ego": 12, - "a_target": 13} + name_to_arr_idx = { + "gas": 0, + "computer_gas": 1, + "user_brake": 2, + "computer_brake": 3, + "v_ego": 4, + "v_pid": 5, + "angle_steers_des": 6, + "angle_steers": 7, + "angle_steers_k": 8, + "steer_torque": 9, + "v_override": 10, + "v_cruise": 11, + "a_ego": 12, + "a_target": 13, + } plot_arr = np.zeros((100, len(name_to_arr_idx.values()))) plot_xlims = [(0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0])] - plot_ylims = [(-0.1, 1.1), (-ANGLE_SCALE, ANGLE_SCALE), (0., 75.), (-3.0, 2.0)] - plot_names = [["gas", "computer_gas", "user_brake", "computer_brake"], - ["angle_steers", "angle_steers_des", "angle_steers_k", "steer_torque"], - ["v_ego", "v_override", "v_pid", "v_cruise"], - ["a_ego", "a_target"]] - plot_colors = [["b", "b", "g", "r", "y"], - ["b", "g", "y", "r"], - ["b", "g", "r", "y"], - ["b", "r"]] - plot_styles = [["-", "-", "-", "-", "-"], - ["-", "-", "-", "-"], - ["-", "-", "-", "-"], - ["-", "-"]] + plot_ylims = [(-0.1, 1.1), (-ANGLE_SCALE, ANGLE_SCALE), (0.0, 75.0), (-3.0, 2.0)] + plot_names = [ + ["gas", "computer_gas", "user_brake", "computer_brake"], + ["angle_steers", "angle_steers_des", "angle_steers_k", "steer_torque"], + ["v_ego", "v_override", "v_pid", "v_cruise"], + ["a_ego", "a_target"], + ] + plot_colors = [["b", "b", "g", "r", "y"], ["b", "g", "y", "r"], ["b", "g", "r", "y"], ["b", "r"]] + plot_styles = [["-", "-", "-", "-", "-"], ["-", "-", "-", "-"], ["-", "-", "-", "-"], ["-", "-"]] draw_plots = init_plots(plot_arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles) - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_ROAD, True) - while True: - for event in pygame.event.get(): - if event.type == pygame.QUIT: - pygame.quit() - sys.exit() - - screen.fill((64, 64, 64)) - lid_overlay = lid_overlay_blank.copy() - top_down = top_down_surface, lid_overlay + # Palette for converting lid_overlay grayscale indices to RGBA colors + palette = np.zeros((256, 4), dtype=np.uint8) + palette[:, 3] = 255 # alpha + palette[1] = [255, 0, 0, 255] # RED + palette[2] = [0, 255, 0, 255] # GREEN + palette[3] = [0, 0, 255, 255] # BLUE + palette[4] = [255, 255, 0, 255] # YELLOW + palette[110] = [110, 110, 110, 255] # car_color (gray) + palette[255] = [255, 255, 255, 255] # WHITE + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_ROAD, True) + while not rl.window_should_close(): # ***** frame ***** if not vipc_client.is_connected(): - vipc_client.connect(True) + vipc_client.connect(False) + + rl.begin_drawing() + rl.clear_background(rl.Color(64, 64, 64, 255)) yuv_img_raw = vipc_client.recv() if yuv_img_raw is None or not yuv_img_raw.data.any(): + rl.draw_text_ex(font, "waiting for frames", rl.Vector2(200, 200), 30, 0, rl.WHITE) + rl.end_drawing() continue + lid_overlay = lid_overlay_blank.copy() + top_down = top_down_texture, lid_overlay + sm.update(0) camera = DEVICE_CAMERAS[("tici", str(sm['roadCameraState'].sensor))] imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8).reshape((len(yuv_img_raw.data) // vipc_client.stride, vipc_client.stride)) num_px = vipc_client.width * vipc_client.height - rgb = cv2.cvtColor(imgff[:vipc_client.height * 3 // 2, :vipc_client.width], cv2.COLOR_YUV2RGB_NV12) + rgb = cv2.cvtColor(imgff[: vipc_client.height * 3 // 2, : vipc_client.width], cv2.COLOR_YUV2RGB_NV12) qcam = "QCAM" in os.environ - bb_scale = (528 if qcam else camera.fcam.width) / 640. - calib_scale = camera.fcam.width / 640. - zoom_matrix = np.asarray([ - [bb_scale, 0., 0.], - [0., bb_scale, 0.], - [0., 0., 1.]]) + bb_scale = (528 if qcam else camera.fcam.width) / 640.0 + calib_scale = camera.fcam.width / 640.0 + zoom_matrix = np.asarray([[bb_scale, 0.0, 0.0], [0.0, bb_scale, 0.0], [0.0, 0.0, 1.0]]) cv2.warpAffine(rgb, zoom_matrix[:2], (img.shape[1], img.shape[0]), dst=img, flags=cv2.WARP_INVERSE_MAP) intrinsic_matrix = camera.fcam.intrinsics @@ -151,11 +186,11 @@ def ui_thread(addr): plot_arr[-1, name_to_arr_idx['angle_steers_k']] = angle_steers_k plot_arr[-1, name_to_arr_idx['gas']] = sm['carState'].gasDEPRECATED # TODO gas is deprecated - plot_arr[-1, name_to_arr_idx['computer_gas']] = np.clip(sm['carControl'].actuators.accel/4.0, 0.0, 1.0) + plot_arr[-1, name_to_arr_idx['computer_gas']] = np.clip(sm['carControl'].actuators.accel / 4.0, 0.0, 1.0) plot_arr[-1, name_to_arr_idx['user_brake']] = sm['carState'].brake plot_arr[-1, name_to_arr_idx['steer_torque']] = sm['carControl'].actuators.torque * ANGLE_SCALE # TODO brake is deprecated - plot_arr[-1, name_to_arr_idx['computer_brake']] = np.clip(-sm['carControl'].actuators.accel/4.0, 0.0, 1.0) + plot_arr[-1, name_to_arr_idx['computer_brake']] = np.clip(-sm['carControl'].actuators.accel / 4.0, 0.0, 1.0) plot_arr[-1, name_to_arr_idx['v_ego']] = sm['carState'].vEgo plot_arr[-1, name_to_arr_idx['v_cruise']] = sm['carState'].cruiseState.speed plot_arr[-1, name_to_arr_idx['a_ego']] = sm['carState'].aEgo @@ -177,56 +212,63 @@ def ui_thread(addr): calibration = Calibration(num_px, rpyCalib, intrinsic_matrix, calib_scale) # *** blits *** - pygame.surfarray.blit_array(camera_surface, img.swapaxes(0, 1)) - screen.blit(camera_surface, (0, 0)) + # Update camera texture from numpy array + img_rgba = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA) + rl.update_texture(camera_texture, rl.ffi.cast("void *", img_rgba.ctypes.data)) + rl.draw_texture(camera_texture, 0, 0, rl.WHITE) # display alerts - alert_line1 = alert1_font.render(sm['selfdriveState'].alertText1, True, (255, 0, 0)) - alert_line2 = alert2_font.render(sm['selfdriveState'].alertText2, True, (255, 0, 0)) - screen.blit(alert_line1, (180, 150)) - screen.blit(alert_line2, (180, 190)) + rl.draw_text_ex(font, sm['selfdriveState'].alertText1, rl.Vector2(180, 150), 30, 0, rl.RED) + rl.draw_text_ex(font, sm['selfdriveState'].alertText2, rl.Vector2(180, 190), 20, 0, rl.RED) + # draw plots (texture is reused internally) + plot_texture = draw_plots(plot_arr) if hor_mode: - screen.blit(draw_plots(plot_arr), (640+384, 0)) + rl.draw_texture(plot_texture, 640 + 384, 0, rl.WHITE) else: - screen.blit(draw_plots(plot_arr), (0, 600)) + rl.draw_texture(plot_texture, 0, 600, rl.WHITE) - pygame.surfarray.blit_array(*top_down) - screen.blit(top_down[0], (640, 0)) + # Convert lid_overlay to RGBA and update top_down texture + # lid_overlay is (384, 960), need to transpose to (960, 384) for row-major RGBA buffer + lid_rgba = palette[lid_overlay.T] + rl.update_texture(top_down_texture, rl.ffi.cast("void *", np.ascontiguousarray(lid_rgba).ctypes.data)) + rl.draw_texture(top_down_texture, 640, 0, rl.WHITE) SPACING = 25 - lines = [ - info_font.render("ENABLED", True, GREEN if sm['selfdriveState'].enabled else BLACK), - info_font.render("SPEED: " + str(round(sm['carState'].vEgo, 1)) + " m/s", True, YELLOW), - info_font.render("LONG CONTROL STATE: " + str(sm['controlsState'].longControlState), True, YELLOW), - info_font.render("LONG MPC SOURCE: " + str(sm['longitudinalPlan'].longitudinalPlanSource), True, YELLOW), + ("ENABLED", GREEN if sm['selfdriveState'].enabled else BLACK), + ("SPEED: " + str(round(sm['carState'].vEgo, 1)) + " m/s", YELLOW), + ("LONG CONTROL STATE: " + str(sm['controlsState'].longControlState), YELLOW), + ("LONG MPC SOURCE: " + str(sm['longitudinalPlan'].longitudinalPlanSource), YELLOW), None, - info_font.render("ANGLE OFFSET (AVG): " + str(round(sm['liveParameters'].angleOffsetAverageDeg, 2)) + " deg", True, YELLOW), - info_font.render("ANGLE OFFSET (INSTANT): " + str(round(sm['liveParameters'].angleOffsetDeg, 2)) + " deg", True, YELLOW), - info_font.render("STIFFNESS: " + str(round(sm['liveParameters'].stiffnessFactor * 100., 2)) + " %", True, YELLOW), - info_font.render("STEER RATIO: " + str(round(sm['liveParameters'].steerRatio, 2)), True, YELLOW) + ("ANGLE OFFSET (AVG): " + str(round(sm['liveParameters'].angleOffsetAverageDeg, 2)) + " deg", YELLOW), + ("ANGLE OFFSET (INSTANT): " + str(round(sm['liveParameters'].angleOffsetDeg, 2)) + " deg", YELLOW), + ("STIFFNESS: " + str(round(sm['liveParameters'].stiffnessFactor * 100.0, 2)) + " %", YELLOW), + ("STEER RATIO: " + str(round(sm['liveParameters'].steerRatio, 2)), YELLOW), ] for i, line in enumerate(lines): if line is not None: - screen.blit(line, (write_x, write_y + i * SPACING)) + color = rl.Color(line[1][0], line[1][1], line[1][2], 255) + rl.draw_text_ex(font, line[0], rl.Vector2(write_x, write_y + i * SPACING), 20, 0, color) + + rl.end_drawing() + + rl.unload_texture(camera_texture) + rl.unload_texture(top_down_texture) + rl.unload_font(font) + rl.close_window() - # this takes time...vsync or something - pygame.display.flip() def get_arg_parser(): - parser = argparse.ArgumentParser( - description="Show replay data in a UI.", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser(description="Show replay data in a UI.", formatter_class=argparse.ArgumentDefaultsHelpFormatter) - parser.add_argument("ip_address", nargs="?", default="127.0.0.1", - help="The ip address on which to receive zmq messages.") + parser.add_argument("ip_address", nargs="?", default="127.0.0.1", help="The ip address on which to receive zmq messages.") - parser.add_argument("--frame-address", default=None, - help="The frame address (fully qualified ZMQ endpoint for frames) on which to receive zmq messages.") + parser.add_argument("--frame-address", default=None, help="The frame address (fully qualified ZMQ endpoint for frames) on which to receive zmq messages.") return parser + if __name__ == "__main__": args = get_arg_parser().parse_args(sys.argv[1:]) From ea64c4c0ae71e22b51852dc82065f76d96973235 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Sun, 4 Jan 2026 21:35:13 -0500 Subject: [PATCH 042/685] VW: Enable torqued (#36983) --- selfdrive/locationd/torqued.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 3f9b846e822..f4dc5a14716 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -33,7 +33,7 @@ MIN_ENGAGE_BUFFER = 2 # secs VERSION = 1 # bump this to invalidate old parameter caches -ALLOWED_CARS = ['toyota', 'hyundai', 'rivian', 'honda'] +ALLOWED_CARS = ['toyota', 'hyundai', 'rivian', 'honda', 'volkswagen'] def slope2rot(slope): From 5d3ab260e1ca25f3e196a42e2e2d7dc08f5dc1e3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 5 Jan 2026 10:15:20 -0800 Subject: [PATCH 043/685] welcome lexus ls --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index 186f1bc0470..6191c6ba3d4 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,6 @@ Version 0.10.4 (2026-02-17) ======================== +* Lexus LS 2018 support thanks to Hacheoy! Version 0.10.3 (2025-12-17) ======================== From c693bc1247e5c5f066262a1790cca4229c1e98a1 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Mon, 5 Jan 2026 16:14:05 -0800 Subject: [PATCH 044/685] =?UTF-8?q?MacroStiff=20Model=20=F0=9F=9F=A5?= =?UTF-8?q?=F0=9F=9F=A9=F0=9F=9F=A6=F0=9F=9F=A8=20(#36972)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 8c06e95e-d7c0-4fd9-ba02-9f0b6848785e/400 * test * test * test now --- selfdrive/modeld/models/driving_policy.onnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index e0eb918125e..4f7aaf0865d 100644 --- a/selfdrive/modeld/models/driving_policy.onnx +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f8fe9a71b0fd428a045a82ed50790179f77aa664391198f078e11e7b2cb2c2d7 +oid sha256:4cf2023d6742c20aac7cecbf520afe370a64bcf5efdcf3531b1b128f9dfa3cc8 size 13926324 From 3edb3243f6ecfcf4a8db6ffa8b2e86ab179a5730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 9 Jan 2026 09:16:57 -0800 Subject: [PATCH 045/685] SC driving (#36986) f1d30a23-4122-400a-80a6-557502284c36/200 --- selfdrive/modeld/models/driving_policy.onnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 4f7aaf0865d..27e4c8f7b9e 100644 --- a/selfdrive/modeld/models/driving_policy.onnx +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4cf2023d6742c20aac7cecbf520afe370a64bcf5efdcf3531b1b128f9dfa3cc8 +oid sha256:66f406ee179d984a4d8c93e38da479dcd1893127308dd3a7c322a7481a6b51b2 size 13926324 From f51c2aecede836a9fa9246cd2436a47d844a2e7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 9 Jan 2026 15:04:33 -0800 Subject: [PATCH 046/685] Modeld: less lat smoothing (#36987) * lat is plenty smooth! * fix --- selfdrive/modeld/modeld.py | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 006eeef6f5e..1f347dc32a0 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -40,7 +40,7 @@ VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' -LAT_SMOOTH_SECONDS = 0.1 +LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 4a58e321fc5..c109bf49f12 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -e0ad86508edb61b3eaa1b84662c515d2c3368295 \ No newline at end of file +b259f6f8f099a9d82e4c65dd5deae2e4e293007b \ No newline at end of file From 15d3a166f725725697e670cae3b5087624a42bf6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 9 Jan 2026 20:49:14 -0800 Subject: [PATCH 047/685] enable pyopencl on arm64 (#36990) --- pyproject.toml | 2 +- uv.lock | 42 +++++++++--------------------------------- 2 files changed, 10 insertions(+), 34 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 903995cf645..c21ba734dd8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -113,7 +113,7 @@ dev = [ "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", - "pyopencl; platform_machine != 'aarch64'", # broken on arm64 + "pyopencl", "pytools>=2025.1.6; platform_machine != 'aarch64'", "pywinctl", "pyprof2calltree", diff --git a/uv.lock b/uv.lock index f0f3cb35b9f..73c8fe7c9ce 100644 --- a/uv.lock +++ b/uv.lock @@ -1349,8 +1349,7 @@ dev = [ { name = "opencv-python-headless" }, { name = "parameterized" }, { name = "pyautogui" }, - { name = "pygame" }, - { name = "pyopencl", marker = "platform_machine != 'aarch64'" }, + { name = "pyopencl" }, { name = "pyprof2calltree" }, { name = "pytools", marker = "platform_machine != 'aarch64'" }, { name = "pywinctl" }, @@ -1423,9 +1422,8 @@ requires-dist = [ { name = "pyautogui", marker = "extra == 'dev'" }, { name = "pycapnp", specifier = "==2.1.0" }, { name = "pycryptodome" }, - { name = "pygame", marker = "extra == 'dev'" }, { name = "pyjwt" }, - { name = "pyopencl", marker = "platform_machine != 'aarch64' and extra == 'dev'" }, + { name = "pyopencl", marker = "extra == 'dev'" }, { name = "pyopenssl", specifier = "<24.3.0" }, { name = "pyprof2calltree", marker = "extra == 'dev'" }, { name = "pyserial" }, @@ -1778,28 +1776,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, ] -[[package]] -name = "pygame" -version = "2.6.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/49/cc/08bba60f00541f62aaa252ce0cfbd60aebd04616c0b9574f755b583e45ae/pygame-2.6.1.tar.gz", hash = "sha256:56fb02ead529cee00d415c3e007f75e0780c655909aaa8e8bf616ee09c9feb1f", size = 14808125, upload-time = "2024-09-29T13:41:34.698Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/ca/8f367cb9fe734c4f6f6400e045593beea2635cd736158f9fabf58ee14e3c/pygame-2.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:20349195326a5e82a16e351ed93465a7845a7e2a9af55b7bc1b2110ea3e344e1", size = 13113753, upload-time = "2024-09-29T14:26:13.751Z" }, - { url = "https://files.pythonhosted.org/packages/83/47/6edf2f890139616b3219be9cfcc8f0cb8f42eb15efd59597927e390538cb/pygame-2.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3935459109da4bb0b3901da9904f0a3e52028a3332a355d298b1673a334cf21", size = 12378146, upload-time = "2024-09-29T14:26:22.456Z" }, - { url = "https://files.pythonhosted.org/packages/00/9e/0d8aa8cf93db2d2ee38ebaf1c7b61d0df36ded27eb726221719c150c673d/pygame-2.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c31dbdb5d0217f32764797d21c2752e258e5fb7e895326538d82b5f75a0cd856", size = 13611760, upload-time = "2024-09-29T11:10:47.317Z" }, - { url = "https://files.pythonhosted.org/packages/d7/9e/d06adaa5cc65876bcd7a24f59f67e07f7e4194e6298130024ed3fb22c456/pygame-2.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:173badf82fa198e6888017bea40f511cb28e69ecdd5a72b214e81e4dcd66c3b1", size = 14298054, upload-time = "2024-09-29T11:39:53.891Z" }, - { url = "https://files.pythonhosted.org/packages/7a/a1/9ae2852ebd3a7cc7d9ae7ff7919ab983e4a5c1b7a14e840732f23b2b48f6/pygame-2.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce8cc108b92de9b149b344ad2e25eedbe773af0dc41dfb24d1f07f679b558c60", size = 13977107, upload-time = "2024-09-29T11:39:56.831Z" }, - { url = "https://files.pythonhosted.org/packages/31/df/6788fd2e9a864d0496a77670e44a7c012184b7a5382866ab0e60c55c0f28/pygame-2.6.1-cp311-cp311-win32.whl", hash = "sha256:811e7b925146d8149d79193652cbb83e0eca0aae66476b1cb310f0f4226b8b5c", size = 10250863, upload-time = "2024-09-29T11:44:48.199Z" }, - { url = "https://files.pythonhosted.org/packages/d2/55/ca3eb851aeef4f6f2e98a360c201f0d00bd1ba2eb98e2c7850d80aabc526/pygame-2.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:91476902426facd4bb0dad4dc3b2573bc82c95c71b135e0daaea072ed528d299", size = 10622016, upload-time = "2024-09-29T12:17:01.545Z" }, - { url = "https://files.pythonhosted.org/packages/92/16/2c602c332f45ff9526d61f6bd764db5096ff9035433e2172e2d2cadae8db/pygame-2.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4ee7f2771f588c966fa2fa8b829be26698c9b4836f82ede5e4edc1a68594942e", size = 13118279, upload-time = "2024-09-29T14:26:30.427Z" }, - { url = "https://files.pythonhosted.org/packages/cd/53/77ccbc384b251c6e34bfd2e734c638233922449a7844e3c7a11ef91cee39/pygame-2.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c8040ea2ab18c6b255af706ec01355c8a6b08dc48d77fd4ee783f8fc46a843bf", size = 12384524, upload-time = "2024-09-29T14:26:49.996Z" }, - { url = "https://files.pythonhosted.org/packages/06/be/3ed337583f010696c3b3435e89a74fb29d0c74d0931e8f33c0a4246307a9/pygame-2.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47a6938de93fa610accd4969e638c2aebcb29b2fca518a84c3a39d91ab47116", size = 13587123, upload-time = "2024-09-29T11:10:50.072Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ca/b015586a450db59313535662991b34d24c1f0c0dc149cc5f496573900f4e/pygame-2.6.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33006f784e1c7d7e466fcb61d5489da59cc5f7eb098712f792a225df1d4e229d", size = 14275532, upload-time = "2024-09-29T11:39:59.356Z" }, - { url = "https://files.pythonhosted.org/packages/b9/f2/d31e6ad42d657af07be2ffd779190353f759a07b51232b9e1d724f2cda46/pygame-2.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1206125f14cae22c44565c9d333607f1d9f59487b1f1432945dfc809aeaa3e88", size = 13952653, upload-time = "2024-09-29T11:40:01.781Z" }, - { url = "https://files.pythonhosted.org/packages/f3/42/8ea2a6979e6fa971702fece1747e862e2256d4a8558fe0da6364dd946c53/pygame-2.6.1-cp312-cp312-win32.whl", hash = "sha256:84fc4054e25262140d09d39e094f6880d730199710829902f0d8ceae0213379e", size = 10252421, upload-time = "2024-09-29T11:14:26.877Z" }, - { url = "https://files.pythonhosted.org/packages/5f/90/7d766d54bb95939725e9a9361f9c06b0cfbe3fe100aa35400f0a461a278a/pygame-2.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9e7396be0d9633831c3f8d5d82dd63ba373ad65599628294b7a4f8a5a01a65", size = 10624591, upload-time = "2024-09-29T11:52:54.489Z" }, -] - [[package]] name = "pygetwindow" version = "0.0.9" @@ -4290,10 +4266,10 @@ name = "pyopencl" version = "2025.2.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "pytools", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy" }, + { name = "platformdirs" }, + { name = "pytools" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f8/cb/8927052160bc0d3bd1123a645aaf57f696da364216b57b49f92ba0777bcc/pyopencl-2025.2.7.tar.gz", hash = "sha256:a68d92eb2970418b1a7ca45aff71984c02d2e4261e01402b273f257b5d6d7511", size = 444787, upload-time = "2025-10-28T14:23:15.497Z" } wheels = [ @@ -4515,9 +4491,9 @@ name = "pytools" version = "2025.2.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "siphash24", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "platformdirs" }, + { name = "siphash24" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c3/7b/f885a57e61ded45b5b10ca60f0b7575c9fb9a282e7513d0e23a33ee647e1/pytools-2025.2.5.tar.gz", hash = "sha256:a7f5350644d46d98ee9c7e67b4b41693308aa0f5e9b188d8f0694b27dc94e3a2", size = 85594, upload-time = "2025-10-07T15:53:30.49Z" } wheels = [ From 1252188b4b8e4228af29ed12639d4183186ef9c7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 13 Jan 2026 14:33:13 -0800 Subject: [PATCH 048/685] sensord: tighten temperature threshold (#36994) * sensord: tighten temperature threshold * lil more --- system/sensord/tests/test_sensord.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py index 5e98e122432..20214e12ffa 100644 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -32,23 +32,17 @@ Sensor = log.SensorEventData.SensorSource SensorConfig = namedtuple('SensorConfig', ['type', 'sanity_min', 'sanity_max']) ALL_SENSORS = { - Sensor.lsm6ds3: { - SensorConfig("acceleration", 5, 15), - SensorConfig("gyroUncalibrated", 0, .2), - SensorConfig("temperature", 0, 60), - }, - Sensor.lsm6ds3trc: { SensorConfig("acceleration", 5, 15), SensorConfig("gyroUncalibrated", 0, .2), - SensorConfig("temperature", 0, 60), + SensorConfig("temperature", 10, 40), # set for max range of our office }, Sensor.mmc5603nj: { SensorConfig("magneticUncalibrated", 0, 300), } } - +ALL_SENSORS[Sensor.lsm6ds3] = ALL_SENSORS[Sensor.lsm6ds3trc] def get_irq_count(irq: int): with open(f"/sys/kernel/irq/{irq}/per_cpu_count") as f: From 5e4b88201e78a2c4d84f29b919c333a2ea1997ce Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 15 Jan 2026 14:38:43 -0800 Subject: [PATCH 049/685] Toyota: whitelist hybrids for standstill resume behaviour (#36996) tioyota --- selfdrive/car/car_specific.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index bc903ec0831..c93d7ac4a24 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -2,6 +2,7 @@ from opendbc.car import DT_CTRL, structs from opendbc.car.car_helpers import interfaces from opendbc.car.interfaces import MAX_CTRL_SPEED +from opendbc.car.toyota.values import ToyotaFlags from openpilot.selfdrive.selfdrived.events import Events @@ -58,7 +59,7 @@ def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): # TODO: when we check for unexpected disengagement, check gear not S1, S2, S3 if self.CP.openpilotLongitudinalControl: # Only can leave standstill when planner wants to move - if CS.cruiseState.standstill and not CS.brakePressed and CC.cruiseControl.resume: + if CS.cruiseState.standstill and not CS.brakePressed and (CC.cruiseControl.resume or self.CP.flags & ToyotaFlags.HYBRID.value): events.add(EventName.resumeRequired) if CS.vEgo < self.CP.minEnableSpeed: events.add(EventName.belowEngageSpeed) From 7f8dbf24e72a37cc1b587d7390c6b6274ef581e7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 15 Jan 2026 17:18:40 -0800 Subject: [PATCH 050/685] Cabana: fix for internal source (#36998) * fix for internal sources from cursor * clean up * more * not needed * clean up * sure --- tools/replay/filereader.cc | 2 +- tools/replay/framereader.cc | 2 +- tools/replay/util.cc | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/replay/filereader.cc b/tools/replay/filereader.cc index d74aaebabae..cb13775a34b 100644 --- a/tools/replay/filereader.cc +++ b/tools/replay/filereader.cc @@ -17,7 +17,7 @@ std::string cacheFilePath(const std::string &url) { } std::string FileReader::read(const std::string &file, std::atomic *abort) { - const bool is_remote = file.find("https://") == 0; + const bool is_remote = (file.find("https://") == 0) || (file.find("http://") == 0); const std::string local_file = is_remote ? cacheFilePath(file) : file; std::string result; diff --git a/tools/replay/framereader.cc b/tools/replay/framereader.cc index a5f0f748c77..96ec5915b45 100644 --- a/tools/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -72,7 +72,7 @@ FrameReader::~FrameReader() { } bool FrameReader::load(CameraType type, const std::string &url, bool no_hw_decoder, std::atomic *abort, bool local_cache, int chunk_size, int retries) { - auto local_file_path = url.find("https://") == 0 ? cacheFilePath(url) : url; + auto local_file_path = (url.find("https://") == 0 || url.find("http://") == 0) ? cacheFilePath(url) : url; if (!util::file_exists(local_file_path)) { FileReader f(local_cache, chunk_size, retries); if (f.read(url, abort).empty()) { diff --git a/tools/replay/util.cc b/tools/replay/util.cc index 94cea961ffc..481564322e4 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -154,6 +154,8 @@ size_t getRemoteFileSize(const std::string &url, std::atomic *abort) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dumy_write_cb); curl_easy_setopt(curl, CURLOPT_HEADER, 1); curl_easy_setopt(curl, CURLOPT_NOBODY, 1); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); CURLM *cm = curl_multi_init(); curl_multi_add_handle(cm, curl); From 1f9efd93115db3dba28459bec790cfb35ee58e2f Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Sat, 17 Jan 2026 00:31:26 -0600 Subject: [PATCH 051/685] transformations: move Cython to pure Python (#36830) * Remove cython for transformations * Add new test * Switch back to program to fix mac builds * Convert to Python instead * Fix failing builds * lint * Implement conversion in pure python/numpy * Add more tests * Fix bugs in tests --- common/SConscript | 7 +- common/transformations/SConscript | 5 - common/transformations/coordinates.cc | 100 ----- common/transformations/coordinates.hpp | 43 --- common/transformations/orientation.cc | 144 -------- common/transformations/orientation.hpp | 17 - .../transformations/tests/test_coordinates.py | 33 ++ .../transformations/tests/test_orientation.py | 30 ++ common/transformations/transformations.pxd | 72 ---- common/transformations/transformations.py | 342 ++++++++++++++++++ common/transformations/transformations.pyx | 173 --------- selfdrive/modeld/SConscript | 2 +- 12 files changed, 407 insertions(+), 561 deletions(-) delete mode 100644 common/transformations/SConscript delete mode 100644 common/transformations/coordinates.cc delete mode 100644 common/transformations/coordinates.hpp delete mode 100644 common/transformations/orientation.cc delete mode 100644 common/transformations/orientation.hpp delete mode 100644 common/transformations/transformations.pxd create mode 100644 common/transformations/transformations.py delete mode 100644 common/transformations/transformations.pyx diff --git a/common/SConscript b/common/SConscript index c771ee78b7f..1c68cf05c7a 100644 --- a/common/SConscript +++ b/common/SConscript @@ -19,11 +19,6 @@ if GetOption('extras'): # Cython bindings params_python = envCython.Program('params_pyx.so', 'params_pyx.pyx', LIBS=envCython['LIBS'] + [_common, 'zmq', 'json11']) -SConscript([ - 'transformations/SConscript', -]) - -Import('transformations_python') -common_python = [params_python, transformations_python] +common_python = [params_python] Export('common_python') diff --git a/common/transformations/SConscript b/common/transformations/SConscript deleted file mode 100644 index 4ac73a165e4..00000000000 --- a/common/transformations/SConscript +++ /dev/null @@ -1,5 +0,0 @@ -Import('env', 'envCython') - -transformations = env.Library('transformations', ['orientation.cc', 'coordinates.cc']) -transformations_python = envCython.Program('transformations.so', 'transformations.pyx') -Export('transformations', 'transformations_python') diff --git a/common/transformations/coordinates.cc b/common/transformations/coordinates.cc deleted file mode 100644 index f3f10e547f5..00000000000 --- a/common/transformations/coordinates.cc +++ /dev/null @@ -1,100 +0,0 @@ -#define _USE_MATH_DEFINES - -#include "common/transformations/coordinates.hpp" - -#include -#include -#include - -double a = 6378137; // lgtm [cpp/short-global-name] -double b = 6356752.3142; // lgtm [cpp/short-global-name] -double esq = 6.69437999014 * 0.001; // lgtm [cpp/short-global-name] -double e1sq = 6.73949674228 * 0.001; - - -static Geodetic to_degrees(Geodetic geodetic){ - geodetic.lat = RAD2DEG(geodetic.lat); - geodetic.lon = RAD2DEG(geodetic.lon); - return geodetic; -} - -static Geodetic to_radians(Geodetic geodetic){ - geodetic.lat = DEG2RAD(geodetic.lat); - geodetic.lon = DEG2RAD(geodetic.lon); - return geodetic; -} - - -ECEF geodetic2ecef(const Geodetic &geodetic) { - auto g = to_radians(geodetic); - double xi = sqrt(1.0 - esq * pow(sin(g.lat), 2)); - double x = (a / xi + g.alt) * cos(g.lat) * cos(g.lon); - double y = (a / xi + g.alt) * cos(g.lat) * sin(g.lon); - double z = (a / xi * (1.0 - esq) + g.alt) * sin(g.lat); - return {x, y, z}; -} - -Geodetic ecef2geodetic(const ECEF &e) { - // Convert from ECEF to geodetic using Ferrari's methods - // https://en.wikipedia.org/wiki/Geographic_coordinate_conversion#Ferrari.27s_solution - double x = e.x; - double y = e.y; - double z = e.z; - - double r = sqrt(x * x + y * y); - double Esq = a * a - b * b; - double F = 54 * b * b * z * z; - double G = r * r + (1 - esq) * z * z - esq * Esq; - double C = (esq * esq * F * r * r) / (pow(G, 3)); - double S = cbrt(1 + C + sqrt(C * C + 2 * C)); - double P = F / (3 * pow((S + 1 / S + 1), 2) * G * G); - double Q = sqrt(1 + 2 * esq * esq * P); - double r_0 = -(P * esq * r) / (1 + Q) + sqrt(0.5 * a * a*(1 + 1.0 / Q) - P * (1 - esq) * z * z / (Q * (1 + Q)) - 0.5 * P * r * r); - double U = sqrt(pow((r - esq * r_0), 2) + z * z); - double V = sqrt(pow((r - esq * r_0), 2) + (1 - esq) * z * z); - double Z_0 = b * b * z / (a * V); - double h = U * (1 - b * b / (a * V)); - - double lat = atan((z + e1sq * Z_0) / r); - double lon = atan2(y, x); - - return to_degrees({lat, lon, h}); -} - -LocalCoord::LocalCoord(const Geodetic &geodetic, const ECEF &e) { - init_ecef << e.x, e.y, e.z; - - auto g = to_radians(geodetic); - - ned2ecef_matrix << - -sin(g.lat)*cos(g.lon), -sin(g.lon), -cos(g.lat)*cos(g.lon), - -sin(g.lat)*sin(g.lon), cos(g.lon), -cos(g.lat)*sin(g.lon), - cos(g.lat), 0, -sin(g.lat); - ecef2ned_matrix = ned2ecef_matrix.transpose(); -} - -NED LocalCoord::ecef2ned(const ECEF &e) { - Eigen::Vector3d ecef; - ecef << e.x, e.y, e.z; - - Eigen::Vector3d ned = (ecef2ned_matrix * (ecef - init_ecef)); - return {ned[0], ned[1], ned[2]}; -} - -ECEF LocalCoord::ned2ecef(const NED &n) { - Eigen::Vector3d ned; - ned << n.n, n.e, n.d; - - Eigen::Vector3d ecef = (ned2ecef_matrix * ned) + init_ecef; - return {ecef[0], ecef[1], ecef[2]}; -} - -NED LocalCoord::geodetic2ned(const Geodetic &g) { - ECEF e = ::geodetic2ecef(g); - return ecef2ned(e); -} - -Geodetic LocalCoord::ned2geodetic(const NED &n) { - ECEF e = ned2ecef(n); - return ::ecef2geodetic(e); -} diff --git a/common/transformations/coordinates.hpp b/common/transformations/coordinates.hpp deleted file mode 100644 index dc8ff7a4b6a..00000000000 --- a/common/transformations/coordinates.hpp +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include - -#define DEG2RAD(x) ((x) * M_PI / 180.0) -#define RAD2DEG(x) ((x) * 180.0 / M_PI) - -struct ECEF { - double x, y, z; - Eigen::Vector3d to_vector() const { - return Eigen::Vector3d(x, y, z); - } -}; - -struct NED { - double n, e, d; - Eigen::Vector3d to_vector() const { - return Eigen::Vector3d(n, e, d); - } -}; - -struct Geodetic { - double lat, lon, alt; - bool radians=false; -}; - -ECEF geodetic2ecef(const Geodetic &g); -Geodetic ecef2geodetic(const ECEF &e); - -class LocalCoord { -public: - Eigen::Matrix3d ned2ecef_matrix; - Eigen::Matrix3d ecef2ned_matrix; - Eigen::Vector3d init_ecef; - LocalCoord(const Geodetic &g, const ECEF &e); - LocalCoord(const Geodetic &g) : LocalCoord(g, ::geodetic2ecef(g)) {} - LocalCoord(const ECEF &e) : LocalCoord(::ecef2geodetic(e), e) {} - - NED ecef2ned(const ECEF &e); - ECEF ned2ecef(const NED &n); - NED geodetic2ned(const Geodetic &g); - Geodetic ned2geodetic(const NED &n); -}; diff --git a/common/transformations/orientation.cc b/common/transformations/orientation.cc deleted file mode 100644 index fb5e47a86a3..00000000000 --- a/common/transformations/orientation.cc +++ /dev/null @@ -1,144 +0,0 @@ -#define _USE_MATH_DEFINES - -#include -#include -#include - -#include "common/transformations/orientation.hpp" -#include "common/transformations/coordinates.hpp" - -Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat) { - if (quat.w() > 0){ - return quat; - } else { - return Eigen::Quaterniond(-quat.w(), -quat.x(), -quat.y(), -quat.z()); - } -} - -Eigen::Quaterniond euler2quat(const Eigen::Vector3d &euler) { - Eigen::Quaterniond q; - - q = Eigen::AngleAxisd(euler(2), Eigen::Vector3d::UnitZ()) - * Eigen::AngleAxisd(euler(1), Eigen::Vector3d::UnitY()) - * Eigen::AngleAxisd(euler(0), Eigen::Vector3d::UnitX()); - return ensure_unique(q); -} - - -Eigen::Vector3d quat2euler(const Eigen::Quaterniond &quat) { - // TODO: switch to eigen implementation if the range of the Euler angles doesn't matter anymore - // Eigen::Vector3d euler = quat.toRotationMatrix().eulerAngles(2, 1, 0); - // return {euler(2), euler(1), euler(0)}; - double gamma = atan2(2 * (quat.w() * quat.x() + quat.y() * quat.z()), 1 - 2 * (quat.x()*quat.x() + quat.y()*quat.y())); - double asin_arg_clipped = std::clamp(2 * (quat.w() * quat.y() - quat.z() * quat.x()), -1.0, 1.0); - double theta = asin(asin_arg_clipped); - double psi = atan2(2 * (quat.w() * quat.z() + quat.x() * quat.y()), 1 - 2 * (quat.y()*quat.y() + quat.z()*quat.z())); - return {gamma, theta, psi}; -} - -Eigen::Matrix3d quat2rot(const Eigen::Quaterniond &quat) { - return quat.toRotationMatrix(); -} - -Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot) { - return ensure_unique(Eigen::Quaterniond(rot)); -} - -Eigen::Matrix3d euler2rot(const Eigen::Vector3d &euler) { - return quat2rot(euler2quat(euler)); -} - -Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot) { - return quat2euler(rot2quat(rot)); -} - -Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw) { - return euler2rot({roll, pitch, yaw}); -} - -Eigen::Matrix3d rot(const Eigen::Vector3d &axis, double angle) { - Eigen::Quaterniond q; - q = Eigen::AngleAxisd(angle, axis); - return q.toRotationMatrix(); -} - - -Eigen::Vector3d ecef_euler_from_ned(const ECEF &ecef_init, const Eigen::Vector3d &ned_pose) { - /* - Using Rotations to Build Aerospace Coordinate Systems - Don Koks - https://apps.dtic.mil/dtic/tr/fulltext/u2/a484864.pdf - */ - LocalCoord converter = LocalCoord(ecef_init); - Eigen::Vector3d zero = ecef_init.to_vector(); - - Eigen::Vector3d x0 = converter.ned2ecef({1, 0, 0}).to_vector() - zero; - Eigen::Vector3d y0 = converter.ned2ecef({0, 1, 0}).to_vector() - zero; - Eigen::Vector3d z0 = converter.ned2ecef({0, 0, 1}).to_vector() - zero; - - Eigen::Vector3d x1 = rot(z0, ned_pose(2)) * x0; - Eigen::Vector3d y1 = rot(z0, ned_pose(2)) * y0; - Eigen::Vector3d z1 = rot(z0, ned_pose(2)) * z0; - - Eigen::Vector3d x2 = rot(y1, ned_pose(1)) * x1; - Eigen::Vector3d y2 = rot(y1, ned_pose(1)) * y1; - Eigen::Vector3d z2 = rot(y1, ned_pose(1)) * z1; - - Eigen::Vector3d x3 = rot(x2, ned_pose(0)) * x2; - Eigen::Vector3d y3 = rot(x2, ned_pose(0)) * y2; - - - x0 = Eigen::Vector3d(1, 0, 0); - y0 = Eigen::Vector3d(0, 1, 0); - z0 = Eigen::Vector3d(0, 0, 1); - - double psi = atan2(x3.dot(y0), x3.dot(x0)); - double theta = atan2(-x3.dot(z0), sqrt(pow(x3.dot(x0), 2) + pow(x3.dot(y0), 2))); - - y2 = rot(z0, psi) * y0; - z2 = rot(y2, theta) * z0; - - double phi = atan2(y3.dot(z2), y3.dot(y2)); - - return {phi, theta, psi}; -} - -Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d &ecef_pose) { - /* - Using Rotations to Build Aerospace Coordinate Systems - Don Koks - https://apps.dtic.mil/dtic/tr/fulltext/u2/a484864.pdf - */ - LocalCoord converter = LocalCoord(ecef_init); - - Eigen::Vector3d x0 = Eigen::Vector3d(1, 0, 0); - Eigen::Vector3d y0 = Eigen::Vector3d(0, 1, 0); - Eigen::Vector3d z0 = Eigen::Vector3d(0, 0, 1); - - Eigen::Vector3d x1 = rot(z0, ecef_pose(2)) * x0; - Eigen::Vector3d y1 = rot(z0, ecef_pose(2)) * y0; - Eigen::Vector3d z1 = rot(z0, ecef_pose(2)) * z0; - - Eigen::Vector3d x2 = rot(y1, ecef_pose(1)) * x1; - Eigen::Vector3d y2 = rot(y1, ecef_pose(1)) * y1; - Eigen::Vector3d z2 = rot(y1, ecef_pose(1)) * z1; - - Eigen::Vector3d x3 = rot(x2, ecef_pose(0)) * x2; - Eigen::Vector3d y3 = rot(x2, ecef_pose(0)) * y2; - - Eigen::Vector3d zero = ecef_init.to_vector(); - x0 = converter.ned2ecef({1, 0, 0}).to_vector() - zero; - y0 = converter.ned2ecef({0, 1, 0}).to_vector() - zero; - z0 = converter.ned2ecef({0, 0, 1}).to_vector() - zero; - - double psi = atan2(x3.dot(y0), x3.dot(x0)); - double theta = atan2(-x3.dot(z0), sqrt(pow(x3.dot(x0), 2) + pow(x3.dot(y0), 2))); - - y2 = rot(z0, psi) * y0; - z2 = rot(y2, theta) * z0; - - double phi = atan2(y3.dot(z2), y3.dot(y2)); - - return {phi, theta, psi}; -} - diff --git a/common/transformations/orientation.hpp b/common/transformations/orientation.hpp deleted file mode 100644 index 0874a0a814b..00000000000 --- a/common/transformations/orientation.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once -#include -#include "common/transformations/coordinates.hpp" - - -Eigen::Quaterniond ensure_unique(const Eigen::Quaterniond &quat); - -Eigen::Quaterniond euler2quat(const Eigen::Vector3d &euler); -Eigen::Vector3d quat2euler(const Eigen::Quaterniond &quat); -Eigen::Matrix3d quat2rot(const Eigen::Quaterniond &quat); -Eigen::Quaterniond rot2quat(const Eigen::Matrix3d &rot); -Eigen::Matrix3d euler2rot(const Eigen::Vector3d &euler); -Eigen::Vector3d rot2euler(const Eigen::Matrix3d &rot); -Eigen::Matrix3d rot_matrix(double roll, double pitch, double yaw); -Eigen::Matrix3d rot(const Eigen::Vector3d &axis, double angle); -Eigen::Vector3d ecef_euler_from_ned(const ECEF &ecef_init, const Eigen::Vector3d &ned_pose); -Eigen::Vector3d ned_euler_from_ecef(const ECEF &ecef_init, const Eigen::Vector3d &ecef_pose); diff --git a/common/transformations/tests/test_coordinates.py b/common/transformations/tests/test_coordinates.py index 11a6bf70eef..0b5d1c36dfb 100644 --- a/common/transformations/tests/test_coordinates.py +++ b/common/transformations/tests/test_coordinates.py @@ -102,3 +102,36 @@ def test_ned_batch(self): np.testing.assert_allclose(converter.ned2ecef(ned_offsets_batch), ecef_positions_offset_batch, rtol=1e-9, atol=1e-7) + + def test_errors(self): + # Test wrong shape/type for geodetic2ecef + # numpy_wrap raises IndexError for scalar input + with np.testing.assert_raises(IndexError): + coord.geodetic2ecef(1.0) + + with np.testing.assert_raises_regex(ValueError, "Geodetic must be size 3"): + coord.geodetic2ecef([0, 0]) + + with np.testing.assert_raises_regex(ValueError, "Geodetic must be size 3"): + coord.geodetic2ecef([0, 0, 0, 0]) + + with np.testing.assert_raises(TypeError): + coord.geodetic2ecef(['a', 'b', 'c']) + + # Test LocalCoord constructor errors + with np.testing.assert_raises(ValueError): + coord.LocalCoord.from_geodetic([0, 0]) + + with np.testing.assert_raises(ValueError): + coord.LocalCoord.from_geodetic(1) + + with np.testing.assert_raises(TypeError): + coord.LocalCoord.from_geodetic(['a', 'b', 'c']) + + # Test wrong shape/type for ecef2geodetic + with np.testing.assert_raises(ValueError): + coord.ecef2geodetic([1, 2]) + with np.testing.assert_raises(ValueError): + coord.ecef2geodetic([1, 2, 3, 4]) + with np.testing.assert_raises(IndexError): + coord.ecef2geodetic(1.0) diff --git a/common/transformations/tests/test_orientation.py b/common/transformations/tests/test_orientation.py index 55fbc6581e3..1bf94115c83 100644 --- a/common/transformations/tests/test_orientation.py +++ b/common/transformations/tests/test_orientation.py @@ -1,4 +1,5 @@ import numpy as np +import pytest from openpilot.common.transformations.orientation import euler2quat, quat2euler, euler2rot, rot2euler, \ rot2quat, quat2rot, \ @@ -59,3 +60,32 @@ def test_euler_ned(self): np.testing.assert_allclose(ned_eulers[i], ned_euler_from_ecef(ecef_positions[i], eulers[i]), rtol=1e-7) #np.testing.assert_allclose(eulers[i], ecef_euler_from_ned(ecef_positions[i], ned_eulers[i]), rtol=1e-7) # np.testing.assert_allclose(ned_eulers, ned_euler_from_ecef(ecef_positions, eulers), rtol=1e-7) + + def test_inputs(self): + with pytest.raises(ValueError): + euler2quat([1, 2]) + + with pytest.raises(ValueError): + quat2rot([1, 2, 3]) + + with pytest.raises(IndexError): + rot2quat(np.zeros((2, 2))) + + def test_euler_rot_consistency(self): + rpy = [0.1, 0.2, 0.3] + R = euler2rot(rpy) + + # R -> q -> R + q = rot2quat(R) + R_new = quat2rot(q) + np.testing.assert_allclose(R, R_new, atol=1e-15) + + # q -> R -> Euler (quat2euler) -> R + rpy_new = quat2euler(q) + R_new2 = euler2rot(rpy_new) + np.testing.assert_allclose(R, R_new2, atol=1e-15) + + # R -> Euler (rot2euler) -> R + rpy_from_rot = rot2euler(R) + R_new3 = euler2rot(rpy_from_rot) + np.testing.assert_allclose(R, R_new3, atol=1e-15) diff --git a/common/transformations/transformations.pxd b/common/transformations/transformations.pxd deleted file mode 100644 index fe32e18deac..00000000000 --- a/common/transformations/transformations.pxd +++ /dev/null @@ -1,72 +0,0 @@ -# cython: language_level=3 -from libcpp cimport bool - -cdef extern from "orientation.cc": - pass - -cdef extern from "orientation.hpp": - cdef cppclass Quaternion "Eigen::Quaterniond": - Quaternion() - Quaternion(double, double, double, double) - double w() - double x() - double y() - double z() - - cdef cppclass Vector3 "Eigen::Vector3d": - Vector3() - Vector3(double, double, double) - double operator()(int) - - cdef cppclass Matrix3 "Eigen::Matrix3d": - Matrix3() - Matrix3(double*) - - double operator()(int, int) - - Quaternion euler2quat(const Vector3 &) - Vector3 quat2euler(const Quaternion &) - Matrix3 quat2rot(const Quaternion &) - Quaternion rot2quat(const Matrix3 &) - Vector3 rot2euler(const Matrix3 &) - Matrix3 euler2rot(const Vector3 &) - Matrix3 rot_matrix(double, double, double) - Vector3 ecef_euler_from_ned(const ECEF &, const Vector3 &) - Vector3 ned_euler_from_ecef(const ECEF &, const Vector3 &) - - -cdef extern from "coordinates.cc": - cdef struct ECEF: - double x - double y - double z - - cdef struct NED: - double n - double e - double d - - cdef struct Geodetic: - double lat - double lon - double alt - bool radians - - ECEF geodetic2ecef(const Geodetic &) - Geodetic ecef2geodetic(const ECEF &) - - cdef cppclass LocalCoord_c "LocalCoord": - Matrix3 ned2ecef_matrix - Matrix3 ecef2ned_matrix - - LocalCoord_c(const Geodetic &, const ECEF &) - LocalCoord_c(const Geodetic &) - LocalCoord_c(const ECEF &) - - NED ecef2ned(const ECEF &) - ECEF ned2ecef(const NED &) - NED geodetic2ned(const Geodetic &) - Geodetic ned2geodetic(const NED &) - -cdef extern from "coordinates.hpp": - pass diff --git a/common/transformations/transformations.py b/common/transformations/transformations.py new file mode 100644 index 00000000000..5cb6220f95e --- /dev/null +++ b/common/transformations/transformations.py @@ -0,0 +1,342 @@ +import numpy as np + + +# Constants +a = 6378137.0 +b = 6356752.3142 +esq = 6.69437999014e-3 +e1sq = 6.73949674228e-3 + + +def geodetic2ecef_single(g): + """ + Convert geodetic coordinates (latitude, longitude, altitude) to ECEF. + """ + try: + if len(g) != 3: + raise ValueError("Geodetic must be size 3") + except TypeError: + raise ValueError("Geodetic must be a sequence of length 3") from None + + lat, lon, alt = g + lat = np.radians(lat) + lon = np.radians(lon) + xi = np.sqrt(1.0 - esq * np.sin(lat)**2) + x = (a / xi + alt) * np.cos(lat) * np.cos(lon) + y = (a / xi + alt) * np.cos(lat) * np.sin(lon) + z = (a / xi * (1.0 - esq) + alt) * np.sin(lat) + return np.array([x, y, z]) + + +def ecef2geodetic_single(e): + """ + Convert ECEF to geodetic coordinates using Ferrari's solution. + """ + x, y, z = e + r = np.sqrt(x**2 + y**2) + Esq = a**2 - b**2 + F = 54 * b**2 * z**2 + G = r**2 + (1 - esq) * z**2 - esq * Esq + C = (esq**2 * F * r**2) / (G**3) + S = np.cbrt(1 + C + np.sqrt(C**2 + 2 * C)) + P = F / (3 * (S + 1 / S + 1)**2 * G**2) + Q = np.sqrt(1 + 2 * esq**2 * P) + r_0 = -(P * esq * r) / (1 + Q) + np.sqrt(0.5 * a**2 * (1 + 1.0 / Q) - P * (1 - esq) * z**2 / (Q * (1 + Q)) - 0.5 * P * r**2) + U = np.sqrt((r - esq * r_0)**2 + z**2) + V = np.sqrt((r - esq * r_0)**2 + (1 - esq) * z**2) + Z_0 = b**2 * z / (a * V) + h = U * (1 - b**2 / (a * V)) + lat = np.arctan((z + e1sq * Z_0) / r) + lon = np.arctan2(y, x) + return np.array([np.degrees(lat), np.degrees(lon), h]) + + +def euler2quat_single(euler): + """ + Convert Euler angles (roll, pitch, yaw) to a quaternion. + Rotation order: Z-Y-X (yaw, pitch, roll). + """ + phi, theta, psi = euler + + c_phi, s_phi = np.cos(phi / 2), np.sin(phi / 2) + c_theta, s_theta = np.cos(theta / 2), np.sin(theta / 2) + c_psi, s_psi = np.cos(psi / 2), np.sin(psi / 2) + + w = c_phi * c_theta * c_psi + s_phi * s_theta * s_psi + x = s_phi * c_theta * c_psi - c_phi * s_theta * s_psi + y = c_phi * s_theta * c_psi + s_phi * c_theta * s_psi + z = c_phi * c_theta * s_psi - s_phi * s_theta * c_psi + + if w < 0: + return np.array([-w, -x, -y, -z]) + return np.array([w, x, y, z]) + + +def quat2euler_single(q): + """ + Convert a quaternion to Euler angles (roll, pitch, yaw). + """ + w, x, y, z = q + gamma = np.arctan2(2 * (w * x + y * z), 1 - 2 * (x**2 + y**2)) + sin_arg = 2 * (w * y - z * x) + sin_arg = np.clip(sin_arg, -1.0, 1.0) + theta = np.arcsin(sin_arg) + psi = np.arctan2(2 * (w * z + x * y), 1 - 2 * (y**2 + z**2)) + return np.array([gamma, theta, psi]) + + +def quat2rot_single(q): + """ + Convert a quaternion to a 3x3 rotation matrix. + """ + w, x, y, z = q + xx, yy, zz = x * x, y * y, z * z + xy, xz, yz = x * y, x * z, y * z + wx, wy, wz = w * x, w * y, w * z + + mat = np.array([ + [1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy)], + [2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx)], + [2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy)] + ]) + return mat + + +def rot2quat_single(rot): + """ + Convert a 3x3 rotation matrix to a quaternion. + """ + trace = np.trace(rot) + if trace > 0: + s = 0.5 / np.sqrt(trace + 1.0) + w = 0.25 / s + x = (rot[2, 1] - rot[1, 2]) * s + y = (rot[0, 2] - rot[2, 0]) * s + z = (rot[1, 0] - rot[0, 1]) * s + else: + if rot[0, 0] > rot[1, 1] and rot[0, 0] > rot[2, 2]: + s = 2.0 * np.sqrt(1.0 + rot[0, 0] - rot[1, 1] - rot[2, 2]) + w = (rot[2, 1] - rot[1, 2]) / s + x = 0.25 * s + y = (rot[0, 1] + rot[1, 0]) / s + z = (rot[0, 2] + rot[2, 0]) / s + elif rot[1, 1] > rot[2, 2]: + s = 2.0 * np.sqrt(1.0 + rot[1, 1] - rot[0, 0] - rot[2, 2]) + w = (rot[0, 2] - rot[2, 0]) / s + x = (rot[0, 1] + rot[1, 0]) / s + y = 0.25 * s + z = (rot[1, 2] + rot[2, 1]) / s + else: + s = 2.0 * np.sqrt(1.0 + rot[2, 2] - rot[0, 0] - rot[1, 1]) + w = (rot[1, 0] - rot[0, 1]) / s + x = (rot[0, 2] + rot[2, 0]) / s + y = (rot[1, 2] + rot[2, 1]) / s + z = 0.25 * s + + if w < 0: + return np.array([-w, -x, -y, -z]) + return np.array([w, x, y, z]) + + +def euler2rot_single(euler): + """ + Convert Euler angles (roll, pitch, yaw) to a 3x3 rotation matrix. + Rotation order: Z-Y-X (yaw, pitch, roll). + """ + phi, theta, psi = euler + + cx, sx = np.cos(phi), np.sin(phi) + cy, sy = np.cos(theta), np.sin(theta) + cz, sz = np.cos(psi), np.sin(psi) + + Rx = np.array([[1, 0, 0], [0, cx, -sx], [0, sx, cx]]) + Ry = np.array([[cy, 0, sy], [0, 1, 0], [-sy, 0, cy]]) + Rz = np.array([[cz, -sz, 0], [sz, cz, 0], [0, 0, 1]]) + + return Rz @ Ry @ Rx + + +def rot2euler_single(rot): + """ + Convert a 3x3 rotation matrix to Euler angles (roll, pitch, yaw). + """ + return quat2euler_single(rot2quat_single(rot)) + + +def rot_matrix(roll, pitch, yaw): + """ + Create a 3x3 rotation matrix from roll, pitch, and yaw angles. + """ + return euler2rot_single([roll, pitch, yaw]) + + +def axis_angle_to_rot(axis, angle): + """ + Convert an axis-angle representation to a 3x3 rotation matrix. + """ + c = np.cos(angle / 2) + s = np.sin(angle / 2) + q = np.array([c, s*axis[0], s*axis[1], s*axis[2]]) + return quat2rot_single(q) + + +class LocalCoord: + """ + A class to handle conversions between ECEF and local NED coordinates. + """ + def __init__(self, geodetic=None, ecef=None): + """ + Initialize LocalCoord with either geodetic or ECEF coordinates. + """ + if geodetic is not None: + self.init_ecef = geodetic2ecef_single(geodetic) + lat, lon, _ = geodetic + elif ecef is not None: + self.init_ecef = np.array(ecef) + lat, lon, _ = ecef2geodetic_single(ecef) + else: + raise ValueError("Must provide geodetic or ecef") + + lat = np.radians(lat) + lon = np.radians(lon) + + self.ned2ecef_matrix = np.array([ + [-np.sin(lat) * np.cos(lon), -np.sin(lon), -np.cos(lat) * np.cos(lon)], + [-np.sin(lat) * np.sin(lon), np.cos(lon), -np.cos(lat) * np.sin(lon)], + [np.cos(lat), 0, -np.sin(lat)] + ]) + self.ecef2ned_matrix = self.ned2ecef_matrix.T + + @classmethod + def from_geodetic(cls, geodetic): + """ + Create a LocalCoord instance from geodetic coordinates. + """ + return cls(geodetic=geodetic) + + @classmethod + def from_ecef(cls, ecef): + """ + Create a LocalCoord instance from ECEF coordinates. + """ + return cls(ecef=ecef) + + def ecef2ned_single(self, ecef): + """ + Convert a single ECEF point to NED coordinates relative to the origin. + """ + return self.ecef2ned_matrix @ (ecef - self.init_ecef) + + def ned2ecef_single(self, ned): + """ + Convert a single NED point to ECEF coordinates. + """ + return self.ned2ecef_matrix @ ned + self.init_ecef + + def geodetic2ned_single(self, geodetic): + """ + Convert a single geodetic point to NED coordinates. + """ + ecef = geodetic2ecef_single(geodetic) + return self.ecef2ned_single(ecef) + + def ned2geodetic_single(self, ned): + """ + Convert a single NED point to geodetic coordinates. + """ + ecef = self.ned2ecef_single(ned) + return ecef2geodetic_single(ecef) + + @property + def ned_from_ecef_matrix(self): + """ + Returns the rotation matrix from ECEF to NED coordinates. + """ + return self.ecef2ned_matrix + + @property + def ecef_from_ned_matrix(self): + """ + Returns the rotation matrix from NED to ECEF coordinates. + """ + return self.ned2ecef_matrix + + +def ecef_euler_from_ned_single(ecef_init, ned_pose): + """ + Convert NED Euler angles (roll, pitch, yaw) at a given ECEF origin + to equivalent ECEF Euler angles. + """ + converter = LocalCoord(ecef=ecef_init) + zero = np.array(ecef_init) + + x0 = converter.ned2ecef_single([1, 0, 0]) - zero + y0 = converter.ned2ecef_single([0, 1, 0]) - zero + z0 = converter.ned2ecef_single([0, 0, 1]) - zero + + phi, theta, psi = ned_pose + + x1 = axis_angle_to_rot(z0, psi) @ x0 + y1 = axis_angle_to_rot(z0, psi) @ y0 + z1 = axis_angle_to_rot(z0, psi) @ z0 + + x2 = axis_angle_to_rot(y1, theta) @ x1 + y2 = axis_angle_to_rot(y1, theta) @ y1 + z2 = axis_angle_to_rot(y1, theta) @ z1 + + x3 = axis_angle_to_rot(x2, phi) @ x2 + y3 = axis_angle_to_rot(x2, phi) @ y2 + + x0 = np.array([1.0, 0, 0]) + y0 = np.array([0, 1.0, 0]) + z0 = np.array([0, 0, 1.0]) + + psi_out = np.arctan2(np.dot(x3, y0), np.dot(x3, x0)) + theta_out = np.arctan2(-np.dot(x3, z0), np.sqrt(np.dot(x3, x0)**2 + np.dot(x3, y0)**2)) + + y2 = axis_angle_to_rot(z0, psi_out) @ y0 + z2 = axis_angle_to_rot(y2, theta_out) @ z0 + + phi_out = np.arctan2(np.dot(y3, z2), np.dot(y3, y2)) + + return np.array([phi_out, theta_out, psi_out]) + + +def ned_euler_from_ecef_single(ecef_init, ecef_pose): + """ + Convert ECEF Euler angles (roll, pitch, yaw) at a given ECEF origin + to equivalent NED Euler angles. + """ + converter = LocalCoord(ecef=ecef_init) + + x0 = np.array([1.0, 0, 0]) + y0 = np.array([0, 1.0, 0]) + z0 = np.array([0, 0, 1.0]) + + phi, theta, psi = ecef_pose + + x1 = axis_angle_to_rot(z0, psi) @ x0 + y1 = axis_angle_to_rot(z0, psi) @ y0 + z1 = axis_angle_to_rot(z0, psi) @ z0 + + x2 = axis_angle_to_rot(y1, theta) @ x1 + y2 = axis_angle_to_rot(y1, theta) @ y1 + z2 = axis_angle_to_rot(y1, theta) @ z1 + + x3 = axis_angle_to_rot(x2, phi) @ x2 + y3 = axis_angle_to_rot(x2, phi) @ y2 + + zero = np.array(ecef_init) + x0 = converter.ned2ecef_single([1, 0, 0]) - zero + y0 = converter.ned2ecef_single([0, 1, 0]) - zero + z0 = converter.ned2ecef_single([0, 0, 1]) - zero + + psi_out = np.arctan2(np.dot(x3, y0), np.dot(x3, x0)) + theta_out = np.arctan2(-np.dot(x3, z0), np.sqrt(np.dot(x3, x0)**2 + np.dot(x3, y0)**2)) + + y2 = axis_angle_to_rot(z0, psi_out) @ y0 + z2 = axis_angle_to_rot(y2, theta_out) @ z0 + + phi_out = np.arctan2(np.dot(y3, z2), np.dot(y3, y2)) + + return np.array([phi_out, theta_out, psi_out]) diff --git a/common/transformations/transformations.pyx b/common/transformations/transformations.pyx deleted file mode 100644 index ae045c369d7..00000000000 --- a/common/transformations/transformations.pyx +++ /dev/null @@ -1,173 +0,0 @@ -# distutils: language = c++ -# cython: language_level = 3 -from openpilot.common.transformations.transformations cimport Matrix3, Vector3, Quaternion -from openpilot.common.transformations.transformations cimport ECEF, NED, Geodetic - -from openpilot.common.transformations.transformations cimport euler2quat as euler2quat_c -from openpilot.common.transformations.transformations cimport quat2euler as quat2euler_c -from openpilot.common.transformations.transformations cimport quat2rot as quat2rot_c -from openpilot.common.transformations.transformations cimport rot2quat as rot2quat_c -from openpilot.common.transformations.transformations cimport euler2rot as euler2rot_c -from openpilot.common.transformations.transformations cimport rot2euler as rot2euler_c -from openpilot.common.transformations.transformations cimport rot_matrix as rot_matrix_c -from openpilot.common.transformations.transformations cimport ecef_euler_from_ned as ecef_euler_from_ned_c -from openpilot.common.transformations.transformations cimport ned_euler_from_ecef as ned_euler_from_ecef_c -from openpilot.common.transformations.transformations cimport geodetic2ecef as geodetic2ecef_c -from openpilot.common.transformations.transformations cimport ecef2geodetic as ecef2geodetic_c -from openpilot.common.transformations.transformations cimport LocalCoord_c - - -import numpy as np -cimport numpy as np - -cdef np.ndarray[double, ndim=2] matrix2numpy(Matrix3 m): - return np.array([ - [m(0, 0), m(0, 1), m(0, 2)], - [m(1, 0), m(1, 1), m(1, 2)], - [m(2, 0), m(2, 1), m(2, 2)], - ]) - -cdef Matrix3 numpy2matrix(np.ndarray[double, ndim=2, mode="fortran"] m): - assert m.shape[0] == 3 - assert m.shape[1] == 3 - return Matrix3(m.data) - -cdef ECEF list2ecef(ecef): - cdef ECEF e - e.x = ecef[0] - e.y = ecef[1] - e.z = ecef[2] - return e - -cdef NED list2ned(ned): - cdef NED n - n.n = ned[0] - n.e = ned[1] - n.d = ned[2] - return n - -cdef Geodetic list2geodetic(geodetic): - cdef Geodetic g - g.lat = geodetic[0] - g.lon = geodetic[1] - g.alt = geodetic[2] - return g - -def euler2quat_single(euler): - cdef Vector3 e = Vector3(euler[0], euler[1], euler[2]) - cdef Quaternion q = euler2quat_c(e) - return [q.w(), q.x(), q.y(), q.z()] - -def quat2euler_single(quat): - cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3]) - cdef Vector3 e = quat2euler_c(q) - return [e(0), e(1), e(2)] - -def quat2rot_single(quat): - cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3]) - cdef Matrix3 r = quat2rot_c(q) - return matrix2numpy(r) - -def rot2quat_single(rot): - cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double)) - cdef Quaternion q = rot2quat_c(r) - return [q.w(), q.x(), q.y(), q.z()] - -def euler2rot_single(euler): - cdef Vector3 e = Vector3(euler[0], euler[1], euler[2]) - cdef Matrix3 r = euler2rot_c(e) - return matrix2numpy(r) - -def rot2euler_single(rot): - cdef Matrix3 r = numpy2matrix(np.asfortranarray(rot, dtype=np.double)) - cdef Vector3 e = rot2euler_c(r) - return [e(0), e(1), e(2)] - -def rot_matrix(roll, pitch, yaw): - return matrix2numpy(rot_matrix_c(roll, pitch, yaw)) - -def ecef_euler_from_ned_single(ecef_init, ned_pose): - cdef ECEF init = list2ecef(ecef_init) - cdef Vector3 pose = Vector3(ned_pose[0], ned_pose[1], ned_pose[2]) - - cdef Vector3 e = ecef_euler_from_ned_c(init, pose) - return [e(0), e(1), e(2)] - -def ned_euler_from_ecef_single(ecef_init, ecef_pose): - cdef ECEF init = list2ecef(ecef_init) - cdef Vector3 pose = Vector3(ecef_pose[0], ecef_pose[1], ecef_pose[2]) - - cdef Vector3 e = ned_euler_from_ecef_c(init, pose) - return [e(0), e(1), e(2)] - -def geodetic2ecef_single(geodetic): - cdef Geodetic g = list2geodetic(geodetic) - cdef ECEF e = geodetic2ecef_c(g) - return [e.x, e.y, e.z] - -def ecef2geodetic_single(ecef): - cdef ECEF e = list2ecef(ecef) - cdef Geodetic g = ecef2geodetic_c(e) - return [g.lat, g.lon, g.alt] - - -cdef class LocalCoord: - cdef LocalCoord_c * lc - - def __init__(self, geodetic=None, ecef=None): - assert (geodetic is not None) or (ecef is not None) - if geodetic is not None: - self.lc = new LocalCoord_c(list2geodetic(geodetic)) - elif ecef is not None: - self.lc = new LocalCoord_c(list2ecef(ecef)) - - @property - def ned2ecef_matrix(self): - return matrix2numpy(self.lc.ned2ecef_matrix) - - @property - def ecef2ned_matrix(self): - return matrix2numpy(self.lc.ecef2ned_matrix) - - @property - def ned_from_ecef_matrix(self): - return self.ecef2ned_matrix - - @property - def ecef_from_ned_matrix(self): - return self.ned2ecef_matrix - - @classmethod - def from_geodetic(cls, geodetic): - return cls(geodetic=geodetic) - - @classmethod - def from_ecef(cls, ecef): - return cls(ecef=ecef) - - def ecef2ned_single(self, ecef): - assert self.lc - cdef ECEF e = list2ecef(ecef) - cdef NED n = self.lc.ecef2ned(e) - return [n.n, n.e, n.d] - - def ned2ecef_single(self, ned): - assert self.lc - cdef NED n = list2ned(ned) - cdef ECEF e = self.lc.ned2ecef(n) - return [e.x, e.y, e.z] - - def geodetic2ned_single(self, geodetic): - assert self.lc - cdef Geodetic g = list2geodetic(geodetic) - cdef NED n = self.lc.geodetic2ned(g) - return [n.n, n.e, n.d] - - def ned2geodetic_single(self, ned): - assert self.lc - cdef NED n = list2ned(ned) - cdef Geodetic g = self.lc.ned2geodetic(n) - return [g.lat, g.lon, g.alt] - - def __dealloc__(self): - del self.lc diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 8b33a457f20..a184b6a23dc 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,7 +1,7 @@ import os import glob -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc', 'transformations') +Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') lenv = env.Clone() lenvCython = envCython.Clone() From a46ff01cab6d79ec35b1fd91b5180da534804d0b Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:39:21 -0800 Subject: [PATCH 052/685] [bot] Update Python packages (#36966) * Update Python packages * ty fixes --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- common/pid.py | 12 +- docs/CARS.md | 159 +- panda | 2 +- pyproject.toml | 4 +- selfdrive/car/car_specific.py | 2 +- .../debug/car/hyundai_enable_radar_points.py | 6 +- selfdrive/debug/car/vw_mqb_config.py | 10 +- selfdrive/debug/cpu_usage_stat.py | 1 - selfdrive/debug/fuzz_fw_fingerprint.py | 1 - selfdrive/debug/measure_torque_time_to_max.py | 1 - selfdrive/debug/test_fw_query_on_routes.py | 1 - selfdrive/locationd/calibrationd.py | 2 +- selfdrive/locationd/paramsd.py | 4 +- system/qcomgpsd/nmeaport.py | 4 +- system/ui/lib/application.py | 3 +- system/ui/widgets/__init__.py | 4 +- system/ui/widgets/network.py | 4 +- tinygrad_repo | 2 +- tools/jotpluggler/pluggle.py | 2 +- tools/tuning/measure_steering_accuracy.py | 1 - uv.lock | 1660 ++++++++--------- 21 files changed, 922 insertions(+), 963 deletions(-) diff --git a/common/pid.py b/common/pid.py index e3fa8afdf40..b3d64d6fcdc 100644 --- a/common/pid.py +++ b/common/pid.py @@ -3,15 +3,9 @@ class PIDController: def __init__(self, k_p, k_i, k_d=0., pos_limit=1e308, neg_limit=-1e308, rate=100): - self._k_p = k_p - self._k_i = k_i - self._k_d = k_d - if isinstance(self._k_p, Number): - self._k_p = [[0], [self._k_p]] - if isinstance(self._k_i, Number): - self._k_i = [[0], [self._k_i]] - if isinstance(self._k_d, Number): - self._k_d = [[0], [self._k_d]] + self._k_p: list[list[float]] = [[0], [k_p]] if isinstance(k_p, Number) else k_p + self._k_i: list[list[float]] = [[0], [k_i]] if isinstance(k_i, Number) else k_i + self._k_d: list[list[float]] = [[0], [k_d]] if isinstance(k_d, Number) else k_d self.set_limits(pos_limit, neg_limit) diff --git a/docs/CARS.md b/docs/CARS.md index e0d61cd4157..08c06b2303d 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,22 +4,22 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 325 Supported Cars +# 326 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Acura|MDX 2025|All except Type S|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|MDX 2025-26|All except Type S|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| @@ -31,33 +31,33 @@ A supported vehicle is one that just works when you install a comma device. All |Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| -|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Focus 2018[2](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Focus Hybrid 2018[2](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Genesis|G70 2019-21|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Genesis|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -91,7 +91,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|15 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|CR-V 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|CR-V Hybrid 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|CR-V Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|CR-V Hybrid 2023-26|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Fit 2018-20|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Freed 2020|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -201,6 +201,7 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|LC 2024-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|LS 2018|All except Lexus Safety System+ A|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX Hybrid 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -216,19 +217,19 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Nissan[5](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[5](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[5](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[5](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 USB-C coupler
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Subaru|Ascent 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| @@ -239,19 +240,19 @@ A supported vehicle is one that just works when you install a comma device. All |Subaru|Outback 2020-22|All[6](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|XV 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|XV 2020-21|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Škoda|Fabia 2022-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Škoda|Kamiq 2021-23[11,13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Škoda|Karoq 2019-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Kodiaq 2017-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia 2015-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia RS 2016[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia Scout 2017-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Scala 2020-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Škoda|Superb 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[9](#footnotes)|Model 3 (with HW3) 2019-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[9](#footnotes)|Model 3 (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[9](#footnotes)|Model Y (with HW3) 2020-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Tesla[9](#footnotes)|Model Y (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 USB-C coupler
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Fabia 2022-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Škoda|Kamiq 2021-23[11,13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Škoda|Karoq 2019-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Kodiaq 2017-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia 2015-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia RS 2016[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Octavia Scout 2017-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Scala 2020-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Škoda|Superb 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model 3 (with HW3) 2019-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model 3 (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model Y (with HW3) 2020-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Tesla[9](#footnotes)|Model Y (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Avalon 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -297,42 +298,42 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Jetta 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat 2015-22[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 USB-C coupler
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat 2015-22[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| +|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| ### Footnotes 1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `nightly-dev`.
diff --git a/panda b/panda index e42367df97f..3dd38b76b48 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e42367df97f1a5ea4ddb152566022c3ae4672e58 +Subproject commit 3dd38b76b48903efb4705f55752e9719ba2f5564 diff --git a/pyproject.toml b/pyproject.toml index c21ba734dd8..9a70f69d268 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -273,6 +273,6 @@ invalid-argument-type = "ignore" call-non-callable = "ignore" # Ignore unsupported-operator - false positives from dynamic types unsupported-operator = "ignore" -# Ignore non-subscriptable - false positives from dynamic types -non-subscriptable = "ignore" +# Ignore not-subscriptable - false positives from dynamic types +not-subscriptable = "ignore" # not-iterable errors are now fixed diff --git a/selfdrive/car/car_specific.py b/selfdrive/car/car_specific.py index c93d7ac4a24..86494afc7a0 100644 --- a/selfdrive/car/car_specific.py +++ b/selfdrive/car/car_specific.py @@ -87,7 +87,7 @@ def update(self, CS: car.CarState, CS_prev: car.CarState, CC: car.CarControl): events.add(EventName.speedTooLow) # TODO: this needs to be implemented generically in carState struct - # if CC.eps_timer_soft_disable_alert: # type: ignore[attr-defined] + # if CC.eps_timer_soft_disable_alert: # events.add(EventName.steerTimeLimit) return events diff --git a/selfdrive/debug/car/hyundai_enable_radar_points.py b/selfdrive/debug/car/hyundai_enable_radar_points.py index 93f5949eac2..df150a5224d 100755 --- a/selfdrive/debug/car/hyundai_enable_radar_points.py +++ b/selfdrive/debug/car/hyundai_enable_radar_points.py @@ -101,11 +101,11 @@ class ConfigValues(NamedTuple): uds_client = UdsClient(panda, 0x7D0, bus=args.bus) print("\n[START DIAGNOSTIC SESSION]") - session_type : SESSION_TYPE = 0x07 # type: ignore + session_type : SESSION_TYPE = 0x07 uds_client.diagnostic_session_control(session_type) print("[HARDWARE/SOFTWARE VERSION]") - fw_version_data_id : DATA_IDENTIFIER_TYPE = 0xf100 # type: ignore + fw_version_data_id : DATA_IDENTIFIER_TYPE = 0xf100 fw_version = uds_client.read_data_by_identifier(fw_version_data_id) print(fw_version) if fw_version not in SUPPORTED_FW_VERSIONS.keys(): @@ -113,7 +113,7 @@ class ConfigValues(NamedTuple): sys.exit(1) print("[GET CONFIGURATION]") - config_data_id : DATA_IDENTIFIER_TYPE = 0x0142 # type: ignore + config_data_id : DATA_IDENTIFIER_TYPE = 0x0142 current_config = uds_client.read_data_by_identifier(config_data_id) config_values = SUPPORTED_FW_VERSIONS[fw_version] new_config = config_values.default_config if args.default else config_values.tracks_enabled diff --git a/selfdrive/debug/car/vw_mqb_config.py b/selfdrive/debug/car/vw_mqb_config.py index 3c55642e40b..13ee7786d9b 100755 --- a/selfdrive/debug/car/vw_mqb_config.py +++ b/selfdrive/debug/car/vw_mqb_config.py @@ -55,7 +55,7 @@ class ACCESS_TYPE_LEVEL_1(IntEnum): sw_ver = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER).decode("utf-8") component = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.SYSTEM_NAME_OR_ENGINE_TYPE).decode("utf-8") odx_file = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.ODX_FILE).decode("utf-8").rstrip('\x00') - current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING) # type: ignore + current_coding = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING) coding_text = current_coding.hex() print("\nEPS diagnostic data\n") @@ -126,9 +126,9 @@ class ACCESS_TYPE_LEVEL_1(IntEnum): new_coding = current_coding[0:coding_byte] + new_byte.to_bytes(1, "little") + current_coding[coding_byte+1:] try: - seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED) # type: ignore + seed = uds_client.security_access(ACCESS_TYPE_LEVEL_1.REQUEST_SEED) key = struct.unpack("!I", seed)[0] + 28183 # yeah, it's like that - uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) # type: ignore + uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) except (NegativeResponseError, MessageTimeoutError): print("Security access failed!") print("Open the hood and retry (disables the \"diagnostic firewall\" on newer vehicles)") @@ -148,7 +148,7 @@ class ACCESS_TYPE_LEVEL_1(IntEnum): uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.PROGRAMMING_DATE, prog_date) tester_num = uds_client.read_data_by_identifier(DATA_IDENTIFIER_TYPE.CALIBRATION_REPAIR_SHOP_CODE_OR_CALIBRATION_EQUIPMENT_SERIAL_NUMBER) uds_client.write_data_by_identifier(DATA_IDENTIFIER_TYPE.REPAIR_SHOP_CODE_OR_TESTER_SERIAL_NUMBER, tester_num) - uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding) # type: ignore + uds_client.write_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING, new_coding) except (NegativeResponseError, MessageTimeoutError): print("Writing new configuration failed!") print("Make sure the comma processes are stopped: tmux kill-session -t comma") @@ -156,7 +156,7 @@ class ACCESS_TYPE_LEVEL_1(IntEnum): try: # Read back result just to make 100% sure everything worked - current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex() # type: ignore + current_coding_text = uds_client.read_data_by_identifier(VOLKSWAGEN_DATA_IDENTIFIER_TYPE.CODING).hex() print(f" New coding: {current_coding_text}") except (NegativeResponseError, MessageTimeoutError): print("Reading back updated coding failed!") diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index 397e9f35f52..089685103f7 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore ''' System tools like top/htop can only show current cpu usage values, so I write this script to do statistics jobs. Features: diff --git a/selfdrive/debug/fuzz_fw_fingerprint.py b/selfdrive/debug/fuzz_fw_fingerprint.py index fa99e6bfbe8..b4276c0c1af 100755 --- a/selfdrive/debug/fuzz_fw_fingerprint.py +++ b/selfdrive/debug/fuzz_fw_fingerprint.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore import random from collections import defaultdict diff --git a/selfdrive/debug/measure_torque_time_to_max.py b/selfdrive/debug/measure_torque_time_to_max.py index 7052dccf7de..e99aeae464b 100755 --- a/selfdrive/debug/measure_torque_time_to_max.py +++ b/selfdrive/debug/measure_torque_time_to_max.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore import os import argparse diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 1216b7299f0..ab539e4feba 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore from collections import defaultdict import argparse diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 3843cf37f30..036f5822d25 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -47,7 +47,7 @@ def is_calibration_valid(rpy: np.ndarray) -> bool: - return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1]) # type: ignore + return (PITCH_LIMITS[0] < rpy[1] < PITCH_LIMITS[1]) and (YAW_LIMITS[0] < rpy[2] < YAW_LIMITS[1]) def sanity_clip(rpy: np.ndarray) -> np.ndarray: diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index b4084fe5bc9..fd03d3d0936 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -127,8 +127,8 @@ def handle_log(self, t: float, which: str, msg: capnp._DynamicStructReader): if not self.active: # Reset time when stopped so uncertainty doesn't grow - self.kf.filter.set_filter_time(t) # type: ignore - self.kf.filter.reset_rewind() # type: ignore + self.kf.filter.set_filter_time(t) + self.kf.filter.reset_rewind() def get_msg(self, valid: bool, debug: bool = False) -> capnp._DynamicStructBuilder: x = self.kf.x diff --git a/system/qcomgpsd/nmeaport.py b/system/qcomgpsd/nmeaport.py index 8b9ab510864..10b8516ed09 100644 --- a/system/qcomgpsd/nmeaport.py +++ b/system/qcomgpsd/nmeaport.py @@ -107,11 +107,11 @@ def process_nmea_port_messages(device:str="/dev/ttyUSB1") -> NoReturn: match fields[0]: case "$GNCLK": # fields at end are reserved (not used) - gnss_clock = GnssClockNmeaPort(*fields[1:10]) # type: ignore[arg-type] + gnss_clock = GnssClockNmeaPort(*fields[1:10]) print(gnss_clock) case "$GNMEAS": # fields at end are reserved (not used) - gnss_meas = GnssMeasNmeaPort(*fields[1:14]) # type: ignore[arg-type] + gnss_meas = GnssMeasNmeaPort(*fields[1:14]) print(gnss_meas) except Exception as e: print(e) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 04cd37af3ec..151f22ac126 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -184,7 +184,8 @@ def _handle_mouse_event(self): time.monotonic(), ) # Only add changes - if self._prev_mouse_event[slot] is None or ev[:-1] != self._prev_mouse_event[slot][:-1]: + prev = self._prev_mouse_event[slot] + if prev is None or ev[:-1] != prev[:-1]: with self._lock: self._events.append(ev) self._prev_mouse_event[slot] = ev diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 4f3c977d2e3..42d9a910aa2 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -12,7 +12,7 @@ except ImportError: class Device: awake = True - device = Device() # type: ignore + device = Device() class DialogResult(IntEnum): @@ -299,7 +299,7 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: # block horizontal swiping if now swiping away if self._can_swipe_away: - if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: # type: ignore + if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: self._swiping_away = True elif mouse_event.left_released: diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index f41a04c2491..8f5168958f0 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -22,8 +22,8 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeType except Exception: Params = None - ui_state = None # type: ignore - PrimeType = None # type: ignore + ui_state = None + PrimeType = None NM_DEVICE_STATE_NEED_AUTH = 60 MIN_PASSWORD_LENGTH = 8 diff --git a/tinygrad_repo b/tinygrad_repo index f5090192c84..7cb7abeeb02 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit f5090192c84760be1227f7e3c4f99ad0603117ae +Subproject commit 7cb7abeeb02c681a463f252179354db4bb5e3809 diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 879b677514b..6fb8d5049bd 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -7,7 +7,7 @@ import multiprocessing import uuid import signal -import yaml # type: ignore +import yaml from openpilot.common.swaglog import cloudlog from openpilot.common.basedir import BASEDIR from openpilot.tools.jotpluggler.data import DataManager diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index 7e4e975742f..e4aef0ba15c 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# type: ignore import os import time diff --git a/uv.lock b/uv.lock index 73c8fe7c9ce..674b18b7eaf 100644 --- a/uv.lock +++ b/uv.lock @@ -2,12 +2,8 @@ version = 1 revision = 3 requires-python = ">=3.11, <3.13" resolution-markers = [ - "python_full_version >= '3.12' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", - "python_full_version < '3.12' and sys_platform == 'darwin'", - "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'linux')", + "python_full_version >= '3.12'", + "python_full_version < '3.12'", ] [[package]] @@ -21,7 +17,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.13.2" +version = "3.13.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -32,42 +28,42 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1c/ce/3b83ebba6b3207a7135e5fcaba49706f8a4b6008153b4e30540c982fae26/aiohttp-3.13.2.tar.gz", hash = "sha256:40176a52c186aefef6eb3cad2cdd30cd06e3afbe88fe8ab2af9c0b90f228daca", size = 7837994, upload-time = "2025-10-28T20:59:39.937Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/74/b321e7d7ca762638cdf8cdeceb39755d9c745aff7a64c8789be96ddf6e96/aiohttp-3.13.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4647d02df098f6434bafd7f32ad14942f05a9caa06c7016fdcc816f343997dd0", size = 743409, upload-time = "2025-10-28T20:56:00.354Z" }, - { url = "https://files.pythonhosted.org/packages/99/3d/91524b905ec473beaf35158d17f82ef5a38033e5809fe8742e3657cdbb97/aiohttp-3.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e3403f24bcb9c3b29113611c3c16a2a447c3953ecf86b79775e7be06f7ae7ccb", size = 497006, upload-time = "2025-10-28T20:56:01.85Z" }, - { url = "https://files.pythonhosted.org/packages/eb/d3/7f68bc02a67716fe80f063e19adbd80a642e30682ce74071269e17d2dba1/aiohttp-3.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:43dff14e35aba17e3d6d5ba628858fb8cb51e30f44724a2d2f0c75be492c55e9", size = 493195, upload-time = "2025-10-28T20:56:03.314Z" }, - { url = "https://files.pythonhosted.org/packages/98/31/913f774a4708775433b7375c4f867d58ba58ead833af96c8af3621a0d243/aiohttp-3.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e2a9ea08e8c58bb17655630198833109227dea914cd20be660f52215f6de5613", size = 1747759, upload-time = "2025-10-28T20:56:04.904Z" }, - { url = "https://files.pythonhosted.org/packages/e8/63/04efe156f4326f31c7c4a97144f82132c3bb21859b7bb84748d452ccc17c/aiohttp-3.13.2-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53b07472f235eb80e826ad038c9d106c2f653584753f3ddab907c83f49eedead", size = 1704456, upload-time = "2025-10-28T20:56:06.986Z" }, - { url = "https://files.pythonhosted.org/packages/8e/02/4e16154d8e0a9cf4ae76f692941fd52543bbb148f02f098ca73cab9b1c1b/aiohttp-3.13.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e736c93e9c274fce6419af4aac199984d866e55f8a4cec9114671d0ea9688780", size = 1807572, upload-time = "2025-10-28T20:56:08.558Z" }, - { url = "https://files.pythonhosted.org/packages/34/58/b0583defb38689e7f06798f0285b1ffb3a6fb371f38363ce5fd772112724/aiohttp-3.13.2-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ff5e771f5dcbc81c64898c597a434f7682f2259e0cd666932a913d53d1341d1a", size = 1895954, upload-time = "2025-10-28T20:56:10.545Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f3/083907ee3437425b4e376aa58b2c915eb1a33703ec0dc30040f7ae3368c6/aiohttp-3.13.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3b6fb0c207cc661fa0bf8c66d8d9b657331ccc814f4719468af61034b478592", size = 1747092, upload-time = "2025-10-28T20:56:12.118Z" }, - { url = "https://files.pythonhosted.org/packages/ac/61/98a47319b4e425cc134e05e5f3fc512bf9a04bf65aafd9fdcda5d57ec693/aiohttp-3.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:97a0895a8e840ab3520e2288db7cace3a1981300d48babeb50e7425609e2e0ab", size = 1606815, upload-time = "2025-10-28T20:56:14.191Z" }, - { url = "https://files.pythonhosted.org/packages/97/4b/e78b854d82f66bb974189135d31fce265dee0f5344f64dd0d345158a5973/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9e8f8afb552297aca127c90cb840e9a1d4bfd6a10d7d8f2d9176e1acc69bad30", size = 1723789, upload-time = "2025-10-28T20:56:16.101Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fc/9d2ccc794fc9b9acd1379d625c3a8c64a45508b5091c546dea273a41929e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:ed2f9c7216e53c3df02264f25d824b079cc5914f9e2deba94155190ef648ee40", size = 1718104, upload-time = "2025-10-28T20:56:17.655Z" }, - { url = "https://files.pythonhosted.org/packages/66/65/34564b8765ea5c7d79d23c9113135d1dd3609173da13084830f1507d56cf/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:99c5280a329d5fa18ef30fd10c793a190d996567667908bef8a7f81f8202b948", size = 1785584, upload-time = "2025-10-28T20:56:19.238Z" }, - { url = "https://files.pythonhosted.org/packages/30/be/f6a7a426e02fc82781afd62016417b3948e2207426d90a0e478790d1c8a4/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ca6ffef405fc9c09a746cb5d019c1672cd7f402542e379afc66b370833170cf", size = 1595126, upload-time = "2025-10-28T20:56:20.836Z" }, - { url = "https://files.pythonhosted.org/packages/e5/c7/8e22d5d28f94f67d2af496f14a83b3c155d915d1fe53d94b66d425ec5b42/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:47f438b1a28e926c37632bff3c44df7d27c9b57aaf4e34b1def3c07111fdb782", size = 1800665, upload-time = "2025-10-28T20:56:22.922Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/91133c8b68b1da9fc16555706aa7276fdf781ae2bb0876c838dd86b8116e/aiohttp-3.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9acda8604a57bb60544e4646a4615c1866ee6c04a8edef9b8ee6fd1d8fa2ddc8", size = 1739532, upload-time = "2025-10-28T20:56:25.924Z" }, - { url = "https://files.pythonhosted.org/packages/17/6b/3747644d26a998774b21a616016620293ddefa4d63af6286f389aedac844/aiohttp-3.13.2-cp311-cp311-win32.whl", hash = "sha256:868e195e39b24aaa930b063c08bb0c17924899c16c672a28a65afded9c46c6ec", size = 431876, upload-time = "2025-10-28T20:56:27.524Z" }, - { url = "https://files.pythonhosted.org/packages/c3/63/688462108c1a00eb9f05765331c107f95ae86f6b197b865d29e930b7e462/aiohttp-3.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:7fd19df530c292542636c2a9a85854fab93474396a52f1695e799186bbd7f24c", size = 456205, upload-time = "2025-10-28T20:56:29.062Z" }, - { url = "https://files.pythonhosted.org/packages/29/9b/01f00e9856d0a73260e86dd8ed0c2234a466c5c1712ce1c281548df39777/aiohttp-3.13.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b1e56bab2e12b2b9ed300218c351ee2a3d8c8fdab5b1ec6193e11a817767e47b", size = 737623, upload-time = "2025-10-28T20:56:30.797Z" }, - { url = "https://files.pythonhosted.org/packages/5a/1b/4be39c445e2b2bd0aab4ba736deb649fabf14f6757f405f0c9685019b9e9/aiohttp-3.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:364e25edaabd3d37b1db1f0cbcee8c73c9a3727bfa262b83e5e4cf3489a2a9dc", size = 492664, upload-time = "2025-10-28T20:56:32.708Z" }, - { url = "https://files.pythonhosted.org/packages/28/66/d35dcfea8050e131cdd731dff36434390479b4045a8d0b9d7111b0a968f1/aiohttp-3.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c5c94825f744694c4b8db20b71dba9a257cd2ba8e010a803042123f3a25d50d7", size = 491808, upload-time = "2025-10-28T20:56:34.57Z" }, - { url = "https://files.pythonhosted.org/packages/00/29/8e4609b93e10a853b65f8291e64985de66d4f5848c5637cddc70e98f01f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ba2715d842ffa787be87cbfce150d5e88c87a98e0b62e0f5aa489169a393dbbb", size = 1738863, upload-time = "2025-10-28T20:56:36.377Z" }, - { url = "https://files.pythonhosted.org/packages/9d/fa/4ebdf4adcc0def75ced1a0d2d227577cd7b1b85beb7edad85fcc87693c75/aiohttp-3.13.2-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:585542825c4bc662221fb257889e011a5aa00f1ae4d75d1d246a5225289183e3", size = 1700586, upload-time = "2025-10-28T20:56:38.034Z" }, - { url = "https://files.pythonhosted.org/packages/da/04/73f5f02ff348a3558763ff6abe99c223381b0bace05cd4530a0258e52597/aiohttp-3.13.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:39d02cb6025fe1aabca329c5632f48c9532a3dabccd859e7e2f110668972331f", size = 1768625, upload-time = "2025-10-28T20:56:39.75Z" }, - { url = "https://files.pythonhosted.org/packages/f8/49/a825b79ffec124317265ca7d2344a86bcffeb960743487cb11988ffb3494/aiohttp-3.13.2-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e67446b19e014d37342f7195f592a2a948141d15a312fe0e700c2fd2f03124f6", size = 1867281, upload-time = "2025-10-28T20:56:41.471Z" }, - { url = "https://files.pythonhosted.org/packages/b9/48/adf56e05f81eac31edcfae45c90928f4ad50ef2e3ea72cb8376162a368f8/aiohttp-3.13.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4356474ad6333e41ccefd39eae869ba15a6c5299c9c01dfdcfdd5c107be4363e", size = 1752431, upload-time = "2025-10-28T20:56:43.162Z" }, - { url = "https://files.pythonhosted.org/packages/30/ab/593855356eead019a74e862f21523db09c27f12fd24af72dbc3555b9bfd9/aiohttp-3.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeacf451c99b4525f700f078becff32c32ec327b10dcf31306a8a52d78166de7", size = 1562846, upload-time = "2025-10-28T20:56:44.85Z" }, - { url = "https://files.pythonhosted.org/packages/39/0f/9f3d32271aa8dc35036e9668e31870a9d3b9542dd6b3e2c8a30931cb27ae/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8a9b889aeabd7a4e9af0b7f4ab5ad94d42e7ff679aaec6d0db21e3b639ad58d", size = 1699606, upload-time = "2025-10-28T20:56:46.519Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3c/52d2658c5699b6ef7692a3f7128b2d2d4d9775f2a68093f74bca06cf01e1/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:fa89cb11bc71a63b69568d5b8a25c3ca25b6d54c15f907ca1c130d72f320b76b", size = 1720663, upload-time = "2025-10-28T20:56:48.528Z" }, - { url = "https://files.pythonhosted.org/packages/9b/d4/8f8f3ff1fb7fb9e3f04fcad4e89d8a1cd8fc7d05de67e3de5b15b33008ff/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8aa7c807df234f693fed0ecd507192fc97692e61fee5702cdc11155d2e5cadc8", size = 1737939, upload-time = "2025-10-28T20:56:50.77Z" }, - { url = "https://files.pythonhosted.org/packages/03/d3/ddd348f8a27a634daae39a1b8e291ff19c77867af438af844bf8b7e3231b/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9eb3e33fdbe43f88c3c75fa608c25e7c47bbd80f48d012763cb67c47f39a7e16", size = 1555132, upload-time = "2025-10-28T20:56:52.568Z" }, - { url = "https://files.pythonhosted.org/packages/39/b8/46790692dc46218406f94374903ba47552f2f9f90dad554eed61bfb7b64c/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9434bc0d80076138ea986833156c5a48c9c7a8abb0c96039ddbb4afc93184169", size = 1764802, upload-time = "2025-10-28T20:56:54.292Z" }, - { url = "https://files.pythonhosted.org/packages/ba/e4/19ce547b58ab2a385e5f0b8aa3db38674785085abcf79b6e0edd1632b12f/aiohttp-3.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff15c147b2ad66da1f2cbb0622313f2242d8e6e8f9b79b5206c84523a4473248", size = 1719512, upload-time = "2025-10-28T20:56:56.428Z" }, - { url = "https://files.pythonhosted.org/packages/70/30/6355a737fed29dcb6dfdd48682d5790cb5eab050f7b4e01f49b121d3acad/aiohttp-3.13.2-cp312-cp312-win32.whl", hash = "sha256:27e569eb9d9e95dbd55c0fc3ec3a9335defbf1d8bc1d20171a49f3c4c607b93e", size = 426690, upload-time = "2025-10-28T20:56:58.736Z" }, - { url = "https://files.pythonhosted.org/packages/0a/0d/b10ac09069973d112de6ef980c1f6bb31cb7dcd0bc363acbdad58f927873/aiohttp-3.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:8709a0f05d59a71f33fd05c17fc11fcb8c30140506e13c2f5e8ee1b8964e1b45", size = 453465, upload-time = "2025-10-28T20:57:00.795Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, + { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, + { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, + { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, + { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, + { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, + { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, + { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, + { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, + { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, + { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, + { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, + { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, + { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, + { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, + { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, + { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, + { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, + { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, + { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, + { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, + { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, + { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, ] [[package]] @@ -152,15 +148,15 @@ wheels = [ [[package]] name = "azure-core" -version = "1.37.0" +version = "1.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ef/83/41c9371c8298999c67b007e308a0a3c4d6a59c6908fa9c62101f031f886f/azure_core-1.37.0.tar.gz", hash = "sha256:7064f2c11e4b97f340e8e8c6d923b822978be3016e46b7bc4aa4b337cfb48aee", size = 357620, upload-time = "2025-12-11T20:05:13.518Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/1b/e503e08e755ea94e7d3419c9242315f888fc664211c90d032e40479022bf/azure_core-1.38.0.tar.gz", hash = "sha256:8194d2682245a3e4e3151a667c686464c3786fed7918b394d035bdcd61bb5993", size = 363033, upload-time = "2026-01-12T17:03:05.535Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/34/a9914e676971a13d6cc671b1ed172f9804b50a3a80a143ff196e52f4c7ee/azure_core-1.37.0-py3-none-any.whl", hash = "sha256:b3abe2c59e7d6bb18b38c275a5029ff80f98990e7c90a5e646249a56630fcc19", size = 214006, upload-time = "2025-12-11T20:05:14.96Z" }, + { url = "https://files.pythonhosted.org/packages/fc/d8/b8fcba9464f02b121f39de2db2bf57f0b216fe11d014513d666e8634380d/azure_core-1.38.0-py3-none-any.whl", hash = "sha256:ab0c9b2cd71fecb1842d52c965c95285d3cfb38902f6766e4a471f1cd8905335", size = 217825, upload-time = "2026-01-12T17:03:07.291Z" }, ] [[package]] @@ -181,7 +177,7 @@ wheels = [ [[package]] name = "azure-storage-blob" -version = "12.27.1" +version = "12.28.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "azure-core" }, @@ -189,9 +185,9 @@ dependencies = [ { name = "isodate" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/7c/2fd872e11a88163f208b9c92de273bf64bb22d0eef9048cc6284d128a77a/azure_storage_blob-12.27.1.tar.gz", hash = "sha256:a1596cc4daf5dac9be115fcb5db67245eae894cf40e4248243754261f7b674a6", size = 597579, upload-time = "2025-10-29T12:27:16.185Z" } +sdist = { url = "https://files.pythonhosted.org/packages/71/24/072ba8e27b0e2d8fec401e9969b429d4f5fc4c8d4f0f05f4661e11f7234a/azure_storage_blob-12.28.0.tar.gz", hash = "sha256:e7d98ea108258d29aa0efbfd591b2e2075fa1722a2fae8699f0b3c9de11eff41", size = 604225, upload-time = "2026-01-06T23:48:57.282Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/9e/1c90a122ea6180e8c72eb7294adc92531b0e08eb3d2324c2ba70d37f4802/azure_storage_blob-12.27.1-py3-none-any.whl", hash = "sha256:65d1e25a4628b7b6acd20ff7902d8da5b4fde8e46e19c8f6d213a3abc3ece272", size = 428954, upload-time = "2025-10-29T12:27:18.072Z" }, + { url = "https://files.pythonhosted.org/packages/d8/3a/6ef2047a072e54e1142718d433d50e9514c999a58f51abfff7902f3a72f8/azure_storage_blob-12.28.0-py3-none-any.whl", hash = "sha256:00fb1db28bf6a7b7ecaa48e3b1d5c83bfadacc5a678b77826081304bd87d6461", size = 431499, upload-time = "2026-01-06T23:48:58.995Z" }, ] [[package]] @@ -219,11 +215,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.11.12" +version = "2026.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, ] [[package]] @@ -475,28 +471,28 @@ wheels = [ [[package]] name = "cython" -version = "3.2.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/39/e1/c0d92b1258722e1bc62a12e630c33f1f842fdab53fd8cd5de2f75c6449a9/cython-3.2.3.tar.gz", hash = "sha256:f13832412d633376ffc08d751cc18ed0d7d00a398a4065e2871db505258748a6", size = 3276650, upload-time = "2025-12-14T07:50:34.691Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/85/77315c92d29d782bee1b36e30b8d76ad1e731cb7ea0af17e285885f3bb68/cython-3.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c041f7e338cca2422e0924716b04fabeda57636214324fc1941396acce99e7c7", size = 2951618, upload-time = "2025-12-14T07:50:53.883Z" }, - { url = "https://files.pythonhosted.org/packages/cb/dd/a8209e0d424a0207ddb4a3097a97b667027af3cfada762d85f3bed08ccf8/cython-3.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:283262b8f902323ceb6ed3b643f275a2a963e7ab059f0714a467933383cbc56d", size = 3243636, upload-time = "2025-12-14T07:50:56.346Z" }, - { url = "https://files.pythonhosted.org/packages/1f/2d/bc1927fd7174f7928b86cc9b83589d39592b9273c8b1d2295ca0c0071984/cython-3.2.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22a624290c2883387b2c2cfb5224c15bff21432c6a2cf0c23ac8df3dcbd45e96", size = 3378528, upload-time = "2025-12-14T07:50:57.988Z" }, - { url = "https://files.pythonhosted.org/packages/ad/10/5add6a6e1721f9c36b5d5b4f3b75fa7af43196e4f2a474921a7277e31b7a/cython-3.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:26404441f733fd1cfb0dd9c45477f501437e7d51fad05bb402bd2feb4e127aa3", size = 2769341, upload-time = "2025-12-14T07:50:59.581Z" }, - { url = "https://files.pythonhosted.org/packages/b4/14/d16282d17c9eb2f78ca9ccd5801fed22f6c3360f5a55dbcce3c93cc70352/cython-3.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cf210228c15b5c625824d8e31d43b6fea25f9e13c81dac632f2f7d838e0229a5", size = 2968471, upload-time = "2025-12-14T07:51:01.207Z" }, - { url = "https://files.pythonhosted.org/packages/d0/3c/46304a942dac5a636701c55f5b05ec00ad151e6722cd068fe3d0993349bb/cython-3.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f5bf0cebeb4147e172a114437d3fce5a507595d8fdd821be792b1bb25c691514", size = 3223581, upload-time = "2025-12-14T07:51:04.336Z" }, - { url = "https://files.pythonhosted.org/packages/29/ad/15da606d71f40bcf2c405f84ca3d4195cb252f4eaa2f551fe6b2e630ee7c/cython-3.2.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d1f8700ba89c977438744f083890d87187f15709507a5489e0f6d682053b7fa0", size = 3391391, upload-time = "2025-12-14T07:51:05.998Z" }, - { url = "https://files.pythonhosted.org/packages/51/9e/045b35eb678682edc3e2d57112cf5ac3581a9ef274eb220b638279195678/cython-3.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:25732f3981a93407826297f4423206e5e22c3cfccfc74e37bf444453bbdc076f", size = 2756814, upload-time = "2025-12-14T07:51:07.759Z" }, - { url = "https://files.pythonhosted.org/packages/43/49/afe1e3df87a770861cf17ba39f4a91f6d22a2571010fc1890b3708360630/cython-3.2.3-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:74f482da8b605c61b4df6ff716d013f20131949cb2fa59b03e63abd36ef5bac0", size = 2874467, upload-time = "2025-12-14T07:51:31.568Z" }, - { url = "https://files.pythonhosted.org/packages/c7/da/044f725a083e28fb4de5bd33d13ec13f0753734b6ae52d4bc07434610cc8/cython-3.2.3-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0a75a04688875b275a6c875565e672325bae04327dd6ec2fc25aeb5c6cf82fce", size = 3211272, upload-time = "2025-12-14T07:51:33.673Z" }, - { url = "https://files.pythonhosted.org/packages/95/14/af02ba6e2e03279f2ca2956e3024a44faed4c8496bda8170b663dc3ba6e8/cython-3.2.3-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6b01b36c9eb1b68c25bddbeef7379f7bfc37f7c9afc044e71840ffab761a2dd0", size = 2856058, upload-time = "2025-12-14T07:51:36.015Z" }, - { url = "https://files.pythonhosted.org/packages/69/16/d254359396c2f099ab154f89b2b35f5b8b0dd21a8102c2c96a7e00291434/cython-3.2.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3829f99d611412288f44ff543e9d2b5c0c83274998b2a6680bbe5cca3539c1fd", size = 2993276, upload-time = "2025-12-14T07:51:37.863Z" }, - { url = "https://files.pythonhosted.org/packages/51/0e/1a071381923e896f751f8fbff2a01c5dc8860a8b9a90066f6ec8df561dc4/cython-3.2.3-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:c2365a0c79ab9c0fa86d30a4a6ba7e37fc1be9537c48b79b9d63ee7e08bf2fef", size = 2890843, upload-time = "2025-12-14T07:51:40.409Z" }, - { url = "https://files.pythonhosted.org/packages/f4/46/1e93e10766db988e6bb8e5c6f7e2e90b9e62f1ac8dee4c1a6cf1fc170773/cython-3.2.3-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3141734fb15f8b5e9402b9240f8da8336edecae91742b41c85678c31ab68f66d", size = 3225339, upload-time = "2025-12-14T07:51:42.09Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ae/c284b06ae6a9c95d5883bf8744d10466cf0df64cef041a4c80ccf9fd07bd/cython-3.2.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9a24cc653fad3adbd9cbaa638d80df3aa08a1fe27f62eb35850971c70be680df", size = 3114751, upload-time = "2025-12-14T07:51:44.088Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d6/7795a4775c70256217134195f06b07233cf17b00f8905d5b3d782208af64/cython-3.2.3-cp39-abi3-win32.whl", hash = "sha256:b39dff92db70cbd95528f3b81d70e06bd6d3fc9c1dd91321e4d3b999ece3bceb", size = 2435616, upload-time = "2025-12-14T07:51:46.063Z" }, - { url = "https://files.pythonhosted.org/packages/18/9e/2a3edcb858ad74e6274448dccf32150c532bc6e423f112a71f65ff3b5680/cython-3.2.3-cp39-abi3-win_arm64.whl", hash = "sha256:18edc858e6a52de47fe03ffa97ea14dadf450e20069de0a8aef531006c4bbd93", size = 2440952, upload-time = "2025-12-14T07:51:47.943Z" }, - { url = "https://files.pythonhosted.org/packages/e5/41/54fd429ff8147475fc24ca43246f85d78fb4e747c27f227e68f1594648f1/cython-3.2.3-py3-none-any.whl", hash = "sha256:06a1317097f540d3bb6c7b81ed58a0d8b9dbfa97abf39dfd4c22ee87a6c7241e", size = 1255561, upload-time = "2025-12-14T07:50:31.217Z" }, +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/91/85/7574c9cd44b69a27210444b6650f6477f56c75fee1b70d7672d3e4166167/cython-3.2.4.tar.gz", hash = "sha256:84226ecd313b233da27dc2eb3601b4f222b8209c3a7216d8733b031da1dc64e6", size = 3280291, upload-time = "2026-01-04T14:14:14.473Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/cc/8f06145ec3efa121c8b1b67f06a640386ddacd77ee3e574da582a21b14ee/cython-3.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff9af2134c05e3734064808db95b4dd7341a39af06e8945d05ea358e1741aaed", size = 2953769, upload-time = "2026-01-04T14:15:00.361Z" }, + { url = "https://files.pythonhosted.org/packages/55/b0/706cf830eddd831666208af1b3058c2e0758ae157590909c1f634b53bed9/cython-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67922c9de058a0bfb72d2e75222c52d09395614108c68a76d9800f150296ddb3", size = 3243841, upload-time = "2026-01-04T14:15:02.066Z" }, + { url = "https://files.pythonhosted.org/packages/ac/25/58893afd4ef45f79e3d4db82742fa4ff874b936d67a83c92939053920ccd/cython-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b362819d155fff1482575e804e43e3a8825332d32baa15245f4642022664a3f4", size = 3378083, upload-time = "2026-01-04T14:15:04.248Z" }, + { url = "https://files.pythonhosted.org/packages/32/e4/424a004d7c0d8a4050c81846ebbd22272ececfa9a498cb340aa44fccbec2/cython-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a64a112a34ec719b47c01395647e54fb4cf088a511613f9a3a5196694e8e382", size = 2769990, upload-time = "2026-01-04T14:15:06.53Z" }, + { url = "https://files.pythonhosted.org/packages/91/4d/1eb0c7c196a136b1926f4d7f0492a96c6fabd604d77e6cd43b56a3a16d83/cython-3.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64d7f71be3dd6d6d4a4c575bb3a4674ea06d1e1e5e4cd1b9882a2bc40ed3c4c9", size = 2970064, upload-time = "2026-01-04T14:15:08.567Z" }, + { url = "https://files.pythonhosted.org/packages/03/1c/46e34b08bea19a1cdd1e938a4c123e6299241074642db9d81983cef95e9f/cython-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:869487ea41d004f8b92171f42271fbfadb1ec03bede3158705d16cd570d6b891", size = 3226757, upload-time = "2026-01-04T14:15:10.812Z" }, + { url = "https://files.pythonhosted.org/packages/12/33/3298a44d201c45bcf0d769659725ae70e9c6c42adf8032f6d89c8241098d/cython-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55b6c44cd30821f0b25220ceba6fe636ede48981d2a41b9bbfe3c7902ce44ea7", size = 3388969, upload-time = "2026-01-04T14:15:12.45Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f3/4275cd3ea0a4cf4606f9b92e7f8766478192010b95a7f516d1b7cf22cb10/cython-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:767b143704bdd08a563153448955935844e53b852e54afdc552b43902ed1e235", size = 2756457, upload-time = "2026-01-04T14:15:14.67Z" }, + { url = "https://files.pythonhosted.org/packages/0a/8b/fd393f0923c82be4ec0db712fffb2ff0a7a131707b842c99bf24b549274d/cython-3.2.4-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:36bf3f5eb56d5281aafabecbaa6ed288bc11db87547bba4e1e52943ae6961ccf", size = 2875622, upload-time = "2026-01-04T14:15:39.749Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/48530d9b9d64ec11dbe0dd3178a5fe1e0b27977c1054ecffb82be81e9b6a/cython-3.2.4-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6d5267f22b6451eb1e2e1b88f6f78a2c9c8733a6ddefd4520d3968d26b824581", size = 3210669, upload-time = "2026-01-04T14:15:41.911Z" }, + { url = "https://files.pythonhosted.org/packages/5e/91/4865fbfef1f6bb4f21d79c46104a53d1a3fa4348286237e15eafb26e0828/cython-3.2.4-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3b6e58f73a69230218d5381817850ce6d0da5bb7e87eb7d528c7027cbba40b06", size = 2856835, upload-time = "2026-01-04T14:15:43.815Z" }, + { url = "https://files.pythonhosted.org/packages/fa/39/60317957dbef179572398253f29d28f75f94ab82d6d39ea3237fb6c89268/cython-3.2.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e71efb20048358a6b8ec604a0532961c50c067b5e63e345e2e359fff72feaee8", size = 2994408, upload-time = "2026-01-04T14:15:45.422Z" }, + { url = "https://files.pythonhosted.org/packages/8d/30/7c24d9292650db4abebce98abc9b49c820d40fa7c87921c0a84c32f4efe7/cython-3.2.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:28b1e363b024c4b8dcf52ff68125e635cb9cb4b0ba997d628f25e32543a71103", size = 2891478, upload-time = "2026-01-04T14:15:47.394Z" }, + { url = "https://files.pythonhosted.org/packages/86/70/03dc3c962cde9da37a93cca8360e576f904d5f9beecfc9d70b1f820d2e5f/cython-3.2.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:31a90b4a2c47bb6d56baeb926948348ec968e932c1ae2c53239164e3e8880ccf", size = 3225663, upload-time = "2026-01-04T14:15:49.446Z" }, + { url = "https://files.pythonhosted.org/packages/b1/97/10b50c38313c37b1300325e2e53f48ea9a2c078a85c0c9572057135e31d5/cython-3.2.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e65e4773021f8dc8532010b4fbebe782c77f9a0817e93886e518c93bd6a44e9d", size = 3115628, upload-time = "2026-01-04T14:15:51.323Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b1/d6a353c9b147848122a0db370863601fdf56de2d983b5c4a6a11e6ee3cd7/cython-3.2.4-cp39-abi3-win32.whl", hash = "sha256:2b1f12c0e4798293d2754e73cd6f35fa5bbdf072bdc14bc6fc442c059ef2d290", size = 2437463, upload-time = "2026-01-04T14:15:53.787Z" }, + { url = "https://files.pythonhosted.org/packages/2d/d8/319a1263b9c33b71343adfd407e5daffd453daef47ebc7b642820a8b68ed/cython-3.2.4-cp39-abi3-win_arm64.whl", hash = "sha256:3b8e62049afef9da931d55de82d8f46c9a147313b69d5ff6af6e9121d545ce7a", size = 2442754, upload-time = "2026-01-04T14:15:55.382Z" }, + { url = "https://files.pythonhosted.org/packages/ff/fa/d3c15189f7c52aaefbaea76fb012119b04b9013f4bf446cb4eb4c26c4e6b/cython-3.2.4-py3-none-any.whl", hash = "sha256:732fc93bc33ae4b14f6afaca663b916c2fdd5dcbfad7114e17fb2434eeaea45c", size = 1257078, upload-time = "2026-01-04T14:14:12.373Z" }, ] [[package]] @@ -538,7 +534,7 @@ version = "0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions", marker = "sys_platform != 'darwin'" }, + { name = "typing-extensions" }, ] wheels = [ { url = "https://files.pythonhosted.org/packages/2f/3a/46ca34abf0725a754bc44ef474ad34aedcc3ea23b052d97b18b76715a6a9/EWMHlib-0.2-py3-none-any.whl", hash = "sha256:f5b07d8cfd4c7734462ee744c32d490f2f3233fa7ab354240069344208d2f6f5", size = 46657, upload-time = "2024-04-17T08:15:56.338Z" }, @@ -564,11 +560,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.1" +version = "3.20.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476, upload-time = "2025-12-15T23:54:28.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/7f/a1a97644e39e7316d850784c642093c99df1290a460df4ede27659056834/filelock-3.20.1-py3-none-any.whl", hash = "sha256:15d9e9a67306188a44baa72f569d2bfd803076269365fdea0934385da4dc361a", size = 16666, upload-time = "2025-12-15T23:54:26.874Z" }, + { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, ] [[package]] @@ -683,10 +679,10 @@ name = "gymnasium" version = "1.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cloudpickle", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "farama-notifications", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "cloudpickle" }, + { name = "farama-notifications" }, + { name = "numpy" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/76/59/653a9417d98ed3e29ef9734ba52c3495f6c6823b8d5c0c75369f25111708/gymnasium-1.2.3.tar.gz", hash = "sha256:2b2cb5b5fbbbdf3afb9f38ca952cc48aa6aa3e26561400d940747fda3ad42509", size = 829230, upload-time = "2025-12-18T16:51:10.234Z" } wheels = [ @@ -1006,22 +1002,22 @@ name = "metadrive-simulator" version = "0.4.2.4" source = { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" } dependencies = [ - { name = "filelock", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "gymnasium", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "lxml", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "matplotlib", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "opencv-python-headless", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "panda3d-gltf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "pillow", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "progressbar", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "psutil", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "pygments", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "requests", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "shapely", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "tqdm", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "yapf", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "filelock" }, + { name = "gymnasium" }, + { name = "lxml" }, + { name = "matplotlib" }, + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "panda3d" }, + { name = "panda3d-gltf" }, + { name = "pillow" }, + { name = "progressbar" }, + { name = "psutil" }, + { name = "pygments" }, + { name = "requests" }, + { name = "shapely" }, + { name = "tqdm" }, + { name = "yapf" }, ] wheels = [ { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, @@ -1216,44 +1212,44 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.0" +version = "2.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a4/7a/6a3d14e205d292b738db449d0de649b373a59edb0d0b4493821d0a3e8718/numpy-2.4.0.tar.gz", hash = "sha256:6e504f7b16118198f138ef31ba24d985b124c2c469fe8467007cf30fd992f934", size = 20685720, upload-time = "2025-12-20T16:18:19.023Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/26/7e/7bae7cbcc2f8132271967aa03e03954fc1e48aa1f3bf32b29ca95fbef352/numpy-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:316b2f2584682318539f0bcaca5a496ce9ca78c88066579ebd11fd06f8e4741e", size = 16940166, upload-time = "2025-12-20T16:15:43.434Z" }, - { url = "https://files.pythonhosted.org/packages/0f/27/6c13f5b46776d6246ec884ac5817452672156a506d08a1f2abb39961930a/numpy-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2718c1de8504121714234b6f8241d0019450353276c88b9453c9c3d92e101db", size = 12641781, upload-time = "2025-12-20T16:15:45.701Z" }, - { url = "https://files.pythonhosted.org/packages/14/1c/83b4998d4860d15283241d9e5215f28b40ac31f497c04b12fa7f428ff370/numpy-2.4.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:21555da4ec4a0c942520ead42c3b0dc9477441e085c42b0fbdd6a084869a6f6b", size = 5470247, upload-time = "2025-12-20T16:15:47.943Z" }, - { url = "https://files.pythonhosted.org/packages/54/08/cbce72c835d937795571b0464b52069f869c9e78b0c076d416c5269d2718/numpy-2.4.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:413aa561266a4be2d06cd2b9665e89d9f54c543f418773076a76adcf2af08bc7", size = 6799807, upload-time = "2025-12-20T16:15:49.795Z" }, - { url = "https://files.pythonhosted.org/packages/ff/be/2e647961cd8c980591d75cdcd9e8f647d69fbe05e2a25613dc0a2ea5fb1a/numpy-2.4.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0feafc9e03128074689183031181fac0897ff169692d8492066e949041096548", size = 14701992, upload-time = "2025-12-20T16:15:51.615Z" }, - { url = "https://files.pythonhosted.org/packages/a2/fb/e1652fb8b6fd91ce6ed429143fe2e01ce714711e03e5b762615e7b36172c/numpy-2.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8fdfed3deaf1928fb7667d96e0567cdf58c2b370ea2ee7e586aa383ec2cb346", size = 16646871, upload-time = "2025-12-20T16:15:54.129Z" }, - { url = "https://files.pythonhosted.org/packages/62/23/d841207e63c4322842f7cd042ae981cffe715c73376dcad8235fb31debf1/numpy-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e06a922a469cae9a57100864caf4f8a97a1026513793969f8ba5b63137a35d25", size = 16487190, upload-time = "2025-12-20T16:15:56.147Z" }, - { url = "https://files.pythonhosted.org/packages/bc/a0/6a842c8421ebfdec0a230e65f61e0dabda6edbef443d999d79b87c273965/numpy-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:927ccf5cd17c48f801f4ed43a7e5673a2724bd2171460be3e3894e6e332ef83a", size = 18580762, upload-time = "2025-12-20T16:15:58.524Z" }, - { url = "https://files.pythonhosted.org/packages/0a/d1/c79e0046641186f2134dde05e6181825b911f8bdcef31b19ddd16e232847/numpy-2.4.0-cp311-cp311-win32.whl", hash = "sha256:882567b7ae57c1b1a0250208cc21a7976d8cbcc49d5a322e607e6f09c9e0bd53", size = 6233359, upload-time = "2025-12-20T16:16:00.938Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f0/74965001d231f28184d6305b8cdc1b6fcd4bf23033f6cb039cfe76c9fca7/numpy-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:8b986403023c8f3bf8f487c2e6186afda156174d31c175f747d8934dfddf3479", size = 12601132, upload-time = "2025-12-20T16:16:02.484Z" }, - { url = "https://files.pythonhosted.org/packages/65/32/55408d0f46dfebce38017f5bd931affa7256ad6beac1a92a012e1fbc67a7/numpy-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:3f3096405acc48887458bbf9f6814d43785ac7ba2a57ea6442b581dedbc60ce6", size = 10573977, upload-time = "2025-12-20T16:16:04.77Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ff/f6400ffec95de41c74b8e73df32e3fff1830633193a7b1e409be7fb1bb8c/numpy-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2a8b6bb8369abefb8bd1801b054ad50e02b3275c8614dc6e5b0373c305291037", size = 16653117, upload-time = "2025-12-20T16:16:06.709Z" }, - { url = "https://files.pythonhosted.org/packages/fd/28/6c23e97450035072e8d830a3c411bf1abd1f42c611ff9d29e3d8f55c6252/numpy-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e284ca13d5a8367e43734148622caf0b261b275673823593e3e3634a6490f83", size = 12369711, upload-time = "2025-12-20T16:16:08.758Z" }, - { url = "https://files.pythonhosted.org/packages/bc/af/acbef97b630ab1bb45e6a7d01d1452e4251aa88ce680ac36e56c272120ec/numpy-2.4.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:49ff32b09f5aa0cd30a20c2b39db3e669c845589f2b7fc910365210887e39344", size = 5198355, upload-time = "2025-12-20T16:16:10.902Z" }, - { url = "https://files.pythonhosted.org/packages/c1/c8/4e0d436b66b826f2e53330adaa6311f5cac9871a5b5c31ad773b27f25a74/numpy-2.4.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:36cbfb13c152b1c7c184ddac43765db8ad672567e7bafff2cc755a09917ed2e6", size = 6545298, upload-time = "2025-12-20T16:16:12.607Z" }, - { url = "https://files.pythonhosted.org/packages/ef/27/e1f5d144ab54eac34875e79037011d511ac57b21b220063310cb96c80fbc/numpy-2.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:35ddc8f4914466e6fc954c76527aa91aa763682a4f6d73249ef20b418fe6effb", size = 14398387, upload-time = "2025-12-20T16:16:14.257Z" }, - { url = "https://files.pythonhosted.org/packages/67/64/4cb909dd5ab09a9a5d086eff9586e69e827b88a5585517386879474f4cf7/numpy-2.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc578891de1db95b2a35001b695451767b580bb45753717498213c5ff3c41d63", size = 16363091, upload-time = "2025-12-20T16:16:17.32Z" }, - { url = "https://files.pythonhosted.org/packages/9d/9c/8efe24577523ec6809261859737cf117b0eb6fdb655abdfdc81b2e468ce4/numpy-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98e81648e0b36e325ab67e46b5400a7a6d4a22b8a7c8e8bbfe20e7db7906bf95", size = 16176394, upload-time = "2025-12-20T16:16:19.524Z" }, - { url = "https://files.pythonhosted.org/packages/61/f0/1687441ece7b47a62e45a1f82015352c240765c707928edd8aef875d5951/numpy-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d57b5046c120561ba8fa8e4030fbb8b822f3063910fa901ffadf16e2b7128ad6", size = 18287378, upload-time = "2025-12-20T16:16:22.866Z" }, - { url = "https://files.pythonhosted.org/packages/d3/6f/f868765d44e6fc466467ed810ba9d8d6db1add7d4a748abfa2a4c99a3194/numpy-2.4.0-cp312-cp312-win32.whl", hash = "sha256:92190db305a6f48734d3982f2c60fa30d6b5ee9bff10f2887b930d7b40119f4c", size = 5955432, upload-time = "2025-12-20T16:16:25.06Z" }, - { url = "https://files.pythonhosted.org/packages/d4/b5/94c1e79fcbab38d1ca15e13777477b2914dd2d559b410f96949d6637b085/numpy-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:680060061adb2d74ce352628cb798cfdec399068aa7f07ba9fb818b2b3305f98", size = 12306201, upload-time = "2025-12-20T16:16:26.979Z" }, - { url = "https://files.pythonhosted.org/packages/70/09/c39dadf0b13bb0768cd29d6a3aaff1fb7c6905ac40e9aaeca26b1c086e06/numpy-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:39699233bc72dd482da1415dcb06076e32f60eddc796a796c5fb6c5efce94667", size = 10308234, upload-time = "2025-12-20T16:16:29.417Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ef/088e7c7342f300aaf3ee5f2c821c4b9996a1bef2aaf6a49cc8ab4883758e/numpy-2.4.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b54c83f1c0c0f1d748dca0af516062b8829d53d1f0c402be24b4257a9c48ada6", size = 16819003, upload-time = "2025-12-20T16:18:03.41Z" }, - { url = "https://files.pythonhosted.org/packages/ff/ce/a53017b5443b4b84517182d463fc7bcc2adb4faa8b20813f8e5f5aeb5faa/numpy-2.4.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:aabb081ca0ec5d39591fc33018cd4b3f96e1a2dd6756282029986d00a785fba4", size = 12567105, upload-time = "2025-12-20T16:18:05.594Z" }, - { url = "https://files.pythonhosted.org/packages/77/58/5ff91b161f2ec650c88a626c3905d938c89aaadabd0431e6d9c1330c83e2/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:8eafe7c36c8430b7794edeab3087dec7bf31d634d92f2af9949434b9d1964cba", size = 5395590, upload-time = "2025-12-20T16:18:08.031Z" }, - { url = "https://files.pythonhosted.org/packages/1d/4e/f1a084106df8c2df8132fc437e56987308e0524836aa7733721c8429d4fe/numpy-2.4.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2f585f52b2baf07ff3356158d9268ea095e221371f1074fadea2f42544d58b4d", size = 6709947, upload-time = "2025-12-20T16:18:09.836Z" }, - { url = "https://files.pythonhosted.org/packages/63/09/3d8aeb809c0332c3f642da812ac2e3d74fc9252b3021f8c30c82e99e3f3d/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32ed06d0fe9cae27d8fb5f400c63ccee72370599c75e683a6358dd3a4fb50aaf", size = 14535119, upload-time = "2025-12-20T16:18:12.105Z" }, - { url = "https://files.pythonhosted.org/packages/fd/7f/68f0fc43a2cbdc6bb239160c754d87c922f60fbaa0fa3cd3d312b8a7f5ee/numpy-2.4.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:57c540ed8fb1f05cb997c6761cd56db72395b0d6985e90571ff660452ade4f98", size = 16475815, upload-time = "2025-12-20T16:18:14.433Z" }, - { url = "https://files.pythonhosted.org/packages/11/73/edeacba3167b1ca66d51b1a5a14697c2c40098b5ffa01811c67b1785a5ab/numpy-2.4.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:a39fb973a726e63223287adc6dafe444ce75af952d711e400f3bf2b36ef55a7b", size = 12489376, upload-time = "2025-12-20T16:18:16.524Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/34/2b1bc18424f3ad9af577f6ce23600319968a70575bd7db31ce66731bbef9/numpy-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0cce2a669e3c8ba02ee563c7835f92c153cf02edff1ae05e1823f1dde21b16a5", size = 16944563, upload-time = "2026-01-10T06:42:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/2c/57/26e5f97d075aef3794045a6ca9eada6a4ed70eb9a40e7a4a93f9ac80d704/numpy-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:899d2c18024984814ac7e83f8f49d8e8180e2fbe1b2e252f2e7f1d06bea92425", size = 12645658, upload-time = "2026-01-10T06:42:17.298Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ba/80fc0b1e3cb2fd5c6143f00f42eb67762aa043eaa05ca924ecc3222a7849/numpy-2.4.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:09aa8a87e45b55a1c2c205d42e2808849ece5c484b2aab11fecabec3841cafba", size = 5474132, upload-time = "2026-01-10T06:42:19.637Z" }, + { url = "https://files.pythonhosted.org/packages/40/ae/0a5b9a397f0e865ec171187c78d9b57e5588afc439a04ba9cab1ebb2c945/numpy-2.4.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:edee228f76ee2dab4579fad6f51f6a305de09d444280109e0f75df247ff21501", size = 6804159, upload-time = "2026-01-10T06:42:21.44Z" }, + { url = "https://files.pythonhosted.org/packages/86/9c/841c15e691c7085caa6fd162f063eff494099c8327aeccd509d1ab1e36ab/numpy-2.4.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a92f227dbcdc9e4c3e193add1a189a9909947d4f8504c576f4a732fd0b54240a", size = 14708058, upload-time = "2026-01-10T06:42:23.546Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9d/7862db06743f489e6a502a3b93136d73aea27d97b2cf91504f70a27501d6/numpy-2.4.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:538bf4ec353709c765ff75ae616c34d3c3dca1a68312727e8f2676ea644f8509", size = 16651501, upload-time = "2026-01-10T06:42:25.909Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9c/6fc34ebcbd4015c6e5f0c0ce38264010ce8a546cb6beacb457b84a75dfc8/numpy-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ac08c63cb7779b85e9d5318e6c3518b424bc1f364ac4cb2c6136f12e5ff2dccc", size = 16492627, upload-time = "2026-01-10T06:42:28.938Z" }, + { url = "https://files.pythonhosted.org/packages/aa/63/2494a8597502dacda439f61b3c0db4da59928150e62be0e99395c3ad23c5/numpy-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f9c360ecef085e5841c539a9a12b883dff005fbd7ce46722f5e9cef52634d82", size = 18585052, upload-time = "2026-01-10T06:42:31.312Z" }, + { url = "https://files.pythonhosted.org/packages/6a/93/098e1162ae7522fc9b618d6272b77404c4656c72432ecee3abc029aa3de0/numpy-2.4.1-cp311-cp311-win32.whl", hash = "sha256:0f118ce6b972080ba0758c6087c3617b5ba243d806268623dc34216d69099ba0", size = 6236575, upload-time = "2026-01-10T06:42:33.872Z" }, + { url = "https://files.pythonhosted.org/packages/8c/de/f5e79650d23d9e12f38a7bc6b03ea0835b9575494f8ec94c11c6e773b1b1/numpy-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:18e14c4d09d55eef39a6ab5b08406e84bc6869c1e34eef45564804f90b7e0574", size = 12604479, upload-time = "2026-01-10T06:42:35.778Z" }, + { url = "https://files.pythonhosted.org/packages/dd/65/e1097a7047cff12ce3369bd003811516b20ba1078dbdec135e1cd7c16c56/numpy-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:6461de5113088b399d655d45c3897fa188766415d0f568f175ab071c8873bd73", size = 10578325, upload-time = "2026-01-10T06:42:38.518Z" }, + { url = "https://files.pythonhosted.org/packages/78/7f/ec53e32bf10c813604edf07a3682616bd931d026fcde7b6d13195dfb684a/numpy-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2", size = 16656888, upload-time = "2026-01-10T06:42:40.913Z" }, + { url = "https://files.pythonhosted.org/packages/b8/e0/1f9585d7dae8f14864e948fd7fa86c6cb72dee2676ca2748e63b1c5acfe0/numpy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8", size = 12373956, upload-time = "2026-01-10T06:42:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/8e/43/9762e88909ff2326f5e7536fa8cb3c49fb03a7d92705f23e6e7f553d9cb3/numpy-2.4.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a", size = 5202567, upload-time = "2026-01-10T06:42:45.107Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ee/34b7930eb61e79feb4478800a4b95b46566969d837546aa7c034c742ef98/numpy-2.4.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26f0bcd9c79a00e339565b303badc74d3ea2bd6d52191eeca5f95936cad107d0", size = 6549459, upload-time = "2026-01-10T06:42:48.152Z" }, + { url = "https://files.pythonhosted.org/packages/79/e3/5f115fae982565771be994867c89bcd8d7208dbfe9469185497d70de5ddf/numpy-2.4.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0093e85df2960d7e4049664b26afc58b03236e967fb942354deef3208857a04c", size = 14404859, upload-time = "2026-01-10T06:42:49.947Z" }, + { url = "https://files.pythonhosted.org/packages/d9/7d/9c8a781c88933725445a859cac5d01b5871588a15969ee6aeb618ba99eee/numpy-2.4.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad270f438cbdd402c364980317fb6b117d9ec5e226fff5b4148dd9aa9fc6e02", size = 16371419, upload-time = "2026-01-10T06:42:52.409Z" }, + { url = "https://files.pythonhosted.org/packages/a6/d2/8aa084818554543f17cf4162c42f162acbd3bb42688aefdba6628a859f77/numpy-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:297c72b1b98100c2e8f873d5d35fb551fce7040ade83d67dd51d38c8d42a2162", size = 16182131, upload-time = "2026-01-10T06:42:54.694Z" }, + { url = "https://files.pythonhosted.org/packages/60/db/0425216684297c58a8df35f3284ef56ec4a043e6d283f8a59c53562caf1b/numpy-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf6470d91d34bf669f61d515499859fa7a4c2f7c36434afb70e82df7217933f9", size = 18295342, upload-time = "2026-01-10T06:42:56.991Z" }, + { url = "https://files.pythonhosted.org/packages/31/4c/14cb9d86240bd8c386c881bafbe43f001284b7cce3bc01623ac9475da163/numpy-2.4.1-cp312-cp312-win32.whl", hash = "sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f", size = 5959015, upload-time = "2026-01-10T06:42:59.631Z" }, + { url = "https://files.pythonhosted.org/packages/51/cf/52a703dbeb0c65807540d29699fef5fda073434ff61846a564d5c296420f/numpy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87", size = 12310730, upload-time = "2026-01-10T06:43:01.627Z" }, + { url = "https://files.pythonhosted.org/packages/69/80/a828b2d0ade5e74a9fe0f4e0a17c30fdc26232ad2bc8c9f8b3197cf7cf18/numpy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8", size = 10312166, upload-time = "2026-01-10T06:43:03.673Z" }, + { url = "https://files.pythonhosted.org/packages/1e/48/d86f97919e79314a1cdee4c832178763e6e98e623e123d0bada19e92c15a/numpy-2.4.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ad35f20be147a204e28b6a0575fbf3540c5e5f802634d4258d55b1ff5facce1", size = 16822202, upload-time = "2026-01-10T06:44:43.738Z" }, + { url = "https://files.pythonhosted.org/packages/51/e9/1e62a7f77e0f37dcfb0ad6a9744e65df00242b6ea37dfafb55debcbf5b55/numpy-2.4.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8097529164c0f3e32bb89412a0905d9100bf434d9692d9fc275e18dcf53c9344", size = 12569985, upload-time = "2026-01-10T06:44:45.945Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7e/914d54f0c801342306fdcdce3e994a56476f1b818c46c47fc21ae968088c/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ea66d2b41ca4a1630aae5507ee0a71647d3124d1741980138aa8f28f44dac36e", size = 5398484, upload-time = "2026-01-10T06:44:48.012Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d8/9570b68584e293a33474e7b5a77ca404f1dcc655e40050a600dee81d27fb/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d3f8f0df9f4b8be57b3bf74a1d087fec68f927a2fab68231fdb442bf2c12e426", size = 6713216, upload-time = "2026-01-10T06:44:49.725Z" }, + { url = "https://files.pythonhosted.org/packages/33/9b/9dd6e2db8d49eb24f86acaaa5258e5f4c8ed38209a4ee9de2d1a0ca25045/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2023ef86243690c2791fd6353e5b4848eedaa88ca8a2d129f462049f6d484696", size = 14538937, upload-time = "2026-01-10T06:44:51.498Z" }, + { url = "https://files.pythonhosted.org/packages/53/87/d5bd995b0f798a37105b876350d346eea5838bd8f77ea3d7a48392f3812b/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8361ea4220d763e54cff2fbe7d8c93526b744f7cd9ddab47afeff7e14e8503be", size = 16479830, upload-time = "2026-01-10T06:44:53.931Z" }, + { url = "https://files.pythonhosted.org/packages/5b/c7/b801bf98514b6ae6475e941ac05c58e6411dd863ea92916bfd6d510b08c1/numpy-2.4.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4f1b68ff47680c2925f8063402a693ede215f0257f02596b1318ecdfb1d79e33", size = 12492579, upload-time = "2026-01-10T06:44:57.094Z" }, ] [[package]] name = "onnx" -version = "1.20.0" +version = "1.20.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ml-dtypes" }, @@ -1261,37 +1257,38 @@ dependencies = [ { name = "protobuf" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bd/bf/824b13b7ea14c2d374b48a296cfa412442e5559326fbab5441a4fcb68924/onnx-1.20.0.tar.gz", hash = "sha256:1a93ec69996b4556062d552ed1aa0671978cfd3c17a40bf4c89a1ae169c6a4ad", size = 12049527, upload-time = "2025-12-01T18:14:34.679Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/9a/125ad5ed919d1782b26b0b4404e51adc44afd029be30d5a81b446dccd9c5/onnx-1.20.0-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:00dc8ae2c7b283f79623961f450b5515bd2c4b47a7027e7a1374ba49cef27768", size = 18341929, upload-time = "2025-12-01T18:13:43.79Z" }, - { url = "https://files.pythonhosted.org/packages/4d/3c/85280dd05396493f3e1b4feb7a3426715e344b36083229437f31d9788a01/onnx-1.20.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f62978ecfb8f320faba6704abd20253a5a79aacc4e5d39a9c061dd63d3b7574f", size = 17899362, upload-time = "2025-12-01T18:13:46.496Z" }, - { url = "https://files.pythonhosted.org/packages/26/db/e11cf9aaa6ccbcd27ea94d321020fef3207cba388bff96111e6431f97d1a/onnx-1.20.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:71177f8fd5c0dd90697bc281f5035f73707bdac83257a5c54d74403a1100ace9", size = 18119129, upload-time = "2025-12-01T18:13:49.662Z" }, - { url = "https://files.pythonhosted.org/packages/ef/0b/1b99e7ba5ccfa8ecb3509ec579c8520098d09b903ccd520026d60faa7c75/onnx-1.20.0-cp311-cp311-win32.whl", hash = "sha256:1d3d0308e2c194f4b782f51e78461b567fac8ce6871c0cf5452ede261683cc8f", size = 16364604, upload-time = "2025-12-01T18:13:52.691Z" }, - { url = "https://files.pythonhosted.org/packages/51/ab/7399817821d0d18ff67292ac183383e41f4f4ddff2047902f1b7b51d2d40/onnx-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:3a6de7dda77926c323b0e5a830dc9c2866ce350c1901229e193be1003a076c25", size = 16488019, upload-time = "2025-12-01T18:13:55.776Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/23059c11d9c0fb1951acec504a5cc86e1dd03d2eef3a98cf1941839f5322/onnx-1.20.0-cp311-cp311-win_arm64.whl", hash = "sha256:afc4cf83ce5d547ebfbb276dae8eb0ec836254a8698d462b4ba5f51e717fd1ae", size = 16446841, upload-time = "2025-12-01T18:13:58.091Z" }, - { url = "https://files.pythonhosted.org/packages/5e/19/2caa972a31014a8cb4525f715f2a75d93caef9d4b9da2809cc05d0489e43/onnx-1.20.0-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:31efe37d7d1d659091f34ddd6a31780334acf7c624176832db9a0a8ececa8fb5", size = 18340913, upload-time = "2025-12-01T18:14:00.477Z" }, - { url = "https://files.pythonhosted.org/packages/78/bb/b98732309f2f6beb4cdcf7b955d7bbfd75a191185370ee21233373db381e/onnx-1.20.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d75da05e743eb9a11ff155a775cae5745e71f1cd0ca26402881b8f20e8d6e449", size = 17896118, upload-time = "2025-12-01T18:14:03.239Z" }, - { url = "https://files.pythonhosted.org/packages/84/a7/38aa564871d062c11538d65c575af9c7e057be880c09ecbd899dd1abfa83/onnx-1.20.0-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02e0d72ab09a983fce46686b155a5049898558d9f3bc6e8515120d6c40666318", size = 18115415, upload-time = "2025-12-01T18:14:06.261Z" }, - { url = "https://files.pythonhosted.org/packages/3b/17/a600b62cf4ad72976c66f83ce9e324205af434706ad5ec0e35129e125aef/onnx-1.20.0-cp312-abi3-win32.whl", hash = "sha256:392ca68b34b97e172d33b507e1e7bfdf2eea96603e6e7ff109895b82ff009dc7", size = 16363019, upload-time = "2025-12-01T18:14:09.16Z" }, - { url = "https://files.pythonhosted.org/packages/9c/3b/5146ba0a89f73c026bb468c49612bab8d005aa28155ebf06cf5f2eb8d36c/onnx-1.20.0-cp312-abi3-win_amd64.whl", hash = "sha256:259b05758d41645f5545c09f887187662b350d40db8d707c33c94a4f398e1733", size = 16485934, upload-time = "2025-12-01T18:14:13.046Z" }, - { url = "https://files.pythonhosted.org/packages/f3/bc/d251b97395e721b3034e9578d4d4d9fb33aac4197ae16ce8c7ed79a26dce/onnx-1.20.0-cp312-abi3-win_arm64.whl", hash = "sha256:2d25a9e1fde44bc69988e50e2211f62d6afcd01b0fd6dfd23429fd978a35d32f", size = 16444946, upload-time = "2025-12-01T18:14:15.801Z" }, + { url = "https://files.pythonhosted.org/packages/0c/38/1a0e74d586c08833404100f5c052f92732fb5be417c0b2d7cb0838443bfe/onnx-1.20.1-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:53426e1b458641e7a537e9f176330012ff59d90206cac1c1a9d03cdd73ed3095", size = 17904965, upload-time = "2026-01-10T01:39:13.532Z" }, + { url = "https://files.pythonhosted.org/packages/96/25/64b076e9684d17335f80b15b3bf502f7a8e1a89f08a6b208d4f2861b3011/onnx-1.20.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca7281f8c576adf396c338cf43fff26faee8d4d2e2577b8e73738f37ceccf945", size = 17415179, upload-time = "2026-01-10T01:39:16.516Z" }, + { url = "https://files.pythonhosted.org/packages/ac/d5/6743b409421ced20ad5af1b3a7b4c4e568689ffaca86db431692fca409a6/onnx-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2297f428c51c7fc6d8fad0cf34384284dfeff3f86799f8e83ef905451348ade0", size = 17513672, upload-time = "2026-01-10T01:39:19.35Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6b/dae82e6fdb2043302f29adca37522312ea2be55b75907b59be06fbdffe87/onnx-1.20.1-cp311-cp311-win32.whl", hash = "sha256:63d9cbcab8c96841eadeb7c930e07bfab4dde8081eb76fb68e0dfb222706b81e", size = 16239336, upload-time = "2026-01-10T01:39:22.506Z" }, + { url = "https://files.pythonhosted.org/packages/8e/17/a0d7863390c1f2067d7c02dcc1477034965c32aaa1407bfcf775305ffee4/onnx-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:d78cde72d7ca8356a2d99c5dc0dbf67264254828cae2c5780184486c0cd7b3bf", size = 16392120, upload-time = "2026-01-10T01:39:25.106Z" }, + { url = "https://files.pythonhosted.org/packages/aa/72/9b879a46eb7a3322223791f36bf9c25d95da9ed93779eabb75a560f22e5b/onnx-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:0104bb2d4394c179bcea3df7599a45a2932b80f4633840896fcf0d7d8daecea2", size = 16346923, upload-time = "2026-01-10T01:39:27.782Z" }, + { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, + { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216, upload-time = "2026-01-10T01:39:39.46Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207, upload-time = "2026-01-10T01:39:41.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155, upload-time = "2026-01-10T01:39:45.536Z" }, ] [[package]] name = "opencv-python-headless" -version = "4.11.0.86" +version = "4.13.0.90" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/2f/5b2b3ba52c864848885ba988f24b7f105052f68da9ab0e693cc7c25b0b30/opencv-python-headless-4.11.0.86.tar.gz", hash = "sha256:996eb282ca4b43ec6a3972414de0e2331f5d9cda2b41091a49739c19fb843798", size = 95177929, upload-time = "2025-01-16T13:53:40.22Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/53/2c50afa0b1e05ecdb4603818e85f7d174e683d874ef63a6abe3ac92220c8/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:48128188ade4a7e517237c8e1e11a9cdf5c282761473383e77beb875bb1e61ca", size = 37326460, upload-time = "2025-01-16T13:52:57.015Z" }, - { url = "https://files.pythonhosted.org/packages/3b/43/68555327df94bb9b59a1fd645f63fafb0762515344d2046698762fc19d58/opencv_python_headless-4.11.0.86-cp37-abi3-macosx_13_0_x86_64.whl", hash = "sha256:a66c1b286a9de872c343ee7c3553b084244299714ebb50fbdcd76f07ebbe6c81", size = 56723330, upload-time = "2025-01-16T13:55:45.731Z" }, - { url = "https://files.pythonhosted.org/packages/45/be/1438ce43ebe65317344a87e4b150865c5585f4c0db880a34cdae5ac46881/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6efabcaa9df731f29e5ea9051776715b1bdd1845d7c9530065c7951d2a2899eb", size = 29487060, upload-time = "2025-01-16T13:51:59.625Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5c/c139a7876099916879609372bfa513b7f1257f7f1a908b0bdc1c2328241b/opencv_python_headless-4.11.0.86-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e0a27c19dd1f40ddff94976cfe43066fbbe9dfbb2ec1907d66c19caef42a57b", size = 49969856, upload-time = "2025-01-16T13:53:29.654Z" }, - { url = "https://files.pythonhosted.org/packages/95/dd/ed1191c9dc91abcc9f752b499b7928aacabf10567bb2c2535944d848af18/opencv_python_headless-4.11.0.86-cp37-abi3-win32.whl", hash = "sha256:f447d8acbb0b6f2808da71fddd29c1cdd448d2bc98f72d9bb78a7a898fc9621b", size = 29324425, upload-time = "2025-01-16T13:52:49.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/8a/69176a64335aed183529207ba8bc3d329c2999d852b4f3818027203f50e6/opencv_python_headless-4.11.0.86-cp37-abi3-win_amd64.whl", hash = "sha256:6c304df9caa7a6a5710b91709dd4786bf20a74d57672b3c31f7033cc638174ca", size = 39402386, upload-time = "2025-01-16T13:52:56.418Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/38c4cbb5ccfce7aaf36fd9be9fc74a15c85a48ef90bfaca2049b486e10c5/opencv_python_headless-4.13.0.90-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:12a28674f215542c9bf93338de1b5bffd76996d32da9acb9e739fdb9c8bbd738", size = 46020414, upload-time = "2026-01-18T09:07:10.801Z" }, + { url = "https://files.pythonhosted.org/packages/93/c5/4b40daa5003b45aa8397f160324a091ed323733e2446dc0bdf3655e77b84/opencv_python_headless-4.13.0.90-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:32255203040dc98803be96362e13f9e4bce20146898222d2e5c242f80de50da5", size = 32568519, upload-time = "2026-01-18T09:07:52.368Z" }, + { url = "https://files.pythonhosted.org/packages/da/65/920e64a7f03cf5917cd2c6a3046293843c1a16ad89f0ed0f1c683979c9de/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e13790342591557050157713af17a7435ac1b50c65282715093c9297fa045d8f", size = 35191272, upload-time = "2026-01-18T09:08:49.235Z" }, + { url = "https://files.pythonhosted.org/packages/fc/13/af150685be342dc09bfb0824e2a280020ccf1c7fc64e15a31d9209016aa9/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dbc1f4625e5af3a80ebdbd84380227c0f445228588f2521b11af47710caca1ba", size = 57683677, upload-time = "2026-01-18T09:10:23.588Z" }, + { url = "https://files.pythonhosted.org/packages/cd/47/baab2a3b6d8da8c52e73d00207d1ed3155601c2c332ea855455b3fbc8ff4/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eba38bc255d0b7d1969c5bcc90a060ca2b61a3403b613872c750bfa5dfe9e03b", size = 36590019, upload-time = "2026-01-18T09:10:49.053Z" }, + { url = "https://files.pythonhosted.org/packages/81/a1/facfe2801a861b424c4221d66e1281cf19735c00e07f063a337a208c11b5/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f46b17ea0aa7e4124ca6ad71143f89233ae9557f61d2326bcdb34329a1ddf9bd", size = 62535926, upload-time = "2026-01-18T09:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/06/d2/5e9ee7512306c1caa518be929d1f44bb1c189f342f538f73bea6fb94919f/opencv_python_headless-4.13.0.90-cp37-abi3-win32.whl", hash = "sha256:96060fc57a1abb1144b0b8129e2ff3bfcdd0ccd8e8bd05bd85256ff4ed587d3b", size = 30811665, upload-time = "2026-01-18T09:13:44.517Z" }, + { url = "https://files.pythonhosted.org/packages/a0/09/0a4d832448dccd03b2b1bdee70b9fc2e02c147cc7e06975e9cd729569d90/opencv_python_headless-4.13.0.90-cp37-abi3-win_amd64.whl", hash = "sha256:0e0c8c9f620802fddc4fa7f471a1d263c7b0dca16cd9e7e2f996bb8bd2128c0c", size = 40070035, upload-time = "2026-01-18T09:15:14.652Z" }, ] [[package]] @@ -1494,8 +1491,8 @@ name = "panda3d-gltf" version = "0.13" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "panda3d-simplepbr", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d" }, + { name = "panda3d-simplepbr" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/7f/9f18fc3fa843a080acb891af6bcc12262e7bdf1d194a530f7042bebfc81f/panda3d-gltf-0.13.tar.gz", hash = "sha256:d06d373bdd91cf530909b669f43080e599463bbf6d3ef00c3558bad6c6b19675", size = 25573, upload-time = "2021-05-21T05:46:32.738Z" } wheels = [ @@ -1507,8 +1504,8 @@ name = "panda3d-simplepbr" version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "panda3d", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, - { name = "typing-extensions", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "panda3d" }, + { name = "typing-extensions" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/be/c4d1ded04c22b357277cf6e6a44c1ab4abb285a700bd1991460460e05b99/panda3d_simplepbr-0.13.1.tar.gz", hash = "sha256:c83766d7c8f47499f365a07fe1dff078fc8b3054c2689bdc8dceabddfe7f1a35", size = 6216055, upload-time = "2025-03-30T16:57:41.087Z" } wheels = [ @@ -1526,48 +1523,48 @@ wheels = [ [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, ] [[package]] name = "pillow" -version = "12.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, - { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, - { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, - { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, - { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, - { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, - { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, - { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, - { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, - { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, - { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, - { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, - { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, - { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, - { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, +version = "12.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, + { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, + { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, + { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, + { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, + { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, + { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, + { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, + { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, + { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, + { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, + { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, + { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, + { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, + { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, + { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, + { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, + { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, + { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, + { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, + { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, + { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, + { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, ] [[package]] @@ -1647,33 +1644,33 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.2" +version = "6.33.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/34/44/e49ecff446afeec9d1a66d6bbf9adc21e3c7cea7803a920ca3773379d4f6/protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4", size = 444296, upload-time = "2025-12-06T00:17:53.311Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/b8/cda15d9d46d03d4aa3a67cb6bffe05173440ccf86a9541afaf7ac59a1b6b/protobuf-6.33.4.tar.gz", hash = "sha256:dc2e61bca3b10470c1912d166fe0af67bfc20eb55971dcef8dfa48ce14f0ed91", size = 444346, upload-time = "2026-01-12T18:33:40.109Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/91/1e3a34881a88697a7354ffd177e8746e97a722e5e8db101544b47e84afb1/protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d", size = 425603, upload-time = "2025-12-06T00:17:41.114Z" }, - { url = "https://files.pythonhosted.org/packages/64/20/4d50191997e917ae13ad0a235c8b42d8c1ab9c3e6fd455ca16d416944355/protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4", size = 436930, upload-time = "2025-12-06T00:17:43.278Z" }, - { url = "https://files.pythonhosted.org/packages/b2/ca/7e485da88ba45c920fb3f50ae78de29ab925d9e54ef0de678306abfbb497/protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43", size = 427621, upload-time = "2025-12-06T00:17:44.445Z" }, - { url = "https://files.pythonhosted.org/packages/7d/4f/f743761e41d3b2b2566748eb76bbff2b43e14d5fcab694f494a16458b05f/protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e", size = 324460, upload-time = "2025-12-06T00:17:45.678Z" }, - { url = "https://files.pythonhosted.org/packages/b1/fa/26468d00a92824020f6f2090d827078c09c9c587e34cbfd2d0c7911221f8/protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872", size = 339168, upload-time = "2025-12-06T00:17:46.813Z" }, - { url = "https://files.pythonhosted.org/packages/56/13/333b8f421738f149d4fe5e49553bc2a2ab75235486259f689b4b91f96cec/protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f", size = 323270, upload-time = "2025-12-06T00:17:48.253Z" }, - { url = "https://files.pythonhosted.org/packages/0e/15/4f02896cc3df04fc465010a4c6a0cd89810f54617a32a70ef531ed75d61c/protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c", size = 170501, upload-time = "2025-12-06T00:17:52.211Z" }, + { url = "https://files.pythonhosted.org/packages/e0/be/24ef9f3095bacdf95b458543334d0c4908ccdaee5130420bf064492c325f/protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d", size = 425612, upload-time = "2026-01-12T18:33:29.656Z" }, + { url = "https://files.pythonhosted.org/packages/31/ad/e5693e1974a28869e7cd244302911955c1cebc0161eb32dfa2b25b6e96f0/protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc", size = 436962, upload-time = "2026-01-12T18:33:31.345Z" }, + { url = "https://files.pythonhosted.org/packages/66/15/6ee23553b6bfd82670207ead921f4d8ef14c107e5e11443b04caeb5ab5ec/protobuf-6.33.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2fe67f6c014c84f655ee06f6f66213f9254b3a8b6bda6cda0ccd4232c73c06f0", size = 427612, upload-time = "2026-01-12T18:33:32.646Z" }, + { url = "https://files.pythonhosted.org/packages/2b/48/d301907ce6d0db75f959ca74f44b475a9caa8fcba102d098d3c3dd0f2d3f/protobuf-6.33.4-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:757c978f82e74d75cba88eddec479df9b99a42b31193313b75e492c06a51764e", size = 324484, upload-time = "2026-01-12T18:33:33.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/1c/e53078d3f7fe710572ab2dcffd993e1e3b438ae71cfc031b71bae44fcb2d/protobuf-6.33.4-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c7c64f259c618f0bef7bee042075e390debbf9682334be2b67408ec7c1c09ee6", size = 339256, upload-time = "2026-01-12T18:33:35.231Z" }, + { url = "https://files.pythonhosted.org/packages/e8/8e/971c0edd084914f7ee7c23aa70ba89e8903918adca179319ee94403701d5/protobuf-6.33.4-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:3df850c2f8db9934de4cf8f9152f8dc2558f49f298f37f90c517e8e5c84c30e9", size = 323311, upload-time = "2026-01-12T18:33:36.305Z" }, + { url = "https://files.pythonhosted.org/packages/75/b1/1dc83c2c661b4c62d56cc081706ee33a4fc2835bd90f965baa2663ef7676/protobuf-6.33.4-py3-none-any.whl", hash = "sha256:1fe3730068fcf2e595816a6c34fe66eeedd37d51d0400b72fabc848811fdc1bc", size = 170532, upload-time = "2026-01-12T18:33:39.199Z" }, ] [[package]] name = "psutil" -version = "7.2.0" +version = "7.2.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/be/7c/31d1c3ceb1260301f87565f50689dc6da3db427ece1e1e012af22abca54e/psutil-7.2.0.tar.gz", hash = "sha256:2e4f8e1552f77d14dc96fb0f6240c5b34a37081c0889f0853b3b29a496e5ef64", size = 489863, upload-time = "2025-12-23T20:26:24.616Z" } +sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/40/c5/a49160bf3e165b7b93a60579a353cf5d939d7f878fe5fd369110f1d18043/psutil-7.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:977a2fcd132d15cb05b32b2d85b98d087cad039b0ce435731670ba74da9e6133", size = 128116, upload-time = "2025-12-23T20:26:53.516Z" }, - { url = "https://files.pythonhosted.org/packages/10/a1/c75feb480f60cd768fb6ed00ac362a16a33e5076ec8475a22d8162fb2659/psutil-7.2.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:24151011c21fadd94214d7139d7c6c54569290d7e553989bdf0eab73b13beb8c", size = 128925, upload-time = "2025-12-23T20:26:55.573Z" }, - { url = "https://files.pythonhosted.org/packages/12/ff/e93136587c00a543f4bc768b157fac2c47cd77b180d4f4e5c6efb6ea53a2/psutil-7.2.0-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:91f211ba9279e7c61d9d8f84b713cfc38fa161cb0597d5cb3f1ca742f6848254", size = 154666, upload-time = "2025-12-23T20:26:57.312Z" }, - { url = "https://files.pythonhosted.org/packages/b8/dd/4c2de9c3827c892599d277a69d2224136800870a8a88a80981de905de28d/psutil-7.2.0-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f37415188b7ea98faf90fed51131181646c59098b077550246e2e092e127418b", size = 156109, upload-time = "2025-12-23T20:26:58.851Z" }, - { url = "https://files.pythonhosted.org/packages/81/3f/090943c682d3629968dd0b04826ddcbc760ee1379021dbe316e2ddfcd01b/psutil-7.2.0-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0d12c7ce6ed1128cd81fd54606afa054ac7dbb9773469ebb58cf2f171c49f2ac", size = 148081, upload-time = "2025-12-23T20:27:01.318Z" }, - { url = "https://files.pythonhosted.org/packages/c4/88/c39648ebb8ec182d0364af53cdefe6eddb5f3872ba718b5855a8ff65d6d4/psutil-7.2.0-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ca0faef7976530940dcd39bc5382d0d0d5eb023b186a4901ca341bd8d8684151", size = 147376, upload-time = "2025-12-23T20:27:03.347Z" }, - { url = "https://files.pythonhosted.org/packages/01/a2/5b39e08bd9b27476bc7cce7e21c71a481ad60b81ffac49baf02687a50d7f/psutil-7.2.0-cp37-abi3-win_amd64.whl", hash = "sha256:abdb74137ca232d20250e9ad471f58d500e7743bc8253ba0bfbf26e570c0e437", size = 136910, upload-time = "2025-12-23T20:27:05.289Z" }, - { url = "https://files.pythonhosted.org/packages/59/54/53839db1258c1eaeb4ded57ff202144ebc75b23facc05a74fd98d338b0c6/psutil-7.2.0-cp37-abi3-win_arm64.whl", hash = "sha256:284e71038b3139e7ab3834b63b3eb5aa5565fcd61a681ec746ef9a0a8c457fd2", size = 133807, upload-time = "2025-12-23T20:27:06.825Z" }, + { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, + { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, + { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, + { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, + { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, + { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, ] [[package]] @@ -1859,167 +1856,167 @@ name = "pyobjc" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-addressbook", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-applescriptkit", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-applicationservices", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-arkit", marker = "platform_release >= '25.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-automator", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-carbon", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cfnetwork", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-compositorservices", marker = "platform_release >= '25.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudiokit", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremidi", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-discrecording", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-discrecordingui", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-diskarbitration", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-dvdplayback", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-exceptionhandling", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fskit", marker = "platform_release >= '24.4' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-gamesave", marker = "platform_release >= '25.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-installerplugins", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-iobluetoothui", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-latentsemanticmapping", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-launchservices", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-network", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-osakit", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-preferencepanes", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-screensaver", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-searchkit", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-securityfoundation", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-securityinterface", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-securityui", marker = "platform_release >= '24.4' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-social", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-syncservices", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-systemconfiguration", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0' and sys_platform == 'darwin'" }, - { name = "pyobjc-framework-webkit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-addressbook" }, + { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-applescriptkit" }, + { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-applicationservices" }, + { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-arkit", marker = "platform_release >= '25.0'" }, + { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-automator" }, + { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4'" }, + { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-carbon" }, + { name = "pyobjc-framework-cfnetwork" }, + { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-compositorservices", marker = "platform_release >= '25.0'" }, + { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coreaudiokit" }, + { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-coredata" }, + { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-coremidi" }, + { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-coreservices" }, + { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-coretext" }, + { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0'" }, + { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-discrecording" }, + { name = "pyobjc-framework-discrecordingui" }, + { name = "pyobjc-framework-diskarbitration" }, + { name = "pyobjc-framework-dvdplayback" }, + { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-exceptionhandling" }, + { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-fskit", marker = "platform_release >= '24.4'" }, + { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-gamesave", marker = "platform_release >= '25.0'" }, + { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-installerplugins" }, + { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0'" }, + { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-iobluetooth" }, + { name = "pyobjc-framework-iobluetoothui" }, + { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-latentsemanticmapping" }, + { name = "pyobjc-framework-launchservices" }, + { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0'" }, + { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0'" }, + { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-network", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0'" }, + { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-osakit" }, + { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0'" }, + { name = "pyobjc-framework-preferencepanes" }, + { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-quartz" }, + { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0'" }, + { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4'" }, + { name = "pyobjc-framework-screensaver" }, + { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0'" }, + { name = "pyobjc-framework-searchkit" }, + { name = "pyobjc-framework-security" }, + { name = "pyobjc-framework-securityfoundation" }, + { name = "pyobjc-framework-securityinterface" }, + { name = "pyobjc-framework-securityui", marker = "platform_release >= '24.4'" }, + { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0'" }, + { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0'" }, + { name = "pyobjc-framework-social", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0'" }, + { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0'" }, + { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0'" }, + { name = "pyobjc-framework-syncservices" }, + { name = "pyobjc-framework-systemconfiguration" }, + { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0'" }, + { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0'" }, + { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0'" }, + { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0'" }, + { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0'" }, + { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0'" }, + { name = "pyobjc-framework-webkit" }, ] sdist = { url = "https://files.pythonhosted.org/packages/17/06/d77639ba166cc09aed2d32ae204811b47bc5d40e035cdc9bff7fff72ec5f/pyobjc-12.1.tar.gz", hash = "sha256:686d6db3eb3182fac9846b8ce3eedf4c7d2680b21b8b8d6e6df054a17e92a12d", size = 11345, upload-time = "2025-11-14T10:07:28.155Z" } wheels = [ @@ -2041,9 +2038,9 @@ name = "pyobjc-framework-accessibility" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2d/87/8ca40428d05a668fecc638f2f47dba86054dbdc35351d247f039749de955/pyobjc_framework_accessibility-12.1.tar.gz", hash = "sha256:5ff362c3425edc242d49deec11f5f3e26e565cefb6a2872eda59ab7362149772", size = 29800, upload-time = "2025-11-14T10:08:31.949Z" } wheels = [ @@ -2056,8 +2053,8 @@ name = "pyobjc-framework-accounts" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/65/10/f6fe336c7624d6753c1f6edac102310ce4434d49b548c479e8e6420d4024/pyobjc_framework_accounts-12.1.tar.gz", hash = "sha256:76d62c5e7b831eb8f4c9ca6abaf79d9ed961dfffe24d89a041fb1de97fe56a3e", size = 15202, upload-time = "2025-11-14T10:08:33.995Z" } wheels = [ @@ -2069,8 +2066,8 @@ name = "pyobjc-framework-addressbook" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/18/28/0404af2a1c6fa8fd266df26fb6196a8f3fb500d6fe3dab94701949247bea/pyobjc_framework_addressbook-12.1.tar.gz", hash = "sha256:c48b740cf981103cef1743d0804a226d86481fcb839bd84b80e9a586187e8000", size = 44359, upload-time = "2025-11-14T10:08:37.687Z" } wheels = [ @@ -2083,8 +2080,8 @@ name = "pyobjc-framework-adservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/04/1c3d3e0a1ac981664f30b33407dcdf8956046ecde6abc88832cf2aa535f4/pyobjc_framework_adservices-12.1.tar.gz", hash = "sha256:7a31fc8d5c6fd58f012db87c89ba581361fc905114bfb912e0a3a87475c02183", size = 11793, upload-time = "2025-11-14T10:08:39.56Z" } wheels = [ @@ -2096,8 +2093,8 @@ name = "pyobjc-framework-adsupport" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/77/f26a2e9994d4df32e9b3680c8014e350b0f1c78d7673b3eba9de2e04816f/pyobjc_framework_adsupport-12.1.tar.gz", hash = "sha256:9a68480e76de567c339dca29a8c739d6d7b5cad30e1cd585ff6e49ec2fc283dd", size = 11645, upload-time = "2025-11-14T10:08:41.439Z" } wheels = [ @@ -2109,8 +2106,8 @@ name = "pyobjc-framework-applescriptkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/f1/e0c07b2a9eb98f1a2050f153d287a52a92f873eeddb41b74c52c144d8767/pyobjc_framework_applescriptkit-12.1.tar.gz", hash = "sha256:cb09f88cf0ad9753dedc02720065818f854b50e33eb4194f0ea34de6d7a3eb33", size = 11451, upload-time = "2025-11-14T10:08:43.328Z" } wheels = [ @@ -2122,8 +2119,8 @@ name = "pyobjc-framework-applescriptobjc" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c0/4b/e4d1592207cbe17355e01828bdd11dd58f31356108f6a49f5e0484a5df50/pyobjc_framework_applescriptobjc-12.1.tar.gz", hash = "sha256:dce080ed07409b0dda2fee75d559bd312ea1ef0243a4338606440f282a6a0f5f", size = 11588, upload-time = "2025-11-14T10:08:45.037Z" } wheels = [ @@ -2135,10 +2132,10 @@ name = "pyobjc-framework-applicationservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coretext", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coretext" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } wheels = [ @@ -2151,8 +2148,8 @@ name = "pyobjc-framework-apptrackingtransparency" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0d/de/f24348982ecab0cb13067c348fc5fbc882c60d704ca290bada9a2b3e594b/pyobjc_framework_apptrackingtransparency-12.1.tar.gz", hash = "sha256:e25bf4e4dfa2d929993ee8e852b28fdf332fa6cde0a33328fdc3b2f502fa50ec", size = 12407, upload-time = "2025-11-14T10:08:54.118Z" } wheels = [ @@ -2164,8 +2161,8 @@ name = "pyobjc-framework-arkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c9/8b/843fe08e696bca8e7fc129344965ab6280f8336f64f01ba0a8862d219c3f/pyobjc_framework_arkit-12.1.tar.gz", hash = "sha256:0c5c6b702926179700b68ba29b8247464c3b609fd002a07a3308e72cfa953adf", size = 35814, upload-time = "2025-11-14T10:08:57.55Z" } wheels = [ @@ -2177,8 +2174,8 @@ name = "pyobjc-framework-audiovideobridging" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9f/51/f81581e7a3c5cb6c9254c6f1e1ee1d614930493761dec491b5b0d49544b9/pyobjc_framework_audiovideobridging-12.1.tar.gz", hash = "sha256:6230ace6bec1f38e8a727c35d054a7be54e039b3053f98e6dd8d08d6baee2625", size = 38457, upload-time = "2025-11-14T10:09:01.122Z" } wheels = [ @@ -2191,8 +2188,8 @@ name = "pyobjc-framework-authenticationservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6c/18/86218de3bf67fc1d810065f353d9df70c740de567ebee8550d476cb23862/pyobjc_framework_authenticationservices-12.1.tar.gz", hash = "sha256:cef71faeae2559f5c0ff9a81c9ceea1c81108e2f4ec7de52a98c269feff7a4b6", size = 58683, upload-time = "2025-11-14T10:09:06.003Z" } wheels = [ @@ -2205,8 +2202,8 @@ name = "pyobjc-framework-automaticassessmentconfiguration" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/24/080afe8189c47c4bb3daa191ccfd962400ca31a67c14b0f7c2d002c2e249/pyobjc_framework_automaticassessmentconfiguration-12.1.tar.gz", hash = "sha256:2b732c02d9097682ca16e48f5d3b10056b740bc091e217ee4d5715194c8970b1", size = 21895, upload-time = "2025-11-14T10:09:08.779Z" } wheels = [ @@ -2219,8 +2216,8 @@ name = "pyobjc-framework-automator" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/08/362bf6ac2bba393c46cf56078d4578b692b56857c385e47690637a72f0dd/pyobjc_framework_automator-12.1.tar.gz", hash = "sha256:7491a99347bb30da3a3f744052a03434ee29bee3e2ae520576f7e796740e4ba7", size = 186068, upload-time = "2025-11-14T10:09:20.82Z" } wheels = [ @@ -2233,11 +2230,11 @@ name = "pyobjc-framework-avfoundation" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/42/c026ab308edc2ed5582d8b4b93da6b15d1b6557c0086914a4aabedd1f032/pyobjc_framework_avfoundation-12.1.tar.gz", hash = "sha256:eda0bb60be380f9ba2344600c4231dd58a3efafa99fdc65d3673ecfbb83f6fcb", size = 310047, upload-time = "2025-11-14T10:09:40.069Z" } wheels = [ @@ -2250,9 +2247,9 @@ name = "pyobjc-framework-avkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/34/a9/e44db1a1f26e2882c140f1d502d508b1f240af9048909dcf1e1a687375b4/pyobjc_framework_avkit-12.1.tar.gz", hash = "sha256:a5c0ddb0cb700f9b09c8afeca2c58952d554139e9bb078236d2355b1fddfb588", size = 28473, upload-time = "2025-11-14T10:09:43.105Z" } wheels = [ @@ -2265,8 +2262,8 @@ name = "pyobjc-framework-avrouting" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6e/83/15bf6c28ec100dae7f92d37c9e117b3b4ee6b4873db062833e16f1cfd6c4/pyobjc_framework_avrouting-12.1.tar.gz", hash = "sha256:6a6c5e583d14f6501df530a9d0559a32269a821fc8140e3646015f097155cd1c", size = 20031, upload-time = "2025-11-14T10:09:45.701Z" } wheels = [ @@ -2279,8 +2276,8 @@ name = "pyobjc-framework-backgroundassets" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/34/d1/e917fba82790495152fd3508c5053827658881cf7e9887ba60def5e3f221/pyobjc_framework_backgroundassets-12.1.tar.gz", hash = "sha256:8da34df9ae4519c360c429415477fdaf3fbba5addbc647b3340b8783454eb419", size = 26210, upload-time = "2025-11-14T10:09:48.792Z" } wheels = [ @@ -2293,11 +2290,11 @@ name = "pyobjc-framework-browserenginekit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5d/b9/39f9de1730e6f8e73be0e4f0c6087cd9439cbe11645b8052d22e1fb8e69b/pyobjc_framework_browserenginekit-12.1.tar.gz", hash = "sha256:6a1a34a155778ab55ab5f463e885f2a3b4680231264e1fe078e62ddeccce49ed", size = 29120, upload-time = "2025-11-14T10:09:51.582Z" } wheels = [ @@ -2310,8 +2307,8 @@ name = "pyobjc-framework-businesschat" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/da/bc09b6ed19e9ea38ecca9387c291ca11fa680a8132d82b27030f82551c23/pyobjc_framework_businesschat-12.1.tar.gz", hash = "sha256:f6fa3a8369a1a51363e1757530128741d9d09ed90692a1d6777a4c0fbad25868", size = 12055, upload-time = "2025-11-14T10:09:53.436Z" } wheels = [ @@ -2323,8 +2320,8 @@ name = "pyobjc-framework-calendarstore" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/88/41/ae955d1c44dcc18b5b9df45c679e9a08311a0f853b9d981bca760cf1eef2/pyobjc_framework_calendarstore-12.1.tar.gz", hash = "sha256:f9a798d560a3c99ad4c0d2af68767bc5695d8b1aabef04d8377861cd1d6d1670", size = 52272, upload-time = "2025-11-14T10:09:58.48Z" } wheels = [ @@ -2336,8 +2333,8 @@ name = "pyobjc-framework-callkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1a/c0/1859d4532d39254df085309aff55b85323576f00a883626325af40da4653/pyobjc_framework_callkit-12.1.tar.gz", hash = "sha256:fd6dc9688b785aab360139d683be56f0844bf68bf5e45d0eb770cb68221083cc", size = 29171, upload-time = "2025-11-14T10:10:01.336Z" } wheels = [ @@ -2350,8 +2347,8 @@ name = "pyobjc-framework-carbon" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0c/0f/9ab8e518a4e5ac4a1e2fdde38a054c32aef82787ff7f30927345c18b7765/pyobjc_framework_carbon-12.1.tar.gz", hash = "sha256:57a72807db252d5746caccc46da4bd20ff8ea9e82109af9f72735579645ff4f0", size = 37293, upload-time = "2025-11-14T10:10:04.464Z" } wheels = [ @@ -2363,8 +2360,8 @@ name = "pyobjc-framework-cfnetwork" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d2/6a/f5f0f191956e187db85312cbffcc41bf863670d121b9190b4a35f0d36403/pyobjc_framework_cfnetwork-12.1.tar.gz", hash = "sha256:2d16e820f2d43522c793f55833fda89888139d7a84ca5758548ba1f3a325a88d", size = 44383, upload-time = "2025-11-14T10:10:08.428Z" } wheels = [ @@ -2377,11 +2374,11 @@ name = "pyobjc-framework-cinematic" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-metal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/67/4e/f4cc7f9f7f66df0290c90fe445f1ff5aa514c6634f5203fe049161053716/pyobjc_framework_cinematic-12.1.tar.gz", hash = "sha256:795068c30447548c0e8614e9c432d4b288b13d5614622ef2f9e3246132329b06", size = 21215, upload-time = "2025-11-14T10:10:10.795Z" } wheels = [ @@ -2393,8 +2390,8 @@ name = "pyobjc-framework-classkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ac/ef/67815278023b344a79c7e95f748f647245d6f5305136fc80615254ad447c/pyobjc_framework_classkit-12.1.tar.gz", hash = "sha256:8d1e9dd75c3d14938ff533d88b72bca2d34918e4461f418ea323bfb2498473b4", size = 26298, upload-time = "2025-11-14T10:10:13.406Z" } wheels = [ @@ -2407,11 +2404,11 @@ name = "pyobjc-framework-cloudkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-accounts", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-accounts" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coredata" }, + { name = "pyobjc-framework-corelocation" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2d/09/762ee4f3ae8568b8e0e5392c705bc4aa1929aa454646c124ca470f1bf9fc/pyobjc_framework_cloudkit-12.1.tar.gz", hash = "sha256:1dddd38e60863f88adb3d1d37d3b4ccb9cbff48c4ef02ab50e36fa40c2379d2f", size = 53730, upload-time = "2025-11-14T10:10:17.831Z" } wheels = [ @@ -2423,7 +2420,7 @@ name = "pyobjc-framework-cocoa" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, ] sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } wheels = [ @@ -2436,8 +2433,8 @@ name = "pyobjc-framework-collaboration" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/64/21/77fe64b39eae98412de1a0d33e9c735aa9949d53fff6b2d81403572b410b/pyobjc_framework_collaboration-12.1.tar.gz", hash = "sha256:2afa264d3233fc0a03a56789c6fefe655ffd81a2da4ba1dc79ea0c45931ad47b", size = 14299, upload-time = "2025-11-14T10:13:04.631Z" } wheels = [ @@ -2449,8 +2446,8 @@ name = "pyobjc-framework-colorsync" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c0/b4/706e4cc9db25b400201fc90f3edfaa1ab2d51b400b19437b043a68532078/pyobjc_framework_colorsync-12.1.tar.gz", hash = "sha256:d69dab7df01245a8c1bd536b9231c97993a5d1a2765d77692ce40ebbe6c1b8e9", size = 25269, upload-time = "2025-11-14T10:13:07.522Z" } wheels = [ @@ -2462,9 +2459,9 @@ name = "pyobjc-framework-compositorservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-metal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/54/c5/0ba31d7af7e464b7f7ece8c2bd09112bdb0b7260848402e79ba6aacc622c/pyobjc_framework_compositorservices-12.1.tar.gz", hash = "sha256:028e357bbee7fbd3723339a321bbe14e6da5a772708a661a13eea5f17c89e4ab", size = 23292, upload-time = "2025-11-14T10:13:10.392Z" } wheels = [ @@ -2476,8 +2473,8 @@ name = "pyobjc-framework-contacts" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4b/a0/ce0542d211d4ea02f5cbcf72ee0a16b66b0d477a4ba5c32e00117703f2f0/pyobjc_framework_contacts-12.1.tar.gz", hash = "sha256:89bca3c5cf31404b714abaa1673577e1aaad6f2ef49d4141c6dbcc0643a789ad", size = 42378, upload-time = "2025-11-14T10:13:14.203Z" } wheels = [ @@ -2490,9 +2487,9 @@ name = "pyobjc-framework-contactsui" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-contacts", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-contacts" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/0c/7bb7f898456a81d88d06a1084a42e374519d2e40a668a872b69b11f8c1f9/pyobjc_framework_contactsui-12.1.tar.gz", hash = "sha256:aaeca7c9e0c9c4e224d73636f9a558f9368c2c7422155a41fd4d7a13613a77c1", size = 18769, upload-time = "2025-11-14T10:13:16.301Z" } wheels = [ @@ -2505,8 +2502,8 @@ name = "pyobjc-framework-coreaudio" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/84/d1/0b884c5564ab952ff5daa949128c64815300556019c1bba0cf2ca752a1a0/pyobjc_framework_coreaudio-12.1.tar.gz", hash = "sha256:a9e72925fcc1795430496ce0bffd4ddaa92c22460a10308a7283ade830089fe1", size = 75077, upload-time = "2025-11-14T10:13:22.345Z" } wheels = [ @@ -2519,9 +2516,9 @@ name = "pyobjc-framework-coreaudiokit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreaudio", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreaudio" }, ] sdist = { url = "https://files.pythonhosted.org/packages/41/1c/5c7e39b9361d4eec99b9115b593edd9825388acd594cb3b4519f8f1ac12c/pyobjc_framework_coreaudiokit-12.1.tar.gz", hash = "sha256:b83624f8de3068ab2ca279f786be0804da5cf904ff9979d96007b69ef4869e1e", size = 20137, upload-time = "2025-11-14T10:13:24.611Z" } wheels = [ @@ -2534,8 +2531,8 @@ name = "pyobjc-framework-corebluetooth" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4b/25/d21d6cb3fd249c2c2aa96ee54279f40876a0c93e7161b3304bf21cbd0bfe/pyobjc_framework_corebluetooth-12.1.tar.gz", hash = "sha256:8060c1466d90bbb9100741a1091bb79975d9ba43911c9841599879fc45c2bbe0", size = 33157, upload-time = "2025-11-14T10:13:28.064Z" } wheels = [ @@ -2548,8 +2545,8 @@ name = "pyobjc-framework-coredata" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3e/c5/8cd46cd4f1b7cf88bdeed3848f830ea9cdcc4e55cd0287a968a2838033fb/pyobjc_framework_coredata-12.1.tar.gz", hash = "sha256:1e47d3c5e51fdc87a90da62b97cae1bc49931a2bb064db1305827028e1fc0ffa", size = 124348, upload-time = "2025-11-14T10:13:36.435Z" } wheels = [ @@ -2562,8 +2559,8 @@ name = "pyobjc-framework-corehaptics" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3a/2f/74a3da79d9188b05dd4be4428a819ea6992d4dfaedf7d629027cf1f57bfc/pyobjc_framework_corehaptics-12.1.tar.gz", hash = "sha256:521dd2986c8a4266d583dd9ed9ae42053b11ae7d3aa89bf53fbee88307d8db10", size = 22164, upload-time = "2025-11-14T10:13:38.941Z" } wheels = [ @@ -2575,8 +2572,8 @@ name = "pyobjc-framework-corelocation" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/79/b75885e0d75397dc2fe1ed9ca80be2b64c18b817f5fb924277cb1bf7b163/pyobjc_framework_corelocation-12.1.tar.gz", hash = "sha256:3674e9353f949d91dde6230ad68f6d5748a7f0424751e08a2c09d06050d66231", size = 53511, upload-time = "2025-11-14T10:13:43.384Z" } wheels = [ @@ -2589,8 +2586,8 @@ name = "pyobjc-framework-coremedia" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/da/7d/5ad600ff7aedfef8ba8f51b11d9aaacdf247b870bd14045d6e6f232e3df9/pyobjc_framework_coremedia-12.1.tar.gz", hash = "sha256:166c66a9c01e7a70103f3ca44c571431d124b9070612ef63a1511a4e6d9d84a7", size = 89566, upload-time = "2025-11-14T10:13:49.788Z" } wheels = [ @@ -2603,8 +2600,8 @@ name = "pyobjc-framework-coremediaio" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/08/8e/23baee53ccd6c011c965cff62eb55638b4088c3df27d2bf05004105d6190/pyobjc_framework_coremediaio-12.1.tar.gz", hash = "sha256:880b313b28f00b27775d630174d09e0b53d1cdbadb74216618c9dd5b3eb6806a", size = 51100, upload-time = "2025-11-14T10:13:54.277Z" } wheels = [ @@ -2617,8 +2614,8 @@ name = "pyobjc-framework-coremidi" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/75/96/2d583060a71a73c8a7e6d92f2a02675621b63c1f489f2639e020fae34792/pyobjc_framework_coremidi-12.1.tar.gz", hash = "sha256:3c6f1fd03997c3b0f20ab8545126b1ce5f0cddcc1587dffacad876c161da8c54", size = 55587, upload-time = "2025-11-14T10:13:58.903Z" } wheels = [ @@ -2631,8 +2628,8 @@ name = "pyobjc-framework-coreml" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } wheels = [ @@ -2645,8 +2642,8 @@ name = "pyobjc-framework-coremotion" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2c/eb/abef7d405670cf9c844befc2330a46ee59f6ff7bac6f199bf249561a2ca6/pyobjc_framework_coremotion-12.1.tar.gz", hash = "sha256:8e1b094d34084cc8cf07bedc0630b4ee7f32b0215011f79c9e3cd09d205a27c7", size = 33851, upload-time = "2025-11-14T10:14:05.619Z" } wheels = [ @@ -2659,9 +2656,9 @@ name = "pyobjc-framework-coreservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fsevents", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-fsevents" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4c/b3/52338a3ff41713f7d7bccaf63bef4ba4a8f2ce0c7eaff39a3629d022a79a/pyobjc_framework_coreservices-12.1.tar.gz", hash = "sha256:fc6a9f18fc6da64c166fe95f2defeb7ac8a9836b3b03bb6a891d36035260dbaa", size = 366150, upload-time = "2025-11-14T10:14:28.133Z" } wheels = [ @@ -2674,8 +2671,8 @@ name = "pyobjc-framework-corespotlight" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/99/d0/88ca73b0cf23847af463334989dd8f98e44f801b811e7e1d8a5627ec20b4/pyobjc_framework_corespotlight-12.1.tar.gz", hash = "sha256:57add47380cd0bbb9793f50a4a4b435a90d4ebd2a33698e058cb353ddfb0d068", size = 38002, upload-time = "2025-11-14T10:14:31.948Z" } wheels = [ @@ -2688,9 +2685,9 @@ name = "pyobjc-framework-coretext" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } wheels = [ @@ -2703,8 +2700,8 @@ name = "pyobjc-framework-corewlan" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/88/71/739a5d023566b506b3fd3d2412983faa95a8c16226c0dcd0f67a9294a342/pyobjc_framework_corewlan-12.1.tar.gz", hash = "sha256:a9d82ec71ef61f37e1d611caf51a4203f3dbd8caf827e98128a1afaa0fd2feb5", size = 32417, upload-time = "2025-11-14T10:14:41.921Z" } wheels = [ @@ -2717,8 +2714,8 @@ name = "pyobjc-framework-cryptotokenkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6b/7c/d03ff4f74054578577296f33bc669fce16c7827eb1a553bb372b5aab30ca/pyobjc_framework_cryptotokenkit-12.1.tar.gz", hash = "sha256:c95116b4b7a41bf5b54aff823a4ef6f4d9da4d0441996d6d2c115026a42d82f5", size = 32716, upload-time = "2025-11-14T10:14:45.024Z" } wheels = [ @@ -2731,8 +2728,8 @@ name = "pyobjc-framework-datadetection" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/db/97/9b03832695ec4d3008e6150ddfdc581b0fda559d9709a98b62815581259a/pyobjc_framework_datadetection-12.1.tar.gz", hash = "sha256:95539e46d3bc970ce890aa4a97515db10b2690597c5dd362996794572e5d5de0", size = 12323, upload-time = "2025-11-14T10:14:46.769Z" } wheels = [ @@ -2744,8 +2741,8 @@ name = "pyobjc-framework-devicecheck" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cd/af/c676107c40d51f55d0a42043865d7246db821d01241b518ea1d3b3ef1394/pyobjc_framework_devicecheck-12.1.tar.gz", hash = "sha256:567e85fc1f567b3fe64ac1cdc323d989509331f64ee54fbcbde2001aec5adbdb", size = 12885, upload-time = "2025-11-14T10:14:48.804Z" } wheels = [ @@ -2757,8 +2754,8 @@ name = "pyobjc-framework-devicediscoveryextension" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/91/b0/e6e2ed6a7f4b689746818000a003ff7ab9c10945df66398ae8d323ae9579/pyobjc_framework_devicediscoveryextension-12.1.tar.gz", hash = "sha256:60e12445fad97ff1f83472255c943685a8f3a9d95b3126d887cfe769b7261044", size = 14718, upload-time = "2025-11-14T10:14:50.723Z" } wheels = [ @@ -2770,8 +2767,8 @@ name = "pyobjc-framework-dictionaryservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-coreservices" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7a/c0/daf03cdaf6d4e04e0cf164db358378c07facd21e4e3f8622505d72573e2c/pyobjc_framework_dictionaryservices-12.1.tar.gz", hash = "sha256:354158f3c55d66681fa903c7b3cb05a435b717fa78d0cef44d258d61156454a7", size = 10573, upload-time = "2025-11-14T10:14:53.961Z" } wheels = [ @@ -2783,8 +2780,8 @@ name = "pyobjc-framework-discrecording" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c4/87/8bd4544793bfcdf507174abddd02b1f077b48fab0004b3db9a63142ce7e9/pyobjc_framework_discrecording-12.1.tar.gz", hash = "sha256:6defc8ea97fb33b4d43870c673710c04c3dc48be30cdf78ba28191a922094990", size = 55607, upload-time = "2025-11-14T10:14:58.276Z" } wheels = [ @@ -2797,9 +2794,9 @@ name = "pyobjc-framework-discrecordingui" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-discrecording", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-discrecording" }, ] sdist = { url = "https://files.pythonhosted.org/packages/30/63/8667f5bb1ecb556add04e86b278cb358dc1f2f03862705cae6f09097464c/pyobjc_framework_discrecordingui-12.1.tar.gz", hash = "sha256:6793d4a1a7f3219d063f39d87f1d4ebbbb3347e35d09194a193cfe16cba718a8", size = 16450, upload-time = "2025-11-14T10:15:00.254Z" } wheels = [ @@ -2811,8 +2808,8 @@ name = "pyobjc-framework-diskarbitration" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3a/42/f75fcabec1a0033e4c5235cc8225773f610321d565b63bf982c10c6bbee4/pyobjc_framework_diskarbitration-12.1.tar.gz", hash = "sha256:6703bc5a09b38a720c9ffca356b58f7e99fa76fc988c9ec4d87112344e63dfc2", size = 17121, upload-time = "2025-11-14T10:15:02.223Z" } wheels = [ @@ -2824,8 +2821,8 @@ name = "pyobjc-framework-dvdplayback" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cf/dd/7859a58e8dd336c77f83feb76d502e9623c394ea09322e29a03f5bc04d32/pyobjc_framework_dvdplayback-12.1.tar.gz", hash = "sha256:279345d4b5fb2c47dd8e5c2fd289e644b6648b74f5c25079805eeb61bfc4a9cd", size = 32332, upload-time = "2025-11-14T10:15:05.257Z" } wheels = [ @@ -2837,8 +2834,8 @@ name = "pyobjc-framework-eventkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b6/42/4ec97e641fdcf30896fe76476181622954cb017117b1429f634d24816711/pyobjc_framework_eventkit-12.1.tar.gz", hash = "sha256:7c1882be2f444b1d0f71e9a0cd1e9c04ad98e0261292ab741fc9de0b8bbbbae9", size = 28538, upload-time = "2025-11-14T10:15:07.878Z" } wheels = [ @@ -2850,8 +2847,8 @@ name = "pyobjc-framework-exceptionhandling" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/17/5c9d4164f7ccf6b9100be0ad597a7857395dd58ea492cba4f0e9c0b77049/pyobjc_framework_exceptionhandling-12.1.tar.gz", hash = "sha256:7f0719eeea6695197fce0e7042342daa462683dc466eb6a442aad897032ab00d", size = 16694, upload-time = "2025-11-14T10:15:10.173Z" } wheels = [ @@ -2863,8 +2860,8 @@ name = "pyobjc-framework-executionpolicy" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/95/11/db765e76e7b00e1521d7bb3a61ae49b59e7573ac108da174720e5d96b61b/pyobjc_framework_executionpolicy-12.1.tar.gz", hash = "sha256:682866589365cd01d3a724d8a2781794b5cba1e152411a58825ea52d7b972941", size = 12594, upload-time = "2025-11-14T10:15:12.077Z" } wheels = [ @@ -2876,8 +2873,8 @@ name = "pyobjc-framework-extensionkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/d4/e9b1f74d29ad9dea3d60468d59b80e14ed3a19f9f7a25afcbc10d29c8a1e/pyobjc_framework_extensionkit-12.1.tar.gz", hash = "sha256:773987353e8aba04223dbba3149253db944abfb090c35318b3a770195b75da6d", size = 18694, upload-time = "2025-11-14T10:15:14.104Z" } wheels = [ @@ -2890,8 +2887,8 @@ name = "pyobjc-framework-externalaccessory" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8e/35/86c097ae2fdf912c61c1276e80f3e090a3fc898c75effdf51d86afec456b/pyobjc_framework_externalaccessory-12.1.tar.gz", hash = "sha256:079f770a115d517a6ab87db1b8a62ca6cdf6c35ae65f45eecc21b491e78776c0", size = 20958, upload-time = "2025-11-14T10:15:16.419Z" } wheels = [ @@ -2904,8 +2901,8 @@ name = "pyobjc-framework-fileprovider" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cc/9a/724b1fae5709f8860f06a6a2a46de568f9bb8bdb2e2aae45b4e010368f51/pyobjc_framework_fileprovider-12.1.tar.gz", hash = "sha256:45034e0d00ae153c991aa01cb1fd41874650a30093e77ba73401dcce5534c8ad", size = 43071, upload-time = "2025-11-14T10:15:19.989Z" } wheels = [ @@ -2918,8 +2915,8 @@ name = "pyobjc-framework-fileproviderui" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-fileprovider", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-fileprovider" }, ] sdist = { url = "https://files.pythonhosted.org/packages/50/00/234f9b93f75255845df81d9d5ea20cb83ecb5c0a4e59147168b622dd0b9d/pyobjc_framework_fileproviderui-12.1.tar.gz", hash = "sha256:15296429d9db0955abc3242b2920b7a810509a85118dbc185f3ac8234e5a6165", size = 12437, upload-time = "2025-11-14T10:15:22.044Z" } wheels = [ @@ -2931,8 +2928,8 @@ name = "pyobjc-framework-findersync" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/63/c8da472e0910238a905bc48620e005a1b8ae7921701408ca13e5fb0bfb4b/pyobjc_framework_findersync-12.1.tar.gz", hash = "sha256:c513104cef0013c233bf8655b527df665ce6f840c8bc0b3781e996933d4dcfa6", size = 13507, upload-time = "2025-11-14T10:15:24.161Z" } wheels = [ @@ -2944,8 +2941,8 @@ name = "pyobjc-framework-fsevents" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/43/17/21f45d2bca2efc72b975f2dfeae7a163dbeabb1236c1f188578403fd4f09/pyobjc_framework_fsevents-12.1.tar.gz", hash = "sha256:a22350e2aa789dec59b62da869c1b494a429f8c618854b1383d6473f4c065a02", size = 26487, upload-time = "2025-11-14T10:15:26.796Z" } wheels = [ @@ -2958,8 +2955,8 @@ name = "pyobjc-framework-fskit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a1/55/d00246d6e6d9756e129e1d94bc131c99eece2daa84b2696f6442b8a22177/pyobjc_framework_fskit-12.1.tar.gz", hash = "sha256:ec54e941cdb0b7d800616c06ca76a93685bd7119b8aa6eb4e7a3ee27658fc7ba", size = 42372, upload-time = "2025-11-14T10:15:30.411Z" } wheels = [ @@ -2972,8 +2969,8 @@ name = "pyobjc-framework-gamecenter" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d2/f8/b5fd86f6b722d4259228922e125b50e0a6975120a1c4d957e990fb84e42c/pyobjc_framework_gamecenter-12.1.tar.gz", hash = "sha256:de4118f14c9cf93eb0316d49da410faded3609ce9cd63425e9ef878cebb7ea72", size = 31473, upload-time = "2025-11-14T10:15:33.38Z" } wheels = [ @@ -2986,8 +2983,8 @@ name = "pyobjc-framework-gamecontroller" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/14/353bb1fe448cd833839fd199ab26426c0248088753e63c22fe19dc07530f/pyobjc_framework_gamecontroller-12.1.tar.gz", hash = "sha256:64ed3cc4844b67f1faeb540c7cc8d512c84f70b3a4bafdb33d4663a2b2a2b1d8", size = 54554, upload-time = "2025-11-14T10:15:37.591Z" } wheels = [ @@ -3000,9 +2997,9 @@ name = "pyobjc-framework-gamekit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/52/7b/d625c0937557f7e2e64200fdbeb867d2f6f86b2f148b8d6bfe085e32d872/pyobjc_framework_gamekit-12.1.tar.gz", hash = "sha256:014d032c3484093f1409f8f631ba8a0fd2ff7a3ae23fd9d14235340889854c16", size = 63833, upload-time = "2025-11-14T10:15:42.842Z" } wheels = [ @@ -3015,9 +3012,9 @@ name = "pyobjc-framework-gameplaykit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-spritekit", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-spritekit" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e2/11/c310bbc2526f95cce662cc1f1359bb11e2458eab0689737b4850d0f6acb7/pyobjc_framework_gameplaykit-12.1.tar.gz", hash = "sha256:935ebd806d802888969357946245d35a304c530c86f1ffe584e2cf21f0a608a8", size = 41511, upload-time = "2025-11-14T10:15:46.529Z" } wheels = [ @@ -3030,8 +3027,8 @@ name = "pyobjc-framework-gamesave" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/1b/1f/8d05585c844535e75dbc242dd6bdfecfc613d074dcb700362d1c908fb403/pyobjc_framework_gamesave-12.1.tar.gz", hash = "sha256:eb731c97aa644e78a87838ed56d0e5bdbaae125bdc8854a7772394877312cc2e", size = 12654, upload-time = "2025-11-14T10:15:48.344Z" } wheels = [ @@ -3043,8 +3040,8 @@ name = "pyobjc-framework-healthkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/af/67/436630d00ba1028ea33cc9df2fc28e081481433e5075600f2ea1ff00f45e/pyobjc_framework_healthkit-12.1.tar.gz", hash = "sha256:29c5e5de54b41080b7a4b0207698ac6f600dcb9149becc9c6b3a69957e200e5c", size = 91802, upload-time = "2025-11-14T10:15:54.661Z" } wheels = [ @@ -3057,8 +3054,8 @@ name = "pyobjc-framework-imagecapturecore" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6d/a1/39347381fc7d3cd5ab942d86af347b25c73f0ddf6f5227d8b4d8f5328016/pyobjc_framework_imagecapturecore-12.1.tar.gz", hash = "sha256:c4776c59f4db57727389d17e1ffd9c567b854b8db52198b3ccc11281711074e5", size = 46397, upload-time = "2025-11-14T10:15:58.541Z" } wheels = [ @@ -3071,8 +3068,8 @@ name = "pyobjc-framework-inputmethodkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5d/b8/d33dd8b7306029bbbd80525bf833fc547e6a223c494bf69a534487283a28/pyobjc_framework_inputmethodkit-12.1.tar.gz", hash = "sha256:f63b6fe2fa7f1412eae63baea1e120e7865e3b68ccfb7d8b0a4aadb309f2b278", size = 23054, upload-time = "2025-11-14T10:16:01.464Z" } wheels = [ @@ -3085,8 +3082,8 @@ name = "pyobjc-framework-installerplugins" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/60/ca4ab04eafa388a97a521db7d60a812e2f81a3c21c2372587872e6b074f9/pyobjc_framework_installerplugins-12.1.tar.gz", hash = "sha256:1329a193bd2e92a2320a981a9a421a9b99749bade3e5914358923e94fe995795", size = 25277, upload-time = "2025-11-14T10:16:04.379Z" } wheels = [ @@ -3098,9 +3095,9 @@ name = "pyobjc-framework-instantmessage" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d4/67/66754e0d26320ba24a33608ca94d3f38e60ee6b2d2e094cb6269b346fdd4/pyobjc_framework_instantmessage-12.1.tar.gz", hash = "sha256:f453118d5693dc3c94554791bd2aaafe32a8b03b0e3d8ec3934b44b7fdd1f7e7", size = 31217, upload-time = "2025-11-14T10:16:07.693Z" } wheels = [ @@ -3112,8 +3109,8 @@ name = "pyobjc-framework-intents" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/a1/3bab6139e94b97eca098e1562f5d6840e3ff10ea1f7fd704a17111a97d5b/pyobjc_framework_intents-12.1.tar.gz", hash = "sha256:bd688c3ab34a18412f56e459e9dae29e1f4152d3c2048fcacdef5fc49dfb9765", size = 132262, upload-time = "2025-11-14T10:16:16.428Z" } wheels = [ @@ -3126,8 +3123,8 @@ name = "pyobjc-framework-intentsui" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-intents", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-intents" }, ] sdist = { url = "https://files.pythonhosted.org/packages/19/cf/f0e385b9cfbf153d68efe8d19e5ae672b59acbbfc1f9b58faaefc5ec8c9e/pyobjc_framework_intentsui-12.1.tar.gz", hash = "sha256:16bdf4b7b91c0d1ec9d5513a1182861f1b5b7af95d4f4218ff7cf03032d57f99", size = 19784, upload-time = "2025-11-14T10:16:18.716Z" } wheels = [ @@ -3140,8 +3137,8 @@ name = "pyobjc-framework-iobluetooth" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e4/aa/ca3944bbdfead4201b4ae6b51510942c5a7d8e5e2dc3139a071c74061fdf/pyobjc_framework_iobluetooth-12.1.tar.gz", hash = "sha256:8a434118812f4c01dfc64339d41fe8229516864a59d2803e9094ee4cbe2b7edd", size = 155241, upload-time = "2025-11-14T10:16:28.896Z" } wheels = [ @@ -3154,8 +3151,8 @@ name = "pyobjc-framework-iobluetoothui" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-iobluetooth", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-iobluetooth" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8f/39/31d9a4e8565a4b1ec0a9ad81480dc0879f3df28799eae3bc22d1dd53705d/pyobjc_framework_iobluetoothui-12.1.tar.gz", hash = "sha256:81f8158bdfb2966a574b6988eb346114d6a4c277300c8c0a978c272018184e6f", size = 16495, upload-time = "2025-11-14T10:16:31.212Z" } wheels = [ @@ -3167,8 +3164,8 @@ name = "pyobjc-framework-iosurface" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/07/61/0f12ad67a72d434e1c84b229ec760b5be71f53671ee9018593961c8bfeb7/pyobjc_framework_iosurface-12.1.tar.gz", hash = "sha256:4b9d0c66431aa296f3ca7c4f84c00dc5fc961194830ad7682fdbbc358fa0db55", size = 17690, upload-time = "2025-11-14T10:16:33.282Z" } wheels = [ @@ -3180,8 +3177,8 @@ name = "pyobjc-framework-ituneslibrary" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f5/46/d9bcec88675bf4ee887b9707bd245e2a793e7cb916cf310f286741d54b1f/pyobjc_framework_ituneslibrary-12.1.tar.gz", hash = "sha256:7f3aa76c4d05f6fa6015056b88986cacbda107c3f29520dd35ef0936c7367a6e", size = 23730, upload-time = "2025-11-14T10:16:36.127Z" } wheels = [ @@ -3193,8 +3190,8 @@ name = "pyobjc-framework-kernelmanagement" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0a/7e/ecbac119866e8ac2cce700d7a48a4297946412ac7cbc243a7084a6582fb1/pyobjc_framework_kernelmanagement-12.1.tar.gz", hash = "sha256:488062893ac2074e0c8178667bf864a21f7909c11111de2f6a10d9bc579df59d", size = 11773, upload-time = "2025-11-14T10:16:38.216Z" } wheels = [ @@ -3206,8 +3203,8 @@ name = "pyobjc-framework-latentsemanticmapping" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/88/3c/b621dac54ae8e77ac25ee75dd93e310e2d6e0faaf15b8da13513258d6657/pyobjc_framework_latentsemanticmapping-12.1.tar.gz", hash = "sha256:f0b1fa823313eefecbf1539b4ed4b32461534b7a35826c2cd9f6024411dc9284", size = 15526, upload-time = "2025-11-14T10:16:40.149Z" } wheels = [ @@ -3219,8 +3216,8 @@ name = "pyobjc-framework-launchservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-coreservices" }, ] sdist = { url = "https://files.pythonhosted.org/packages/37/d0/24673625922b0ad21546be5cf49e5ec1afaa4553ae92f222adacdc915907/pyobjc_framework_launchservices-12.1.tar.gz", hash = "sha256:4d2d34c9bd6fb7f77566155b539a2c70283d1f0326e1695da234a93ef48352dc", size = 20470, upload-time = "2025-11-14T10:16:42.499Z" } wheels = [ @@ -3232,8 +3229,8 @@ name = "pyobjc-framework-libdispatch" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/26/e8/75b6b9b3c88b37723c237e5a7600384ea2d84874548671139db02e76652b/pyobjc_framework_libdispatch-12.1.tar.gz", hash = "sha256:4035535b4fae1b5e976f3e0e38b6e3442ffea1b8aa178d0ca89faa9b8ecdea41", size = 38277, upload-time = "2025-11-14T10:16:46.235Z" } wheels = [ @@ -3246,8 +3243,8 @@ name = "pyobjc-framework-libxpc" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/16/e4/364db7dc26f235e3d7eaab2f92057f460b39800bffdec3128f113388ac9f/pyobjc_framework_libxpc-12.1.tar.gz", hash = "sha256:e46363a735f3ecc9a2f91637750623f90ee74f9938a4e7c833e01233174af44d", size = 35186, upload-time = "2025-11-14T10:16:49.503Z" } wheels = [ @@ -3260,9 +3257,9 @@ name = "pyobjc-framework-linkpresentation" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e3/58/c0c5919d883485ccdb6dccd8ecfe50271d2f6e6ab7c9b624789235ccec5a/pyobjc_framework_linkpresentation-12.1.tar.gz", hash = "sha256:84df6779591bb93217aa8bd82c10e16643441678547d2d73ba895475a02ade94", size = 13330, upload-time = "2025-11-14T10:16:52.169Z" } wheels = [ @@ -3274,9 +3271,9 @@ name = "pyobjc-framework-localauthentication" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8d/0e/7e5d9a58bb3d5b79a75d925557ef68084171526191b1c0929a887a553d4f/pyobjc_framework_localauthentication-12.1.tar.gz", hash = "sha256:2284f587d8e1206166e4495b33f420c1de486c36c28c4921d09eec858a699d05", size = 29947, upload-time = "2025-11-14T10:16:54.923Z" } wheels = [ @@ -3289,9 +3286,9 @@ name = "pyobjc-framework-localauthenticationembeddedui" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-localauthentication", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-localauthentication" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/20/83ab4180e29b9a4a44d735c7f88909296c6adbe6250e8e00a156aff753e1/pyobjc_framework_localauthenticationembeddedui-12.1.tar.gz", hash = "sha256:a15ec44bf2769c872e86c6b550b6dd4f58d4eda40ad9ff00272a67d279d1d4e9", size = 13611, upload-time = "2025-11-14T10:16:57.145Z" } wheels = [ @@ -3303,8 +3300,8 @@ name = "pyobjc-framework-mailkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2a/98/3d9028620c1cd32ff4fb031155aba3b5511e980cdd114dd51383be9cb51b/pyobjc_framework_mailkit-12.1.tar.gz", hash = "sha256:d5574b7259baec17096410efcaacf5d45c7bb5f893d4c25cbb7072369799b652", size = 20996, upload-time = "2025-11-14T10:16:59.449Z" } wheels = [ @@ -3316,10 +3313,10 @@ name = "pyobjc-framework-mapkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-corelocation", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-corelocation" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/36/bb/2a668203c20e509a648c35e803d79d0c7f7816dacba74eb5ad8acb186790/pyobjc_framework_mapkit-12.1.tar.gz", hash = "sha256:dbc32dc48e821aaa9b4294402c240adbc1c6834e658a07677b7c19b7990533c5", size = 63520, upload-time = "2025-11-14T10:17:04.221Z" } wheels = [ @@ -3332,8 +3329,8 @@ name = "pyobjc-framework-mediaaccessibility" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e0/10/dc1007e56944ed2e981e69e7b2fed2b2202c79b0d5b742b29b1081d1cbdd/pyobjc_framework_mediaaccessibility-12.1.tar.gz", hash = "sha256:cc4e3b1d45e84133d240318d53424eff55968f5c6873c2c53267598853445a3f", size = 16325, upload-time = "2025-11-14T10:17:07.454Z" } wheels = [ @@ -3345,10 +3342,10 @@ name = "pyobjc-framework-mediaextension" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d6/aa/1e8015711df1cdb5e4a0aa0ed4721409d39971ae6e1e71915e3ab72423a3/pyobjc_framework_mediaextension-12.1.tar.gz", hash = "sha256:44409d63cc7d74e5724a68e3f9252cb62fd0fd3ccf0ca94c6a33e5c990149953", size = 39425, upload-time = "2025-11-14T10:17:11.486Z" } wheels = [ @@ -3361,9 +3358,9 @@ name = "pyobjc-framework-medialibrary" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/34/e9/848ebd02456f8fdb41b42298ec585bfed5899dbd30306ea5b0a7e4c4b341/pyobjc_framework_medialibrary-12.1.tar.gz", hash = "sha256:690dcca09b62511df18f58e8566cb33d9652aae09fe63a83f594bd018b5edfcd", size = 15995, upload-time = "2025-11-14T10:17:15.45Z" } wheels = [ @@ -3375,8 +3372,8 @@ name = "pyobjc-framework-mediaplayer" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e9/f0/851f6f47e11acbd62d5f5dcb8274afc969135e30018591f75bf3cbf6417f/pyobjc_framework_mediaplayer-12.1.tar.gz", hash = "sha256:5ef3f669bdf837d87cdb5a486ec34831542360d14bcba099c7c2e0383380794c", size = 35402, upload-time = "2025-11-14T10:17:18.97Z" } wheels = [ @@ -3388,8 +3385,8 @@ name = "pyobjc-framework-mediatoolbox" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a3/71/be5879380a161f98212a336b432256f307d1dcbaaaeb8ec988aea2ada2cd/pyobjc_framework_mediatoolbox-12.1.tar.gz", hash = "sha256:385b48746a5f08756ee87afc14037e552954c427ed5745d7ece31a21a7bad5ab", size = 22305, upload-time = "2025-11-14T10:17:22.501Z" } wheels = [ @@ -3402,8 +3399,8 @@ name = "pyobjc-framework-metal" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/06/a84f7eb8561d5631954b9458cfca04b690b80b5b85ce70642bc89335f52a/pyobjc_framework_metal-12.1.tar.gz", hash = "sha256:bb554877d5ee2bf3f340ad88e8fe1b85baab7b5ec4bd6ae0f4f7604147e3eae7", size = 181847, upload-time = "2025-11-14T10:17:34.157Z" } wheels = [ @@ -3416,8 +3413,8 @@ name = "pyobjc-framework-metalfx" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-metal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f1/09/ce5c74565677fde66de3b9d35389066b19e5d1bfef9d9a4ad80f0c858c0c/pyobjc_framework_metalfx-12.1.tar.gz", hash = "sha256:1551b686fb80083a97879ce0331bdb1d4c9b94557570b7ecc35ebf40ff65c90b", size = 29470, upload-time = "2025-11-14T10:17:37.16Z" } wheels = [ @@ -3430,9 +3427,9 @@ name = "pyobjc-framework-metalkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-metal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/15/5091147aae12d4011a788b93971c3376aaaf9bf32aa935a2c9a06a71e18b/pyobjc_framework_metalkit-12.1.tar.gz", hash = "sha256:14cc5c256f0e3471b412a5b3582cb2a0d36d3d57401a8aa09e433252d1c34824", size = 25473, upload-time = "2025-11-14T10:17:39.721Z" } wheels = [ @@ -3445,8 +3442,8 @@ name = "pyobjc-framework-metalperformanceshaders" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metal", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-metal" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c5/68/58da38e54aa0d8c19f0d3084d8c84e92d54cc8c9254041f07119d86aa073/pyobjc_framework_metalperformanceshaders-12.1.tar.gz", hash = "sha256:b198e755b95a1de1525e63c3b14327ae93ef1d88359e6be1ce554a3493755b50", size = 137301, upload-time = "2025-11-14T10:17:49.554Z" } wheels = [ @@ -3459,8 +3456,8 @@ name = "pyobjc-framework-metalperformanceshadersgraph" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-metalperformanceshaders", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-metalperformanceshaders" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a7/56/7ad0cd085532f7bdea9a8d4e9a2dfde376d26dd21e5eabdf1a366040eff8/pyobjc_framework_metalperformanceshadersgraph-12.1.tar.gz", hash = "sha256:b8fd017b47698037d7b172d41bed7a4835f4c4f2a288235819d200005f89ee35", size = 42992, upload-time = "2025-11-14T10:17:53.502Z" } wheels = [ @@ -3472,8 +3469,8 @@ name = "pyobjc-framework-metrickit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ba/13/5576ddfbc0b174810a49171e2dbe610bdafd3b701011c6ecd9b3a461de8a/pyobjc_framework_metrickit-12.1.tar.gz", hash = "sha256:77841daf6b36ba0c19df88545fd910c0516acf279e6b7b4fa0a712a046eaa9f1", size = 27627, upload-time = "2025-11-14T10:17:56.353Z" } wheels = [ @@ -3486,8 +3483,8 @@ name = "pyobjc-framework-mlcompute" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/69/15f8ce96c14383aa783c8e4bc1e6d936a489343bb197b8e71abb3ddc1cb8/pyobjc_framework_mlcompute-12.1.tar.gz", hash = "sha256:3281db120273dcc56e97becffd5cedf9c62042788289f7be6ea067a863164f1e", size = 40698, upload-time = "2025-11-14T10:17:59.792Z" } wheels = [ @@ -3499,9 +3496,9 @@ name = "pyobjc-framework-modelio" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b4/11/32c358111b623b4a0af9e90470b198fffc068b45acac74e1ba711aee7199/pyobjc_framework_modelio-12.1.tar.gz", hash = "sha256:d041d7bca7c2a4526344d3e593347225b7a2e51a499b3aa548895ba516d1bdbb", size = 66482, upload-time = "2025-11-14T10:18:04.92Z" } wheels = [ @@ -3514,8 +3511,8 @@ name = "pyobjc-framework-multipeerconnectivity" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/87/35/0d0bb6881004cb238cfd7bf74f4b2e42601a1accdf27b2189ec61cf3a2dc/pyobjc_framework_multipeerconnectivity-12.1.tar.gz", hash = "sha256:7123f734b7174cacbe92a51a62b4645cc9033f6b462ff945b504b62e1b9e6c1c", size = 22816, upload-time = "2025-11-14T10:18:07.363Z" } wheels = [ @@ -3528,8 +3525,8 @@ name = "pyobjc-framework-naturallanguage" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8a/d1/c81c0cdbb198d498edc9bc5fbb17e79b796450c17bb7541adbf502f9ad65/pyobjc_framework_naturallanguage-12.1.tar.gz", hash = "sha256:cb27a1e1e5b2913d308c49fcd2fd04ab5ea87cb60cac4a576a91ebf6a50e52f6", size = 23524, upload-time = "2025-11-14T10:18:09.883Z" } wheels = [ @@ -3541,8 +3538,8 @@ name = "pyobjc-framework-netfs" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/46/68/4bf0e5b8cc0780cf7acf0aec54def58c8bcf8d733db0bd38f5a264d1af06/pyobjc_framework_netfs-12.1.tar.gz", hash = "sha256:e8d0c25f41d7d9ced1aa2483238d0a80536df21f4b588640a72e1bdb87e75c1e", size = 14799, upload-time = "2025-11-14T10:18:11.85Z" } wheels = [ @@ -3554,8 +3551,8 @@ name = "pyobjc-framework-network" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/38/13/a71270a1b0a9ec979e68b8ec84b0f960e908b17b51cb3cac246a74d52b6b/pyobjc_framework_network-12.1.tar.gz", hash = "sha256:dbf736ff84d1caa41224e86ff84d34b4e9eb6918ae4e373a44d3cb597648a16a", size = 56990, upload-time = "2025-11-14T10:18:16.714Z" } wheels = [ @@ -3568,8 +3565,8 @@ name = "pyobjc-framework-networkextension" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/bf/3e/ac51dbb2efa16903e6af01f3c1f5a854c558661a7a5375c3e8767ac668e8/pyobjc_framework_networkextension-12.1.tar.gz", hash = "sha256:36abc339a7f214ab6a05cb2384a9df912f247163710741e118662bd049acfa2e", size = 62796, upload-time = "2025-11-14T10:18:21.769Z" } wheels = [ @@ -3582,8 +3579,8 @@ name = "pyobjc-framework-notificationcenter" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c6/12/ae0fe82fb1e02365c9fe9531c9de46322f7af09e3659882212c6bf24d75e/pyobjc_framework_notificationcenter-12.1.tar.gz", hash = "sha256:2d09f5ab9dc39770bae4fa0c7cfe961e6c440c8fc465191d403633dccc941094", size = 21282, upload-time = "2025-11-14T10:18:24.51Z" } wheels = [ @@ -3596,8 +3593,8 @@ name = "pyobjc-framework-opendirectory" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/5b/11/bc2f71d3077b3bd078dccad5c0c5c57ec807fefe3d90c97b97dd0ed3d04b/pyobjc_framework_opendirectory-12.1.tar.gz", hash = "sha256:2c63ce5dd179828ef2d8f9e3961da3bfa971a57db07a6c34eedc296548a928bb", size = 61049, upload-time = "2025-11-14T10:18:29.336Z" } wheels = [ @@ -3609,8 +3606,8 @@ name = "pyobjc-framework-osakit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/cb/b9/bf52c555c75a83aa45782122432fa06066bb76469047f13d06fb31e585c4/pyobjc_framework_osakit-12.1.tar.gz", hash = "sha256:36ea6acf03483dc1e4344a0cce7250a9656f44277d12bc265fa86d4cbde01f23", size = 17102, upload-time = "2025-11-14T10:18:31.354Z" } wheels = [ @@ -3622,10 +3619,10 @@ name = "pyobjc-framework-oslog" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/12/42/805c9b4ac6ad25deb4215989d8fc41533d01e07ffd23f31b65620bade546/pyobjc_framework_oslog-12.1.tar.gz", hash = "sha256:d0ec6f4e3d1689d5e4341bc1130c6f24cb4ad619939f6c14d11a7e80c0ac4553", size = 21193, upload-time = "2025-11-14T10:18:33.645Z" } wheels = [ @@ -3638,8 +3635,8 @@ name = "pyobjc-framework-passkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6c/d4/2afb59fb0f99eb2f03888850887e536f1ef64b303fd756283679471a5189/pyobjc_framework_passkit-12.1.tar.gz", hash = "sha256:d8c27c352e86a3549bf696504e6b25af5f2134b173d9dd60d66c6d3da53bb078", size = 53835, upload-time = "2025-11-14T10:18:37.906Z" } wheels = [ @@ -3652,8 +3649,8 @@ name = "pyobjc-framework-pencilkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7e/43/859068016bcbe7d80597d5c579de0b84b0da62c5c55cdf9cc940e9f9c0f8/pyobjc_framework_pencilkit-12.1.tar.gz", hash = "sha256:d404982d1f7a474369f3e7fea3fbd6290326143fa4138d64b6753005a6263dc4", size = 17664, upload-time = "2025-11-14T10:18:40.045Z" } wheels = [ @@ -3665,8 +3662,8 @@ name = "pyobjc-framework-phase" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-avfoundation", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-avfoundation" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/51/3b25eaf7ca85f38ceef892fdf066b7faa0fec716f35ea928c6ffec6ae311/pyobjc_framework_phase-12.1.tar.gz", hash = "sha256:3a69005c572f6fd777276a835115eb8359a33673d4a87e754209f99583534475", size = 32730, upload-time = "2025-11-14T10:18:43.102Z" } wheels = [ @@ -3678,8 +3675,8 @@ name = "pyobjc-framework-photos" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b8/53/f8a3dc7f711034d2283e289cd966fb7486028ea132a24260290ff32d3525/pyobjc_framework_photos-12.1.tar.gz", hash = "sha256:adb68aaa29e186832d3c36a0b60b0592a834e24c5263e9d78c956b2b77dce563", size = 47034, upload-time = "2025-11-14T10:18:47.27Z" } wheels = [ @@ -3692,8 +3689,8 @@ name = "pyobjc-framework-photosui" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/40/a5/14c538828ed1a420e047388aedc4a2d7d9292030d81bf6b1ced2ec27b6e9/pyobjc_framework_photosui-12.1.tar.gz", hash = "sha256:9141234bb9d17687f1e8b66303158eccdd45132341fbe5e892174910035f029a", size = 29886, upload-time = "2025-11-14T10:18:50.238Z" } wheels = [ @@ -3706,8 +3703,8 @@ name = "pyobjc-framework-preferencepanes" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/90/bc/e87df041d4f7f6b7721bf7996fa02aa0255939fb0fac0ecb294229765f92/pyobjc_framework_preferencepanes-12.1.tar.gz", hash = "sha256:b2a02f9049f136bdeca7642b3307637b190850e5853b74b5c372bc7d88ef9744", size = 24543, upload-time = "2025-11-14T10:18:53.259Z" } wheels = [ @@ -3719,8 +3716,8 @@ name = "pyobjc-framework-pushkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a9/45/de756b62709add6d0615f86e48291ee2bee40223e7dde7bbe68a952593f0/pyobjc_framework_pushkit-12.1.tar.gz", hash = "sha256:829a2fc8f4780e75fc2a41217290ee0ff92d4ade43c42def4d7e5af436d8ae82", size = 19465, upload-time = "2025-11-14T10:18:57.727Z" } wheels = [ @@ -3733,8 +3730,8 @@ name = "pyobjc-framework-quartz" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } wheels = [ @@ -3747,9 +3744,9 @@ name = "pyobjc-framework-quicklookthumbnailing" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/97/1a/b90539500e9a27c2049c388d85a824fc0704009b11e33b05009f52a6dc67/pyobjc_framework_quicklookthumbnailing-12.1.tar.gz", hash = "sha256:4f7e09e873e9bda236dce6e2f238cab571baeb75eca2e0bc0961d5fcd85f3c8f", size = 14790, upload-time = "2025-11-14T10:21:26.442Z" } wheels = [ @@ -3761,8 +3758,8 @@ name = "pyobjc-framework-replaykit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/35/f8/b92af879734d91c1726227e7a03b9e68ab8d9d2bb1716d1a5c29254087f2/pyobjc_framework_replaykit-12.1.tar.gz", hash = "sha256:95801fd35c329d7302b2541f2754e6574bf36547ab869fbbf41e408dfa07268a", size = 23312, upload-time = "2025-11-14T10:21:29.18Z" } wheels = [ @@ -3775,8 +3772,8 @@ name = "pyobjc-framework-safariservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3e/4b/8f896bafbdbfa180a5ba1e21a6f5dc63150c09cba69d85f68708e02866ae/pyobjc_framework_safariservices-12.1.tar.gz", hash = "sha256:6a56f71c1e692bca1f48fe7c40e4c5a41e148b4e3c6cfb185fd80a4d4a951897", size = 25165, upload-time = "2025-11-14T10:21:32.041Z" } wheels = [ @@ -3789,9 +3786,9 @@ name = "pyobjc-framework-safetykit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f4/bf/ad6bf60ceb61614c9c9f5758190971e9b90c45b1c7a244e45db64138b6c2/pyobjc_framework_safetykit-12.1.tar.gz", hash = "sha256:0cd4850659fb9b5632fd8ad21f2de6863e8303ff0d51c5cc9c0034aac5db08d8", size = 20086, upload-time = "2025-11-14T10:21:34.212Z" } wheels = [ @@ -3804,9 +3801,9 @@ name = "pyobjc-framework-scenekit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/94/8c/1f4005cf0cb68f84dd98b93bbc0974ee7851bb33d976791c85e042dc2278/pyobjc_framework_scenekit-12.1.tar.gz", hash = "sha256:1bd5b866f31fd829f26feac52e807ed942254fd248115c7c742cfad41d949426", size = 101212, upload-time = "2025-11-14T10:21:41.265Z" } wheels = [ @@ -3819,9 +3816,9 @@ name = "pyobjc-framework-screencapturekit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, ] sdist = { url = "https://files.pythonhosted.org/packages/2d/7f/73458db1361d2cb408f43821a1e3819318a0f81885f833d78d93bdc698e0/pyobjc_framework_screencapturekit-12.1.tar.gz", hash = "sha256:50992c6128b35ab45d9e336f0993ddd112f58b8c8c8f0892a9cb42d61bd1f4c9", size = 32573, upload-time = "2025-11-14T10:21:44.497Z" } wheels = [ @@ -3834,8 +3831,8 @@ name = "pyobjc-framework-screensaver" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7d/99/7cfbce880cea61253a44eed594dce66c2b2fbf29e37eaedcd40cffa949e9/pyobjc_framework_screensaver-12.1.tar.gz", hash = "sha256:c4ca111317c5a3883b7eace0a9e7dd72bc6ffaa2ca954bdec918c3ab7c65c96f", size = 22229, upload-time = "2025-11-14T10:21:47.299Z" } wheels = [ @@ -3848,8 +3845,8 @@ name = "pyobjc-framework-screentime" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/10/11/ba18f905321895715dac3cae2071c2789745ae13605b283b8114b41e0459/pyobjc_framework_screentime-12.1.tar.gz", hash = "sha256:583de46b365543bbbcf27cd70eedd375d397441d64a2cf43c65286fd9c91af55", size = 13413, upload-time = "2025-11-14T10:21:49.17Z" } wheels = [ @@ -3861,8 +3858,8 @@ name = "pyobjc-framework-scriptingbridge" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0c/cb/adc0a09e8c4755c2281bd12803a87f36e0832a8fc853a2d663433dbb72ce/pyobjc_framework_scriptingbridge-12.1.tar.gz", hash = "sha256:0e90f866a7e6a8aeaf723d04c826657dd528c8c1b91e7a605f8bb947c74ad082", size = 20339, upload-time = "2025-11-14T10:21:51.769Z" } wheels = [ @@ -3875,8 +3872,8 @@ name = "pyobjc-framework-searchkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreservices", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-coreservices" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6e/60/a38523198430e14fdef21ebe62a93c43aedd08f1f3a07ea3d96d9997db5d/pyobjc_framework_searchkit-12.1.tar.gz", hash = "sha256:ddd94131dabbbc2d7c3f17db3da87c1a712c431310eef16f07187771e7e85226", size = 30942, upload-time = "2025-11-14T10:21:55.483Z" } wheels = [ @@ -3888,8 +3885,8 @@ name = "pyobjc-framework-security" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } wheels = [ @@ -3902,9 +3899,9 @@ name = "pyobjc-framework-securityfoundation" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, ] sdist = { url = "https://files.pythonhosted.org/packages/57/d5/c2b77e83c1585ba43e5f00c917273ba4bf7ed548c1b691f6766eb0418d52/pyobjc_framework_securityfoundation-12.1.tar.gz", hash = "sha256:1f39f4b3db6e3bd3a420aaf4923228b88e48c90692cf3612b0f6f1573302a75d", size = 12669, upload-time = "2025-11-14T10:22:09.256Z" } wheels = [ @@ -3916,9 +3913,9 @@ name = "pyobjc-framework-securityinterface" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, ] sdist = { url = "https://files.pythonhosted.org/packages/f9/64/bf5b5d82655112a2314422ee649f1e1e73d4381afa87e1651ce7e8444694/pyobjc_framework_securityinterface-12.1.tar.gz", hash = "sha256:deef11ad03be8d9ff77db6e7ac40f6b641ee2d72eaafcf91040537942472e88b", size = 25552, upload-time = "2025-11-14T10:22:12.098Z" } wheels = [ @@ -3931,9 +3928,9 @@ name = "pyobjc-framework-securityui" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-security", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-security" }, ] sdist = { url = "https://files.pythonhosted.org/packages/83/3f/d870305f5dec58cd02966ca06ac29b69fb045d8b46dfb64e2da31f295345/pyobjc_framework_securityui-12.1.tar.gz", hash = "sha256:f1435fed85edc57533c334a4efc8032170424b759da184cb7a7a950ceea0e0b6", size = 12184, upload-time = "2025-11-14T10:22:14.323Z" } wheels = [ @@ -3945,9 +3942,9 @@ name = "pyobjc-framework-sensitivecontentanalysis" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/17bf31753e14cb4d64fffaaba2377453c4977c2c5d3cf2ff0a3db30026c7/pyobjc_framework_sensitivecontentanalysis-12.1.tar.gz", hash = "sha256:2c615ac10e93eb547b32b214cd45092056bee0e79696426fd09978dc3e670f25", size = 13745, upload-time = "2025-11-14T10:22:16.447Z" } wheels = [ @@ -3959,8 +3956,8 @@ name = "pyobjc-framework-servicemanagement" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/d0/b26c83ae96ab55013df5fedf89337d4d62311b56ce3f520fc7597d223d82/pyobjc_framework_servicemanagement-12.1.tar.gz", hash = "sha256:08120981749a698033a1d7a6ab99dbbe412c5c0d40f2b4154014b52113511c1d", size = 14585, upload-time = "2025-11-14T10:22:18.735Z" } wheels = [ @@ -3972,8 +3969,8 @@ name = "pyobjc-framework-sharedwithyou" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-sharedwithyoucore", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-sharedwithyoucore" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0f/8b/8ab209a143c11575a857e2111acc5427fb4986b84708b21324cbcbf5591b/pyobjc_framework_sharedwithyou-12.1.tar.gz", hash = "sha256:167d84794a48f408ee51f885210c616fda1ec4bff3dd8617a4b5547f61b05caf", size = 24791, upload-time = "2025-11-14T10:22:21.248Z" } wheels = [ @@ -3986,8 +3983,8 @@ name = "pyobjc-framework-sharedwithyoucore" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/55/ef/84059c5774fd5435551ab7ab40b51271cfb9997b0d21f491c6b429fe57a8/pyobjc_framework_sharedwithyoucore-12.1.tar.gz", hash = "sha256:0813149eeb755d718b146ec9365eb4ca3262b6af9ff9ba7db2f7b6f4fd104518", size = 22350, upload-time = "2025-11-14T10:22:23.611Z" } wheels = [ @@ -4000,8 +3997,8 @@ name = "pyobjc-framework-shazamkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/2c/8d82c5066cc376de68ad8c1454b7c722c7a62215e5c2f9dac5b33a6c3d42/pyobjc_framework_shazamkit-12.1.tar.gz", hash = "sha256:71db2addd016874639a224ed32b2000b858802b0370c595a283cce27f76883fe", size = 22518, upload-time = "2025-11-14T10:22:25.996Z" } wheels = [ @@ -4014,8 +4011,8 @@ name = "pyobjc-framework-social" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/31/21/afc6f37dfdd2cafcba0227e15240b5b0f1f4ad57621aeefda2985ac9560e/pyobjc_framework_social-12.1.tar.gz", hash = "sha256:1963db6939e92ae40dd9d68852e8f88111cbfd37a83a9fdbc9a0c08993ca7e60", size = 13184, upload-time = "2025-11-14T10:22:28.048Z" } wheels = [ @@ -4027,8 +4024,8 @@ name = "pyobjc-framework-soundanalysis" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/6b/d6/5039b61edc310083425f87ce2363304d3a87617e941c1d07968c63b5638d/pyobjc_framework_soundanalysis-12.1.tar.gz", hash = "sha256:e2deead8b9a1c4513dbdcf703b21650dcb234b60a32d08afcec4895582b040b1", size = 14804, upload-time = "2025-11-14T10:22:29.998Z" } wheels = [ @@ -4040,8 +4037,8 @@ name = "pyobjc-framework-speech" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/8d/3d/194cf19fe7a56c2be5dfc28f42b3b597a62ebb1e1f52a7dd9c55b917ac6c/pyobjc_framework_speech-12.1.tar.gz", hash = "sha256:2a2a546ba6c52d5dd35ddcfee3fd9226a428043d1719597e8701851a6566afdd", size = 25218, upload-time = "2025-11-14T10:22:32.505Z" } wheels = [ @@ -4054,9 +4051,9 @@ name = "pyobjc-framework-spritekit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b6/78/d683ebe0afb49f46d2d21d38c870646e7cb3c2e83251f264e79d357b1b74/pyobjc_framework_spritekit-12.1.tar.gz", hash = "sha256:a851f4ef5aa65cc9e08008644a528e83cb31021a1c0f17ebfce4de343764d403", size = 64470, upload-time = "2025-11-14T10:22:37.569Z" } wheels = [ @@ -4069,8 +4066,8 @@ name = "pyobjc-framework-storekit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/00/87/8a66a145feb026819775d44975c71c1c64df4e5e9ea20338f01456a61208/pyobjc_framework_storekit-12.1.tar.gz", hash = "sha256:818452e67e937a10b5c8451758274faa44ad5d4329df0fa85735115fb0608da9", size = 34574, upload-time = "2025-11-14T10:22:40.73Z" } wheels = [ @@ -4083,8 +4080,8 @@ name = "pyobjc-framework-symbols" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/9a/ce/a48819eb8524fa2dc11fb3dd40bb9c4dcad0596fe538f5004923396c2c6c/pyobjc_framework_symbols-12.1.tar.gz", hash = "sha256:7d8e999b8a59c97d38d1d343b6253b1b7d04bf50b665700957d89c8ac43b9110", size = 12782, upload-time = "2025-11-14T10:22:42.609Z" } wheels = [ @@ -4096,9 +4093,9 @@ name = "pyobjc-framework-syncservices" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coredata", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coredata" }, ] sdist = { url = "https://files.pythonhosted.org/packages/21/91/6d03a988831ddb0fb001b13573560e9a5bcccde575b99350f98fe56a2dd4/pyobjc_framework_syncservices-12.1.tar.gz", hash = "sha256:6a213e93d9ce15128810987e4c5de8c73cfab1564ac8d273e6b437a49965e976", size = 31032, upload-time = "2025-11-14T10:22:45.902Z" } wheels = [ @@ -4111,8 +4108,8 @@ name = "pyobjc-framework-systemconfiguration" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/90/7d/50848df8e1c6b5e13967dee9fb91d3391fe1f2399d2d0797d2fc5edb32ba/pyobjc_framework_systemconfiguration-12.1.tar.gz", hash = "sha256:90fe04aa059876a21626931c71eaff742a27c79798a46347fd053d7008ec496e", size = 59158, upload-time = "2025-11-14T10:22:53.056Z" } wheels = [ @@ -4125,8 +4122,8 @@ name = "pyobjc-framework-systemextensions" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/12/01/8a706cd3f7dfcb9a5017831f2e6f9e5538298e90052db3bb8163230cbc4f/pyobjc_framework_systemextensions-12.1.tar.gz", hash = "sha256:243e043e2daee4b5c46cd90af5fff46b34596aac25011bab8ba8a37099685eeb", size = 20701, upload-time = "2025-11-14T10:22:58.257Z" } wheels = [ @@ -4139,8 +4136,8 @@ name = "pyobjc-framework-threadnetwork" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/62/7e/f1816c3461e4121186f2f7750c58af083d1826bbd73f72728da3edcf4915/pyobjc_framework_threadnetwork-12.1.tar.gz", hash = "sha256:e071eedb41bfc1b205111deb54783ec5a035ccd6929e6e0076336107fdd046ee", size = 12788, upload-time = "2025-11-14T10:23:00.329Z" } wheels = [ @@ -4152,8 +4149,8 @@ name = "pyobjc-framework-uniformtypeidentifiers" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" } wheels = [ @@ -4165,8 +4162,8 @@ name = "pyobjc-framework-usernotifications" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/90/cd/e0253072f221fa89a42fe53f1a2650cc9bf415eb94ae455235bd010ee12e/pyobjc_framework_usernotifications-12.1.tar.gz", hash = "sha256:019ccdf2d400f9a428769df7dba4ea97c02453372bc5f8b75ce7ae54dfe130f9", size = 29749, upload-time = "2025-11-14T10:23:05.364Z" } wheels = [ @@ -4179,9 +4176,9 @@ name = "pyobjc-framework-usernotificationsui" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-usernotifications", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-usernotifications" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/03/73e29fd5e5973cb3800c9d56107c1062547ef7524cbcc757c3cbbd5465c6/pyobjc_framework_usernotificationsui-12.1.tar.gz", hash = "sha256:51381c97c7344099377870e49ed0871fea85ba50efe50ab05ccffc06b43ec02e", size = 13125, upload-time = "2025-11-14T10:23:07.259Z" } wheels = [ @@ -4193,8 +4190,8 @@ name = "pyobjc-framework-videosubscriberaccount" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/10/f8/27927a9c125c622656ee5aada4596ccb8e5679da0260742360f193df6dcf/pyobjc_framework_videosubscriberaccount-12.1.tar.gz", hash = "sha256:750459fa88220ab83416f769f2d5d210a1f77b8938fa4d119aad0002fc32846b", size = 18793, upload-time = "2025-11-14T10:23:09.33Z" } wheels = [ @@ -4206,10 +4203,10 @@ name = "pyobjc-framework-videotoolbox" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coremedia", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coremedia" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/b3/5f/6995ee40dc0d1a3460ee183f696e5254c0ad14a25b5bc5fd9bd7266c077b/pyobjc_framework_videotoolbox-12.1.tar.gz", hash = "sha256:7adc8670f3b94b086aed6e86c3199b388892edab4f02933c2e2d9b1657561bef", size = 57825, upload-time = "2025-11-14T10:23:13.825Z" } wheels = [ @@ -4222,8 +4219,8 @@ name = "pyobjc-framework-virtualization" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/3b/6a/9d110b5521d9b898fad10928818c9f55d66a4af9ac097426c65a9878b095/pyobjc_framework_virtualization-12.1.tar.gz", hash = "sha256:e96afd8e801e92c6863da0921e40a3b68f724804f888bce43791330658abdb0f", size = 40682, upload-time = "2025-11-14T10:23:17.456Z" } wheels = [ @@ -4236,10 +4233,10 @@ name = "pyobjc-framework-vision" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-coreml", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, + { name = "pyobjc-framework-coreml" }, + { name = "pyobjc-framework-quartz" }, ] sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } wheels = [ @@ -4252,8 +4249,8 @@ name = "pyobjc-framework-webkit" version = "12.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-cocoa", marker = "sys_platform == 'darwin'" }, + { name = "pyobjc-core" }, + { name = "pyobjc-framework-cocoa" }, ] sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } wheels = [ @@ -4263,7 +4260,7 @@ wheels = [ [[package]] name = "pyopencl" -version = "2025.2.7" +version = "2026.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, @@ -4271,18 +4268,22 @@ dependencies = [ { name = "pytools" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/cb/8927052160bc0d3bd1123a645aaf57f696da364216b57b49f92ba0777bcc/pyopencl-2025.2.7.tar.gz", hash = "sha256:a68d92eb2970418b1a7ca45aff71984c02d2e4261e01402b273f257b5d6d7511", size = 444787, upload-time = "2025-10-28T14:23:15.497Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/81/fd8a2a695916a82e861bcf17b5b8fd9f81e12c9e5931f9ba536678d7b43a/pyopencl-2026.1.2.tar.gz", hash = "sha256:4397dd0b4cbb8b55f3e09bf87114a2465574506b363890b805b860c348b61970", size = 445132, upload-time = "2026-01-16T22:52:24.765Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/ec/5e511d47fa0d5ad576cd259ceffe4662d6e617dc716044f1ead0d1ba29da/pyopencl-2025.2.7-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:359b484989e9caf532d73206d83422bc0cb8a0e4708c7feeb1f9115752ecda1c", size = 445658, upload-time = "2025-10-28T14:22:41.047Z" }, - { url = "https://files.pythonhosted.org/packages/bd/04/bba1d1ba36ba57eed24ee5407cd2172414792c0a6e89fbf9cc0591e0ba9d/pyopencl-2025.2.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3749a54e124ab72f2cb656aa4049befd300ac31f2125d03c8a1ec42c10f6c22e", size = 427841, upload-time = "2025-10-28T14:22:42.378Z" }, - { url = "https://files.pythonhosted.org/packages/93/cd/ae3ee576c3f296be713e63b2088fdf217ea3c90d7957bbe5bf2ee3165d6a/pyopencl-2025.2.7-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5244cd402f4411832599f3891b557a4be4c3ce98d0165111ab8f8f3638898b94", size = 734543, upload-time = "2025-10-28T14:22:43.614Z" }, - { url = "https://files.pythonhosted.org/packages/d7/30/321d4d174af5e58122e381afac9ce851e9f84f9b7d6502708913a6a1ef8b/pyopencl-2025.2.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c25a874f94cd2126bc526e22e94a2e4def7c82ebd095fed7e3b5708332d2391", size = 1227048, upload-time = "2025-10-28T14:22:44.947Z" }, - { url = "https://files.pythonhosted.org/packages/20/28/7363bc9b124ad5b4fa6c4bcb0b4fc2a8e213e85e3c192af8c31c320796f2/pyopencl-2025.2.7-cp311-cp311-win_amd64.whl", hash = "sha256:0c9989080124f27f44df86f048e55ecd3e376e20933e470cc9c14506c82ee5dd", size = 472884, upload-time = "2025-10-28T14:22:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/f9/04/e3a17fa79266140246c6193934c0526185f31ffbe45f4ecd6a41ba58c42d/pyopencl-2025.2.7-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:3525c7e35369d505b8a6141f4dfcdcc78a9714c0c30f37e82834906a678c992a", size = 447013, upload-time = "2025-10-28T14:22:48.442Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3b/b2d3c4e69063b53ea6728f09dee7220c780e040b159b21e5d5a369c877fe/pyopencl-2025.2.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:91ab33d7844c2712c65f1881dbf10755e76010db01b1b9d041eb3b947a29f994", size = 427543, upload-time = "2025-10-28T14:22:49.956Z" }, - { url = "https://files.pythonhosted.org/packages/24/fd/8f1042308f3369c653443e9b461156cb28f805da41889a34788dbcaf6470/pyopencl-2025.2.7-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:386c1cdad727df58b34ad6026cf3fe1a2254474977a8afdf8d40eb6daf8806bc", size = 732584, upload-time = "2025-10-28T14:22:51.613Z" }, - { url = "https://files.pythonhosted.org/packages/24/2f/7413a39eb56e3f9130232a2964e50ef72b5d6a351af7624225d185c1ed11/pyopencl-2025.2.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ce3643e6367d2c7362a33df4f1d676068ae45db3721bf8aee7d7c60c73065e7", size = 1223000, upload-time = "2025-10-28T14:22:52.976Z" }, - { url = "https://files.pythonhosted.org/packages/08/70/66b371eb8fa3867d139df5e6b7107b611eb1cc8231b7755101f589dda66f/pyopencl-2025.2.7-cp312-cp312-win_amd64.whl", hash = "sha256:0791a7303236f53bcc99ae87515452b2e9cb0303949212cd5e115c4889a44f06", size = 472938, upload-time = "2025-10-28T14:22:54.689Z" }, + { url = "https://files.pythonhosted.org/packages/e3/88/abf34e31d572c59203774a66cd81c1e3b3d60b911241483675151149c6f1/pyopencl-2026.1.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8052e8b402b3ed33ee0807d87d4734f66f67dbafbfb3f5a8b81e478e4d417372", size = 437029, upload-time = "2026-01-16T22:51:30.953Z" }, + { url = "https://files.pythonhosted.org/packages/5c/3d/2dd2d8bbf05a190681582b40fc1ee55b210d00ccebcbb416c62b9f9c81a1/pyopencl-2026.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5e03681c3fe22d5185b16a727d96783e3787e0b65e7a29e4afe01ae0cb4e802", size = 429031, upload-time = "2026-01-16T22:51:32.674Z" }, + { url = "https://files.pythonhosted.org/packages/41/16/e554b3bd20be2e858cfb6683ee6549aeebbe5f769e5b95f561f79340ab20/pyopencl-2026.1.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c8c209d517d1421b17d20b80589a2c39e09ea33350f0367314e1caeed3bc741", size = 689596, upload-time = "2026-01-16T22:51:33.913Z" }, + { url = "https://files.pythonhosted.org/packages/22/a8/1df41cf6c7b25b3bfda14aa0183c6a90eaf849528ba27753eaa25fb26e20/pyopencl-2026.1.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e64e2e34bcfad426bd24b71fdb6b02aa5cb02475147742fe07ef93e81866fc7e", size = 736427, upload-time = "2026-01-16T22:51:36.595Z" }, + { url = "https://files.pythonhosted.org/packages/fc/3d/177b6a675691f7b6f708faef33f981e72fbc4bfed2b1dfa94dc70d0e8a25/pyopencl-2026.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65b151c56b936481d6b6050c2b9bc520840e1402be78c282ba5c01921c25477d", size = 1163888, upload-time = "2026-01-16T22:51:37.973Z" }, + { url = "https://files.pythonhosted.org/packages/e9/fa/5905571d9fa48827c0427a3e664c0213dd045940d581b3b739d83df9c0f6/pyopencl-2026.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc40003446037f391ca0970694efb0627e2870fabb20ee21be75bc445a39d8f4", size = 1228235, upload-time = "2026-01-16T22:51:39.786Z" }, + { url = "https://files.pythonhosted.org/packages/1e/3d/538c675d078b91680d8d82962110d0c9fd42e1584763d515d6e2e82d8c57/pyopencl-2026.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:b6a8e109ade7db60e8b1beb48df8f080941d0cd77fb2c225ad509c80cdef603e", size = 474753, upload-time = "2026-01-16T22:51:41.771Z" }, + { url = "https://files.pythonhosted.org/packages/cd/34/1497070e44d1689ddbd01d24a2265910e84ebc53457a489b9d2b6e1ac675/pyopencl-2026.1.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:7d88e59901bfe1f9296fd89acd9968f008dc7cfee7995f8cd09c3f1a77119aa6", size = 438145, upload-time = "2026-01-16T22:51:43.658Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a3/71d6af8741b52d3bef443518c1ccfda003adcfa9cc1d0df83dac7005d08c/pyopencl-2026.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f96a3bff8a09d2fa924e7c33dafac6ea3ef7ec70e746d6d8e17ce2d959a6836", size = 428820, upload-time = "2026-01-16T22:51:45.326Z" }, + { url = "https://files.pythonhosted.org/packages/db/ea/c8dbabeceac9cad3dbb368e08e0aa208cc6c6251c5134cc25eb15da03639/pyopencl-2026.1.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d4e8e8215ec4fdee4b235b61977cdb1c4f041b487bdcf357be799f45b423d61", size = 685478, upload-time = "2026-01-16T22:51:46.545Z" }, + { url = "https://files.pythonhosted.org/packages/64/c7/5854ef7471dfee195bcef6348a107525ca4d1b73c15240e6444d490f9920/pyopencl-2026.1.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0052a8ccbd282d8ab196705e31f4c3ab344113ea5d5c3ddaeede00cdcab068b", size = 734017, upload-time = "2026-01-16T22:51:48.277Z" }, + { url = "https://files.pythonhosted.org/packages/3d/79/42d4eec282ed299b38d8136d05545113ec8771a1bd6b10bb4ba83ae1236c/pyopencl-2026.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e43da12a376e9283407c2820b24cceeaa129b042ac710947cf8e07b13e294689", size = 1159871, upload-time = "2026-01-16T22:51:49.569Z" }, + { url = "https://files.pythonhosted.org/packages/a0/9a/fdc5d3bed0440d6206109e051008aa0a54ca131d64314bbd42177b8f0763/pyopencl-2026.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b14b2cf11dec9e0b75cbd14223d1b3c93950fc3e2f7a306b54fa1b17a2cae0f", size = 1225288, upload-time = "2026-01-16T22:51:51.125Z" }, + { url = "https://files.pythonhosted.org/packages/2d/e3/358c19180e0dab5c7dd1fcacc569e6a7ab02a7fddcb9c954f393ceddb2fa/pyopencl-2026.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:d02d7ecabc8d34590dccffe12346689adc5a1ceb07df5acc4ea6c4db8aa28277", size = 474876, upload-time = "2026-01-16T22:51:52.912Z" }, ] [[package]] @@ -4473,7 +4474,7 @@ name = "python-xlib" version = "0.33" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "six", marker = "sys_platform != 'darwin'" }, + { name = "six" }, ] sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" } wheels = [ @@ -4652,7 +4653,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/5a/8f60d367147019acef342746f20121b2341ec6596acd5c7941cb36bda02e/raylib-5.5.0.4-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:bdaa119b767f380caf6dd4f9d42ab3bf8596d8fb98737d2951b36924a5a83ac0", size = 2036797, upload-time = "2025-12-11T15:27:20.044Z" }, { url = "https://files.pythonhosted.org/packages/dd/ad/97dd93c389263c61a3057065f0f70db5fdc3c5768fa383a9b3e989ddb6a7/raylib-5.5.0.4-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:6a5cdeeb803d081342961eb1f7c4161af27e951d9ecf2b56d469d5730fcc6213", size = 2188009, upload-time = "2025-12-11T18:50:05.612Z" }, { url = "https://files.pythonhosted.org/packages/42/6a/55be04012f3459842389689326910204f985cffcb8989a92475221f5660a/raylib-5.5.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4067fa8a6ed3eb78a1162fc2d40ce8c26c26c5ee544019d1902accf21ec22add", size = 2187633, upload-time = "2025-12-11T15:27:22.345Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b7/702ea311fcb1b82064a1c50f32fe86fce1f21caa39c54ca1d598a9862444/raylib-5.5.0.4-cp311-cp311-manylinux_2_35_aarch64.whl", hash = "sha256:faa403252fd0a692dd2f37d9dd4e79fa293ec05deae8a3b086b063cd725a48f9", size = 2247484, upload-time = "2025-12-11T15:27:24.718Z" }, { url = "https://files.pythonhosted.org/packages/6b/18/b69d9ad9f4064785ad29c73672d40b36c59c3b3efd1dee264cdff4b48bf6/raylib-5.5.0.4-cp311-cp311-win32.whl", hash = "sha256:f01a769bb0797ab4f6e1efc950d5d8aca53548e97da7f527190a1ca5f671c389", size = 1456775, upload-time = "2025-12-11T15:27:26.776Z" }, { url = "https://files.pythonhosted.org/packages/1a/7a/4025d9ceeee8e3ae4748b0f6c356c5ce97628bd5da8a056b6782c87f7e65/raylib-5.5.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:34771dea34a30fa4657f35b344d5ebf9eb11d9b62b23d9349742db5c5f3992bd", size = 1705555, upload-time = "2025-12-11T15:27:28.888Z" }, { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, @@ -4660,7 +4660,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, - { url = "https://files.pythonhosted.org/packages/ae/f1/a9bde00b01956bbccc82c9112c8a6a64d50d44d7e4752c04dc59e59bde7e/raylib-5.5.0.4-cp312-cp312-manylinux_2_35_aarch64.whl", hash = "sha256:fb80c638f74a3a59af6a702078da3152a0013b959a225fc76cc6cc8fd0d91e36", size = 2259080, upload-time = "2025-12-11T15:27:41.862Z" }, { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, { url = "https://files.pythonhosted.org/packages/e8/ba/fee7e6ae0be850f6581d4084ea97825b7895c8866fa8b2df347d408c8293/raylib-5.5.0.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c318357ce721c62a6848b6d84b26574cd77267e5758cfa2dbc01d4deb2a2b0b8", size = 1211520, upload-time = "2025-12-11T15:28:30.266Z" }, @@ -4685,42 +4684,11 @@ wheels = [ [[package]] name = "ruamel-yaml" -version = "0.18.17" +version = "0.19.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ruamel-yaml-clib", marker = "platform_python_implementation == 'CPython'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/2b/7a1f1ebcd6b3f14febdc003e658778d81e76b40df2267904ee6b13f0c5c6/ruamel_yaml-0.18.17.tar.gz", hash = "sha256:9091cd6e2d93a3a4b157ddb8fabf348c3de7f1fb1381346d985b6b247dcd8d3c", size = 149602, upload-time = "2025-12-17T20:02:55.757Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/af/fe/b6045c782f1fd1ae317d2a6ca1884857ce5c20f59befe6ab25a8603c43a7/ruamel_yaml-0.18.17-py3-none-any.whl", hash = "sha256:9c8ba9eb3e793efdf924b60d521820869d5bf0cb9c6f1b82d82de8295e290b9d", size = 121594, upload-time = "2025-12-17T20:02:07.657Z" }, -] - -[[package]] -name = "ruamel-yaml-clib" -version = "0.2.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/97/60fda20e2fb54b83a61ae14648b0817c8f5d84a3821e40bfbdae1437026a/ruamel_yaml_clib-0.2.15.tar.gz", hash = "sha256:46e4cc8c43ef6a94885f72512094e482114a8a706d3c555a34ed4b0d20200600", size = 225794, upload-time = "2025-11-16T16:12:59.761Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/80/8ce7b9af532aa94dd83360f01ce4716264db73de6bc8efd22c32341f6658/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c583229f336682b7212a43d2fa32c30e643d3076178fb9f7a6a14dde85a2d8bd", size = 147998, upload-time = "2025-11-16T16:13:13.241Z" }, - { url = "https://files.pythonhosted.org/packages/53/09/de9d3f6b6701ced5f276d082ad0f980edf08ca67114523d1b9264cd5e2e0/ruamel_yaml_clib-0.2.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:56ea19c157ed8c74b6be51b5fa1c3aff6e289a041575f0556f66e5fb848bb137", size = 132743, upload-time = "2025-11-16T16:13:14.265Z" }, - { url = "https://files.pythonhosted.org/packages/0e/f7/73a9b517571e214fe5c246698ff3ed232f1ef863c8ae1667486625ec688a/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5fea0932358e18293407feb921d4f4457db837b67ec1837f87074667449f9401", size = 731459, upload-time = "2025-11-16T20:22:44.338Z" }, - { url = "https://files.pythonhosted.org/packages/9b/a2/0dc0013169800f1c331a6f55b1282c1f4492a6d32660a0cf7b89e6684919/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef71831bd61fbdb7aa0399d5c4da06bea37107ab5c79ff884cc07f2450910262", size = 749289, upload-time = "2025-11-16T16:13:15.633Z" }, - { url = "https://files.pythonhosted.org/packages/aa/ed/3fb20a1a96b8dc645d88c4072df481fe06e0289e4d528ebbdcc044ebc8b3/ruamel_yaml_clib-0.2.15-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:617d35dc765715fa86f8c3ccdae1e4229055832c452d4ec20856136acc75053f", size = 777630, upload-time = "2025-11-16T16:13:16.898Z" }, - { url = "https://files.pythonhosted.org/packages/60/50/6842f4628bc98b7aa4733ab2378346e1441e150935ad3b9f3c3c429d9408/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b45498cc81a4724a2d42273d6cfc243c0547ad7c6b87b4f774cb7bcc131c98d", size = 744368, upload-time = "2025-11-16T16:13:18.117Z" }, - { url = "https://files.pythonhosted.org/packages/d3/b0/128ae8e19a7d794c2e36130a72b3bb650ce1dd13fb7def6cf10656437dcf/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:def5663361f6771b18646620fca12968aae730132e104688766cf8a3b1d65922", size = 745233, upload-time = "2025-11-16T20:22:45.833Z" }, - { url = "https://files.pythonhosted.org/packages/75/05/91130633602d6ba7ce3e07f8fc865b40d2a09efd4751c740df89eed5caf9/ruamel_yaml_clib-0.2.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:014181cdec565c8745b7cbc4de3bf2cc8ced05183d986e6d1200168e5bb59490", size = 770963, upload-time = "2025-11-16T16:13:19.344Z" }, - { url = "https://files.pythonhosted.org/packages/fd/4b/fd4542e7f33d7d1bc64cc9ac9ba574ce8cf145569d21f5f20133336cdc8c/ruamel_yaml_clib-0.2.15-cp311-cp311-win32.whl", hash = "sha256:d290eda8f6ada19e1771b54e5706b8f9807e6bb08e873900d5ba114ced13e02c", size = 102640, upload-time = "2025-11-16T16:13:20.498Z" }, - { url = "https://files.pythonhosted.org/packages/bb/eb/00ff6032c19c7537371e3119287999570867a0eafb0154fccc80e74bf57a/ruamel_yaml_clib-0.2.15-cp311-cp311-win_amd64.whl", hash = "sha256:bdc06ad71173b915167702f55d0f3f027fc61abd975bd308a0968c02db4a4c3e", size = 121996, upload-time = "2025-11-16T16:13:21.855Z" }, - { url = "https://files.pythonhosted.org/packages/72/4b/5fde11a0722d676e469d3d6f78c6a17591b9c7e0072ca359801c4bd17eee/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cb15a2e2a90c8475df45c0949793af1ff413acfb0a716b8b94e488ea95ce7cff", size = 149088, upload-time = "2025-11-16T16:13:22.836Z" }, - { url = "https://files.pythonhosted.org/packages/85/82/4d08ac65ecf0ef3b046421985e66301a242804eb9a62c93ca3437dc94ee0/ruamel_yaml_clib-0.2.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64da03cbe93c1e91af133f5bec37fd24d0d4ba2418eaf970d7166b0a26a148a2", size = 134553, upload-time = "2025-11-16T16:13:24.151Z" }, - { url = "https://files.pythonhosted.org/packages/b9/cb/22366d68b280e281a932403b76da7a988108287adff2bfa5ce881200107a/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f6d3655e95a80325b84c4e14c080b2470fe4f33b6846f288379ce36154993fb1", size = 737468, upload-time = "2025-11-16T20:22:47.335Z" }, - { url = "https://files.pythonhosted.org/packages/71/73/81230babf8c9e33770d43ed9056f603f6f5f9665aea4177a2c30ae48e3f3/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:71845d377c7a47afc6592aacfea738cc8a7e876d586dfba814501d8c53c1ba60", size = 753349, upload-time = "2025-11-16T16:13:26.269Z" }, - { url = "https://files.pythonhosted.org/packages/61/62/150c841f24cda9e30f588ef396ed83f64cfdc13b92d2f925bb96df337ba9/ruamel_yaml_clib-0.2.15-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e5499db1ccbc7f4b41f0565e4f799d863ea720e01d3e99fa0b7b5fcd7802c9", size = 788211, upload-time = "2025-11-16T16:13:27.441Z" }, - { url = "https://files.pythonhosted.org/packages/30/93/e79bd9cbecc3267499d9ead919bd61f7ddf55d793fb5ef2b1d7d92444f35/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4b293a37dc97e2b1e8a1aec62792d1e52027087c8eea4fc7b5abd2bdafdd6642", size = 743203, upload-time = "2025-11-16T16:13:28.671Z" }, - { url = "https://files.pythonhosted.org/packages/8d/06/1eb640065c3a27ce92d76157f8efddb184bd484ed2639b712396a20d6dce/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:512571ad41bba04eac7268fe33f7f4742210ca26a81fe0c75357fa682636c690", size = 747292, upload-time = "2025-11-16T20:22:48.584Z" }, - { url = "https://files.pythonhosted.org/packages/a5/21/ee353e882350beab65fcc47a91b6bdc512cace4358ee327af2962892ff16/ruamel_yaml_clib-0.2.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e5e9f630c73a490b758bf14d859a39f375e6999aea5ddd2e2e9da89b9953486a", size = 771624, upload-time = "2025-11-16T16:13:29.853Z" }, - { url = "https://files.pythonhosted.org/packages/57/34/cc1b94057aa867c963ecf9ea92ac59198ec2ee3a8d22a126af0b4d4be712/ruamel_yaml_clib-0.2.15-cp312-cp312-win32.whl", hash = "sha256:f4421ab780c37210a07d138e56dd4b51f8642187cdfb433eb687fe8c11de0144", size = 100342, upload-time = "2025-11-16T16:13:31.067Z" }, - { url = "https://files.pythonhosted.org/packages/b3/e5/8925a4208f131b218f9a7e459c0d6fcac8324ae35da269cb437894576366/ruamel_yaml_clib-0.2.15-cp312-cp312-win_amd64.whl", hash = "sha256:2b216904750889133d9222b7b873c199d48ecbb12912aca78970f84a5aa1a4bc", size = 119013, upload-time = "2025-11-16T16:13:32.164Z" }, + { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, ] [[package]] @@ -4734,28 +4702,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/08/52232a877978dd8f9cf2aeddce3e611b40a63287dfca29b6b8da791f5e8d/ruff-0.14.10.tar.gz", hash = "sha256:9a2e830f075d1a42cd28420d7809ace390832a490ed0966fe373ba288e77aaf4", size = 5859763, upload-time = "2025-12-18T19:28:57.98Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/60/01/933704d69f3f05ee16ef11406b78881733c186fe14b6a46b05cfcaf6d3b2/ruff-0.14.10-py3-none-linux_armv6l.whl", hash = "sha256:7a3ce585f2ade3e1f29ec1b92df13e3da262178df8c8bdf876f48fa0e8316c49", size = 13527080, upload-time = "2025-12-18T19:29:25.642Z" }, - { url = "https://files.pythonhosted.org/packages/df/58/a0349197a7dfa603ffb7f5b0470391efa79ddc327c1e29c4851e85b09cc5/ruff-0.14.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:674f9be9372907f7257c51f1d4fc902cb7cf014b9980152b802794317941f08f", size = 13797320, upload-time = "2025-12-18T19:29:02.571Z" }, - { url = "https://files.pythonhosted.org/packages/7b/82/36be59f00a6082e38c23536df4e71cdbc6af8d7c707eade97fcad5c98235/ruff-0.14.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d85713d522348837ef9df8efca33ccb8bd6fcfc86a2cde3ccb4bc9d28a18003d", size = 12918434, upload-time = "2025-12-18T19:28:51.202Z" }, - { url = "https://files.pythonhosted.org/packages/a6/00/45c62a7f7e34da92a25804f813ebe05c88aa9e0c25e5cb5a7d23dd7450e3/ruff-0.14.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6987ebe0501ae4f4308d7d24e2d0fe3d7a98430f5adfd0f1fead050a740a3a77", size = 13371961, upload-time = "2025-12-18T19:29:04.991Z" }, - { url = "https://files.pythonhosted.org/packages/40/31/a5906d60f0405f7e57045a70f2d57084a93ca7425f22e1d66904769d1628/ruff-0.14.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16a01dfb7b9e4eee556fbfd5392806b1b8550c9b4a9f6acd3dbe6812b193c70a", size = 13275629, upload-time = "2025-12-18T19:29:21.381Z" }, - { url = "https://files.pythonhosted.org/packages/3e/60/61c0087df21894cf9d928dc04bcd4fb10e8b2e8dca7b1a276ba2155b2002/ruff-0.14.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7165d31a925b7a294465fa81be8c12a0e9b60fb02bf177e79067c867e71f8b1f", size = 14029234, upload-time = "2025-12-18T19:29:00.132Z" }, - { url = "https://files.pythonhosted.org/packages/44/84/77d911bee3b92348b6e5dab5a0c898d87084ea03ac5dc708f46d88407def/ruff-0.14.10-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c561695675b972effb0c0a45db233f2c816ff3da8dcfbe7dfc7eed625f218935", size = 15449890, upload-time = "2025-12-18T19:28:53.573Z" }, - { url = "https://files.pythonhosted.org/packages/e9/36/480206eaefa24a7ec321582dda580443a8f0671fdbf6b1c80e9c3e93a16a/ruff-0.14.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bb98fcbbc61725968893682fd4df8966a34611239c9fd07a1f6a07e7103d08e", size = 15123172, upload-time = "2025-12-18T19:29:23.453Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/68e414156015ba80cef5473d57919d27dfb62ec804b96180bafdeaf0e090/ruff-0.14.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f24b47993a9d8cb858429e97bdf8544c78029f09b520af615c1d261bf827001d", size = 14460260, upload-time = "2025-12-18T19:29:27.808Z" }, - { url = "https://files.pythonhosted.org/packages/b3/19/9e050c0dca8aba824d67cc0db69fb459c28d8cd3f6855b1405b3f29cc91d/ruff-0.14.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59aabd2e2c4fd614d2862e7939c34a532c04f1084476d6833dddef4afab87e9f", size = 14229978, upload-time = "2025-12-18T19:29:11.32Z" }, - { url = "https://files.pythonhosted.org/packages/51/eb/e8dd1dd6e05b9e695aa9dd420f4577debdd0f87a5ff2fedda33c09e9be8c/ruff-0.14.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:213db2b2e44be8625002dbea33bb9c60c66ea2c07c084a00d55732689d697a7f", size = 14338036, upload-time = "2025-12-18T19:29:09.184Z" }, - { url = "https://files.pythonhosted.org/packages/6a/12/f3e3a505db7c19303b70af370d137795fcfec136d670d5de5391e295c134/ruff-0.14.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b914c40ab64865a17a9a5b67911d14df72346a634527240039eb3bd650e5979d", size = 13264051, upload-time = "2025-12-18T19:29:13.431Z" }, - { url = "https://files.pythonhosted.org/packages/08/64/8c3a47eaccfef8ac20e0484e68e0772013eb85802f8a9f7603ca751eb166/ruff-0.14.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1484983559f026788e3a5c07c81ef7d1e97c1c78ed03041a18f75df104c45405", size = 13283998, upload-time = "2025-12-18T19:29:06.994Z" }, - { url = "https://files.pythonhosted.org/packages/12/84/534a5506f4074e5cc0529e5cd96cfc01bb480e460c7edf5af70d2bcae55e/ruff-0.14.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c70427132db492d25f982fffc8d6c7535cc2fd2c83fc8888f05caaa248521e60", size = 13601891, upload-time = "2025-12-18T19:28:55.811Z" }, - { url = "https://files.pythonhosted.org/packages/0d/1e/14c916087d8598917dbad9b2921d340f7884824ad6e9c55de948a93b106d/ruff-0.14.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5bcf45b681e9f1ee6445d317ce1fa9d6cba9a6049542d1c3d5b5958986be8830", size = 14336660, upload-time = "2025-12-18T19:29:16.531Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1c/d7b67ab43f30013b47c12b42d1acd354c195351a3f7a1d67f59e54227ede/ruff-0.14.10-py3-none-win32.whl", hash = "sha256:104c49fc7ab73f3f3a758039adea978869a918f31b73280db175b43a2d9b51d6", size = 13196187, upload-time = "2025-12-18T19:29:19.006Z" }, - { url = "https://files.pythonhosted.org/packages/fb/9c/896c862e13886fae2af961bef3e6312db9ebc6adc2b156fe95e615dee8c1/ruff-0.14.10-py3-none-win_amd64.whl", hash = "sha256:466297bd73638c6bdf06485683e812db1c00c7ac96d4ddd0294a338c62fdc154", size = 14661283, upload-time = "2025-12-18T19:29:30.16Z" }, - { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, +version = "0.14.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, + { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, + { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, + { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, + { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, + { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, + { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, + { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, + { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, + { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, + { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, + { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, + { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, + { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, ] [[package]] @@ -4769,15 +4737,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.48.0" +version = "2.49.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f0/0e9dc590513d5e742d7799e2038df3a05167cba084c6ca4f3cdd75b55164/sentry_sdk-2.48.0.tar.gz", hash = "sha256:5213190977ff7fdff8a58b722fb807f8d5524a80488626ebeda1b5676c0c1473", size = 384828, upload-time = "2025-12-16T14:55:41.722Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/94/23ac26616a883f492428d9ee9ad6eee391612125326b784dbfc30e1e7bab/sentry_sdk-2.49.0.tar.gz", hash = "sha256:c1878599cde410d481c04ef50ee3aedd4f600e4d0d253f4763041e468b332c30", size = 387228, upload-time = "2026-01-08T09:56:25.642Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/19/8d77f9992e5cbfcaa9133c3bf63b4fbbb051248802e1e803fed5c552fbb2/sentry_sdk-2.48.0-py2.py3-none-any.whl", hash = "sha256:6b12ac256769d41825d9b7518444e57fa35b5642df4c7c5e322af4d2c8721172", size = 414555, upload-time = "2025-12-16T14:55:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/88/43/1c586f9f413765201234541857cb82fda076f4b0f7bad4a0ec248da39cf3/sentry_sdk-2.49.0-py2.py3-none-any.whl", hash = "sha256:6ea78499133874445a20fe9c826c9e960070abeb7ae0cdf930314ab16bb97aa0", size = 415693, upload-time = "2026-01-08T09:56:21.872Z" }, ] [[package]] @@ -4825,7 +4793,7 @@ name = "shapely" version = "2.1.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "numpy" }, ] sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ @@ -4950,39 +4918,39 @@ wheels = [ [[package]] name = "ty" -version = "0.0.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/43/8be3ec2e2ce6119cff9ee3a207fae0cb4f2b4f8ed6534175130a32be24a7/ty-0.0.7.tar.gz", hash = "sha256:90e53b20b86c418ee41a8385f17da44cc7f916f96f9eee87593423ce8292ca72", size = 4826677, upload-time = "2025-12-24T21:28:49.136Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/56/fafa123acf955089306372add312f16e97aba61f7c4daf74e2bb9c350d23/ty-0.0.7-py3-none-linux_armv6l.whl", hash = "sha256:b30105bd9a0b064497111c50c206d5b6a032f29bcf39f09a12085c3009d72784", size = 9862360, upload-time = "2025-12-24T21:28:36.762Z" }, - { url = "https://files.pythonhosted.org/packages/71/f4/9c30ff498d9a60e24f16d26c0cf93cd03a119913ffa720a77149f02df06e/ty-0.0.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b4df20889115f3d5611a9d9cdedc222e3fd82b5fe87bb0a9f7246e53a23becc7", size = 9712866, upload-time = "2025-12-24T21:28:25.926Z" }, - { url = "https://files.pythonhosted.org/packages/43/84/e06a4a6e4011890027ffee41efbf261b1335103d09009d625ace7f1a60eb/ty-0.0.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f699589d8511e1e17c5a7edfc5f4a4e80f2a6d4a3932a0e9e3422fd32d731472", size = 9221692, upload-time = "2025-12-24T21:28:29.649Z" }, - { url = "https://files.pythonhosted.org/packages/7a/e9/ebb4192d3627730125d40ee403a17dc91bab59d69c3eff286453b3218d01/ty-0.0.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eaec2d8aa153ee4bcc43b17a384d0f9e66177c8c8127be3358b6b8348b9e3b", size = 9710340, upload-time = "2025-12-24T21:28:55.148Z" }, - { url = "https://files.pythonhosted.org/packages/8f/4a/ec144458a9cfb324d5cb471483094e62e74d73179343dff262a5cca1a1e1/ty-0.0.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:177d160295e6a56bdf0b61f6120bc4502fff301d4d10855ba711c109aa7f37fb", size = 9670317, upload-time = "2025-12-24T21:28:43.096Z" }, - { url = "https://files.pythonhosted.org/packages/b6/94/fe7106fd5e2ac06b81fba7b785a6216774618edc3fda9e17f58efe3cede6/ty-0.0.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30518b95ab5cc83615794cca765a5fb86df39a0d9c3dadc0ab2d787ab7830008", size = 10096517, upload-time = "2025-12-24T21:28:23.667Z" }, - { url = "https://files.pythonhosted.org/packages/45/d9/db96ccfd663c96bdd4bb63db72899198c01445012f939477a5318a563f14/ty-0.0.7-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7867b3f75c2d9602cc6fb3b6d462580b707c2d112d4b27037142b0d01f8bfd03", size = 10996406, upload-time = "2025-12-24T21:28:39.134Z" }, - { url = "https://files.pythonhosted.org/packages/94/da/103915c08c3e6a14f95959614646fcdc9a240cd9a039fadbdcd086c819ee/ty-0.0.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:878d45858e209b7904753fbc5155f4cb75dadc20a26bbb77614bfef31580f9ae", size = 10712829, upload-time = "2025-12-24T21:28:27.745Z" }, - { url = "https://files.pythonhosted.org/packages/47/c0/d9be417bc8e459e13e9698978579eec9868f91f4c5d6ef663249967fec8b/ty-0.0.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:651820b193901825afce40ae68f6a51cd64dbfa4b81a45db90061401261f25e4", size = 10486541, upload-time = "2025-12-24T21:28:45.17Z" }, - { url = "https://files.pythonhosted.org/packages/ad/09/d1858c66620d8ae566e021ad0d7168914b1568841f8fe9e439116ce6b440/ty-0.0.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f56a5a0c1c045863b1b70c358a392b3f73b8528c5c571d409f19dd465525e116", size = 10255312, upload-time = "2025-12-24T21:28:53.17Z" }, - { url = "https://files.pythonhosted.org/packages/b6/0a/78f75089db491fd5fcc13d2845a0b2771b7f7d377450c64c6616e9c227bc/ty-0.0.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:748218fbc1f7b7f1b9d14e77d4f3d7fec72af794417e26b0185bdb94153afe1c", size = 9696201, upload-time = "2025-12-24T21:28:57.345Z" }, - { url = "https://files.pythonhosted.org/packages/01/9e/b26e94832fd563fef6f77a4487affc77a027b0e53106422c66aafb37fa01/ty-0.0.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1ff80f3985a52a7358b9069b4a8d223e92cf312544a934a062d6d3a4fb6876b3", size = 9688907, upload-time = "2025-12-24T21:28:59.485Z" }, - { url = "https://files.pythonhosted.org/packages/5a/8f/cc48601fb92c964cf6c34277e0d947076146b7de47aa11b5dbae45e01ce7/ty-0.0.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a808910ce672ba4446699f4c021283208f58f988bcfc3bdbdfc6e005819d9ee0", size = 9829982, upload-time = "2025-12-24T21:28:34.429Z" }, - { url = "https://files.pythonhosted.org/packages/b5/af/7fa9c2bfa25865968bded637f7e71f1a712f4fbede88f487b6a9101ab936/ty-0.0.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:2718fea5f314eda01703fb406ec89b1fc8710b3fc6a09bbd6f7a4f3502ddc889", size = 10361037, upload-time = "2025-12-24T21:28:47.027Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5b/1a6ff1495975cd1c02aa8d03bc5c9d8006eaeb8bf354446f88d70f0518fd/ty-0.0.7-py3-none-win32.whl", hash = "sha256:ae89bb8dc50deb66f34eab3113aa61ac5d7f85ecf16279e5918548085a89021c", size = 9295092, upload-time = "2025-12-24T21:28:51.041Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f6/47e9364635d048002354f84d2d0d6dfc9eb166dc67850739f88e1fec4fc5/ty-0.0.7-py3-none-win_amd64.whl", hash = "sha256:25bd20e3d4d0f07b422f9b42711ba24d28116031273bd23dbda66cec14df1c06", size = 10162816, upload-time = "2025-12-24T21:28:41.006Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f4/c4fc28410c4493982b7481fb23f62bacb02fd2912ebec3b9bc7de18bebb8/ty-0.0.7-py3-none-win_arm64.whl", hash = "sha256:c87d27484dba9fca0053b6a9eee47eecc760aab2bbb8e6eab3d7f81531d1ad0c", size = 9653112, upload-time = "2025-12-24T21:28:31.562Z" }, +version = "0.0.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/78/ba1a4ad403c748fbba8be63b7e774a90e80b67192f6443d624c64fe4aaab/ty-0.0.12.tar.gz", hash = "sha256:cd01810e106c3b652a01b8f784dd21741de9fdc47bd595d02c122a7d5cefeee7", size = 4981303, upload-time = "2026-01-14T22:30:48.537Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/8f/c21314d074dda5fb13d3300fa6733fd0d8ff23ea83a721818740665b6314/ty-0.0.12-py3-none-linux_armv6l.whl", hash = "sha256:eb9da1e2c68bd754e090eab39ed65edf95168d36cbeb43ff2bd9f86b4edd56d1", size = 9614164, upload-time = "2026-01-14T22:30:44.016Z" }, + { url = "https://files.pythonhosted.org/packages/09/28/f8a4d944d13519d70c486e8f96d6fa95647ac2aa94432e97d5cfec1f42f6/ty-0.0.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c181f42aa19b0ed7f1b0c2d559980b1f1d77cc09419f51c8321c7ddf67758853", size = 9542337, upload-time = "2026-01-14T22:30:05.687Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9c/f576e360441de7a8201daa6dc4ebc362853bc5305e059cceeb02ebdd9a48/ty-0.0.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1f829e1eecd39c3e1b032149db7ae6a3284f72fc36b42436e65243a9ed1173db", size = 8909582, upload-time = "2026-01-14T22:30:46.089Z" }, + { url = "https://files.pythonhosted.org/packages/d6/13/0898e494032a5d8af3060733d12929e3e7716db6c75eac63fa125730a3e7/ty-0.0.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45162e7826e1789cf3374627883cdeb0d56b82473a0771923e4572928e90be3", size = 9384932, upload-time = "2026-01-14T22:30:13.769Z" }, + { url = "https://files.pythonhosted.org/packages/e4/1a/b35b6c697008a11d4cedfd34d9672db2f0a0621ec80ece109e13fca4dfef/ty-0.0.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d11fec40b269bec01e751b2337d1c7ffa959a2c2090a950d7e21c2792442cccd", size = 9453140, upload-time = "2026-01-14T22:30:11.131Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/71c9edbc79a3c88a0711324458f29c7dbf6c23452c6e760dc25725483064/ty-0.0.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09d99e37e761a4d2651ad9d5a610d11235fbcbf35dc6d4bc04abf54e7cf894f1", size = 9960680, upload-time = "2026-01-14T22:30:33.621Z" }, + { url = "https://files.pythonhosted.org/packages/0e/75/39375129f62dd22f6ad5a99cd2a42fd27d8b91b235ce2db86875cdad397d/ty-0.0.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d9ca0cdb17bd37397da7b16a7cd23423fc65c3f9691e453ad46c723d121225a1", size = 10904518, upload-time = "2026-01-14T22:30:08.464Z" }, + { url = "https://files.pythonhosted.org/packages/32/5e/26c6d88fafa11a9d31ca9f4d12989f57782ec61e7291d4802d685b5be118/ty-0.0.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcf2757b905e7eddb7e456140066335b18eb68b634a9f72d6f54a427ab042c64", size = 10525001, upload-time = "2026-01-14T22:30:16.454Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a5/2f0b91894af13187110f9ad7ee926d86e4e6efa755c9c88a820ed7f84c85/ty-0.0.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00cf34c1ebe1147efeda3021a1064baa222c18cdac114b7b050bbe42deb4ca80", size = 10307103, upload-time = "2026-01-14T22:30:41.221Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/13d0410827e4bc713ebb7fdaf6b3590b37dcb1b82e0a81717b65548f2442/ty-0.0.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb3a655bd869352e9a22938d707631ac9fbca1016242b1f6d132d78f347c851", size = 10072737, upload-time = "2026-01-14T22:30:51.783Z" }, + { url = "https://files.pythonhosted.org/packages/e1/dd/fc36d8bac806c74cf04b4ca735bca14d19967ca84d88f31e121767880df1/ty-0.0.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4658e282c7cb82be304052f8f64f9925f23c3c4f90eeeb32663c74c4b095d7ba", size = 9368726, upload-time = "2026-01-14T22:30:18.683Z" }, + { url = "https://files.pythonhosted.org/packages/54/70/9e8e461647550f83e2fe54bc632ccbdc17a4909644783cdbdd17f7296059/ty-0.0.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c167d838eaaa06e03bb66a517f75296b643d950fbd93c1d1686a187e5a8dbd1f", size = 9454704, upload-time = "2026-01-14T22:30:22.759Z" }, + { url = "https://files.pythonhosted.org/packages/04/9b/6292cf7c14a0efeca0539cf7d78f453beff0475cb039fbea0eb5d07d343d/ty-0.0.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2956e0c9ab7023533b461d8a0e6b2ea7b78e01a8dde0688e8234d0fce10c4c1c", size = 9649829, upload-time = "2026-01-14T22:30:31.234Z" }, + { url = "https://files.pythonhosted.org/packages/49/bd/472a5d2013371e4870886cff791c94abdf0b92d43d305dd0f8e06b6ff719/ty-0.0.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c6a3fd7479580009f21002f3828320621d8a82d53b7ba36993234e3ccad58c8", size = 10162814, upload-time = "2026-01-14T22:30:36.174Z" }, + { url = "https://files.pythonhosted.org/packages/31/e9/2ecbe56826759845a7c21d80aa28187865ea62bc9757b056f6cbc06f78ed/ty-0.0.12-py3-none-win32.whl", hash = "sha256:a91c24fd75c0f1796d8ede9083e2c0ec96f106dbda73a09fe3135e075d31f742", size = 9140115, upload-time = "2026-01-14T22:30:38.903Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/d9531eff35a5c0ec9dbc10231fac21f9dd6504814048e81d6ce1c84dc566/ty-0.0.12-py3-none-win_amd64.whl", hash = "sha256:df151894be55c22d47068b0f3b484aff9e638761e2267e115d515fcc9c5b4a4b", size = 9884532, upload-time = "2026-01-14T22:30:25.112Z" }, + { url = "https://files.pythonhosted.org/packages/e9/f3/20b49e75967023b123a221134548ad7000f9429f13fdcdda115b4c26305f/ty-0.0.12-py3-none-win_arm64.whl", hash = "sha256:cea99d334b05629de937ce52f43278acf155d3a316ad6a35356635f886be20ea", size = 9313974, upload-time = "2026-01-14T22:30:27.44Z" }, ] [[package]] name = "types-requests" -version = "2.32.4.20250913" +version = "2.32.4.20260107" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/27/489922f4505975b11de2b5ad07b4fe1dca0bca9be81a703f26c5f3acfce5/types_requests-2.32.4.20250913.tar.gz", hash = "sha256:abd6d4f9ce3a9383f269775a9835a4c24e5cd6b9f647d64f88aa4613c33def5d", size = 23113, upload-time = "2025-09-13T02:40:02.309Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/20/9a227ea57c1285986c4cf78400d0a91615d25b24e257fd9e2969606bdfae/types_requests-2.32.4.20250913-py3-none-any.whl", hash = "sha256:78c9c1fffebbe0fa487a418e0fa5252017e9c60d1a2da394077f1780f655d7e1", size = 20658, upload-time = "2025-09-13T02:40:01.115Z" }, + { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, ] [[package]] @@ -5005,11 +4973,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.2" +version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/24/a2a2ed9addd907787d7aa0355ba36a6cadf1768b934c652ea78acbd59dcd/urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797", size = 432930, upload-time = "2025-12-11T15:56:40.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182, upload-time = "2025-12-11T15:56:38.584Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] [[package]] @@ -5075,7 +5043,7 @@ name = "yapf" version = "0.43.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "platformdirs", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "platformdirs" }, ] sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } wheels = [ From 0b41b42f7b23637e3bbb1f7495d318d954f971e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 19 Jan 2026 11:48:06 -0800 Subject: [PATCH 053/685] =?UTF-8?q?WMI=20model=20=F0=9F=8D=89=20(#36798)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 1791ea0f-8667-4e0b-be73-084d912f6c4c/100 * eab53871-1f8c-45be-9a98-f6b3dd6a0adc/100 * dd075c9d-0c49-402e-b4f2-9adbe5301c84/100 * e8b5b1b0-2d37-4b62-bd39-21ff0d08ee68/100 * 1aff00c7-06c5-46a6-8a79-7e56f77d81bf/100 * 3547a2cc-1699-4e7d-a2ab-4eb87d0b8684/100 * 849aa9fb-dae6-4604-923e-050883def218/100 * 0e0f6dd2-96dc-4f34-a7cd-63bccc2f5616/100 * 887f923b-7e79-43c6-8f1f-053e1490f859/100 * 1fa82260-1171-4db5-9968-d34ce2e14694/100 * Revert "1fa82260-1171-4db5-9968-d34ce2e14694/100" This reverts commit 855f5e4ddefd69a20cc4e9da004eb53f3e00d950. * a27b3122-733e-4a65-938b-acfebebbe5e8/100 --------- Co-authored-by: Yassine Yousfi --- selfdrive/modeld/models/driving_policy.onnx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 27e4c8f7b9e..92c81954d29 100644 --- a/selfdrive/modeld/models/driving_policy.onnx +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66f406ee179d984a4d8c93e38da479dcd1893127308dd3a7c322a7481a6b51b2 +oid sha256:1edea5bb56f876db4cec97c150799513f6a59373f3ad152d55e4dcaab1b809e3 size 13926324 From 039b85f355d6a6638575cd0a311d354ded99b5b3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 19 Jan 2026 15:33:23 -0800 Subject: [PATCH 054/685] bump opendbc (#37003) * bump opendbc * bump * bump * bump * bump bump bump --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index edf19be8efe..796ece26acd 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit edf19be8efe06a5387265adc4650725bb56b5c46 +Subproject commit 796ece26acd8b9255810ca71941ed72626589ee7 From 10db1edc7f6eaba28e93479b11d483340bd3826b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 19 Jan 2026 15:50:00 -0800 Subject: [PATCH 055/685] merge common.util and common.utils (#36951) * common: merge common.util and common.utils * cleanup * cleanup --- cereal/messaging/__init__.py | 2 +- common/realtime.py | 2 +- common/util.py | 46 --------------------------- common/utils.py | 53 ++++++++++++++++++++++++++++++-- system/hardware/tici/hardware.py | 2 +- system/sensord/sensord.py | 2 +- 6 files changed, 54 insertions(+), 53 deletions(-) delete mode 100644 common/util.py diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py index 0ad846f0f45..d5033cd634a 100644 --- a/cereal/messaging/__init__.py +++ b/cereal/messaging/__init__.py @@ -13,7 +13,7 @@ from cereal import log from cereal.services import SERVICE_LIST -from openpilot.common.util import MovingAverage +from openpilot.common.utils import MovingAverage NO_TRAVERSAL_LIMIT = 2**64-1 diff --git a/common/realtime.py b/common/realtime.py index 57926b4c4fa..0b14681021c 100644 --- a/common/realtime.py +++ b/common/realtime.py @@ -6,7 +6,7 @@ from setproctitle import getproctitle -from openpilot.common.util import MovingAverage +from openpilot.common.utils import MovingAverage from openpilot.system.hardware import PC diff --git a/common/util.py b/common/util.py deleted file mode 100644 index e6ddb46e7b9..00000000000 --- a/common/util.py +++ /dev/null @@ -1,46 +0,0 @@ -import os -import subprocess - -def sudo_write(val: str, path: str) -> None: - try: - with open(path, 'w') as f: - f.write(str(val)) - except PermissionError: - os.system(f"sudo chmod a+w {path}") - try: - with open(path, 'w') as f: - f.write(str(val)) - except PermissionError: - # fallback for debugfs files - os.system(f"sudo su -c 'echo {val} > {path}'") - -def sudo_read(path: str) -> str: - try: - return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip() - except Exception: - return "" - -class MovingAverage: - def __init__(self, window_size: int): - self.window_size: int = window_size - self.buffer: list[float] = [0.0] * window_size - self.index: int = 0 - self.count: int = 0 - self.sum: float = 0.0 - - def add_value(self, new_value: float): - # Update the sum: subtract the value being replaced and add the new value - self.sum -= self.buffer[self.index] - self.buffer[self.index] = new_value - self.sum += new_value - - # Update the index in a circular manner - self.index = (self.index + 1) % self.window_size - - # Track the number of added values (for partial windows) - self.count = min(self.count + 1, self.window_size) - - def get_average(self) -> float: - if self.count == 0: - return float('nan') - return self.sum / self.count diff --git a/common/utils.py b/common/utils.py index 71b29a0c4eb..caa9a579581 100644 --- a/common/utils.py +++ b/common/utils.py @@ -7,14 +7,61 @@ import functools from subprocess import Popen, PIPE, TimeoutExpired import zstandard as zstd -from openpilot.common.swaglog import cloudlog LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change +def sudo_write(val: str, path: str) -> None: + try: + with open(path, 'w') as f: + f.write(str(val)) + except PermissionError: + os.system(f"sudo chmod a+w {path}") + try: + with open(path, 'w') as f: + f.write(str(val)) + except PermissionError: + # fallback for debugfs files + os.system(f"sudo su -c 'echo {val} > {path}'") + + +def sudo_read(path: str) -> str: + try: + return subprocess.check_output(f"sudo cat {path}", shell=True, encoding='utf8').strip() + except Exception: + return "" + + +class MovingAverage: + def __init__(self, window_size: int): + self.window_size: int = window_size + self.buffer: list[float] = [0.0] * window_size + self.index: int = 0 + self.count: int = 0 + self.sum: float = 0.0 + + def add_value(self, new_value: float): + # Update the sum: subtract the value being replaced and add the new value + self.sum -= self.buffer[self.index] + self.buffer[self.index] = new_value + self.sum += new_value + + # Update the index in a circular manner + self.index = (self.index + 1) % self.window_size + + # Track the number of added values (for partial windows) + self.count = min(self.count + 1, self.window_size) + + def get_average(self) -> float: + if self.count == 0: + return float('nan') + return self.sum / self.count + + class CallbackReader: """Wraps a file, but overrides the read method to also call a callback function with the number of bytes read so far.""" + def __init__(self, f, callback, *args): self.f = f self.callback = callback @@ -107,11 +154,11 @@ def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception: - cloudlog.exception(f"{func.__name__} failed, trying again") + print(f"{func.__name__} failed, trying again") time.sleep(delay) if ignore_failure: - cloudlog.error(f"{func.__name__} failed after retry") + print(f"{func.__name__} failed after retry") else: raise Exception(f"{func.__name__} failed after retry") return wrapper diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 3cdb336086a..5a84afce03b 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -8,7 +8,7 @@ from pathlib import Path from cereal import log -from openpilot.common.util import sudo_read, sudo_write +from openpilot.common.utils import sudo_read, sudo_write from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action from openpilot.system.hardware.base import HardwareBase, LPABase, ThermalConfig, ThermalZone from openpilot.system.hardware.tici import iwlist diff --git a/system/sensord/sensord.py b/system/sensord/sensord.py index cc0366881b0..62908c6f185 100755 --- a/system/sensord/sensord.py +++ b/system/sensord/sensord.py @@ -7,7 +7,7 @@ import cereal.messaging as messaging from cereal.services import SERVICE_LIST -from openpilot.common.util import sudo_write +from openpilot.common.utils import sudo_write from openpilot.common.realtime import config_realtime_process, Ratekeeper from openpilot.common.swaglog import cloudlog from openpilot.common.gpio import gpiochip_get_ro_value_fd, gpioevent_data From 13efc421c42f6e9c06430d80be02ba2a8ea50643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 19 Jan 2026 16:27:41 -0800 Subject: [PATCH 056/685] NV12 buffer shape helpers (#36683) * Give this a try * can codex debug? * simpler * Revert "simpler" This reverts commit 572335008c1c719aa985d14bd740253ff94b94a9. * better * cleanup * try again * tie * try this * try this * do tests fail without this? * doesn't seem needed * unused * don't need duplicate * passes CI? * try this * try this * try this * I don't understand this, so back to before * keep that alignment * set uv_height --------- Co-authored-by: Adeeb Shihadeh --- system/camerad/cameras/camera_common.cc | 6 +----- system/camerad/cameras/nv12_info.h | 19 +++++++++++++++++++ system/camerad/cameras/spectra.cc | 14 +++----------- system/camerad/cameras/spectra.h | 1 + tools/replay/camera.cc | 11 ++--------- tools/replay/camera.h | 2 -- 6 files changed, 26 insertions(+), 27 deletions(-) create mode 100644 system/camerad/cameras/nv12_info.h diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 1f6ad9b4be3..88bca7f775b 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -26,11 +26,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera * LOGD("allocated %d CL buffers", frame_buf_count); } - // the encoder HW tells us the size it wants after setting it up. - // TODO: VENUS_BUFFER_SIZE should give the size, but it's too small. dependent on encoder settings? - size_t nv12_size = (out_img_width <= 1344 ? 2900 : 2346)*cam->stride; - - vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, nv12_size, cam->stride, cam->uv_offset); + vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); LOGD("created %d YUV vipc buffers with size %dx%d", VIPC_BUFFER_COUNT, cam->stride, cam->y_height); } diff --git a/system/camerad/cameras/nv12_info.h b/system/camerad/cameras/nv12_info.h new file mode 100644 index 00000000000..0f4aee81a0b --- /dev/null +++ b/system/camerad/cameras/nv12_info.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +#include "third_party/linux/include/msm_media_info.h" + +// Returns NV12 aligned width, height, and buffer size for the given frame. +inline std::tuple get_nv12_info(int width, int height) { + // the encoder HW tells us the size it wants after setting it up. + // TODO: VENUS_BUFFER_SIZE should give the size, but it's too small. dependent on encoder settings? + const uint32_t nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); + const uint32_t nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); + assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, width)); + assert(nv12_height / 2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, height)); + const uint32_t nv12_buffer_size = (width <= 1344 ? 2900 : 2346)*nv12_width; + return {nv12_width, nv12_height, nv12_buffer_size}; +} diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 0d93b704659..20f48bb5566 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -283,20 +283,12 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c buf.out_img_width = sensor->frame_width / sensor->out_scale; buf.out_img_height = (sensor->hdr_offset > 0 ? (sensor->frame_height - sensor->hdr_offset) / 2 : sensor->frame_height) / sensor->out_scale; - - // size is driven by all the HW that handles frames, - // the video encoder has certain alignment requirements in this case - stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, buf.out_img_width); - y_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, buf.out_img_height); - uv_height = VENUS_UV_SCANLINES(COLOR_FMT_NV12, buf.out_img_height); - uv_offset = stride*y_height; - yuv_size = uv_offset + stride*uv_height; + std::tie(stride, y_height, yuv_size) = get_nv12_info(buf.out_img_width, buf.out_img_height); + uv_height = y_height / 2; + uv_offset = stride * y_height; if (cc.output_type != ISP_RAW_OUTPUT) { uv_offset = ALIGNED_SIZE(uv_offset, 0x1000); - yuv_size = uv_offset + ALIGNED_SIZE(stride*uv_height, 0x1000); } - assert(stride == VENUS_UV_STRIDE(COLOR_FMT_NV12, buf.out_img_width)); - assert(y_height/2 == uv_height); open = true; configISP(); diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 13cb13f98f6..983873c7f82 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -12,6 +12,7 @@ #include "common/util.h" #include "common/swaglog.h" #include "system/camerad/cameras/hw.h" +#include "system/camerad/cameras/nv12_info.h" #include "system/camerad/cameras/camera_common.h" #include "system/camerad/sensors/sensor.h" diff --git a/tools/replay/camera.cc b/tools/replay/camera.cc index 73243ed20d5..2962195aff5 100644 --- a/tools/replay/camera.cc +++ b/tools/replay/camera.cc @@ -7,17 +7,10 @@ #include "third_party/linux/include/msm_media_info.h" #include "tools/replay/util.h" +#include "system/camerad/cameras/nv12_info.h" -const int BUFFER_COUNT = 40; -std::tuple get_nv12_info(int width, int height) { - int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); - int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); - assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, width)); - assert(nv12_height / 2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, height)); - size_t nv12_buffer_size = 2346 * nv12_width; // comes from v4l2_format.fmt.pix_mp.plane_fmt[0].sizeimage - return {nv12_width, nv12_height, nv12_buffer_size}; -} +const int BUFFER_COUNT = 40; CameraServer::CameraServer(std::pair camera_size[MAX_CAMERAS]) { for (int i = 0; i < MAX_CAMERAS; ++i) { diff --git a/tools/replay/camera.h b/tools/replay/camera.h index 21c3d98dcf2..94330188488 100644 --- a/tools/replay/camera.h +++ b/tools/replay/camera.h @@ -10,8 +10,6 @@ #include "tools/replay/framereader.h" #include "tools/replay/logreader.h" -std::tuple get_nv12_info(int width, int height); - class CameraServer { public: CameraServer(std::pair camera_size[MAX_CAMERAS] = nullptr); From c179a3ccb7a5e3a6ff550feb04e97255f40256f2 Mon Sep 17 00:00:00 2001 From: Mauricio Alvarez Leon <65101411+BBBmau@users.noreply.github.com> Date: Mon, 19 Jan 2026 16:45:45 -0800 Subject: [PATCH 057/685] `CI`: enable `macos` tests (#37005) enable macos tests --- .github/workflows/tests.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f0028841a08..c5802b5cb2c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -91,7 +91,6 @@ jobs: build_mac: name: build macOS - if: false # tmp disable due to brew install not working runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} steps: - uses: actions/checkout@v4 From 6c7f3751e7fb30680810fe6d7141121099a21aa0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 19 Jan 2026 17:18:22 -0800 Subject: [PATCH 058/685] camerad: calculate buffer sizes with VENUS helpers (#37006) * Revert "NV12 buffer shape helpers (#36683)" This reverts commit 13efc421c42f6e9c06430d80be02ba2a8ea50643. * camerad: calculate buffer sizes with VENUS helpers * copy header: * assert aligned * python nv12 info * debug * handle padding * use the helper --- system/camerad/cameras/nv12_info.h | 23 +- system/camerad/cameras/nv12_info.py | 21 + system/camerad/cameras/spectra.cc | 11 +- system/camerad/cameras/spectra.h | 1 - system/camerad/snapshot.py | 10 +- third_party/linux/include/msm_media_info.h | 587 ++++++++++++++++++++- tools/replay/camera.cc | 10 +- 7 files changed, 631 insertions(+), 32 deletions(-) create mode 100644 system/camerad/cameras/nv12_info.py diff --git a/system/camerad/cameras/nv12_info.h b/system/camerad/cameras/nv12_info.h index 0f4aee81a0b..e8eb1174062 100644 --- a/system/camerad/cameras/nv12_info.h +++ b/system/camerad/cameras/nv12_info.h @@ -6,14 +6,17 @@ #include "third_party/linux/include/msm_media_info.h" -// Returns NV12 aligned width, height, and buffer size for the given frame. -inline std::tuple get_nv12_info(int width, int height) { - // the encoder HW tells us the size it wants after setting it up. - // TODO: VENUS_BUFFER_SIZE should give the size, but it's too small. dependent on encoder settings? - const uint32_t nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); - const uint32_t nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); - assert(nv12_width == VENUS_UV_STRIDE(COLOR_FMT_NV12, width)); - assert(nv12_height / 2 == VENUS_UV_SCANLINES(COLOR_FMT_NV12, height)); - const uint32_t nv12_buffer_size = (width <= 1344 ? 2900 : 2346)*nv12_width; - return {nv12_width, nv12_height, nv12_buffer_size}; +// Returns NV12 aligned (stride, y_height, uv_height, buffer_size) for the given frame dimensions. +inline std::tuple get_nv12_info(int width, int height) { + const uint32_t stride = VENUS_Y_STRIDE(COLOR_FMT_NV12, width); + const uint32_t y_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, height); + const uint32_t uv_height = VENUS_UV_SCANLINES(COLOR_FMT_NV12, height); + const uint32_t size = VENUS_BUFFER_SIZE(COLOR_FMT_NV12, width, height); + + // Sanity checks for NV12 format assumptions + assert(stride == VENUS_UV_STRIDE(COLOR_FMT_NV12, width)); + assert(y_height / 2 == uv_height); + assert((stride * y_height) % 0x1000 == 0); // uv_offset must be page-aligned + + return {stride, y_height, uv_height, size}; } diff --git a/system/camerad/cameras/nv12_info.py b/system/camerad/cameras/nv12_info.py new file mode 100644 index 00000000000..bcb6312d2bc --- /dev/null +++ b/system/camerad/cameras/nv12_info.py @@ -0,0 +1,21 @@ +# Python version of system/camerad/cameras/nv12_info.h +# Calculations from third_party/linux/include/msm_media_info.h (VENUS_BUFFER_SIZE) + +def align(val: int, alignment: int) -> int: + return ((val + alignment - 1) // alignment) * alignment + +def get_nv12_info(width: int, height: int) -> tuple[int, int, int, int]: + """Returns (stride, y_height, uv_height, buffer_size) for NV12 frame dimensions.""" + stride = align(width, 128) + y_height = align(height, 32) + uv_height = align(height // 2, 16) + + # VENUS_BUFFER_SIZE for NV12 + y_plane = stride * y_height + uv_plane = stride * uv_height + 4096 + size = y_plane + uv_plane + max(16 * 1024, 8 * stride) + size = align(size, 4096) + size += align(width, 512) * 512 # kernel padding for non-aligned frames + size = align(size, 4096) + + return stride, y_height, uv_height, size diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 20f48bb5566..5c3e7a9d233 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -12,11 +12,11 @@ #include "media/cam_isp_ife.h" #include "media/cam_sensor_cmn_header.h" #include "media/cam_sync.h" -#include "third_party/linux/include/msm_media_info.h" #include "common/util.h" #include "common/swaglog.h" #include "system/camerad/cameras/ife.h" +#include "system/camerad/cameras/nv12_info.h" #include "system/camerad/cameras/spectra.h" #include "system/camerad/cameras/bps_blobs.h" @@ -283,12 +283,11 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c buf.out_img_width = sensor->frame_width / sensor->out_scale; buf.out_img_height = (sensor->hdr_offset > 0 ? (sensor->frame_height - sensor->hdr_offset) / 2 : sensor->frame_height) / sensor->out_scale; - std::tie(stride, y_height, yuv_size) = get_nv12_info(buf.out_img_width, buf.out_img_height); - uv_height = y_height / 2; + + // size is driven by all the HW that handles frames, + // the video encoder has certain alignment requirements in this case + std::tie(stride, y_height, uv_height, yuv_size) = get_nv12_info(buf.out_img_width, buf.out_img_height); uv_offset = stride * y_height; - if (cc.output_type != ISP_RAW_OUTPUT) { - uv_offset = ALIGNED_SIZE(uv_offset, 0x1000); - } open = true; configISP(); diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 983873c7f82..13cb13f98f6 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -12,7 +12,6 @@ #include "common/util.h" #include "common/swaglog.h" #include "system/camerad/cameras/hw.h" -#include "system/camerad/cameras/nv12_info.h" #include "system/camerad/cameras/camera_common.h" #include "system/camerad/sensors/sensor.h" diff --git a/system/camerad/snapshot.py b/system/camerad/snapshot.py index b3369891d7f..035a4acdcf4 100755 --- a/system/camerad/snapshot.py +++ b/system/camerad/snapshot.py @@ -43,9 +43,15 @@ def yuv_to_rgb(y, u, v): def extract_image(buf): + # NV12 format: Y plane followed by interleaved UV plane + # UV plane size is stride * uv_height, where uv_height = align(height/2, 16) + uv_height = ((buf.height // 2) + 15) // 16 * 16 + uv_plane_size = buf.stride * uv_height + y = np.array(buf.data[:buf.uv_offset], dtype=np.uint8).reshape((-1, buf.stride))[:buf.height, :buf.width] - u = np.array(buf.data[buf.uv_offset::2], dtype=np.uint8).reshape((-1, buf.stride//2))[:buf.height//2, :buf.width//2] - v = np.array(buf.data[buf.uv_offset+1::2], dtype=np.uint8).reshape((-1, buf.stride//2))[:buf.height//2, :buf.width//2] + uv_data = buf.data[buf.uv_offset:buf.uv_offset + uv_plane_size] + u = np.array(uv_data[::2], dtype=np.uint8).reshape((-1, buf.stride//2))[:buf.height//2, :buf.width//2] + v = np.array(uv_data[1::2], dtype=np.uint8).reshape((-1, buf.stride//2))[:buf.height//2, :buf.width//2] return yuv_to_rgb(y, u, v) diff --git a/third_party/linux/include/msm_media_info.h b/third_party/linux/include/msm_media_info.h index 39dceb2c49f..3fd0c8849a4 100644 --- a/third_party/linux/include/msm_media_info.h +++ b/third_party/linux/include/msm_media_info.h @@ -2,7 +2,9 @@ #define __MEDIA_INFO_H__ #ifndef MSM_MEDIA_ALIGN -#define MSM_MEDIA_ALIGN(__sz, __align) (((__sz) + (__align-1)) & (~(__align-1))) +#define MSM_MEDIA_ALIGN(__sz, __align) (((__align) & ((__align) - 1)) ?\ + ((((__sz) + (__align) - 1) / (__align)) * (__align)) :\ + (((__sz) + (__align) - 1) & (~((__align) - 1)))) #endif #ifndef MSM_MEDIA_ROUNDUP @@ -148,7 +150,12 @@ enum color_fmts { * + 2*(UV_Stride * UV_Scanlines) + Extradata), 4096) */ COLOR_FMT_NV12_MVTB, - /* Venus NV12 UBWC: + /* + * The buffer can be of 2 types: + * (1) Venus NV12 UBWC Progressive + * (2) Venus NV12 UBWC Interlaced + * + * (1) Venus NV12 UBWC Progressive Buffer Format: * Compressed Macro-tile format for NV12. * Contains 4 planes in the following order - * (A) Y_Meta_Plane @@ -234,6 +241,186 @@ enum color_fmts { * Total size = align( Y_UBWC_Plane_size + UV_UBWC_Plane_size + * Y_Meta_Plane_size + UV_Meta_Plane_size * + max(Extradata, Y_Stride * 48), 4096) + * + * + * (2) Venus NV12 UBWC Interlaced Buffer Format: + * Compressed Macro-tile format for NV12 interlaced. + * Contains 8 planes in the following order - + * (A) Y_Meta_Top_Field_Plane + * (B) Y_UBWC_Top_Field_Plane + * (C) UV_Meta_Top_Field_Plane + * (D) UV_UBWC_Top_Field_Plane + * (E) Y_Meta_Bottom_Field_Plane + * (F) Y_UBWC_Bottom_Field_Plane + * (G) UV_Meta_Bottom_Field_Plane + * (H) UV_UBWC_Bottom_Field_Plane + * Y_Meta_Top_Field_Plane consists of meta information to decode + * compressed tile data for Y_UBWC_Top_Field_Plane. + * Y_UBWC_Top_Field_Plane consists of Y data in compressed macro-tile + * format for top field of an interlaced frame. + * UBWC decoder block will use the Y_Meta_Top_Field_Plane data together + * with Y_UBWC_Top_Field_Plane data to produce loss-less uncompressed + * 8 bit Y samples for top field of an interlaced frame. + * + * UV_Meta_Top_Field_Plane consists of meta information to decode + * compressed tile data in UV_UBWC_Top_Field_Plane. + * UV_UBWC_Top_Field_Plane consists of UV data in compressed macro-tile + * format for top field of an interlaced frame. + * UBWC decoder block will use UV_Meta_Top_Field_Plane data together + * with UV_UBWC_Top_Field_Plane data to produce loss-less uncompressed + * 8 bit subsampled color difference samples for top field of an + * interlaced frame. + * + * Each tile in Y_UBWC_Top_Field_Plane/UV_UBWC_Top_Field_Plane is + * independently decodable and randomly accessible. There is no + * dependency between tiles. + * + * Y_Meta_Bottom_Field_Plane consists of meta information to decode + * compressed tile data for Y_UBWC_Bottom_Field_Plane. + * Y_UBWC_Bottom_Field_Plane consists of Y data in compressed macro-tile + * format for bottom field of an interlaced frame. + * UBWC decoder block will use the Y_Meta_Bottom_Field_Plane data + * together with Y_UBWC_Bottom_Field_Plane data to produce loss-less + * uncompressed 8 bit Y samples for bottom field of an interlaced frame. + * + * UV_Meta_Bottom_Field_Plane consists of meta information to decode + * compressed tile data in UV_UBWC_Bottom_Field_Plane. + * UV_UBWC_Bottom_Field_Plane consists of UV data in compressed + * macro-tile format for bottom field of an interlaced frame. + * UBWC decoder block will use UV_Meta_Bottom_Field_Plane data together + * with UV_UBWC_Bottom_Field_Plane data to produce loss-less + * uncompressed 8 bit subsampled color difference samples for bottom + * field of an interlaced frame. + * + * Each tile in Y_UBWC_Bottom_Field_Plane/UV_UBWC_Bottom_Field_Plane is + * independently decodable and randomly accessible. There is no + * dependency between tiles. + * + * <-----Y_TF_Meta_Stride----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Half_height | + * M M M M M M M M M M M M . . | Meta_Y_TF_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-Compressed tile Y_TF Stride-> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Half_height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_TF_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----UV_TF_Meta_Stride----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_TF_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <-Compressed tile UV_TF Stride-> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_TF_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <-----Y_BF_Meta_Stride----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Half_height | + * M M M M M M M M M M M M . . | Meta_Y_BF_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-Compressed tile Y_BF Stride-> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Half_height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_BF_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----UV_BF_Meta_Stride----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_BF_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <-Compressed tile UV_BF Stride-> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_BF_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * + * Half_height = (Height+1)>>1 + * Y_TF_Stride = align(Width, 128) + * UV_TF_Stride = align(Width, 128) + * Y_TF_Scanlines = align(Half_height, 32) + * UV_TF_Scanlines = align((Half_height+1)/2, 32) + * Y_UBWC_TF_Plane_size = align(Y_TF_Stride * Y_TF_Scanlines, 4096) + * UV_UBWC_TF_Plane_size = align(UV_TF_Stride * UV_TF_Scanlines, 4096) + * Y_TF_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_TF_Meta_Scanlines = align(roundup(Half_height, Y_TileHeight), 16) + * Y_TF_Meta_Plane_size = + * align(Y_TF_Meta_Stride * Y_TF_Meta_Scanlines, 4096) + * UV_TF_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_TF_Meta_Scanlines = align(roundup(Half_height, UV_TileHeight), 16) + * UV_TF_Meta_Plane_size = + * align(UV_TF_Meta_Stride * UV_TF_Meta_Scanlines, 4096) + * Y_BF_Stride = align(Width, 128) + * UV_BF_Stride = align(Width, 128) + * Y_BF_Scanlines = align(Half_height, 32) + * UV_BF_Scanlines = align((Half_height+1)/2, 32) + * Y_UBWC_BF_Plane_size = align(Y_BF_Stride * Y_BF_Scanlines, 4096) + * UV_UBWC_BF_Plane_size = align(UV_BF_Stride * UV_BF_Scanlines, 4096) + * Y_BF_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_BF_Meta_Scanlines = align(roundup(Half_height, Y_TileHeight), 16) + * Y_BF_Meta_Plane_size = + * align(Y_BF_Meta_Stride * Y_BF_Meta_Scanlines, 4096) + * UV_BF_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_BF_Meta_Scanlines = align(roundup(Half_height, UV_TileHeight), 16) + * UV_BF_Meta_Plane_size = + * align(UV_BF_Meta_Stride * UV_BF_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align( Y_UBWC_TF_Plane_size + UV_UBWC_TF_Plane_size + + * Y_TF_Meta_Plane_size + UV_TF_Meta_Plane_size + + * Y_UBWC_BF_Plane_size + UV_UBWC_BF_Plane_size + + * Y_BF_Meta_Plane_size + UV_BF_Meta_Plane_size + + * + max(Extradata, Y_TF_Stride * 48), 4096) */ COLOR_FMT_NV12_UBWC, /* Venus NV12 10-bit UBWC: @@ -399,8 +586,233 @@ enum color_fmts { * Extradata, 4096) */ COLOR_FMT_RGBA8888_UBWC, + /* Venus RGBA1010102 UBWC format: + * Contains 2 planes in the following order - + * (A) Meta plane + * (B) RGBA plane + * + * <--- RGB_Meta_Stride ----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_RGB_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-------- RGB_Stride --------> + * <------- Width -------> + * R R R R R R R R R R R R . . . . ^ ^ + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . Height | + * R R R R R R R R R R R R . . . . | RGB_Scanlines + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * + * RGB_Stride = align(Width * 4, 256) + * RGB_Scanlines = align(Height, 16) + * RGB_Plane_size = align(RGB_Stride * RGB_Scanlines, 4096) + * RGB_Meta_Stride = align(roundup(Width, RGB_TileWidth), 64) + * RGB_Meta_Scanline = align(roundup(Height, RGB_TileHeight), 16) + * RGB_Meta_Plane_size = align(RGB_Meta_Stride * + * RGB_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(RGB_Meta_Plane_size + RGB_Plane_size + + * Extradata, 4096) + */ + COLOR_FMT_RGBA1010102_UBWC, + /* Venus RGB565 UBWC format: + * Contains 2 planes in the following order - + * (A) Meta plane + * (B) RGB plane + * + * <--- RGB_Meta_Stride ----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_RGB_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-------- RGB_Stride --------> + * <------- Width -------> + * R R R R R R R R R R R R . . . . ^ ^ + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . Height | + * R R R R R R R R R R R R . . . . | RGB_Scanlines + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * + * RGB_Stride = align(Width * 2, 128) + * RGB_Scanlines = align(Height, 16) + * RGB_Plane_size = align(RGB_Stride * RGB_Scanlines, 4096) + * RGB_Meta_Stride = align(roundup(Width, RGB_TileWidth), 64) + * RGB_Meta_Scanline = align(roundup(Height, RGB_TileHeight), 16) + * RGB_Meta_Plane_size = align(RGB_Meta_Stride * + * RGB_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(RGB_Meta_Plane_size + RGB_Plane_size + + * Extradata, 4096) + */ + COLOR_FMT_RGB565_UBWC, + /* P010 UBWC: + * Compressed Macro-tile format for NV12. + * Contains 4 planes in the following order - + * (A) Y_Meta_Plane + * (B) Y_UBWC_Plane + * (C) UV_Meta_Plane + * (D) UV_UBWC_Plane + * + * Y_Meta_Plane consists of meta information to decode compressed + * tile data in Y_UBWC_Plane. + * Y_UBWC_Plane consists of Y data in compressed macro-tile format. + * UBWC decoder block will use the Y_Meta_Plane data together with + * Y_UBWC_Plane data to produce loss-less uncompressed 10 bit Y samples. + * + * UV_Meta_Plane consists of meta information to decode compressed + * tile data in UV_UBWC_Plane. + * UV_UBWC_Plane consists of UV data in compressed macro-tile format. + * UBWC decoder block will use UV_Meta_Plane data together with + * UV_UBWC_Plane data to produce loss-less uncompressed 10 bit 2x2 + * subsampled color difference samples. + * + * Each tile in Y_UBWC_Plane/UV_UBWC_Plane is independently decodable + * and randomly accessible. There is no dependency between tiles. + * + * <----- Y_Meta_Stride -----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_Y_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <--Compressed tile Y Stride---> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----- UV_Meta_Stride ----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <--Compressed tile UV Stride---> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * + * + * Y_Stride = align(Width * 2, 256) + * UV_Stride = align(Width * 2, 256) + * Y_Scanlines = align(Height, 16) + * UV_Scanlines = align(Height/2, 16) + * Y_UBWC_Plane_Size = align(Y_Stride * Y_Scanlines, 4096) + * UV_UBWC_Plane_Size = align(UV_Stride * UV_Scanlines, 4096) + * Y_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_Meta_Scanlines = align(roundup(Height, Y_TileHeight), 16) + * Y_Meta_Plane_size = align(Y_Meta_Stride * Y_Meta_Scanlines, 4096) + * UV_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_Meta_Scanlines = align(roundup(Height, UV_TileHeight), 16) + * UV_Meta_Plane_size = align(UV_Meta_Stride * UV_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(Y_UBWC_Plane_size + UV_UBWC_Plane_size + + * Y_Meta_Plane_size + UV_Meta_Plane_size + * + max(Extradata, Y_Stride * 48), 4096) + */ + COLOR_FMT_P010_UBWC, + /* Venus P010: + * YUV 4:2:0 image with a plane of 10 bit Y samples followed + * by an interleaved U/V plane containing 10 bit 2x2 subsampled + * colour difference samples. + * + * <-------- Y/UV_Stride --------> + * <------- Width -------> + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . ^ ^ + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . Height | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | Y_Scanlines + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * U V U V U V U V U V U V . . . . ^ + * U V U V U V U V U V U V . . . . | + * U V U V U V U V U V U V . . . . | + * U V U V U V U V U V U V . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . --> Buffer size alignment + * + * Y_Stride : Width * 2 aligned to 128 + * UV_Stride : Width * 2 aligned to 128 + * Y_Scanlines: Height aligned to 32 + * UV_Scanlines: Height/2 aligned to 16 + * Extradata: Arbitrary (software-imposed) padding + * Total size = align((Y_Stride * Y_Scanlines + * + UV_Stride * UV_Scanlines + * + max(Extradata, Y_Stride * 8), 4096) + */ + COLOR_FMT_P010, }; +#define COLOR_FMT_RGBA1010102_UBWC COLOR_FMT_RGBA1010102_UBWC +#define COLOR_FMT_RGB565_UBWC COLOR_FMT_RGB565_UBWC +#define COLOR_FMT_P010_UBWC COLOR_FMT_P010_UBWC +#define COLOR_FMT_P010 COLOR_FMT_P010 + static inline unsigned int VENUS_EXTRADATA_SIZE(int width, int height) { (void)height; @@ -413,9 +825,17 @@ static inline unsigned int VENUS_EXTRADATA_SIZE(int width, int height) return 16 * 1024; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ static inline unsigned int VENUS_Y_STRIDE(int color_fmt, int width) { unsigned int alignment, stride = 0; + if (!width) goto invalid_input; @@ -432,6 +852,14 @@ static inline unsigned int VENUS_Y_STRIDE(int color_fmt, int width) stride = MSM_MEDIA_ALIGN(width, 192); stride = MSM_MEDIA_ALIGN(stride * 4/3, alignment); break; + case COLOR_FMT_P010_UBWC: + alignment = 256; + stride = MSM_MEDIA_ALIGN(width * 2, alignment); + break; + case COLOR_FMT_P010: + alignment = 128; + stride = MSM_MEDIA_ALIGN(width*2, alignment); + break; default: break; } @@ -439,9 +867,17 @@ static inline unsigned int VENUS_Y_STRIDE(int color_fmt, int width) return stride; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ static inline unsigned int VENUS_UV_STRIDE(int color_fmt, int width) { unsigned int alignment, stride = 0; + if (!width) goto invalid_input; @@ -458,6 +894,14 @@ static inline unsigned int VENUS_UV_STRIDE(int color_fmt, int width) stride = MSM_MEDIA_ALIGN(width, 192); stride = MSM_MEDIA_ALIGN(stride * 4/3, alignment); break; + case COLOR_FMT_P010_UBWC: + alignment = 256; + stride = MSM_MEDIA_ALIGN(width * 2, alignment); + break; + case COLOR_FMT_P010: + alignment = 128; + stride = MSM_MEDIA_ALIGN(width*2, alignment); + break; default: break; } @@ -465,9 +909,17 @@ static inline unsigned int VENUS_UV_STRIDE(int color_fmt, int width) return stride; } +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ static inline unsigned int VENUS_Y_SCANLINES(int color_fmt, int height) { unsigned int alignment, sclines = 0; + if (!height) goto invalid_input; @@ -476,9 +928,11 @@ static inline unsigned int VENUS_Y_SCANLINES(int color_fmt, int height) case COLOR_FMT_NV12: case COLOR_FMT_NV12_MVTB: case COLOR_FMT_NV12_UBWC: + case COLOR_FMT_P010: alignment = 32; break; case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: alignment = 16; break; default: @@ -489,9 +943,17 @@ static inline unsigned int VENUS_Y_SCANLINES(int color_fmt, int height) return sclines; } +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ static inline unsigned int VENUS_UV_SCANLINES(int color_fmt, int height) { unsigned int alignment, sclines = 0; + if (!height) goto invalid_input; @@ -500,6 +962,8 @@ static inline unsigned int VENUS_UV_SCANLINES(int color_fmt, int height) case COLOR_FMT_NV12: case COLOR_FMT_NV12_MVTB: case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: + case COLOR_FMT_P010: alignment = 16; break; case COLOR_FMT_NV12_UBWC: @@ -509,12 +973,19 @@ static inline unsigned int VENUS_UV_SCANLINES(int color_fmt, int height) goto invalid_input; } - sclines = MSM_MEDIA_ALIGN(height / 2, alignment); + sclines = MSM_MEDIA_ALIGN((height+1)>>1, alignment); invalid_input: return sclines; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ static inline unsigned int VENUS_Y_META_STRIDE(int color_fmt, int width) { int y_tile_width = 0, y_meta_stride = 0; @@ -524,6 +995,7 @@ static inline unsigned int VENUS_Y_META_STRIDE(int color_fmt, int width) switch (color_fmt) { case COLOR_FMT_NV12_UBWC: + case COLOR_FMT_P010_UBWC: y_tile_width = 32; break; case COLOR_FMT_NV12_BPP10_UBWC: @@ -540,6 +1012,13 @@ static inline unsigned int VENUS_Y_META_STRIDE(int color_fmt, int width) return y_meta_stride; } +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ static inline unsigned int VENUS_Y_META_SCANLINES(int color_fmt, int height) { int y_tile_height = 0, y_meta_scanlines = 0; @@ -552,6 +1031,7 @@ static inline unsigned int VENUS_Y_META_SCANLINES(int color_fmt, int height) y_tile_height = 8; break; case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: y_tile_height = 4; break; default: @@ -565,6 +1045,13 @@ static inline unsigned int VENUS_Y_META_SCANLINES(int color_fmt, int height) return y_meta_scanlines; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ static inline unsigned int VENUS_UV_META_STRIDE(int color_fmt, int width) { int uv_tile_width = 0, uv_meta_stride = 0; @@ -574,6 +1061,7 @@ static inline unsigned int VENUS_UV_META_STRIDE(int color_fmt, int width) switch (color_fmt) { case COLOR_FMT_NV12_UBWC: + case COLOR_FMT_P010_UBWC: uv_tile_width = 16; break; case COLOR_FMT_NV12_BPP10_UBWC: @@ -583,13 +1071,20 @@ static inline unsigned int VENUS_UV_META_STRIDE(int color_fmt, int width) goto invalid_input; } - uv_meta_stride = MSM_MEDIA_ROUNDUP(width / 2, uv_tile_width); + uv_meta_stride = MSM_MEDIA_ROUNDUP((width+1)>>1, uv_tile_width); uv_meta_stride = MSM_MEDIA_ALIGN(uv_meta_stride, 64); invalid_input: return uv_meta_stride; } +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ static inline unsigned int VENUS_UV_META_SCANLINES(int color_fmt, int height) { int uv_tile_height = 0, uv_meta_scanlines = 0; @@ -602,13 +1097,14 @@ static inline unsigned int VENUS_UV_META_SCANLINES(int color_fmt, int height) uv_tile_height = 8; break; case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: uv_tile_height = 4; break; default: goto invalid_input; } - uv_meta_scanlines = MSM_MEDIA_ROUNDUP(height / 2, uv_tile_height); + uv_meta_scanlines = MSM_MEDIA_ROUNDUP((height+1)>>1, uv_tile_height); uv_meta_scanlines = MSM_MEDIA_ALIGN(uv_meta_scanlines, 16); invalid_input: @@ -617,7 +1113,8 @@ static inline unsigned int VENUS_UV_META_SCANLINES(int color_fmt, int height) static inline unsigned int VENUS_RGB_STRIDE(int color_fmt, int width) { - unsigned int alignment = 0, stride = 0; + unsigned int alignment = 0, stride = 0, bpp = 4; + if (!width) goto invalid_input; @@ -625,14 +1122,19 @@ static inline unsigned int VENUS_RGB_STRIDE(int color_fmt, int width) case COLOR_FMT_RGBA8888: alignment = 128; break; + case COLOR_FMT_RGB565_UBWC: + alignment = 256; + bpp = 2; + break; case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: alignment = 256; break; default: goto invalid_input; } - stride = MSM_MEDIA_ALIGN(width * 4, alignment); + stride = MSM_MEDIA_ALIGN(width * bpp, alignment); invalid_input: return stride; @@ -650,6 +1152,8 @@ static inline unsigned int VENUS_RGB_SCANLINES(int color_fmt, int height) alignment = 32; break; case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: alignment = 16; break; default: @@ -671,6 +1175,8 @@ static inline unsigned int VENUS_RGB_META_STRIDE(int color_fmt, int width) switch (color_fmt) { case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: rgb_tile_width = 16; break; default: @@ -693,6 +1199,8 @@ static inline unsigned int VENUS_RGB_META_SCANLINES(int color_fmt, int height) switch (color_fmt) { case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: rgb_tile_height = 4; break; default: @@ -706,11 +1214,22 @@ static inline unsigned int VENUS_RGB_META_SCANLINES(int color_fmt, int height) return rgb_meta_scanlines; } +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + * @height + * Progressive: height + * Interlaced: height + */ static inline unsigned int VENUS_BUFFER_SIZE( int color_fmt, int width, int height) { const unsigned int extra_size = VENUS_EXTRADATA_SIZE(width, height); unsigned int uv_alignment = 0, size = 0; + unsigned int w_alignment = 512; unsigned int y_plane, uv_plane, y_stride, uv_stride, y_sclines, uv_sclines; unsigned int y_ubwc_plane = 0, uv_ubwc_plane = 0; @@ -740,6 +1259,18 @@ static inline unsigned int VENUS_BUFFER_SIZE( size = y_plane + uv_plane + MSM_MEDIA_MAX(extra_size, 8 * y_stride); size = MSM_MEDIA_ALIGN(size, 4096); + + /* Additional size to cover last row of non-aligned frame */ + size += MSM_MEDIA_ALIGN(width, w_alignment) * w_alignment; + size = MSM_MEDIA_ALIGN(size, 4096); + break; + case COLOR_FMT_P010: + uv_alignment = 4096; + y_plane = y_stride * y_sclines; + uv_plane = uv_stride * uv_sclines + uv_alignment; + size = y_plane + uv_plane + + MSM_MEDIA_MAX(extra_size, 8 * y_stride); + size = MSM_MEDIA_ALIGN(size, 4096); break; case COLOR_FMT_NV12_MVTB: uv_alignment = 4096; @@ -750,6 +1281,30 @@ static inline unsigned int VENUS_BUFFER_SIZE( size = MSM_MEDIA_ALIGN(size, 4096); break; case COLOR_FMT_NV12_UBWC: + y_sclines = VENUS_Y_SCANLINES(color_fmt, (height+1)>>1); + y_ubwc_plane = MSM_MEDIA_ALIGN(y_stride * y_sclines, 4096); + uv_sclines = VENUS_UV_SCANLINES(color_fmt, (height+1)>>1); + uv_ubwc_plane = MSM_MEDIA_ALIGN(uv_stride * uv_sclines, 4096); + y_meta_stride = VENUS_Y_META_STRIDE(color_fmt, width); + y_meta_scanlines = + VENUS_Y_META_SCANLINES(color_fmt, (height+1)>>1); + y_meta_plane = MSM_MEDIA_ALIGN( + y_meta_stride * y_meta_scanlines, 4096); + uv_meta_stride = VENUS_UV_META_STRIDE(color_fmt, width); + uv_meta_scanlines = + VENUS_UV_META_SCANLINES(color_fmt, (height+1)>>1); + uv_meta_plane = MSM_MEDIA_ALIGN(uv_meta_stride * + uv_meta_scanlines, 4096); + + size = (y_ubwc_plane + uv_ubwc_plane + y_meta_plane + + uv_meta_plane)*2 + + MSM_MEDIA_MAX(extra_size + 8192, 48 * y_stride); + size = MSM_MEDIA_ALIGN(size, 4096); + + /* Additional size to cover last row of non-aligned frame */ + size += MSM_MEDIA_ALIGN(width, w_alignment) * w_alignment; + size = MSM_MEDIA_ALIGN(size, 4096); + break; case COLOR_FMT_NV12_BPP10_UBWC: y_ubwc_plane = MSM_MEDIA_ALIGN(y_stride * y_sclines, 4096); uv_ubwc_plane = MSM_MEDIA_ALIGN(uv_stride * uv_sclines, 4096); @@ -767,12 +1322,30 @@ static inline unsigned int VENUS_BUFFER_SIZE( MSM_MEDIA_MAX(extra_size + 8192, 48 * y_stride); size = MSM_MEDIA_ALIGN(size, 4096); break; + case COLOR_FMT_P010_UBWC: + y_ubwc_plane = MSM_MEDIA_ALIGN(y_stride * y_sclines, 4096); + uv_ubwc_plane = MSM_MEDIA_ALIGN(uv_stride * uv_sclines, 4096); + y_meta_stride = VENUS_Y_META_STRIDE(color_fmt, width); + y_meta_scanlines = VENUS_Y_META_SCANLINES(color_fmt, height); + y_meta_plane = MSM_MEDIA_ALIGN( + y_meta_stride * y_meta_scanlines, 4096); + uv_meta_stride = VENUS_UV_META_STRIDE(color_fmt, width); + uv_meta_scanlines = VENUS_UV_META_SCANLINES(color_fmt, height); + uv_meta_plane = MSM_MEDIA_ALIGN(uv_meta_stride * + uv_meta_scanlines, 4096); + + size = y_ubwc_plane + uv_ubwc_plane + y_meta_plane + + uv_meta_plane; + size = MSM_MEDIA_ALIGN(size, 4096); + break; case COLOR_FMT_RGBA8888: rgb_plane = MSM_MEDIA_ALIGN(rgb_stride * rgb_scanlines, 4096); size = rgb_plane; size = MSM_MEDIA_ALIGN(size, 4096); break; case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: rgb_ubwc_plane = MSM_MEDIA_ALIGN(rgb_stride * rgb_scanlines, 4096); rgb_meta_stride = VENUS_RGB_META_STRIDE(color_fmt, width); diff --git a/tools/replay/camera.cc b/tools/replay/camera.cc index 2962195aff5..671c0320bb0 100644 --- a/tools/replay/camera.cc +++ b/tools/replay/camera.cc @@ -1,14 +1,11 @@ #include "tools/replay/camera.h" -#include #include #include -#include "third_party/linux/include/msm_media_info.h" -#include "tools/replay/util.h" #include "system/camerad/cameras/nv12_info.h" - +#include "tools/replay/util.h" const int BUFFER_COUNT = 40; @@ -43,9 +40,10 @@ void CameraServer::startVipcServer() { if (cam.width > 0 && cam.height > 0) { rInfo("camera[%d] frame size %dx%d", cam.type, cam.width, cam.height); - auto [nv12_width, nv12_height, nv12_buffer_size] = get_nv12_info(cam.width, cam.height); + auto [stride, y_height, uv_height_, buffer_size] = get_nv12_info(cam.width, cam.height); + (void)uv_height_; // unused in replay vipc_server_->create_buffers_with_sizes(cam.stream_type, BUFFER_COUNT, cam.width, cam.height, - nv12_buffer_size, nv12_width, nv12_width * nv12_height); + buffer_size, stride, stride * y_height); if (!cam.thread.joinable()) { cam.thread = std::thread(&CameraServer::cameraThread, this, std::ref(cam)); } From a7dfd36c00c910b694dfd3e2b3f6e0a8cc892802 Mon Sep 17 00:00:00 2001 From: Lukas <61192133+lukasloetkolben@users.noreply.github.com> Date: Wed, 21 Jan 2026 00:15:12 +0100 Subject: [PATCH 059/685] docs: comma 3X to comma four (#37009) * comma 3X -> comma four * add comma four ports image --- README.md | 8 ++++---- docs/CONTRIBUTING.md | 8 ++++---- docs/assets/four-ports.svg | 3 +++ docs/concepts/glossary.md | 2 +- docs/getting-started/what-is-openpilot.md | 2 +- docs/how-to/connect-to-comma.md | 17 +++++++++-------- docs/how-to/replay-a-drive.md | 2 +- mkdocs.yml | 2 +- selfdrive/debug/README.md | 2 +- tools/camerastream/README.md | 2 +- tools/joystick/joystick_control.py | 4 ++-- tools/replay/README.md | 2 +- 12 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 docs/assets/four-ports.svg diff --git a/README.md b/README.md index a77a80935d5..158bb08c49b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ · Community · - Try it on a comma 3X + Try it on a comma four Quick start: `bash <(curl -fsSL openpilot.comma.ai)` @@ -42,10 +42,10 @@ Using openpilot in a car ------ To use openpilot in a car, you need four things: -1. **Supported Device:** a comma 3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). -2. **Software:** The setup procedure for the comma 3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. +1. **Supported Device:** a comma four, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). +2. **Software:** The setup procedure for the comma four allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. 3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md). -4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3X to your car. +4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma four to your car. We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7583095eaf4..d189324ff75 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -13,13 +13,13 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu ## What contributions are we looking for? **openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** -openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. +openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. ### What gets merged? The probability of a pull request being merged is a function of its value to the project and the effort it will take us to get it merged. If a PR offers *some* value but will take lots of time to get merged, it will be closed. -Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. +Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. All of these are examples of good PRs: * typo fix: https://github.com/commaai/openpilot/pull/30678 @@ -29,7 +29,7 @@ All of these are examples of good PRs: ### What doesn't get merged? -* **style changes**: code is art, and it's up to the author to make it beautiful +* **style changes**: code is art, and it's up to the author to make it beautiful * **500+ line PRs**: clean it up, break it up into smaller PRs, or both * **PRs without a clear goal**: every PR must have a singular and clear goal * **UI design**: we do not have a good review process for this yet @@ -39,7 +39,7 @@ All of these are examples of good PRs: ### First contribution [Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty. -There's lot of bounties that don't require a comma 3X or a car. +There's lot of bounties that don't require a comma four or a car. ## Pull Requests diff --git a/docs/assets/four-ports.svg b/docs/assets/four-ports.svg new file mode 100644 index 00000000000..7bab31abf3a --- /dev/null +++ b/docs/assets/four-ports.svg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:494bd79c4d81d8bf766845cb451fa14e5b9ad931ff8aa90daea0ba67e164abe3 +size 105562 diff --git a/docs/concepts/glossary.md b/docs/concepts/glossary.md index a09b0f07853..df9e9aa0804 100644 --- a/docs/concepts/glossary.md +++ b/docs/concepts/glossary.md @@ -6,4 +6,4 @@ * **segment**: routes are split into one minute chunks called segments. * **comma connect**: the web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai). * **panda**: this is the secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda). -* **comma 3X**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop). +* **comma four**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop). diff --git a/docs/getting-started/what-is-openpilot.md b/docs/getting-started/what-is-openpilot.md index b3c56c8410d..6fab2b979b1 100644 --- a/docs/getting-started/what-is-openpilot.md +++ b/docs/getting-started/what-is-openpilot.md @@ -5,7 +5,7 @@ ## How do I use it? -openpilot is designed to be used on the comma 3X. +openpilot is designed to be used on the comma four. ## How does it work? diff --git a/docs/how-to/connect-to-comma.md b/docs/how-to/connect-to-comma.md index 5f02e115994..f0e45b19aff 100644 --- a/docs/how-to/connect-to-comma.md +++ b/docs/how-to/connect-to-comma.md @@ -1,19 +1,20 @@ -# connect to a comma 3X +# Connect to comma 3X or comma four -A comma 3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). +A comma four is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). ## Serial Console -On both the comma three and 3X, the serial console is accessible from the main OBD-C port. +On the comma 3X, the serial console is accessible from the main OBD-C port. Connect the comma 3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power. -On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect. - -On the comma 3X, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. +The serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. * Username: `comma` * Password: `comma` +> [!NOTE] +> Serial console access through the OBD-C port is not available on the comma four. On comma four devices, serial access requires opening the device to access the internal debug connector. + ## SSH In order to SSH into your device, you'll need a GitHub account with SSH keys. See this [GitHub article](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) for getting your account setup with SSH keys. @@ -34,7 +35,7 @@ For doing development work on device, it's recommended to use [SSH agent forward In order to use ADB on your device, you'll need to perform the following steps using the image below for reference: -![comma 3/3x back](../assets/three-back.svg) +![comma four ports](../assets/four-ports.svg) * Plug your device into constant power using port 2, letting the device boot up * Enable ADB in your device's settings @@ -45,7 +46,7 @@ In order to use ADB on your device, you'll need to perform the following steps u * Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555` > [!NOTE] -> The default port for ADB is 5555 on the comma 3X. +> The default port for ADB is 5555 on the comma four. For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb). diff --git a/docs/how-to/replay-a-drive.md b/docs/how-to/replay-a-drive.md index b0db36a46f0..a11b29dcc4e 100644 --- a/docs/how-to/replay-a-drive.md +++ b/docs/how-to/replay-a-drive.md @@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging. Just run `tools/replay/replay --demo`. ## Replaying CAN data -*Hardware required: jungle and comma 3X* +*Hardware required: jungle and comma four* 1. Connect your PC to a jungle. 2. diff --git a/mkdocs.yml b/mkdocs.yml index 550f807aca0..f54c6e39bbf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,7 +21,7 @@ nav: - What is openpilot?: getting-started/what-is-openpilot.md - How-to: - Turn the speed blue: how-to/turn-the-speed-blue.md - - Connect to a comma 3X: how-to/connect-to-comma.md + - Connect to a comma 3X or comma four: how-to/connect-to-comma.md # - Make your first pull request: how-to/make-first-pr.md #- Replay a drive: how-to/replay-a-drive.md - Concepts: diff --git a/selfdrive/debug/README.md b/selfdrive/debug/README.md index 83b8a994db1..172cf700e48 100644 --- a/selfdrive/debug/README.md +++ b/selfdrive/debug/README.md @@ -52,7 +52,7 @@ optional arguments: -h, --help show this help message and exit --debug enable ISO-TP/UDS stack debugging output -This tool is meant to run directly on a vehicle-installed comma three, with +This tool is meant to run directly on a vehicle-installed comma four, with the openpilot/tmux processes stopped. It should also work on a separate PC with a USB- attached comma panda. Vehicle ignition must be on. Recommend engine not be running when making changes. Must turn ignition off and on again for any changes to take effect. diff --git a/tools/camerastream/README.md b/tools/camerastream/README.md index 2f7498a07c0..f75ebbf0fb1 100644 --- a/tools/camerastream/README.md +++ b/tools/camerastream/README.md @@ -49,7 +49,7 @@ usage: compressed_vipc.py [-h] [--nvidia] [--cams CAMS] [--silent] addr Decode video streams and broadcast on VisionIPC positional arguments: - addr Address of comma three + addr Address of comma four options: -h, --help show this help message and exit diff --git a/tools/joystick/joystick_control.py b/tools/joystick/joystick_control.py index 11d17e587e8..8fe28ec0f04 100755 --- a/tools/joystick/joystick_control.py +++ b/tools/joystick/joystick_control.py @@ -42,7 +42,7 @@ def update(self): class Joystick: def __init__(self): - # This class supports a PlayStation 5 DualSense controller on the comma 3X + # This class supports a PlayStation 5 DualSense controller on the comma four # TODO: find a way to get this from API or detect gamepad/PC, perhaps "inputs" doesn't support it self.cancel_button = 'BTN_NORTH' # BTN_NORTH=X/triangle if HARDWARE.get_device_type() == 'pc': @@ -123,7 +123,7 @@ def main(): if __name__ == '__main__': parser = argparse.ArgumentParser(description='Publishes events from your joystick to control your car.\n' + 'openpilot must be offroad before starting joystick_control. This tool supports ' + - 'a PlayStation 5 DualSense controller on the comma 3X.', + 'a PlayStation 5 DualSense controller on the comma four.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--keyboard', action='store_true', help='Use your keyboard instead of a joystick') args = parser.parse_args() diff --git a/tools/replay/README.md b/tools/replay/README.md index 794c08f6a39..97ee91d9885 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -101,7 +101,7 @@ tools/plotjuggler/juggle.py --stream ## watch3 -watch all three cameras simultaneously from your comma three routes with watch3 +watch all three cameras simultaneously from your comma four routes with watch3 simply replay a route using the `--dcam` and `--ecam` flags: From adf6f28ebf31fc3dcc502a5d761bf113d0d46546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 20 Jan 2026 15:34:57 -0800 Subject: [PATCH 060/685] LatcontrolTorque: always fill buffer (#36991) --- selfdrive/controls/lib/latcontrol_torque.py | 17 +++++++++-------- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 0ba38736db4..1f7fb4dfa42 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -59,27 +59,28 @@ def update_limits(self): def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay): pid_log = log.ControlsState.LateralTorqueState.new_message() pid_log.version = VERSION + measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) + measurement = measured_curvature * CS.vEgo ** 2 + future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 + self.lat_accel_request_buffer.append(future_desired_lateral_accel) + if not active: output_torque = 0.0 pid_log.active = False else: - measured_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - delay_frames = int(np.clip(lat_delay / self.dt, 1, self.lat_accel_request_buffer_len)) + delay_frames = int(np.clip(lat_delay / self.dt + 1, 1, self.lat_accel_request_buffer_len)) expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] + setpoint = expected_lateral_accel + error = setpoint - measurement + lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) - future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 - self.lat_accel_request_buffer.append(future_desired_lateral_accel) gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - setpoint = expected_lateral_accel - - measurement = measured_curvature * CS.vEgo ** 2 - error = setpoint - measurement # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index c109bf49f12..274bec8b4fa 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -b259f6f8f099a9d82e4c65dd5deae2e4e293007b \ No newline at end of file +cdd8ecaf03b0581d6a4df7659b916f3d22167a23 \ No newline at end of file From 79472cdf83c65345e666f40069ac34082bb4be84 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 20 Jan 2026 16:04:05 -0800 Subject: [PATCH 061/685] Revert "docs: comma 3X to comma four (#37009)" This reverts commit a7dfd36c00c910b694dfd3e2b3f6e0a8cc892802. --- README.md | 8 ++++---- docs/CONTRIBUTING.md | 8 ++++---- docs/assets/four-ports.svg | 3 --- docs/concepts/glossary.md | 2 +- docs/getting-started/what-is-openpilot.md | 2 +- docs/how-to/connect-to-comma.md | 17 ++++++++--------- docs/how-to/replay-a-drive.md | 2 +- mkdocs.yml | 2 +- selfdrive/debug/README.md | 2 +- tools/camerastream/README.md | 2 +- tools/joystick/joystick_control.py | 4 ++-- tools/replay/README.md | 2 +- 12 files changed, 25 insertions(+), 29 deletions(-) delete mode 100644 docs/assets/four-ports.svg diff --git a/README.md b/README.md index 158bb08c49b..a77a80935d5 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ · Community · - Try it on a comma four + Try it on a comma 3X Quick start: `bash <(curl -fsSL openpilot.comma.ai)` @@ -42,10 +42,10 @@ Using openpilot in a car ------ To use openpilot in a car, you need four things: -1. **Supported Device:** a comma four, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). -2. **Software:** The setup procedure for the comma four allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. +1. **Supported Device:** a comma 3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). +2. **Software:** The setup procedure for the comma 3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. 3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md). -4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma four to your car. +4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3X to your car. We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index d189324ff75..7583095eaf4 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -13,13 +13,13 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu ## What contributions are we looking for? **openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** -openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. +openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. ### What gets merged? The probability of a pull request being merged is a function of its value to the project and the effort it will take us to get it merged. If a PR offers *some* value but will take lots of time to get merged, it will be closed. -Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. +Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. All of these are examples of good PRs: * typo fix: https://github.com/commaai/openpilot/pull/30678 @@ -29,7 +29,7 @@ All of these are examples of good PRs: ### What doesn't get merged? -* **style changes**: code is art, and it's up to the author to make it beautiful +* **style changes**: code is art, and it's up to the author to make it beautiful * **500+ line PRs**: clean it up, break it up into smaller PRs, or both * **PRs without a clear goal**: every PR must have a singular and clear goal * **UI design**: we do not have a good review process for this yet @@ -39,7 +39,7 @@ All of these are examples of good PRs: ### First contribution [Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty. -There's lot of bounties that don't require a comma four or a car. +There's lot of bounties that don't require a comma 3X or a car. ## Pull Requests diff --git a/docs/assets/four-ports.svg b/docs/assets/four-ports.svg deleted file mode 100644 index 7bab31abf3a..00000000000 --- a/docs/assets/four-ports.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:494bd79c4d81d8bf766845cb451fa14e5b9ad931ff8aa90daea0ba67e164abe3 -size 105562 diff --git a/docs/concepts/glossary.md b/docs/concepts/glossary.md index df9e9aa0804..a09b0f07853 100644 --- a/docs/concepts/glossary.md +++ b/docs/concepts/glossary.md @@ -6,4 +6,4 @@ * **segment**: routes are split into one minute chunks called segments. * **comma connect**: the web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai). * **panda**: this is the secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda). -* **comma four**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop). +* **comma 3X**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop). diff --git a/docs/getting-started/what-is-openpilot.md b/docs/getting-started/what-is-openpilot.md index 6fab2b979b1..b3c56c8410d 100644 --- a/docs/getting-started/what-is-openpilot.md +++ b/docs/getting-started/what-is-openpilot.md @@ -5,7 +5,7 @@ ## How do I use it? -openpilot is designed to be used on the comma four. +openpilot is designed to be used on the comma 3X. ## How does it work? diff --git a/docs/how-to/connect-to-comma.md b/docs/how-to/connect-to-comma.md index f0e45b19aff..5f02e115994 100644 --- a/docs/how-to/connect-to-comma.md +++ b/docs/how-to/connect-to-comma.md @@ -1,20 +1,19 @@ -# Connect to comma 3X or comma four +# connect to a comma 3X -A comma four is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). +A comma 3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). ## Serial Console -On the comma 3X, the serial console is accessible from the main OBD-C port. +On both the comma three and 3X, the serial console is accessible from the main OBD-C port. Connect the comma 3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power. -The serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. +On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect. + +On the comma 3X, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. * Username: `comma` * Password: `comma` -> [!NOTE] -> Serial console access through the OBD-C port is not available on the comma four. On comma four devices, serial access requires opening the device to access the internal debug connector. - ## SSH In order to SSH into your device, you'll need a GitHub account with SSH keys. See this [GitHub article](https://docs.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh) for getting your account setup with SSH keys. @@ -35,7 +34,7 @@ For doing development work on device, it's recommended to use [SSH agent forward In order to use ADB on your device, you'll need to perform the following steps using the image below for reference: -![comma four ports](../assets/four-ports.svg) +![comma 3/3x back](../assets/three-back.svg) * Plug your device into constant power using port 2, letting the device boot up * Enable ADB in your device's settings @@ -46,7 +45,7 @@ In order to use ADB on your device, you'll need to perform the following steps u * Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555` > [!NOTE] -> The default port for ADB is 5555 on the comma four. +> The default port for ADB is 5555 on the comma 3X. For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb). diff --git a/docs/how-to/replay-a-drive.md b/docs/how-to/replay-a-drive.md index a11b29dcc4e..b0db36a46f0 100644 --- a/docs/how-to/replay-a-drive.md +++ b/docs/how-to/replay-a-drive.md @@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging. Just run `tools/replay/replay --demo`. ## Replaying CAN data -*Hardware required: jungle and comma four* +*Hardware required: jungle and comma 3X* 1. Connect your PC to a jungle. 2. diff --git a/mkdocs.yml b/mkdocs.yml index f54c6e39bbf..550f807aca0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -21,7 +21,7 @@ nav: - What is openpilot?: getting-started/what-is-openpilot.md - How-to: - Turn the speed blue: how-to/turn-the-speed-blue.md - - Connect to a comma 3X or comma four: how-to/connect-to-comma.md + - Connect to a comma 3X: how-to/connect-to-comma.md # - Make your first pull request: how-to/make-first-pr.md #- Replay a drive: how-to/replay-a-drive.md - Concepts: diff --git a/selfdrive/debug/README.md b/selfdrive/debug/README.md index 172cf700e48..83b8a994db1 100644 --- a/selfdrive/debug/README.md +++ b/selfdrive/debug/README.md @@ -52,7 +52,7 @@ optional arguments: -h, --help show this help message and exit --debug enable ISO-TP/UDS stack debugging output -This tool is meant to run directly on a vehicle-installed comma four, with +This tool is meant to run directly on a vehicle-installed comma three, with the openpilot/tmux processes stopped. It should also work on a separate PC with a USB- attached comma panda. Vehicle ignition must be on. Recommend engine not be running when making changes. Must turn ignition off and on again for any changes to take effect. diff --git a/tools/camerastream/README.md b/tools/camerastream/README.md index f75ebbf0fb1..2f7498a07c0 100644 --- a/tools/camerastream/README.md +++ b/tools/camerastream/README.md @@ -49,7 +49,7 @@ usage: compressed_vipc.py [-h] [--nvidia] [--cams CAMS] [--silent] addr Decode video streams and broadcast on VisionIPC positional arguments: - addr Address of comma four + addr Address of comma three options: -h, --help show this help message and exit diff --git a/tools/joystick/joystick_control.py b/tools/joystick/joystick_control.py index 8fe28ec0f04..11d17e587e8 100755 --- a/tools/joystick/joystick_control.py +++ b/tools/joystick/joystick_control.py @@ -42,7 +42,7 @@ def update(self): class Joystick: def __init__(self): - # This class supports a PlayStation 5 DualSense controller on the comma four + # This class supports a PlayStation 5 DualSense controller on the comma 3X # TODO: find a way to get this from API or detect gamepad/PC, perhaps "inputs" doesn't support it self.cancel_button = 'BTN_NORTH' # BTN_NORTH=X/triangle if HARDWARE.get_device_type() == 'pc': @@ -123,7 +123,7 @@ def main(): if __name__ == '__main__': parser = argparse.ArgumentParser(description='Publishes events from your joystick to control your car.\n' + 'openpilot must be offroad before starting joystick_control. This tool supports ' + - 'a PlayStation 5 DualSense controller on the comma four.', + 'a PlayStation 5 DualSense controller on the comma 3X.', formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--keyboard', action='store_true', help='Use your keyboard instead of a joystick') args = parser.parse_args() diff --git a/tools/replay/README.md b/tools/replay/README.md index 97ee91d9885..794c08f6a39 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -101,7 +101,7 @@ tools/plotjuggler/juggle.py --stream ## watch3 -watch all three cameras simultaneously from your comma four routes with watch3 +watch all three cameras simultaneously from your comma three routes with watch3 simply replay a route using the `--dcam` and `--ecam` flags: From bc979ea6aa858a811b8ecb75485577ecf10f251d Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Tue, 20 Jan 2026 16:16:38 -0800 Subject: [PATCH 062/685] Latcontrol torque test: ensure desired lateral accel buffer is consistent (#37004) --- .../tests/test_latcontrol_torque_buffer.py | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 selfdrive/controls/tests/test_latcontrol_torque_buffer.py diff --git a/selfdrive/controls/tests/test_latcontrol_torque_buffer.py b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py new file mode 100644 index 00000000000..76d0c28423c --- /dev/null +++ b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py @@ -0,0 +1,36 @@ +from parameterized import parameterized + +from cereal import car, log +from opendbc.car.car_helpers import interfaces +from opendbc.car.toyota.values import CAR as TOYOTA +from opendbc.car.vehicle_model import VehicleModel +from openpilot.common.realtime import DT_CTRL +from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque, LAT_ACCEL_REQUEST_BUFFER_SECONDS + +def get_controller(car_name): + CarInterface = interfaces[car_name] + CP = CarInterface.get_non_essential_params(car_name) + CI = CarInterface(CP) + VM = VehicleModel(CP) + controller = LatControlTorque(CP.as_reader(), CI, DT_CTRL) + return controller, VM + +class TestLatControlTorqueBuffer: + + @parameterized.expand([(TOYOTA.TOYOTA_COROLLA_TSS2,)]) + def test_request_buffer_consistency(self, car_name): + buffer_steps = int(LAT_ACCEL_REQUEST_BUFFER_SECONDS / DT_CTRL) + controller, VM = get_controller(car_name) + + CS = car.CarState.new_message() + CS.vEgo = 30 + CS.steeringPressed = False + params = log.LiveParametersData.new_message() + + for _ in range(buffer_steps): + controller.update(True, CS, VM, params, False, 0.001, False, 0.2) + assert all(val != 0 for val in controller.lat_accel_request_buffer) + + for _ in range(buffer_steps): + controller.update(False, CS, VM, params, False, 0.0, False, 0.2) + assert all(val == 0 for val in controller.lat_accel_request_buffer) From c9cfe2c7273074a350a39fd4d78d16933cb6a81f Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 21 Jan 2026 12:32:56 -0800 Subject: [PATCH 063/685] LatcontrolTorque: move jerk calculation and filtering outside if else (#37011) --- selfdrive/controls/lib/latcontrol_torque.py | 36 ++++++++++----------- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 1f7fb4dfa42..903700d4b3c 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -64,30 +64,30 @@ def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvat future_desired_lateral_accel = desired_curvature * CS.vEgo ** 2 self.lat_accel_request_buffer.append(future_desired_lateral_accel) + roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY + curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) + lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 + + delay_frames = int(np.clip(lat_delay / self.dt + 1, 1, self.lat_accel_request_buffer_len)) + expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] + setpoint = expected_lateral_accel + error = setpoint - measurement + + lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) + raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) + desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) + gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation + ff = gravity_adjusted_future_lateral_accel + # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll + ff -= self.torque_params.latAccelOffset + ff += get_friction(error + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) + if not active: output_torque = 0.0 pid_log.active = False else: - roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY - curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) - lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - - delay_frames = int(np.clip(lat_delay / self.dt + 1, 1, self.lat_accel_request_buffer_len)) - expected_lateral_accel = self.lat_accel_request_buffer[-delay_frames] - setpoint = expected_lateral_accel - error = setpoint - measurement - - lookahead_idx = int(np.clip(-delay_frames + self.lookahead_frames, -self.lat_accel_request_buffer_len+1, -2)) - raw_lateral_jerk = (self.lat_accel_request_buffer[lookahead_idx+1] - self.lat_accel_request_buffer[lookahead_idx-1]) / (2 * self.dt) - desired_lateral_jerk = self.jerk_filter.update(raw_lateral_jerk) - gravity_adjusted_future_lateral_accel = future_desired_lateral_accel - roll_compensation - # do error correction in lateral acceleration space, convert at end to handle non-linear torque responses correctly pid_log.error = float(error) - ff = gravity_adjusted_future_lateral_accel - # latAccelOffset corrects roll compensation bias from device roll misalignment relative to car roll - ff -= self.torque_params.latAccelOffset - ff += get_friction(error + JERK_GAIN * desired_lateral_jerk, lateral_accel_deadzone, FRICTION_THRESHOLD, self.torque_params) freeze_integrator = steer_limited_by_safety or CS.steeringPressed or CS.vEgo < 5 output_lataccel = self.pid.update(pid_log.error, speed=CS.vEgo, feedforward=ff, freeze_integrator=freeze_integrator) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 274bec8b4fa..7b9039180ce 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -cdd8ecaf03b0581d6a4df7659b916f3d22167a23 \ No newline at end of file +77951c4ccd0916b87c8dfda9faa33cd2d5d2cc11 \ No newline at end of file From 1459d3519da2fdb2d981baf7811c2eaa2127eb80 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Thu, 22 Jan 2026 18:41:08 -0800 Subject: [PATCH 064/685] DM: Ford GT model (#37013) * b483cec4-7816-4570-a774-be3a2c100098/50 * shipfest * da4b8724-8998-45da-aa36-d8fb390492b9 * revert * typo * deprecates --- cereal/log.capnp | 4 +-- .../modeld/models/dmonitoring_model.onnx | 4 +-- selfdrive/monitoring/helpers.py | 25 +++---------------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 2f300881b1e..12bef17b950 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2227,9 +2227,9 @@ struct DriverMonitoringState @0xb83cda094a1da284 { isActiveMode @16 :Bool; isRHD @4 :Bool; uncertainCount @19 :UInt32; - phoneProbOffset @20 :Float32; - phoneProbValidCount @21 :UInt32; + phoneProbOffsetDEPRECATED @20 :Float32; + phoneProbValidCountDEPRECATED @21 :UInt32; isPreviewDEPRECATED @15 :Bool; rhdCheckedDEPRECATED @5 :Bool; eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED); diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 9b1c4a18347..4052a154818 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3446bf8b22e50e47669a25bf32460ae8baf8547037f346753e19ecbfcf6d4e59 -size 6954368 +oid sha256:35e4a5d4c4d481f915e42358af4665b2c92b8f5c1efd1c0731f21b876ad1d856 +size 6954249 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 3377ce6c683..0b54504b647 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -35,14 +35,7 @@ def __init__(self, device_type): self._EYE_THRESHOLD = 0.65 self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - - self._PHONE_THRESH = 0.75 if device_type == 'mici' else 0.4 - self._PHONE_THRESH2 = 15.0 - self._PHONE_MAX_OFFSET = 0.06 - self._PHONE_MIN_OFFSET = 0.025 - self._PHONE_DATA_AVG = 0.05 - self._PHONE_DATA_VAR = 3*0.005 - self._PHONE_MAX_COUNT = int(360 / self._DT_DMON) + self._PHONE_THRESH = 0.5 self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 @@ -152,11 +145,10 @@ def __init__(self, rhd_saved=False, settings=None, always_on=False): # init driver status wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) - phone_filter_raw_priors = (self.settings._PHONE_DATA_AVG, self.settings._PHONE_DATA_VAR, 2) self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) - self.phone = DriverProb(raw_priors=phone_filter_raw_priors, max_trackable=self.settings._PHONE_MAX_COUNT) self.pose = DriverPose(settings=self.settings) self.blink = DriverBlink() + self.phone_prob = 0. self.always_on = always_on self.distracted_types = [] @@ -257,12 +249,7 @@ def _get_distracted_types(self): if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) - if self.phone.prob_calibrated: - using_phone = self.phone.prob > max(min(self.phone.prob_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ - * self.settings._PHONE_THRESH2 - else: - using_phone = self.phone.prob > self.settings._PHONE_THRESH - if using_phone: + if self.phone_prob > self.settings._PHONE_THRESH: distracted_types.append(DistractedType.DISTRACTED_PHONE) return distracted_types @@ -301,7 +288,7 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.phone.prob = driver_data.phoneProb + self.phone_prob = driver_data.phoneProb self.distracted_types = self._get_distracted_types() self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types @@ -315,11 +302,9 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) - self.phone.prob_offseter.push_and_update(self.phone.prob) self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT - self.phone.prob_calibrated = self.phone.prob_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: @@ -425,8 +410,6 @@ def get_state_packet(self, valid=True): "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, - "phoneProbOffset": self.phone.prob_offseter.filtered_stat.mean(), - "phoneProbValidCount": self.phone.prob_offseter.filtered_stat.n, "stepChange": self.step_change, "awarenessActive": self.awareness_active, "awarenessPassive": self.awareness_passive, From ba6e5f125dfa2f1e3fa3cd2a57be77de989fe3d6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 23 Jan 2026 00:24:15 -0800 Subject: [PATCH 065/685] Fix bridge w/ ZMQ (#37018) * fix * can also do this * 1 less +lines but more diff - Revert "can also do this" This reverts commit 8e18218099af6d3bc852d8ef0069b80d9322d6ca. --- cereal/messaging/bridge.cc | 3 ++- cereal/messaging/msgq_to_zmq.cc | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/cereal/messaging/bridge.cc b/cereal/messaging/bridge.cc index 69ecd188e19..fb92c575c94 100644 --- a/cereal/messaging/bridge.cc +++ b/cereal/messaging/bridge.cc @@ -33,7 +33,8 @@ void zmq_to_msgq(const std::vector &endpoints, const std::string &i for (auto endpoint : endpoints) { auto pub_sock = new MSGQPubSocket(); auto sub_sock = new ZMQSubSocket(); - pub_sock->connect(pub_context.get(), endpoint); + size_t queue_size = services.at(endpoint).queue_size; + pub_sock->connect(pub_context.get(), endpoint, true, queue_size); sub_sock->connect(sub_context.get(), endpoint, ip, false); poller->registerSocket(sub_sock); diff --git a/cereal/messaging/msgq_to_zmq.cc b/cereal/messaging/msgq_to_zmq.cc index ce626f2aad3..7f8c738d4d2 100644 --- a/cereal/messaging/msgq_to_zmq.cc +++ b/cereal/messaging/msgq_to_zmq.cc @@ -2,6 +2,7 @@ #include +#include "cereal/services.h" #include "common/util.h" extern ExitHandler do_exit; @@ -108,7 +109,8 @@ void MsgqToZmq::zmqMonitorThread() { if (++pair.connected_clients == 1) { // Create new MSGQ subscriber socket and map to ZMQ publisher pair.sub_sock = std::make_unique(); - pair.sub_sock->connect(msgq_context.get(), pair.endpoint, "127.0.0.1"); + size_t queue_size = services.at(pair.endpoint).queue_size; + pair.sub_sock->connect(msgq_context.get(), pair.endpoint, "127.0.0.1", false, true, queue_size); sub2pub[pair.sub_sock.get()] = pair.pub_sock.get(); registerSockets(); } From 3715fe85aa76506e3be33505ef5dab64979d24e8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 23 Jan 2026 00:55:12 -0800 Subject: [PATCH 066/685] bump opendbc (#37019) --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 796ece26acd..1908668b056 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 796ece26acd8b9255810ca71941ed72626589ee7 +Subproject commit 1908668b05691564ea5fc80bc11b784a9dee0714 From 12220ec82dc50cd12e9df57e6c350ab0d774443e Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Fri, 23 Jan 2026 19:11:23 -0600 Subject: [PATCH 067/685] cereal: update msgq imports (#36833) Update outdated reference Co-authored-by: Adeeb Shihadeh --- cereal/messaging/__init__.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cereal/messaging/__init__.py b/cereal/messaging/__init__.py index d5033cd634a..2c925b4cc40 100644 --- a/cereal/messaging/__init__.py +++ b/cereal/messaging/__init__.py @@ -1,10 +1,8 @@ # must be built with scons -from msgq.ipc_pyx import Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \ - set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event -from msgq.ipc_pyx import MultiplePublishersError, IpcError -from msgq import fake_event_handle, drain_sock_raw +from msgq import fake_event_handle, drain_sock_raw, MultiplePublishersError, IpcError, \ + Context, Poller, SubSocket, PubSocket, SocketEventHandle, toggle_fake_events, \ + set_fake_prefix, get_fake_prefix, delete_fake_prefix, wait_for_one_event import msgq - import os import capnp import time From 560ed80123caa15ee9a7f81ecd57f05815ba857e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Sat, 24 Jan 2026 04:04:54 +0000 Subject: [PATCH 068/685] tools: seekable URLFile (#37022) * Make URLFile seekable * Return value in seek --- tools/lib/url_file.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 790fa7e8fb1..8e2f0a9222f 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -192,8 +192,25 @@ def get_multi_range(self, ranges: list[tuple[int, int]]) -> list[bytes]: raise URLFileException(f"Expected {len(ranges)} parts, got {len(parts)} ({self._url})") return parts - def seek(self, pos: int) -> None: - self._pos = int(pos) + def seekable(self) -> bool: + return True + + def seek(self, pos: int, whence: int = 0) -> int: + pos = int(pos) + if whence == os.SEEK_SET: + self._pos = pos + elif whence == os.SEEK_CUR: + self._pos += pos + elif whence == os.SEEK_END: + length = self.get_length() + assert length != -1, "Cannot seek from end on unknown length file" + self._pos = length + pos + else: + raise URLFileException("Invalid whence value") + return self._pos + + def tell(self) -> int: + return self._pos @property def name(self) -> str: From 7c90c0669a8257fb9e086d9ae392950cc511ce82 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 24 Jan 2026 10:51:41 -0800 Subject: [PATCH 069/685] script for CI results (#37024) --- scripts/ci_results.py | 209 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100755 scripts/ci_results.py diff --git a/scripts/ci_results.py b/scripts/ci_results.py new file mode 100755 index 00000000000..c3d53f222a2 --- /dev/null +++ b/scripts/ci_results.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +"""Fetch CI results from GitHub Actions and Jenkins.""" + +import argparse +import json +import subprocess +import time +import urllib.error +import urllib.request +from datetime import datetime + +JENKINS_URL = "https://jenkins.comma.life" +DEFAULT_TIMEOUT = 1800 # 30 minutes +POLL_INTERVAL = 30 # seconds +LOG_TAIL_LINES = 10 # lines of log to include for failed jobs + + +def get_git_info(): + branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"], text=True).strip() + commit = subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip() + return branch, commit + + +def get_github_actions_status(commit_sha): + result = subprocess.run( + ["gh", "run", "list", "--commit", commit_sha, "--workflow", "tests.yaml", "--json", "databaseId,status,conclusion"], + capture_output=True, text=True, check=True + ) + runs = json.loads(result.stdout) + if not runs: + return None, None + + run_id = runs[0]["databaseId"] + result = subprocess.run( + ["gh", "run", "view", str(run_id), "--json", "jobs"], + capture_output=True, text=True, check=True + ) + data = json.loads(result.stdout) + jobs = {job["name"]: {"status": job["status"], "conclusion": job["conclusion"], + "duration": format_duration(job) if job["conclusion"] not in ("skipped", None) and job.get("startedAt") else "", + "id": job["databaseId"]} + for job in data.get("jobs", [])} + return jobs, run_id + + +def get_github_job_log(run_id, job_id): + result = subprocess.run( + ["gh", "run", "view", str(run_id), "--job", str(job_id), "--log-failed"], + capture_output=True, text=True + ) + lines = result.stdout.strip().split('\n') + return '\n'.join(lines[-LOG_TAIL_LINES:]) if len(lines) > LOG_TAIL_LINES else result.stdout.strip() + + +def format_duration(job): + start = datetime.fromisoformat(job["startedAt"].replace("Z", "+00:00")) + end = datetime.fromisoformat(job["completedAt"].replace("Z", "+00:00")) + secs = int((end - start).total_seconds()) + return f"{secs // 60}m {secs % 60}s" + + +def get_jenkins_status(branch, commit_sha): + base_url = f"{JENKINS_URL}/job/openpilot/job/{branch}" + try: + # Get list of recent builds + with urllib.request.urlopen(f"{base_url}/api/json?tree=builds[number,url]", timeout=10) as resp: + builds = json.loads(resp.read().decode()).get("builds", []) + + # Find build matching commit + for build in builds[:20]: # check last 20 builds + with urllib.request.urlopen(f"{build['url']}api/json", timeout=10) as resp: + data = json.loads(resp.read().decode()) + for action in data.get("actions", []): + if action.get("_class") == "hudson.plugins.git.util.BuildData": + build_sha = action.get("lastBuiltRevision", {}).get("SHA1", "") + if build_sha.startswith(commit_sha) or commit_sha.startswith(build_sha): + # Get stages info + stages = [] + try: + with urllib.request.urlopen(f"{build['url']}wfapi/describe", timeout=10) as resp2: + wf_data = json.loads(resp2.read().decode()) + stages = [{"name": s["name"], "status": s["status"]} for s in wf_data.get("stages", [])] + except urllib.error.HTTPError: + pass + return { + "number": data["number"], + "in_progress": data.get("inProgress", False), + "result": data.get("result"), + "url": data.get("url", ""), + "stages": stages, + } + return None # no build found for this commit + except urllib.error.HTTPError: + return None # branch doesn't exist on Jenkins + + +def get_jenkins_log(build_url): + url = f"{build_url}consoleText" + with urllib.request.urlopen(url, timeout=30) as resp: + text = resp.read().decode(errors='replace') + lines = text.strip().split('\n') + return '\n'.join(lines[-LOG_TAIL_LINES:]) if len(lines) > LOG_TAIL_LINES else text.strip() + + +def is_complete(gh_status, jenkins_status): + gh_done = gh_status is None or all(j["status"] == "completed" for j in gh_status.values()) + jenkins_done = jenkins_status is None or not jenkins_status.get("in_progress", True) + return gh_done and jenkins_done + + +def status_icon(status, conclusion=None): + if status == "completed": + return ":white_check_mark:" if conclusion == "success" else ":x:" + return ":hourglass:" if status == "in_progress" else ":grey_question:" + + +def format_markdown(gh_status, gh_run_id, jenkins_status, commit_sha, branch): + lines = ["# CI Results", "", + f"**Branch**: {branch}", + f"**Commit**: {commit_sha[:7]}", + f"**Generated**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", ""] + + lines.extend(["## GitHub Actions", "", "| Job | Status | Duration |", "|-----|--------|----------|"]) + failed_gh_jobs = [] + if gh_status: + for job_name, job in gh_status.items(): + icon = status_icon(job["status"], job.get("conclusion")) + conclusion = job.get("conclusion") or job["status"] + lines.append(f"| {job_name} | {icon} {conclusion} | {job.get('duration', '')} |") + if job.get("conclusion") == "failure": + failed_gh_jobs.append((job_name, job.get("id"))) + else: + lines.append("| - | No workflow runs found | |") + + lines.extend(["", "## Jenkins", "", "| Stage | Status |", "|-------|--------|"]) + failed_jenkins_stages = [] + if jenkins_status: + stages = jenkins_status.get("stages", []) + if stages: + for stage in stages: + icon = ":white_check_mark:" if stage["status"] == "SUCCESS" else ( + ":x:" if stage["status"] == "FAILED" else ":hourglass:") + lines.append(f"| {stage['name']} | {icon} {stage['status'].lower()} |") + if stage["status"] == "FAILED": + failed_jenkins_stages.append(stage["name"]) + else: + icon = ":hourglass:" if jenkins_status["in_progress"] else ( + ":white_check_mark:" if jenkins_status["result"] == "SUCCESS" else ":x:") + status = "in progress" if jenkins_status["in_progress"] else (jenkins_status["result"] or "unknown") + lines.append(f"| #{jenkins_status['number']} | {icon} {status.lower()} |") + if jenkins_status.get("url"): + lines.append(f"\n[View build]({jenkins_status['url']})") + else: + lines.append("| - | No builds found for branch |") + + if failed_gh_jobs or failed_jenkins_stages: + lines.extend(["", "## Failure Logs", ""]) + + for job_name, job_id in failed_gh_jobs: + lines.append(f"### GitHub Actions: {job_name}") + log = get_github_job_log(gh_run_id, job_id) + lines.extend(["", "```", log, "```", ""]) + + for stage_name in failed_jenkins_stages: + lines.append(f"### Jenkins: {stage_name}") + log = get_jenkins_log(jenkins_status["url"]) + lines.extend(["", "```", log, "```", ""]) + + return "\n".join(lines) + "\n" + + +def main(): + parser = argparse.ArgumentParser(description="Fetch CI results from GitHub Actions and Jenkins") + parser.add_argument("--wait", action="store_true", help="Wait for CI to complete") + parser.add_argument("--timeout", type=int, default=DEFAULT_TIMEOUT, help="Timeout in seconds (default: 1800)") + parser.add_argument("-o", "--output", default="ci_results.md", help="Output file (default: ci_results.md)") + parser.add_argument("--branch", help="Branch to check (default: current branch)") + parser.add_argument("--commit", help="Commit SHA to check (default: HEAD)") + args = parser.parse_args() + + branch, commit = get_git_info() + branch = args.branch or branch + commit = args.commit or commit + print(f"Fetching CI results for {branch} @ {commit[:7]}") + + start_time = time.monotonic() + while True: + gh_status, gh_run_id = get_github_actions_status(commit) + jenkins_status = get_jenkins_status(branch, commit) if branch != "HEAD" else None + + if not args.wait or is_complete(gh_status, jenkins_status): + break + + elapsed = time.monotonic() - start_time + if elapsed >= args.timeout: + print(f"Timeout after {int(elapsed)}s") + break + + print(f"CI still running, waiting {POLL_INTERVAL}s... ({int(elapsed)}s elapsed)") + time.sleep(POLL_INTERVAL) + + content = format_markdown(gh_status, gh_run_id, jenkins_status, commit, branch) + with open(args.output, "w") as f: + f.write(content) + print(f"Results written to {args.output}") + + +if __name__ == "__main__": + main() From de024fd4a7f7d92a825dbbf614078d123c2fc25c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 24 Jan 2026 12:02:33 -0800 Subject: [PATCH 070/685] pandad: pure Python capnp helpers (#37025) * pandad: pure Python capnp helpers * cleanup --- .../lib/longitudinal_mpc_lib/SConscript | 4 +- selfdrive/pandad/SConscript | 6 +- selfdrive/pandad/__init__.py | 1 - selfdrive/pandad/can_list_to_can_capnp.cc | 50 ----------- selfdrive/pandad/can_types.h | 15 ---- selfdrive/pandad/pandad_api_impl.py | 88 +++++++++++++++++++ selfdrive/pandad/pandad_api_impl.pyx | 56 ------------ 7 files changed, 91 insertions(+), 129 deletions(-) delete mode 100644 selfdrive/pandad/can_list_to_can_capnp.cc delete mode 100644 selfdrive/pandad/can_types.h create mode 100644 selfdrive/pandad/pandad_api_impl.py delete mode 100644 selfdrive/pandad/pandad_api_impl.pyx diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 164b965142c..7a6c02a538d 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'pandad_python', 'np_version') +Import('env', 'envCython', 'arch', 'msgq_python', 'common_python', 'np_version') gen = "c_generated_code" @@ -67,7 +67,7 @@ lenv.Clean(generated_files, Dir(gen)) generated_long = lenv.Command(generated_files, source_list, f"cd {Dir('.').abspath} && python3 long_mpc.py") -lenv.Depends(generated_long, [msgq_python, common_python, pandad_python]) +lenv.Depends(generated_long, [msgq_python, common_python]) lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") diff --git a/selfdrive/pandad/SConscript b/selfdrive/pandad/SConscript index 58777cafe96..5e0b782c1e1 100644 --- a/selfdrive/pandad/SConscript +++ b/selfdrive/pandad/SConscript @@ -1,13 +1,9 @@ -Import('env', 'envCython', 'common', 'messaging') +Import('env', 'common', 'messaging') libs = ['usb-1.0', common, messaging, 'pthread'] panda = env.Library('panda', ['panda.cc', 'panda_comms.cc', 'spi.cc']) env.Program('pandad', ['main.cc', 'pandad.cc', 'panda_safety.cc'], LIBS=[panda] + libs) -env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) - -pandad_python = envCython.Program('pandad_api_impl.so', 'pandad_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) -Export('pandad_python') if GetOption('extras'): env.Program('tests/test_pandad_usbprotocol', ['tests/test_pandad_usbprotocol.cc'], LIBS=[panda] + libs) diff --git a/selfdrive/pandad/__init__.py b/selfdrive/pandad/__init__.py index cc680e16765..0c17e886a2e 100644 --- a/selfdrive/pandad/__init__.py +++ b/selfdrive/pandad/__init__.py @@ -1,4 +1,3 @@ -# Cython, now uses scons to build from openpilot.selfdrive.pandad.pandad_api_impl import can_list_to_can_capnp, can_capnp_to_list assert can_list_to_can_capnp assert can_capnp_to_list diff --git a/selfdrive/pandad/can_list_to_can_capnp.cc b/selfdrive/pandad/can_list_to_can_capnp.cc deleted file mode 100644 index f2cf1534533..00000000000 --- a/selfdrive/pandad/can_list_to_can_capnp.cc +++ /dev/null @@ -1,50 +0,0 @@ -#include "cereal/messaging/messaging.h" -#include "selfdrive/pandad/can_types.h" - -void can_list_to_can_capnp_cpp(const std::vector &can_list, std::string &out, bool sendcan, bool valid) { - MessageBuilder msg; - auto event = msg.initEvent(valid); - - auto canData = sendcan ? event.initSendcan(can_list.size()) : event.initCan(can_list.size()); - int j = 0; - for (auto it = can_list.begin(); it != can_list.end(); it++, j++) { - auto c = canData[j]; - c.setAddress(it->address); - c.setDat(kj::arrayPtr((uint8_t*)it->dat.data(), it->dat.size())); - c.setSrc(it->src); - } - const uint64_t msg_size = capnp::computeSerializedSizeInWords(msg) * sizeof(capnp::word); - out.resize(msg_size); - kj::ArrayOutputStream output_stream(kj::ArrayPtr((unsigned char *)out.data(), msg_size)); - capnp::writeMessage(output_stream, msg); -} - -// Converts a vector of Cap'n Proto serialized can strings into a vector of CanData structures. -void can_capnp_to_can_list_cpp(const std::vector &strings, std::vector &can_list, bool sendcan) { - AlignedBuffer aligned_buf; - can_list.reserve(strings.size()); - - for (const auto &str : strings) { - // extract the messages - capnp::FlatArrayMessageReader reader(aligned_buf.align(str.data(), str.size())); - cereal::Event::Reader event = reader.getRoot(); - - auto frames = sendcan ? event.getSendcan() : event.getCan(); - - // Add new CanData entry - CanData &can_data = can_list.emplace_back(); - can_data.nanos = event.getLogMonoTime(); - can_data.frames.reserve(frames.size()); - - // Populate CAN frames - for (const auto &frame : frames) { - CanFrame &can_frame = can_data.frames.emplace_back(); - can_frame.src = frame.getSrc(); - can_frame.address = frame.getAddress(); - - // Copy CAN data - auto dat = frame.getDat(); - can_frame.dat.assign(dat.begin(), dat.end()); - } - } -} diff --git a/selfdrive/pandad/can_types.h b/selfdrive/pandad/can_types.h deleted file mode 100644 index 5fae581cfae..00000000000 --- a/selfdrive/pandad/can_types.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -struct CanFrame { - long src; - uint32_t address; - std::vector dat; -}; - -struct CanData { - uint64_t nanos; - std::vector frames; -}; \ No newline at end of file diff --git a/selfdrive/pandad/pandad_api_impl.py b/selfdrive/pandad/pandad_api_impl.py new file mode 100644 index 00000000000..75a7ba484e1 --- /dev/null +++ b/selfdrive/pandad/pandad_api_impl.py @@ -0,0 +1,88 @@ +import time +from cereal import log + +NO_TRAVERSAL_LIMIT = 2**64 - 1 + +# Cache schema fields for faster access (avoids string lookup on each field access) +_cached_reader_fields = None # (address_field, dat_field, src_field) for reading +_cached_writer_fields = None # (address_field, dat_field, src_field) for writing + + +def _get_reader_fields(schema): + """Get cached schema field objects for reading.""" + global _cached_reader_fields + if _cached_reader_fields is None: + fields = schema.fields + _cached_reader_fields = (fields['address'], fields['dat'], fields['src']) + return _cached_reader_fields + + +def _get_writer_fields(schema): + """Get cached schema field objects for writing.""" + global _cached_writer_fields + if _cached_writer_fields is None: + fields = schema.fields + _cached_writer_fields = (fields['address'], fields['dat'], fields['src']) + return _cached_writer_fields + + +def can_list_to_can_capnp(can_msgs, msgtype='can', valid=True): + """Convert list of CAN messages to Cap'n Proto serialized bytes. + + Args: + can_msgs: List of tuples [(address, data_bytes, src), ...] + msgtype: 'can' or 'sendcan' + valid: Whether the event is valid + + Returns: + Cap'n Proto serialized bytes + """ + global _cached_writer_fields + + dat = log.Event.new_message(valid=valid, logMonoTime=int(time.monotonic() * 1e9)) + can_data = dat.init(msgtype, len(can_msgs)) + + # Cache schema fields on first call + if _cached_writer_fields is None and len(can_msgs) > 0: + _cached_writer_fields = _get_writer_fields(can_data[0].schema) + + if _cached_writer_fields is not None: + addr_f, dat_f, src_f = _cached_writer_fields + for i, msg in enumerate(can_msgs): + f = can_data[i] + f._set_by_field(addr_f, msg[0]) + f._set_by_field(dat_f, msg[1]) + f._set_by_field(src_f, msg[2]) + + return dat.to_bytes() + + +def can_capnp_to_list(strings, msgtype='can'): + """Convert Cap'n Proto serialized bytes to list of CAN messages. + + Args: + strings: Tuple/list of serialized Cap'n Proto bytes + msgtype: 'can' or 'sendcan' + + Returns: + List of tuples [(nanos, [(address, data, src), ...]), ...] + """ + global _cached_reader_fields + result = [] + + for s in strings: + with log.Event.from_bytes(s, traversal_limit_in_words=NO_TRAVERSAL_LIMIT) as event: + frames = getattr(event, msgtype) + + # Cache schema fields on first frame for faster access + if _cached_reader_fields is None and len(frames) > 0: + _cached_reader_fields = _get_reader_fields(frames[0].schema) + + if _cached_reader_fields is not None: + addr_f, dat_f, src_f = _cached_reader_fields + frame_list = [(f._get_by_field(addr_f), f._get_by_field(dat_f), f._get_by_field(src_f)) for f in frames] + else: + frame_list = [] + + result.append((event.logMonoTime, frame_list)) + return result diff --git a/selfdrive/pandad/pandad_api_impl.pyx b/selfdrive/pandad/pandad_api_impl.pyx deleted file mode 100644 index aaecb8a594e..00000000000 --- a/selfdrive/pandad/pandad_api_impl.pyx +++ /dev/null @@ -1,56 +0,0 @@ -# distutils: language = c++ -# cython: language_level=3 -from cython.operator cimport dereference as deref, preincrement as preinc -from libcpp.vector cimport vector -from libcpp.string cimport string -from libcpp cimport bool -from libc.stdint cimport uint8_t, uint32_t, uint64_t - -cdef extern from "selfdrive/pandad/can_types.h": - cdef struct CanFrame: - long src - uint32_t address - vector[uint8_t] dat - - cdef struct CanData: - uint64_t nanos - vector[CanFrame] frames - -cdef extern from "can_list_to_can_capnp.cc": - void can_list_to_can_capnp_cpp(const vector[CanFrame] &can_list, string &out, bool sendcan, bool valid) nogil - void can_capnp_to_can_list_cpp(const vector[string] &strings, vector[CanData] &can_data, bool sendcan) - -def can_list_to_can_capnp(can_msgs, msgtype='can', valid=True): - cdef CanFrame *f - cdef vector[CanFrame] can_list - cdef uint32_t cpp_can_msgs_len = len(can_msgs) - - with nogil: - can_list.reserve(cpp_can_msgs_len) - - for can_msg in can_msgs: - f = &(can_list.emplace_back()) - f.address = can_msg[0] - f.dat = can_msg[1] - f.src = can_msg[2] - - cdef string out - cdef bool is_sendcan = (msgtype == 'sendcan') - cdef bool is_valid = valid - with nogil: - can_list_to_can_capnp_cpp(can_list, out, is_sendcan, is_valid) - return out - -def can_capnp_to_list(strings, msgtype='can'): - cdef vector[CanData] data - can_capnp_to_can_list_cpp(strings, data, msgtype == 'sendcan') - - result = [] - cdef CanData *d - cdef vector[CanData].iterator it = data.begin() - while it != data.end(): - d = &deref(it) - frames = [(f.address, (&f.dat[0])[:f.dat.size()], f.src) for f in d.frames] - result.append((d.nanos, frames)) - preinc(it) - return result From 71a418d166d00d86226c02453a70c57776f46009 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 26 Jan 2026 09:14:57 -0800 Subject: [PATCH 071/685] [bot] Update Python packages (#37028) Update Python packages Co-authored-by: Vehicle Researcher --- docs/CARS.md | 3 +- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 278 +++++++++++++++++++++++++------------------------- 5 files changed, 144 insertions(+), 143 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 08c06b2303d..b3496793952 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 326 Supported Cars +# 327 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -14,6 +14,7 @@ A supported vehicle is one that just works when you install a comma device. All |Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|TLX 2025|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| diff --git a/opendbc_repo b/opendbc_repo index 1908668b056..d424d1f2473 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 1908668b05691564ea5fc80bc11b784a9dee0714 +Subproject commit d424d1f247384b68923b8093875e1a370ef8221d diff --git a/panda b/panda index 3dd38b76b48..81615ad9d53 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 3dd38b76b48903efb4705f55752e9719ba2f5564 +Subproject commit 81615ad9d53aef5583e064f340e9cdeb23d4119c diff --git a/tinygrad_repo b/tinygrad_repo index 7cb7abeeb02..774a454bb5e 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 7cb7abeeb02c681a463f252179354db4bb5e3809 +Subproject commit 774a454bb5e6d0fe3756a8add9302c0a3d592bd9 diff --git a/uv.lock b/uv.lock index 674b18b7eaf..b221995b85e 100644 --- a/uv.lock +++ b/uv.lock @@ -378,37 +378,37 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/23/f9/e92df5e07f3fc8d4c7f9a0f146ef75446bf870351cd37b788cf5897f8079/coverage-7.13.1.tar.gz", hash = "sha256:b7593fe7eb5feaa3fbb461ac79aac9f9fc0387a5ca8080b0c6fe2ca27b091afd", size = 825862, upload-time = "2025-12-28T15:42:56.969Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/9b/77baf488516e9ced25fc215a6f75d803493fc3f6a1a1227ac35697910c2a/coverage-7.13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a55d509a1dc5a5b708b5dad3b5334e07a16ad4c2185e27b40e4dba796ab7f88", size = 218755, upload-time = "2025-12-28T15:40:30.812Z" }, - { url = "https://files.pythonhosted.org/packages/d7/cd/7ab01154e6eb79ee2fab76bf4d89e94c6648116557307ee4ebbb85e5c1bf/coverage-7.13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4d010d080c4888371033baab27e47c9df7d6fb28d0b7b7adf85a4a49be9298b3", size = 219257, upload-time = "2025-12-28T15:40:32.333Z" }, - { url = "https://files.pythonhosted.org/packages/01/d5/b11ef7863ffbbdb509da0023fad1e9eda1c0eaea61a6d2ea5b17d4ac706e/coverage-7.13.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d938b4a840fb1523b9dfbbb454f652967f18e197569c32266d4d13f37244c3d9", size = 249657, upload-time = "2025-12-28T15:40:34.1Z" }, - { url = "https://files.pythonhosted.org/packages/f7/7c/347280982982383621d29b8c544cf497ae07ac41e44b1ca4903024131f55/coverage-7.13.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bf100a3288f9bb7f919b87eb84f87101e197535b9bd0e2c2b5b3179633324fee", size = 251581, upload-time = "2025-12-28T15:40:36.131Z" }, - { url = "https://files.pythonhosted.org/packages/82/f6/ebcfed11036ade4c0d75fa4453a6282bdd225bc073862766eec184a4c643/coverage-7.13.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef6688db9bf91ba111ae734ba6ef1a063304a881749726e0d3575f5c10a9facf", size = 253691, upload-time = "2025-12-28T15:40:37.626Z" }, - { url = "https://files.pythonhosted.org/packages/02/92/af8f5582787f5d1a8b130b2dcba785fa5e9a7a8e121a0bb2220a6fdbdb8a/coverage-7.13.1-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0b609fc9cdbd1f02e51f67f51e5aee60a841ef58a68d00d5ee2c0faf357481a3", size = 249799, upload-time = "2025-12-28T15:40:39.47Z" }, - { url = "https://files.pythonhosted.org/packages/24/aa/0e39a2a3b16eebf7f193863323edbff38b6daba711abaaf807d4290cf61a/coverage-7.13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c43257717611ff5e9a1d79dce8e47566235ebda63328718d9b65dd640bc832ef", size = 251389, upload-time = "2025-12-28T15:40:40.954Z" }, - { url = "https://files.pythonhosted.org/packages/73/46/7f0c13111154dc5b978900c0ccee2e2ca239b910890e674a77f1363d483e/coverage-7.13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e09fbecc007f7b6afdfb3b07ce5bd9f8494b6856dd4f577d26c66c391b829851", size = 249450, upload-time = "2025-12-28T15:40:42.489Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ca/e80da6769e8b669ec3695598c58eef7ad98b0e26e66333996aee6316db23/coverage-7.13.1-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:a03a4f3a19a189919c7055098790285cc5c5b0b3976f8d227aea39dbf9f8bfdb", size = 249170, upload-time = "2025-12-28T15:40:44.279Z" }, - { url = "https://files.pythonhosted.org/packages/af/18/9e29baabdec1a8644157f572541079b4658199cfd372a578f84228e860de/coverage-7.13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3820778ea1387c2b6a818caec01c63adc5b3750211af6447e8dcfb9b6f08dbba", size = 250081, upload-time = "2025-12-28T15:40:45.748Z" }, - { url = "https://files.pythonhosted.org/packages/00/f8/c3021625a71c3b2f516464d322e41636aea381018319050a8114105872ee/coverage-7.13.1-cp311-cp311-win32.whl", hash = "sha256:ff10896fa55167371960c5908150b434b71c876dfab97b69478f22c8b445ea19", size = 221281, upload-time = "2025-12-28T15:40:47.232Z" }, - { url = "https://files.pythonhosted.org/packages/27/56/c216625f453df6e0559ed666d246fcbaaa93f3aa99eaa5080cea1229aa3d/coverage-7.13.1-cp311-cp311-win_amd64.whl", hash = "sha256:a998cc0aeeea4c6d5622a3754da5a493055d2d95186bad877b0a34ea6e6dbe0a", size = 222215, upload-time = "2025-12-28T15:40:49.19Z" }, - { url = "https://files.pythonhosted.org/packages/5c/9a/be342e76f6e531cae6406dc46af0d350586f24d9b67fdfa6daee02df71af/coverage-7.13.1-cp311-cp311-win_arm64.whl", hash = "sha256:fea07c1a39a22614acb762e3fbbb4011f65eedafcb2948feeef641ac78b4ee5c", size = 220886, upload-time = "2025-12-28T15:40:51.067Z" }, - { url = "https://files.pythonhosted.org/packages/ce/8a/87af46cccdfa78f53db747b09f5f9a21d5fc38d796834adac09b30a8ce74/coverage-7.13.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6f34591000f06e62085b1865c9bc5f7858df748834662a51edadfd2c3bfe0dd3", size = 218927, upload-time = "2025-12-28T15:40:52.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/a8/6e22fdc67242a4a5a153f9438d05944553121c8f4ba70cb072af4c41362e/coverage-7.13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b67e47c5595b9224599016e333f5ec25392597a89d5744658f837d204e16c63e", size = 219288, upload-time = "2025-12-28T15:40:54.262Z" }, - { url = "https://files.pythonhosted.org/packages/d0/0a/853a76e03b0f7c4375e2ca025df45c918beb367f3e20a0a8e91967f6e96c/coverage-7.13.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3e7b8bd70c48ffb28461ebe092c2345536fb18bbbf19d287c8913699735f505c", size = 250786, upload-time = "2025-12-28T15:40:56.059Z" }, - { url = "https://files.pythonhosted.org/packages/ea/b4/694159c15c52b9f7ec7adf49d50e5f8ee71d3e9ef38adb4445d13dd56c20/coverage-7.13.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:c223d078112e90dc0e5c4e35b98b9584164bea9fbbd221c0b21c5241f6d51b62", size = 253543, upload-time = "2025-12-28T15:40:57.585Z" }, - { url = "https://files.pythonhosted.org/packages/96/b2/7f1f0437a5c855f87e17cf5d0dc35920b6440ff2b58b1ba9788c059c26c8/coverage-7.13.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:794f7c05af0763b1bbd1b9e6eff0e52ad068be3b12cd96c87de037b01390c968", size = 254635, upload-time = "2025-12-28T15:40:59.443Z" }, - { url = "https://files.pythonhosted.org/packages/e9/d1/73c3fdb8d7d3bddd9473c9c6a2e0682f09fc3dfbcb9c3f36412a7368bcab/coverage-7.13.1-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0642eae483cc8c2902e4af7298bf886d605e80f26382124cddc3967c2a3df09e", size = 251202, upload-time = "2025-12-28T15:41:01.328Z" }, - { url = "https://files.pythonhosted.org/packages/66/3c/f0edf75dcc152f145d5598329e864bbbe04ab78660fe3e8e395f9fff010f/coverage-7.13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9f5e772ed5fef25b3de9f2008fe67b92d46831bd2bc5bdc5dd6bfd06b83b316f", size = 252566, upload-time = "2025-12-28T15:41:03.319Z" }, - { url = "https://files.pythonhosted.org/packages/17/b3/e64206d3c5f7dcbceafd14941345a754d3dbc78a823a6ed526e23b9cdaab/coverage-7.13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:45980ea19277dc0a579e432aef6a504fe098ef3a9032ead15e446eb0f1191aee", size = 250711, upload-time = "2025-12-28T15:41:06.411Z" }, - { url = "https://files.pythonhosted.org/packages/dc/ad/28a3eb970a8ef5b479ee7f0c484a19c34e277479a5b70269dc652b730733/coverage-7.13.1-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:e4f18eca6028ffa62adbd185a8f1e1dd242f2e68164dba5c2b74a5204850b4cf", size = 250278, upload-time = "2025-12-28T15:41:08.285Z" }, - { url = "https://files.pythonhosted.org/packages/54/e3/c8f0f1a93133e3e1291ca76cbb63565bd4b5c5df63b141f539d747fff348/coverage-7.13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8dca5590fec7a89ed6826fce625595279e586ead52e9e958d3237821fbc750c", size = 252154, upload-time = "2025-12-28T15:41:09.969Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bf/9939c5d6859c380e405b19e736321f1c7d402728792f4c752ad1adcce005/coverage-7.13.1-cp312-cp312-win32.whl", hash = "sha256:ff86d4e85188bba72cfb876df3e11fa243439882c55957184af44a35bd5880b7", size = 221487, upload-time = "2025-12-28T15:41:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/fa/dc/7282856a407c621c2aad74021680a01b23010bb8ebf427cf5eacda2e876f/coverage-7.13.1-cp312-cp312-win_amd64.whl", hash = "sha256:16cc1da46c04fb0fb128b4dc430b78fa2aba8a6c0c9f8eb391fd5103409a6ac6", size = 222299, upload-time = "2025-12-28T15:41:13.386Z" }, - { url = "https://files.pythonhosted.org/packages/10/79/176a11203412c350b3e9578620013af35bcdb79b651eb976f4a4b32044fa/coverage-7.13.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d9bc218650022a768f3775dd7fdac1886437325d8d295d923ebcfef4892ad5c", size = 220941, upload-time = "2025-12-28T15:41:14.975Z" }, - { url = "https://files.pythonhosted.org/packages/cc/48/d9f421cb8da5afaa1a64570d9989e00fb7955e6acddc5a12979f7666ef60/coverage-7.13.1-py3-none-any.whl", hash = "sha256:2016745cb3ba554469d02819d78958b571792bb68e31302610e898f80dd3a573", size = 210722, upload-time = "2025-12-28T15:42:54.901Z" }, +version = "7.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, + { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, + { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, + { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, + { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, + { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, + { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, + { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, + { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, + { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, + { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, + { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, + { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, + { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, + { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, + { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, + { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, + { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, + { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, + { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, + { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, + { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, + { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, + { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, + { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, + { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, ] [[package]] @@ -915,11 +915,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.10" +version = "3.10.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, ] [[package]] @@ -1158,47 +1158,47 @@ wheels = [ [[package]] name = "multidict" -version = "6.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/80/1e/5492c365f222f907de1039b91f922b93fa4f764c713ee858d235495d8f50/multidict-6.7.0.tar.gz", hash = "sha256:c6e99d9a65ca282e578dfea819cfa9c0a62b2499d8677392e09feaf305e9e6f5", size = 101834, upload-time = "2025-10-06T14:52:30.657Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/34/9e/5c727587644d67b2ed479041e4b1c58e30afc011e3d45d25bbe35781217c/multidict-6.7.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4d409aa42a94c0b3fa617708ef5276dfe81012ba6753a0370fcc9d0195d0a1fc", size = 76604, upload-time = "2025-10-06T14:48:54.277Z" }, - { url = "https://files.pythonhosted.org/packages/17/e4/67b5c27bd17c085a5ea8f1ec05b8a3e5cba0ca734bfcad5560fb129e70ca/multidict-6.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:14c9e076eede3b54c636f8ce1c9c252b5f057c62131211f0ceeec273810c9721", size = 44715, upload-time = "2025-10-06T14:48:55.445Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/866a5d77be6ea435711bef2a4291eed11032679b6b28b56b4776ab06ba3e/multidict-6.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4c09703000a9d0fa3c3404b27041e574cc7f4df4c6563873246d0e11812a94b6", size = 44332, upload-time = "2025-10-06T14:48:56.706Z" }, - { url = "https://files.pythonhosted.org/packages/31/61/0c2d50241ada71ff61a79518db85ada85fdabfcf395d5968dae1cbda04e5/multidict-6.7.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a265acbb7bb33a3a2d626afbe756371dce0279e7b17f4f4eda406459c2b5ff1c", size = 245212, upload-time = "2025-10-06T14:48:58.042Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e0/919666a4e4b57fff1b57f279be1c9316e6cdc5de8a8b525d76f6598fefc7/multidict-6.7.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51cb455de290ae462593e5b1cb1118c5c22ea7f0d3620d9940bf695cea5a4bd7", size = 246671, upload-time = "2025-10-06T14:49:00.004Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cc/d027d9c5a520f3321b65adea289b965e7bcbd2c34402663f482648c716ce/multidict-6.7.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:db99677b4457c7a5c5a949353e125ba72d62b35f74e26da141530fbb012218a7", size = 225491, upload-time = "2025-10-06T14:49:01.393Z" }, - { url = "https://files.pythonhosted.org/packages/75/c4/bbd633980ce6155a28ff04e6a6492dd3335858394d7bb752d8b108708558/multidict-6.7.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f470f68adc395e0183b92a2f4689264d1ea4b40504a24d9882c27375e6662bb9", size = 257322, upload-time = "2025-10-06T14:49:02.745Z" }, - { url = "https://files.pythonhosted.org/packages/4c/6d/d622322d344f1f053eae47e033b0b3f965af01212de21b10bcf91be991fb/multidict-6.7.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0db4956f82723cc1c270de9c6e799b4c341d327762ec78ef82bb962f79cc07d8", size = 254694, upload-time = "2025-10-06T14:49:04.15Z" }, - { url = "https://files.pythonhosted.org/packages/a8/9f/78f8761c2705d4c6d7516faed63c0ebdac569f6db1bef95e0d5218fdc146/multidict-6.7.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3e56d780c238f9e1ae66a22d2adf8d16f485381878250db8d496623cd38b22bd", size = 246715, upload-time = "2025-10-06T14:49:05.967Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/950818e04f91b9c2b95aab3d923d9eabd01689d0dcd889563988e9ea0fd8/multidict-6.7.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9d14baca2ee12c1a64740d4531356ba50b82543017f3ad6de0deb943c5979abb", size = 243189, upload-time = "2025-10-06T14:49:07.37Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3d/77c79e1934cad2ee74991840f8a0110966d9599b3af95964c0cd79bb905b/multidict-6.7.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:295a92a76188917c7f99cda95858c822f9e4aae5824246bba9b6b44004ddd0a6", size = 237845, upload-time = "2025-10-06T14:49:08.759Z" }, - { url = "https://files.pythonhosted.org/packages/63/1b/834ce32a0a97a3b70f86437f685f880136677ac00d8bce0027e9fd9c2db7/multidict-6.7.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39f1719f57adbb767ef592a50ae5ebb794220d1188f9ca93de471336401c34d2", size = 246374, upload-time = "2025-10-06T14:49:10.574Z" }, - { url = "https://files.pythonhosted.org/packages/23/ef/43d1c3ba205b5dec93dc97f3fba179dfa47910fc73aaaea4f7ceb41cec2a/multidict-6.7.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0a13fb8e748dfc94749f622de065dd5c1def7e0d2216dba72b1d8069a389c6ff", size = 253345, upload-time = "2025-10-06T14:49:12.331Z" }, - { url = "https://files.pythonhosted.org/packages/6b/03/eaf95bcc2d19ead522001f6a650ef32811aa9e3624ff0ad37c445c7a588c/multidict-6.7.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e3aa16de190d29a0ea1b48253c57d99a68492c8dd8948638073ab9e74dc9410b", size = 246940, upload-time = "2025-10-06T14:49:13.821Z" }, - { url = "https://files.pythonhosted.org/packages/e8/df/ec8a5fd66ea6cd6f525b1fcbb23511b033c3e9bc42b81384834ffa484a62/multidict-6.7.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a048ce45dcdaaf1defb76b2e684f997fb5abf74437b6cb7b22ddad934a964e34", size = 242229, upload-time = "2025-10-06T14:49:15.603Z" }, - { url = "https://files.pythonhosted.org/packages/8a/a2/59b405d59fd39ec86d1142630e9049243015a5f5291ba49cadf3c090c541/multidict-6.7.0-cp311-cp311-win32.whl", hash = "sha256:a90af66facec4cebe4181b9e62a68be65e45ac9b52b67de9eec118701856e7ff", size = 41308, upload-time = "2025-10-06T14:49:16.871Z" }, - { url = "https://files.pythonhosted.org/packages/32/0f/13228f26f8b882c34da36efa776c3b7348455ec383bab4a66390e42963ae/multidict-6.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:95b5ffa4349df2887518bb839409bcf22caa72d82beec453216802f475b23c81", size = 46037, upload-time = "2025-10-06T14:49:18.457Z" }, - { url = "https://files.pythonhosted.org/packages/84/1f/68588e31b000535a3207fd3c909ebeec4fb36b52c442107499c18a896a2a/multidict-6.7.0-cp311-cp311-win_arm64.whl", hash = "sha256:329aa225b085b6f004a4955271a7ba9f1087e39dcb7e65f6284a988264a63912", size = 43023, upload-time = "2025-10-06T14:49:19.648Z" }, - { url = "https://files.pythonhosted.org/packages/c2/9e/9f61ac18d9c8b475889f32ccfa91c9f59363480613fc807b6e3023d6f60b/multidict-6.7.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8a3862568a36d26e650a19bb5cbbba14b71789032aebc0423f8cc5f150730184", size = 76877, upload-time = "2025-10-06T14:49:20.884Z" }, - { url = "https://files.pythonhosted.org/packages/38/6f/614f09a04e6184f8824268fce4bc925e9849edfa654ddd59f0b64508c595/multidict-6.7.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:960c60b5849b9b4f9dcc9bea6e3626143c252c74113df2c1540aebce70209b45", size = 45467, upload-time = "2025-10-06T14:49:22.054Z" }, - { url = "https://files.pythonhosted.org/packages/b3/93/c4f67a436dd026f2e780c433277fff72be79152894d9fc36f44569cab1a6/multidict-6.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2049be98fb57a31b4ccf870bf377af2504d4ae35646a19037ec271e4c07998aa", size = 43834, upload-time = "2025-10-06T14:49:23.566Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f5/013798161ca665e4a422afbc5e2d9e4070142a9ff8905e482139cd09e4d0/multidict-6.7.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:0934f3843a1860dd465d38895c17fce1f1cb37295149ab05cd1b9a03afacb2a7", size = 250545, upload-time = "2025-10-06T14:49:24.882Z" }, - { url = "https://files.pythonhosted.org/packages/71/2f/91dbac13e0ba94669ea5119ba267c9a832f0cb65419aca75549fcf09a3dc/multidict-6.7.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3e34f3a1b8131ba06f1a73adab24f30934d148afcd5f5de9a73565a4404384e", size = 258305, upload-time = "2025-10-06T14:49:26.778Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b0/754038b26f6e04488b48ac621f779c341338d78503fb45403755af2df477/multidict-6.7.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:efbb54e98446892590dc2458c19c10344ee9a883a79b5cec4bc34d6656e8d546", size = 242363, upload-time = "2025-10-06T14:49:28.562Z" }, - { url = "https://files.pythonhosted.org/packages/87/15/9da40b9336a7c9fa606c4cf2ed80a649dffeb42b905d4f63a1d7eb17d746/multidict-6.7.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a35c5fc61d4f51eb045061e7967cfe3123d622cd500e8868e7c0c592a09fedc4", size = 268375, upload-time = "2025-10-06T14:49:29.96Z" }, - { url = "https://files.pythonhosted.org/packages/82/72/c53fcade0cc94dfaad583105fd92b3a783af2091eddcb41a6d5a52474000/multidict-6.7.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29fe6740ebccba4175af1b9b87bf553e9c15cd5868ee967e010efcf94e4fd0f1", size = 269346, upload-time = "2025-10-06T14:49:31.404Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e2/9baffdae21a76f77ef8447f1a05a96ec4bc0a24dae08767abc0a2fe680b8/multidict-6.7.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:123e2a72e20537add2f33a79e605f6191fba2afda4cbb876e35c1a7074298a7d", size = 256107, upload-time = "2025-10-06T14:49:32.974Z" }, - { url = "https://files.pythonhosted.org/packages/3c/06/3f06f611087dc60d65ef775f1fb5aca7c6d61c6db4990e7cda0cef9b1651/multidict-6.7.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b284e319754366c1aee2267a2036248b24eeb17ecd5dc16022095e747f2f4304", size = 253592, upload-time = "2025-10-06T14:49:34.52Z" }, - { url = "https://files.pythonhosted.org/packages/20/24/54e804ec7945b6023b340c412ce9c3f81e91b3bf5fa5ce65558740141bee/multidict-6.7.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:803d685de7be4303b5a657b76e2f6d1240e7e0a8aa2968ad5811fa2285553a12", size = 251024, upload-time = "2025-10-06T14:49:35.956Z" }, - { url = "https://files.pythonhosted.org/packages/14/48/011cba467ea0b17ceb938315d219391d3e421dfd35928e5dbdc3f4ae76ef/multidict-6.7.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c04a328260dfd5db8c39538f999f02779012268f54614902d0afc775d44e0a62", size = 251484, upload-time = "2025-10-06T14:49:37.631Z" }, - { url = "https://files.pythonhosted.org/packages/0d/2f/919258b43bb35b99fa127435cfb2d91798eb3a943396631ef43e3720dcf4/multidict-6.7.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8a19cdb57cd3df4cd865849d93ee14920fb97224300c88501f16ecfa2604b4e0", size = 263579, upload-time = "2025-10-06T14:49:39.502Z" }, - { url = "https://files.pythonhosted.org/packages/31/22/a0e884d86b5242b5a74cf08e876bdf299e413016b66e55511f7a804a366e/multidict-6.7.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b2fd74c52accced7e75de26023b7dccee62511a600e62311b918ec5c168fc2a", size = 259654, upload-time = "2025-10-06T14:49:41.32Z" }, - { url = "https://files.pythonhosted.org/packages/b2/e5/17e10e1b5c5f5a40f2fcbb45953c9b215f8a4098003915e46a93f5fcaa8f/multidict-6.7.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e8bfdd0e487acf992407a140d2589fe598238eaeffa3da8448d63a63cd363f8", size = 251511, upload-time = "2025-10-06T14:49:46.021Z" }, - { url = "https://files.pythonhosted.org/packages/e3/9a/201bb1e17e7af53139597069c375e7b0dcbd47594604f65c2d5359508566/multidict-6.7.0-cp312-cp312-win32.whl", hash = "sha256:dd32a49400a2c3d52088e120ee00c1e3576cbff7e10b98467962c74fdb762ed4", size = 41895, upload-time = "2025-10-06T14:49:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/46/e2/348cd32faad84eaf1d20cce80e2bb0ef8d312c55bca1f7fa9865e7770aaf/multidict-6.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:92abb658ef2d7ef22ac9f8bb88e8b6c3e571671534e029359b6d9e845923eb1b", size = 46073, upload-time = "2025-10-06T14:49:50.28Z" }, - { url = "https://files.pythonhosted.org/packages/25/ec/aad2613c1910dce907480e0c3aa306905830f25df2e54ccc9dea450cb5aa/multidict-6.7.0-cp312-cp312-win_arm64.whl", hash = "sha256:490dab541a6a642ce1a9d61a4781656b346a55c13038f0b1244653828e3a83ec", size = 43226, upload-time = "2025-10-06T14:49:52.304Z" }, - { url = "https://files.pythonhosted.org/packages/b7/da/7d22601b625e241d4f23ef1ebff8acfc60da633c9e7e7922e24d10f592b3/multidict-6.7.0-py3-none-any.whl", hash = "sha256:394fc5c42a333c9ffc3e421a4c85e08580d990e08b99f6bf35b4132114c5dcb3", size = 12317, upload-time = "2025-10-06T14:52:29.272Z" }, +version = "6.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, + { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, + { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, + { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, + { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, + { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, + { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, + { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, + { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, + { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, + { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, + { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, + { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, + { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, + { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, + { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, + { url = "https://files.pythonhosted.org/packages/cf/3b/d6bd75dc4f3ff7c73766e04e705b00ed6dbbaccf670d9e05a12b006f5a21/multidict-6.7.1-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:cb2a55f408c3043e42b40cc8eecd575afa27b7e0b956dfb190de0f8499a57a53", size = 251018, upload-time = "2026-01-26T02:43:56.198Z" }, + { url = "https://files.pythonhosted.org/packages/fd/80/c959c5933adedb9ac15152e4067c702a808ea183a8b64cf8f31af8ad3155/multidict-6.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb0ce7b2a32d09892b3dd6cc44877a0d02a33241fafca5f25c8b6b62374f8b75", size = 258883, upload-time = "2026-01-26T02:43:57.499Z" }, + { url = "https://files.pythonhosted.org/packages/86/85/7ed40adafea3d4f1c8b916e3b5cc3a8e07dfcdcb9cd72800f4ed3ca1b387/multidict-6.7.1-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c3a32d23520ee37bf327d1e1a656fec76a2edd5c038bf43eddfa0572ec49c60b", size = 242413, upload-time = "2026-01-26T02:43:58.755Z" }, + { url = "https://files.pythonhosted.org/packages/d2/57/b8565ff533e48595503c785f8361ff9a4fde4d67de25c207cd0ba3befd03/multidict-6.7.1-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9c90fed18bffc0189ba814749fdcc102b536e83a9f738a9003e569acd540a733", size = 268404, upload-time = "2026-01-26T02:44:00.216Z" }, + { url = "https://files.pythonhosted.org/packages/e0/50/9810c5c29350f7258180dfdcb2e52783a0632862eb334c4896ac717cebcb/multidict-6.7.1-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:da62917e6076f512daccfbbde27f46fed1c98fee202f0559adec8ee0de67f71a", size = 269456, upload-time = "2026-01-26T02:44:02.202Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8d/5e5be3ced1d12966fefb5c4ea3b2a5b480afcea36406559442c6e31d4a48/multidict-6.7.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bfde23ef6ed9db7eaee6c37dcec08524cb43903c60b285b172b6c094711b3961", size = 256322, upload-time = "2026-01-26T02:44:03.56Z" }, + { url = "https://files.pythonhosted.org/packages/31/6e/d8a26d81ac166a5592782d208dd90dfdc0a7a218adaa52b45a672b46c122/multidict-6.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3758692429e4e32f1ba0df23219cd0b4fc0a52f476726fff9337d1a57676a582", size = 253955, upload-time = "2026-01-26T02:44:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/59/4c/7c672c8aad41534ba619bcd4ade7a0dc87ed6b8b5c06149b85d3dd03f0cd/multidict-6.7.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:398c1478926eca669f2fd6a5856b6de9c0acf23a2cb59a14c0ba5844fa38077e", size = 251254, upload-time = "2026-01-26T02:44:06.133Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bd/84c24de512cbafbdbc39439f74e967f19570ce7924e3007174a29c348916/multidict-6.7.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c102791b1c4f3ab36ce4101154549105a53dc828f016356b3e3bcae2e3a039d3", size = 252059, upload-time = "2026-01-26T02:44:07.518Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ba/f5449385510825b73d01c2d4087bf6d2fccc20a2d42ac34df93191d3dd03/multidict-6.7.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a088b62bd733e2ad12c50dad01b7d0166c30287c166e137433d3b410add807a6", size = 263588, upload-time = "2026-01-26T02:44:09.382Z" }, + { url = "https://files.pythonhosted.org/packages/d7/11/afc7c677f68f75c84a69fe37184f0f82fce13ce4b92f49f3db280b7e92b3/multidict-6.7.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3d51ff4785d58d3f6c91bdbffcb5e1f7ddfda557727043aa20d20ec4f65e324a", size = 259642, upload-time = "2026-01-26T02:44:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/2b/17/ebb9644da78c4ab36403739e0e6e0e30ebb135b9caf3440825001a0bddcb/multidict-6.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc5907494fccf3e7d3f94f95c91d6336b092b5fc83811720fae5e2765890dfba", size = 251377, upload-time = "2026-01-26T02:44:12.042Z" }, + { url = "https://files.pythonhosted.org/packages/ca/a4/840f5b97339e27846c46307f2530a2805d9d537d8b8bd416af031cad7fa0/multidict-6.7.1-cp312-cp312-win32.whl", hash = "sha256:28ca5ce2fd9716631133d0e9a9b9a745ad7f60bac2bccafb56aa380fc0b6c511", size = 41887, upload-time = "2026-01-26T02:44:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/80/31/0b2517913687895f5904325c2069d6a3b78f66cc641a86a2baf75a05dcbb/multidict-6.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcee94dfbd638784645b066074b338bc9cc155d4b4bffa4adce1615c5a426c19", size = 46053, upload-time = "2026-01-26T02:44:15.371Z" }, + { url = "https://files.pythonhosted.org/packages/0c/5b/aba28e4ee4006ae4c7df8d327d31025d760ffa992ea23812a601d226e682/multidict-6.7.1-cp312-cp312-win_arm64.whl", hash = "sha256:ba0a9fb644d0c1a2194cf7ffb043bd852cea63a57f66fbd33959f7dae18517bf", size = 43307, upload-time = "2026-01-26T02:44:16.852Z" }, + { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] [[package]] @@ -1461,11 +1461,11 @@ provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -1735,11 +1735,11 @@ wheels = [ [[package]] name = "pycparser" -version = "2.23" +version = "3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fe/cf/d2d3b9f5699fb1e4615c8e32ff220203e43b248e1dfcc6736ad9057731ca/pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2", size = 173734, upload-time = "2025-09-09T13:23:47.91Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/e3/59cd50310fc9b59512193629e1984c1f95e5c8ae6e5d8c69532ccc65a7fe/pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934", size = 118140, upload-time = "2025-09-09T13:23:46.651Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, ] [[package]] @@ -4300,11 +4300,11 @@ wheels = [ [[package]] name = "pyparsing" -version = "3.3.1" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/33/c1/1d9de9aeaa1b89b0186e5fe23294ff6517fce1bc69149185577cd31016b2/pyparsing-3.3.1.tar.gz", hash = "sha256:47fad0f17ac1e2cad3de3b458570fbc9b03560aa029ed5e16ee5554da9a2251c", size = 1550512, upload-time = "2025-12-23T03:14:04.391Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] @@ -4702,28 +4702,28 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/50/0a/1914efb7903174b381ee2ffeebb4253e729de57f114e63595114c8ca451f/ruff-0.14.13.tar.gz", hash = "sha256:83cd6c0763190784b99650a20fec7633c59f6ebe41c5cc9d45ee42749563ad47", size = 6059504, upload-time = "2026-01-15T20:15:16.918Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c3/ae/0deefbc65ca74b0ab1fd3917f94dc3b398233346a74b8bbb0a916a1a6bf6/ruff-0.14.13-py3-none-linux_armv6l.whl", hash = "sha256:76f62c62cd37c276cb03a275b198c7c15bd1d60c989f944db08a8c1c2dbec18b", size = 13062418, upload-time = "2026-01-15T20:14:50.779Z" }, - { url = "https://files.pythonhosted.org/packages/47/df/5916604faa530a97a3c154c62a81cb6b735c0cb05d1e26d5ad0f0c8ac48a/ruff-0.14.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:914a8023ece0528d5cc33f5a684f5f38199bbb566a04815c2c211d8f40b5d0ed", size = 13442344, upload-time = "2026-01-15T20:15:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/4c/f3/e0e694dd69163c3a1671e102aa574a50357536f18a33375050334d5cd517/ruff-0.14.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d24899478c35ebfa730597a4a775d430ad0d5631b8647a3ab368c29b7e7bd063", size = 12354720, upload-time = "2026-01-15T20:15:09.854Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e8/67f5fcbbaee25e8fc3b56cc33e9892eca7ffe09f773c8e5907757a7e3bdb/ruff-0.14.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9aaf3870f14d925bbaf18b8a2347ee0ae7d95a2e490e4d4aea6813ed15ebc80e", size = 12774493, upload-time = "2026-01-15T20:15:20.908Z" }, - { url = "https://files.pythonhosted.org/packages/6b/ce/d2e9cb510870b52a9565d885c0d7668cc050e30fa2c8ac3fb1fda15c083d/ruff-0.14.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac5b7f63dd3b27cc811850f5ffd8fff845b00ad70e60b043aabf8d6ecc304e09", size = 12815174, upload-time = "2026-01-15T20:15:05.74Z" }, - { url = "https://files.pythonhosted.org/packages/88/00/c38e5da58beebcf4fa32d0ddd993b63dfacefd02ab7922614231330845bf/ruff-0.14.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78d2b1097750d90ba82ce4ba676e85230a0ed694178ca5e61aa9b459970b3eb9", size = 13680909, upload-time = "2026-01-15T20:15:14.537Z" }, - { url = "https://files.pythonhosted.org/packages/61/61/cd37c9dd5bd0a3099ba79b2a5899ad417d8f3b04038810b0501a80814fd7/ruff-0.14.13-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:7d0bf87705acbbcb8d4c24b2d77fbb73d40210a95c3903b443cd9e30824a5032", size = 15144215, upload-time = "2026-01-15T20:15:22.886Z" }, - { url = "https://files.pythonhosted.org/packages/56/8a/85502d7edbf98c2df7b8876f316c0157359165e16cdf98507c65c8d07d3d/ruff-0.14.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3eb5da8e2c9e9f13431032fdcbe7681de9ceda5835efee3269417c13f1fed5c", size = 14706067, upload-time = "2026-01-15T20:14:48.271Z" }, - { url = "https://files.pythonhosted.org/packages/7e/2f/de0df127feb2ee8c1e54354dc1179b4a23798f0866019528c938ba439aca/ruff-0.14.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:642442b42957093811cd8d2140dfadd19c7417030a7a68cf8d51fcdd5f217427", size = 14133916, upload-time = "2026-01-15T20:14:57.357Z" }, - { url = "https://files.pythonhosted.org/packages/0d/77/9b99686bb9fe07a757c82f6f95e555c7a47801a9305576a9c67e0a31d280/ruff-0.14.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4acdf009f32b46f6e8864af19cbf6841eaaed8638e65c8dac845aea0d703c841", size = 13859207, upload-time = "2026-01-15T20:14:55.111Z" }, - { url = "https://files.pythonhosted.org/packages/7d/46/2bdcb34a87a179a4d23022d818c1c236cb40e477faf0d7c9afb6813e5876/ruff-0.14.13-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:591a7f68860ea4e003917d19b5c4f5ac39ff558f162dc753a2c5de897fd5502c", size = 14043686, upload-time = "2026-01-15T20:14:52.841Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a9/5c6a4f56a0512c691cf143371bcf60505ed0f0860f24a85da8bd123b2bf1/ruff-0.14.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:774c77e841cc6e046fc3e91623ce0903d1cd07e3a36b1a9fe79b81dab3de506b", size = 12663837, upload-time = "2026-01-15T20:15:18.921Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bb/b920016ece7651fa7fcd335d9d199306665486694d4361547ccb19394c44/ruff-0.14.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:61f4e40077a1248436772bb6512db5fc4457fe4c49e7a94ea7c5088655dd21ae", size = 12805867, upload-time = "2026-01-15T20:14:59.272Z" }, - { url = "https://files.pythonhosted.org/packages/7d/b3/0bd909851e5696cd21e32a8fc25727e5f58f1934b3596975503e6e85415c/ruff-0.14.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6d02f1428357fae9e98ac7aa94b7e966fd24151088510d32cf6f902d6c09235e", size = 13208528, upload-time = "2026-01-15T20:15:03.732Z" }, - { url = "https://files.pythonhosted.org/packages/3b/3b/e2d94cb613f6bbd5155a75cbe072813756363eba46a3f2177a1fcd0cd670/ruff-0.14.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:e399341472ce15237be0c0ae5fbceca4b04cd9bebab1a2b2c979e015455d8f0c", size = 13929242, upload-time = "2026-01-15T20:15:11.918Z" }, - { url = "https://files.pythonhosted.org/packages/6a/c5/abd840d4132fd51a12f594934af5eba1d5d27298a6f5b5d6c3be45301caf/ruff-0.14.13-py3-none-win32.whl", hash = "sha256:ef720f529aec113968b45dfdb838ac8934e519711da53a0456038a0efecbd680", size = 12919024, upload-time = "2026-01-15T20:14:43.647Z" }, - { url = "https://files.pythonhosted.org/packages/c2/55/6384b0b8ce731b6e2ade2b5449bf07c0e4c31e8a2e68ea65b3bafadcecc5/ruff-0.14.13-py3-none-win_amd64.whl", hash = "sha256:6070bd026e409734b9257e03e3ef18c6e1a216f0435c6751d7a8ec69cb59abef", size = 14097887, upload-time = "2026-01-15T20:15:01.48Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/7348090988095e4e39560cfc2f7555b1b2a7357deba19167b600fdf5215d/ruff-0.14.13-py3-none-win_arm64.whl", hash = "sha256:7ab819e14f1ad9fe39f246cfcc435880ef7a9390d81a2b6ac7e01039083dd247", size = 13080224, upload-time = "2026-01-15T20:14:45.853Z" }, +version = "0.14.14" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, + { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, + { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, + { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, + { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, + { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, + { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, + { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, + { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, + { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, + { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, + { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, + { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, + { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, + { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, + { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, ] [[package]] @@ -4737,15 +4737,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.49.0" +version = "2.50.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/94/23ac26616a883f492428d9ee9ad6eee391612125326b784dbfc30e1e7bab/sentry_sdk-2.49.0.tar.gz", hash = "sha256:c1878599cde410d481c04ef50ee3aedd4f600e4d0d253f4763041e468b332c30", size = 387228, upload-time = "2026-01-08T09:56:25.642Z" } +sdist = { url = "https://files.pythonhosted.org/packages/15/8a/3c4f53d32c21012e9870913544e56bfa9e931aede080779a0f177513f534/sentry_sdk-2.50.0.tar.gz", hash = "sha256:873437a989ee1b8b25579847bae8384515bf18cfed231b06c591b735c1781fe3", size = 401233, upload-time = "2026-01-20T12:53:16.244Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/43/1c586f9f413765201234541857cb82fda076f4b0f7bad4a0ec248da39cf3/sentry_sdk-2.49.0-py2.py3-none-any.whl", hash = "sha256:6ea78499133874445a20fe9c826c9e960070abeb7ae0cdf930314ab16bb97aa0", size = 415693, upload-time = "2026-01-08T09:56:21.872Z" }, + { url = "https://files.pythonhosted.org/packages/4e/5b/cbc2bb9569f03c8e15d928357e7e6179e5cfab45544a3bbac8aec4caf9be/sentry_sdk-2.50.0-py2.py3-none-any.whl", hash = "sha256:0ef0ed7168657ceb5a0be081f4102d92042a125462d1d1a29277992e344e749e", size = 424961, upload-time = "2026-01-20T12:53:14.826Z" }, ] [[package]] @@ -4781,11 +4781,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.9.0" +version = "80.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, ] [[package]] @@ -4864,17 +4864,18 @@ wheels = [ [[package]] name = "sounddevice" -version = "0.5.3" +version = "0.5.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4e/4f/28e734898b870db15b6474453f19813d3c81b91c806d9e6f867bd6e4dd03/sounddevice-0.5.3.tar.gz", hash = "sha256:cbac2b60198fbab84533697e7c4904cc895ec69d5fb3973556c9eb74a4629b2c", size = 53465, upload-time = "2025-10-19T13:23:57.922Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2a/f9/2592608737553638fca98e21e54bfec40bf577bb98a61b2770c912aab25e/sounddevice-0.5.5.tar.gz", hash = "sha256:22487b65198cb5bf2208755105b524f78ad173e5ab6b445bdab1c989f6698df3", size = 143191, upload-time = "2026-01-23T18:36:43.529Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/e7/9020e9f0f3df00432728f4c4044387468a743e3d9a4f91123d77be10010e/sounddevice-0.5.3-py3-none-any.whl", hash = "sha256:ea7738baa0a9f9fef7390f649e41c9f2c8ada776180e56c2ffd217133c92a806", size = 32670, upload-time = "2025-10-19T13:23:51.779Z" }, - { url = "https://files.pythonhosted.org/packages/2f/39/714118f8413e0e353436914f2b976665161f1be2b6483ac15a8f61484c14/sounddevice-0.5.3-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:278dc4451fff70934a176df048b77d80d7ce1623a6ec9db8b34b806f3112f9c2", size = 108306, upload-time = "2025-10-19T13:23:53.277Z" }, - { url = "https://files.pythonhosted.org/packages/f5/74/52186e3e5c833d00273f7949a9383adff93692c6e02406bf359cb4d3e921/sounddevice-0.5.3-py3-none-win32.whl", hash = "sha256:845d6927bcf14e84be5292a61ab3359cf8e6b9145819ec6f3ac2619ff089a69c", size = 312882, upload-time = "2025-10-19T13:23:54.829Z" }, - { url = "https://files.pythonhosted.org/packages/66/c7/16123d054aef6d445176c9122bfbe73c11087589b2413cab22aff5a7839a/sounddevice-0.5.3-py3-none-win_amd64.whl", hash = "sha256:f55ad20082efc2bdec06928e974fbcae07bc6c405409ae1334cefe7d377eb687", size = 364025, upload-time = "2025-10-19T13:23:56.362Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0a/478e441fd049002cf308520c0d62dd8333e7c6cc8d997f0dda07b9fbcc46/sounddevice-0.5.5-py3-none-any.whl", hash = "sha256:30ff99f6c107f49d25ad16a45cacd8d91c25a1bcdd3e81a206b921a3a6405b1f", size = 32807, upload-time = "2026-01-23T18:36:35.649Z" }, + { url = "https://files.pythonhosted.org/packages/56/f9/c037c35f6d0b6bc3bc7bfb314f1d6f1f9a341328ef47cd63fc4f850a7b27/sounddevice-0.5.5-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:05eb9fd6c54c38d67741441c19164c0dae8ce80453af2d8c4ad2e7823d15b722", size = 108557, upload-time = "2026-01-23T18:36:37.41Z" }, + { url = "https://files.pythonhosted.org/packages/88/a1/d19dd9889cd4bce2e233c4fac007cd8daaf5b9fe6e6a5d432cf17be0b807/sounddevice-0.5.5-py3-none-win32.whl", hash = "sha256:1234cc9b4c9df97b6cbe748146ae0ec64dd7d6e44739e8e42eaa5b595313a103", size = 317765, upload-time = "2026-01-23T18:36:39.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/0e/002ed7c4c1c2ab69031f78989d3b789fee3a7fba9e586eb2b81688bf4961/sounddevice-0.5.5-py3-none-win_amd64.whl", hash = "sha256:cfc6b2c49fb7f555591c78cb8ecf48d6a637fd5b6e1db5fec6ed9365d64b3519", size = 365324, upload-time = "2026-01-23T18:36:40.496Z" }, + { url = "https://files.pythonhosted.org/packages/4e/39/a61d4b83a7746b70d23d9173be688c0c6bfc7173772344b7442c2c155497/sounddevice-0.5.5-py3-none-win_arm64.whl", hash = "sha256:3861901ddd8230d2e0e8ae62ac320cdd4c688d81df89da036dcb812f757bb3e6", size = 317115, upload-time = "2026-01-23T18:36:42.235Z" }, ] [[package]] @@ -4918,27 +4919,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.12" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b5/78/ba1a4ad403c748fbba8be63b7e774a90e80b67192f6443d624c64fe4aaab/ty-0.0.12.tar.gz", hash = "sha256:cd01810e106c3b652a01b8f784dd21741de9fdc47bd595d02c122a7d5cefeee7", size = 4981303, upload-time = "2026-01-14T22:30:48.537Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/8f/c21314d074dda5fb13d3300fa6733fd0d8ff23ea83a721818740665b6314/ty-0.0.12-py3-none-linux_armv6l.whl", hash = "sha256:eb9da1e2c68bd754e090eab39ed65edf95168d36cbeb43ff2bd9f86b4edd56d1", size = 9614164, upload-time = "2026-01-14T22:30:44.016Z" }, - { url = "https://files.pythonhosted.org/packages/09/28/f8a4d944d13519d70c486e8f96d6fa95647ac2aa94432e97d5cfec1f42f6/ty-0.0.12-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c181f42aa19b0ed7f1b0c2d559980b1f1d77cc09419f51c8321c7ddf67758853", size = 9542337, upload-time = "2026-01-14T22:30:05.687Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9c/f576e360441de7a8201daa6dc4ebc362853bc5305e059cceeb02ebdd9a48/ty-0.0.12-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1f829e1eecd39c3e1b032149db7ae6a3284f72fc36b42436e65243a9ed1173db", size = 8909582, upload-time = "2026-01-14T22:30:46.089Z" }, - { url = "https://files.pythonhosted.org/packages/d6/13/0898e494032a5d8af3060733d12929e3e7716db6c75eac63fa125730a3e7/ty-0.0.12-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45162e7826e1789cf3374627883cdeb0d56b82473a0771923e4572928e90be3", size = 9384932, upload-time = "2026-01-14T22:30:13.769Z" }, - { url = "https://files.pythonhosted.org/packages/e4/1a/b35b6c697008a11d4cedfd34d9672db2f0a0621ec80ece109e13fca4dfef/ty-0.0.12-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d11fec40b269bec01e751b2337d1c7ffa959a2c2090a950d7e21c2792442cccd", size = 9453140, upload-time = "2026-01-14T22:30:11.131Z" }, - { url = "https://files.pythonhosted.org/packages/dd/1e/71c9edbc79a3c88a0711324458f29c7dbf6c23452c6e760dc25725483064/ty-0.0.12-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09d99e37e761a4d2651ad9d5a610d11235fbcbf35dc6d4bc04abf54e7cf894f1", size = 9960680, upload-time = "2026-01-14T22:30:33.621Z" }, - { url = "https://files.pythonhosted.org/packages/0e/75/39375129f62dd22f6ad5a99cd2a42fd27d8b91b235ce2db86875cdad397d/ty-0.0.12-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:d9ca0cdb17bd37397da7b16a7cd23423fc65c3f9691e453ad46c723d121225a1", size = 10904518, upload-time = "2026-01-14T22:30:08.464Z" }, - { url = "https://files.pythonhosted.org/packages/32/5e/26c6d88fafa11a9d31ca9f4d12989f57782ec61e7291d4802d685b5be118/ty-0.0.12-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcf2757b905e7eddb7e456140066335b18eb68b634a9f72d6f54a427ab042c64", size = 10525001, upload-time = "2026-01-14T22:30:16.454Z" }, - { url = "https://files.pythonhosted.org/packages/c2/a5/2f0b91894af13187110f9ad7ee926d86e4e6efa755c9c88a820ed7f84c85/ty-0.0.12-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:00cf34c1ebe1147efeda3021a1064baa222c18cdac114b7b050bbe42deb4ca80", size = 10307103, upload-time = "2026-01-14T22:30:41.221Z" }, - { url = "https://files.pythonhosted.org/packages/4b/77/13d0410827e4bc713ebb7fdaf6b3590b37dcb1b82e0a81717b65548f2442/ty-0.0.12-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb3a655bd869352e9a22938d707631ac9fbca1016242b1f6d132d78f347c851", size = 10072737, upload-time = "2026-01-14T22:30:51.783Z" }, - { url = "https://files.pythonhosted.org/packages/e1/dd/fc36d8bac806c74cf04b4ca735bca14d19967ca84d88f31e121767880df1/ty-0.0.12-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4658e282c7cb82be304052f8f64f9925f23c3c4f90eeeb32663c74c4b095d7ba", size = 9368726, upload-time = "2026-01-14T22:30:18.683Z" }, - { url = "https://files.pythonhosted.org/packages/54/70/9e8e461647550f83e2fe54bc632ccbdc17a4909644783cdbdd17f7296059/ty-0.0.12-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:c167d838eaaa06e03bb66a517f75296b643d950fbd93c1d1686a187e5a8dbd1f", size = 9454704, upload-time = "2026-01-14T22:30:22.759Z" }, - { url = "https://files.pythonhosted.org/packages/04/9b/6292cf7c14a0efeca0539cf7d78f453beff0475cb039fbea0eb5d07d343d/ty-0.0.12-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2956e0c9ab7023533b461d8a0e6b2ea7b78e01a8dde0688e8234d0fce10c4c1c", size = 9649829, upload-time = "2026-01-14T22:30:31.234Z" }, - { url = "https://files.pythonhosted.org/packages/49/bd/472a5d2013371e4870886cff791c94abdf0b92d43d305dd0f8e06b6ff719/ty-0.0.12-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5c6a3fd7479580009f21002f3828320621d8a82d53b7ba36993234e3ccad58c8", size = 10162814, upload-time = "2026-01-14T22:30:36.174Z" }, - { url = "https://files.pythonhosted.org/packages/31/e9/2ecbe56826759845a7c21d80aa28187865ea62bc9757b056f6cbc06f78ed/ty-0.0.12-py3-none-win32.whl", hash = "sha256:a91c24fd75c0f1796d8ede9083e2c0ec96f106dbda73a09fe3135e075d31f742", size = 9140115, upload-time = "2026-01-14T22:30:38.903Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6d/d9531eff35a5c0ec9dbc10231fac21f9dd6504814048e81d6ce1c84dc566/ty-0.0.12-py3-none-win_amd64.whl", hash = "sha256:df151894be55c22d47068b0f3b484aff9e638761e2267e115d515fcc9c5b4a4b", size = 9884532, upload-time = "2026-01-14T22:30:25.112Z" }, - { url = "https://files.pythonhosted.org/packages/e9/f3/20b49e75967023b123a221134548ad7000f9429f13fdcdda115b4c26305f/ty-0.0.12-py3-none-win_arm64.whl", hash = "sha256:cea99d334b05629de937ce52f43278acf155d3a316ad6a35356635f886be20ea", size = 9313974, upload-time = "2026-01-14T22:30:27.44Z" }, +version = "0.0.13" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/dc/b607f00916f5a7c52860b84a66dc17bc6988e8445e96b1d6e175a3837397/ty-0.0.13.tar.gz", hash = "sha256:7a1d135a400ca076407ea30012d1f75419634160ed3b9cad96607bf2956b23b3", size = 4999183, upload-time = "2026-01-21T13:21:16.133Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/df/3632f1918f4c0a33184f107efc5d436ab6da147fd3d3b94b3af6461efbf4/ty-0.0.13-py3-none-linux_armv6l.whl", hash = "sha256:1b2b8e02697c3a94c722957d712a0615bcc317c9b9497be116ef746615d892f2", size = 9993501, upload-time = "2026-01-21T13:21:26.628Z" }, + { url = "https://files.pythonhosted.org/packages/92/87/6a473ced5ac280c6ce5b1627c71a8a695c64481b99aabc798718376a441e/ty-0.0.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f15cdb8e233e2b5adfce673bb21f4c5e8eaf3334842f7eea3c70ac6fda8c1de5", size = 9860986, upload-time = "2026-01-21T13:21:24.425Z" }, + { url = "https://files.pythonhosted.org/packages/5d/9b/d89ae375cf0a7cd9360e1164ce017f8c753759be63b6a11ed4c944abe8c6/ty-0.0.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0819e89ac9f0d8af7a062837ce197f0461fee2fc14fd07e2c368780d3a397b73", size = 9350748, upload-time = "2026-01-21T13:21:28.502Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a6/9ad58518056fab344b20c0bb2c1911936ebe195318e8acc3bc45ac1c6b6b/ty-0.0.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de79f481084b7cc7a202ba0d7a75e10970d10ffa4f025b23f2e6b7324b74886", size = 9849884, upload-time = "2026-01-21T13:21:21.886Z" }, + { url = "https://files.pythonhosted.org/packages/b1/c3/8add69095fa179f523d9e9afcc15a00818af0a37f2b237a9b59bc0046c34/ty-0.0.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4fb2154cff7c6e95d46bfaba283c60642616f20d73e5f96d0c89c269f3e1bcec", size = 9822975, upload-time = "2026-01-21T13:21:14.292Z" }, + { url = "https://files.pythonhosted.org/packages/a4/05/4c0927c68a0a6d43fb02f3f0b6c19c64e3461dc8ed6c404dde0efb8058f7/ty-0.0.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00be58d89337c27968a20d58ca553458608c5b634170e2bec82824c2e4cf4d96", size = 10294045, upload-time = "2026-01-21T13:21:30.505Z" }, + { url = "https://files.pythonhosted.org/packages/b4/86/6dc190838aba967557fe0bfd494c595d00b5081315a98aaf60c0e632aaeb/ty-0.0.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72435eade1fa58c6218abb4340f43a6c3ff856ae2dc5722a247d3a6dd32e9737", size = 10916460, upload-time = "2026-01-21T13:21:07.788Z" }, + { url = "https://files.pythonhosted.org/packages/04/40/9ead96b7c122e1109dfcd11671184c3506996bf6a649306ec427e81d9544/ty-0.0.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77a548742ee8f621d718159e7027c3b555051d096a49bb580249a6c5fc86c271", size = 10597154, upload-time = "2026-01-21T13:21:18.064Z" }, + { url = "https://files.pythonhosted.org/packages/aa/7d/e832a2c081d2be845dc6972d0c7998914d168ccbc0b9c86794419ab7376e/ty-0.0.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da067c57c289b7cf914669704b552b6207c2cc7f50da4118c3e12388642e6b3f", size = 10410710, upload-time = "2026-01-21T13:21:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/31/e3/898be3a96237a32f05c4c29b43594dc3b46e0eedfe8243058e46153b324f/ty-0.0.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d1b50a01fffa140417fca5a24b658fbe0734074a095d5b6f0552484724474343", size = 9826299, upload-time = "2026-01-21T13:21:00.845Z" }, + { url = "https://files.pythonhosted.org/packages/bb/eb/db2d852ce0ed742505ff18ee10d7d252f3acfd6fc60eca7e9c7a0288a6d8/ty-0.0.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f33c46f52e5e9378378eca0d8059f026f3c8073ace02f7f2e8d079ddfe5207e", size = 9831610, upload-time = "2026-01-21T13:21:05.842Z" }, + { url = "https://files.pythonhosted.org/packages/9e/61/149f59c8abaddcbcbb0bd13b89c7741ae1c637823c5cf92ed2c644fcadef/ty-0.0.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:168eda24d9a0b202cf3758c2962cc295878842042b7eca9ed2965259f59ce9f2", size = 9978885, upload-time = "2026-01-21T13:21:10.306Z" }, + { url = "https://files.pythonhosted.org/packages/a0/cd/026d4e4af60a80918a8d73d2c42b8262dd43ab2fa7b28d9743004cb88d57/ty-0.0.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d4917678b95dc8cb399cc459fab568ba8d5f0f33b7a94bf840d9733043c43f29", size = 10506453, upload-time = "2026-01-21T13:20:56.633Z" }, + { url = "https://files.pythonhosted.org/packages/63/06/8932833a4eca2df49c997a29afb26721612de8078ae79074c8fe87e17516/ty-0.0.13-py3-none-win32.whl", hash = "sha256:c1f2ec40daa405508b053e5b8e440fbae5fdb85c69c9ab0ee078f8bc00eeec3d", size = 9433482, upload-time = "2026-01-21T13:20:58.717Z" }, + { url = "https://files.pythonhosted.org/packages/aa/fd/e8d972d1a69df25c2cecb20ea50e49ad5f27a06f55f1f5f399a563e71645/ty-0.0.13-py3-none-win_amd64.whl", hash = "sha256:8b7b1ab9f187affbceff89d51076038363b14113be29bda2ddfa17116de1d476", size = 10319156, upload-time = "2026-01-21T13:21:03.266Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c2/05fdd64ac003a560d4fbd1faa7d9a31d75df8f901675e5bed1ee2ceeff87/ty-0.0.13-py3-none-win_arm64.whl", hash = "sha256:1c9630333497c77bb9bcabba42971b96ee1f36c601dd3dcac66b4134f9fa38f0", size = 9808316, upload-time = "2026-01-21T13:20:54.053Z" }, ] [[package]] From d76f756f42a4af121d64d46611ecb93936081760 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Mon, 26 Jan 2026 15:02:57 -0800 Subject: [PATCH 072/685] long_mpc: simplify longitudinal planner by removing "modes" (#37014) --- .../lib/longitudinal_mpc_lib/long_mpc.py | 125 +++++------------- .../controls/lib/longitudinal_planner.py | 32 ++--- .../controls/tests/test_following_distance.py | 7 +- 3 files changed, 47 insertions(+), 117 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 3f9d8245bd5..9408132c5b8 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -35,7 +35,7 @@ X_EGO_COST = 0. V_EGO_COST = 0. A_EGO_COST = 0. -J_EGO_COST = 5.0 +J_EGO_COST = 5. A_CHANGE_COST = 200. DANGER_ZONE_COST = 100. CRASH_DISTANCE = .25 @@ -43,7 +43,6 @@ LIMIT_COST = 1e6 ACADOS_SOLVER_TYPE = 'SQP_RTI' - # Fewer timestamps don't hurt performance and lead to # much better convergence of the MPC with low iterations N = 12 @@ -57,6 +56,7 @@ STOP_DISTANCE = 6.0 CRUISE_MIN_ACCEL = -1.2 CRUISE_MAX_ACCEL = 1.6 +MIN_X_LEAD_FACTOR = 0.5 def get_jerk_factor(personality=log.LongitudinalPersonality.standard): if personality==log.LongitudinalPersonality.relaxed: @@ -85,20 +85,12 @@ def get_stopped_equivalence_factor(v_lead): def get_safe_obstacle_distance(v_ego, t_follow): return (v_ego**2) / (2 * COMFORT_BRAKE) + t_follow * v_ego + STOP_DISTANCE -def desired_follow_distance(v_ego, v_lead, t_follow=None): - if t_follow is None: - t_follow = get_T_FOLLOW() - return get_safe_obstacle_distance(v_ego, t_follow) - get_stopped_equivalence_factor(v_lead) - - def gen_long_model(): model = AcadosModel() model.name = MODEL_NAME - # set up states & controls - x_ego = SX.sym('x_ego') - v_ego = SX.sym('v_ego') - a_ego = SX.sym('a_ego') + # states + x_ego, v_ego, a_ego = SX.sym('x_ego'), SX.sym('v_ego'), SX.sym('a_ego') model.x = vertcat(x_ego, v_ego, a_ego) # controls @@ -126,7 +118,6 @@ def gen_long_model(): model.f_expl_expr = f_expl return model - def gen_long_ocp(): ocp = AcadosOcp() ocp.model = gen_long_model() @@ -222,30 +213,31 @@ def gen_long_ocp(): class LongitudinalMpc: - def __init__(self, mode='acc', dt=DT_MDL): - self.mode = mode + def __init__(self, dt=DT_MDL): self.dt = dt self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() self.source = SOURCES[2] def reset(self): - # self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.solver.reset() - # self.solver.options_set('print_level', 2) + + self.x_sol = np.zeros((N+1, X_DIM)) + self.u_sol = np.zeros((N, 1)) self.v_solution = np.zeros(N+1) self.a_solution = np.zeros(N+1) - self.prev_a = np.array(self.a_solution) self.j_solution = np.zeros(N) + self.prev_a = np.array(self.a_solution) self.yref = np.zeros((N+1, COST_DIM)) + for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM]) - self.x_sol = np.zeros((N+1, X_DIM)) - self.u_sol = np.zeros((N,1)) + self.params = np.zeros((N+1, PARAM_DIM)) for i in range(N+1): self.solver.set(i, 'x', np.zeros(X_DIM)) + self.last_cloudlog_t = 0 self.status = False self.crash_cnt = 0.0 @@ -276,16 +268,9 @@ def set_cost_weights(self, cost_weights, constraint_cost_weights): def set_weights(self, prev_accel_constraint=True, personality=log.LongitudinalPersonality.standard): jerk_factor = get_jerk_factor(personality) - if self.mode == 'acc': - a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0 - cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, jerk_factor * a_change_cost, jerk_factor * J_EGO_COST] - constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] - elif self.mode == 'blended': - a_change_cost = 40.0 if prev_accel_constraint else 0 - cost_weights = [0., 0.1, 0.2, 5.0, a_change_cost, 1.0] - constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] - else: - raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner cost set') + a_change_cost = A_CHANGE_COST if prev_accel_constraint else 0 + cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, jerk_factor * a_change_cost, jerk_factor * J_EGO_COST] + constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] self.set_cost_weights(cost_weights, constraint_cost_weights) def set_cur_state(self, v, a): @@ -320,14 +305,14 @@ def process_lead(self, lead): # MPC will not converge if immediate crash is expected # Clip lead distance to what is still possible to brake for - min_x_lead = ((v_ego + v_lead)/2) * (v_ego - v_lead) / (-ACCEL_MIN * 2) + min_x_lead = MIN_X_LEAD_FACTOR * (v_ego + v_lead) * (v_ego - v_lead) / (-ACCEL_MIN * 2) x_lead = np.clip(x_lead, min_x_lead, 1e8) v_lead = np.clip(v_lead, 0.0, 1e8) a_lead = np.clip(a_lead, -10., 5.) lead_xv = self.extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau) return lead_xv - def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalPersonality.standard): + def update(self, radarstate, v_cruise, personality=log.LongitudinalPersonality.standard): t_follow = get_T_FOLLOW(personality) v_ego = self.x0[1] self.status = radarstate.leadOne.status or radarstate.leadTwo.status @@ -341,56 +326,28 @@ def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalP lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1]) lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1]) - self.params[:,0] = ACCEL_MIN - self.params[:,1] = ACCEL_MAX - - # Update in ACC mode or ACC/e2e blend - if self.mode == 'acc': - self.params[:,5] = LEAD_DANGER_FACTOR - - # Fake an obstacle for cruise, this ensures smooth acceleration to set speed - # when the leads are no factor. - v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05) - # TODO does this make sense when max_a is negative? - v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05) - v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), - v_lower, - v_upper) - cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) - x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) - self.source = SOURCES[np.argmin(x_obstacles[0])] - - # These are not used in ACC mode - x[:], v[:], a[:], j[:] = 0.0, 0.0, 0.0, 0.0 - - elif self.mode == 'blended': - self.params[:,5] = 1.0 - - x_obstacles = np.column_stack([lead_0_obstacle, - lead_1_obstacle]) - cruise_target = T_IDXS * np.clip(v_cruise, v_ego - 2.0, 1e3) + x[0] - xforward = ((v[1:] + v[:-1]) / 2) * (T_IDXS[1:] - T_IDXS[:-1]) - x = np.cumsum(np.insert(xforward, 0, x[0])) - - x_and_cruise = np.column_stack([x, cruise_target]) - x = np.min(x_and_cruise, axis=1) + # Fake an obstacle for cruise, this ensures smooth acceleration to set speed + # when the leads are no factor. + v_lower = v_ego + (T_IDXS * CRUISE_MIN_ACCEL * 1.05) + # TODO does this make sense when max_a is negative? + v_upper = v_ego + (T_IDXS * CRUISE_MAX_ACCEL * 1.05) + v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), v_lower, v_upper) + cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) - self.source = 'e2e' if x_and_cruise[1,0] < x_and_cruise[1,1] else 'cruise' + x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) + self.source = SOURCES[np.argmin(x_obstacles[0])] - else: - raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner update') - - self.yref[:,1] = x - self.yref[:,2] = v - self.yref[:,3] = a - self.yref[:,5] = j + self.yref[:,:] = 0.0 for i in range(N): self.solver.set(i, "yref", self.yref[i]) self.solver.set(N, "yref", self.yref[N][:COST_E_DIM]) + self.params[:,0] = ACCEL_MIN + self.params[:,1] = ACCEL_MAX self.params[:,2] = np.min(x_obstacles, axis=1) self.params[:,3] = np.copy(self.prev_a) self.params[:,4] = t_follow + self.params[:,5] = LEAD_DANGER_FACTOR self.run() if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and @@ -399,18 +356,7 @@ def update(self, radarstate, v_cruise, x, v, a, j, personality=log.LongitudinalP else: self.crash_cnt = 0 - # Check if it got within lead comfort range - # TODO This should be done cleaner - if self.mode == 'blended': - if any((lead_0_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0): - self.source = 'lead0' - if any((lead_1_obstacle - get_safe_obstacle_distance(self.x_sol[:,1], t_follow))- self.x_sol[:,0] < 0.0) and \ - (lead_1_obstacle[0] - lead_0_obstacle[0]): - self.source = 'lead1' - def run(self): - # t0 = time.monotonic() - # reset = 0 for i in range(N+1): self.solver.set(i, 'p', self.params[i]) self.solver.constraints_set(0, "lbx", self.x0) @@ -422,13 +368,6 @@ def run(self): self.time_linearization = float(self.solver.get_stats('time_lin')[0]) self.time_integrator = float(self.solver.get_stats('time_sim')[0]) - # qp_iter = self.solver.get_stats('statistics')[-1][-1] # SQP_RTI specific - # print(f"long_mpc timings: tot {self.solve_time:.2e}, qp {self.time_qp_solution:.2e}, lin {self.time_linearization:.2e}, \ - # integrator {self.time_integrator:.2e}, qp_iter {qp_iter}") - # res = self.solver.get_residuals() - # print(f"long_mpc residuals: {res[0]:.2e}, {res[1]:.2e}, {res[2]:.2e}, {res[3]:.2e}") - # self.solver.print_statistics() - for i in range(N+1): self.x_sol[i] = self.solver.get(i, 'x') for i in range(N): @@ -446,12 +385,8 @@ def run(self): self.last_cloudlog_t = t cloudlog.warning(f"Long mpc reset, solution_status: {self.solution_status}") self.reset() - # reset = 1 - # print(f"long_mpc timings: total internal {self.solve_time:.2e}, external: {(time.monotonic() - t0):.2e} qp {self.time_qp_solution:.2e}, \ - # lin {self.time_linearization:.2e} qp_iter {qp_iter}, reset {reset}") if __name__ == "__main__": ocp = gen_long_ocp() AcadosOcpSolver.generate(ocp, json_file=JSON_FILE) - # AcadosOcpSolver.build(ocp.code_export_directory, with_cython=True) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 34fc85f8a55..ad84ecf24fa 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -9,13 +9,12 @@ from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, SOURCES from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET from openpilot.common.swaglog import cloudlog -LON_MPC_STEP = 0.2 # first step is 0.2s A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6] A_CRUISE_MAX_BP = [0., 10.0, 25., 40.] CONTROL_N_T_IDX = ModelConstants.T_IDXS[:CONTROL_N] @@ -26,14 +25,12 @@ _A_TOTAL_MAX_V = [1.7, 3.2] _A_TOTAL_MAX_BP = [20., 40.] - def get_max_accel(v_ego): return np.interp(v_ego, A_CRUISE_MAX_BP, A_CRUISE_MAX_VALS) def get_coast_accel(pitch): return np.sin(pitch) * -5.65 - 0.3 # fitted from data using xx/projects/allow_throttle/compute_coast_accel.py - def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): """ This function returns a limited long acceleration allowed, depending on the existing lateral acceleration @@ -52,8 +49,6 @@ class LongitudinalPlanner: def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL): self.CP = CP self.mpc = LongitudinalMpc(dt=dt) - # TODO remove mpc modes when TR released - self.mpc.mode = 'acc' self.fcw = False self.dt = dt self.allow_throttle = True @@ -67,7 +62,6 @@ def __init__(self, CP, init_v=0.0, init_a=0.0, dt=DT_MDL): self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) self.j_desired_trajectory = np.zeros(CONTROL_N) - self.solverExecutionTime = 0.0 @staticmethod def parse_model(model_msg): @@ -90,8 +84,6 @@ def parse_model(model_msg): return x, v, a, j, throttle_prob def update(self, sm): - mode = 'blended' if sm['selfdriveState'].experimentalMode else 'acc' - if len(sm['carControl'].orientationNED) == 3: accel_coast = get_coast_accel(sm['carControl'].orientationNED[1]) else: @@ -113,12 +105,9 @@ def update(self, sm): # No change cost when user is controlling the speed, or when standstill prev_accel_constraint = not (reset_state or sm['carState'].standstill) - if mode == 'acc': - accel_clip = [ACCEL_MIN, get_max_accel(v_ego)] - steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg - accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP) - else: - accel_clip = [ACCEL_MIN, ACCEL_MAX] + accel_clip = [ACCEL_MIN, get_max_accel(v_ego)] + steer_angle_without_offset = sm['carState'].steeringAngleDeg - sm['liveParameters'].angleOffsetDeg + accel_clip = limit_accel_in_turns(v_ego, steer_angle_without_offset, accel_clip, self.CP) if reset_state: self.v_desired_filter.x = v_ego @@ -127,7 +116,7 @@ def update(self, sm): # Prevent divergence, smooth in current v_ego self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) - x, v, a, j, throttle_prob = self.parse_model(sm['modelV2']) + _, _, _, _, throttle_prob = self.parse_model(sm['modelV2']) # Don't clip at low speeds since throttle_prob doesn't account for creep self.allow_throttle = throttle_prob > ALLOW_THROTTLE_THRESHOLD or v_ego <= MIN_ALLOW_THROTTLE_SPEED @@ -141,7 +130,7 @@ def update(self, sm): self.mpc.set_weights(prev_accel_constraint, personality=sm['selfdriveState'].personality) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) - self.mpc.update(sm['radarState'], v_cruise, x, v, a, j, personality=sm['selfdriveState'].personality) + self.mpc.update(sm['radarState'], v_cruise, personality=sm['selfdriveState'].personality) self.v_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(CONTROL_N_T_IDX, T_IDXS_MPC, self.mpc.a_solution) @@ -163,12 +152,13 @@ def update(self, sm): output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_should_stop_e2e = sm['modelV2'].action.shouldStop - if mode == 'acc': + if (output_a_target_e2e < output_a_target_mpc) and sm['selfdriveState'].experimentalMode: + output_a_target = output_a_target_e2e + self.output_should_stop = output_should_stop_e2e + self.mpc.source = SOURCES[3] + else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc - else: - output_a_target = min(output_a_target_mpc, output_a_target_e2e) - self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc for idx in range(2): accel_clip[idx] = np.clip(accel_clip[idx], self.prev_accel_clip[idx] - 0.05, self.prev_accel_clip[idx] + 0.05) diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index 0fd543dd605..8f66d89bf87 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -4,10 +4,15 @@ from cereal import log -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance, get_T_FOLLOW +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import get_safe_obstacle_distance, get_stopped_equivalence_factor, get_T_FOLLOW from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver +def desired_follow_distance(v_ego, v_lead, t_follow=None): + if t_follow is None: + t_follow = get_T_FOLLOW() + return get_safe_obstacle_distance(v_ego, t_follow) - get_stopped_equivalence_factor(v_lead) + def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False, personality=0): man = Maneuver( '', From 97329e46ae11b92cf7f82fed485e5e141be6dfe2 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Mon, 26 Jan 2026 16:07:13 -0800 Subject: [PATCH 073/685] longitudinal maneuvers: add report for longitudinal mpc tuning (#37030) --- .../mpc_longitudinal_tuning_report.py | 276 ++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py new file mode 100644 index 00000000000..583c6240e52 --- /dev/null +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -0,0 +1,276 @@ +import io +import sys +import markdown +import numpy as np +import matplotlib.pyplot as plt +from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver +from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance + +TIME = 0 +EGO_V = 3 +EGO_A = 5 +LEAD_DISTANCE= 2 + +axis_labels = ['Time (s)', + 'Ego position (m)', + 'Lead distance (m)', + 'Ego Velocity (m/s)', + 'Lead Velocity (m/s)', + 'Ego acceleration (m/s^2)', + ] + + +def get_html_from_results(results, labels, AXIS): + fig, ax = plt.subplots(figsize=(16, 8)) + for idx, speed in enumerate(list(results.keys())): + ax.plot(results[speed][:, TIME], results[speed][:, AXIS], label=labels[idx]) + + ax.set_xlabel('Time (s)') + ax.set_ylabel(axis_labels[AXIS]) + ax.legend(bbox_to_anchor=(1.02, 1), loc='upper left', borderaxespad=0) + ax.grid(True, linestyle='--', alpha=0.7) + ax.text(-0.075, 0.5, '.', transform=ax.transAxes, color='none') + + fig_buffer = io.StringIO() + fig.savefig(fig_buffer, format='svg', bbox_inches='tight') + plt.close(fig) + return fig_buffer.getvalue() + '
' + + +htmls = [] + +results = {} +name = 'Resuming behind lead' +labels = [] +for lead_accel in np.linspace(1.0, 4.0, 4): + man = Maneuver( + '', + duration=11, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 10 * lead_accel], + cruise_values=[100, 100], + prob_lead_values=[1.0, 1.0], + breakpoints=[1., 11], + ) + valid, results[lead_accel] = man.evaluate() + labels.append(f'{lead_accel} m/s^2 lead acceleration') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + + +results = {} +name = 'Approaching stopped car from 140m' +labels = [] +for speed in np.arange(0,45,5): + man = Maneuver( + name, + duration=30., + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=140., + speed_lead_values=[0.0, 0.], + breakpoints=[0., 30.], + ) + valid, results[speed] = man.evaluate() + results[speed][:,2] = results[speed][:,2] - results[speed][:,1] + labels.append(f'{speed} m/s approach speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_A)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Following 5s oscillating lead' +labels = [] +speed = np.int64(10) +for oscil in np.arange(0, 10, 1): + man = Maneuver( + '', + duration=30., + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed), + speed_lead_values=[speed, speed, speed - oscil, speed + oscil, speed - oscil, speed + oscil, speed - oscil], + breakpoints=[0.,2., 5, 8, 15, 18, 25.], + ) + valid, results[oscil] = man.evaluate() + labels.append(f'{oscil} m/s oscilliation size') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + + + +results = {} +name = 'Speed profile when converging to steady state lead at 30m/s' +labels = [] +for distance in np.arange(20, 140, 10): + man = Maneuver( + '', + duration=50, + initial_speed=30.0, + lead_relevancy=True, + initial_distance_lead=distance, + speed_lead_values=[30.0], + breakpoints=[0.], + ) + valid, results[distance] = man.evaluate() + results[distance][:,2] = results[distance][:,2] - results[distance][:,1] + labels.append(f'{distance} m initial distance') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Speed profile when converging to steady state lead at 20m/s' +labels = [] +for distance in np.arange(20, 140, 10): + man = Maneuver( + '', + duration=50, + initial_speed=20.0, + lead_relevancy=True, + initial_distance_lead=distance, + speed_lead_values=[20.0], + breakpoints=[0.], + ) + valid, results[distance] = man.evaluate() + results[distance][:,2] = results[distance][:,2] - results[distance][:,1] + labels.append(f'{distance} m initial distance') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Following car at 30m/s that comes to a stop' +labels = [] +for stop_time in np.arange(4, 14, 1): + man = Maneuver( + '', + duration=50, + initial_speed=30.0, + lead_relevancy=True, + initial_distance_lead=60.0, + speed_lead_values=[30.0, 30.0, 0.0, 0.0], + breakpoints=[0., 20., 20 + stop_time, 30 + stop_time], + ) + valid, results[stop_time] = man.evaluate() + results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] + labels.append(f'{stop_time} seconds stop time') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_A)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Response to cut-in at half follow distance' +labels = [] +for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=10, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed)/2, + speed_lead_values=[speed, speed, speed], + cruise_values=[speed, speed, speed], + prob_lead_values=[0.0, 0.0, 1.0], + breakpoints=[0., 5.0, 5.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_A)) +htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) + + +results = {} +name = 'Follow a lead that accelerates at 2m/s^2 until steady state speed' +labels = [] +for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0, speed], + prob_lead_values=[1.0, 1.0, 1.0], + breakpoints=[0., 1.0, speed/2], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + + +results = {} +name = 'From stop to cruise' +labels = [] +for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0], + cruise_values=[0.0, speed], + prob_lead_values=[0.0, 0.0], + breakpoints=[1., 1.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + + +results = {} +name = 'From cruise to min' +labels = [] +for speed in np.arange(10, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0], + cruise_values=[speed, 10.0], + prob_lead_values=[0.0, 0.0], + breakpoints=[1., 1.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + +htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, EGO_V)) +htmls.append(get_html_from_results(results, labels, EGO_A)) + +if len(sys.argv) < 2: + file_name = 'long_mpc_tune_report.html' +else: + file_name = sys.argv[1] + +with open(file_name, 'w') as f: + f.write(markdown.markdown('# MPC longitudinal tuning report')) + +with open(file_name, 'a') as f: + for html in htmls: + f.write(html) From 93015c1c178e218adcfe9688ea13f58effc6d94e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Jan 2026 15:40:09 -0800 Subject: [PATCH 074/685] ui: fix button label color (#37031) label color --- selfdrive/ui/mici/widgets/button.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 82310577b0a..9678827a91e 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -16,7 +16,7 @@ SCROLLING_SPEED_PX_S = 50 COMPLICATION_SIZE = 36 -LABEL_COLOR = rl.WHITE +LABEL_COLOR = rl.Color(255, 255, 255, int(255 * 0.9)) LABEL_HORIZONTAL_PADDING = 40 COMPLICATION_GREY = rl.Color(0xAA, 0xAA, 0xAA, 255) PRESSED_SCALE = 1.15 if DO_ZOOM else 1.07 From bf8cae5e7cbbec97483f7e358568ea2e21ca8579 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Jan 2026 16:20:32 -0800 Subject: [PATCH 075/685] mici ui: new icons (#37021) * new icons * add missing * fixed tethering big icon, size of pairing comma, buttons now use 90percent white * why o why * newline * fancy * already default * fixes * add firehose * ltl * fix caps lock icon --------- Co-authored-by: nickorie --- selfdrive/assets/icons_mici/adb_short.png | 3 +++ .../buttons/toggle_dot_disabled.png | 4 +-- .../assets/icons_mici/exclamation_point.png | 4 +-- .../assets/icons_mici/experimental_mode.png | 4 +-- selfdrive/assets/icons_mici/microphone.png | 4 +-- .../icons_mici/offroad_alerts/green_wheel.png | 4 +-- .../offroad_alerts/orange_warning.png | 4 +-- .../icons_mici/offroad_alerts/red_warning.png | 4 +-- .../icons_mici/onroad/blind_spot_left.png | 4 +-- .../icons_mici/onroad/blind_spot_right.png | 4 +-- .../assets/icons_mici/onroad/bookmark.png | 4 +-- .../icons_mici/onroad/bookmark_fill.png | 3 +++ .../driver_monitoring/dm_background.png | 4 +-- .../onroad/driver_monitoring/dm_person.png | 4 +-- .../assets/icons_mici/onroad/eye_fill.png | 4 +-- .../assets/icons_mici/onroad/eye_orange.png | 4 +-- .../assets/icons_mici/onroad/glasses.png | 4 +-- .../assets/icons_mici/onroad/onroad_fade.png | 4 +-- .../icons_mici/onroad/turn_signal_left.png | 4 +-- .../icons_mici/onroad/turn_signal_right.png | 4 +-- selfdrive/assets/icons_mici/settings.png | 4 +-- .../assets/icons_mici/settings/comma_icon.png | 4 +-- .../icons_mici/settings/developer/ssh.png | 4 +-- .../icons_mici/settings/developer_icon.png | 4 +-- .../icons_mici/settings/device/cameras.png | 4 +-- .../icons_mici/settings/device/info.png | 4 +-- .../icons_mici/settings/device/language.png | 4 +-- .../icons_mici/settings/device/lkas.png | 4 +-- .../icons_mici/settings/device/pair.png | 4 +-- .../icons_mici/settings/device/power.png | 4 +-- .../icons_mici/settings/device/reboot.png | 4 +-- .../icons_mici/settings/device/uninstall.png | 4 +-- .../icons_mici/settings/device/up_to_date.png | 4 +-- .../icons_mici/settings/device/update.png | 4 +-- .../icons_mici/settings/device_icon.png | 4 +-- .../assets/icons_mici/settings/firehose.png | 3 +++ .../settings/keyboard/backspace.png | 4 +-- .../settings/keyboard/caps_lock.png | 4 +-- .../settings/keyboard/caps_lower.png | 4 +-- .../settings/keyboard/caps_upper.png | 4 +-- .../icons_mici/settings/keyboard/confirm.png | 4 +-- .../icons_mici/settings/keyboard/space.png | 4 +-- .../icons_mici/settings/manual_icon.png | 3 --- .../settings/network/cell_strength_full.png | 4 +-- .../settings/network/cell_strength_high.png | 4 +-- .../settings/network/cell_strength_low.png | 4 +-- .../settings/network/cell_strength_medium.png | 4 +-- .../settings/network/cell_strength_none.png | 4 +-- .../icons_mici/settings/network/new/lock.png | 4 +-- .../icons_mici/settings/network/new/trash.png | 4 +-- .../icons_mici/settings/network/tethering.png | 4 +-- .../settings/network/wifi_strength_full.png | 4 +-- .../settings/network/wifi_strength_low.png | 4 +-- .../settings/network/wifi_strength_medium.png | 4 +-- .../settings/network/wifi_strength_none.png | 4 +-- .../settings/network/wifi_strength_slash.png | 4 +-- .../icons_mici/settings/toggles_icon.png | 3 --- .../assets/icons_mici/setup/back_new.png | 4 +-- .../setup/driver_monitoring/dm_check.png | 4 +-- .../setup/driver_monitoring/dm_question.png | 4 +-- .../assets/icons_mici/setup/green_car.png | 3 --- .../assets/icons_mici/setup/green_dm.png | 4 +-- .../assets/icons_mici/setup/green_info.png | 4 +-- .../assets/icons_mici/setup/green_pedal.png | 3 --- .../assets/icons_mici/setup/orange_dm.png | 4 +-- .../assets/icons_mici/setup/red_warning.png | 4 +-- selfdrive/assets/icons_mici/setup/restore.png | 4 +-- .../setup/scroll_down_indicator.png | 4 +-- .../setup/small_slider/slider_arrow.png | 4 +-- selfdrive/assets/icons_mici/setup/warning.png | 4 +-- selfdrive/assets/icons_mici/ssh_short.png | 3 +++ .../assets/icons_mici/tethering_short.png | 3 +++ .../assets/icons_mici/turn_intent_left.png | 4 +-- .../assets/icons_mici/turn_intent_right.png | 4 +-- selfdrive/assets/icons_mici/wheel.png | 4 +-- .../assets/icons_mici/wheel_critical.png | 4 +-- selfdrive/ui/mici/layouts/home.py | 22 ++++++++-------- selfdrive/ui/mici/layouts/onboarding.py | 4 +-- .../ui/mici/layouts/settings/developer.py | 8 +++--- selfdrive/ui/mici/layouts/settings/device.py | 12 ++++----- .../mici/layouts/settings/network/__init__.py | 9 +++---- .../mici/layouts/settings/network/wifi_ui.py | 12 ++++----- .../ui/mici/layouts/settings/settings.py | 10 ++++---- selfdrive/ui/mici/onroad/alert_renderer.py | 8 +++--- selfdrive/ui/mici/onroad/hud_renderer.py | 4 +-- selfdrive/ui/mici/widgets/button.py | 25 +++++++++++-------- selfdrive/ui/mici/widgets/dialog.py | 4 +-- selfdrive/ui/mici/widgets/pairing_dialog.py | 2 +- system/ui/widgets/mici_keyboard.py | 20 ++++++++------- 89 files changed, 221 insertions(+), 214 deletions(-) create mode 100644 selfdrive/assets/icons_mici/adb_short.png create mode 100644 selfdrive/assets/icons_mici/onroad/bookmark_fill.png create mode 100644 selfdrive/assets/icons_mici/settings/firehose.png delete mode 100644 selfdrive/assets/icons_mici/settings/manual_icon.png delete mode 100644 selfdrive/assets/icons_mici/settings/toggles_icon.png delete mode 100644 selfdrive/assets/icons_mici/setup/green_car.png delete mode 100644 selfdrive/assets/icons_mici/setup/green_pedal.png create mode 100644 selfdrive/assets/icons_mici/ssh_short.png create mode 100644 selfdrive/assets/icons_mici/tethering_short.png diff --git a/selfdrive/assets/icons_mici/adb_short.png b/selfdrive/assets/icons_mici/adb_short.png new file mode 100644 index 00000000000..c49226c858a --- /dev/null +++ b/selfdrive/assets/icons_mici/adb_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:263598da73c577c01cebd31ae78f45969ef8b335be1a5f55d54a696bb2982c0a +size 2062 diff --git a/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png b/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png index 0e21bc1b5ae..1ff4db45a55 100644 --- a/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png +++ b/selfdrive/assets/icons_mici/buttons/toggle_dot_disabled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:613af9ed79bb26c60fbd19c094214f0881736c0e293f6d000b530cde0478a273 -size 2470 +oid sha256:89ac033d879beeb0a7fa1919838e0ec64b1a625a4aafc14f7b990c607a79b676 +size 2220 diff --git a/selfdrive/assets/icons_mici/exclamation_point.png b/selfdrive/assets/icons_mici/exclamation_point.png index 246fc015ecf..ede3b638bc3 100644 --- a/selfdrive/assets/icons_mici/exclamation_point.png +++ b/selfdrive/assets/icons_mici/exclamation_point.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b77579c099c688d1a27f356197fba9c2c8efcf4d391af580b4b29f0e70587919 -size 2086 +oid sha256:254b7f753b70c964847b686f0f71af751f2f49beea6ede4aeb333fe06062a257 +size 2289 diff --git a/selfdrive/assets/icons_mici/experimental_mode.png b/selfdrive/assets/icons_mici/experimental_mode.png index e0138bfd653..75850d08f51 100644 --- a/selfdrive/assets/icons_mici/experimental_mode.png +++ b/selfdrive/assets/icons_mici/experimental_mode.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb42b8d6259238beb26f286dc28fb2dc8d91b00fec1f7a7655296b5769439a15 -size 15690 +oid sha256:01841b602632c66ab14a8e52b874a1623f09641dc2ef0620f4e2d00bb4a913f3 +size 16243 diff --git a/selfdrive/assets/icons_mici/microphone.png b/selfdrive/assets/icons_mici/microphone.png index 9718a6b1355..9af8f2f4552 100644 --- a/selfdrive/assets/icons_mici/microphone.png +++ b/selfdrive/assets/icons_mici/microphone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17b6fe530598cbad34bcf31d4f21f929b792aacedef51b3ffef1941c86017811 -size 7331 +oid sha256:744dbaa68ee74e300cd46439bad79449c860e1c5c027304b0f382bd5383fba77 +size 6817 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png b/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png index 6a8351f6eea..08181ca35f4 100644 --- a/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png +++ b/selfdrive/assets/icons_mici/offroad_alerts/green_wheel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:05f3626e790622a4ad90e982c4aacb612d0785a752339352a3187addf763e2e9 -size 13288 +oid sha256:3b11ee84d48972a2499cb29f01594d77a1a39692f6424a315a3f83262bc16087 +size 13481 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png b/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png index 13af475c6dc..52e6836d4b4 100644 --- a/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png +++ b/selfdrive/assets/icons_mici/offroad_alerts/orange_warning.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a877882a8dccb884bd35918f9f9b427a724a59e90a638e54f6fd5d0680ad173c -size 12137 +oid sha256:d548405a65ba4d4590c55866612dc6aa0e78d9278fc864ef60fe3e463edf4a68 +size 12169 diff --git a/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png b/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png index 83c3595b295..df608d3518b 100644 --- a/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png +++ b/selfdrive/assets/icons_mici/offroad_alerts/red_warning.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba944b208abed9b8b9752adb8017bd29cd2e98c89fb07ee5d0a595185c7564a5 -size 11898 +oid sha256:b6fc63326d34fbe72f6daf104d101ce19e547dbfe134427c067c957a7179df74 +size 12124 diff --git a/selfdrive/assets/icons_mici/onroad/blind_spot_left.png b/selfdrive/assets/icons_mici/onroad/blind_spot_left.png index 5d3b1e5d7b7..fdc189b8582 100644 --- a/selfdrive/assets/icons_mici/onroad/blind_spot_left.png +++ b/selfdrive/assets/icons_mici/onroad/blind_spot_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a23743d21bc8160e013625210654a55634e4ed58e60057b70e08761bac1c3680 -size 40406 +oid sha256:77b20a8c478d982412d556afb3a035b80b4aa9fe7a86aea761af4a42147d9435 +size 45297 diff --git a/selfdrive/assets/icons_mici/onroad/blind_spot_right.png b/selfdrive/assets/icons_mici/onroad/blind_spot_right.png index 67216078d95..b6cd7834ef5 100644 --- a/selfdrive/assets/icons_mici/onroad/blind_spot_right.png +++ b/selfdrive/assets/icons_mici/onroad/blind_spot_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:acbfa3e38f0b9f422f5c1335ce20013852df2892b813db176a51918adc83ad58 -size 40979 +oid sha256:584cea202afff6dd20d67ae1a9cd6d2b8cc07598bccb91a8d1bac0142567308e +size 45489 diff --git a/selfdrive/assets/icons_mici/onroad/bookmark.png b/selfdrive/assets/icons_mici/onroad/bookmark.png index 207182276ed..305561f509c 100644 --- a/selfdrive/assets/icons_mici/onroad/bookmark.png +++ b/selfdrive/assets/icons_mici/onroad/bookmark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e0d00d743b01c49c2b739127e9916a229caf8c48346d6d168863b080ddcaa409 -size 11124 +oid sha256:fd91685bf656e828648acf035a4737acb2c4709e8514cf0aa0a10fa470a9bb60 +size 11580 diff --git a/selfdrive/assets/icons_mici/onroad/bookmark_fill.png b/selfdrive/assets/icons_mici/onroad/bookmark_fill.png new file mode 100644 index 00000000000..531d5db1cfb --- /dev/null +++ b/selfdrive/assets/icons_mici/onroad/bookmark_fill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f3f57346a1cf9a66f9fd746f87bcebb23b7a403e9d6e4fd7701b126abcdd47ea +size 18476 diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png index 04ffc24356d..4129b13d922 100644 --- a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_background.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7eb870d01e5bf6c421e204026a4ea08e177731f2d6b5b17c4ad43c90c1c3e78 -size 23549 +oid sha256:cb89d9f11cf44992f92142aa5ad84e1ac700a2601aff2abab373e2a822af149e +size 11678 diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png index 540b2029a0f..5b917f3a4a8 100644 --- a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png +++ b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_person.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f7b3bb76ee2359076339285ea6bced5b680e5b919a1b7dee163f36cd819c9ea1 -size 1746 +oid sha256:e2772c6a9fe9c57099d347ad49f0cb7c906593f1fdf0e6dde96d104baf0200b0 +size 1365 diff --git a/selfdrive/assets/icons_mici/onroad/eye_fill.png b/selfdrive/assets/icons_mici/onroad/eye_fill.png index 8f0e8ebfb1d..78758a9809c 100644 --- a/selfdrive/assets/icons_mici/onroad/eye_fill.png +++ b/selfdrive/assets/icons_mici/onroad/eye_fill.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:51af75afbaf30abeaae1c99c7ad3e25cf5d5c90a2d6c799aad353b3302384b0a -size 4829 +oid sha256:07310879d093108435c0011846ae1184966db86443bc6e7ca036a6fa6123700b +size 4983 diff --git a/selfdrive/assets/icons_mici/onroad/eye_orange.png b/selfdrive/assets/icons_mici/onroad/eye_orange.png index b61b9b063c4..932c71260b4 100644 --- a/selfdrive/assets/icons_mici/onroad/eye_orange.png +++ b/selfdrive/assets/icons_mici/onroad/eye_orange.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88b2ecf3a9834d2b156bb632ec2090d7dc112e8ab61711ba645c03489d1c457f -size 29157 +oid sha256:7be447e56d649e0362ef650494b484e140a01ead31799ce43b266f5781c918d2 +size 36473 diff --git a/selfdrive/assets/icons_mici/onroad/glasses.png b/selfdrive/assets/icons_mici/onroad/glasses.png index 1ac4442f491..006972fd397 100644 --- a/selfdrive/assets/icons_mici/onroad/glasses.png +++ b/selfdrive/assets/icons_mici/onroad/glasses.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28c95c8970648d40b35b94724936a9ab7a6f4cbca367a40f01b86f9abedc70e5 -size 1587 +oid sha256:56de402482b5987ed9a0ff3f793a1c89f857304b34fbb8a3deb5b5d4a332be1c +size 3688 diff --git a/selfdrive/assets/icons_mici/onroad/onroad_fade.png b/selfdrive/assets/icons_mici/onroad/onroad_fade.png index bc12e57e178..3f823061b9b 100644 --- a/selfdrive/assets/icons_mici/onroad/onroad_fade.png +++ b/selfdrive/assets/icons_mici/onroad/onroad_fade.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2a2cb4db429467783d7f721ffbed7838551e4aabf32771e73759c87b4a67bca -size 28880 +oid sha256:2aa6d04ba038f15a92868de6e6c7b04f624b4fe89d03bc3e9c4cd44cb729b24e +size 38317 diff --git a/selfdrive/assets/icons_mici/onroad/turn_signal_left.png b/selfdrive/assets/icons_mici/onroad/turn_signal_left.png index 48f52ff9cec..97b5cf1443f 100644 --- a/selfdrive/assets/icons_mici/onroad/turn_signal_left.png +++ b/selfdrive/assets/icons_mici/onroad/turn_signal_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e845a211cf5d03f781efdd6eec4f8106e8dd85799ea59b51834a9099b479141 -size 30348 +oid sha256:f9f7d0554c0c79ab605c1119ffdef0a4f55196e53b75a65b6ac5218911e24a02 +size 45701 diff --git a/selfdrive/assets/icons_mici/onroad/turn_signal_right.png b/selfdrive/assets/icons_mici/onroad/turn_signal_right.png index 87ca979fbe8..6bcb68dac55 100644 --- a/selfdrive/assets/icons_mici/onroad/turn_signal_right.png +++ b/selfdrive/assets/icons_mici/onroad/turn_signal_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:009005539f14acc29a4f5510b4e9531d2ba3667133644f6e0069c12b08ba0fd9 -size 35370 +oid sha256:7fae4872ab3c24d5e4c2be6150127a844f89bbdcadfccdff2dfed180e125d577 +size 45699 diff --git a/selfdrive/assets/icons_mici/settings.png b/selfdrive/assets/icons_mici/settings.png index e668ed1fe4d..4ba7df9fdf6 100644 --- a/selfdrive/assets/icons_mici/settings.png +++ b/selfdrive/assets/icons_mici/settings.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38a52171bdc6feb3ddfd2d9f9e59db3dabd09fa0aafbc9f81137c59bd03b7c26 -size 2321 +oid sha256:14b457d2dc19d8658f525cc6989c9cfcf0edaf695b18767514242acbdbe2a6dd +size 2198 diff --git a/selfdrive/assets/icons_mici/settings/comma_icon.png b/selfdrive/assets/icons_mici/settings/comma_icon.png index 72a7c8c8f95..dd38a8938f6 100644 --- a/selfdrive/assets/icons_mici/settings/comma_icon.png +++ b/selfdrive/assets/icons_mici/settings/comma_icon.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:10f469a6f5d25d9e2b0b1aae51b4fbd06d2c7b8417613bb321c2a30bb7298dab -size 1392 +oid sha256:7ad4ee47ec6470f788a026f95ed86bf344f64f9cf3186c9c78927233d2694a1d +size 1388 diff --git a/selfdrive/assets/icons_mici/settings/developer/ssh.png b/selfdrive/assets/icons_mici/settings/developer/ssh.png index cd86937aea5..0f17d04eca8 100644 --- a/selfdrive/assets/icons_mici/settings/developer/ssh.png +++ b/selfdrive/assets/icons_mici/settings/developer/ssh.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c655994336b7da4ca986c6f27494bcab66e77f016ec9db8df271de53ed93e517 -size 1328 +oid sha256:b26133bee089627202d5e89a4e939ad23aaceb5d8e26d7381b1aea3ef892f2ee +size 2620 diff --git a/selfdrive/assets/icons_mici/settings/developer_icon.png b/selfdrive/assets/icons_mici/settings/developer_icon.png index af16c029127..f9d553c7c30 100644 --- a/selfdrive/assets/icons_mici/settings/developer_icon.png +++ b/selfdrive/assets/icons_mici/settings/developer_icon.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1f058c5640bd763d2f6927432a1daff1587770ea0d06f2e351a28462e9d8335 -size 1743 +oid sha256:ebb4f7ad9fd2f9fb3c69a38fbc00cbe690809b0ff202ffd4768ae5b699acc035 +size 1759 diff --git a/selfdrive/assets/icons_mici/settings/device/cameras.png b/selfdrive/assets/icons_mici/settings/device/cameras.png index c44c5112754..ae9a88c4dc8 100644 --- a/selfdrive/assets/icons_mici/settings/device/cameras.png +++ b/selfdrive/assets/icons_mici/settings/device/cameras.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:77a1281979f0b50f0e109ead56a88a33b81ef5901dd1a4537eb3fa048e0d90de -size 1345 +oid sha256:5f47e636025e044977f278a35546e0fc971f48fd53c2eeafd3508e95c35f378f +size 3117 diff --git a/selfdrive/assets/icons_mici/settings/device/info.png b/selfdrive/assets/icons_mici/settings/device/info.png index cb163206935..9a29c46d0d2 100644 --- a/selfdrive/assets/icons_mici/settings/device/info.png +++ b/selfdrive/assets/icons_mici/settings/device/info.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2649d36259700d32a0edef878647e76492b1bec2fe34ac8ea806d4e7e4c57855 -size 2668 +oid sha256:66858a5d3302333485fa391f7a9bb3a9b1ab4ae881e7fb47b04c3a4507011c94 +size 2613 diff --git a/selfdrive/assets/icons_mici/settings/device/language.png b/selfdrive/assets/icons_mici/settings/device/language.png index f6d57b31347..d2ef27de36b 100644 --- a/selfdrive/assets/icons_mici/settings/device/language.png +++ b/selfdrive/assets/icons_mici/settings/device/language.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4b982ac1b78b45487490d1dbbffed1f68735f6a35def502e882f706c30683aff -size 3664 +oid sha256:f646263b26de46f79cac836ef6865b0f25ddc91e386b99311723b68bd06693c9 +size 3304 diff --git a/selfdrive/assets/icons_mici/settings/device/lkas.png b/selfdrive/assets/icons_mici/settings/device/lkas.png index 186ea78fb94..80d37d4d5c1 100644 --- a/selfdrive/assets/icons_mici/settings/device/lkas.png +++ b/selfdrive/assets/icons_mici/settings/device/lkas.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ab6aeb6cba94acf948a0ad64a485db00bf1f3de1360ae4c57212f3f083b2bd24 -size 2554 +oid sha256:a05a41e66c7a24d461a4bbcdab0979031e5900e1db270af52ca363f0bed521f5 +size 2028 diff --git a/selfdrive/assets/icons_mici/settings/device/pair.png b/selfdrive/assets/icons_mici/settings/device/pair.png index f072b2363f4..807d44335dc 100644 --- a/selfdrive/assets/icons_mici/settings/device/pair.png +++ b/selfdrive/assets/icons_mici/settings/device/pair.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ed671f4ad1523f0e66498af39e6075a0c19842ae05eddd00871a6e48ed3685d7 -size 1594 +oid sha256:678483230831d0a7d3dcad5f067a7b641e5d2ae0db477665dfc6c53a675eba18 +size 1779 diff --git a/selfdrive/assets/icons_mici/settings/device/power.png b/selfdrive/assets/icons_mici/settings/device/power.png index a2de14a4e86..711f1a4ab9b 100644 --- a/selfdrive/assets/icons_mici/settings/device/power.png +++ b/selfdrive/assets/icons_mici/settings/device/power.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b45645ad9ff27776fdb1caa27827c526cae57f8bd4e23bd1160cb0094121ff2 -size 2338 +oid sha256:a34885e79f42d19b7777dd07e7ab51df344880cb770c48e0baaddb177c2ae938 +size 2228 diff --git a/selfdrive/assets/icons_mici/settings/device/reboot.png b/selfdrive/assets/icons_mici/settings/device/reboot.png index 6c89cd9fc23..298a85c5041 100644 --- a/selfdrive/assets/icons_mici/settings/device/reboot.png +++ b/selfdrive/assets/icons_mici/settings/device/reboot.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f24039f82d7399d02a155022de65b6dc3b8edcf17059a73a9fd3a9209e3f5575 -size 2360 +oid sha256:1356fe3ddda14568e9be1dca4e16ca9048852e3a27a3f531cd58d7d368485a82 +size 2362 diff --git a/selfdrive/assets/icons_mici/settings/device/uninstall.png b/selfdrive/assets/icons_mici/settings/device/uninstall.png index f9173711ebd..53f8bc0e7d3 100644 --- a/selfdrive/assets/icons_mici/settings/device/uninstall.png +++ b/selfdrive/assets/icons_mici/settings/device/uninstall.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:558ea538fb258079f9eb05fe048b2806c7635b9f0452af874b00cb8d79b45f9b -size 2421 +oid sha256:50a8ce4fa8ff7f5b0f56ba0dc65b4802dc0be2dc0967b5cb3a15e3b79a4e513e +size 2424 diff --git a/selfdrive/assets/icons_mici/settings/device/up_to_date.png b/selfdrive/assets/icons_mici/settings/device/up_to_date.png index ee925458d32..e09f7d33085 100644 --- a/selfdrive/assets/icons_mici/settings/device/up_to_date.png +++ b/selfdrive/assets/icons_mici/settings/device/up_to_date.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4510e65775c6001758ebcf4dc13e9fa561cce5159d1fd54fbb506f22d3c7bdf3 -size 3149 +oid sha256:61bc44b6e0f99640434d6abcb64880c7bf575eda5cdcf7d74cba7d73307dd39a +size 2739 diff --git a/selfdrive/assets/icons_mici/settings/device/update.png b/selfdrive/assets/icons_mici/settings/device/update.png index cc05931b035..498c066191a 100644 --- a/selfdrive/assets/icons_mici/settings/device/update.png +++ b/selfdrive/assets/icons_mici/settings/device/update.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c6137349218ea22adba44f46a096afe2efc35536b2251192ed0ea61be443a3c5 -size 2493 +oid sha256:f28cdeaba9146521335bc11ad60a8e0368eb0ed1381e88b35a12a6138ba22ed6 +size 2409 diff --git a/selfdrive/assets/icons_mici/settings/device_icon.png b/selfdrive/assets/icons_mici/settings/device_icon.png index 0caf0d07ce3..6a716e4dfde 100644 --- a/selfdrive/assets/icons_mici/settings/device_icon.png +++ b/selfdrive/assets/icons_mici/settings/device_icon.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:db20bea98259b204be634ce0d9a23fbfdcfc73a324fc0aac0f9ac54e1c51556d -size 2443 +oid sha256:2273629450aa870f0964dd285721c35d3d313fb8b4684122215a65844ae744d0 +size 1888 diff --git a/selfdrive/assets/icons_mici/settings/firehose.png b/selfdrive/assets/icons_mici/settings/firehose.png new file mode 100644 index 00000000000..37451c0482c --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/firehose.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:416656861380981acc114e5285b448d6e4dc42b98539d0ba16821cbc3db89208 +size 1364 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/backspace.png b/selfdrive/assets/icons_mici/settings/keyboard/backspace.png index 342f8e28daa..53ff00c2ae7 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/backspace.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/backspace.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:116bbbd1509e6644f7b65b8dacd2402b0918785bd80207504a99ab7e13ab738f -size 2049 +oid sha256:69bb4a401429c3fdf473778f751288b2aafea27eb13f09b20e83d55212f084ba +size 1963 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png b/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png index d63cc56fbc4..2d173bfc9fa 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/caps_lock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e8c7fec57640de6bfa8d0ede977e40920a8e651b68ed14e3d6c1850e702f3e3 -size 1399 +oid sha256:563c211fd98018e24418235602e596f3a481f04fddde0a14590e563474fcffd2 +size 1423 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png b/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png index eb38934302f..a3ce71f0492 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/caps_lower.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7dab3af28938e9c3ad7b6c3b60526bb76498b0103c7276d90c4bff3622f07d0 -size 1157 +oid sha256:6f81811ea9cdc409d5549035ca928c76e22396193e1cefb6cacab3747ee0c297 +size 1142 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png b/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png index 4a2cae6c8a1..7c147bc07bb 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/caps_upper.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0c5a88a0e8e810115b6d497d3e230d866bd96a715ddac632f48c78b40e1df702 -size 1059 +oid sha256:60875e73dd9659122c9248d8e99d5cfd301d68dabeec2cb42cebce812c9baae9 +size 1102 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/confirm.png b/selfdrive/assets/icons_mici/settings/keyboard/confirm.png index 09b180e97fd..98ca5c61dc8 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/confirm.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/confirm.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:32ce109a9fe4814bb9bed88f67d85292791f4a6d7c162e07561920221ac38b2d -size 1411 +oid sha256:43b64365a42d7bf772d567b8867a6ced4ec0175bb88b6acaa3a5345f19ca696e +size 1268 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/space.png b/selfdrive/assets/icons_mici/settings/keyboard/space.png index 778d1847d77..3d61109721b 100644 --- a/selfdrive/assets/icons_mici/settings/keyboard/space.png +++ b/selfdrive/assets/icons_mici/settings/keyboard/space.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b04d17f3b0340a94210efa5c9547e0ac340dd6b6dd9ac1f81ba5eb3f89f405d -size 619 +oid sha256:f431e428772991323ee3ce662479e1ab29c3d80a72b93cf9c9673716ba245d5f +size 654 diff --git a/selfdrive/assets/icons_mici/settings/manual_icon.png b/selfdrive/assets/icons_mici/settings/manual_icon.png deleted file mode 100644 index 100b29da457..00000000000 --- a/selfdrive/assets/icons_mici/settings/manual_icon.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:957330e9fbc8c03f05dbef8097178a40efc0fc52a6faf7a9917f97046d9a5e99 -size 1559 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png index 4bf0cd87268..13f70386d44 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_full.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a981d5c5558859b283cb6321c84eec947f82fc2dea8dbdd19b66781e4d3f61f -size 1060 +oid sha256:fb7af523411c5ed75c6e1418dfc2a379486f6dbd7f2f1c281d3ff54e1ea7810e +size 777 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png index df6d0093356..1fea6d23b80 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_high.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58da16ede432cf89096c11dc0f4ea098735863fb09a1d655cb06de8a112bd263 -size 1205 +oid sha256:db86e176e016458fcff00d40e37636a808977e0cc01bcc9c04b31a1001562de8 +size 936 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png index c3323a9fea9..d763f86c7fa 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_low.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:031bbd50c34d8fd5e71bdc292ba3e50b28a13c56a48dc84117723f1b35b42f51 -size 1224 +oid sha256:1cd0b3a00db36ee7eacf5887d07d40e5351fb441d98643a02df4c742cd1e935d +size 945 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png index 64ab947c539..148ee63e990 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_medium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ccb5f2227c72dd28e40c9f19965abe007cbd7b47cdca924907dc9fad906f5c81 -size 1219 +oid sha256:25724acfe0c261070b103ef5933053d5dd8b726ece42d0e5f715f05c67be2294 +size 956 diff --git a/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png b/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png index 6cdef706bd1..c6d82ac316e 100644 --- a/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png +++ b/selfdrive/assets/icons_mici/settings/network/cell_strength_none.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:92c195721fe2b4ca42176077bf4ca3484cdfc314e961f1431b2296476bcae891 -size 1178 +oid sha256:cb0aeb6260bcd0642204f842112479f4b19b350db9addae5e14c9c5131bcf956 +size 781 diff --git a/selfdrive/assets/icons_mici/settings/network/new/lock.png b/selfdrive/assets/icons_mici/settings/network/new/lock.png index 0a0b18c7a98..9fc152d3dbc 100644 --- a/selfdrive/assets/icons_mici/settings/network/new/lock.png +++ b/selfdrive/assets/icons_mici/settings/network/new/lock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:40dbbb3000e1137ec11fe658fbfebae7cadfc91356953317335f9bb70fcb40d3 -size 1235 +oid sha256:782161f35b4925c7063c441b0c341331c814614cf241f21b4e70134280c630f0 +size 1182 diff --git a/selfdrive/assets/icons_mici/settings/network/new/trash.png b/selfdrive/assets/icons_mici/settings/network/new/trash.png index 99e1a2e2464..81e5f13e43a 100644 --- a/selfdrive/assets/icons_mici/settings/network/new/trash.png +++ b/selfdrive/assets/icons_mici/settings/network/new/trash.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:efabf98ed66fe4447c0f13c74aec681b084de780c551ce18258c79636d4123c5 -size 1524 +oid sha256:9074162bf0469fc5ab0b5711a121289a983c887161df269ac120edd8fd024499 +size 1533 diff --git a/selfdrive/assets/icons_mici/settings/network/tethering.png b/selfdrive/assets/icons_mici/settings/network/tethering.png index 9e7b90be41c..4bb416b0b10 100644 --- a/selfdrive/assets/icons_mici/settings/network/tethering.png +++ b/selfdrive/assets/icons_mici/settings/network/tethering.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2907ce46d1b6e676402f390c530955b65e76baf0b77fafc0616c50b988b3994c -size 1609 +oid sha256:b1e322ea6e57b05b3515fcd4e9100f890e6ff80607c11360b7927fa5a9765beb +size 2752 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png index 1a1655fddca..fe81ffa5720 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_full.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2715ea698eccb3648ab96cbddf897ea1842acbc1eb9667bc6f34aba82d0896b -size 1976 +oid sha256:73c76e5240bdff64c1d1ed0ac2bb9c3fadb2fd61fbf8dc710b812757af8bcf6c +size 2026 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png index 4d64d8062f5..2649cc89dce 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_low.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58d839402c6f002ba8d2217888190b338fc3ac13d372df0988fac7bf95b89302 -size 2111 +oid sha256:e66cc6174a54177793c42ef3525a9aa1592e05b0abb677442c7226269d1371a5 +size 2196 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png index 2d53a20cef9..88818333753 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_medium.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a9918724409dbfa1973a097a692c2f57e45cc2bc0ce71c498ef3e02aa82559d3 -size 2128 +oid sha256:7948a9234f2bc996aefb3a9e58a37c06ebbf54e8e4596e47800f78ef7e81961f +size 2231 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png index 482a0e10426..848d7849a23 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_none.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3fcef95eb18e2db566b907ae99b8d8f450424b3b7823fdc24cdfe066ccf64378 -size 2141 +oid sha256:a57ea402448dacc2026631174e448b6254698fe92309221576400cbf28196936 +size 2195 diff --git a/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png b/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png index 38ddff84b70..4457a3fcd27 100644 --- a/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png +++ b/selfdrive/assets/icons_mici/settings/network/wifi_strength_slash.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73e4ae4741a039f41d79827c40be6da83f8c6eb79e9103db2dfec718ca96efb7 -size 2512 +oid sha256:7e6d166bdbbcdc106e7cd4a44ba85848888f18a6ef34e86daac8e12a3f519443 +size 2318 diff --git a/selfdrive/assets/icons_mici/settings/toggles_icon.png b/selfdrive/assets/icons_mici/settings/toggles_icon.png deleted file mode 100644 index ccb343e8ede..00000000000 --- a/selfdrive/assets/icons_mici/settings/toggles_icon.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0297535eb73bea71e87c363dc12385bb9163b81403797e50966b20259f725542 -size 2528 diff --git a/selfdrive/assets/icons_mici/setup/back_new.png b/selfdrive/assets/icons_mici/setup/back_new.png index c4834a56490..20e7fe3b88b 100644 --- a/selfdrive/assets/icons_mici/setup/back_new.png +++ b/selfdrive/assets/icons_mici/setup/back_new.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7198352d23952d0f2fbc128f20523ea6f2f2b7e378aa495da748a0e34f192806 -size 1641 +oid sha256:d29a9c295b33b3164c37a68ad77795595e6ac877a5b308d28112b0315ecd498f +size 1687 diff --git a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png index 92993e3e007..dfb9799b0b8 100644 --- a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png +++ b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_check.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5b7dce550c008ff7a65ed19ccf308ecf92cd0118bb544978b7dd7393c5c27ae5 -size 809 +oid sha256:2290105f9b055b3c3d482d883d148de3418cad07b653133b0f61137e1976c407 +size 1412 diff --git a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png index 53a837afbe3..fa29be1827f 100644 --- a/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png +++ b/selfdrive/assets/icons_mici/setup/driver_monitoring/dm_question.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e102b8b2e71a25d9f818b37d6f75ed958430cb765a07ae50713995779fb6a886 -size 1388 +oid sha256:ec9691d2572e2e084f0b3c99a1dcd0daadf5040d16c02347ffec9dd5466c061a +size 1438 diff --git a/selfdrive/assets/icons_mici/setup/green_car.png b/selfdrive/assets/icons_mici/setup/green_car.png deleted file mode 100644 index 867cadbbd61..00000000000 --- a/selfdrive/assets/icons_mici/setup/green_car.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ce8a34777e0b185f457b98845aa17fe6b5192ca46101463aecd21a9e04c0f0f0 -size 13281 diff --git a/selfdrive/assets/icons_mici/setup/green_dm.png b/selfdrive/assets/icons_mici/setup/green_dm.png index d41edd4c2a1..87f4ffe7885 100644 --- a/selfdrive/assets/icons_mici/setup/green_dm.png +++ b/selfdrive/assets/icons_mici/setup/green_dm.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78795eaa5e0be5fa369e172c02f5bd4b06d20f44363ccb8cbd02cb181b13e529 -size 14289 +oid sha256:8b6d7747dd6bbf47d9782fc0d847c224b933f6616218ade1f9220018aa9d6acc +size 15052 diff --git a/selfdrive/assets/icons_mici/setup/green_info.png b/selfdrive/assets/icons_mici/setup/green_info.png index 309e56e6eec..57e005abd67 100644 --- a/selfdrive/assets/icons_mici/setup/green_info.png +++ b/selfdrive/assets/icons_mici/setup/green_info.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2b0b1777d5bed7149982af9f2abab3fab7b6c576e3d53cf2c459804c6ec9ca1e -size 3957 +oid sha256:5055bc385a1de674e6f3cbafdb611ee4b1088de2a3c357bce76f6a192226c952 +size 14154 diff --git a/selfdrive/assets/icons_mici/setup/green_pedal.png b/selfdrive/assets/icons_mici/setup/green_pedal.png deleted file mode 100644 index 2dd18f489aa..00000000000 --- a/selfdrive/assets/icons_mici/setup/green_pedal.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6cadcda59bc861a1e710e0a8ac67024bdcc44b5f9261abbf098ff11cefb1da51 -size 12209 diff --git a/selfdrive/assets/icons_mici/setup/orange_dm.png b/selfdrive/assets/icons_mici/setup/orange_dm.png index 74cce9d975c..97df767a987 100644 --- a/selfdrive/assets/icons_mici/setup/orange_dm.png +++ b/selfdrive/assets/icons_mici/setup/orange_dm.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38a108f96f85a154b698693b07f2e4214124b8f2545b7c4490cea0aa998d75fd -size 11855 +oid sha256:9c45ab0b949c1c71651f9f48cf6ff10196d64eb85e042b063e92b1d7ca02dcb5 +size 13155 diff --git a/selfdrive/assets/icons_mici/setup/red_warning.png b/selfdrive/assets/icons_mici/setup/red_warning.png index ed0634079b7..387794cf13a 100644 --- a/selfdrive/assets/icons_mici/setup/red_warning.png +++ b/selfdrive/assets/icons_mici/setup/red_warning.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:448d3e7214a77b02b32020ddb440ccd8fe72e110493a51cc10901c8242e72ca8 -size 3185 +oid sha256:e8e8bc3c15df7512a81b902e47fb069eff1370c833095d3b25f3866efb815fff +size 11123 diff --git a/selfdrive/assets/icons_mici/setup/restore.png b/selfdrive/assets/icons_mici/setup/restore.png index 6aa6c6b851d..5eff9240406 100644 --- a/selfdrive/assets/icons_mici/setup/restore.png +++ b/selfdrive/assets/icons_mici/setup/restore.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9d6b99696163cac1867d46998af9e53e212b82641b33c93b51276671f400a5ac -size 2962 +oid sha256:1f5ee67cd334d259ac33f932281db36533877009b5769c92d9cff3054fd5627c +size 2942 diff --git a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png b/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png index 4d74d860750..3cd26e51810 100644 --- a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png +++ b/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:52535e34e27b0341f7690a72dc16555eeb6e032bc2c2cde0786469852fdf5987 -size 1267 +oid sha256:a733c425113a7f6ff5ec3dc50ef94b5481c0f2d306e33d1485be8ee6b2798532 +size 1136 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png index bbf1d962541..acf5b174147 100644 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_arrow.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8425c56cb413ba757c94febe0332ce472dbf1472236b03cc4e627746fb86d701 -size 1149 +oid sha256:75a6557935075a646b17d083202832daafb263d4cfa38aea2af407afc04e2ef4 +size 1312 diff --git a/selfdrive/assets/icons_mici/setup/warning.png b/selfdrive/assets/icons_mici/setup/warning.png index 806eea28b77..1b7839f47f6 100644 --- a/selfdrive/assets/icons_mici/setup/warning.png +++ b/selfdrive/assets/icons_mici/setup/warning.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3bc7a85a0672183d80817f337084060465e143362037955025c11bc8ac531076 -size 3247 +oid sha256:7584d32ac0231381e38646fdac2f71b4517905ef22024f01bd9e124d3918f33a +size 9194 diff --git a/selfdrive/assets/icons_mici/ssh_short.png b/selfdrive/assets/icons_mici/ssh_short.png new file mode 100644 index 00000000000..699ddd72e8f --- /dev/null +++ b/selfdrive/assets/icons_mici/ssh_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef1735e6effcb625ea618fa35a6b908b28ca483d5997e15241d48e2d3d29819e +size 1433 diff --git a/selfdrive/assets/icons_mici/tethering_short.png b/selfdrive/assets/icons_mici/tethering_short.png new file mode 100644 index 00000000000..f97fed95ded --- /dev/null +++ b/selfdrive/assets/icons_mici/tethering_short.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fce940a3cbd2e9530e8efdde90794013a272919b2f3ea482bc06535c795640e7 +size 2176 diff --git a/selfdrive/assets/icons_mici/turn_intent_left.png b/selfdrive/assets/icons_mici/turn_intent_left.png index 6c2c47e8824..3934200c9d9 100644 --- a/selfdrive/assets/icons_mici/turn_intent_left.png +++ b/selfdrive/assets/icons_mici/turn_intent_left.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ead8287b7041c32456e13721c238a71933256ca3d2b7e649c8f8731585eb5de8 -size 906 +oid sha256:001cb8227eaaff5367055395d9b3ccd5822f9a47276091832d8ad28b074d77c9 +size 914 diff --git a/selfdrive/assets/icons_mici/turn_intent_right.png b/selfdrive/assets/icons_mici/turn_intent_right.png index 03a7245e76c..e342778731d 100644 --- a/selfdrive/assets/icons_mici/turn_intent_right.png +++ b/selfdrive/assets/icons_mici/turn_intent_right.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6fe0532f7040aae78baa85c4cca44f5c939adb6a6f15889e2ca036f4a493f848 -size 935 +oid sha256:7b7e0194a8b9009e493cdce35cd15711596a54227c740e9d6419a3891c6c4037 +size 912 diff --git a/selfdrive/assets/icons_mici/wheel.png b/selfdrive/assets/icons_mici/wheel.png index f122349b82b..a43bcb3b993 100644 --- a/selfdrive/assets/icons_mici/wheel.png +++ b/selfdrive/assets/icons_mici/wheel.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc3ef0c8c3038d75f99df2c565a361107bc903944d1afe91de0cbed9f6ca062a -size 2725 +oid sha256:8cf9c6361ed82551eb99e028e0a75ff56b72ca856ccf7c9a76afe6745434980a +size 2720 diff --git a/selfdrive/assets/icons_mici/wheel_critical.png b/selfdrive/assets/icons_mici/wheel_critical.png index c0e5e8619e7..676b0b4d710 100644 --- a/selfdrive/assets/icons_mici/wheel_critical.png +++ b/selfdrive/assets/icons_mici/wheel_critical.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:12783dc05ea6dae2647ac3a3a7c8391d520c3f0cf2f458333a357ee9633eb6c4 -size 10909 +oid sha256:4c3d9082b295f9e5ddef93f8d4e9cb961ea2374c7affd26394bbccb26e7137b2 +size 11023 diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 9152bdc7fa7..f5dab7249a4 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -92,22 +92,22 @@ def __init__(self): self._settings_txt = gui_app.texture("icons_mici/settings.png", 48, 48) self._experimental_txt = gui_app.texture("icons_mici/experimental_mode.png", 48, 48) - self._mic_txt = gui_app.texture("icons_mici/microphone.png", 48, 48) + self._mic_txt = gui_app.texture("icons_mici/microphone.png", 32, 46) self._net_type = NETWORK_TYPES.get(NetworkType.none) self._net_strength = 0 self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 50, 44) - self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 50, 44) - self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 50, 44) - self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 50, 44) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 50, 44) - - self._cell_none_txt = gui_app.texture("icons_mici/settings/network/cell_strength_none.png", 55, 35) - self._cell_low_txt = gui_app.texture("icons_mici/settings/network/cell_strength_low.png", 55, 35) - self._cell_medium_txt = gui_app.texture("icons_mici/settings/network/cell_strength_medium.png", 55, 35) - self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 55, 35) - self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 55, 35) + self._wifi_none_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_none.png", 50, 37) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 50, 37) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 50, 37) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 50, 37) + + self._cell_none_txt = gui_app.texture("icons_mici/settings/network/cell_strength_none.png", 54, 36) + self._cell_low_txt = gui_app.texture("icons_mici/settings/network/cell_strength_low.png", 54, 36) + self._cell_medium_txt = gui_app.texture("icons_mici/settings/network/cell_strength_medium.png", 54, 36) + self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 54, 36) + self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 54, 36) self._openpilot_label = MiciLabel("openpilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 16e96d6f7df..4248fef2ecc 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -124,9 +124,9 @@ class TrainingGuideDMTutorial(Widget): def __init__(self, continue_callback): super().__init__() - self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 48, 48)) + self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 28, 48)) self._back_button.set_click_callback(self._show_bad_face_page) - self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 48, 35)) + self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 42, 42)) # Wrap the continue callback to restore settings def wrapped_continue_callback(): diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 8fc63e89637..b6145e042eb 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -3,7 +3,7 @@ from openpilot.common.time_helpers import system_time_valid from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl, BigCircleParamControl from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import NavWidget @@ -36,15 +36,15 @@ def ssh_keys_callback(): return gui_app.set_modal_overlay(dlg) - txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 77, 44) + txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 56, 64) github_username = ui_state.params.get("GithubUsername") or "" self._ssh_keys_btn = BigButton("SSH keys", "Not set" if not github_username else github_username, icon=txt_ssh) self._ssh_keys_btn.set_click_callback(ssh_keys_callback) # adb, ssh, ssh keys, debug mode, joystick debug mode, longitudinal maneuver mode, ip address # ******** Main Scroller ******** - self._adb_toggle = BigParamControl("enable ADB", "AdbEnabled") - self._ssh_toggle = BigParamControl("enable SSH", "SshEnabled") + self._adb_toggle = BigCircleParamControl("icons_mici/adb_short.png", "AdbEnabled", icon_size=(82, 82), icon_offset=(0, 12)) + self._ssh_toggle = BigCircleParamControl("icons_mici/ssh_short.png", "SshEnabled", icon_size=(82, 82), icon_offset=(0, 12)) self._joystick_toggle = BigToggle("joystick debug mode", initial_state=ui_state.params.get_bool("JoystickDebugMode"), toggle_callback=self._on_joystick_debug_mode) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 988c823a994..30ea90f3d17 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -119,7 +119,7 @@ class UpdaterState(IntEnum): class PairBigButton(BigButton): def __init__(self): - super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png") + super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png", icon_size=(33, 60)) def _update_state(self): if ui_state.prime_state.is_paired(): @@ -153,8 +153,8 @@ def _handle_mouse_release(self, mouse_pos: MousePos): class UpdateOpenpilotBigButton(BigButton): def __init__(self): - self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 64) - self._txt_reboot_icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 64) + self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 75) + self._txt_reboot_icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70) self._txt_up_to_date_icon = gui_app.texture("icons_mici/settings/device/up_to_date.png", 64, 64) super().__init__("update openpilot", "", self._txt_update_icon) @@ -291,16 +291,16 @@ def reset_calibration_callback(): def uninstall_openpilot_callback(): ui_state.params.put_bool("DoUninstall", True) - reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png") + reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png", icon_size=(114, 60)) reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset")) uninstall_openpilot_btn = BigButton("uninstall openpilot", "", "icons_mici/settings/device/uninstall.png") uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall")) - reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False) + reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) reboot_btn.set_click_callback(lambda: _engaged_confirmation_callback(reboot_callback, "reboot")) - self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True) + self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True, icon_size=(64, 66)) self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) self._load_languages() diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 1faf49311a7..0d5e5278368 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -4,7 +4,7 @@ from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigToggle, BigParamControl +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigCircleToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType @@ -33,15 +33,14 @@ def __init__(self, back_callback: Callable): networks_updated=self._on_network_updated, ) - _tethering_icon = "icons_mici/settings/network/tethering.png" - # ******** Tethering ******** def tethering_toggle_callback(checked: bool): self._tethering_toggle_btn.set_enabled(False) self._network_metered_btn.set_enabled(False) self._wifi_manager.set_tethering_active(checked) - self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) + self._tethering_toggle_btn = BigCircleToggle("icons_mici/tethering_short.png", toggle_callback=tethering_toggle_callback, + icon_size=(82, 82), icon_offset=(0, 12)) def tethering_password_callback(password: str): if password: @@ -53,7 +52,7 @@ def tethering_password_clicked(): confirm_callback=tethering_password_callback) gui_app.set_modal_overlay(dlg) - txt_tethering = gui_app.texture(_tethering_icon, 64, 53) + txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) self._tethering_password_btn.set_click_callback(tethering_password_clicked) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 565fef5af3f..23b89438dca 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -34,12 +34,12 @@ def _render(self, _): class WifiIcon(Widget): def __init__(self): super().__init__() - self.set_rect(rl.Rectangle(0, 0, 89, 64)) + self.set_rect(rl.Rectangle(0, 0, 86, 64)) - self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 89, 64) - self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 89, 64) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 89, 64) - self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 23, 32) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 86, 64) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 86, 64) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 86, 64) + self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 22, 32) self._network: Network | None = None self._scale = 1.0 @@ -169,7 +169,7 @@ def __init__(self, forget_network: Callable, open_network_manage_page): self._bg_txt = gui_app.texture("icons_mici/settings/network/new/forget_button.png", 100, 100) self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/forget_button_pressed.png", 100, 100) - self._trash_txt = gui_app.texture("icons_mici/settings/network/new/trash.png", 32, 36) + self._trash_txt = gui_app.texture("icons_mici/settings/network/new/trash.png", 35, 42) self.set_rect(rl.Rectangle(0, 0, 100 + self.HORIZONTAL_MARGIN * 2, 100)) def _handle_mouse_release(self, mouse_pos: MousePos): diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index a452777748e..3917899032e 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -36,16 +36,16 @@ def __init__(self): self._params = Params() self._current_panel = None # PanelType.DEVICE - toggles_btn = BigButton("toggles", "", "icons_mici/settings/toggles_icon.png") + toggles_btn = BigButton("toggles", "", "icons_mici/settings.png") toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES)) - network_btn = BigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png") + network_btn = BigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK)) - device_btn = BigButton("device", "", "icons_mici/settings/device_icon.png") + device_btn = BigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) device_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVICE)) - developer_btn = BigButton("developer", "", "icons_mici/settings/developer_icon.png") + developer_btn = BigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) developer_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVELOPER)) - firehose_btn = BigButton("firehose", "", "icons_mici/settings/comma_icon.png") + firehose_btn = BigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) firehose_btn.set_click_callback(lambda: self._set_current_panel(PanelType.FIREHOSE)) self._scroller = Scroller([ diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index 7ee83ff8806..64dd04c3103 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -111,10 +111,10 @@ def __init__(self): self._load_icons() def _load_icons(self): - self._txt_turn_signal_left = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 100, 91) - self._txt_turn_signal_right = gui_app.texture('icons_mici/onroad/turn_signal_right.png', 100, 91) - self._txt_blind_spot_left = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 108, 128) - self._txt_blind_spot_right = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 108, 128) + self._txt_turn_signal_left = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 104, 96) + self._txt_turn_signal_right = gui_app.texture('icons_mici/onroad/turn_signal_right.png', 104, 96) + self._txt_blind_spot_left = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 134, 150) + self._txt_blind_spot_right = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 134, 150) def get_alert(self, sm: messaging.SubMaster) -> Alert | None: """Generate the current alert based on selfdrive state.""" diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 7f489ccf981..a6fa1a62bba 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -49,8 +49,8 @@ def __init__(self): self._turn_intent_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) self._turn_intent_rotation_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) - self._txt_turn_intent_left: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 19) - self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_right.png', 50, 19) + self._txt_turn_intent_left: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 20) + self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_right.png', 50, 20) def _render(self, _): if self._turn_intent_alpha_filter.x > 1e-2: diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 9678827a91e..0b252c21aab 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -29,9 +29,10 @@ class ScrollState(Enum): class BigCircleButton(Widget): - def __init__(self, icon: str, red: bool = False): + def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): super().__init__() self._red = red + self._icon_offset = icon_offset # State self.set_rect(rl.Rectangle(0, 0, 180, 180)) @@ -39,7 +40,7 @@ def __init__(self, icon: str, red: bool = False): self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) # Icons - self._txt_icon = gui_app.texture(icon, 64, 53) + self._txt_icon = gui_app.texture(icon, *icon_size) self._txt_btn_disabled_bg = gui_app.texture("icons_mici/buttons/button_circle_disabled.png", 180, 180) self._txt_btn_bg = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) @@ -66,13 +67,13 @@ def _render(self, _): # draw icon icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) - rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2), - int(self._rect.y + (self._rect.height - self._txt_icon.height) / 2), icon_color) + rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0]), + int(self._rect.y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), icon_color) class BigCircleToggle(BigCircleButton): - def __init__(self, icon: str, toggle_callback: Callable | None = None): - super().__init__(icon, False) + def __init__(self, icon: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, False, icon_size=icon_size, icon_offset=icon_offset) self._toggle_callback = toggle_callback # State @@ -80,7 +81,7 @@ def __init__(self, icon: str, toggle_callback: Callable | None = None): # Icons self._txt_toggle_enabled = gui_app.texture("icons_mici/buttons/toggle_dot_enabled.png", 66, 66) - self._txt_toggle_disabled = gui_app.texture("icons_mici/buttons/toggle_dot_disabled.png", 70, 70) # TODO: why discrepancy? + self._txt_toggle_disabled = gui_app.texture("icons_mici/buttons/toggle_dot_disabled.png", 66, 66) def set_checked(self, checked: bool): self._checked = checked @@ -104,11 +105,12 @@ def _render(self, _): class BigButton(Widget): """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" - def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = ""): + def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64)): super().__init__() self.set_rect(rl.Rectangle(0, 0, 402, 180)) self.text = text self.value = value + self._icon_size = icon_size self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) @@ -134,7 +136,7 @@ def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "" self._scroll_state = ScrollState.PRE_SCROLL def set_icon(self, icon: Union[str, rl.Texture]): - self._txt_icon = gui_app.texture(icon, 64, 64) if isinstance(icon, str) and len(icon) else icon + self._txt_icon = gui_app.texture(icon, *self._icon_size) if isinstance(icon, str) and len(icon) else icon def set_rotate_icon(self, rotate: bool): if rotate and self._rotate_icon_t is not None: @@ -361,8 +363,9 @@ def refresh(self): # TODO: param control base class class BigCircleParamControl(BigCircleToggle): - def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None): - super().__init__(icon, toggle_callback) + def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), + icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, toggle_callback, icon_size=icon_size, icon_offset=icon_offset) self._param = param self.params = Params() self.set_checked(self.params.get_bool(self._param, False)) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index abd558aa8db..67123d33a79 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -147,10 +147,10 @@ def __init__(self, self._backspace_held_time: float | None = None - self._backspace_img = gui_app.texture("icons_mici/settings/keyboard/backspace.png", 44, 44) + self._backspace_img = gui_app.texture("icons_mici/settings/keyboard/backspace.png", 42, 36) self._backspace_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) - self._enter_img = gui_app.texture("icons_mici/settings/keyboard/confirm.png", 44, 44) + self._enter_img = gui_app.texture("icons_mici/settings/keyboard/confirm.png", 42, 36) self._enter_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) # rects for top buttons diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index e064205d599..88bab2d0011 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -24,7 +24,7 @@ def __init__(self): self._qr_texture: rl.Texture | None = None self._last_qr_generation = float("-inf") - self._txt_pair = gui_app.texture("icons_mici/settings/device/pair.png", 84, 64) + self._txt_pair = gui_app.texture("icons_mici/settings/device/pair.png", 33, 60) self._pair_label = MiciLabel("pair with comma connect", 48, font_weight=FontWeight.BOLD, color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=40, wrap_text=True) diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 7459dc57317..a81cf853071 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -105,13 +105,15 @@ def set_font_size(self, size: float): class IconKey(Key): - def __init__(self, icon: str, vertical_align: str = "center", char: str = ""): + def __init__(self, icon: str, vertical_align: str = "center", char: str = "", icon_size: tuple[int, int] = (38, 38)): super().__init__(char) - self._icon = gui_app.texture(icon, 38, 38) + self._icon_size = icon_size + self._icon = gui_app.texture(icon, *icon_size) self._vertical_align = vertical_align - def set_icon(self, icon: str): - self._icon = gui_app.texture(icon, 38, 38) + def set_icon(self, icon: str, icon_size: tuple[int, int] | None = None): + size = icon_size if icon_size is not None else self._icon_size + self._icon = gui_app.texture(icon, *size) def _render(self, _): scale = np.interp(self._size_filter.x, [CHAR_FONT_SIZE, CHAR_NEAR_FONT_SIZE], [1, 1.5]) @@ -167,8 +169,8 @@ def __init__(self): self._super_special_keys = [[Key(char) for char in row] for row in super_special_chars] # control keys - self._space_key = IconKey("icons_mici/settings/keyboard/space.png", char=" ", vertical_align="bottom") - self._caps_key = IconKey("icons_mici/settings/keyboard/caps_lower.png") + self._space_key = IconKey("icons_mici/settings/keyboard/space.png", char=" ", vertical_align="bottom", icon_size=(43, 14)) + self._caps_key = IconKey("icons_mici/settings/keyboard/caps_lower.png", icon_size=(38, 33)) # these two are in different places on some layouts self._123_key, self._123_key2 = SmallKey("123"), SmallKey("123") self._abc_key = SmallKey("abc") @@ -269,14 +271,14 @@ def _set_uppercase(self, cycle: bool): self._set_keys(self._upper_keys if cycle else self._lower_keys) if not cycle: self._caps_state = CapsState.LOWER - self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lower.png") + self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lower.png", icon_size=(38, 33)) else: if self._caps_state == CapsState.LOWER: self._caps_state = CapsState.UPPER - self._caps_key.set_icon("icons_mici/settings/keyboard/caps_upper.png") + self._caps_key.set_icon("icons_mici/settings/keyboard/caps_upper.png", icon_size=(38, 33)) elif self._caps_state == CapsState.UPPER: self._caps_state = CapsState.LOCK - self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lock.png") + self._caps_key.set_icon("icons_mici/settings/keyboard/caps_lock.png", icon_size=(39, 38)) else: self._set_uppercase(False) From 2fc10e82998373a9bfe88f1de3d8179393f7567c Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:15:39 -0800 Subject: [PATCH 076/685] Maneuver: log drel and use it in tuning report (#37033) --- .../test/longitudinal_maneuvers/maneuver.py | 3 ++- .../mpc_longitudinal_tuning_report.py | 17 ++++++++++------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/selfdrive/test/longitudinal_maneuvers/maneuver.py b/selfdrive/test/longitudinal_maneuvers/maneuver.py index dfd5b3e109b..ba0379f2d72 100644 --- a/selfdrive/test/longitudinal_maneuvers/maneuver.py +++ b/selfdrive/test/longitudinal_maneuvers/maneuver.py @@ -60,7 +60,8 @@ def evaluate(self): log['distance_lead'], log['speed'], speed_lead, - log['acceleration']])) + log['acceleration'], + log['d_rel']])) if d_rel < .4 and (self.only_radar or prob_lead > 0.5): print("Crashed!!!!") diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 583c6240e52..8c1a60f5b76 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -7,16 +7,18 @@ from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance TIME = 0 +LEAD_DISTANCE= 2 EGO_V = 3 EGO_A = 5 -LEAD_DISTANCE= 2 +D_REL = 6 axis_labels = ['Time (s)', 'Ego position (m)', - 'Lead distance (m)', + 'Lead absolute position (m)', 'Ego Velocity (m/s)', 'Lead Velocity (m/s)', 'Ego acceleration (m/s^2)', + 'Lead distance (m)' ] @@ -81,7 +83,7 @@ def get_html_from_results(results, labels, AXIS): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} @@ -102,6 +104,7 @@ def get_html_from_results(results, labels, AXIS): labels.append(f'{oscil} m/s oscilliation size') htmls.append(markdown.markdown('# ' + name)) +htmls.append(get_html_from_results(results, labels, D_REL)) htmls.append(get_html_from_results(results, labels, EGO_V)) htmls.append(get_html_from_results(results, labels, EGO_A)) @@ -126,7 +129,7 @@ def get_html_from_results(results, labels, AXIS): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} @@ -148,7 +151,7 @@ def get_html_from_results(results, labels, AXIS): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} @@ -170,7 +173,7 @@ def get_html_from_results(results, labels, AXIS): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} @@ -193,7 +196,7 @@ def get_html_from_results(results, labels, AXIS): htmls.append(markdown.markdown('# ' + name)) htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, LEAD_DISTANCE)) +htmls.append(get_html_from_results(results, labels, D_REL)) results = {} From 0b958f7c9ae682e0ab95d0dc9f45f605be0dfce0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Jan 2026 19:59:25 -0800 Subject: [PATCH 077/685] onroad: fill bookmark icon when activated (#37034) * bookmark fill * and here's what i would have done * add --- selfdrive/ui/mici/onroad/augmented_road_view.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 71ca03cccfa..4e00a3aafe1 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -46,6 +46,8 @@ def __init__(self, bookmark_callback): super().__init__() self._bookmark_callback = bookmark_callback self._icon = gui_app.texture("icons_mici/onroad/bookmark.png", 180, 180) + self._icon_fill = gui_app.texture("icons_mici/onroad/bookmark_fill.png", 180, 180) + self._active_icon = self._icon self._offset_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps) # State @@ -84,6 +86,7 @@ def _update_state(self): if self._offset_filter.x < 1e-3: self._interacting = False + self._active_icon = self._icon def _handle_mouse_event(self, mouse_event: MouseEvent): if not ui_state.started: @@ -96,6 +99,7 @@ def _handle_mouse_event(self, mouse_event: MouseEvent): self._is_swiping = True self._is_swiping_left = False self._state = BookmarkState.DRAGGING + self._active_icon = self._icon elif mouse_event.left_down and self._is_swiping: self._swipe_current_x = mouse_event.pos.x @@ -112,6 +116,7 @@ def _handle_mouse_event(self, mouse_event: MouseEvent): if swipe_distance > self.PEEK_THRESHOLD: self._state = BookmarkState.TRIGGERED self._triggered_time = rl.get_time() + self._active_icon = self._icon_fill self._bookmark_callback() else: # Otherwise, transition back to hidden @@ -125,8 +130,8 @@ def _render(self, _): """Render the bookmark icon.""" if self._offset_filter.x > 0: icon_x = self.rect.x + self.rect.width - round(self._offset_filter.x) - icon_y = self.rect.y + (self.rect.height - self._icon.height) / 2 # Vertically centered - rl.draw_texture(self._icon, int(icon_x), int(icon_y), rl.WHITE) + icon_y = self.rect.y + (self.rect.height - self._active_icon.height) / 2 # Vertically centered + rl.draw_texture(self._active_icon, int(icon_x), int(icon_y), rl.WHITE) class AugmentedRoadView(CameraView): From d849d6f1d7a7c380038a9e7e72df70a125fb7b03 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 27 Jan 2026 21:28:50 -0800 Subject: [PATCH 078/685] mici keyboard: bold SmallKey (#37035) bold SmallKey --- system/ui/widgets/mici_keyboard.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index a81cf853071..7fc3847809f 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -38,10 +38,10 @@ def fast_euclidean_distance(dx, dy): class Key(Widget): - def __init__(self, char: str): + def __init__(self, char: str, font_weight: FontWeight = FontWeight.SEMI_BOLD): super().__init__() self.char = char - self._font = gui_app.font(FontWeight.SEMI_BOLD) + self._font = gui_app.font(font_weight) self._x_filter = BounceFilter(0.0, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) self._y_filter = BounceFilter(0.0, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) self._size_filter = BounceFilter(CHAR_FONT_SIZE, 0.1 * ANIMATION_SCALE, 1 / gui_app.target_fps) @@ -97,7 +97,7 @@ def _get_font_size(self) -> int: class SmallKey(Key): def __init__(self, chars: str): - super().__init__(chars) + super().__init__(chars, FontWeight.BOLD) self._size_filter.x = NUMBER_LAYER_SWITCH_FONT_SIZE def set_font_size(self, size: float): From e89e4407c57f0616bcdc3bd3d48bd19e95274eb9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 28 Jan 2026 19:50:53 -0800 Subject: [PATCH 079/685] Tweak stockLkas alert (#37040) * stockLkas alert is orange, small, mid prio, ldw vis alert * copy exactly from existing ldw alert with prompt sound, black alert --- selfdrive/selfdrived/events.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 35d4bda42f3..0e37a959c5e 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -479,10 +479,10 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin EventName.stockLkas: { ET.PERMANENT: Alert( - "TAKE CONTROL", "Stock LKAS: Lane Departure Detected", - AlertStatus.critical, AlertSize.full, - Priority.HIGH, VisualAlert.fcw, AudibleAlert.none, 2.), + "", + AlertStatus.userPrompt, AlertSize.small, + Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.), ET.NO_ENTRY: NoEntryAlert("Stock LKAS: Lane Departure Detected"), }, From bddd20c4252303362f33241ac35ee7d51acec87b Mon Sep 17 00:00:00 2001 From: T3d Date: Thu, 29 Jan 2026 19:36:51 +0100 Subject: [PATCH 080/685] Complete french translations in app_fr.po (#37023) --- selfdrive/ui/translations/app_fr.po | 94 +++++++++++++++-------------- 1 file changed, 50 insertions(+), 44 deletions(-) diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po index f883d4d485e..409761588e9 100644 --- a/selfdrive/ui/translations/app_fr.po +++ b/selfdrive/ui/translations/app_fr.po @@ -5,10 +5,10 @@ # msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" +"Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-20 18:19-0700\n" +"PO-Revision-Date: 2026-01-24 12:37+0100\n" "Last-Translator: Automatically generated\n" "Language-Team: none\n" "Language: fr\n" @@ -16,21 +16,22 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 3.8\n" #: selfdrive/ui/layouts/settings/device.py:160 #, python-format msgid " Steering torque response calibration is complete." -msgstr "" +msgstr " L'étalonnage de la réponse du couple de direction est terminé." #: selfdrive/ui/layouts/settings/device.py:158 #, python-format msgid " Steering torque response calibration is {}% complete." -msgstr "" +msgstr " L'étalonnage de la réponse du couple de direction est terminé à {}%." #: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." -msgstr "" +msgstr " Votre appareil est orienté {:.1f}° {} et {:.1f}° {}." #: selfdrive/ui/layouts/sidebar.py:43 msgid "--" @@ -79,12 +80,13 @@ msgstr "" #: selfdrive/ui/layouts/settings/device.py:148 #, python-format msgid "

Steering lag calibration is complete." -msgstr "" +msgstr "

L'étalonnage du délai de réponse de la direction est terminé." #: selfdrive/ui/layouts/settings/device.py:146 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" +"

L'étalonnage du délai de réponse de la direction est terminé à {}%." #: selfdrive/ui/layouts/settings/firehose.py:138 #, python-format @@ -107,7 +109,7 @@ msgstr "AJOUTER" #: system/ui/widgets/network.py:139 #, python-format msgid "APN Setting" -msgstr "" +msgstr "Paramètres APN" #: selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format @@ -117,7 +119,7 @@ msgstr "Accuser réception d'actionnement excessif" #: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 #, python-format msgid "Advanced" -msgstr "" +msgstr "Avancé" #: selfdrive/ui/layouts/settings/toggles.py:98 #, python-format @@ -208,18 +210,18 @@ msgstr "CONNECTER" #: system/ui/widgets/network.py:369 #, python-format msgid "CONNECTING..." -msgstr "CONNECTER" +msgstr "CONNECTER..." #: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 #: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 #, python-format msgid "Cancel" -msgstr "" +msgstr "Annuler" #: system/ui/widgets/network.py:134 #, python-format msgid "Cellular Metered" -msgstr "" +msgstr "Données cellulaire limitées" #: selfdrive/ui/layouts/settings/device.py:68 #, python-format @@ -230,7 +232,7 @@ msgstr "Changer la langue" #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" -" La modification de ce réglage redémarrera openpilot si la voiture est sous " +"La modification de ce réglage redémarrera openpilot si la voiture est sous " "tension." #: selfdrive/ui/widgets/pairing_dialog.py:129 @@ -318,7 +320,7 @@ msgstr "Personnalité de conduite" #: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 #, python-format msgid "EDIT" -msgstr "" +msgstr "EDITER" #: selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" @@ -387,22 +389,22 @@ msgstr "" #: system/ui/widgets/network.py:204 #, python-format msgid "Enter APN" -msgstr "" +msgstr "Saisir l'APN" #: system/ui/widgets/network.py:241 #, python-format msgid "Enter SSID" -msgstr "" +msgstr "Entrer le SSID" #: system/ui/widgets/network.py:254 #, python-format msgid "Enter new tethering password" -msgstr "" +msgstr "Saisir le mot de passe du partage de connexion" #: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "Enter password" -msgstr "" +msgstr "Saisir le mot de passe" #: selfdrive/ui/widgets/ssh_key.py:89 #, python-format @@ -412,7 +414,7 @@ msgstr "Entrez votre nom d'utilisateur GitHub" #: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 #, python-format msgid "Error" -msgstr "" +msgstr "Erreur" #: selfdrive/ui/layouts/settings/toggles.py:52 #, python-format @@ -431,7 +433,7 @@ msgstr "" #: system/ui/widgets/network.py:373 #, python-format msgid "FORGETTING..." -msgstr "" +msgstr "OUBLIER..." #: selfdrive/ui/widgets/setup.py:44 #, python-format @@ -493,12 +495,12 @@ msgstr "" #: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 #, python-format msgid "Forget" -msgstr "" +msgstr "Oublier" #: system/ui/widgets/network.py:319 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "" +msgstr "Oublier le réseau Wi-Fi \"{}\" ?" #: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" @@ -532,7 +534,7 @@ msgstr "INSTALLER" #: system/ui/widgets/network.py:150 #, python-format msgid "IP Address" -msgstr "" +msgstr "Adresse IP" #: selfdrive/ui/layouts/settings/software.py:53 #, python-format @@ -574,7 +576,7 @@ msgstr "" #: selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "N/A" -msgstr "" +msgstr "NC" #: selfdrive/ui/layouts/sidebar.py:142 msgid "NO" @@ -592,7 +594,7 @@ msgstr "Aucune clé SSH trouvée" #: selfdrive/ui/widgets/ssh_key.py:126 #, python-format msgid "No SSH keys found for user '{}'" -msgstr "Aucune clé SSH trouvée pour l'utilisateur '{username}'" +msgstr "Aucune clé SSH trouvée pour l'utilisateur '{}'" #: selfdrive/ui/widgets/offroad_alerts.py:320 #, python-format @@ -677,11 +679,15 @@ msgstr "Éteindre" #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" +"Eviter les transferts de données volumineux lorsque vous êtes connecté à un " +"réseau Wi-Fi limité" #: system/ui/widgets/network.py:135 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" +"Eviter les transferts de données volumineux lors d'une connexion à un réseau " +"cellulaire limité" #: selfdrive/ui/layouts/settings/device.py:25 msgid "" @@ -802,32 +808,32 @@ msgstr "Consultez les règles, fonctionnalités et limitations d'openpilot" #: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "SELECT" -msgstr "" +msgstr "SELECTIONNER" #: selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" -msgstr "" +msgstr "Clefs SSH" #: system/ui/widgets/network.py:310 #, python-format msgid "Scanning Wi-Fi networks..." -msgstr "" +msgstr "Analyse des réseaux Wi-Fi..." #: system/ui/widgets/option_dialog.py:36 #, python-format msgid "Select" -msgstr "" +msgstr "Sélectionner" #: selfdrive/ui/layouts/settings/software.py:183 #, python-format msgid "Select a branch" -msgstr "" +msgstr "Sélectionner une branche" #: selfdrive/ui/layouts/settings/device.py:91 #, python-format msgid "Select a language" -msgstr "" +msgstr "Sélectionner un langage" #: selfdrive/ui/layouts/settings/device.py:60 #, python-format @@ -880,12 +886,12 @@ msgstr "TEMPÉRATURE" #: selfdrive/ui/layouts/settings/software.py:61 #, python-format msgid "Target Branch" -msgstr "" +msgstr "Branche cible" #: system/ui/widgets/network.py:124 #, python-format msgid "Tethering Password" -msgstr "" +msgstr "Mot de passe du partage de connexion" #: selfdrive/ui/layouts/settings/settings.py:64 msgid "Toggles" @@ -986,12 +992,12 @@ msgstr "Wi‑Fi" #: system/ui/widgets/network.py:144 #, python-format msgid "Wi-Fi Network Metered" -msgstr "" +msgstr "Réseau Wi-Fi limité" #: system/ui/widgets/network.py:314 #, python-format msgid "Wrong password" -msgstr "" +msgstr "Mauvais mot de passe" #: selfdrive/ui/layouts/onboarding.py:145 #, python-format @@ -1020,12 +1026,12 @@ msgstr "comma prime" #: system/ui/widgets/network.py:142 #, python-format msgid "default" -msgstr "" +msgstr "défaut" #: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "down" -msgstr "" +msgstr "bas" #: selfdrive/ui/layouts/settings/software.py:106 #, python-format @@ -1035,7 +1041,7 @@ msgstr "échec de la vérification de mise à jour" #: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 #, python-format msgid "for \"{}\"" -msgstr "" +msgstr "pour \"{}\"" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format @@ -1045,17 +1051,17 @@ msgstr "km/h" #: system/ui/widgets/network.py:204 #, python-format msgid "leave blank for automatic configuration" -msgstr "" +msgstr "ne pas remplir pour une configuration automatique" #: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "left" -msgstr "" +msgstr "gauche" #: system/ui/widgets/network.py:142 #, python-format msgid "metered" -msgstr "" +msgstr "limité" #: selfdrive/ui/onroad/hud_renderer.py:177 #, python-format @@ -1116,7 +1122,7 @@ msgid "" "openpilot is continuously calibrating, resetting is rarely required. " "Resetting calibration will restart openpilot if the car is powered on." msgstr "" -" La modification de ce réglage redémarrera openpilot si la voiture est sous " +"La modification de ce réglage redémarrera openpilot si la voiture est sous " "tension." #: selfdrive/ui/layouts/settings/firehose.py:20 @@ -1153,17 +1159,17 @@ msgstr "" #: selfdrive/ui/layouts/settings/device.py:134 #, python-format msgid "right" -msgstr "" +msgstr "droite" #: system/ui/widgets/network.py:142 #, python-format msgid "unmetered" -msgstr "" +msgstr "non limité" #: selfdrive/ui/layouts/settings/device.py:133 #, python-format msgid "up" -msgstr "" +msgstr "haut" #: selfdrive/ui/layouts/settings/software.py:117 #, python-format From df7f426405de7ea4885cc54cd44c8ee9c9152f8c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Jan 2026 00:09:19 -0800 Subject: [PATCH 081/685] bump opendbc (#37043) * bump opendbc * update refs --- opendbc_repo | 2 +- selfdrive/test/process_replay/ref_commit | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index d424d1f2473..c8e92d04632 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit d424d1f247384b68923b8093875e1a370ef8221d +Subproject commit c8e92d046324be54cfedccd2a27101060861e82b diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 7b9039180ce..85b79391c3a 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -77951c4ccd0916b87c8dfda9faa33cd2d5d2cc11 \ No newline at end of file +67f3daf309dc6cbb6844fcbaeb83e6596637e551 \ No newline at end of file From 569099eb70eef3c379bb61c84e4dd0504c63bd99 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Jan 2026 00:09:44 -0800 Subject: [PATCH 082/685] update docs --- docs/CARS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index b3496793952..65f79cdba4d 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 327 Supported Cars +# 328 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -103,6 +103,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|N-Box 2018|All|openpilot available[1](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Odyssey 2021-26|All|openpilot available[1](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|openpilot|19 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Passport 2026|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Pilot 2016-22|Honda Sensing|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -202,7 +203,7 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|LC 2024-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|LS 2018|All except Lexus Safety System+ A|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|LS 2018|All except Lexus Safety System+ A|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|NX Hybrid 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| From 32f0a2cbbc0f39b23105c906ff0d77bc4a746c7f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 30 Jan 2026 00:30:11 -0800 Subject: [PATCH 083/685] bump opendbc (#37046) bump --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index c8e92d04632..e76c2cf5bb0 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit c8e92d046324be54cfedccd2a27101060861e82b +Subproject commit e76c2cf5bb0042bc5822efa78fff0362feed7b54 From db3df61c34cc8280e81f3204f58454bf3393a743 Mon Sep 17 00:00:00 2001 From: King Art Date: Sat, 31 Jan 2026 01:16:56 +0000 Subject: [PATCH 084/685] fix non-determinism in modeld build (#37042) * fix non-determinism in selfservice model build also trim down model compile dependencies to the minimum required * Apply suggestions from code review --------- Co-authored-by: Shane Smiskol --- selfdrive/modeld/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index a184b6a23dc..91f3597447b 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -29,7 +29,7 @@ for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transfor cython_libs = envCython["LIBS"] + libs commonmodel_lib = lenv.Library('commonmodel', common_src) lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) -tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x] +tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: From c35df583a5cb287304a88701b2a9040f74788ecc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 31 Jan 2026 15:52:50 -0800 Subject: [PATCH 085/685] tools: enable log caching by default (#36962) --- .github/workflows/repo-maintenance.yaml | 2 +- .github/workflows/tests.yaml | 2 +- selfdrive/car/tests/big_cars_test.sh | 1 - selfdrive/test/process_replay/README.md | 2 +- tools/lib/tests/test_caching.py | 13 ++++++++----- tools/lib/tests/test_logreader.py | 10 ++++++++-- tools/lib/url_file.py | 4 ++-- tools/replay/can_replay.py | 2 -- tools/tuning/measure_steering_accuracy.py | 4 ---- 9 files changed, 21 insertions(+), 19 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 7bb91c0ca4f..b8b29e602f3 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -8,7 +8,7 @@ on: env: BASE_IMAGE: openpilot-base BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c jobs: update_translations: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c5802b5cb2c..c7da6194546 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -25,7 +25,7 @@ env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical diff --git a/selfdrive/car/tests/big_cars_test.sh b/selfdrive/car/tests/big_cars_test.sh index 863b8bead04..bb6e82dd0eb 100755 --- a/selfdrive/car/tests/big_cars_test.sh +++ b/selfdrive/car/tests/big_cars_test.sh @@ -6,7 +6,6 @@ cd $BASEDIR export MAX_EXAMPLES=300 export INTERNAL_SEG_CNT=300 -export FILEREADER_CACHE=1 export INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt cd selfdrive/car/tests && pytest test_models.py test_car_interfaces.py diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index dc801e4285c..8e279c71cd8 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -5,7 +5,7 @@ Process replay is a regression test designed to identify any changes in the outp If the test fails, make sure that you didn't unintentionally change anything. If there are intentional changes, the reference logs will be updated. Use `test_processes.py` to run the test locally. -Use `FILEREADER_CACHE='1' test_processes.py` to cache log files. +Log files are cached by default. Use `DISABLE_FILEREADER_CACHE='1' test_processes.py` to disable caching. Currently the following processes are tested: diff --git a/tools/lib/tests/test_caching.py b/tools/lib/tests/test_caching.py index 6e70ef90b05..cb14098e6dc 100644 --- a/tools/lib/tests/test_caching.py +++ b/tools/lib/tests/test_caching.py @@ -56,13 +56,13 @@ def test_pipeline_defaults(self, host): for k, v in retry_defaults.items(): assert getattr(URLFile.pool_manager().connection_pool_kw["retries"], k) == v - # ensure caching off by default and cache dir doesn't get created - os.environ.pop("FILEREADER_CACHE", None) + # ensure caching on by default and cache dir gets created + os.environ.pop("DISABLE_FILEREADER_CACHE", None) if os.path.exists(Paths.download_cache_root()): shutil.rmtree(Paths.download_cache_root()) URLFile(f"{host}/test.txt").get_length() URLFile(f"{host}/test.txt").read() - assert not os.path.exists(Paths.download_cache_root()) + assert os.path.exists(Paths.download_cache_root()) def compare_loads(self, url, start=0, length=None): """Compares range between cached and non cached version""" @@ -90,7 +90,7 @@ def compare_loads(self, url, start=0, length=None): def test_small_file(self): # Make sure we don't force cache - os.environ["FILEREADER_CACHE"] = "0" + os.environ.pop("DISABLE_FILEREADER_CACHE", None) small_file_url = "https://raw.githubusercontent.com/commaai/openpilot/master/docs/SAFETY.md" # If you want large file to be larger than a chunk # large_file_url = "https://commadataci.blob.core.windows.net/openpilotci/0375fdf7b1ce594d/2019-06-13--08-32-25/3/fcamera.hevc" @@ -119,7 +119,10 @@ def test_large_file(self): @pytest.mark.parametrize("cache_enabled", [True, False]) def test_recover_from_missing_file(self, host, cache_enabled): - os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" + if cache_enabled: + os.environ.pop("DISABLE_FILEREADER_CACHE", None) + else: + os.environ["DISABLE_FILEREADER_CACHE"] = "1" file_url = f"{host}/test.png" diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index ee75a8b1ce8..0151940c44d 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -93,7 +93,10 @@ def test_canonical_name(self, identifier, expected): @pytest.mark.parametrize("cache_enabled", [True, False]) def test_direct_parsing(self, mocker, cache_enabled): file_exists_mock = mocker.patch("openpilot.tools.lib.filereader.file_exists") - os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" + if cache_enabled: + os.environ.pop("DISABLE_FILEREADER_CACHE", None) + else: + os.environ["DISABLE_FILEREADER_CACHE"] = "1" qlog = tempfile.NamedTemporaryFile(mode='wb', delete=False) with requests.get(QLOG_FILE, stream=True) as r: @@ -181,7 +184,10 @@ def test_helpers(self): @parameterized.expand([(True,), (False,)]) @pytest.mark.slow def test_run_across_segments(self, cache_enabled): - os.environ["FILEREADER_CACHE"] = "1" if cache_enabled else "0" + if cache_enabled: + os.environ.pop("DISABLE_FILEREADER_CACHE", None) + else: + os.environ["DISABLE_FILEREADER_CACHE"] = "1" lr = LogReader(f"{TEST_ROUTE}/0:4") assert len(lr.run_across_segments(4, noop)) == len(list(lr)) diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 8e2f0a9222f..de120704659 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -74,8 +74,8 @@ def __init__(self, url: str, timeout: int = 10, cache: bool | None = None): self._timeout = Timeout(connect=timeout, read=timeout) self._pos = 0 self._length: int | None = None - # True by default, false if FILEREADER_CACHE is defined, but can be overwritten by the cache input - self._force_download = not int(os.environ.get("FILEREADER_CACHE", "0")) + # Caching enabled by default, can be disabled with DISABLE_FILEREADER_CACHE=1, or overwritten by the cache input + self._force_download = int(os.environ.get("DISABLE_FILEREADER_CACHE", "0")) == 1 if cache is not None: self._force_download = not cache diff --git a/tools/replay/can_replay.py b/tools/replay/can_replay.py index 13c30a62ad0..07173200779 100755 --- a/tools/replay/can_replay.py +++ b/tools/replay/can_replay.py @@ -5,8 +5,6 @@ import usb1 import threading -os.environ['FILEREADER_CACHE'] = '1' - from openpilot.common.realtime import config_realtime_process, Ratekeeper, DT_CTRL from openpilot.selfdrive.pandad import can_capnp_to_list from openpilot.tools.lib.logreader import LogReader diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index e4aef0ba15c..ae3344c2eb1 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -117,12 +117,8 @@ def update(self, sm): parser.add_argument('--route', help="route name") parser.add_argument('--addr', default='127.0.0.1', help="IP address for optional ZMQ listener, default to msgq") parser.add_argument('--group', default='all', help="speed group to display, [crawl|slow|medium|fast|veryfast|germany|all], default to all") - parser.add_argument('--cache', default=False, action='store_true', help="use cached data, default to False") args = parser.parse_args() - if args.cache: - os.environ['FILEREADER_CACHE'] = '1' - tool = SteeringAccuracyTool(args) if args.route is not None: From 1dfef69a3c8b6a5e622cfa648075a67ed267bbdd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A1draic=20Slattery?= Date: Sun, 1 Feb 2026 03:37:00 +0100 Subject: [PATCH 086/685] chore: Update outdated GitHub Actions versions (#37020) * chore: Update outdated GitHub Actions versions * just the github ones --------- Co-authored-by: Adeeb Shihadeh --- .github/workflows/auto_pr_review.yaml | 6 ++--- .github/workflows/badges.yaml | 2 +- .github/workflows/ci_weekly_report.yaml | 2 +- .github/workflows/docs.yaml | 4 +-- .github/workflows/jenkins-pr-trigger.yaml | 6 ++--- .github/workflows/mici_raylib_ui_preview.yaml | 4 +-- .github/workflows/model_review.yaml | 4 +-- .github/workflows/prebuilt.yaml | 2 +- .github/workflows/raylib_ui_preview.yaml | 2 +- .github/workflows/release.yaml | 2 +- .github/workflows/repo-maintenance.yaml | 4 +-- .github/workflows/stale.yaml | 4 +-- .github/workflows/tests.yaml | 26 +++++++++---------- 13 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index c6a1cb98219..725154d21fa 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -11,12 +11,12 @@ jobs: pull-requests: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: false # Label PRs - - uses: actions/labeler@v5.0.0 + - uses: actions/labeler@v6 with: dot: true configuration-path: .github/labeler.yaml @@ -36,7 +36,7 @@ jobs: # Welcome comment - name: "First timers PR" - uses: actions/first-interaction@v1 + uses: actions/first-interaction@v3 if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' with: repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index cd30e4f3708..3f9c9c1c590 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -17,7 +17,7 @@ jobs: permissions: contents: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry diff --git a/.github/workflows/ci_weekly_report.yaml b/.github/workflows/ci_weekly_report.yaml index 37a46b20968..c7f5ec34f0b 100644 --- a/.github/workflows/ci_weekly_report.yaml +++ b/.github/workflows/ci_weekly_report.yaml @@ -41,7 +41,7 @@ jobs: if: always() && github.repository == 'commaai/openpilot' steps: - name: Get job results - uses: actions/github-script@v7 + uses: actions/github-script@v8 id: get-job-results with: script: | diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 92c311829c0..23a89de1c11 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -22,7 +22,7 @@ jobs: steps: - uses: commaai/timeout@v1 - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true @@ -34,7 +34,7 @@ jobs: mkdocs build # Push to docs.comma.ai - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' with: path: openpilot-docs diff --git a/.github/workflows/jenkins-pr-trigger.yaml b/.github/workflows/jenkins-pr-trigger.yaml index 14e2fdf49ba..f8a53c5ae0c 100644 --- a/.github/workflows/jenkins-pr-trigger.yaml +++ b/.github/workflows/jenkins-pr-trigger.yaml @@ -15,7 +15,7 @@ jobs: steps: - name: Check for trigger phrase id: check_comment - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const triggerPhrase = "trigger-jenkins"; @@ -35,7 +35,7 @@ jobs: - name: Checkout repository if: steps.check_comment.outputs.result == 'true' - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: refs/pull/${{ github.event.issue.number }}/head @@ -49,7 +49,7 @@ jobs: - name: Delete trigger comment if: steps.check_comment.outputs.result == 'true' && always() - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | await github.rest.issues.deleteComment({ diff --git a/.github/workflows/mici_raylib_ui_preview.yaml b/.github/workflows/mici_raylib_ui_preview.yaml index 707825b1ac4..5025d407cd6 100644 --- a/.github/workflows/mici_raylib_ui_preview.yaml +++ b/.github/workflows/mici_raylib_ui_preview.yaml @@ -33,7 +33,7 @@ jobs: pull-requests: write actions: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true @@ -62,7 +62,7 @@ jobs: path: ${{ github.workspace }}/pr_ui - name: Getting master ui # filename: master_ui_raylib/mici_ui_replay.mp4 - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: commaai/ci-artifacts ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} diff --git a/.github/workflows/model_review.yaml b/.github/workflows/model_review.yaml index 0e1825864c2..6b8ce143dbf 100644 --- a/.github/workflows/model_review.yaml +++ b/.github/workflows/model_review.yaml @@ -16,9 +16,9 @@ jobs: if: github.repository == 'commaai/openpilot' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Checkout master - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: master path: base diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index d8963ec89f4..921c27465b9 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -29,7 +29,7 @@ jobs: running-workflow-name: 'build prebuilt' repo-token: ${{ secrets.GITHUB_TOKEN }} check-regexp: ^((?!.*(build master-ci).*).)*$ - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - run: git lfs pull diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml index 18880e8a17a..9044a97f536 100644 --- a/.github/workflows/raylib_ui_preview.yaml +++ b/.github/workflows/raylib_ui_preview.yaml @@ -58,7 +58,7 @@ jobs: path: ${{ github.workspace }}/pr_ui - name: Getting master ui - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: commaai/ci-artifacts ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0f4ce6cb3a7..0f34dbe435b 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -30,7 +30,7 @@ jobs: running-workflow-name: 'build master-ci' repo-token: ${{ secrets.GITHUB_TOKEN }} check-regexp: ^((?!.*(build prebuilt).*).)*$ - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true fetch-depth: 0 diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index b8b29e602f3..810b602d711 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest if: github.repository == 'commaai/openpilot' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: ./.github/workflows/setup-with-retry - name: Update translations run: | @@ -39,7 +39,7 @@ jobs: image: ghcr.io/commaai/openpilot-base:latest if: github.repository == 'commaai/openpilot' steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - name: uv lock diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml index 1ecd114dc44..cb7c0ac0764 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yaml @@ -13,7 +13,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: exempt-all-milestones: true @@ -34,7 +34,7 @@ jobs: stale_drafts: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v10 with: exempt-all-milestones: true diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index c7da6194546..4ade42b665c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -41,7 +41,7 @@ jobs: env: STRIPPED_DIR: /tmp/releasepilot steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - name: Getting LFS files @@ -77,7 +77,7 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - name: Setup docker push @@ -93,7 +93,7 @@ jobs: name: build macOS runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV @@ -133,7 +133,7 @@ jobs: env: PYTHONWARNINGS: default steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -150,7 +150,7 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -175,14 +175,14 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry id: setup-step - name: Cache test routes id: dependency-cache - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .ci_cache/comma_download_cache key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_processes.py') }} @@ -198,7 +198,7 @@ jobs: id: print-diff if: always() run: cat selfdrive/test/process_replay/diff.txt - - uses: actions/upload-artifact@v4 + - uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: @@ -225,7 +225,7 @@ jobs: || fromJSON('["ubuntu-24.04"]') }} if: false # FIXME: Started to timeout recently steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -249,7 +249,7 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -261,7 +261,7 @@ jobs: source selfdrive/test/setup_xvfb.sh && python3 selfdrive/ui/tests/test_ui/raylib_screenshots.py" - name: Upload Raylib UI Report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} path: selfdrive/ui/tests/test_ui/raylib_report/screenshots @@ -275,7 +275,7 @@ jobs: && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: true - uses: ./.github/workflows/setup-with-retry @@ -287,7 +287,7 @@ jobs: source selfdrive/test/setup_xvfb.sh && WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py" - name: Upload Raylib UI Report - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v6 with: name: mici-raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} path: selfdrive/ui/tests/diff/report From cd70e23dc3b990eecea2364c8e0b2d2ac48de7fd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 31 Jan 2026 20:15:23 -0800 Subject: [PATCH 087/685] clips: direct rendering with raylib (#36935) * good clips * replace * fix * fix font * lil more --- common/utils.py | 21 ++ system/ui/lib/application.py | 54 +++- tools/clip/run.py | 598 ++++++++++++++++++----------------- 3 files changed, 381 insertions(+), 292 deletions(-) diff --git a/common/utils.py b/common/utils.py index caa9a579581..ccc6719f5f2 100644 --- a/common/utils.py +++ b/common/utils.py @@ -10,6 +10,27 @@ LOG_COMPRESSION_LEVEL = 10 # little benefit up to level 15. level ~17 is a small step change +class Timer: + """Simple lap timer for profiling sequential operations.""" + + def __init__(self): + self._start = self._lap = time.monotonic() + self._sections = {} + + def lap(self, name): + now = time.monotonic() + self._sections[name] = now - self._lap + self._lap = now + + @property + def total(self): + return time.monotonic() - self._start + + def fmt(self, duration): + parts = ", ".join(f"{k}={v:.2f}s" + (f" ({duration/v:.0f}x)" if k == 'render' and v > 0 else "") for k, v in self._sections.items()) + total = self.total + realtime = f"{duration/total:.1f}x realtime" if total > 0 else "N/A" + return f"{duration}s in {total:.1f}s ({realtime}) | {parts}" def sudo_write(val: str, path: str) -> None: try: diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 151f22ac126..da314a394f5 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -1,6 +1,7 @@ import atexit import cffi import os +import queue import time import signal import sys @@ -40,6 +41,9 @@ PROFILE_STATS = int(os.getenv("PROFILE_STATS", "100")) # Number of functions to show in profile output RECORD = os.getenv("RECORD") == "1" RECORD_OUTPUT = str(Path(os.getenv("RECORD_OUTPUT", "output")).with_suffix(".mp4")) +RECORD_BITRATE = os.getenv("RECORD_BITRATE", "") # Target bitrate e.g. "2000k" +RECORD_SPEED = int(os.getenv("RECORD_SPEED", "1")) # Speed multiplier +OFFSCREEN = os.getenv("OFFSCREEN") == "1" # Disable FPS limiting for fast offline rendering GL_VERSION = """ #version 300 es @@ -213,6 +217,9 @@ def __init__(self, width: int | None = None, height: int | None = None): self._render_texture: rl.RenderTexture | None = None self._burn_in_shader: rl.Shader | None = None self._ffmpeg_proc: subprocess.Popen | None = None + self._ffmpeg_queue: queue.Queue | None = None + self._ffmpeg_thread: threading.Thread | None = None + self._ffmpeg_stop_event: threading.Event | None = None self._textures: dict[str, rl.Texture] = {} self._target_fps: int = _DEFAULT_FPS self._last_fps_log_time: float = time.monotonic() @@ -277,25 +284,36 @@ def _close(sig, frame): rl.set_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) if RECORD: + output_fps = fps * RECORD_SPEED ffmpeg_args = [ 'ffmpeg', '-v', 'warning', # Reduce ffmpeg log spam - '-stats', # Show encoding progress + '-nostats', # Suppress encoding progress '-f', 'rawvideo', # Input format '-pix_fmt', 'rgba', # Input pixel format '-s', f'{self._width}x{self._height}', # Input resolution '-r', str(fps), # Input frame rate '-i', 'pipe:0', # Input from stdin - '-vf', 'vflip,format=yuv420p', # Flip vertically and convert rgba to yuv420p - '-c:v', 'libx264', # Video codec - '-preset', 'ultrafast', # Encoding speed + '-vf', 'vflip,format=yuv420p', # Flip vertically and convert to yuv420p + '-r', str(output_fps), # Output frame rate (for speed multiplier) + '-c:v', 'libx264', + '-preset', 'ultrafast', + ] + if RECORD_BITRATE: + ffmpeg_args += ['-b:v', RECORD_BITRATE, '-maxrate', RECORD_BITRATE, '-bufsize', RECORD_BITRATE] + ffmpeg_args += [ '-y', # Overwrite existing file '-f', 'mp4', # Output format RECORD_OUTPUT, # Output file path ] self._ffmpeg_proc = subprocess.Popen(ffmpeg_args, stdin=subprocess.PIPE) + self._ffmpeg_queue = queue.Queue(maxsize=60) # Buffer up to 60 frames + self._ffmpeg_stop_event = threading.Event() + self._ffmpeg_thread = threading.Thread(target=self._ffmpeg_writer_thread, daemon=True) + self._ffmpeg_thread.start() - rl.set_target_fps(fps) + # OFFSCREEN disables FPS limiting for fast offline rendering (e.g. clips) + rl.set_target_fps(0 if OFFSCREEN else fps) self._target_fps = fps self._set_styles() @@ -337,6 +355,21 @@ def _startup_profile_context(self): print(f"{green}UI window ready in {elapsed_ms:.1f} ms{reset}") sys.exit(0) + def _ffmpeg_writer_thread(self): + """Background thread that writes frames to ffmpeg.""" + while True: + try: + data = self._ffmpeg_queue.get(timeout=1.0) + if data is None: # Sentinel to stop + break + self._ffmpeg_proc.stdin.write(data) + except queue.Empty: + if self._ffmpeg_stop_event.is_set(): + break + continue + except Exception: + break + def set_modal_overlay(self, overlay, callback: Callable | None = None): if self._modal_overlay.overlay is not None: if hasattr(self._modal_overlay.overlay, 'hide_event'): @@ -409,11 +442,17 @@ def _load_texture_from_image(self, image: rl.Image) -> rl.Texture: return texture def close_ffmpeg(self): + if self._ffmpeg_thread is not None: + # Signal thread to stop, send sentinel, then wait for it to drain + self._ffmpeg_stop_event.set() + self._ffmpeg_queue.put(None) + self._ffmpeg_thread.join(timeout=30) + if self._ffmpeg_proc is not None: self._ffmpeg_proc.stdin.flush() self._ffmpeg_proc.stdin.close() try: - self._ffmpeg_proc.wait(timeout=5) + self._ffmpeg_proc.wait(timeout=30) except subprocess.TimeoutExpired: self._ffmpeg_proc.terminate() self._ffmpeg_proc.wait() @@ -525,8 +564,7 @@ def render(self): image = rl.load_image_from_texture(self._render_texture.texture) data_size = image.width * image.height * 4 data = bytes(rl.ffi.buffer(image.data, data_size)) - self._ffmpeg_proc.stdin.write(data) - self._ffmpeg_proc.stdin.flush() + self._ffmpeg_queue.put(data) # Async write via background thread rl.unload_image(image) self._monitor_fps() diff --git a/tools/clip/run.py b/tools/clip/run.py index 9045a4381b1..324ee666906 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -1,310 +1,340 @@ #!/usr/bin/env python3 - -import logging import os -import platform -import shutil import sys import time -from argparse import ArgumentParser, ArgumentTypeError -from collections.abc import Sequence +import logging +import subprocess +import threading +import queue +import multiprocessing +import itertools +import numpy as np +import tqdm +from argparse import ArgumentParser from pathlib import Path -from random import randint -from subprocess import Popen -from typing import Literal +from concurrent.futures import ThreadPoolExecutor, as_completed -from cereal.messaging import SubMaster -from openpilot.common.basedir import BASEDIR -from openpilot.common.params import Params, UnknownKeyName -from openpilot.common.prefix import OpenpilotPrefix -from openpilot.common.utils import managed_proc from openpilot.tools.lib.route import Route from openpilot.tools.lib.logreader import LogReader +from openpilot.tools.lib.filereader import FileReader +from openpilot.tools.lib.framereader import FrameReader, ffprobe +from openpilot.selfdrive.test.process_replay.migration import migrate_all +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.common.utils import Timer +from msgq.visionipc import VisionIpcServer, VisionStreamType -DEFAULT_OUTPUT = 'output.mp4' -DEMO_START = 90 -DEMO_END = 105 -DEMO_ROUTE = 'a2a0ccea32023010/2023-07-27--13-01-19' FRAMERATE = 20 -PIXEL_DEPTH = '24' -RESOLUTION = '2160x1080' -SECONDS_TO_WARM = 2 -PROC_WAIT_SECONDS = 30*10 - -OPENPILOT_FONT = str(Path(BASEDIR, 'selfdrive/assets/fonts/Inter-Regular.ttf').resolve()) -REPLAY = str(Path(BASEDIR, 'tools/replay/replay').resolve()) -UI = str(Path(BASEDIR, 'selfdrive/ui/ui').resolve()) - -logger = logging.getLogger('clip.py') - - -def check_for_failure(procs: list[Popen]): - for proc in procs: - exit_code = proc.poll() - if exit_code is not None and exit_code != 0: - cmd = str(proc.args) - if isinstance(proc.args, str): - cmd = proc.args - elif isinstance(proc.args, Sequence): - cmd = str(proc.args[0]) - msg = f'{cmd} failed, exit code {exit_code}' - logger.error(msg) - stdout, stderr = proc.communicate() - if stdout: - logger.error(stdout.decode()) - if stderr: - logger.error(stderr.decode()) - raise ChildProcessError(msg) - - -def escape_ffmpeg_text(value: str): - special_chars = {',': '\\,', ':': '\\:', '=': '\\=', '[': '\\[', ']': '\\]'} - value = value.replace('\\', '\\\\\\\\\\\\\\\\') - for char, escaped in special_chars.items(): - value = value.replace(char, escaped) - return value - - -def get_logreader(route: Route): - return LogReader(route.qlog_paths()[0] if len(route.qlog_paths()) else route.name.canonical_name) - - -def get_meta_text(lr: LogReader, route: Route): - init_data = lr.first('initData') - car_params = lr.first('carParams') - origin_parts = init_data.gitRemote.split('/') - origin = origin_parts[3] if len(origin_parts) > 3 else 'unknown' - return ', '.join([ - f"openpilot v{init_data.version}", - f"route: {route.name.canonical_name}", - f"car: {car_params.carFingerprint}", - f"origin: {origin}", - f"branch: {init_data.gitBranch}", - f"commit: {init_data.gitCommit[:7]}", - f"modified: {str(init_data.dirty).lower()}", - ]) - - -def parse_args(parser: ArgumentParser): +DEMO_ROUTE, DEMO_START, DEMO_END = 'a2a0ccea32023010/2023-07-27--13-01-19', 90, 105 + +logger = logging.getLogger('clip') + + +def parse_args(): + parser = ArgumentParser(description="Direct clip renderer") + parser.add_argument("route", nargs="?", help="Route ID (dongle/route or dongle/route/start/end)") + parser.add_argument("-s", "--start", type=int, help="Start time in seconds") + parser.add_argument("-e", "--end", type=int, help="End time in seconds") + parser.add_argument("-o", "--output", default="output.mp4", help="Output file path") + parser.add_argument("-d", "--data-dir", help="Local directory with route data") + parser.add_argument("-t", "--title", help="Title overlay text") + parser.add_argument("-f", "--file-size", type=float, default=9.0, help="Target file size in MB") + parser.add_argument("-x", "--speed", type=int, default=1, help="Speed multiplier") + parser.add_argument("--demo", action="store_true", help="Use demo route with default timing") + parser.add_argument("--big", action="store_true", default=True, help="Use big UI (2160x1080)") + parser.add_argument("--qcam", action="store_true", help="Use qcamera instead of fcamera") + parser.add_argument("--windowed", action="store_true", help="Show window") + parser.add_argument("--no-metadata", action="store_true", help="Disable metadata overlay") + parser.add_argument("--no-time-overlay", action="store_true", help="Disable time overlay") args = parser.parse_args() + if args.demo: - args.route = DEMO_ROUTE - if args.start is None or args.end is None: - args.start = DEMO_START - args.end = DEMO_END - elif args.route.count('/') == 1: - if args.start is None or args.end is None: - parser.error('must provide both start and end if timing is not in the route ID') - elif args.route.count('/') == 3: - if args.start is not None or args.end is not None: - parser.error('don\'t provide timing when including it in the route ID') + args.route, args.start, args.end = args.route or DEMO_ROUTE, args.start or DEMO_START, args.end or DEMO_END + elif not args.route: + parser.error("route is required (or use --demo)") + + if args.route and args.route.count('/') == 3: parts = args.route.split('/') - args.route = '/'.join(parts[:2]) - args.start = int(parts[2]) - args.end = int(parts[3]) - if args.end <= args.start: - parser.error(f'end ({args.end}) must be greater than start ({args.start})') - if args.start < SECONDS_TO_WARM: - parser.error(f'start must be greater than {SECONDS_TO_WARM}s to allow the UI time to warm up') - - try: - args.route = Route(args.route, data_dir=args.data_dir) - except Exception as e: - parser.error(f'failed to get route: {e}') - - # FIXME: length isn't exactly max segment seconds, simplify to replay exiting at end of data - length = round(args.route.max_seg_number * 60) - if args.start >= length: - parser.error(f'start ({args.start}s) cannot be after end of route ({length}s)') - if args.end > length: - parser.error(f'end ({args.end}s) cannot be after end of route ({length}s)') + args.route, args.start, args.end = '/'.join(parts[:2]), args.start or int(parts[2]), args.end or int(parts[3]) + if args.start is None or args.end is None: + parser.error("--start and --end are required") + if args.end <= args.start: + parser.error(f"end ({args.end}) must be greater than start ({args.start})") return args -def populate_car_params(lr: LogReader): - init_data = lr.first('initData') - assert init_data is not None +def setup_env(output_path: str, big: bool = False, speed: int = 1, target_mb: float = 0, duration: int = 0): + os.environ.update({"RECORD": "1", "OFFSCREEN": "1", "RECORD_OUTPUT": str(Path(output_path).with_suffix(".mp4"))}) + if speed > 1: + os.environ["RECORD_SPEED"] = str(speed) + if target_mb > 0 and duration > 0: + os.environ["RECORD_BITRATE"] = f"{int(target_mb * 8 * 1024 / (duration / speed))}k" + if big: + os.environ["BIG"] = "1" + + +def _download_segment(path: str) -> bytes: + with FileReader(path) as f: + return bytes(f.read()) + + +def _parse_and_chunk_segment(args: tuple) -> list[dict]: + raw_data, fps = args + from openpilot.tools.lib.logreader import _LogFileReader + messages = migrate_all(list(_LogFileReader("", dat=raw_data, sort_by_time=True))) + if not messages: + return [] + + dt_ns, chunks, current, next_time = 1e9 / fps, [], {}, messages[0].logMonoTime + 1e9 / fps # type: ignore[var-annotated] + for msg in messages: + if msg.logMonoTime >= next_time: + chunks.append(current) + current, next_time = {}, next_time + dt_ns * ((msg.logMonoTime - next_time) // dt_ns + 1) + current[msg.which()] = msg + return chunks + [current] if current else chunks + + +def load_logs_parallel(log_paths: list[str], fps: int = 20) -> list[dict]: + num_workers = min(16, len(log_paths), (multiprocessing.cpu_count() or 1)) + logger.info(f"Downloading {len(log_paths)} segments with {num_workers} workers...") + + with ThreadPoolExecutor(max_workers=num_workers) as pool: + futures = {pool.submit(_download_segment, path): idx for idx, path in enumerate(log_paths)} + raw_data = {futures[f]: f.result() for f in as_completed(futures)} + + logger.info("Parsing and chunking segments...") + with multiprocessing.Pool(num_workers) as pool: + return list(itertools.chain.from_iterable(pool.map(_parse_and_chunk_segment, [(raw_data[i], fps) for i in range(len(log_paths))]))) + + +def patch_submaster(message_chunks, ui_state): + # Reset started_frame so alerts render correctly (recv_frame must be >= started_frame) + ui_state.started_frame = 0 + ui_state.started_time = time.monotonic() + + def mock_update(timeout=None): + sm, t = ui_state.sm, time.monotonic() + sm.updated = dict.fromkeys(sm.services, False) + if sm.frame < len(message_chunks): + for svc, msg in message_chunks[sm.frame].items(): + if svc in sm.data: + sm.seen[svc] = sm.updated[svc] = sm.alive[svc] = sm.valid[svc] = True + sm.data[svc] = getattr(msg.as_builder(), svc) + sm.logMonoTime[svc], sm.recv_time[svc], sm.recv_frame[svc] = msg.logMonoTime, t, sm.frame + sm.frame += 1 + ui_state.sm.update = mock_update + + +def get_frame_dimensions(camera_path: str) -> tuple[int, int]: + """Get frame dimensions from a video file using ffprobe.""" + probe = ffprobe(camera_path) + stream = probe["streams"][0] + return stream["width"], stream["height"] + + +def iter_segment_frames(camera_paths, start_time, end_time, fps=20, use_qcam=False, frame_size: tuple[int, int] | None = None): + frames_per_seg = fps * 60 + start_frame, end_frame = int(start_time * fps), int(end_time * fps) + current_seg: int = -1 + seg_frames: FrameReader | np.ndarray | None = None + + for global_idx in range(start_frame, end_frame): + seg_idx, local_idx = global_idx // frames_per_seg, global_idx % frames_per_seg + + if seg_idx != current_seg: + current_seg = seg_idx + path = camera_paths[seg_idx] if seg_idx < len(camera_paths) else None + if not path: + raise RuntimeError(f"No camera file for segment {seg_idx}") + + if use_qcam: + w, h = frame_size or get_frame_dimensions(path) + with FileReader(path) as f: + result = subprocess.run(["ffmpeg", "-v", "quiet", "-i", "-", "-f", "rawvideo", "-pix_fmt", "nv12", "-"], + input=f.read(), capture_output=True) + if result.returncode != 0: + raise RuntimeError(f"ffmpeg failed: {result.stderr.decode()}") + seg_frames = np.frombuffer(result.stdout, dtype=np.uint8).reshape(-1, w * h * 3 // 2) + else: + seg_frames = FrameReader(path, pix_fmt="nv12") + + assert seg_frames is not None + frame = seg_frames[local_idx] if use_qcam else seg_frames.get(local_idx) # type: ignore[index, union-attr] + yield global_idx, frame + + +class FrameQueue: + def __init__(self, camera_paths, start_time, end_time, fps=20, prefetch_count=60, use_qcam=False): + # Probe first valid camera file for dimensions + first_path = next((p for p in camera_paths if p), None) + if not first_path: + raise RuntimeError("No valid camera paths") + self.frame_w, self.frame_h = get_frame_dimensions(first_path) + + self._queue, self._stop, self._error = queue.Queue(maxsize=prefetch_count), threading.Event(), None + self._thread = threading.Thread(target=self._worker, + args=(camera_paths, start_time, end_time, fps, use_qcam, (self.frame_w, self.frame_h)), daemon=True) + self._thread.start() + + def _worker(self, camera_paths, start_time, end_time, fps, use_qcam, frame_size): + try: + for idx, data in iter_segment_frames(camera_paths, start_time, end_time, fps, use_qcam, frame_size): + if self._stop.is_set(): + break + self._queue.put((idx, data.tobytes())) + except Exception as e: + logger.exception("Decode error") + self._error = e + finally: + self._queue.put(None) + + def get(self, timeout=60.0): + if self._error: + raise self._error + result = self._queue.get(timeout=timeout) + if result is None: + raise StopIteration("No more frames") + return result + + def stop(self): + self._stop.set() + while not self._queue.empty(): + try: + self._queue.get_nowait() + except queue.Empty: + break + self._thread.join(timeout=2.0) + + +def load_route_metadata(route): + from openpilot.common.params import Params, UnknownKeyName + lr = LogReader(route.log_paths()[0]) + init_data, car_params = lr.first('initData'), lr.first('carParams') params = Params() - entries = init_data.params.entries - for cp in entries: - key, value = cp.key, cp.value + for entry in init_data.params.entries: try: - params.put(key, params.cpp2python(key, value)) + params.put(entry.key, params.cpp2python(entry.key, entry.value)) except UnknownKeyName: - # forks of openpilot may have other Params keys configured. ignore these - logger.warning(f"unknown Params key '{key}', skipping") - logger.debug('persisted CarParams') - - -def validate_env(parser: ArgumentParser): - if platform.system() not in ['Linux']: - parser.exit(1, f'clip.py: error: {platform.system()} is not a supported operating system\n') - for proc in ['Xvfb', 'ffmpeg']: - if shutil.which(proc) is None: - parser.exit(1, f'clip.py: error: missing {proc} command, is it installed?\n') - for proc in [REPLAY, UI]: - if shutil.which(proc) is None: - parser.exit(1, f'clip.py: error: missing {proc} command, did you build openpilot yet?\n') - - -def validate_output_file(output_file: str): - if not output_file.endswith('.mp4'): - raise ArgumentTypeError('output must be an mp4') - return output_file - - -def validate_route(route: str): - if route.count('/') not in (1, 3): - raise ArgumentTypeError(f'route must include or exclude timing, example: {DEMO_ROUTE}') - return route - - -def validate_title(title: str): - if len(title) > 80: - raise ArgumentTypeError('title must be no longer than 80 chars') - return title - - -def wait_for_frames(procs: list[Popen]): - sm = SubMaster(['uiDebug']) - no_frames_drawn = True - while no_frames_drawn: - sm.update() - no_frames_drawn = sm['uiDebug'].drawTimeMillis == 0. - check_for_failure(procs) - - -def clip( - data_dir: str | None, - quality: Literal['low', 'high'], - prefix: str, - route: Route, - out: str, - start: int, - end: int, - speed: int, - target_mb: int, - title: str | None, -): - logger.info(f'clipping route {route.name.canonical_name}, start={start} end={end} quality={quality} target_filesize={target_mb}MB') - lr = get_logreader(route) - - begin_at = max(start - SECONDS_TO_WARM, 0) - duration = end - start - bit_rate_kbps = int(round(target_mb * 8 * 1024 * 1024 / duration / 1000)) - - # TODO: evaluate creating fn that inspects /tmp/.X11-unix and creates unused display to avoid possibility of collision - display = f':{randint(99, 999)}' - - box_style = 'box=1:boxcolor=black@0.33:boxborderw=7' - meta_text = get_meta_text(lr, route) - overlays = [ - # metadata overlay - f"drawtext=text='{escape_ffmpeg_text(meta_text)}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=15:{box_style}:x=(w-text_w)/2:y=5.5:enable='between(t,1,5)'", - # route time overlay - f"drawtext=text='%{{eif\\:floor(({start}+t)/60)\\:d\\:2}}\\:%{{eif\\:mod({start}+t\\,60)\\:d\\:2}}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=24:{box_style}:x=w-text_w-38:y=38" - ] - if title: - overlays.append(f"drawtext=text='{escape_ffmpeg_text(title)}':fontfile={OPENPILOT_FONT}:fontcolor=white:fontsize=32:{box_style}:x=(w-text_w)/2:y=53") + pass + + origin = init_data.gitRemote.split('/')[3] if len(init_data.gitRemote.split('/')) > 3 else 'unknown' + return { + 'version': init_data.version, 'route': route.name.canonical_name, + 'car': car_params.carFingerprint if car_params else 'unknown', 'origin': origin, + 'branch': init_data.gitBranch, 'commit': init_data.gitCommit[:7], 'modified': str(init_data.dirty).lower(), + } + + +def draw_text_box(rl, text, x, y, size, gui_app, font, font_scale, color=None, center=False): + box_color, text_color = rl.Color(0, 0, 0, 85), color or rl.WHITE + # measure_text_ex is NOT auto-scaled, so multiply by font_scale + # draw_text_ex IS auto-scaled, so pass size directly + text_size = rl.measure_text_ex(font, text, size * font_scale, 0) + text_width, text_height = int(text_size.x), int(text_size.y) + if center: + x = (gui_app.width - text_width) // 2 + rl.draw_rectangle(x - 8, y - 4, text_width + 16, text_height + 8, box_color) + rl.draw_text_ex(font, text, rl.Vector2(x, y), size, 0, text_color) + + +def render_overlays(rl, gui_app, font, font_scale, metadata, title, start_time, frame_idx, show_metadata, show_time): + if show_metadata and metadata and frame_idx < FRAMERATE * 5: + m = metadata + text = ", ".join([f"openpilot v{m['version']}", f"route: {m['route']}", f"car: {m['car']}", f"origin: {m['origin']}", + f"branch: {m['branch']}", f"commit: {m['commit']}", f"modified: {m['modified']}"]) + # Truncate if too wide (leave 20px margin on each side) + max_width = gui_app.width - 40 + while rl.measure_text_ex(font, text, 15 * font_scale, 0).x > max_width and len(text) > 20: + text = text[:-4] + "..." + draw_text_box(rl, text, 0, 8, 15, gui_app, font, font_scale, center=True) - if speed > 1: - overlays += [ - f"setpts=PTS/{speed}", - "fps=60", - ] - - ffmpeg_cmd = [ - 'ffmpeg', '-y', - '-video_size', RESOLUTION, - '-framerate', str(FRAMERATE), - '-f', 'x11grab', - '-rtbufsize', '100M', - '-draw_mouse', '0', - '-i', display, - '-c:v', 'libx264', - '-maxrate', f'{bit_rate_kbps}k', - '-bufsize', f'{bit_rate_kbps*2}k', - '-crf', '23', - '-filter:v', ','.join(overlays), - '-preset', 'ultrafast', - '-tune', 'zerolatency', - '-pix_fmt', 'yuv420p', - '-movflags', '+faststart', - '-f', 'mp4', - '-t', str(duration), - out, - ] - - replay_cmd = [REPLAY, '--ecam', '-c', '1', '-s', str(begin_at), '--prefix', prefix] - if data_dir: - replay_cmd.extend(['--data_dir', data_dir]) - if quality == 'low': - replay_cmd.append('--qcam') - replay_cmd.append(route.name.canonical_name) - - ui_cmd = [UI, '-platform', 'xcb'] - xvfb_cmd = ['Xvfb', display, '-terminate', '-screen', '0', f'{RESOLUTION}x{PIXEL_DEPTH}'] - - with OpenpilotPrefix(prefix, shared_download_cache=True): - populate_car_params(lr) - env = os.environ.copy() - env['DISPLAY'] = display - - with managed_proc(xvfb_cmd, env) as xvfb_proc, managed_proc(ui_cmd, env) as ui_proc, managed_proc(replay_cmd, env) as replay_proc: - procs = [xvfb_proc, ui_proc, replay_proc] - logger.info('waiting for replay to begin (loading segments, may take a while)...') - wait_for_frames(procs) - logger.debug(f'letting UI warm up ({SECONDS_TO_WARM}s)...') - time.sleep(SECONDS_TO_WARM) - check_for_failure(procs) - with managed_proc(ffmpeg_cmd, env) as ffmpeg_proc: - procs.append(ffmpeg_proc) - logger.info(f'recording in progress ({duration}s)...') - ffmpeg_proc.wait(duration + PROC_WAIT_SECONDS) - check_for_failure(procs) - logger.info(f'recording complete: {Path(out).resolve()}') + if title: + draw_text_box(rl, title, 0, 60, 32, gui_app, font, font_scale, center=True) + + if show_time: + t = start_time + frame_idx / FRAMERATE + time_text = f"{int(t)//60:02d}:{int(t)%60:02d}" + time_width = int(rl.measure_text_ex(font, time_text, 24 * font_scale, 0).x) + draw_text_box(rl, time_text, gui_app.width - time_width - 45, 45, 24, gui_app, font, font_scale) + + +def clip(route: Route, output: str, start: int, end: int, headless: bool = True, big: bool = False, + title: str | None = None, show_metadata: bool = True, show_time: bool = True, use_qcam: bool = False): + timer, duration = Timer(), end - start + + import pyray as rl + if big: + from openpilot.selfdrive.ui.layouts.main import MainLayout + else: + from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout as MainLayout # type: ignore[assignment] + from openpilot.selfdrive.ui.ui_state import ui_state + from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE + timer.lap("import") + + logger.info(f"Clipping {route.name.canonical_name}, {start}s-{end}s ({duration}s)") + seg_start, seg_end = start // 60, (end - 1) // 60 + 1 + all_chunks = load_logs_parallel(route.log_paths()[seg_start:seg_end], fps=FRAMERATE) + timer.lap("logs") + + frame_start = (start - seg_start * 60) * FRAMERATE + message_chunks = all_chunks[frame_start:frame_start + duration * FRAMERATE] + if not message_chunks: + logger.error("No messages to render") + sys.exit(1) + + metadata = load_route_metadata(route) if show_metadata else None + if headless: + rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) + + with OpenpilotPrefix(shared_download_cache=True): + camera_paths = route.qcamera_paths() if use_qcam else route.camera_paths() + frame_queue = FrameQueue(camera_paths, start, end, fps=FRAMERATE, use_qcam=use_qcam) + + vipc = VisionIpcServer("camerad") + vipc.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 4, frame_queue.frame_w, frame_queue.frame_h) + vipc.start_listener() + + patch_submaster(message_chunks, ui_state) + gui_app.init_window("clip", fps=FRAMERATE) + + main_layout = MainLayout() + main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + font = gui_app.font(FontWeight.NORMAL) + timer.lap("setup") + + frame_idx = 0 + with tqdm.tqdm(total=len(message_chunks), desc="Rendering", unit="frame") as pbar: + for should_render in gui_app.render(): + if frame_idx >= len(message_chunks): + break + _, frame_bytes = frame_queue.get() + vipc.send(VisionStreamType.VISION_STREAM_ROAD, frame_bytes, frame_idx, int(frame_idx * 5e7), int(frame_idx * 5e7)) + ui_state.update() + if should_render: + main_layout.render() + render_overlays(rl, gui_app, font, FONT_SCALE, metadata, title, start, frame_idx, show_metadata, show_time) + frame_idx += 1 + pbar.update(1) + timer.lap("render") + + frame_queue.stop() + gui_app.close() + timer.lap("ffmpeg") + + logger.info(f"Clip saved to: {Path(output).resolve()}") + logger.info(f"Generated {timer.fmt(duration)}") def main(): - p = ArgumentParser(prog='clip.py', description='clip your openpilot route.', epilog='comma.ai') - validate_env(p) - route_group = p.add_mutually_exclusive_group(required=True) - route_group.add_argument('route', nargs='?', type=validate_route, help=f'The route (e.g. {DEMO_ROUTE} or {DEMO_ROUTE}/{DEMO_START}/{DEMO_END})') - route_group.add_argument('--demo', help='use the demo route', action='store_true') - p.add_argument('-d', '--data-dir', help='local directory where route data is stored') - p.add_argument('-e', '--end', help='stop clipping at seconds', type=int) - p.add_argument('-f', '--file-size', help='target file size (Discord/GitHub support max 10MB, default is 9MB)', type=float, default=9.) - p.add_argument('-o', '--output', help='output clip to (.mp4)', type=validate_output_file, default=DEFAULT_OUTPUT) - p.add_argument('-p', '--prefix', help='openpilot prefix', default=f'clip_{randint(100, 99999)}') - p.add_argument('-q', '--quality', help='quality of camera (low = qcam, high = hevc)', choices=['low', 'high'], default='high') - p.add_argument('-x', '--speed', help='record the clip at this speed multiple', type=int, default=1) - p.add_argument('-s', '--start', help='start clipping at seconds', type=int) - p.add_argument('-t', '--title', help='overlay this title on the video (e.g. "Chill driving across the Golden Gate Bridge")', type=validate_title) - args = parse_args(p) - exit_code = 1 - try: - clip( - data_dir=args.data_dir, - quality=args.quality, - prefix=args.prefix, - route=args.route, - out=args.output, - start=args.start, - end=args.end, - speed=args.speed, - target_mb=args.file_size, - title=args.title, - ) - exit_code = 0 - except KeyboardInterrupt as e: - logger.exception('interrupted by user', exc_info=e) - except Exception as e: - logger.exception('encountered error', exc_info=e) - sys.exit(exit_code) - - -if __name__ == '__main__': - logging.basicConfig(level=logging.INFO, format='%(asctime)s %(name)s %(levelname)s\t%(message)s') + logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s\t%(message)s") + args = parse_args() + assert args.big, "Clips doesn't support mici UI yet. TODO: make it work" + + setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start) + clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, not args.windowed, + args.big, args.title, not args.no_metadata, not args.no_time_overlay, args.qcam) + + +if __name__ == "__main__": main() From e76e1e500c07a02a1b534f85238ee1ab0a441d41 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sun, 1 Feb 2026 14:21:00 -0700 Subject: [PATCH 088/685] clips: use AugmentedRoadView instead of MainLayout (#37053) Render only the road view in clips rather than the full main layout, matching the updated UI module structure. --- tools/clip/run.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 324ee666906..5fb693f30a4 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -265,9 +265,9 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, import pyray as rl if big: - from openpilot.selfdrive.ui.layouts.main import MainLayout + from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView else: - from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout as MainLayout # type: ignore[assignment] + from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView # type: ignore[assignment] from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE timer.lap("import") @@ -298,8 +298,8 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, patch_submaster(message_chunks, ui_state) gui_app.init_window("clip", fps=FRAMERATE) - main_layout = MainLayout() - main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + road_view = AugmentedRoadView() + road_view.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) font = gui_app.font(FontWeight.NORMAL) timer.lap("setup") @@ -312,7 +312,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, vipc.send(VisionStreamType.VISION_STREAM_ROAD, frame_bytes, frame_idx, int(frame_idx * 5e7), int(frame_idx * 5e7)) ui_state.update() if should_render: - main_layout.render() + road_view.render() render_overlays(rl, gui_app, font, FONT_SCALE, metadata, title, start, frame_idx, show_metadata, show_time) frame_idx += 1 pbar.update(1) From 0a84b004065edfa2f0efaedad329b3853409c316 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 13:36:55 -0800 Subject: [PATCH 089/685] fix up status for in progress builds --- scripts/ci_results.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/ci_results.py b/scripts/ci_results.py index c3d53f222a2..a133541c69c 100755 --- a/scripts/ci_results.py +++ b/scripts/ci_results.py @@ -143,6 +143,9 @@ def format_markdown(gh_status, gh_run_id, jenkins_status, commit_sha, branch): lines.append(f"| {stage['name']} | {icon} {stage['status'].lower()} |") if stage["status"] == "FAILED": failed_jenkins_stages.append(stage["name"]) + # Show overall build status if still in progress + if jenkins_status["in_progress"]: + lines.append("| (build in progress) | :hourglass: in_progress |") else: icon = ":hourglass:" if jenkins_status["in_progress"] else ( ":white_check_mark:" if jenkins_status["result"] == "SUCCESS" else ":x:") From 7a990b99f7e2a714ee00b7f0e7955dd588f3c88e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 15:07:22 -0800 Subject: [PATCH 090/685] rm future-fstrings package (#37056) --- pyproject.toml | 1 - uv.lock | 11 ----------- 2 files changed, 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9a70f69d268..57fd0b8355b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,6 @@ dependencies = [ # acados deps "casadi >=3.6.6", # 3.12 fixed in 3.6.6 - "future-fstrings", # joystickd "inputs", diff --git a/uv.lock b/uv.lock index b221995b85e..e488d1d78be 100644 --- a/uv.lock +++ b/uv.lock @@ -633,15 +633,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] -[[package]] -name = "future-fstrings" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5d/e2/3874574cce18a2e3608abfe5b4b5b3c9765653c464f5da18df8971cf501d/future_fstrings-1.2.0.tar.gz", hash = "sha256:6cf41cbe97c398ab5a81168ce0dbb8ad95862d3caf23c21e4430627b90844089", size = 5786, upload-time = "2019-06-16T03:04:42.651Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ab/6d/ea1d52e9038558dd37f5d30647eb9f07888c164960a5d4daa5f970c6da25/future_fstrings-1.2.0-py2.py3-none-any.whl", hash = "sha256:90e49598b553d8746c4dc7d9442e0359d038c3039d802c91c0a55505da318c63", size = 6138, upload-time = "2019-06-16T03:04:40.395Z" }, -] - [[package]] name = "ghp-import" version = "2.1.0" @@ -1302,7 +1293,6 @@ dependencies = [ { name = "cffi" }, { name = "crcmod-plus" }, { name = "cython" }, - { name = "future-fstrings" }, { name = "inputs" }, { name = "json-rpc" }, { name = "kaitaistruct" }, @@ -1396,7 +1386,6 @@ requires-dist = [ { name = "cython" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, { name = "dictdiffer", marker = "extra == 'dev'" }, - { name = "future-fstrings" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney", marker = "extra == 'dev'" }, From 422de598984a26225ba729546852a2e5eb000eeb Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 15:24:42 -0800 Subject: [PATCH 091/685] acados: strip future-fstrings declaration (#37057) * Revert "rm future-fstrings package (#37056)" This reverts commit 7a990b99f7e2a714ee00b7f0e7955dd588f3c88e. * Reapply "rm future-fstrings package (#37056)" This reverts commit 8b93f6646eed6863ad67b9bab558d305ecb8b7b4. * strip it * cleanup --- third_party/acados/acados_template/acados_ocp.py | 1 - third_party/acados/acados_template/acados_ocp_solver.py | 1 - .../acados/acados_template/acados_ocp_solver_pyx.pyx | 1 - third_party/acados/acados_template/acados_sim.py | 1 - third_party/acados/acados_template/acados_sim_solver.py | 1 - .../acados/acados_template/acados_sim_solver_common.pxd | 1 - .../acados/acados_template/acados_sim_solver_pyx.pyx | 1 - third_party/acados/acados_template/acados_solver_common.pxd | 1 - third_party/acados/acados_template/builders.py | 1 - third_party/acados/acados_template/gnsf/__init__.py | 0 third_party/acados/acados_template/utils.py | 1 - third_party/acados/build.sh | 6 ++++++ 12 files changed, 6 insertions(+), 10 deletions(-) delete mode 100644 third_party/acados/acados_template/gnsf/__init__.py diff --git a/third_party/acados/acados_template/acados_ocp.py b/third_party/acados/acados_template/acados_ocp.py index ec02822ceb2..d6236e1f6e9 100644 --- a/third_party/acados/acados_template/acados_ocp.py +++ b/third_party/acados/acados_template/acados_ocp.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_ocp_solver.py b/third_party/acados/acados_template/acados_ocp_solver.py index ffc9cf4b0e1..229bdf60398 100644 --- a/third_party/acados/acados_template/acados_ocp_solver.py +++ b/third_party/acados/acados_template/acados_ocp_solver.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx b/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx index acd7f02d0a8..bc03ba086fe 100644 --- a/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx +++ b/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_sim.py b/third_party/acados/acados_template/acados_sim.py index c0d6937a49a..7faa49fb125 100644 --- a/third_party/acados/acados_template/acados_sim.py +++ b/third_party/acados/acados_template/acados_sim.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_sim_solver.py b/third_party/acados/acados_template/acados_sim_solver.py index 612f439eaf7..de5ee107094 100644 --- a/third_party/acados/acados_template/acados_sim_solver.py +++ b/third_party/acados/acados_template/acados_sim_solver.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_sim_solver_common.pxd b/third_party/acados/acados_template/acados_sim_solver_common.pxd index cc6a58efd77..7c20a6d24de 100644 --- a/third_party/acados/acados_template/acados_sim_solver_common.pxd +++ b/third_party/acados/acados_template/acados_sim_solver_common.pxd @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_sim_solver_pyx.pyx b/third_party/acados/acados_template/acados_sim_solver_pyx.pyx index be400addc7d..01964fd7a0b 100644 --- a/third_party/acados/acados_template/acados_sim_solver_pyx.pyx +++ b/third_party/acados/acados_template/acados_sim_solver_pyx.pyx @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/acados_solver_common.pxd b/third_party/acados/acados_template/acados_solver_common.pxd index c6d59d40a50..75d021626f3 100644 --- a/third_party/acados/acados_template/acados_solver_common.pxd +++ b/third_party/acados/acados_template/acados_solver_common.pxd @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/acados_template/builders.py b/third_party/acados/acados_template/builders.py index 6f21bfe8cd2..8acc05b5287 100644 --- a/third_party/acados/acados_template/builders.py +++ b/third_party/acados/acados_template/builders.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright 2019 Gianluca Frison, Dimitris Kouzoupis, Robin Verschueren, # Andrea Zanelli, Niels van Duijkeren, Jonathan Frey, Tommaso Sartor, diff --git a/third_party/acados/acados_template/gnsf/__init__.py b/third_party/acados/acados_template/gnsf/__init__.py deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/third_party/acados/acados_template/utils.py b/third_party/acados/acados_template/utils.py index d6f6c02f84a..f27617fa309 100644 --- a/third_party/acados/acados_template/utils.py +++ b/third_party/acados/acados_template/utils.py @@ -1,4 +1,3 @@ -# -*- coding: future_fstrings -*- # # Copyright (c) The acados authors. # diff --git a/third_party/acados/build.sh b/third_party/acados/build.sh index b45c167b164..2b803ef6b24 100755 --- a/third_party/acados/build.sh +++ b/third_party/acados/build.sh @@ -44,6 +44,12 @@ cp -r $DIR/acados_repo/lib $INSTALL_DIR cp -r $DIR/acados_repo/interfaces/acados_template/acados_template $DIR/ #pip3 install -e $DIR/acados/interfaces/acados_template +# skip macOS - sed is different :/ +if [[ "$OSTYPE" != "darwin"* ]]; then + # strip future_fstrings to avoid having to install the compatibility package + find $DIR/acados_template/ -type f -exec sed -i '/future.fstrings/d' {} + +fi + # build tera cd $DIR/acados_repo/interfaces/acados_template/tera_renderer/ if [[ "$OSTYPE" == "darwin"* ]]; then From 948d42b3e59e073d23e3506dd254a58a0227f4a5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 15:42:42 -0800 Subject: [PATCH 092/685] rm pyopencl package (#37058) rm pyopencl --- .gitignore | 4 + pyproject.toml | 1 - tools/sim/lib/camerad.py | 54 +++++----- tools/sim/rgb_to_nv12.cl | 119 ----------------------- tools/sim/tests/test_metadrive_bridge.py | 1 - uv.lock | 30 ------ 6 files changed, 35 insertions(+), 174 deletions(-) delete mode 100644 tools/sim/rgb_to_nv12.cl diff --git a/.gitignore b/.gitignore index e4992a3d055..e2a30fb70ab 100644 --- a/.gitignore +++ b/.gitignore @@ -95,3 +95,7 @@ Pipfile # Ignore all local history of files .history .ionide + +.claude/ +PLAN.md +TASK.md diff --git a/pyproject.toml b/pyproject.toml index 57fd0b8355b..2239770ac9a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,6 @@ dev = [ "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", - "pyopencl", "pytools>=2025.1.6; platform_machine != 'aarch64'", "pywinctl", "pyprof2calltree", diff --git a/tools/sim/lib/camerad.py b/tools/sim/lib/camerad.py index be4e1a610c3..7634b8524d1 100644 --- a/tools/sim/lib/camerad.py +++ b/tools/sim/lib/camerad.py @@ -1,14 +1,39 @@ import numpy as np -import os -import pyopencl as cl -import pyopencl.array as cl_array from msgq.visionipc import VisionIpcServer, VisionStreamType from cereal import messaging -from openpilot.common.basedir import BASEDIR from openpilot.tools.sim.lib.common import W, H + +def rgb_to_nv12(rgb): + """Convert RGB image to NV12 (YUV420) format using BT.601 coefficients.""" + h, w = rgb.shape[:2] + r = rgb[:, :, 0].astype(np.int32) + g = rgb[:, :, 1].astype(np.int32) + b = rgb[:, :, 2].astype(np.int32) + + # Y plane - BT.601 coefficients (matches original OpenCL kernel) + y = (((b * 13 + g * 65 + r * 33) + 64) >> 7) + 16 + y = np.clip(y, 0, 255).astype(np.uint8) + + # Subsample RGB for UV (2x2 box filter) + r_sub = (r[0::2, 0::2] + r[0::2, 1::2] + r[1::2, 0::2] + r[1::2, 1::2] + 2) >> 2 + g_sub = (g[0::2, 0::2] + g[0::2, 1::2] + g[1::2, 0::2] + g[1::2, 1::2] + 2) >> 2 + b_sub = (b[0::2, 0::2] + b[0::2, 1::2] + b[1::2, 0::2] + b[1::2, 1::2] + 2) >> 2 + + # U and V planes + u = np.clip((b_sub * 56 - g_sub * 37 - r_sub * 19 + 0x8080) >> 8, 0, 255).astype(np.uint8) + v = np.clip((r_sub * 56 - g_sub * 47 - b_sub * 9 + 0x8080) >> 8, 0, 255).astype(np.uint8) + + # Interleave UV for NV12 format + uv = np.empty((h // 2, w), dtype=np.uint8) + uv[:, 0::2] = u + uv[:, 1::2] = v + + return np.concatenate([y.ravel(), uv.ravel()]).tobytes() + + class Camerad: """Simulates the camerad daemon""" def __init__(self, dual_camera): @@ -24,18 +49,6 @@ def __init__(self, dual_camera): self.vipc_server.start_listener() - # set up for pyopencl rgb to yuv conversion - self.ctx = cl.create_some_context() - self.queue = cl.CommandQueue(self.ctx) - cl_arg = f" -DHEIGHT={H} -DWIDTH={W} -DRGB_STRIDE={W * 3} -DUV_WIDTH={W // 2} -DUV_HEIGHT={H // 2} -DRGB_SIZE={W * H} -DCL_DEBUG " - - kernel_fn = os.path.join(BASEDIR, "tools/sim/rgb_to_nv12.cl") - with open(kernel_fn) as f: - prg = cl.Program(self.ctx, f.read()).build(cl_arg) - self.krnl = prg.rgb_to_nv12 - self.Wdiv4 = W // 4 if (W % 4 == 0) else (W + (4 - W % 4)) // 4 - self.Hdiv4 = H // 4 if (H % 4 == 0) else (H + (4 - H % 4)) // 4 - def cam_send_yuv_road(self, yuv): self._send_yuv(yuv, self.frame_road_id, 'roadCameraState', VisionStreamType.VISION_STREAM_ROAD) self.frame_road_id += 1 @@ -44,16 +57,11 @@ def cam_send_yuv_wide_road(self, yuv): self._send_yuv(yuv, self.frame_wide_id, 'wideRoadCameraState', VisionStreamType.VISION_STREAM_WIDE_ROAD) self.frame_wide_id += 1 - # Returns: yuv bytes def rgb_to_yuv(self, rgb): + """Convert RGB to NV12 YUV format.""" assert rgb.shape == (H, W, 3), f"{rgb.shape}" assert rgb.dtype == np.uint8 - - rgb_cl = cl_array.to_device(self.queue, rgb) - yuv_cl = cl_array.empty_like(rgb_cl) - self.krnl(self.queue, (self.Wdiv4, self.Hdiv4), None, rgb_cl.data, yuv_cl.data).wait() - yuv = np.resize(yuv_cl.get(), rgb.size // 2) - return yuv.data.tobytes() + return rgb_to_nv12(rgb) def _send_yuv(self, yuv, frame_id, pub_type, yuv_type): eof = int(frame_id * 0.05 * 1e9) diff --git a/tools/sim/rgb_to_nv12.cl b/tools/sim/rgb_to_nv12.cl deleted file mode 100644 index 54816d5d7d5..00000000000 --- a/tools/sim/rgb_to_nv12.cl +++ /dev/null @@ -1,119 +0,0 @@ -#define RGB_TO_Y(r, g, b) ((((mul24(b, 13) + mul24(g, 65) + mul24(r, 33)) + 64) >> 7) + 16) -#define RGB_TO_U(r, g, b) ((mul24(b, 56) - mul24(g, 37) - mul24(r, 19) + 0x8080) >> 8) -#define RGB_TO_V(r, g, b) ((mul24(r, 56) - mul24(g, 47) - mul24(b, 9) + 0x8080) >> 8) -#define AVERAGE(x, y, z, w) ((convert_ushort(x) + convert_ushort(y) + convert_ushort(z) + convert_ushort(w) + 1) >> 1) - -inline void convert_2_ys(__global uchar * out_yuv, int yi, const uchar8 rgbs1) { - uchar2 yy = (uchar2)( - RGB_TO_Y(rgbs1.s2, rgbs1.s1, rgbs1.s0), - RGB_TO_Y(rgbs1.s5, rgbs1.s4, rgbs1.s3) - ); -#ifdef CL_DEBUG - if(yi >= RGB_SIZE) - printf("Y vector2 overflow, %d > %d\n", yi, RGB_SIZE); -#endif - vstore2(yy, 0, out_yuv + yi); -} - -inline void convert_4_ys(__global uchar * out_yuv, int yi, const uchar8 rgbs1, const uchar8 rgbs3) { - const uchar4 yy = (uchar4)( - RGB_TO_Y(rgbs1.s2, rgbs1.s1, rgbs1.s0), - RGB_TO_Y(rgbs1.s5, rgbs1.s4, rgbs1.s3), - RGB_TO_Y(rgbs3.s0, rgbs1.s7, rgbs1.s6), - RGB_TO_Y(rgbs3.s3, rgbs3.s2, rgbs3.s1) - ); -#ifdef CL_DEBUG - if(yi > RGB_SIZE - 4) - printf("Y vector4 overflow, %d > %d\n", yi, RGB_SIZE - 4); -#endif - vstore4(yy, 0, out_yuv + yi); -} - -inline void convert_uv(__global uchar * out_yuv, int uvi, - const uchar8 rgbs1, const uchar8 rgbs2) { - // U & V: average of 2x2 pixels square - const short ab = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); - const short ag = AVERAGE(rgbs1.s1, rgbs1.s4, rgbs2.s1, rgbs2.s4); - const short ar = AVERAGE(rgbs1.s2, rgbs1.s5, rgbs2.s2, rgbs2.s5); -#ifdef CL_DEBUG - if(uvi >= RGB_SIZE + RGB_SIZE / 2) - printf("UV overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2); -#endif - out_yuv[uvi] = RGB_TO_U(ar, ag, ab); - out_yuv[uvi+1] = RGB_TO_V(ar, ag, ab); -} - -inline void convert_2_uvs(__global uchar * out_yuv, int uvi, - const uchar8 rgbs1, const uchar8 rgbs2, const uchar8 rgbs3, const uchar8 rgbs4) { - // U & V: average of 2x2 pixels square - const short ab1 = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); - const short ag1 = AVERAGE(rgbs1.s1, rgbs1.s4, rgbs2.s1, rgbs2.s4); - const short ar1 = AVERAGE(rgbs1.s2, rgbs1.s5, rgbs2.s2, rgbs2.s5); - const short ab2 = AVERAGE(rgbs1.s6, rgbs3.s1, rgbs2.s6, rgbs4.s1); - const short ag2 = AVERAGE(rgbs1.s7, rgbs3.s2, rgbs2.s7, rgbs4.s2); - const short ar2 = AVERAGE(rgbs3.s0, rgbs3.s3, rgbs4.s0, rgbs4.s3); - uchar4 uv = (uchar4)( - RGB_TO_U(ar1, ag1, ab1), - RGB_TO_V(ar1, ag1, ab1), - RGB_TO_U(ar2, ag2, ab2), - RGB_TO_V(ar2, ag2, ab2) - ); -#ifdef CL_DEBUG1 - if(uvi > RGB_SIZE + RGB_SIZE / 2 - 4) - printf("UV2 overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2 - 2); -#endif - vstore4(uv, 0, out_yuv + uvi); -} - -__kernel void rgb_to_nv12(__global uchar const * const rgb, - __global uchar * out_yuv) -{ - const int dx = get_global_id(0); - const int dy = get_global_id(1); - const int col = mul24(dx, 4); // Current column in rgb image - const int row = mul24(dy, 4); // Current row in rgb image - const int bgri_start = mad24(row, RGB_STRIDE, mul24(col, 3)); // Start offset of rgb data being converted - const int yi_start = mad24(row, WIDTH, col); // Start offset in the target yuv buffer - int uvi = mad24(row / 2, WIDTH, RGB_SIZE + col); - int num_col = min(WIDTH - col, 4); - int num_row = min(HEIGHT - row, 4); - if(num_row == 4) { - const uchar8 rgbs0_0 = vload8(0, rgb + bgri_start); - const uchar8 rgbs0_1 = vload8(0, rgb + bgri_start + 8); - const uchar8 rgbs1_0 = vload8(0, rgb + bgri_start + RGB_STRIDE); - const uchar8 rgbs1_1 = vload8(0, rgb + bgri_start + RGB_STRIDE + 8); - const uchar8 rgbs2_0 = vload8(0, rgb + bgri_start + RGB_STRIDE * 2); - const uchar8 rgbs2_1 = vload8(0, rgb + bgri_start + RGB_STRIDE * 2 + 8); - const uchar8 rgbs3_0 = vload8(0, rgb + bgri_start + RGB_STRIDE * 3); - const uchar8 rgbs3_1 = vload8(0, rgb + bgri_start + RGB_STRIDE * 3 + 8); - if(num_col == 4) { - convert_4_ys(out_yuv, yi_start, rgbs0_0, rgbs0_1); - convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1); - convert_4_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0, rgbs2_1); - convert_4_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0, rgbs3_1); - convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); - convert_2_uvs(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0, rgbs2_1, rgbs3_1); - } else if(num_col == 2) { - convert_2_ys(out_yuv, yi_start, rgbs0_0); - convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0); - convert_2_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0); - convert_2_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0); - convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0); - convert_uv(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0); - } - } else { - const uchar8 rgbs0_0 = vload8(0, rgb + bgri_start); - const uchar8 rgbs0_1 = vload8(0, rgb + bgri_start + 8); - const uchar8 rgbs1_0 = vload8(0, rgb + bgri_start + RGB_STRIDE); - const uchar8 rgbs1_1 = vload8(0, rgb + bgri_start + RGB_STRIDE + 8); - if(num_col == 4) { - convert_4_ys(out_yuv, yi_start, rgbs0_0, rgbs0_1); - convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1); - convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); - } else if(num_col == 2) { - convert_2_ys(out_yuv, yi_start, rgbs0_0); - convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0); - convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0); - } - } -} diff --git a/tools/sim/tests/test_metadrive_bridge.py b/tools/sim/tests/test_metadrive_bridge.py index 04ce5d584f9..9be640d736e 100644 --- a/tools/sim/tests/test_metadrive_bridge.py +++ b/tools/sim/tests/test_metadrive_bridge.py @@ -8,7 +8,6 @@ from openpilot.tools.sim.tests.test_sim_bridge import TestSimBridgeBase @pytest.mark.slow -@pytest.mark.filterwarnings("ignore::pyopencl.CompilerWarning") # Unimportant warning of non-empty compile log class TestMetaDriveBridge(TestSimBridgeBase): @pytest.fixture(autouse=True) def setup_create_bridge(self, test_duration): diff --git a/uv.lock b/uv.lock index e488d1d78be..da387a39062 100644 --- a/uv.lock +++ b/uv.lock @@ -1336,7 +1336,6 @@ dev = [ { name = "opencv-python-headless" }, { name = "parameterized" }, { name = "pyautogui" }, - { name = "pyopencl" }, { name = "pyprof2calltree" }, { name = "pytools", marker = "platform_machine != 'aarch64'" }, { name = "pywinctl" }, @@ -1409,7 +1408,6 @@ requires-dist = [ { name = "pycapnp", specifier = "==2.1.0" }, { name = "pycryptodome" }, { name = "pyjwt" }, - { name = "pyopencl", marker = "extra == 'dev'" }, { name = "pyopenssl", specifier = "<24.3.0" }, { name = "pyprof2calltree", marker = "extra == 'dev'" }, { name = "pyserial" }, @@ -4247,34 +4245,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, ] -[[package]] -name = "pyopencl" -version = "2026.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "platformdirs" }, - { name = "pytools" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d8/81/fd8a2a695916a82e861bcf17b5b8fd9f81e12c9e5931f9ba536678d7b43a/pyopencl-2026.1.2.tar.gz", hash = "sha256:4397dd0b4cbb8b55f3e09bf87114a2465574506b363890b805b860c348b61970", size = 445132, upload-time = "2026-01-16T22:52:24.765Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/88/abf34e31d572c59203774a66cd81c1e3b3d60b911241483675151149c6f1/pyopencl-2026.1.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:8052e8b402b3ed33ee0807d87d4734f66f67dbafbfb3f5a8b81e478e4d417372", size = 437029, upload-time = "2026-01-16T22:51:30.953Z" }, - { url = "https://files.pythonhosted.org/packages/5c/3d/2dd2d8bbf05a190681582b40fc1ee55b210d00ccebcbb416c62b9f9c81a1/pyopencl-2026.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5e03681c3fe22d5185b16a727d96783e3787e0b65e7a29e4afe01ae0cb4e802", size = 429031, upload-time = "2026-01-16T22:51:32.674Z" }, - { url = "https://files.pythonhosted.org/packages/41/16/e554b3bd20be2e858cfb6683ee6549aeebbe5f769e5b95f561f79340ab20/pyopencl-2026.1.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c8c209d517d1421b17d20b80589a2c39e09ea33350f0367314e1caeed3bc741", size = 689596, upload-time = "2026-01-16T22:51:33.913Z" }, - { url = "https://files.pythonhosted.org/packages/22/a8/1df41cf6c7b25b3bfda14aa0183c6a90eaf849528ba27753eaa25fb26e20/pyopencl-2026.1.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e64e2e34bcfad426bd24b71fdb6b02aa5cb02475147742fe07ef93e81866fc7e", size = 736427, upload-time = "2026-01-16T22:51:36.595Z" }, - { url = "https://files.pythonhosted.org/packages/fc/3d/177b6a675691f7b6f708faef33f981e72fbc4bfed2b1dfa94dc70d0e8a25/pyopencl-2026.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65b151c56b936481d6b6050c2b9bc520840e1402be78c282ba5c01921c25477d", size = 1163888, upload-time = "2026-01-16T22:51:37.973Z" }, - { url = "https://files.pythonhosted.org/packages/e9/fa/5905571d9fa48827c0427a3e664c0213dd045940d581b3b739d83df9c0f6/pyopencl-2026.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cc40003446037f391ca0970694efb0627e2870fabb20ee21be75bc445a39d8f4", size = 1228235, upload-time = "2026-01-16T22:51:39.786Z" }, - { url = "https://files.pythonhosted.org/packages/1e/3d/538c675d078b91680d8d82962110d0c9fd42e1584763d515d6e2e82d8c57/pyopencl-2026.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:b6a8e109ade7db60e8b1beb48df8f080941d0cd77fb2c225ad509c80cdef603e", size = 474753, upload-time = "2026-01-16T22:51:41.771Z" }, - { url = "https://files.pythonhosted.org/packages/cd/34/1497070e44d1689ddbd01d24a2265910e84ebc53457a489b9d2b6e1ac675/pyopencl-2026.1.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:7d88e59901bfe1f9296fd89acd9968f008dc7cfee7995f8cd09c3f1a77119aa6", size = 438145, upload-time = "2026-01-16T22:51:43.658Z" }, - { url = "https://files.pythonhosted.org/packages/5b/a3/71d6af8741b52d3bef443518c1ccfda003adcfa9cc1d0df83dac7005d08c/pyopencl-2026.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3f96a3bff8a09d2fa924e7c33dafac6ea3ef7ec70e746d6d8e17ce2d959a6836", size = 428820, upload-time = "2026-01-16T22:51:45.326Z" }, - { url = "https://files.pythonhosted.org/packages/db/ea/c8dbabeceac9cad3dbb368e08e0aa208cc6c6251c5134cc25eb15da03639/pyopencl-2026.1.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d4e8e8215ec4fdee4b235b61977cdb1c4f041b487bdcf357be799f45b423d61", size = 685478, upload-time = "2026-01-16T22:51:46.545Z" }, - { url = "https://files.pythonhosted.org/packages/64/c7/5854ef7471dfee195bcef6348a107525ca4d1b73c15240e6444d490f9920/pyopencl-2026.1.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0052a8ccbd282d8ab196705e31f4c3ab344113ea5d5c3ddaeede00cdcab068b", size = 734017, upload-time = "2026-01-16T22:51:48.277Z" }, - { url = "https://files.pythonhosted.org/packages/3d/79/42d4eec282ed299b38d8136d05545113ec8771a1bd6b10bb4ba83ae1236c/pyopencl-2026.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e43da12a376e9283407c2820b24cceeaa129b042ac710947cf8e07b13e294689", size = 1159871, upload-time = "2026-01-16T22:51:49.569Z" }, - { url = "https://files.pythonhosted.org/packages/a0/9a/fdc5d3bed0440d6206109e051008aa0a54ca131d64314bbd42177b8f0763/pyopencl-2026.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1b14b2cf11dec9e0b75cbd14223d1b3c93950fc3e2f7a306b54fa1b17a2cae0f", size = 1225288, upload-time = "2026-01-16T22:51:51.125Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e3/358c19180e0dab5c7dd1fcacc569e6a7ab02a7fddcb9c954f393ceddb2fa/pyopencl-2026.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:d02d7ecabc8d34590dccffe12346689adc5a1ceb07df5acc4ea6c4db8aa28277", size = 474876, upload-time = "2026-01-16T22:51:52.912Z" }, -] - [[package]] name = "pyopenssl" version = "24.2.1" From 5da6bf9e036aa69994bb462fa972648e8ac33255 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 15:46:40 -0800 Subject: [PATCH 093/685] rm pytools package (#37059) --- pyproject.toml | 1 - uv.lock | 36 ------------------------------------ 2 files changed, 37 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2239770ac9a..76b02d0c284 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,6 @@ dev = [ "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", - "pytools>=2025.1.6; platform_machine != 'aarch64'", "pywinctl", "pyprof2calltree", "tabulate", diff --git a/uv.lock b/uv.lock index da387a39062..85f12bb0556 100644 --- a/uv.lock +++ b/uv.lock @@ -1337,7 +1337,6 @@ dev = [ { name = "parameterized" }, { name = "pyautogui" }, { name = "pyprof2calltree" }, - { name = "pytools", marker = "platform_machine != 'aarch64'" }, { name = "pywinctl" }, { name = "tabulate" }, { name = "types-requests" }, @@ -1420,7 +1419,6 @@ requires-dist = [ { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-timeout", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, - { name = "pytools", marker = "platform_machine != 'aarch64' and extra == 'dev'", specifier = ">=2025.1.6" }, { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, @@ -4446,20 +4444,6 @@ version = "0.15" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ef/c6/2c5999de3bb1533521f1101e8fe56fd9c266732f4d48011c7c69b29d12ae/python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8", size = 132828, upload-time = "2014-05-31T12:28:59.603Z" } -[[package]] -name = "pytools" -version = "2025.2.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "platformdirs" }, - { name = "siphash24" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c3/7b/f885a57e61ded45b5b10ca60f0b7575c9fb9a282e7513d0e23a33ee647e1/pytools-2025.2.5.tar.gz", hash = "sha256:a7f5350644d46d98ee9c7e67b4b41693308aa0f5e9b188d8f0694b27dc94e3a2", size = 85594, upload-time = "2025-10-07T15:53:30.49Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/84/c42c29ca4bff35baa286df70b0097e0b1c88fd57e8e6bdb09cb161a6f3c1/pytools-2025.2.5-py3-none-any.whl", hash = "sha256:42e93751ec425781e103bbcd769ba35ecbacd43339c2905401608f2fdc30cf19", size = 98811, upload-time = "2025-10-07T15:53:29.089Z" }, -] - [[package]] name = "pytweening" version = "1.2.0" @@ -4774,26 +4758,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, ] -[[package]] -name = "siphash24" -version = "1.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/67/a2/e049b6fccf7a94bd1b2f68b3059a7d6a7aea86a808cac80cb9ae71ab6254/siphash24-1.8.tar.gz", hash = "sha256:aa932f0af4a7335caef772fdaf73a433a32580405c41eb17ff24077944b0aa97", size = 19946, upload-time = "2025-09-02T20:42:04.856Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/23/f53f5bd8866c6ea3abe434c9f208e76ea027210d8b75cd0e0dc849661c7a/siphash24-1.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4662ac616bce4d3c9d6003a0d398e56f8be408fc53a166b79fad08d4f34268e", size = 76930, upload-time = "2025-09-02T20:41:00.869Z" }, - { url = "https://files.pythonhosted.org/packages/0b/25/aebf246904424a06e7ffb7a40cfa9ea9e590ea0fac82e182e0f5d1f1d7ef/siphash24-1.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:53d6bed0951a99c6d2891fa6f8acfd5ca80c3e96c60bcee99f6fa01a04773b1c", size = 74315, upload-time = "2025-09-02T20:41:02.38Z" }, - { url = "https://files.pythonhosted.org/packages/59/3f/7010407c3416ef052d46550d54afb2581fb247018fc6500af8c66669eff2/siphash24-1.8-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d114c03648630e9e07dac2fe95442404e4607adca91640d274ece1a4fa71123e", size = 99756, upload-time = "2025-09-02T20:41:03.902Z" }, - { url = "https://files.pythonhosted.org/packages/d4/9f/09c734833e69badd7e3faed806b4372bd6564ae0946bd250d5239885914f/siphash24-1.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:88c1a55ff82b127c5d3b96927a430d8859e6a98846a5b979833ac790682dd91b", size = 104044, upload-time = "2025-09-02T20:41:05.505Z" }, - { url = "https://files.pythonhosted.org/packages/24/30/56a26d9141a34433da221f732599e2b23d2d70a966c249a9f00feb9a2915/siphash24-1.8-cp311-cp311-win32.whl", hash = "sha256:9430255e6a1313470f52c07c4a4643c451a5b2853f6d4008e4dda05cafb6ce7c", size = 62196, upload-time = "2025-09-02T20:41:07.299Z" }, - { url = "https://files.pythonhosted.org/packages/47/b2/11b0ae63fd374652544e1b12f72ba2cc3fe6c93c1483bd8ff6935b0a8a4b/siphash24-1.8-cp311-cp311-win_amd64.whl", hash = "sha256:1e4b37e4ef0b4496169adce2a58b6c3f230b5852dfa5f7ad0b2d664596409e47", size = 77162, upload-time = "2025-09-02T20:41:08.878Z" }, - { url = "https://files.pythonhosted.org/packages/7f/82/ce3545ce8052ac7ca104b183415a27ec3335e5ed51978fdd7b433f3cfe5b/siphash24-1.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:df5ed437c6e6cc96196b38728e57cd30b0427df45223475a90e173f5015ef5ba", size = 78136, upload-time = "2025-09-02T20:41:10.083Z" }, - { url = "https://files.pythonhosted.org/packages/15/88/896c3b91bc9deb78c415448b1db67343917f35971a9e23a5967a9d323b8a/siphash24-1.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4ef78abdf811325c7089a35504df339c48c0007d4af428a044431d329721e56", size = 74588, upload-time = "2025-09-02T20:41:11.251Z" }, - { url = "https://files.pythonhosted.org/packages/12/fd/8dad3f5601db485ba862e1c1f91a5d77fb563650856a6708e9acb40ee53c/siphash24-1.8-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:065eff55c4fefb3a29fd26afb2c072abf7f668ffd53b91d41f92a1c485fcbe5c", size = 98655, upload-time = "2025-09-02T20:41:12.45Z" }, - { url = "https://files.pythonhosted.org/packages/e3/cc/e0c352624c1f2faad270aeb5cce6e173977ef66b9b5e918aa6f32af896bf/siphash24-1.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ac6fa84ebfd47677262aa0bcb0f5a70f796f5fc5704b287ee1b65a3bd4fb7a5d", size = 103217, upload-time = "2025-09-02T20:41:13.746Z" }, - { url = "https://files.pythonhosted.org/packages/5b/f6/0b1675bea4d40affcae642d9c7337702a4138b93c544230280712403e968/siphash24-1.8-cp312-cp312-win32.whl", hash = "sha256:6582f73615552ca055e51e03cb02a28e570a641a7f500222c86c2d811b5037eb", size = 63114, upload-time = "2025-09-02T20:41:14.972Z" }, - { url = "https://files.pythonhosted.org/packages/3d/39/afefef85d72ed8b5cf1aa9283f712e3cd43c9682fabbc809dec54baa8452/siphash24-1.8-cp312-cp312-win_amd64.whl", hash = "sha256:44ea6d794a7cbe184e1e1da2df81c5ebb672ab3867935c3e87c08bb0c2fa4879", size = 76232, upload-time = "2025-09-02T20:41:16.112Z" }, -] - [[package]] name = "six" version = "1.17.0" From 35241a5fb871623b69f793946188b41964d82196 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 16:03:18 -0800 Subject: [PATCH 094/685] cleanup pyproject (#37060) * cleanup pyproject * lil more * fix warning --- pyproject.toml | 26 +++++-------------- tools/plotjuggler/juggle.py | 2 +- uv.lock | 51 ++----------------------------------- 3 files changed, 9 insertions(+), 70 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 76b02d0c284..bba80ee8dbd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ license = {text = "MIT License"} version = "0.1.0" description = "an open source driver assistance system" authors = [ - {name ="Vehicle Researcher", email="user@comma.ai"} + {name = "Vehicle Researcher", email="user@comma.ai"} ] dependencies = [ @@ -74,6 +74,7 @@ dependencies = [ "raylib > 5.5.0.3", "qrcode", "mapbox-earcut", + "jeepney", ] [project.optional-dependencies] @@ -93,7 +94,6 @@ testing = [ # https://github.com/pytest-dev/pytest-xdist/pull/1229 "pytest-xdist @ git+https://github.com/sshane/pytest-xdist@2b4372bd62699fb412c4fe2f95bf9f01bd2018da", "pytest-timeout", - "pytest-randomly", "pytest-asyncio", "pytest-mock", "pytest-repeat", @@ -107,16 +107,12 @@ dev = [ "azure-identity", "azure-storage-blob", "dictdiffer", - "jeepney", "matplotlib", "opencv-python-headless", "parameterized >=0.8, <0.9", "pyautogui", "pywinctl", - "pyprof2calltree", "tabulate", - "types-requests", - "types-tabulate", ] tools = [ @@ -153,19 +149,9 @@ markers = [ testpaths = [ "common", "selfdrive", - "system/manager", - "system/updated", - "system/athena", - "system/camerad", - "system/hardware", - "system/loggerd", - "system/tests", - "system/ubloxd", - "system/webrtc", - "tools/lib/tests", - "tools/replay", - "tools/cabana", - "cereal/messaging/tests", + "system", + "tools", + "cereal", ] [tool.codespell] @@ -175,7 +161,7 @@ ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,w builtin = "clear,rare,informal,code,names,en-GB_to_en-US" skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" -# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml +# https://docs.astral.sh/ruff/configuration/#using-pyprojecttoml [tool.ruff] indent-width = 2 lint.select = [ diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 34f33d1959b..142e640504d 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -47,7 +47,7 @@ def install(): tmpf.write(chunk) with tarfile.open(tmp.name) as tar: - tar.extractall(path=INSTALL_DIR) + tar.extractall(path=INSTALL_DIR, filter="data") def get_plotjuggler_version(): diff --git a/uv.lock b/uv.lock index 85f12bb0556..3e3522eb393 100644 --- a/uv.lock +++ b/uv.lock @@ -1294,6 +1294,7 @@ dependencies = [ { name = "crcmod-plus" }, { name = "cython" }, { name = "inputs" }, + { name = "jeepney" }, { name = "json-rpc" }, { name = "kaitaistruct" }, { name = "libusb1" }, @@ -1331,16 +1332,12 @@ dev = [ { name = "azure-identity" }, { name = "azure-storage-blob" }, { name = "dictdiffer" }, - { name = "jeepney" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, { name = "parameterized" }, { name = "pyautogui" }, - { name = "pyprof2calltree" }, { name = "pywinctl" }, { name = "tabulate" }, - { name = "types-requests" }, - { name = "types-tabulate" }, ] docs = [ { name = "jinja2" }, @@ -1356,7 +1353,6 @@ testing = [ { name = "pytest-asyncio" }, { name = "pytest-cpp" }, { name = "pytest-mock" }, - { name = "pytest-randomly" }, { name = "pytest-repeat" }, { name = "pytest-subtests" }, { name = "pytest-timeout" }, @@ -1386,7 +1382,7 @@ requires-dist = [ { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, - { name = "jeepney", marker = "extra == 'dev'" }, + { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, { name = "kaitaistruct" }, @@ -1408,13 +1404,11 @@ requires-dist = [ { name = "pycryptodome" }, { name = "pyjwt" }, { name = "pyopenssl", specifier = "<24.3.0" }, - { name = "pyprof2calltree", marker = "extra == 'dev'" }, { name = "pyserial" }, { name = "pytest", marker = "extra == 'testing'" }, { name = "pytest-asyncio", marker = "extra == 'testing'" }, { name = "pytest-cpp", marker = "extra == 'testing'" }, { name = "pytest-mock", marker = "extra == 'testing'" }, - { name = "pytest-randomly", marker = "extra == 'testing'" }, { name = "pytest-repeat", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-timeout", marker = "extra == 'testing'" }, @@ -1436,8 +1430,6 @@ requires-dist = [ { name = "tabulate", marker = "extra == 'dev'" }, { name = "tqdm" }, { name = "ty", marker = "extra == 'testing'" }, - { name = "types-requests", marker = "extra == 'dev'" }, - { name = "types-tabulate", marker = "extra == 'dev'" }, { name = "websocket-client" }, { name = "xattr" }, { name = "zstandard" }, @@ -4273,12 +4265,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, ] -[[package]] -name = "pyprof2calltree" -version = "1.4.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/2a/e9a76261183b4b5e059a6625d7aae0bcb0a77622bc767d4497148ce2e218/pyprof2calltree-1.4.5.tar.gz", hash = "sha256:a635672ff31677486350b2be9a823ef92f740e6354a6aeda8fa4a8a3768e8f2f", size = 10080, upload-time = "2020-04-19T10:39:09.819Z" } - [[package]] name = "pyrect" version = "0.2.0" @@ -4356,18 +4342,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] -[[package]] -name = "pytest-randomly" -version = "4.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/1d/258a4bf1109258c00c35043f40433be5c16647387b6e7cd5582d638c116b/pytest_randomly-4.0.1.tar.gz", hash = "sha256:174e57bb12ac2c26f3578188490bd333f0e80620c3f47340158a86eca0593cd8", size = 14130, upload-time = "2025-09-12T15:23:00.085Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/3e/a4a9227807b56869790aad3e24472a554b585974fe7e551ea350f50897ae/pytest_randomly-4.0.1-py3-none-any.whl", hash = "sha256:e0dfad2fd4f35e07beff1e47c17fbafcf98f9bf4531fd369d9260e2f858bfcb7", size = 8304, upload-time = "2025-09-12T15:22:58.946Z" }, -] - [[package]] name = "pytest-repeat" version = "0.9.4" @@ -4864,27 +4838,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/c2/05fdd64ac003a560d4fbd1faa7d9a31d75df8f901675e5bed1ee2ceeff87/ty-0.0.13-py3-none-win_arm64.whl", hash = "sha256:1c9630333497c77bb9bcabba42971b96ee1f36c601dd3dcac66b4134f9fa38f0", size = 9808316, upload-time = "2026-01-21T13:20:54.053Z" }, ] -[[package]] -name = "types-requests" -version = "2.32.4.20260107" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/f3/a0663907082280664d745929205a89d41dffb29e89a50f753af7d57d0a96/types_requests-2.32.4.20260107.tar.gz", hash = "sha256:018a11ac158f801bfa84857ddec1650750e393df8a004a8a9ae2a9bec6fcb24f", size = 23165, upload-time = "2026-01-07T03:20:54.091Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1c/12/709ea261f2bf91ef0a26a9eed20f2623227a8ed85610c1e54c5805692ecb/types_requests-2.32.4.20260107-py3-none-any.whl", hash = "sha256:b703fe72f8ce5b31ef031264fe9395cac8f46a04661a79f7ed31a80fb308730d", size = 20676, upload-time = "2026-01-07T03:20:52.929Z" }, -] - -[[package]] -name = "types-tabulate" -version = "0.9.0.20241207" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3f/43/16030404a327e4ff8c692f2273854019ed36718667b2993609dc37d14dd4/types_tabulate-0.9.0.20241207.tar.gz", hash = "sha256:ac1ac174750c0a385dfd248edc6279fa328aaf4ea317915ab879a2ec47833230", size = 8195, upload-time = "2024-12-07T02:54:42.554Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/86/a9ebfd509cbe74471106dffed320e208c72537f9aeb0a55eaa6b1b5e4d17/types_tabulate-0.9.0.20241207-py3-none-any.whl", hash = "sha256:b8dad1343c2a8ba5861c5441370c3e35908edd234ff036d4298708a1d4cf8a85", size = 8307, upload-time = "2024-12-07T02:54:41.031Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0" From b03e7821d46d2876c1ddd5995bad24b9f59ca76e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 17:26:58 -0800 Subject: [PATCH 095/685] replace smbus2 package with minimal implementation (#37061) * replace smbus2 package with minimal implementation * cleanup * fix up --- common/i2c.py | 81 ++++++++++++++++++++++++++++ pyproject.toml | 3 -- system/hardware/tici/amplifier.py | 3 +- system/sensord/sensors/i2c_sensor.py | 5 +- uv.lock | 11 ---- 5 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 common/i2c.py diff --git a/common/i2c.py b/common/i2c.py new file mode 100644 index 00000000000..1dfaa659ad3 --- /dev/null +++ b/common/i2c.py @@ -0,0 +1,81 @@ +import os +import fcntl +import ctypes + +# I2C constants from /usr/include/linux/i2c-dev.h +I2C_SLAVE = 0x0703 +I2C_SLAVE_FORCE = 0x0706 +I2C_SMBUS = 0x0720 + +# SMBus transfer types +I2C_SMBUS_READ = 1 +I2C_SMBUS_WRITE = 0 +I2C_SMBUS_BYTE_DATA = 2 +I2C_SMBUS_I2C_BLOCK_DATA = 8 + +I2C_SMBUS_BLOCK_MAX = 32 + + +class _I2cSmbusData(ctypes.Union): + _fields_ = [ + ("byte", ctypes.c_uint8), + ("word", ctypes.c_uint16), + ("block", ctypes.c_uint8 * (I2C_SMBUS_BLOCK_MAX + 2)), + ] + + +class _I2cSmbusIoctlData(ctypes.Structure): + _fields_ = [ + ("read_write", ctypes.c_uint8), + ("command", ctypes.c_uint8), + ("size", ctypes.c_uint32), + ("data", ctypes.POINTER(_I2cSmbusData)), + ] + + +class SMBus: + def __init__(self, bus: int): + self._fd = os.open(f'/dev/i2c-{bus}', os.O_RDWR) + + def __enter__(self) -> 'SMBus': + return self + + def __exit__(self, *args) -> None: + self.close() + + def close(self) -> None: + if hasattr(self, '_fd') and self._fd >= 0: + os.close(self._fd) + self._fd = -1 + + def _set_address(self, addr: int, force: bool = False) -> None: + ioctl_arg = I2C_SLAVE_FORCE if force else I2C_SLAVE + fcntl.ioctl(self._fd, ioctl_arg, addr) + + def _smbus_access(self, read_write: int, command: int, size: int, data: _I2cSmbusData) -> None: + ioctl_data = _I2cSmbusIoctlData(read_write, command, size, ctypes.pointer(data)) + fcntl.ioctl(self._fd, I2C_SMBUS, ioctl_data) + + def read_byte_data(self, addr: int, register: int, force: bool = False) -> int: + self._set_address(addr, force) + data = _I2cSmbusData() + self._smbus_access(I2C_SMBUS_READ, register, I2C_SMBUS_BYTE_DATA, data) + return int(data.byte) + + def write_byte_data(self, addr: int, register: int, value: int, force: bool = False) -> None: + self._set_address(addr, force) + data = _I2cSmbusData() + data.byte = value & 0xFF + self._smbus_access(I2C_SMBUS_WRITE, register, I2C_SMBUS_BYTE_DATA, data) + + def read_i2c_block_data(self, addr: int, register: int, length: int, force: bool = False) -> list[int]: + self._set_address(addr, force) + if not (0 <= length <= I2C_SMBUS_BLOCK_MAX): + raise ValueError(f"length must be 0..{I2C_SMBUS_BLOCK_MAX}") + + data = _I2cSmbusData() + data.block[0] = length + self._smbus_access(I2C_SMBUS_READ, register, I2C_SMBUS_I2C_BLOCK_DATA, data) + read_len = int(data.block[0]) or length + read_len = min(read_len, length) + return [int(b) for b in data.block[1 : read_len + 1]] diff --git a/pyproject.toml b/pyproject.toml index bba80ee8dbd..19491ba5327 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,9 +17,6 @@ dependencies = [ "crcmod-plus", # cars + qcomgpsd "tqdm", # cars (fw_versions.py) on start + many one-off uses - # hardwared - "smbus2", # configuring amp - # core "cffi", "scons", diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py index d714837bb3e..09436e6ff46 100755 --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 import time -from smbus2 import SMBus from collections import namedtuple +from openpilot.common.i2c import SMBus + # https://datasheets.maximintegrated.com/en/ds/MAX98089.pdf AmpConfig = namedtuple('AmpConfig', ['name', 'value', 'register', 'offset', 'mask']) diff --git a/system/sensord/sensors/i2c_sensor.py b/system/sensord/sensors/i2c_sensor.py index 336ebb1fd39..57edcc52d90 100644 --- a/system/sensord/sensors/i2c_sensor.py +++ b/system/sensord/sensors/i2c_sensor.py @@ -1,9 +1,10 @@ import time -import smbus2 import ctypes from collections.abc import Iterable from cereal import log +from openpilot.common.i2c import SMBus + class Sensor: class SensorException(Exception): @@ -13,7 +14,7 @@ class DataNotReady(SensorException): pass def __init__(self, bus: int) -> None: - self.bus = smbus2.SMBus(bus) + self.bus = SMBus(bus) self.source = log.SensorEventData.SensorSource.velodyne # unknown self.start_ts = 0. diff --git a/uv.lock b/uv.lock index 3e3522eb393..2c5f32ec7b9 100644 --- a/uv.lock +++ b/uv.lock @@ -1316,7 +1316,6 @@ dependencies = [ { name = "sentry-sdk" }, { name = "setproctitle" }, { name = "setuptools" }, - { name = "smbus2" }, { name = "sounddevice" }, { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, @@ -1423,7 +1422,6 @@ requires-dist = [ { name = "sentry-sdk" }, { name = "setproctitle" }, { name = "setuptools" }, - { name = "smbus2" }, { name = "sounddevice" }, { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, @@ -4741,15 +4739,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, ] -[[package]] -name = "smbus2" -version = "0.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/36/afafd43770caae69f04e21402552a8f94a072def46a002fab9357f4852ce/smbus2-0.6.0.tar.gz", hash = "sha256:9b5ff1e998e114730f9dfe0c4babbef06c92468cfb61eaa684e30f225661b95b", size = 17403, upload-time = "2025-12-20T09:02:52.017Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/cf/2e1d6805da6f9c9b3a4358076ff2e072d828ba7fed124edc1b729e210c55/smbus2-0.6.0-py2.py3-none-any.whl", hash = "sha256:03d83d2a9a4afc5ddca0698ccabf101cb3de52bc5aefd7b76778ffb27ff654e0", size = 11849, upload-time = "2025-12-20T09:02:51.219Z" }, -] - [[package]] name = "sortedcontainers" version = "2.4.0" From 5fc4c2b25cce04ce6229da76893a2b46d010ccde Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Feb 2026 20:00:55 -0800 Subject: [PATCH 096/685] ubloxd: remove kaitai (#37055) * rm kaitai * lil less * bs * lil less * lil less --- SConstruct | 2 - pyproject.toml | 3 - system/ubloxd/SConscript | 11 -- system/ubloxd/binary_struct.py | 280 +++++++++++++++++++++++++++ system/ubloxd/generated/glonass.py | 247 ------------------------ system/ubloxd/generated/gps.py | 193 ------------------- system/ubloxd/generated/ubx.py | 273 --------------------------- system/ubloxd/glonass.ksy | 176 ----------------- system/ubloxd/glonass.py | 156 +++++++++++++++ system/ubloxd/gps.ksy | 189 ------------------- system/ubloxd/gps.py | 116 ++++++++++++ system/ubloxd/ubloxd.py | 33 +++- system/ubloxd/ubx.ksy | 293 ----------------------------- system/ubloxd/ubx.py | 180 ++++++++++++++++++ uv.lock | 11 -- 15 files changed, 756 insertions(+), 1407 deletions(-) delete mode 100644 system/ubloxd/SConscript create mode 100644 system/ubloxd/binary_struct.py delete mode 100644 system/ubloxd/generated/glonass.py delete mode 100644 system/ubloxd/generated/gps.py delete mode 100644 system/ubloxd/generated/ubx.py delete mode 100644 system/ubloxd/glonass.ksy create mode 100644 system/ubloxd/glonass.py delete mode 100644 system/ubloxd/gps.ksy create mode 100644 system/ubloxd/gps.py delete mode 100644 system/ubloxd/ubx.ksy create mode 100644 system/ubloxd/ubx.py diff --git a/SConstruct b/SConstruct index 094503cfa79..ca5b7b6cb72 100644 --- a/SConstruct +++ b/SConstruct @@ -14,7 +14,6 @@ Decider('MD5-timestamp') SetOption('num_jobs', max(1, int(os.cpu_count()/2))) -AddOption('--kaitai', action='store_true', help='Regenerate kaitai struct parsers') AddOption('--asan', action='store_true', help='turn on ASAN') AddOption('--ubsan', action='store_true', help='turn on UBSan') AddOption('--mutation', action='store_true', help='generate mutation-ready code') @@ -202,7 +201,6 @@ SConscript(['rednose/SConscript']) # Build system services SConscript([ - 'system/ubloxd/SConscript', 'system/loggerd/SConscript', ]) diff --git a/pyproject.toml b/pyproject.toml index 19491ba5327..1be5c395f1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,9 +33,6 @@ dependencies = [ "pyopenssl < 24.3.0", "pyaudio", - # ubloxd (TODO: just use struct) - "kaitaistruct", - # panda "libusb1", "spidev; platform_system == 'Linux'", diff --git a/system/ubloxd/SConscript b/system/ubloxd/SConscript deleted file mode 100644 index 9eb50760bad..00000000000 --- a/system/ubloxd/SConscript +++ /dev/null @@ -1,11 +0,0 @@ -Import('env') - -if GetOption('kaitai'): - current_dir = Dir('./generated/').srcnode().abspath - python_cmd = f"kaitai-struct-compiler --target python --outdir {current_dir} $SOURCES" - env.Command(File('./generated/ubx.py'), 'ubx.ksy', python_cmd) - env.Command(File('./generated/gps.py'), 'gps.ksy', python_cmd) - env.Command(File('./generated/glonass.py'), 'glonass.ksy', python_cmd) - # kaitai issue: https://github.com/kaitai-io/kaitai_struct/issues/910 - py_glonass_fix = env.Command(None, File('./generated/glonass.py'), "sed -i 's/self._io.align_to_byte()/# self._io.align_to_byte()/' $SOURCES") - env.Depends(py_glonass_fix, File('./generated/glonass.py')) diff --git a/system/ubloxd/binary_struct.py b/system/ubloxd/binary_struct.py new file mode 100644 index 00000000000..7b229620a2f --- /dev/null +++ b/system/ubloxd/binary_struct.py @@ -0,0 +1,280 @@ +""" +Binary struct parsing DSL. + +Defines a declarative schema for binary messages using dataclasses +and type annotations. +""" + +import struct +from enum import Enum +from dataclasses import dataclass, is_dataclass +from typing import Annotated, Any, TypeVar, get_args, get_origin + + +class FieldType: + """Base class for field type descriptors.""" + + +@dataclass(frozen=True) +class IntType(FieldType): + bits: int + signed: bool + big_endian: bool = False + +@dataclass(frozen=True) +class FloatType(FieldType): + bits: int + +@dataclass(frozen=True) +class BitsType(FieldType): + bits: int + +@dataclass(frozen=True) +class BytesType(FieldType): + size: int + +@dataclass(frozen=True) +class ArrayType(FieldType): + element_type: Any + count_field: str + +@dataclass(frozen=True) +class SwitchType(FieldType): + selector: str + cases: dict[Any, Any] + default: Any = None + +@dataclass(frozen=True) +class EnumType(FieldType): + base_type: FieldType + enum_cls: type[Enum] + +@dataclass(frozen=True) +class ConstType(FieldType): + base_type: FieldType + expected: Any + +@dataclass(frozen=True) +class SubstreamType(FieldType): + length_field: str + element_type: Any + +# Common types - little endian +u8 = IntType(8, False) +u16 = IntType(16, False) +u32 = IntType(32, False) +s8 = IntType(8, True) +s16 = IntType(16, True) +s32 = IntType(32, True) +f32 = FloatType(32) +f64 = FloatType(64) +# Big endian variants +u16be = IntType(16, False, big_endian=True) +u32be = IntType(32, False, big_endian=True) +s16be = IntType(16, True, big_endian=True) +s32be = IntType(32, True, big_endian=True) + + +def bits(n: int) -> BitsType: + """Create a bit-level field type.""" + return BitsType(n) + +def bytes_field(size: int) -> BytesType: + """Create a fixed-size bytes field.""" + return BytesType(size) + +def array(element_type: Any, count_field: str) -> ArrayType: + """Create an array/repeated field.""" + return ArrayType(element_type, count_field) + +def switch(selector: str, cases: dict[Any, Any], default: Any = None) -> SwitchType: + """Create a switch-on field.""" + return SwitchType(selector, cases, default) + +def enum(base_type: Any, enum_cls: type[Enum]) -> EnumType: + """Create an enum-wrapped field.""" + field_type = _field_type_from_spec(base_type) + if field_type is None: + raise TypeError(f"Unsupported field type: {base_type!r}") + return EnumType(field_type, enum_cls) + +def const(base_type: Any, expected: Any) -> ConstType: + """Create a constant-value field.""" + field_type = _field_type_from_spec(base_type) + if field_type is None: + raise TypeError(f"Unsupported field type: {base_type!r}") + return ConstType(field_type, expected) + +def substream(length_field: str, element_type: Any) -> SubstreamType: + """Parse a fixed-length substream using an inner schema.""" + return SubstreamType(length_field, element_type) + + +class BinaryReader: + def __init__(self, data: bytes): + self.data = data + self.pos = 0 + self.bit_pos = 0 # 0-7, position within current byte + + def _require(self, n: int) -> None: + if self.pos + n > len(self.data): + raise EOFError("Unexpected end of data") + + def _read_struct(self, fmt: str): + self._align_to_byte() + size = struct.calcsize(fmt) + self._require(size) + value = struct.unpack_from(fmt, self.data, self.pos)[0] + self.pos += size + return value + + def read_bytes(self, n: int) -> bytes: + self._align_to_byte() + self._require(n) + result = self.data[self.pos : self.pos + n] + self.pos += n + return result + + def read_bits_int_be(self, n: int) -> int: + result = 0 + bits_remaining = n + while bits_remaining > 0: + if self.pos >= len(self.data): + raise EOFError("Unexpected end of data while reading bits") + bits_in_byte = 8 - self.bit_pos + bits_to_read = min(bits_remaining, bits_in_byte) + byte_val = self.data[self.pos] + shift = bits_in_byte - bits_to_read + mask = (1 << bits_to_read) - 1 + extracted = (byte_val >> shift) & mask + result = (result << bits_to_read) | extracted + self.bit_pos += bits_to_read + bits_remaining -= bits_to_read + if self.bit_pos >= 8: + self.bit_pos = 0 + self.pos += 1 + return result + + def _align_to_byte(self) -> None: + if self.bit_pos > 0: + self.bit_pos = 0 + self.pos += 1 + + +T = TypeVar('T', bound='BinaryStruct') + + +class BinaryStruct: + """Base class for binary struct definitions.""" + + def __init_subclass__(cls, **kwargs) -> None: + super().__init_subclass__(**kwargs) + if cls is BinaryStruct: + return + if not is_dataclass(cls): + dataclass(init=False)(cls) + fields = list(getattr(cls, '__annotations__', {}).items()) + cls.__binary_fields__ = fields # type: ignore[attr-defined] + + @classmethod + def _read(inner_cls, reader: BinaryReader): + obj = inner_cls.__new__(inner_cls) + for name, spec in inner_cls.__binary_fields__: + value = _parse_field(spec, reader, obj) + setattr(obj, name, value) + return obj + + cls._read = _read # type: ignore[attr-defined] + + @classmethod + def from_bytes(cls: type[T], data: bytes) -> T: + """Parse struct from bytes.""" + reader = BinaryReader(data) + return cls._read(reader) + + @classmethod + def _read(cls: type[T], reader: BinaryReader) -> T: + """Override in subclasses to implement parsing.""" + raise NotImplementedError + + +def _resolve_path(obj: Any, path: str) -> Any: + cur = obj + for part in path.split('.'): + cur = getattr(cur, part) + return cur + +def _unwrap_annotated(spec: Any) -> tuple[Any, ...]: + if get_origin(spec) is Annotated: + return get_args(spec)[1:] + return () + +def _field_type_from_spec(spec: Any) -> FieldType | None: + if isinstance(spec, FieldType): + return spec + for item in _unwrap_annotated(spec): + if isinstance(item, FieldType): + return item + return None + + +def _int_format(field_type: IntType) -> str: + if field_type.bits == 8: + return 'b' if field_type.signed else 'B' + endian = '>' if field_type.big_endian else '<' + if field_type.bits == 16: + code = 'h' if field_type.signed else 'H' + elif field_type.bits == 32: + code = 'i' if field_type.signed else 'I' + else: + raise ValueError(f"Unsupported integer size: {field_type.bits}") + return f"{endian}{code}" + +def _float_format(field_type: FloatType) -> str: + if field_type.bits == 32: + return ' Any: + field_type = _field_type_from_spec(spec) + if field_type is not None: + spec = field_type + if isinstance(spec, ConstType): + value = _parse_field(spec.base_type, reader, obj) + if value != spec.expected: + raise ValueError(f"Invalid constant: expected {spec.expected!r}, got {value!r}") + return value + if isinstance(spec, EnumType): + raw = _parse_field(spec.base_type, reader, obj) + try: + return spec.enum_cls(raw) + except ValueError: + return raw + if isinstance(spec, SwitchType): + key = _resolve_path(obj, spec.selector) + target = spec.cases.get(key, spec.default) + if target is None: + return None + return _parse_field(target, reader, obj) + if isinstance(spec, ArrayType): + count = _resolve_path(obj, spec.count_field) + return [_parse_field(spec.element_type, reader, obj) for _ in range(int(count))] + if isinstance(spec, SubstreamType): + length = _resolve_path(obj, spec.length_field) + data = reader.read_bytes(int(length)) + sub_reader = BinaryReader(data) + return _parse_field(spec.element_type, sub_reader, obj) + if isinstance(spec, IntType): + return reader._read_struct(_int_format(spec)) + if isinstance(spec, FloatType): + return reader._read_struct(_float_format(spec)) + if isinstance(spec, BitsType): + value = reader.read_bits_int_be(spec.bits) + return bool(value) if spec.bits == 1 else value + if isinstance(spec, BytesType): + return reader.read_bytes(spec.size) + if isinstance(spec, type) and issubclass(spec, BinaryStruct): + return spec._read(reader) + raise TypeError(f"Unsupported field spec: {spec!r}") diff --git a/system/ubloxd/generated/glonass.py b/system/ubloxd/generated/glonass.py deleted file mode 100644 index 40aa16bb6f1..00000000000 --- a/system/ubloxd/generated/glonass.py +++ /dev/null @@ -1,247 +0,0 @@ -# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild - -import kaitaistruct -from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO - - -if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9): - raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__)) - -class Glonass(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.idle_chip = self._io.read_bits_int_be(1) != 0 - self.string_number = self._io.read_bits_int_be(4) - # workaround for kaitai bit alignment issue (see glonass_fix.patch for C++) - # self._io.align_to_byte() - _on = self.string_number - if _on == 4: - self.data = Glonass.String4(self._io, self, self._root) - elif _on == 1: - self.data = Glonass.String1(self._io, self, self._root) - elif _on == 3: - self.data = Glonass.String3(self._io, self, self._root) - elif _on == 5: - self.data = Glonass.String5(self._io, self, self._root) - elif _on == 2: - self.data = Glonass.String2(self._io, self, self._root) - else: - self.data = Glonass.StringNonImmediate(self._io, self, self._root) - self.hamming_code = self._io.read_bits_int_be(8) - self.pad_1 = self._io.read_bits_int_be(11) - self.superframe_number = self._io.read_bits_int_be(16) - self.pad_2 = self._io.read_bits_int_be(8) - self.frame_number = self._io.read_bits_int_be(8) - - class String4(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.tau_n_sign = self._io.read_bits_int_be(1) != 0 - self.tau_n_value = self._io.read_bits_int_be(21) - self.delta_tau_n_sign = self._io.read_bits_int_be(1) != 0 - self.delta_tau_n_value = self._io.read_bits_int_be(4) - self.e_n = self._io.read_bits_int_be(5) - self.not_used_1 = self._io.read_bits_int_be(14) - self.p4 = self._io.read_bits_int_be(1) != 0 - self.f_t = self._io.read_bits_int_be(4) - self.not_used_2 = self._io.read_bits_int_be(3) - self.n_t = self._io.read_bits_int_be(11) - self.n = self._io.read_bits_int_be(5) - self.m = self._io.read_bits_int_be(2) - - @property - def tau_n(self): - if hasattr(self, '_m_tau_n'): - return self._m_tau_n - - self._m_tau_n = ((self.tau_n_value * -1) if self.tau_n_sign else self.tau_n_value) - return getattr(self, '_m_tau_n', None) - - @property - def delta_tau_n(self): - if hasattr(self, '_m_delta_tau_n'): - return self._m_delta_tau_n - - self._m_delta_tau_n = ((self.delta_tau_n_value * -1) if self.delta_tau_n_sign else self.delta_tau_n_value) - return getattr(self, '_m_delta_tau_n', None) - - - class StringNonImmediate(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.data_1 = self._io.read_bits_int_be(64) - self.data_2 = self._io.read_bits_int_be(8) - - - class String5(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.n_a = self._io.read_bits_int_be(11) - self.tau_c = self._io.read_bits_int_be(32) - self.not_used = self._io.read_bits_int_be(1) != 0 - self.n_4 = self._io.read_bits_int_be(5) - self.tau_gps = self._io.read_bits_int_be(22) - self.l_n = self._io.read_bits_int_be(1) != 0 - - - class String1(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.not_used = self._io.read_bits_int_be(2) - self.p1 = self._io.read_bits_int_be(2) - self.t_k = self._io.read_bits_int_be(12) - self.x_vel_sign = self._io.read_bits_int_be(1) != 0 - self.x_vel_value = self._io.read_bits_int_be(23) - self.x_accel_sign = self._io.read_bits_int_be(1) != 0 - self.x_accel_value = self._io.read_bits_int_be(4) - self.x_sign = self._io.read_bits_int_be(1) != 0 - self.x_value = self._io.read_bits_int_be(26) - - @property - def x_vel(self): - if hasattr(self, '_m_x_vel'): - return self._m_x_vel - - self._m_x_vel = ((self.x_vel_value * -1) if self.x_vel_sign else self.x_vel_value) - return getattr(self, '_m_x_vel', None) - - @property - def x_accel(self): - if hasattr(self, '_m_x_accel'): - return self._m_x_accel - - self._m_x_accel = ((self.x_accel_value * -1) if self.x_accel_sign else self.x_accel_value) - return getattr(self, '_m_x_accel', None) - - @property - def x(self): - if hasattr(self, '_m_x'): - return self._m_x - - self._m_x = ((self.x_value * -1) if self.x_sign else self.x_value) - return getattr(self, '_m_x', None) - - - class String2(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.b_n = self._io.read_bits_int_be(3) - self.p2 = self._io.read_bits_int_be(1) != 0 - self.t_b = self._io.read_bits_int_be(7) - self.not_used = self._io.read_bits_int_be(5) - self.y_vel_sign = self._io.read_bits_int_be(1) != 0 - self.y_vel_value = self._io.read_bits_int_be(23) - self.y_accel_sign = self._io.read_bits_int_be(1) != 0 - self.y_accel_value = self._io.read_bits_int_be(4) - self.y_sign = self._io.read_bits_int_be(1) != 0 - self.y_value = self._io.read_bits_int_be(26) - - @property - def y_vel(self): - if hasattr(self, '_m_y_vel'): - return self._m_y_vel - - self._m_y_vel = ((self.y_vel_value * -1) if self.y_vel_sign else self.y_vel_value) - return getattr(self, '_m_y_vel', None) - - @property - def y_accel(self): - if hasattr(self, '_m_y_accel'): - return self._m_y_accel - - self._m_y_accel = ((self.y_accel_value * -1) if self.y_accel_sign else self.y_accel_value) - return getattr(self, '_m_y_accel', None) - - @property - def y(self): - if hasattr(self, '_m_y'): - return self._m_y - - self._m_y = ((self.y_value * -1) if self.y_sign else self.y_value) - return getattr(self, '_m_y', None) - - - class String3(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.p3 = self._io.read_bits_int_be(1) != 0 - self.gamma_n_sign = self._io.read_bits_int_be(1) != 0 - self.gamma_n_value = self._io.read_bits_int_be(10) - self.not_used = self._io.read_bits_int_be(1) != 0 - self.p = self._io.read_bits_int_be(2) - self.l_n = self._io.read_bits_int_be(1) != 0 - self.z_vel_sign = self._io.read_bits_int_be(1) != 0 - self.z_vel_value = self._io.read_bits_int_be(23) - self.z_accel_sign = self._io.read_bits_int_be(1) != 0 - self.z_accel_value = self._io.read_bits_int_be(4) - self.z_sign = self._io.read_bits_int_be(1) != 0 - self.z_value = self._io.read_bits_int_be(26) - - @property - def gamma_n(self): - if hasattr(self, '_m_gamma_n'): - return self._m_gamma_n - - self._m_gamma_n = ((self.gamma_n_value * -1) if self.gamma_n_sign else self.gamma_n_value) - return getattr(self, '_m_gamma_n', None) - - @property - def z_vel(self): - if hasattr(self, '_m_z_vel'): - return self._m_z_vel - - self._m_z_vel = ((self.z_vel_value * -1) if self.z_vel_sign else self.z_vel_value) - return getattr(self, '_m_z_vel', None) - - @property - def z_accel(self): - if hasattr(self, '_m_z_accel'): - return self._m_z_accel - - self._m_z_accel = ((self.z_accel_value * -1) if self.z_accel_sign else self.z_accel_value) - return getattr(self, '_m_z_accel', None) - - @property - def z(self): - if hasattr(self, '_m_z'): - return self._m_z - - self._m_z = ((self.z_value * -1) if self.z_sign else self.z_value) - return getattr(self, '_m_z', None) - - diff --git a/system/ubloxd/generated/gps.py b/system/ubloxd/generated/gps.py deleted file mode 100644 index a999016f3ed..00000000000 --- a/system/ubloxd/generated/gps.py +++ /dev/null @@ -1,193 +0,0 @@ -# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild - -import kaitaistruct -from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO - - -if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9): - raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__)) - -class Gps(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.tlm = Gps.Tlm(self._io, self, self._root) - self.how = Gps.How(self._io, self, self._root) - _on = self.how.subframe_id - if _on == 1: - self.body = Gps.Subframe1(self._io, self, self._root) - elif _on == 2: - self.body = Gps.Subframe2(self._io, self, self._root) - elif _on == 3: - self.body = Gps.Subframe3(self._io, self, self._root) - elif _on == 4: - self.body = Gps.Subframe4(self._io, self, self._root) - - class Subframe1(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.week_no = self._io.read_bits_int_be(10) - self.code = self._io.read_bits_int_be(2) - self.sv_accuracy = self._io.read_bits_int_be(4) - self.sv_health = self._io.read_bits_int_be(6) - self.iodc_msb = self._io.read_bits_int_be(2) - self.l2_p_data_flag = self._io.read_bits_int_be(1) != 0 - self.reserved1 = self._io.read_bits_int_be(23) - self.reserved2 = self._io.read_bits_int_be(24) - self.reserved3 = self._io.read_bits_int_be(24) - self.reserved4 = self._io.read_bits_int_be(16) - self._io.align_to_byte() - self.t_gd = self._io.read_s1() - self.iodc_lsb = self._io.read_u1() - self.t_oc = self._io.read_u2be() - self.af_2 = self._io.read_s1() - self.af_1 = self._io.read_s2be() - self.af_0_sign = self._io.read_bits_int_be(1) != 0 - self.af_0_value = self._io.read_bits_int_be(21) - self.reserved5 = self._io.read_bits_int_be(2) - - @property - def af_0(self): - if hasattr(self, '_m_af_0'): - return self._m_af_0 - - self._m_af_0 = ((self.af_0_value - (1 << 21)) if self.af_0_sign else self.af_0_value) - return getattr(self, '_m_af_0', None) - - - class Subframe3(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.c_ic = self._io.read_s2be() - self.omega_0 = self._io.read_s4be() - self.c_is = self._io.read_s2be() - self.i_0 = self._io.read_s4be() - self.c_rc = self._io.read_s2be() - self.omega = self._io.read_s4be() - self.omega_dot_sign = self._io.read_bits_int_be(1) != 0 - self.omega_dot_value = self._io.read_bits_int_be(23) - self._io.align_to_byte() - self.iode = self._io.read_u1() - self.idot_sign = self._io.read_bits_int_be(1) != 0 - self.idot_value = self._io.read_bits_int_be(13) - self.reserved = self._io.read_bits_int_be(2) - - @property - def omega_dot(self): - if hasattr(self, '_m_omega_dot'): - return self._m_omega_dot - - self._m_omega_dot = ((self.omega_dot_value - (1 << 23)) if self.omega_dot_sign else self.omega_dot_value) - return getattr(self, '_m_omega_dot', None) - - @property - def idot(self): - if hasattr(self, '_m_idot'): - return self._m_idot - - self._m_idot = ((self.idot_value - (1 << 13)) if self.idot_sign else self.idot_value) - return getattr(self, '_m_idot', None) - - - class Subframe4(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.data_id = self._io.read_bits_int_be(2) - self.page_id = self._io.read_bits_int_be(6) - self._io.align_to_byte() - _on = self.page_id - if _on == 56: - self.body = Gps.Subframe4.IonosphereData(self._io, self, self._root) - - class IonosphereData(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.a0 = self._io.read_s1() - self.a1 = self._io.read_s1() - self.a2 = self._io.read_s1() - self.a3 = self._io.read_s1() - self.b0 = self._io.read_s1() - self.b1 = self._io.read_s1() - self.b2 = self._io.read_s1() - self.b3 = self._io.read_s1() - - - - class How(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.tow_count = self._io.read_bits_int_be(17) - self.alert = self._io.read_bits_int_be(1) != 0 - self.anti_spoof = self._io.read_bits_int_be(1) != 0 - self.subframe_id = self._io.read_bits_int_be(3) - self.reserved = self._io.read_bits_int_be(2) - - - class Tlm(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.preamble = self._io.read_bytes(1) - if not self.preamble == b"\x8B": - raise kaitaistruct.ValidationNotEqualError(b"\x8B", self.preamble, self._io, u"/types/tlm/seq/0") - self.tlm = self._io.read_bits_int_be(14) - self.integrity_status = self._io.read_bits_int_be(1) != 0 - self.reserved = self._io.read_bits_int_be(1) != 0 - - - class Subframe2(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.iode = self._io.read_u1() - self.c_rs = self._io.read_s2be() - self.delta_n = self._io.read_s2be() - self.m_0 = self._io.read_s4be() - self.c_uc = self._io.read_s2be() - self.e = self._io.read_s4be() - self.c_us = self._io.read_s2be() - self.sqrt_a = self._io.read_u4be() - self.t_oe = self._io.read_u2be() - self.fit_interval_flag = self._io.read_bits_int_be(1) != 0 - self.aoda = self._io.read_bits_int_be(5) - self.reserved = self._io.read_bits_int_be(2) - - - diff --git a/system/ubloxd/generated/ubx.py b/system/ubloxd/generated/ubx.py deleted file mode 100644 index 99465843881..00000000000 --- a/system/ubloxd/generated/ubx.py +++ /dev/null @@ -1,273 +0,0 @@ -# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild - -import kaitaistruct -from kaitaistruct import KaitaiStruct, KaitaiStream, BytesIO -from enum import Enum - - -if getattr(kaitaistruct, 'API_VERSION', (0, 9)) < (0, 9): - raise Exception("Incompatible Kaitai Struct Python API: 0.9 or later is required, but you have %s" % (kaitaistruct.__version__)) - -class Ubx(KaitaiStruct): - - class GnssType(Enum): - gps = 0 - sbas = 1 - galileo = 2 - beidou = 3 - imes = 4 - qzss = 5 - glonass = 6 - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.magic = self._io.read_bytes(2) - if not self.magic == b"\xB5\x62": - raise kaitaistruct.ValidationNotEqualError(b"\xB5\x62", self.magic, self._io, u"/seq/0") - self.msg_type = self._io.read_u2be() - self.length = self._io.read_u2le() - _on = self.msg_type - if _on == 2569: - self.body = Ubx.MonHw(self._io, self, self._root) - elif _on == 533: - self.body = Ubx.RxmRawx(self._io, self, self._root) - elif _on == 531: - self.body = Ubx.RxmSfrbx(self._io, self, self._root) - elif _on == 309: - self.body = Ubx.NavSat(self._io, self, self._root) - elif _on == 2571: - self.body = Ubx.MonHw2(self._io, self, self._root) - elif _on == 263: - self.body = Ubx.NavPvt(self._io, self, self._root) - - class RxmRawx(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.rcv_tow = self._io.read_f8le() - self.week = self._io.read_u2le() - self.leap_s = self._io.read_s1() - self.num_meas = self._io.read_u1() - self.rec_stat = self._io.read_u1() - self.reserved1 = self._io.read_bytes(3) - self._raw_meas = [] - self.meas = [] - for i in range(self.num_meas): - self._raw_meas.append(self._io.read_bytes(32)) - _io__raw_meas = KaitaiStream(BytesIO(self._raw_meas[i])) - self.meas.append(Ubx.RxmRawx.Measurement(_io__raw_meas, self, self._root)) - - - class Measurement(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.pr_mes = self._io.read_f8le() - self.cp_mes = self._io.read_f8le() - self.do_mes = self._io.read_f4le() - self.gnss_id = KaitaiStream.resolve_enum(Ubx.GnssType, self._io.read_u1()) - self.sv_id = self._io.read_u1() - self.reserved2 = self._io.read_bytes(1) - self.freq_id = self._io.read_u1() - self.lock_time = self._io.read_u2le() - self.cno = self._io.read_u1() - self.pr_stdev = self._io.read_u1() - self.cp_stdev = self._io.read_u1() - self.do_stdev = self._io.read_u1() - self.trk_stat = self._io.read_u1() - self.reserved3 = self._io.read_bytes(1) - - - - class RxmSfrbx(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.gnss_id = KaitaiStream.resolve_enum(Ubx.GnssType, self._io.read_u1()) - self.sv_id = self._io.read_u1() - self.reserved1 = self._io.read_bytes(1) - self.freq_id = self._io.read_u1() - self.num_words = self._io.read_u1() - self.reserved2 = self._io.read_bytes(1) - self.version = self._io.read_u1() - self.reserved3 = self._io.read_bytes(1) - self.body = [] - for i in range(self.num_words): - self.body.append(self._io.read_u4le()) - - - - class NavSat(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.itow = self._io.read_u4le() - self.version = self._io.read_u1() - self.num_svs = self._io.read_u1() - self.reserved = self._io.read_bytes(2) - self._raw_svs = [] - self.svs = [] - for i in range(self.num_svs): - self._raw_svs.append(self._io.read_bytes(12)) - _io__raw_svs = KaitaiStream(BytesIO(self._raw_svs[i])) - self.svs.append(Ubx.NavSat.Nav(_io__raw_svs, self, self._root)) - - - class Nav(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.gnss_id = KaitaiStream.resolve_enum(Ubx.GnssType, self._io.read_u1()) - self.sv_id = self._io.read_u1() - self.cno = self._io.read_u1() - self.elev = self._io.read_s1() - self.azim = self._io.read_s2le() - self.pr_res = self._io.read_s2le() - self.flags = self._io.read_u4le() - - - - class NavPvt(KaitaiStruct): - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.i_tow = self._io.read_u4le() - self.year = self._io.read_u2le() - self.month = self._io.read_u1() - self.day = self._io.read_u1() - self.hour = self._io.read_u1() - self.min = self._io.read_u1() - self.sec = self._io.read_u1() - self.valid = self._io.read_u1() - self.t_acc = self._io.read_u4le() - self.nano = self._io.read_s4le() - self.fix_type = self._io.read_u1() - self.flags = self._io.read_u1() - self.flags2 = self._io.read_u1() - self.num_sv = self._io.read_u1() - self.lon = self._io.read_s4le() - self.lat = self._io.read_s4le() - self.height = self._io.read_s4le() - self.h_msl = self._io.read_s4le() - self.h_acc = self._io.read_u4le() - self.v_acc = self._io.read_u4le() - self.vel_n = self._io.read_s4le() - self.vel_e = self._io.read_s4le() - self.vel_d = self._io.read_s4le() - self.g_speed = self._io.read_s4le() - self.head_mot = self._io.read_s4le() - self.s_acc = self._io.read_s4le() - self.head_acc = self._io.read_u4le() - self.p_dop = self._io.read_u2le() - self.flags3 = self._io.read_u1() - self.reserved1 = self._io.read_bytes(5) - self.head_veh = self._io.read_s4le() - self.mag_dec = self._io.read_s2le() - self.mag_acc = self._io.read_u2le() - - - class MonHw2(KaitaiStruct): - - class ConfigSource(Enum): - flash = 102 - otp = 111 - config_pins = 112 - rom = 113 - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.ofs_i = self._io.read_s1() - self.mag_i = self._io.read_u1() - self.ofs_q = self._io.read_s1() - self.mag_q = self._io.read_u1() - self.cfg_source = KaitaiStream.resolve_enum(Ubx.MonHw2.ConfigSource, self._io.read_u1()) - self.reserved1 = self._io.read_bytes(3) - self.low_lev_cfg = self._io.read_u4le() - self.reserved2 = self._io.read_bytes(8) - self.post_status = self._io.read_u4le() - self.reserved3 = self._io.read_bytes(4) - - - class MonHw(KaitaiStruct): - - class AntennaStatus(Enum): - init = 0 - dontknow = 1 - ok = 2 - short = 3 - open = 4 - - class AntennaPower(Enum): - false = 0 - true = 1 - dontknow = 2 - def __init__(self, _io, _parent=None, _root=None): - self._io = _io - self._parent = _parent - self._root = _root if _root else self - self._read() - - def _read(self): - self.pin_sel = self._io.read_u4le() - self.pin_bank = self._io.read_u4le() - self.pin_dir = self._io.read_u4le() - self.pin_val = self._io.read_u4le() - self.noise_per_ms = self._io.read_u2le() - self.agc_cnt = self._io.read_u2le() - self.a_status = KaitaiStream.resolve_enum(Ubx.MonHw.AntennaStatus, self._io.read_u1()) - self.a_power = KaitaiStream.resolve_enum(Ubx.MonHw.AntennaPower, self._io.read_u1()) - self.flags = self._io.read_u1() - self.reserved1 = self._io.read_bytes(1) - self.used_mask = self._io.read_u4le() - self.vp = self._io.read_bytes(17) - self.jam_ind = self._io.read_u1() - self.reserved2 = self._io.read_bytes(2) - self.pin_irq = self._io.read_u4le() - self.pull_h = self._io.read_u4le() - self.pull_l = self._io.read_u4le() - - - @property - def checksum(self): - if hasattr(self, '_m_checksum'): - return self._m_checksum - - _pos = self._io.pos() - self._io.seek((self.length + 6)) - self._m_checksum = self._io.read_u2le() - self._io.seek(_pos) - return getattr(self, '_m_checksum', None) - - diff --git a/system/ubloxd/glonass.ksy b/system/ubloxd/glonass.ksy deleted file mode 100644 index be99f6e497a..00000000000 --- a/system/ubloxd/glonass.ksy +++ /dev/null @@ -1,176 +0,0 @@ -# http://gauss.gge.unb.ca/GLONASS.ICD.pdf -# some variables are misprinted but good in the old doc -# https://www.unavco.org/help/glossary/docs/ICD_GLONASS_4.0_(1998)_en.pdf -meta: - id: glonass - endian: be - bit-endian: be -seq: - - id: idle_chip - type: b1 - - id: string_number - type: b4 - - id: data - type: - switch-on: string_number - cases: - 1: string_1 - 2: string_2 - 3: string_3 - 4: string_4 - 5: string_5 - _: string_non_immediate - - id: hamming_code - type: b8 - - id: pad_1 - type: b11 - - id: superframe_number - type: b16 - - id: pad_2 - type: b8 - - id: frame_number - type: b8 - -types: - string_1: - seq: - - id: not_used - type: b2 - - id: p1 - type: b2 - - id: t_k - type: b12 - - id: x_vel_sign - type: b1 - - id: x_vel_value - type: b23 - - id: x_accel_sign - type: b1 - - id: x_accel_value - type: b4 - - id: x_sign - type: b1 - - id: x_value - type: b26 - instances: - x_vel: - value: 'x_vel_sign ? (x_vel_value * (-1)) : x_vel_value' - x_accel: - value: 'x_accel_sign ? (x_accel_value * (-1)) : x_accel_value' - x: - value: 'x_sign ? (x_value * (-1)) : x_value' - string_2: - seq: - - id: b_n - type: b3 - - id: p2 - type: b1 - - id: t_b - type: b7 - - id: not_used - type: b5 - - id: y_vel_sign - type: b1 - - id: y_vel_value - type: b23 - - id: y_accel_sign - type: b1 - - id: y_accel_value - type: b4 - - id: y_sign - type: b1 - - id: y_value - type: b26 - instances: - y_vel: - value: 'y_vel_sign ? (y_vel_value * (-1)) : y_vel_value' - y_accel: - value: 'y_accel_sign ? (y_accel_value * (-1)) : y_accel_value' - y: - value: 'y_sign ? (y_value * (-1)) : y_value' - string_3: - seq: - - id: p3 - type: b1 - - id: gamma_n_sign - type: b1 - - id: gamma_n_value - type: b10 - - id: not_used - type: b1 - - id: p - type: b2 - - id: l_n - type: b1 - - id: z_vel_sign - type: b1 - - id: z_vel_value - type: b23 - - id: z_accel_sign - type: b1 - - id: z_accel_value - type: b4 - - id: z_sign - type: b1 - - id: z_value - type: b26 - instances: - gamma_n: - value: 'gamma_n_sign ? (gamma_n_value * (-1)) : gamma_n_value' - z_vel: - value: 'z_vel_sign ? (z_vel_value * (-1)) : z_vel_value' - z_accel: - value: 'z_accel_sign ? (z_accel_value * (-1)) : z_accel_value' - z: - value: 'z_sign ? (z_value * (-1)) : z_value' - string_4: - seq: - - id: tau_n_sign - type: b1 - - id: tau_n_value - type: b21 - - id: delta_tau_n_sign - type: b1 - - id: delta_tau_n_value - type: b4 - - id: e_n - type: b5 - - id: not_used_1 - type: b14 - - id: p4 - type: b1 - - id: f_t - type: b4 - - id: not_used_2 - type: b3 - - id: n_t - type: b11 - - id: n - type: b5 - - id: m - type: b2 - instances: - tau_n: - value: 'tau_n_sign ? (tau_n_value * (-1)) : tau_n_value' - delta_tau_n: - value: 'delta_tau_n_sign ? (delta_tau_n_value * (-1)) : delta_tau_n_value' - string_5: - seq: - - id: n_a - type: b11 - - id: tau_c - type: b32 - - id: not_used - type: b1 - - id: n_4 - type: b5 - - id: tau_gps - type: b22 - - id: l_n - type: b1 - string_non_immediate: - seq: - - id: data_1 - type: b64 - - id: data_2 - type: b8 diff --git a/system/ubloxd/glonass.py b/system/ubloxd/glonass.py new file mode 100644 index 00000000000..144ccdde6e2 --- /dev/null +++ b/system/ubloxd/glonass.py @@ -0,0 +1,156 @@ +""" +Parses GLONASS navigation strings per GLONASS ICD specification. +http://gauss.gge.unb.ca/GLONASS.ICD.pdf +https://www.unavco.org/help/glossary/docs/ICD_GLONASS_4.0_(1998)_en.pdf +""" + +from typing import Annotated + +from openpilot.system.ubloxd import binary_struct as bs + + +class Glonass(bs.BinaryStruct): + class String1(bs.BinaryStruct): + not_used: Annotated[int, bs.bits(2)] + p1: Annotated[int, bs.bits(2)] + t_k: Annotated[int, bs.bits(12)] + x_vel_sign: Annotated[bool, bs.bits(1)] + x_vel_value: Annotated[int, bs.bits(23)] + x_accel_sign: Annotated[bool, bs.bits(1)] + x_accel_value: Annotated[int, bs.bits(4)] + x_sign: Annotated[bool, bs.bits(1)] + x_value: Annotated[int, bs.bits(26)] + + @property + def x_vel(self) -> int: + """Computed x_vel from sign-magnitude representation.""" + return (self.x_vel_value * -1) if self.x_vel_sign else self.x_vel_value + + @property + def x_accel(self) -> int: + """Computed x_accel from sign-magnitude representation.""" + return (self.x_accel_value * -1) if self.x_accel_sign else self.x_accel_value + + @property + def x(self) -> int: + """Computed x from sign-magnitude representation.""" + return (self.x_value * -1) if self.x_sign else self.x_value + + class String2(bs.BinaryStruct): + b_n: Annotated[int, bs.bits(3)] + p2: Annotated[bool, bs.bits(1)] + t_b: Annotated[int, bs.bits(7)] + not_used: Annotated[int, bs.bits(5)] + y_vel_sign: Annotated[bool, bs.bits(1)] + y_vel_value: Annotated[int, bs.bits(23)] + y_accel_sign: Annotated[bool, bs.bits(1)] + y_accel_value: Annotated[int, bs.bits(4)] + y_sign: Annotated[bool, bs.bits(1)] + y_value: Annotated[int, bs.bits(26)] + + @property + def y_vel(self) -> int: + """Computed y_vel from sign-magnitude representation.""" + return (self.y_vel_value * -1) if self.y_vel_sign else self.y_vel_value + + @property + def y_accel(self) -> int: + """Computed y_accel from sign-magnitude representation.""" + return (self.y_accel_value * -1) if self.y_accel_sign else self.y_accel_value + + @property + def y(self) -> int: + """Computed y from sign-magnitude representation.""" + return (self.y_value * -1) if self.y_sign else self.y_value + + class String3(bs.BinaryStruct): + p3: Annotated[bool, bs.bits(1)] + gamma_n_sign: Annotated[bool, bs.bits(1)] + gamma_n_value: Annotated[int, bs.bits(10)] + not_used: Annotated[bool, bs.bits(1)] + p: Annotated[int, bs.bits(2)] + l_n: Annotated[bool, bs.bits(1)] + z_vel_sign: Annotated[bool, bs.bits(1)] + z_vel_value: Annotated[int, bs.bits(23)] + z_accel_sign: Annotated[bool, bs.bits(1)] + z_accel_value: Annotated[int, bs.bits(4)] + z_sign: Annotated[bool, bs.bits(1)] + z_value: Annotated[int, bs.bits(26)] + + @property + def gamma_n(self) -> int: + """Computed gamma_n from sign-magnitude representation.""" + return (self.gamma_n_value * -1) if self.gamma_n_sign else self.gamma_n_value + + @property + def z_vel(self) -> int: + """Computed z_vel from sign-magnitude representation.""" + return (self.z_vel_value * -1) if self.z_vel_sign else self.z_vel_value + + @property + def z_accel(self) -> int: + """Computed z_accel from sign-magnitude representation.""" + return (self.z_accel_value * -1) if self.z_accel_sign else self.z_accel_value + + @property + def z(self) -> int: + """Computed z from sign-magnitude representation.""" + return (self.z_value * -1) if self.z_sign else self.z_value + + class String4(bs.BinaryStruct): + tau_n_sign: Annotated[bool, bs.bits(1)] + tau_n_value: Annotated[int, bs.bits(21)] + delta_tau_n_sign: Annotated[bool, bs.bits(1)] + delta_tau_n_value: Annotated[int, bs.bits(4)] + e_n: Annotated[int, bs.bits(5)] + not_used_1: Annotated[int, bs.bits(14)] + p4: Annotated[bool, bs.bits(1)] + f_t: Annotated[int, bs.bits(4)] + not_used_2: Annotated[int, bs.bits(3)] + n_t: Annotated[int, bs.bits(11)] + n: Annotated[int, bs.bits(5)] + m: Annotated[int, bs.bits(2)] + + @property + def tau_n(self) -> int: + """Computed tau_n from sign-magnitude representation.""" + return (self.tau_n_value * -1) if self.tau_n_sign else self.tau_n_value + + @property + def delta_tau_n(self) -> int: + """Computed delta_tau_n from sign-magnitude representation.""" + return (self.delta_tau_n_value * -1) if self.delta_tau_n_sign else self.delta_tau_n_value + + class String5(bs.BinaryStruct): + n_a: Annotated[int, bs.bits(11)] + tau_c: Annotated[int, bs.bits(32)] + not_used: Annotated[bool, bs.bits(1)] + n_4: Annotated[int, bs.bits(5)] + tau_gps: Annotated[int, bs.bits(22)] + l_n: Annotated[bool, bs.bits(1)] + + class StringNonImmediate(bs.BinaryStruct): + data_1: Annotated[int, bs.bits(64)] + data_2: Annotated[int, bs.bits(8)] + + idle_chip: Annotated[bool, bs.bits(1)] + string_number: Annotated[int, bs.bits(4)] + data: Annotated[ + object, + bs.switch( + 'string_number', + { + 1: String1, + 2: String2, + 3: String3, + 4: String4, + 5: String5, + }, + default=StringNonImmediate, + ), + ] + hamming_code: Annotated[int, bs.bits(8)] + pad_1: Annotated[int, bs.bits(11)] + superframe_number: Annotated[int, bs.bits(16)] + pad_2: Annotated[int, bs.bits(8)] + frame_number: Annotated[int, bs.bits(8)] diff --git a/system/ubloxd/gps.ksy b/system/ubloxd/gps.ksy deleted file mode 100644 index 893ad1b25be..00000000000 --- a/system/ubloxd/gps.ksy +++ /dev/null @@ -1,189 +0,0 @@ -# https://www.gps.gov/technical/icwg/IS-GPS-200E.pdf -meta: - id: gps - endian: be - bit-endian: be -seq: - - id: tlm - type: tlm - - id: how - type: how - - id: body - type: - switch-on: how.subframe_id - cases: - 1: subframe_1 - 2: subframe_2 - 3: subframe_3 - 4: subframe_4 -types: - tlm: - seq: - - id: preamble - contents: [0x8b] - - id: tlm - type: b14 - - id: integrity_status - type: b1 - - id: reserved - type: b1 - how: - seq: - - id: tow_count - type: b17 - - id: alert - type: b1 - - id: anti_spoof - type: b1 - - id: subframe_id - type: b3 - - id: reserved - type: b2 - subframe_1: - seq: - # Word 3 - - id: week_no - type: b10 - - id: code - type: b2 - - id: sv_accuracy - type: b4 - - id: sv_health - type: b6 - - id: iodc_msb - type: b2 - # Word 4 - - id: l2_p_data_flag - type: b1 - - id: reserved1 - type: b23 - # Word 5 - - id: reserved2 - type: b24 - # Word 6 - - id: reserved3 - type: b24 - # Word 7 - - id: reserved4 - type: b16 - - id: t_gd - type: s1 - # Word 8 - - id: iodc_lsb - type: u1 - - id: t_oc - type: u2 - # Word 9 - - id: af_2 - type: s1 - - id: af_1 - type: s2 - # Word 10 - - id: af_0_sign - type: b1 - - id: af_0_value - type: b21 - - id: reserved5 - type: b2 - instances: - af_0: - value: 'af_0_sign ? (af_0_value - (1 << 21)) : af_0_value' - subframe_2: - seq: - # Word 3 - - id: iode - type: u1 - - id: c_rs - type: s2 - # Word 4 & 5 - - id: delta_n - type: s2 - - id: m_0 - type: s4 - # Word 6 & 7 - - id: c_uc - type: s2 - - id: e - type: s4 - # Word 8 & 9 - - id: c_us - type: s2 - - id: sqrt_a - type: u4 - # Word 10 - - id: t_oe - type: u2 - - id: fit_interval_flag - type: b1 - - id: aoda - type: b5 - - id: reserved - type: b2 - subframe_3: - seq: - # Word 3 & 4 - - id: c_ic - type: s2 - - id: omega_0 - type: s4 - # Word 5 & 6 - - id: c_is - type: s2 - - id: i_0 - type: s4 - # Word 7 & 8 - - id: c_rc - type: s2 - - id: omega - type: s4 - # Word 9 - - id: omega_dot_sign - type: b1 - - id: omega_dot_value - type: b23 - # Word 10 - - id: iode - type: u1 - - id: idot_sign - type: b1 - - id: idot_value - type: b13 - - id: reserved - type: b2 - instances: - omega_dot: - value: 'omega_dot_sign ? (omega_dot_value - (1 << 23)) : omega_dot_value' - idot: - value: 'idot_sign ? (idot_value - (1 << 13)) : idot_value' - subframe_4: - seq: - # Word 3 - - id: data_id - type: b2 - - id: page_id - type: b6 - - id: body - type: - switch-on: page_id - cases: - 56: ionosphere_data - types: - ionosphere_data: - seq: - - id: a0 - type: s1 - - id: a1 - type: s1 - - id: a2 - type: s1 - - id: a3 - type: s1 - - id: b0 - type: s1 - - id: b1 - type: s1 - - id: b2 - type: s1 - - id: b3 - type: s1 - diff --git a/system/ubloxd/gps.py b/system/ubloxd/gps.py new file mode 100644 index 00000000000..1c0833bd92d --- /dev/null +++ b/system/ubloxd/gps.py @@ -0,0 +1,116 @@ +""" +Parses GPS navigation subframes per IS-GPS-200E specification. +https://www.gps.gov/technical/icwg/IS-GPS-200E.pdf +""" + +from typing import Annotated + +from openpilot.system.ubloxd import binary_struct as bs + + +class Gps(bs.BinaryStruct): + class Tlm(bs.BinaryStruct): + preamble: Annotated[bytes, bs.const(bs.bytes_field(1), b"\x8b")] + tlm: Annotated[int, bs.bits(14)] + integrity_status: Annotated[bool, bs.bits(1)] + reserved: Annotated[bool, bs.bits(1)] + + class How(bs.BinaryStruct): + tow_count: Annotated[int, bs.bits(17)] + alert: Annotated[bool, bs.bits(1)] + anti_spoof: Annotated[bool, bs.bits(1)] + subframe_id: Annotated[int, bs.bits(3)] + reserved: Annotated[int, bs.bits(2)] + + class Subframe1(bs.BinaryStruct): + week_no: Annotated[int, bs.bits(10)] + code: Annotated[int, bs.bits(2)] + sv_accuracy: Annotated[int, bs.bits(4)] + sv_health: Annotated[int, bs.bits(6)] + iodc_msb: Annotated[int, bs.bits(2)] + l2_p_data_flag: Annotated[bool, bs.bits(1)] + reserved1: Annotated[int, bs.bits(23)] + reserved2: Annotated[int, bs.bits(24)] + reserved3: Annotated[int, bs.bits(24)] + reserved4: Annotated[int, bs.bits(16)] + t_gd: Annotated[int, bs.s8] + iodc_lsb: Annotated[int, bs.u8] + t_oc: Annotated[int, bs.u16be] + af_2: Annotated[int, bs.s8] + af_1: Annotated[int, bs.s16be] + af_0_sign: Annotated[bool, bs.bits(1)] + af_0_value: Annotated[int, bs.bits(21)] + reserved5: Annotated[int, bs.bits(2)] + + @property + def af_0(self) -> int: + """Computed af_0 from sign-magnitude representation.""" + return (self.af_0_value - (1 << 21)) if self.af_0_sign else self.af_0_value + + class Subframe2(bs.BinaryStruct): + iode: Annotated[int, bs.u8] + c_rs: Annotated[int, bs.s16be] + delta_n: Annotated[int, bs.s16be] + m_0: Annotated[int, bs.s32be] + c_uc: Annotated[int, bs.s16be] + e: Annotated[int, bs.s32be] + c_us: Annotated[int, bs.s16be] + sqrt_a: Annotated[int, bs.u32be] + t_oe: Annotated[int, bs.u16be] + fit_interval_flag: Annotated[bool, bs.bits(1)] + aoda: Annotated[int, bs.bits(5)] + reserved: Annotated[int, bs.bits(2)] + + class Subframe3(bs.BinaryStruct): + c_ic: Annotated[int, bs.s16be] + omega_0: Annotated[int, bs.s32be] + c_is: Annotated[int, bs.s16be] + i_0: Annotated[int, bs.s32be] + c_rc: Annotated[int, bs.s16be] + omega: Annotated[int, bs.s32be] + omega_dot_sign: Annotated[bool, bs.bits(1)] + omega_dot_value: Annotated[int, bs.bits(23)] + iode: Annotated[int, bs.u8] + idot_sign: Annotated[bool, bs.bits(1)] + idot_value: Annotated[int, bs.bits(13)] + reserved: Annotated[int, bs.bits(2)] + + @property + def omega_dot(self) -> int: + """Computed omega_dot from sign-magnitude representation.""" + return (self.omega_dot_value - (1 << 23)) if self.omega_dot_sign else self.omega_dot_value + + @property + def idot(self) -> int: + """Computed idot from sign-magnitude representation.""" + return (self.idot_value - (1 << 13)) if self.idot_sign else self.idot_value + + class Subframe4(bs.BinaryStruct): + class IonosphereData(bs.BinaryStruct): + a0: Annotated[int, bs.s8] + a1: Annotated[int, bs.s8] + a2: Annotated[int, bs.s8] + a3: Annotated[int, bs.s8] + b0: Annotated[int, bs.s8] + b1: Annotated[int, bs.s8] + b2: Annotated[int, bs.s8] + b3: Annotated[int, bs.s8] + + data_id: Annotated[int, bs.bits(2)] + page_id: Annotated[int, bs.bits(6)] + body: Annotated[object, bs.switch('page_id', {56: IonosphereData})] + + tlm: Tlm + how: How + body: Annotated[ + object, + bs.switch( + 'how.subframe_id', + { + 1: Subframe1, + 2: Subframe2, + 3: Subframe3, + 4: Subframe4, + }, + ), + ] diff --git a/system/ubloxd/ubloxd.py b/system/ubloxd/ubloxd.py index 6882ad09551..e55cadcf78b 100755 --- a/system/ubloxd/ubloxd.py +++ b/system/ubloxd/ubloxd.py @@ -8,9 +8,9 @@ from cereal import log from cereal import messaging -from openpilot.system.ubloxd.generated.ubx import Ubx -from openpilot.system.ubloxd.generated.gps import Gps -from openpilot.system.ubloxd.generated.glonass import Glonass +from openpilot.system.ubloxd.ubx import Ubx +from openpilot.system.ubloxd.gps import Gps +from openpilot.system.ubloxd.glonass import Glonass SECS_IN_MIN = 60 @@ -52,7 +52,7 @@ def add_data(self, log_time: float, incoming: bytes) -> list[bytes]: # find preamble if len(self.buf) < 2: break - start = self.buf.find(b"\xB5\x62") + start = self.buf.find(b"\xb5\x62") if start < 0: # no preamble in buffer self.buf.clear() @@ -98,9 +98,22 @@ class UbloxMsgParser: # user range accuracy in meters glonass_URA_lookup: dict[int, float] = { - 0: 1, 1: 2, 2: 2.5, 3: 4, 4: 5, 5: 7, - 6: 10, 7: 12, 8: 14, 9: 16, 10: 32, - 11: 64, 12: 128, 13: 256, 14: 512, 15: 1024, + 0: 1, + 1: 2, + 2: 2.5, + 3: 4, + 4: 5, + 5: 7, + 6: 10, + 7: 12, + 8: 14, + 9: 16, + 10: 32, + 11: 64, + 12: 128, + 13: 256, + 14: 512, + 15: 1024, } def __init__(self) -> None: @@ -121,7 +134,7 @@ def parse_frame(self, frame: bytes) -> tuple[str, capnp.lib.capnp._DynamicStruct body = Ubx.NavPvt.from_bytes(payload) return self._gen_nav_pvt(body) if msg_type == 0x0213: - # Manually parse RXM-SFRBX to avoid Kaitai EOF on some frames + # Manually parse RXM-SFRBX to avoid EOF on some frames if len(payload) < 8: return None gnss_id = payload[0] @@ -134,7 +147,7 @@ def parse_frame(self, frame: bytes) -> tuple[str, capnp.lib.capnp._DynamicStruct words: list[int] = [] off = 8 for _ in range(num_words): - words.append(int.from_bytes(payload[off:off+4], 'little')) + words.append(int.from_bytes(payload[off : off + 4], 'little')) off += 4 class _SfrbxView: @@ -143,6 +156,7 @@ def __init__(self, gid: int, sid: int, fid: int, body: list[int]): self.sv_id = sid self.freq_id = fid self.body = body + view = _SfrbxView(gnss_id, sv_id, freq_id, words) return self._gen_rxm_sfrbx(view) if msg_type == 0x0215: @@ -515,5 +529,6 @@ def main(): service, dat = res pm.send(service, dat) + if __name__ == '__main__': main() diff --git a/system/ubloxd/ubx.ksy b/system/ubloxd/ubx.ksy deleted file mode 100644 index 02c757fe717..00000000000 --- a/system/ubloxd/ubx.ksy +++ /dev/null @@ -1,293 +0,0 @@ -meta: - id: ubx - endian: le -seq: - - id: magic - contents: [0xb5, 0x62] - - id: msg_type - type: u2be - - id: length - type: u2 - - id: body - type: - switch-on: msg_type - cases: - 0x0107: nav_pvt - 0x0213: rxm_sfrbx - 0x0215: rxm_rawx - 0x0a09: mon_hw - 0x0a0b: mon_hw2 - 0x0135: nav_sat -instances: - checksum: - pos: length + 6 - type: u2 - -types: - mon_hw: - seq: - - id: pin_sel - type: u4 - - id: pin_bank - type: u4 - - id: pin_dir - type: u4 - - id: pin_val - type: u4 - - id: noise_per_ms - type: u2 - - id: agc_cnt - type: u2 - - id: a_status - type: u1 - enum: antenna_status - - id: a_power - type: u1 - enum: antenna_power - - id: flags - type: u1 - - id: reserved1 - size: 1 - - id: used_mask - type: u4 - - id: vp - size: 17 - - id: jam_ind - type: u1 - - id: reserved2 - size: 2 - - id: pin_irq - type: u4 - - id: pull_h - type: u4 - - id: pull_l - type: u4 - enums: - antenna_status: - 0: init - 1: dontknow - 2: ok - 3: short - 4: open - antenna_power: - 0: off - 1: on - 2: dontknow - - mon_hw2: - seq: - - id: ofs_i - type: s1 - - id: mag_i - type: u1 - - id: ofs_q - type: s1 - - id: mag_q - type: u1 - - id: cfg_source - type: u1 - enum: config_source - - id: reserved1 - size: 3 - - id: low_lev_cfg - type: u4 - - id: reserved2 - size: 8 - - id: post_status - type: u4 - - id: reserved3 - size: 4 - - enums: - config_source: - 113: rom - 111: otp - 112: config_pins - 102: flash - - rxm_sfrbx: - seq: - - id: gnss_id - type: u1 - enum: gnss_type - - id: sv_id - type: u1 - - id: reserved1 - size: 1 - - id: freq_id - type: u1 - - id: num_words - type: u1 - - id: reserved2 - size: 1 - - id: version - type: u1 - - id: reserved3 - size: 1 - - id: body - type: u4 - repeat: expr - repeat-expr: num_words - - rxm_rawx: - seq: - - id: rcv_tow - type: f8 - - id: week - type: u2 - - id: leap_s - type: s1 - - id: num_meas - type: u1 - - id: rec_stat - type: u1 - - id: reserved1 - size: 3 - - id: meas - type: measurement - size: 32 - repeat: expr - repeat-expr: num_meas - types: - measurement: - seq: - - id: pr_mes - type: f8 - - id: cp_mes - type: f8 - - id: do_mes - type: f4 - - id: gnss_id - type: u1 - enum: gnss_type - - id: sv_id - type: u1 - - id: reserved2 - size: 1 - - id: freq_id - type: u1 - - id: lock_time - type: u2 - - id: cno - type: u1 - - id: pr_stdev - type: u1 - - id: cp_stdev - type: u1 - - id: do_stdev - type: u1 - - id: trk_stat - type: u1 - - id: reserved3 - size: 1 - nav_sat: - seq: - - id: itow - type: u4 - - id: version - type: u1 - - id: num_svs - type: u1 - - id: reserved - size: 2 - - id: svs - type: nav - size: 12 - repeat: expr - repeat-expr: num_svs - types: - nav: - seq: - - id: gnss_id - type: u1 - enum: gnss_type - - id: sv_id - type: u1 - - id: cno - type: u1 - - id: elev - type: s1 - - id: azim - type: s2 - - id: pr_res - type: s2 - - id: flags - type: u4 - - nav_pvt: - seq: - - id: i_tow - type: u4 - - id: year - type: u2 - - id: month - type: u1 - - id: day - type: u1 - - id: hour - type: u1 - - id: min - type: u1 - - id: sec - type: u1 - - id: valid - type: u1 - - id: t_acc - type: u4 - - id: nano - type: s4 - - id: fix_type - type: u1 - - id: flags - type: u1 - - id: flags2 - type: u1 - - id: num_sv - type: u1 - - id: lon - type: s4 - - id: lat - type: s4 - - id: height - type: s4 - - id: h_msl - type: s4 - - id: h_acc - type: u4 - - id: v_acc - type: u4 - - id: vel_n - type: s4 - - id: vel_e - type: s4 - - id: vel_d - type: s4 - - id: g_speed - type: s4 - - id: head_mot - type: s4 - - id: s_acc - type: s4 - - id: head_acc - type: u4 - - id: p_dop - type: u2 - - id: flags3 - type: u1 - - id: reserved1 - size: 5 - - id: head_veh - type: s4 - - id: mag_dec - type: s2 - - id: mag_acc - type: u2 -enums: - gnss_type: - 0: gps - 1: sbas - 2: galileo - 3: beidou - 4: imes - 5: qzss - 6: glonass diff --git a/system/ubloxd/ubx.py b/system/ubloxd/ubx.py new file mode 100644 index 00000000000..857498ebf13 --- /dev/null +++ b/system/ubloxd/ubx.py @@ -0,0 +1,180 @@ +""" +UBX protocol parser +""" + +from enum import IntEnum +from typing import Annotated + +from openpilot.system.ubloxd import binary_struct as bs + + +class GnssType(IntEnum): + gps = 0 + sbas = 1 + galileo = 2 + beidou = 3 + imes = 4 + qzss = 5 + glonass = 6 + + +class Ubx(bs.BinaryStruct): + GnssType = GnssType + + class RxmRawx(bs.BinaryStruct): + class Measurement(bs.BinaryStruct): + pr_mes: Annotated[float, bs.f64] + cp_mes: Annotated[float, bs.f64] + do_mes: Annotated[float, bs.f32] + gnss_id: Annotated[GnssType | int, bs.enum(bs.u8, GnssType)] + sv_id: Annotated[int, bs.u8] + reserved2: Annotated[bytes, bs.bytes_field(1)] + freq_id: Annotated[int, bs.u8] + lock_time: Annotated[int, bs.u16] + cno: Annotated[int, bs.u8] + pr_stdev: Annotated[int, bs.u8] + cp_stdev: Annotated[int, bs.u8] + do_stdev: Annotated[int, bs.u8] + trk_stat: Annotated[int, bs.u8] + reserved3: Annotated[bytes, bs.bytes_field(1)] + + rcv_tow: Annotated[float, bs.f64] + week: Annotated[int, bs.u16] + leap_s: Annotated[int, bs.s8] + num_meas: Annotated[int, bs.u8] + rec_stat: Annotated[int, bs.u8] + reserved1: Annotated[bytes, bs.bytes_field(3)] + meas: Annotated[list[Measurement], bs.array(Measurement, count_field='num_meas')] + + class RxmSfrbx(bs.BinaryStruct): + gnss_id: Annotated[GnssType | int, bs.enum(bs.u8, GnssType)] + sv_id: Annotated[int, bs.u8] + reserved1: Annotated[bytes, bs.bytes_field(1)] + freq_id: Annotated[int, bs.u8] + num_words: Annotated[int, bs.u8] + reserved2: Annotated[bytes, bs.bytes_field(1)] + version: Annotated[int, bs.u8] + reserved3: Annotated[bytes, bs.bytes_field(1)] + body: Annotated[list[int], bs.array(bs.u32, count_field='num_words')] + + class NavSat(bs.BinaryStruct): + class Nav(bs.BinaryStruct): + gnss_id: Annotated[GnssType | int, bs.enum(bs.u8, GnssType)] + sv_id: Annotated[int, bs.u8] + cno: Annotated[int, bs.u8] + elev: Annotated[int, bs.s8] + azim: Annotated[int, bs.s16] + pr_res: Annotated[int, bs.s16] + flags: Annotated[int, bs.u32] + + itow: Annotated[int, bs.u32] + version: Annotated[int, bs.u8] + num_svs: Annotated[int, bs.u8] + reserved: Annotated[bytes, bs.bytes_field(2)] + svs: Annotated[list[Nav], bs.array(Nav, count_field='num_svs')] + + class NavPvt(bs.BinaryStruct): + i_tow: Annotated[int, bs.u32] + year: Annotated[int, bs.u16] + month: Annotated[int, bs.u8] + day: Annotated[int, bs.u8] + hour: Annotated[int, bs.u8] + min: Annotated[int, bs.u8] + sec: Annotated[int, bs.u8] + valid: Annotated[int, bs.u8] + t_acc: Annotated[int, bs.u32] + nano: Annotated[int, bs.s32] + fix_type: Annotated[int, bs.u8] + flags: Annotated[int, bs.u8] + flags2: Annotated[int, bs.u8] + num_sv: Annotated[int, bs.u8] + lon: Annotated[int, bs.s32] + lat: Annotated[int, bs.s32] + height: Annotated[int, bs.s32] + h_msl: Annotated[int, bs.s32] + h_acc: Annotated[int, bs.u32] + v_acc: Annotated[int, bs.u32] + vel_n: Annotated[int, bs.s32] + vel_e: Annotated[int, bs.s32] + vel_d: Annotated[int, bs.s32] + g_speed: Annotated[int, bs.s32] + head_mot: Annotated[int, bs.s32] + s_acc: Annotated[int, bs.s32] + head_acc: Annotated[int, bs.u32] + p_dop: Annotated[int, bs.u16] + flags3: Annotated[int, bs.u8] + reserved1: Annotated[bytes, bs.bytes_field(5)] + head_veh: Annotated[int, bs.s32] + mag_dec: Annotated[int, bs.s16] + mag_acc: Annotated[int, bs.u16] + + class MonHw2(bs.BinaryStruct): + class ConfigSource(IntEnum): + flash = 102 + otp = 111 + config_pins = 112 + rom = 113 + + ofs_i: Annotated[int, bs.s8] + mag_i: Annotated[int, bs.u8] + ofs_q: Annotated[int, bs.s8] + mag_q: Annotated[int, bs.u8] + cfg_source: Annotated[ConfigSource | int, bs.enum(bs.u8, ConfigSource)] + reserved1: Annotated[bytes, bs.bytes_field(3)] + low_lev_cfg: Annotated[int, bs.u32] + reserved2: Annotated[bytes, bs.bytes_field(8)] + post_status: Annotated[int, bs.u32] + reserved3: Annotated[bytes, bs.bytes_field(4)] + + class MonHw(bs.BinaryStruct): + class AntennaStatus(IntEnum): + init = 0 + dontknow = 1 + ok = 2 + short = 3 + open = 4 + + class AntennaPower(IntEnum): + false = 0 + true = 1 + dontknow = 2 + + pin_sel: Annotated[int, bs.u32] + pin_bank: Annotated[int, bs.u32] + pin_dir: Annotated[int, bs.u32] + pin_val: Annotated[int, bs.u32] + noise_per_ms: Annotated[int, bs.u16] + agc_cnt: Annotated[int, bs.u16] + a_status: Annotated[AntennaStatus | int, bs.enum(bs.u8, AntennaStatus)] + a_power: Annotated[AntennaPower | int, bs.enum(bs.u8, AntennaPower)] + flags: Annotated[int, bs.u8] + reserved1: Annotated[bytes, bs.bytes_field(1)] + used_mask: Annotated[int, bs.u32] + vp: Annotated[bytes, bs.bytes_field(17)] + jam_ind: Annotated[int, bs.u8] + reserved2: Annotated[bytes, bs.bytes_field(2)] + pin_irq: Annotated[int, bs.u32] + pull_h: Annotated[int, bs.u32] + pull_l: Annotated[int, bs.u32] + + magic: Annotated[bytes, bs.const(bs.bytes_field(2), b"\xb5\x62")] + msg_type: Annotated[int, bs.u16be] + length: Annotated[int, bs.u16] + body: Annotated[ + object, + bs.substream( + 'length', + bs.switch( + 'msg_type', + { + 0x0107: NavPvt, + 0x0213: RxmSfrbx, + 0x0215: RxmRawx, + 0x0A09: MonHw, + 0x0A0B: MonHw2, + 0x0135: NavSat, + }, + ), + ), + ] + checksum: Annotated[int, bs.u16] diff --git a/uv.lock b/uv.lock index 2c5f32ec7b9..572305f08c0 100644 --- a/uv.lock +++ b/uv.lock @@ -768,15 +768,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/9e/820c4b086ad01ba7d77369fb8b11470a01fac9b4977f02e18659cf378b6b/json_rpc-1.15.0-py2.py3-none-any.whl", hash = "sha256:4a4668bbbe7116feb4abbd0f54e64a4adcf4b8f648f19ffa0848ad0f6606a9bf", size = 39450, upload-time = "2023-06-11T09:45:47.136Z" }, ] -[[package]] -name = "kaitaistruct" -version = "0.11" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/b8/ca7319556912f68832daa4b81425314857ec08dfccd8dbc8c0f65c992108/kaitaistruct-0.11.tar.gz", hash = "sha256:053ee764288e78b8e53acf748e9733268acbd579b8d82a427b1805453625d74b", size = 11519, upload-time = "2025-09-08T15:46:25.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/4a/cf14bf3b1f5ffb13c69cf5f0ea78031247790558ee88984a8bdd22fae60d/kaitaistruct-0.11-py2.py3-none-any.whl", hash = "sha256:5c6ce79177b4e193a577ecd359e26516d1d6d000a0bffd6e1010f2a46a62a561", size = 11372, upload-time = "2025-09-08T15:46:23.635Z" }, -] - [[package]] name = "kiwisolver" version = "1.4.9" @@ -1296,7 +1287,6 @@ dependencies = [ { name = "inputs" }, { name = "jeepney" }, { name = "json-rpc" }, - { name = "kaitaistruct" }, { name = "libusb1" }, { name = "mapbox-earcut" }, { name = "numpy" }, @@ -1384,7 +1374,6 @@ requires-dist = [ { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, - { name = "kaitaistruct" }, { name = "libusb1" }, { name = "mapbox-earcut" }, { name = "matplotlib", marker = "extra == 'dev'" }, From 831f2396d9a60eb8c73b7d0af4c4961353c73e51 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 2 Feb 2026 08:08:09 -0800 Subject: [PATCH 097/685] bump opendbc --- opendbc_repo | 2 +- pyproject.toml | 1 - uv.lock | 11 ----------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index e76c2cf5bb0..7c78ee87b7b 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit e76c2cf5bb0042bc5822efa78fff0362feed7b54 +Subproject commit 7c78ee87b7b54bb2179d86d5e28c1f65bbf96669 diff --git a/pyproject.toml b/pyproject.toml index 1be5c395f1c..c80d23b8ebb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -74,7 +74,6 @@ dependencies = [ [project.optional-dependencies] docs = [ "Jinja2", - "natsort", "mkdocs", ] diff --git a/uv.lock b/uv.lock index 572305f08c0..054a67cc9a2 100644 --- a/uv.lock +++ b/uv.lock @@ -1183,15 +1183,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/08/7036c080d7117f28a4af526d794aab6a84463126db031b007717c1a6676e/multidict-6.7.1-py3-none-any.whl", hash = "sha256:55d97cc6dae627efa6a6e548885712d4864b81110ac76fa4e534c03819fa4a56", size = 12319, upload-time = "2026-01-26T02:46:44.004Z" }, ] -[[package]] -name = "natsort" -version = "8.4.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, -] - [[package]] name = "numpy" version = "2.4.1" @@ -1331,7 +1322,6 @@ dev = [ docs = [ { name = "jinja2" }, { name = "mkdocs" }, - { name = "natsort" }, ] testing = [ { name = "codespell" }, @@ -1379,7 +1369,6 @@ requires-dist = [ { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" }, { name = "mkdocs", marker = "extra == 'docs'" }, - { name = "natsort", marker = "extra == 'docs'" }, { name = "numpy", specifier = ">=2.0" }, { name = "onnx", specifier = ">=1.14.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, From fd50941cff09b6710358aa9651c7dfeb94ffe79c Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 2 Feb 2026 09:13:49 -0700 Subject: [PATCH 098/685] chore: bump minimum Python version to 3.12.3 (#37052) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c80d23b8ebb..2400364c706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "openpilot" -requires-python = ">= 3.11, < 3.13" +requires-python = ">= 3.12.3, < 3.13" license = {text = "MIT License"} version = "0.1.0" description = "an open source driver assistance system" From a668bc9edad0223a79c797b8943fe8daaa95ade1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Feb 2026 16:58:45 -0800 Subject: [PATCH 099/685] comma four setup improvements (#37066) * always check, no flickering from has inter -> waiting -> has inter from the reset * 1s interval. i see read timeouts at 0.5s sometimes * clean up * cursor * Revert "cursor" This reverts commit 13ec6312aa7f71b58771f8789456e97c4481856a. * clean up --- system/ui/mici_setup.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 2c6090b4ac5..fac26f06eac 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -49,7 +49,7 @@ class NetworkConnectivityMonitor: - def __init__(self, should_check: Callable[[], bool] | None = None, check_interval: float = 0.5): + def __init__(self, should_check: Callable[[], bool] | None = None, check_interval: float = 1.0): self.network_connected = threading.Event() self.wifi_connected = threading.Event() self._should_check = should_check or (lambda: True) @@ -78,7 +78,7 @@ def _run(self): if self._should_check(): try: request = urllib.request.Request(OPENPILOT_URL, method="HEAD") - urllib.request.urlopen(request, timeout=0.5) + urllib.request.urlopen(request, timeout=1.0) self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() @@ -528,9 +528,8 @@ def __init__(self): self.download_thread = None self._wifi_manager = WifiManager() self._wifi_manager.set_active(True) - self._network_monitor = NetworkConnectivityMonitor( - lambda: self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE) - ) + self._network_monitor = NetworkConnectivityMonitor() + self._network_monitor.start() self._prev_has_internet = False gui_app.set_modal_overlay_tick(self._modal_overlay_tick) @@ -569,10 +568,8 @@ def _set_state(self, state: SetupState): if self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE): self._network_setup_page.show_event() self._network_monitor.reset() - self._network_monitor.start() else: self._network_setup_page.hide_event() - self._network_monitor.stop() def _render(self, rect: rl.Rectangle): if self.state == SetupState.GETTING_STARTED: @@ -618,7 +615,6 @@ def _network_setup_back_button_callback(self): self._set_state(SetupState.SOFTWARE_SELECTION) def _network_setup_continue_button_callback(self): - self._network_monitor.stop() if self.state == SetupState.NETWORK_SETUP: self.download(OPENPILOT_URL) elif self.state == SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE: @@ -628,10 +624,10 @@ def close(self): self._network_monitor.stop() def render_network_setup(self, rect: rl.Rectangle): - self._network_setup_page.render(rect) has_internet = self._network_monitor.network_connected.is_set() self._prev_has_internet = has_internet self._network_setup_page.set_has_internet(has_internet) + self._network_setup_page.render(rect) def render_downloading(self, rect: rl.Rectangle): self._downloading_page.set_progress(self.download_progress) From 85b9f8962e8330283f3ec59a025ee97328dc29ae Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Feb 2026 22:32:52 -0800 Subject: [PATCH 100/685] Clean up four keyboard text rects (#37068) * start clean up * rm * not really needed * more * clean up --- selfdrive/ui/mici/widgets/dialog.py | 43 ++++++++++++----------------- 1 file changed, 17 insertions(+), 26 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 67123d33a79..b23abe6080d 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -132,6 +132,7 @@ def _render(self, _) -> DialogResult: class BigInputDialog(BigDialogBase): BACK_TOUCH_AREA_PERCENTAGE = 0.2 BACKSPACE_RATE = 25 # hz + TEXT_INPUT_SIZE = 35 def __init__(self, hint: str, @@ -179,53 +180,44 @@ def _update_state(self): self._backspace_held_time = None def _render(self, _): - text_input_size = 35 - # draw current text so far below everything. text floats left but always stays in view text = self._keyboard.text() candidate_char = self._keyboard.get_candidate_character() - text_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), text + candidate_char or self._hint_label.text, text_input_size) - text_x = PADDING * 2 + self._enter_img.width - - # text needs to move left if we're at the end where right button is - text_rect = rl.Rectangle(text_x, - int(self._rect.y + PADDING), - # clip width to right button when in view - int(self._rect.width - text_x - PADDING * 2 - self._enter_img.width + 5), # TODO: why 5? - int(text_size.y)) + text_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), text + candidate_char or self._hint_label.text, self.TEXT_INPUT_SIZE) - # draw rounded background for text input bg_block_margin = 5 - text_field_rect = rl.Rectangle(text_rect.x - bg_block_margin, text_rect.y - bg_block_margin, - text_rect.width + bg_block_margin * 2, text_input_size + bg_block_margin * 2) + text_x = PADDING * 2 + self._enter_img.width + bg_block_margin + text_field_rect = rl.Rectangle(text_x, int(self._rect.y + PADDING) - bg_block_margin, + int(self._rect.width - text_x - PADDING * 2 - self._enter_img.width) - bg_block_margin * 2, + int(text_size.y)) # draw text input # push text left with a gradient on left side if too long - if text_size.x > text_rect.width: - text_x -= text_size.x - text_rect.width + if text_size.x > text_field_rect.width: + text_x -= text_size.x - text_field_rect.width - rl.begin_scissor_mode(int(text_rect.x), int(text_rect.y), int(text_rect.width), int(text_rect.height)) - rl.draw_text_ex(gui_app.font(FontWeight.ROMAN), text, rl.Vector2(text_x, text_rect.y), text_input_size, 0, rl.WHITE) + rl.begin_scissor_mode(int(text_field_rect.x), int(text_field_rect.y), int(text_field_rect.width), int(text_field_rect.height)) + rl.draw_text_ex(gui_app.font(FontWeight.ROMAN), text, rl.Vector2(text_x, text_field_rect.y), self.TEXT_INPUT_SIZE, 0, rl.WHITE) # draw grayed out character user is hovering over if candidate_char: - candidate_char_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), candidate_char, text_input_size) + candidate_char_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), candidate_char, self.TEXT_INPUT_SIZE) rl.draw_text_ex(gui_app.font(FontWeight.ROMAN), candidate_char, - rl.Vector2(min(text_x + text_size.x, text_rect.x + text_rect.width) - candidate_char_size.x, text_rect.y), - text_input_size, 0, rl.Color(255, 255, 255, 128)) + rl.Vector2(min(text_x + text_size.x, text_field_rect.x + text_field_rect.width) - candidate_char_size.x, text_field_rect.y), + self.TEXT_INPUT_SIZE, 0, rl.Color(255, 255, 255, 128)) rl.end_scissor_mode() # draw gradient on left side to indicate more text - if text_size.x > text_rect.width: - rl.draw_rectangle_gradient_h(int(text_rect.x), int(text_rect.y), 80, int(text_rect.height), + if text_size.x > text_field_rect.width: + rl.draw_rectangle_gradient_h(int(text_field_rect.x), int(text_field_rect.y), 80, int(text_field_rect.height), rl.BLACK, rl.BLANK) # draw cursor if text: blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 - cursor_x = min(text_x + text_size.x + 3, text_rect.x + text_rect.width) - rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_rect.y), 4, int(text_size.y)), + cursor_x = min(text_x + text_size.x + 3, text_field_rect.x + text_field_rect.width) + rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_field_rect.y), 4, int(text_size.y)), 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) # draw backspace icon with nice fade @@ -255,7 +247,6 @@ def _render(self, _): # draw debugging rect bounds if DEBUG: rl.draw_rectangle_lines_ex(text_field_rect, 1, rl.Color(100, 100, 100, 255)) - rl.draw_rectangle_lines_ex(text_rect, 1, rl.Color(0, 255, 0, 255)) rl.draw_rectangle_lines_ex(self._top_right_button_rect, 1, rl.Color(0, 255, 0, 255)) rl.draw_rectangle_lines_ex(self._top_left_button_rect, 1, rl.Color(0, 255, 0, 255)) From aac90dd11b449996307b863e1ef0564cfdc771fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 3 Feb 2026 13:59:45 -0800 Subject: [PATCH 101/685] Bump tg (#37069) bump tg --- tinygrad_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tinygrad_repo b/tinygrad_repo index 774a454bb5e..2f55005ad93 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 774a454bb5e6d0fe3756a8add9302c0a3d592bd9 +Subproject commit 2f55005ad93c777cca69b20dddc28c7f02f0eb01 From 54cf8d6a5ecb59714c4ac8991e53fa85269b31a1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Feb 2026 15:55:05 -0800 Subject: [PATCH 102/685] four keyboard: fix keys lagging behind parent widget (#37073) * fix keys lagging behind * use parent rect * use parent rect * cmt --- system/ui/widgets/mici_keyboard.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 7fc3847809f..6d2e08e0539 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -53,20 +53,23 @@ def __init__(self, char: str, font_weight: FontWeight = FontWeight.SEMI_BOLD): self.original_position = rl.Vector2(0, 0) def set_position(self, x: float, y: float, smooth: bool = True): - # TODO: swipe up from NavWidget has the keys lag behind other elements a bit + # Smooth keys within parent rect + base_y = self._parent_rect.y if self._parent_rect else 0.0 + local_y = y - base_y + if not self._position_initialized: self._x_filter.x = x - self._y_filter.x = y + self._y_filter.x = local_y # keep track of original position so dragging around feels consistent. also move touch area down a bit self.original_position = rl.Vector2(x, y + KEY_TOUCH_AREA_OFFSET) self._position_initialized = True if not smooth: self._x_filter.x = x - self._y_filter.x = y + self._y_filter.x = local_y self._rect.x = self._x_filter.update(x) - self._rect.y = self._y_filter.update(y) + self._rect.y = base_y + self._y_filter.update(local_y) def set_alpha(self, alpha: float): self._alpha_filter.update(alpha) @@ -367,6 +370,7 @@ def _lay_out_keys(self, bg_x, bg_y, keys: list[list[Key]]): key.set_font_size(font_size) # TODO: I like the push amount, so we should clip the pos inside the keyboard rect + key.set_parent_rect(self._rect) key.set_position(key_x, key_y) def _render(self, _): From ee7601ae9db36708281fa42c35b5c9e31b74a888 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 3 Feb 2026 15:55:13 -0800 Subject: [PATCH 103/685] long planner: Min(stopping) is also important (#37074) Min(stopping) is also important --- selfdrive/controls/lib/longitudinal_planner.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index ad84ecf24fa..c5c03eba180 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -152,10 +152,11 @@ def update(self, sm): output_a_target_e2e = sm['modelV2'].action.desiredAcceleration output_should_stop_e2e = sm['modelV2'].action.shouldStop - if (output_a_target_e2e < output_a_target_mpc) and sm['selfdriveState'].experimentalMode: - output_a_target = output_a_target_e2e - self.output_should_stop = output_should_stop_e2e - self.mpc.source = SOURCES[3] + if sm['selfdriveState'].experimentalMode: + output_a_target = min(output_a_target_e2e, output_a_target_mpc) + self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc + if output_a_target < output_a_target_mpc: + self.mpc.source = SOURCES[3] else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc From 5b6436a90cf6902b8aaa71c2b6f3d7164d8ae391 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Tue, 3 Feb 2026 19:14:02 -0800 Subject: [PATCH 104/685] CD210 model (#37050) a27b3122-733e-4a65-938b-acfebebbe5e8/100 --- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 92c81954d29..611ae9fe85f 100644 --- a/selfdrive/modeld/models/driving_policy.onnx +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1edea5bb56f876db4cec97c150799513f6a59373f3ad152d55e4dcaab1b809e3 -size 13926324 +oid sha256:78477124cbf3ffe30fa951ebada8410b43c4242c6054584d656f1d329b067e15 +size 14060847 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 76c96670a92..6c9fc4c84d3 100644 --- a/selfdrive/modeld/models/driving_vision.onnx +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1dc66bc06f250b577653ccbeaa2c6521b3d46749f601d0a1a366419e929ca438 -size 46271942 +oid sha256:ee29ee5bce84d1ce23e9ff381280de9b4e4d96d2934cd751740354884e112c66 +size 46877473 From 5e35feb3aba8b2f8c7f8d03c1a5949555bfde097 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:25:09 -0600 Subject: [PATCH 105/685] clips: allow mici UI (now default) (#37070) fix: make big false by default and remove assertion --- tools/clip/run.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 5fb693f30a4..f2b871be69b 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -40,7 +40,7 @@ def parse_args(): parser.add_argument("-f", "--file-size", type=float, default=9.0, help="Target file size in MB") parser.add_argument("-x", "--speed", type=int, default=1, help="Speed multiplier") parser.add_argument("--demo", action="store_true", help="Use demo route with default timing") - parser.add_argument("--big", action="store_true", default=True, help="Use big UI (2160x1080)") + parser.add_argument("--big", action="store_true", help="Use big UI (2160x1080)") parser.add_argument("--qcam", action="store_true", help="Use qcamera instead of fcamera") parser.add_argument("--windowed", action="store_true", help="Show window") parser.add_argument("--no-metadata", action="store_true", help="Disable metadata overlay") @@ -329,7 +329,6 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, def main(): logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s\t%(message)s") args = parse_args() - assert args.big, "Clips doesn't support mici UI yet. TODO: make it work" setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start) clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, not args.windowed, From 8879d481e55bc026cf2750aaee984866bd5a4f6c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 16:04:49 -0800 Subject: [PATCH 106/685] comma four: new keyboard enter button (#37072) * works * enter dis * clean up * clean up * no debug * useless * poadding --- .../icons_mici/settings/keyboard/confirm.png | 3 --- .../icons_mici/settings/keyboard/enter.png | 3 +++ .../settings/keyboard/enter_disabled.png | 3 +++ selfdrive/ui/mici/widgets/dialog.py | 19 +++++++++++-------- 4 files changed, 17 insertions(+), 11 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/settings/keyboard/confirm.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/enter.png create mode 100644 selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png diff --git a/selfdrive/assets/icons_mici/settings/keyboard/confirm.png b/selfdrive/assets/icons_mici/settings/keyboard/confirm.png deleted file mode 100644 index 98ca5c61dc8..00000000000 --- a/selfdrive/assets/icons_mici/settings/keyboard/confirm.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43b64365a42d7bf772d567b8867a6ced4ec0175bb88b6acaa3a5345f19ca696e -size 1268 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/enter.png b/selfdrive/assets/icons_mici/settings/keyboard/enter.png new file mode 100644 index 00000000000..0b7fc95c510 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/enter.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3dd956d5ccfce01a01bea74ef59c9e73dfca406a5ff9ac62417203afa6027fba +size 5620 diff --git a/selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png b/selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png new file mode 100644 index 00000000000..251d5d8d140 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/keyboard/enter_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1dd1c2308872729d58adab390030ae9c987dc7908f0c39391651ea2b6cb620c5 +size 2445 diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index b23abe6080d..49d73f7b0d4 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -151,7 +151,8 @@ def __init__(self, self._backspace_img = gui_app.texture("icons_mici/settings/keyboard/backspace.png", 42, 36) self._backspace_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) - self._enter_img = gui_app.texture("icons_mici/settings/keyboard/confirm.png", 42, 36) + self._enter_img = gui_app.texture("icons_mici/settings/keyboard/enter.png", 76, 62) + self._enter_disabled_img = gui_app.texture("icons_mici/settings/keyboard/enter_disabled.png", 76, 62) self._enter_img_alpha = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) # rects for top buttons @@ -186,9 +187,9 @@ def _render(self, _): text_size = measure_text_cached(gui_app.font(FontWeight.ROMAN), text + candidate_char or self._hint_label.text, self.TEXT_INPUT_SIZE) bg_block_margin = 5 - text_x = PADDING * 2 + self._enter_img.width + bg_block_margin + text_x = PADDING / 2 + self._enter_img.width + PADDING text_field_rect = rl.Rectangle(text_x, int(self._rect.y + PADDING) - bg_block_margin, - int(self._rect.width - text_x - PADDING * 2 - self._enter_img.width) - bg_block_margin * 2, + int(self._rect.width - text_x * 2), int(text_size.y)) # draw text input @@ -224,7 +225,7 @@ def _render(self, _): self._backspace_img_alpha.update(255 * bool(text)) if self._backspace_img_alpha.x > 1: color = rl.Color(255, 255, 255, int(self._backspace_img_alpha.x)) - rl.draw_texture(self._backspace_img, int(self._rect.width - self._enter_img.width - 15), int(text_field_rect.y), color) + rl.draw_texture(self._backspace_img, int(self._rect.width - self._backspace_img.width - 27), int(self._rect.y + 14), color) if not text and self._hint_label.text and not candidate_char: # draw description if no text entered yet and not drawing candidate char @@ -236,10 +237,12 @@ def _render(self, _): self._top_right_button_rect = rl.Rectangle(text_field_rect.x + text_field_rect.width, self._rect.y, self._rect.width - (text_field_rect.x + text_field_rect.width), self._top_left_button_rect.height) - self._enter_img_alpha.update(255 if (len(text) >= self._minimum_length) else 255 * 0.35) - if self._enter_img_alpha.x > 1: - color = rl.Color(255, 255, 255, int(self._enter_img_alpha.x)) - rl.draw_texture(self._enter_img, int(self._rect.x + 15), int(text_field_rect.y), color) + # draw enter button + self._enter_img_alpha.update(255 if len(text) >= self._minimum_length else 0) + color = rl.Color(255, 255, 255, int(self._enter_img_alpha.x)) + rl.draw_texture(self._enter_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) + color = rl.Color(255, 255, 255, 255 - int(self._enter_img_alpha.x)) + rl.draw_texture(self._enter_disabled_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) # keyboard goes over everything self._keyboard.render(self._rect) From 3ea474dd5877300987841cf3f4df296a1a34f18a Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 4 Feb 2026 19:31:44 -0500 Subject: [PATCH 107/685] tools: fix Python version comparison using normalized semantic version format (#37075) --- tools/op.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/op.sh b/tools/op.sh index 8b5062ad9b4..8c41926e0ce 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -161,9 +161,9 @@ function op_check_python() { loge "ERROR_PYTHON_NOT_FOUND" return 1 else - LB=$(echo $REQUIRED_PYTHON_VERSION | tr -d -c '[0-9,]' | cut -d ',' -f1) - UB=$(echo $REQUIRED_PYTHON_VERSION | tr -d -c '[0-9,]' | cut -d ',' -f2) - VERSION=$(echo $INSTALLED_PYTHON_VERSION | grep -o '[0-9]\+\.[0-9]\+' | tr -d -c '[0-9]') + LB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($4, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') + UB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($6, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') + VERSION=$(echo $INSTALLED_PYTHON_VERSION | awk '{ split($2, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') if [[ $VERSION -ge LB && $VERSION -lt UB ]]; then echo -e " ↳ [${GREEN}✔${NC}] $INSTALLED_PYTHON_VERSION detected." else From 2230933d4be2c3db83e7737208065aba79b961a9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 16:38:24 -0800 Subject: [PATCH 108/685] Back to tethering BigButton (#37082) Back to tethering big button --- selfdrive/assets/icons_mici/tethering_short.png | 3 --- selfdrive/ui/mici/layouts/settings/network/__init__.py | 5 ++--- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/tethering_short.png diff --git a/selfdrive/assets/icons_mici/tethering_short.png b/selfdrive/assets/icons_mici/tethering_short.png deleted file mode 100644 index f97fed95ded..00000000000 --- a/selfdrive/assets/icons_mici/tethering_short.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fce940a3cbd2e9530e8efdde90794013a272919b2f3ea482bc06535c795640e7 -size 2176 diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 0d5e5278368..d3ca11ce336 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -4,7 +4,7 @@ from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigCircleToggle +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType @@ -39,8 +39,7 @@ def tethering_toggle_callback(checked: bool): self._network_metered_btn.set_enabled(False) self._wifi_manager.set_tethering_active(checked) - self._tethering_toggle_btn = BigCircleToggle("icons_mici/tethering_short.png", toggle_callback=tethering_toggle_callback, - icon_size=(82, 82), icon_offset=(0, 12)) + self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) def tethering_password_callback(password: str): if password: From 944c23f369ad660ee8f3d0ab260370ee6922f087 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 17:06:11 -0800 Subject: [PATCH 109/685] Stock LKAS: remove permanent alert (#37083) rm perm --- selfdrive/selfdrived/events.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 0e37a959c5e..6fb96b6e5a0 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -478,11 +478,6 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin }, EventName.stockLkas: { - ET.PERMANENT: Alert( - "Stock LKAS: Lane Departure Detected", - "", - AlertStatus.userPrompt, AlertSize.small, - Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.), ET.NO_ENTRY: NoEntryAlert("Stock LKAS: Lane Departure Detected"), }, From 1979a6d8e848b2946a7db1b8a866f6eaa45f2463 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 19:30:20 -0800 Subject: [PATCH 110/685] BigButton: remove unused scrolling (#37085) * BigButton: remove unused scrolling * clean up --- selfdrive/ui/mici/widgets/button.py | 39 ----------------------------- 1 file changed, 39 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 0b252c21aab..172a156c30f 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -5,7 +5,6 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import MiciLabel from openpilot.system.ui.widgets.scroller import DO_ZOOM -from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.common.filter_simple import BounceFilter @@ -129,12 +128,6 @@ def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "" self._load_images() - # internal state - self._scroll_offset = 0 # in pixels - self._needs_scroll = measure_text_cached(self._label_font, text, self._get_label_font_size()).x + 25 > self._rect.width - self._scroll_timer = 0 - self._scroll_state = ScrollState.PRE_SCROLL - def set_icon(self, icon: Union[str, rl.Texture]): self._txt_icon = gui_app.texture(icon, *self._icon_size) if isinstance(icon, str) and len(icon) else icon @@ -178,38 +171,6 @@ def get_value(self) -> str: def get_text(self): return self.text - def _update_state(self): - # hold on text for a bit, scroll, hold again, reset - if self._needs_scroll: - """`dt` should be seconds since last frame (rl.get_frame_time()).""" - # TODO: this comment is generated by GPT, prob wrong and misused - dt = rl.get_frame_time() - - self._scroll_timer += dt - if self._scroll_state == ScrollState.PRE_SCROLL: - if self._scroll_timer < 0.5: - return - self._scroll_state = ScrollState.SCROLLING - self._scroll_timer = 0 - - elif self._scroll_state == ScrollState.SCROLLING: - self._scroll_offset -= SCROLLING_SPEED_PX_S * dt - # reset when text has completely left the button + 50 px gap - # TODO: use global constant for 30+30 px gap - # TODO: add std Widget padding option integrated into the self._rect - full_len = measure_text_cached(self._label_font, self.text, self._get_label_font_size()).x + 30 + 30 - if self._scroll_offset < (self._rect.width - full_len): - self._scroll_state = ScrollState.POST_SCROLL - self._scroll_timer = 0 - - elif self._scroll_state == ScrollState.POST_SCROLL: - # wait for a bit before starting to scroll again - if self._scroll_timer < 0.75: - return - self._scroll_state = ScrollState.PRE_SCROLL - self._scroll_timer = 0 - self._scroll_offset = 0 - def _render(self, _): # draw _txt_default_bg txt_bg = self._txt_default_bg From 177a1a3c8b022c85e92a1bc68e32a136ee551735 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 22:25:28 -0800 Subject: [PATCH 111/685] BigButton: use UnifiedLabel (#37086) * BigButton: remove unused scrolling * start * debug * stash * actually removing the hardcoded size for multioption fixes it * back * cursor does sub label * clean up * more * more * try this for now * nick suggest * clean up * more * more --- selfdrive/ui/mici/layouts/settings/device.py | 4 +- selfdrive/ui/mici/widgets/button.py | 42 +++++++++----------- 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 30ea90f3d17..c12a92482c6 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -322,11 +322,11 @@ def selected_language_callback(): regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") regulatory_btn.set_click_callback(self._on_regulatory) - driver_cam_btn = BigButton("driver camera preview", "", "icons_mici/settings/device/cameras.png") + driver_cam_btn = BigButton("driver\ncamera preview", "", "icons_mici/settings/device/cameras.png") driver_cam_btn.set_click_callback(self._show_driver_camera) driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) - review_training_guide_btn = BigButton("review training guide", "", "icons_mici/settings/device/info.png") + review_training_guide_btn = BigButton("review\nntraining guide", "", "icons_mici/settings/device/info.png") review_training_guide_btn.set_click_callback(self._on_review_training_guide) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 172a156c30f..30a66376ea0 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -3,7 +3,7 @@ from enum import Enum from collections.abc import Callable from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.scroller import DO_ZOOM from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.common.filter_simple import BounceFilter @@ -119,12 +119,10 @@ def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "" self._label_font = gui_app.font(FontWeight.DISPLAY) self._value_font = gui_app.font(FontWeight.ROMAN) - self._label = MiciLabel(text, font_size=self._get_label_font_size(), width=int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2), - font_weight=FontWeight.DISPLAY, color=LABEL_COLOR, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, wrap_text=True) - self._sub_label = MiciLabel(value, font_size=COMPLICATION_SIZE, width=int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2), - font_weight=FontWeight.ROMAN, color=COMPLICATION_GREY, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, wrap_text=True) + self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.DISPLAY, + text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, line_height=0.9) + self._sub_label = UnifiedLabel(value, font_size=COMPLICATION_SIZE, font_weight=FontWeight.ROMAN, + text_color=COMPLICATION_GREY, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) self._load_images() @@ -142,15 +140,16 @@ def _load_images(self): self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180) self._txt_hover_bg = gui_app.texture("icons_mici/buttons/button_rectangle_hover.png", 402, 180) + def _width_hint(self) -> int: + return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2) + def _get_label_font_size(self): if len(self.text) < 12: font_size = 64 elif len(self.text) < 17: font_size = 48 - elif len(self.text) < 20: - font_size = 42 else: - font_size = 36 + font_size = 42 if self.value: font_size -= 20 @@ -189,14 +188,16 @@ def _render(self, _): ly = btn_y + self._rect.height - 33 # - 40# - self._get_label_font_size() / 2 if self.value: - self._sub_label.set_position(lx, ly) - ly -= self._sub_label.font_size + 9 - self._sub_label.render() + sub_label_height = self._sub_label.get_content_height(self._width_hint()) + sub_label_rect = rl.Rectangle(lx, ly - sub_label_height, self._width_hint(), sub_label_height) + self._sub_label.render(sub_label_rect) + ly -= sub_label_height label_color = LABEL_COLOR if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) self._label.set_color(label_color) - self._label.set_position(lx, ly) - self._label.render() + label_height = self._label.get_content_height(self._width_hint()) + label_rect = rl.Rectangle(lx, ly - label_height, self._width_hint(), label_height) + self._label.render(label_rect) # ICON ------------------------------------------------------------------- if self._txt_icon: @@ -219,8 +220,6 @@ def __init__(self, text: str, value: str = "", initial_state: bool = False, togg self._checked = initial_state self._toggle_callback = toggle_callback - self._label.set_font_size(48) - def _load_images(self): super()._load_images() self._txt_enabled_toggle = gui_app.texture("icons_mici/buttons/toggle_pill_enabled.png", 84, 66) @@ -258,15 +257,10 @@ def __init__(self, text: str, options: list[str], toggle_callback: Callable | No self._options = options self._select_callback = select_callback - self._label.set_width(int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width)) - # TODO: why isn't this automatic? - self._label.set_font_size(self._get_label_font_size()) - self.set_value(self._options[0]) - def _get_label_font_size(self): - font_size = super()._get_label_font_size() - return font_size - 6 + def _width_hint(self) -> int: + return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width) def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) From fc6afd5ab8bac4b19f0ca0564015884a9b90489b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 22:58:16 -0800 Subject: [PATCH 112/685] Network menu improvements (#37077) * try this * that's handy * todo, not sure what happens here * set_text * normalize * scroll wifi * clean up * more * better check --- .../mici/layouts/settings/network/__init__.py | 35 +++++++++++++------ .../mici/layouts/settings/network/wifi_ui.py | 6 +++- selfdrive/ui/mici/widgets/button.py | 11 ++++-- 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index d3ca11ce336..fb1d56a1f64 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -3,7 +3,7 @@ from collections.abc import Callable from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon, normalize_ssid from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.ui_state import ui_state @@ -55,9 +55,6 @@ def tethering_password_clicked(): self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) self._tethering_password_btn.set_click_callback(tethering_password_clicked) - # ******** IP Address ******** - self._ip_address_btn = BigButton("IP Address", "Not connected") - # ******** Network Metered ******** def network_metered_callback(value: str): self._network_metered_btn.set_enabled(False) @@ -73,8 +70,13 @@ def network_metered_callback(value: str): self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) self._network_metered_btn.set_enabled(False) - wifi_button = BigButton("wi-fi") - wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 64, 56) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 64, 47) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 64, 47) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47) + + self._wifi_button = BigButton("wi-fi", "not connected", self._wifi_slash_txt, scroll=True) + self._wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) # ******** Advanced settings ******** # ******** Roaming toggle ******** @@ -89,7 +91,7 @@ def network_metered_callback(value: str): # Main scroller ---------------------------------- self._scroller = Scroller([ - wifi_button, + self._wifi_button, self._network_metered_btn, self._tethering_toggle_btn, self._tethering_password_btn, @@ -98,7 +100,6 @@ def network_metered_callback(value: str): self._apn_btn, self._cellular_metered_btn, # */ - self._ip_address_btn, ], snap_items=False) # Set initial config @@ -157,8 +158,22 @@ def _on_network_updated(self, networks: list[Network]): self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) self._tethering_toggle_btn.set_checked(tethering_active) - # Update IP address - self._ip_address_btn.set_value(self._wifi_manager.ipv4_address or "Not connected") + # Update wi-fi button with ssid and ip address + # TODO: make sure we handle hidden ssids + connected_network = next((network for network in networks if network.is_connected), None) + self._wifi_button.set_text(normalize_ssid(connected_network.ssid) if connected_network is not None else "wi-fi") + self._wifi_button.set_value(self._wifi_manager.ipv4_address or "not connected") + if connected_network is not None: + strength = WifiIcon.get_strength_icon_idx(connected_network.strength) + if strength == 2: + strength_icon = self._wifi_full_txt + elif strength == 1: + strength_icon = self._wifi_medium_txt + else: + strength_icon = self._wifi_low_txt + self._wifi_button.set_icon(strength_icon) + else: + self._wifi_button.set_icon(self._wifi_slash_txt) # Update network metered self._network_metered_btn.set_value( diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 23b89438dca..7791f18cf73 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -50,12 +50,16 @@ def set_current_network(self, network: Network): def set_scale(self, scale: float): self._scale = scale + @staticmethod + def get_strength_icon_idx(strength: int) -> int: + return round(strength / 100 * 2) + def _render(self, _): if self._network is None: return # Determine which wifi strength icon to use - strength = round(self._network.strength / 100 * 2) + strength = self.get_strength_icon_idx(self._network.strength) if strength == 2: strength_icon = self._wifi_full_txt elif strength == 1: diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 30a66376ea0..a5f7ee686ed 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -104,12 +104,14 @@ def _render(self, _): class BigButton(Widget): """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" - def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64)): + def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64), + scroll: bool = False): super().__init__() self.set_rect(rl.Rectangle(0, 0, 402, 180)) self.text = text self.value = value self._icon_size = icon_size + self._scroll = scroll self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) @@ -120,7 +122,8 @@ def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "" self._value_font = gui_app.font(FontWeight.ROMAN) self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.DISPLAY, - text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, line_height=0.9) + text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, scroll=scroll, + line_height=0.9) self._sub_label = UnifiedLabel(value, font_size=COMPLICATION_SIZE, font_weight=FontWeight.ROMAN, text_color=COMPLICATION_GREY, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) @@ -141,7 +144,9 @@ def _load_images(self): self._txt_hover_bg = gui_app.texture("icons_mici/buttons/button_rectangle_hover.png", 402, 180) def _width_hint(self) -> int: - return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2) + # Single line if scrolling, so hide behind icon if exists + icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0 + return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): if len(self.text) < 12: From 39dcc883301f47c4061e3206cba6d5d50ab72778 Mon Sep 17 00:00:00 2001 From: ugtthis <142481257+ugtthis@users.noreply.github.com> Date: Thu, 5 Feb 2026 01:03:01 -0600 Subject: [PATCH 113/685] UI: only show `onroad_fade.png` when engaged (#37051) * only show when engaged * retrigger CI * fade animation 0.1 * nl --------- Co-authored-by: Shane Smiskol --- selfdrive/ui/mici/onroad/augmented_road_view.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 4e00a3aafe1..69bcca401d9 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -14,7 +14,7 @@ from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets import Widget -from openpilot.common.filter_simple import BounceFilter +from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame from openpilot.common.transformations.orientation import rot_from_euler from enum import IntEnum @@ -165,6 +165,7 @@ def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = Visio alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png") + self._fade_alpha_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) # debug self._pm = messaging.PubMaster(['uiDebug']) @@ -217,8 +218,11 @@ def _render(self, _): # Draw all UI overlays self._model_renderer.render(self._content_rect) - # Fade out bottom of overlays for looks - rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, rl.WHITE) + # Fade out bottom of overlays for looks (only when engaged) + fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) + if fade_alpha > 1e-2: + rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, + rl.Color(255, 255, 255, int(255 * fade_alpha))) alert_to_render, not_animating_out = self._alert_renderer.will_render() From a5c973be896bb87469e70250ea18af3783e09434 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Feb 2026 23:52:59 -0800 Subject: [PATCH 114/685] NavBar: fix no show animation (#37090) * 1.5 not enough time on small screen * ohhhh * clean up --- system/ui/widgets/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 42d9a910aa2..5d474e8aedf 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -195,7 +195,7 @@ def hide_event(self): NAV_BAR_HEIGHT = 8 DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing -DISMISS_TIME_SECONDS = 1.5 +DISMISS_TIME_SECONDS = 2.0 class NavBar(Widget): @@ -371,13 +371,13 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: else: self._nav_bar_y_filter.update(NAV_BAR_MARGIN) - self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) - self._nav_bar.render() - # draw black above widget when dismissing if self._rect.y > 0: rl.draw_rectangle(int(self._rect.x), 0, int(self._rect.width), int(self._rect.y), rl.BLACK) + self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) + self._nav_bar.render() + return ret def show_event(self): From 8aed5a1a89a8d699cf96c35eacca6432bad95d20 Mon Sep 17 00:00:00 2001 From: royjr Date: Thu, 5 Feb 2026 12:28:01 -0500 Subject: [PATCH 115/685] translations: remove arabic (#37087) * remove arabic * more --- selfdrive/assets/fonts/process.py | 2 +- selfdrive/ui/translations/app_ar.po | 1218 ---------------------- selfdrive/ui/translations/languages.json | 1 - system/ui/lib/multilang.py | 1 - 4 files changed, 1 insertion(+), 1221 deletions(-) delete mode 100644 selfdrive/ui/translations/app_ar.po diff --git a/selfdrive/assets/fonts/process.py b/selfdrive/assets/fonts/process.py index ddc8b3a8682..30ccf9ca551 100755 --- a/selfdrive/assets/fonts/process.py +++ b/selfdrive/assets/fonts/process.py @@ -11,7 +11,7 @@ GLYPH_PADDING = 6 EXTRA_CHARS = "–‑✓×°§•X⚙✕◀▶✔⌫⇧␣○●↳çêüñ–‑✓×°§•€£¥" -UNIFONT_LANGUAGES = {"ar", "th", "zh-CHT", "zh-CHS", "ko", "ja"} +UNIFONT_LANGUAGES = {"th", "zh-CHT", "zh-CHS", "ko", "ja"} def _languages(): diff --git a/selfdrive/ui/translations/app_ar.po b/selfdrive/ui/translations/app_ar.po deleted file mode 100644 index 608389fc07d..00000000000 --- a/selfdrive/ui/translations/app_ar.po +++ /dev/null @@ -1,1218 +0,0 @@ -# Arabic translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# -msgid "" -msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-22 16:32-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: ar\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=6; plural=n==0?0:n==1?1:n==2?2:(n%100>=3 && " -"n%100<=10)?3:(n%100>=11 && n%100<=99)?4:5;\n" - -#: selfdrive/ui/layouts/settings/device.py:160 -#, python-format -msgid " Steering torque response calibration is complete." -msgstr " اكتملت معايرة استجابة عزم التوجيه." - -#: selfdrive/ui/layouts/settings/device.py:158 -#, python-format -msgid " Steering torque response calibration is {}% complete." -msgstr " اكتملت معايرة استجابة عزم التوجيه بنسبة {}٪." - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." -msgstr " جهازك موجه بمقدار {:.1f}° {} و {:.1f}° {}." - -#: selfdrive/ui/layouts/sidebar.py:43 -msgid "--" -msgstr "--" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "1 year of drive storage" -msgstr "سنة واحدة من تخزين القيادة" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "24/7 LTE connectivity" -msgstr "اتصال LTE على مدار الساعة" - -#: selfdrive/ui/layouts/sidebar.py:46 -msgid "2G" -msgstr "2G" - -#: selfdrive/ui/layouts/sidebar.py:47 -msgid "3G" -msgstr "3G" - -#: selfdrive/ui/layouts/sidebar.py:49 -msgid "5G" -msgstr "5G" - -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"تحذير: التحكم الطولي لـ openpilot في مرحلة ألفا لهذه السيارة وسيُعطّل نظام " -"الكبح التلقائي في حالات الطوارئ (AEB).

في هذه السيارة، يعتمد " -"openpilot افتراضياً على نظام ACC المدمج بدلاً من التحكم الطولي لـ openpilot. " -"فعّل هذا الخيار للتبديل إلى التحكم الطولي لـ openpilot. يُنصح بتمكين وضع " -"التجربة عند تفعيل نسخة ألفا من التحكم الطولي. تغيير هذا الإعداد سيعيد تشغيل " -"openpilot إذا كانت السيارة قيد التشغيل." - -#: selfdrive/ui/layouts/settings/device.py:148 -#, python-format -msgid "

Steering lag calibration is complete." -msgstr "

اكتملت معايرة تأخر التوجيه." - -#: selfdrive/ui/layouts/settings/device.py:146 -#, python-format -msgid "

Steering lag calibration is {}% complete." -msgstr "

اكتملت معايرة تأخر التوجيه بنسبة {}٪." - -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "نشط" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"يتيح ADB (Android Debug Bridge) الاتصال بجهازك عبر USB أو عبر الشبكة. راجع " -"https://docs.comma.ai/how-to/connect-to-comma لمزيد من المعلومات." - -#: selfdrive/ui/widgets/ssh_key.py:30 -msgid "ADD" -msgstr "إضافة" - -#: system/ui/widgets/network.py:139 -#, python-format -msgid "APN Setting" -msgstr "إعداد APN" - -#: selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format -msgid "Acknowledge Excessive Actuation" -msgstr "تأكيد التشغيل المفرط" - -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 -#, python-format -msgid "Advanced" -msgstr "متقدم" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Aggressive" -msgstr "عدواني" - -#: selfdrive/ui/layouts/onboarding.py:116 -#, python-format -msgid "Agree" -msgstr "موافقة" - -#: selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format -msgid "Always-On Driver Monitoring" -msgstr "مراقبة السائق دائماً" - -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"يمكن اختبار نسخة ألفا من التحكم الطولي لـ openpilot، مع وضع التجربة، على " -"الفروع غير الإصدارية." - -#: selfdrive/ui/layouts/settings/device.py:187 -#, python-format -msgid "Are you sure you want to power off?" -msgstr "هل أنت متأكد أنك تريد إيقاف التشغيل؟" - -#: selfdrive/ui/layouts/settings/device.py:175 -#, python-format -msgid "Are you sure you want to reboot?" -msgstr "هل أنت متأكد أنك تريد إعادة التشغيل؟" - -#: selfdrive/ui/layouts/settings/device.py:119 -#, python-format -msgid "Are you sure you want to reset calibration?" -msgstr "هل أنت متأكد أنك تريد إعادة ضبط المعايرة؟" - -#: selfdrive/ui/layouts/settings/software.py:163 -#, python-format -msgid "Are you sure you want to uninstall?" -msgstr "هل أنت متأكد أنك تريد إلغاء التثبيت؟" - -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 -#, python-format -msgid "Back" -msgstr "رجوع" - -#: selfdrive/ui/widgets/prime.py:38 -#, python-format -msgid "Become a comma prime member at connect.comma.ai" -msgstr "انضم إلى comma prime عبر connect.comma.ai" - -#: selfdrive/ui/widgets/pairing_dialog.py:130 -#, python-format -msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "ثبّت connect.comma.ai على شاشتك الرئيسية لاستخدامه كتطبيق" - -#: selfdrive/ui/layouts/settings/device.py:68 -#, python-format -msgid "CHANGE" -msgstr "تغيير" - -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 -#, python-format -msgid "CHECK" -msgstr "تحقق" - -#: selfdrive/ui/widgets/exp_mode_button.py:50 -#, python-format -msgid "CHILL MODE ON" -msgstr "وضع الهدوء مُفعل" - -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 -#, python-format -msgid "CONNECT" -msgstr "CONNECT" - -#: system/ui/widgets/network.py:369 -#, python-format -msgid "CONNECTING..." -msgstr "CONNECTING..." - -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 -#, python-format -msgid "Cancel" -msgstr "إلغاء" - -#: system/ui/widgets/network.py:134 -#, python-format -msgid "Cellular Metered" -msgstr "خلوي بتعرفة محدودة" - -#: selfdrive/ui/layouts/settings/device.py:68 -#, python-format -msgid "Change Language" -msgstr "تغيير اللغة" - -#: selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format -msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"سيؤدي تغيير هذا الإعداد إلى إعادة تشغيل openpilot إذا كانت السيارة قيد " -"التشغيل." - -#: selfdrive/ui/widgets/pairing_dialog.py:129 -#, python-format -msgid "Click \"add new device\" and scan the QR code on the right" -msgstr "اضغط \"إضافة جهاز جديد\" ثم امسح رمز QR على اليمين" - -#: selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format -msgid "Close" -msgstr "إغلاق" - -#: selfdrive/ui/layouts/settings/software.py:49 -#, python-format -msgid "Current Version" -msgstr "الإصدار الحالي" - -#: selfdrive/ui/layouts/settings/software.py:110 -#, python-format -msgid "DOWNLOAD" -msgstr "تنزيل" - -#: selfdrive/ui/layouts/onboarding.py:115 -#, python-format -msgid "Decline" -msgstr "رفض" - -#: selfdrive/ui/layouts/onboarding.py:148 -#, python-format -msgid "Decline, uninstall openpilot" -msgstr "رفض، وإلغاء تثبيت openpilot" - -#: selfdrive/ui/layouts/settings/settings.py:67 -msgid "Developer" -msgstr "المطور" - -#: selfdrive/ui/layouts/settings/settings.py:62 -msgid "Device" -msgstr "الجهاز" - -#: selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format -msgid "Disengage on Accelerator Pedal" -msgstr "فصل عند الضغط على دواسة الوقود" - -#: selfdrive/ui/layouts/settings/device.py:184 -#, python-format -msgid "Disengage to Power Off" -msgstr "افصل لإيقاف التشغيل" - -#: selfdrive/ui/layouts/settings/device.py:172 -#, python-format -msgid "Disengage to Reboot" -msgstr "افصل لإعادة التشغيل" - -#: selfdrive/ui/layouts/settings/device.py:103 -#, python-format -msgid "Disengage to Reset Calibration" -msgstr "افصل لإعادة ضبط المعايرة" - -#: selfdrive/ui/layouts/settings/toggles.py:32 -msgid "Display speed in km/h instead of mph." -msgstr "عرض السرعة بالكيلومتر/ساعة بدلاً من الميل/ساعة." - -#: selfdrive/ui/layouts/settings/device.py:59 -#, python-format -msgid "Dongle ID" -msgstr "معرّف الدونجل" - -#: selfdrive/ui/layouts/settings/software.py:50 -#, python-format -msgid "Download" -msgstr "تنزيل" - -#: selfdrive/ui/layouts/settings/device.py:62 -#, python-format -msgid "Driver Camera" -msgstr "كاميرا السائق" - -#: selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format -msgid "Driving Personality" -msgstr "شخصية القيادة" - -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 -#, python-format -msgid "EDIT" -msgstr "تعديل" - -#: selfdrive/ui/layouts/sidebar.py:138 -msgid "ERROR" -msgstr "خطأ" - -#: selfdrive/ui/layouts/sidebar.py:45 -msgid "ETH" -msgstr "ETH" - -#: selfdrive/ui/widgets/exp_mode_button.py:50 -#, python-format -msgid "EXPERIMENTAL MODE ON" -msgstr "وضع التجربة مُفعل" - -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 -#, python-format -msgid "Enable" -msgstr "تمكين" - -#: selfdrive/ui/layouts/settings/developer.py:39 -#, python-format -msgid "Enable ADB" -msgstr "تمكين ADB" - -#: selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format -msgid "Enable Lane Departure Warnings" -msgstr "تمكين تحذيرات مغادرة المسار" - -#: system/ui/widgets/network.py:129 -#, python-format -msgid "Enable Roaming" -msgstr "تمكين التجوال" - -#: selfdrive/ui/layouts/settings/developer.py:48 -#, python-format -msgid "Enable SSH" -msgstr "تمكين SSH" - -#: system/ui/widgets/network.py:120 -#, python-format -msgid "Enable Tethering" -msgstr "تمكين الربط" - -#: selfdrive/ui/layouts/settings/toggles.py:30 -msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "تمكين مراقبة السائق حتى عندما لا يكون openpilot مُشغلاً." - -#: selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format -msgid "Enable openpilot" -msgstr "تمكين openpilot" - -#: selfdrive/ui/layouts/settings/toggles.py:189 -#, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "فعّل تبديل التحكم الطولي (ألفا) لـ openpilot للسماح بوضع التجربة." - -#: system/ui/widgets/network.py:204 -#, python-format -msgid "Enter APN" -msgstr "أدخل APN" - -#: system/ui/widgets/network.py:241 -#, python-format -msgid "Enter SSID" -msgstr "أدخل SSID" - -#: system/ui/widgets/network.py:254 -#, python-format -msgid "Enter new tethering password" -msgstr "أدخل كلمة مرور الربط الجديدة" - -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 -#, python-format -msgid "Enter password" -msgstr "أدخل كلمة المرور" - -#: selfdrive/ui/widgets/ssh_key.py:89 -#, python-format -msgid "Enter your GitHub username" -msgstr "أدخل اسم مستخدم GitHub الخاص بك" - -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 -#, python-format -msgid "Error" -msgstr "خطأ" - -#: selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format -msgid "Experimental Mode" -msgstr "وضع التجربة" - -#: selfdrive/ui/layouts/settings/toggles.py:181 -#, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"وضع التجربة غير متاح حالياً في هذه السيارة لأن نظام ACC الأصلي يُستخدم للتحكم " -"الطولي." - -#: system/ui/widgets/network.py:373 -#, python-format -msgid "FORGETTING..." -msgstr "جارٍ النسيان..." - -#: selfdrive/ui/widgets/setup.py:44 -#, python-format -msgid "Finish Setup" -msgstr "إنهاء الإعداد" - -#: selfdrive/ui/layouts/settings/settings.py:66 -msgid "Firehose" -msgstr "Firehose" - -#: selfdrive/ui/layouts/settings/firehose.py:18 -msgid "Firehose Mode" -msgstr "وضع Firehose" - -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"لأقصى فعالية، أحضر جهازك إلى الداخل واتصل بمحوّل USB‑C جيد وبشبكة Wi‑Fi " -"أسبوعياً.\n" -"\n" -"يمكن أن يعمل وضع Firehose أيضاً أثناء القيادة إذا كنت متصلاً بنقطة اتصال أو " -"بشريحة غير محدودة.\n" -"\n" -"\n" -"الأسئلة الشائعة\n" -"\n" -"هل يهم كيف أو أين أقود؟ لا، قد بقدر المعتاد.\n" -"\n" -"هل يتم سحب كل مقاطعي في وضع Firehose؟ لا، نقوم بسحب مجموعة فرعية من " -"المقاطع.\n" -"\n" -"ما هو محول USB‑C الجيد؟ أي شاحن هاتف أو حاسب محمول سريع سيكون مناسباً.\n" -"\n" -"هل يهم أي برنامج أشغّل؟ نعم، فقط openpilot الأصلي (وبعض التفرعات المحددة) " -"يمكن استخدامه للتدريب." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 -#, python-format -msgid "Forget" -msgstr "نسيان" - -#: system/ui/widgets/network.py:319 -#, python-format -msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "هل تريد نسيان شبكة Wi‑Fi \"{}\"؟" - -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -msgid "GOOD" -msgstr "جيد" - -#: selfdrive/ui/widgets/pairing_dialog.py:128 -#, python-format -msgid "Go to https://connect.comma.ai on your phone" -msgstr "اذهب إلى https://connect.comma.ai على هاتفك" - -#: selfdrive/ui/layouts/sidebar.py:129 -msgid "HIGH" -msgstr "مرتفع" - -#: system/ui/widgets/network.py:155 -#, python-format -msgid "Hidden Network" -msgstr "شبكة مخفية" - -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "غير نشط: اتصل بشبكة غير محدودة التعرفة" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 -#, python-format -msgid "INSTALL" -msgstr "تثبيت" - -#: system/ui/widgets/network.py:150 -#, python-format -msgid "IP Address" -msgstr "عنوان IP" - -#: selfdrive/ui/layouts/settings/software.py:53 -#, python-format -msgid "Install Update" -msgstr "تثبيت التحديث" - -#: selfdrive/ui/layouts/settings/developer.py:56 -#, python-format -msgid "Joystick Debug Mode" -msgstr "وضع تصحيح عصا التحكم" - -#: selfdrive/ui/widgets/ssh_key.py:29 -msgid "LOADING" -msgstr "جارٍ التحميل" - -#: selfdrive/ui/layouts/sidebar.py:48 -msgid "LTE" -msgstr "LTE" - -#: selfdrive/ui/layouts/settings/developer.py:64 -#, python-format -msgid "Longitudinal Maneuver Mode" -msgstr "وضع المناورة الطولية" - -#: selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format -msgid "MAX" -msgstr "أقصى" - -#: selfdrive/ui/widgets/setup.py:75 -#, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "زد من تحميل بيانات التدريب لتحسين نماذج قيادة openpilot." - -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 -#, python-format -msgid "N/A" -msgstr "غير متوفر" - -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "NO" -msgstr "لا" - -#: selfdrive/ui/layouts/settings/settings.py:63 -msgid "Network" -msgstr "الشبكة" - -#: selfdrive/ui/widgets/ssh_key.py:114 -#, python-format -msgid "No SSH keys found" -msgstr "لم يتم العثور على مفاتيح SSH" - -#: selfdrive/ui/widgets/ssh_key.py:126 -#, python-format -msgid "No SSH keys found for user '{}'" -msgstr "لم يتم العثور على مفاتيح SSH للمستخدم '{}'" - -#: selfdrive/ui/widgets/offroad_alerts.py:320 -#, python-format -msgid "No release notes available." -msgstr "لا توجد ملاحظات إصدار متاحة." - -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 -msgid "OFFLINE" -msgstr "غير متصل" - -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 -#, python-format -msgid "OK" -msgstr "موافق" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 -msgid "ONLINE" -msgstr "متصل" - -#: selfdrive/ui/widgets/setup.py:20 -#, python-format -msgid "Open" -msgstr "فتح" - -#: selfdrive/ui/layouts/settings/device.py:48 -#, python-format -msgid "PAIR" -msgstr "إقران" - -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "PANDA" -msgstr "PANDA" - -#: selfdrive/ui/layouts/settings/device.py:62 -#, python-format -msgid "PREVIEW" -msgstr "معاينة" - -#: selfdrive/ui/widgets/prime.py:44 -#, python-format -msgid "PRIME FEATURES:" -msgstr "ميزات PRIME:" - -#: selfdrive/ui/layouts/settings/device.py:48 -#, python-format -msgid "Pair Device" -msgstr "إقران الجهاز" - -#: selfdrive/ui/widgets/setup.py:19 -#, python-format -msgid "Pair device" -msgstr "إقران الجهاز" - -#: selfdrive/ui/widgets/pairing_dialog.py:103 -#, python-format -msgid "Pair your device to your comma account" -msgstr "قم بإقران جهازك بحساب comma" - -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 -#, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"أقرِن جهازك مع comma connect (connect.comma.ai) واحصل على عرض comma prime." - -#: selfdrive/ui/widgets/setup.py:91 -#, python-format -msgid "Please connect to Wi-Fi to complete initial pairing" -msgstr "يرجى الاتصال بشبكة Wi‑Fi لإكمال الاقتران الأولي" - -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 -#, python-format -msgid "Power Off" -msgstr "إيقاف التشغيل" - -#: system/ui/widgets/network.py:144 -#, python-format -msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "منع رفع البيانات الكبيرة عند الاتصال بشبكة Wi‑Fi محدودة التعرفة" - -#: system/ui/widgets/network.py:135 -#, python-format -msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "منع رفع البيانات الكبيرة عند الاتصال الخلوي محدود التعرفة" - -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"عاين كاميرا مواجهة السائق للتأكد من أن مراقبة السائق تتم برؤية جيدة. (يجب أن " -"تكون المركبة متوقفة)" - -#: selfdrive/ui/widgets/pairing_dialog.py:161 -#, python-format -msgid "QR Code Error" -msgstr "خطأ في رمز QR" - -#: selfdrive/ui/widgets/ssh_key.py:31 -msgid "REMOVE" -msgstr "إزالة" - -#: selfdrive/ui/layouts/settings/device.py:51 -#, python-format -msgid "RESET" -msgstr "إعادة ضبط" - -#: selfdrive/ui/layouts/settings/device.py:65 -#, python-format -msgid "REVIEW" -msgstr "مراجعة" - -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 -#, python-format -msgid "Reboot" -msgstr "إعادة التشغيل" - -#: selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format -msgid "Reboot Device" -msgstr "إعادة تشغيل الجهاز" - -#: selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format -msgid "Reboot and Update" -msgstr "إعادة التشغيل والتحديث" - -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"استقبال تنبيهات للتوجيه للعودة إلى المسار عند انحراف المركبة فوق خط المسار " -"المُكتشف بدون إشارة انعطاف مفعّلة أثناء القيادة فوق 31 ميل/س (50 كم/س)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format -msgid "Record and Upload Driver Camera" -msgstr "تسجيل ورفع فيديو كاميرا السائق" - -#: selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format -msgid "Record and Upload Microphone Audio" -msgstr "تسجيل ورفع صوت الميكروفون" - -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"تسجيل وتخزين صوت الميكروفون أثناء القيادة. سيُدرج الصوت في فيديو الكاميرا " -"الأمامية في comma connect." - -#: selfdrive/ui/layouts/settings/device.py:67 -#, python-format -msgid "Regulatory" -msgstr "لوائح" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Relaxed" -msgstr "مسترخٍ" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "Remote access" -msgstr "وصول عن بُعد" - -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "Remote snapshots" -msgstr "لقطات عن بُعد" - -#: selfdrive/ui/widgets/ssh_key.py:123 -#, python-format -msgid "Request timed out" -msgstr "انتهت مهلة الطلب" - -#: selfdrive/ui/layouts/settings/device.py:119 -#, python-format -msgid "Reset" -msgstr "إعادة ضبط" - -#: selfdrive/ui/layouts/settings/device.py:51 -#, python-format -msgid "Reset Calibration" -msgstr "إعادة ضبط المعايرة" - -#: selfdrive/ui/layouts/settings/device.py:65 -#, python-format -msgid "Review Training Guide" -msgstr "مراجعة دليل التدريب" - -#: selfdrive/ui/layouts/settings/device.py:27 -msgid "Review the rules, features, and limitations of openpilot" -msgstr "مراجعة قواعد وميزات وحدود openpilot" - -#: selfdrive/ui/layouts/settings/software.py:61 -#, python-format -msgid "SELECT" -msgstr "اختيار" - -#: selfdrive/ui/layouts/settings/developer.py:53 -#, python-format -msgid "SSH Keys" -msgstr "مفاتيح SSH" - -#: system/ui/widgets/network.py:310 -#, python-format -msgid "Scanning Wi-Fi networks..." -msgstr "جارٍ مسح شبكات Wi‑Fi..." - -#: system/ui/widgets/option_dialog.py:36 -#, python-format -msgid "Select" -msgstr "اختيار" - -#: selfdrive/ui/layouts/settings/software.py:183 -#, python-format -msgid "Select a branch" -msgstr "اختر فرعاً" - -#: selfdrive/ui/layouts/settings/device.py:91 -#, python-format -msgid "Select a language" -msgstr "اختر لغة" - -#: selfdrive/ui/layouts/settings/device.py:60 -#, python-format -msgid "Serial" -msgstr "الرقم التسلسلي" - -#: selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format -msgid "Snooze Update" -msgstr "تأجيل التحديث" - -#: selfdrive/ui/layouts/settings/settings.py:65 -msgid "Software" -msgstr "البرمجيات" - -#: selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Standard" -msgstr "قياسي" - -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"يوصى بالوضع القياسي. في الوضع العدواني، سيتبع openpilot السيارات الأمامية عن " -"قرب وسيكون أكثر شدة في الوقود والفرامل. في الوضع المسترخي سيبقى بعيداً أكثر " -"عن السيارات الأمامية. في السيارات المدعومة، يمكنك التنقل بين هذه الشخصيات " -"بزر مسافة المقود." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format -msgid "System Unresponsive" -msgstr "النظام لا يستجيب" - -#: selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format -msgid "TAKE CONTROL IMMEDIATELY" -msgstr "تولَّ السيطرة فوراً" - -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 -msgid "TEMP" -msgstr "الحرارة" - -#: selfdrive/ui/layouts/settings/software.py:61 -#, python-format -msgid "Target Branch" -msgstr "الفرع المستهدف" - -#: system/ui/widgets/network.py:124 -#, python-format -msgid "Tethering Password" -msgstr "كلمة مرور الربط" - -#: selfdrive/ui/layouts/settings/settings.py:64 -msgid "Toggles" -msgstr "مفاتيح التبديل" - -#: selfdrive/ui/layouts/settings/software.py:72 -#, python-format -msgid "UNINSTALL" -msgstr "إلغاء التثبيت" - -#: selfdrive/ui/layouts/home.py:155 -#, python-format -msgid "UPDATE" -msgstr "تحديث" - -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 -#, python-format -msgid "Uninstall" -msgstr "إلغاء التثبيت" - -#: selfdrive/ui/layouts/sidebar.py:117 -msgid "Unknown" -msgstr "غير معروف" - -#: selfdrive/ui/layouts/settings/software.py:48 -#, python-format -msgid "Updates are only downloaded while the car is off." -msgstr "يتم تنزيل التحديثات فقط عندما تكون السيارة متوقفة." - -#: selfdrive/ui/widgets/prime.py:33 -#, python-format -msgid "Upgrade Now" -msgstr "الترقية الآن" - -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"ارفع بيانات من كاميرا مواجهة السائق وساعد في تحسين خوارزمية مراقبة السائق." - -#: selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format -msgid "Use Metric System" -msgstr "استخدام النظام المتري" - -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"استخدم نظام openpilot للتحكم الذكي بالسرعة والمساعدة على البقاء داخل المسار. " -"يتطلب استخدام هذه الميزة انتباهك الكامل في جميع الأوقات." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 -msgid "VEHICLE" -msgstr "المركبة" - -#: selfdrive/ui/layouts/settings/device.py:67 -#, python-format -msgid "VIEW" -msgstr "عرض" - -#: selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format -msgid "Waiting to start" -msgstr "بانتظار البدء" - -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"تحذير: يمنح هذا وصول SSH إلى جميع المفاتيح العامة في إعدادات GitHub الخاصة " -"بك. لا تُدخل مطلقاً اسم مستخدم GitHub غير اسمك. لن يطلب منك موظف في comma أبداً " -"إضافة اسم مستخدمهم." - -#: selfdrive/ui/layouts/onboarding.py:111 -#, python-format -msgid "Welcome to openpilot" -msgstr "مرحباً بك في openpilot" - -#: selfdrive/ui/layouts/settings/toggles.py:20 -msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "عند التمكين، سيؤدي الضغط على دواسة الوقود إلى فصل openpilot." - -#: selfdrive/ui/layouts/sidebar.py:44 -msgid "Wi-Fi" -msgstr "Wi‑Fi" - -#: system/ui/widgets/network.py:144 -#, python-format -msgid "Wi-Fi Network Metered" -msgstr "شبكة Wi‑Fi محدودة التعرفة" - -#: system/ui/widgets/network.py:314 -#, python-format -msgid "Wrong password" -msgstr "كلمة مرور خاطئة" - -#: selfdrive/ui/layouts/onboarding.py:145 -#, python-format -msgid "You must accept the Terms and Conditions in order to use openpilot." -msgstr "يجب عليك قبول الشروط والأحكام لاستخدام openpilot." - -#: selfdrive/ui/layouts/onboarding.py:112 -#, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"يجب عليك قبول الشروط والأحكام لاستخدام openpilot. اقرأ أحدث الشروط على " -"https://comma.ai/terms قبل المتابعة." - -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 -#, python-format -msgid "camera starting" -msgstr "بدء تشغيل الكاميرا" - -#: selfdrive/ui/widgets/prime.py:63 -#, python-format -msgid "comma prime" -msgstr "comma prime" - -#: system/ui/widgets/network.py:142 -#, python-format -msgid "default" -msgstr "افتراضي" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid "down" -msgstr "أسفل" - -#: selfdrive/ui/layouts/settings/software.py:106 -#, python-format -msgid "failed to check for update" -msgstr "فشل التحقق من وجود تحديث" - -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 -#, python-format -msgid "for \"{}\"" -msgstr "لـ \"{}\"" - -#: selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format -msgid "km/h" -msgstr "كم/س" - -#: system/ui/widgets/network.py:204 -#, python-format -msgid "leave blank for automatic configuration" -msgstr "اتركه فارغاً للإعداد التلقائي" - -#: selfdrive/ui/layouts/settings/device.py:134 -#, python-format -msgid "left" -msgstr "يسار" - -#: system/ui/widgets/network.py:142 -#, python-format -msgid "metered" -msgstr "محدود التعرفة" - -#: selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format -msgid "mph" -msgstr "ميل/س" - -#: selfdrive/ui/layouts/settings/software.py:20 -#, python-format -msgid "never" -msgstr "أبداً" - -#: selfdrive/ui/layouts/settings/software.py:31 -#, python-format -msgid "now" -msgstr "الآن" - -#: selfdrive/ui/layouts/settings/developer.py:71 -#, python-format -msgid "openpilot Longitudinal Control (Alpha)" -msgstr "التحكم الطولي لـ openpilot (ألفا)" - -#: selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format -msgid "openpilot Unavailable" -msgstr "openpilot غير متاح" - -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"يعمل openpilot افتراضياً في وضع الهدوء. يفعّل وضع التجربة ميزات بمستوى ألفا " -"غير الجاهزة لوضع الهدوء. الميزات التجريبية مدرجة أدناه:

التحكم الطولي " -"من طرف لطرف


دع نموذج القيادة يتحكم في الوقود والفرامل. سيقود " -"openpilot كما يظن أن الإنسان سيقود، بما في ذلك التوقف عند الإشارات الحمراء " -"وعلامات التوقف. بما أن نموذج القيادة يقرر السرعة، فإن السرعة المضبوطة تعمل " -"كحد أعلى فقط. هذه ميزة بجودة ألفا؛ يُتوقع حدوث أخطاء.

تصوير قيادة " -"جديد


سينتقل عرض القيادة إلى الكاميرا الواسعة المواجهة للطريق عند " -"السرعات المنخفضة لإظهار بعض المنعطفات بشكل أفضل. كما سيظهر شعار وضع التجربة " -"في الزاوية العلوية اليمنى." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"يقوم openpilot بالمعايرة بشكل مستمر، ونادراً ما تتطلب إعادة الضبط. ستؤدي " -"إعادة ضبط المعايرة إلى إعادة تشغيل openpilot إذا كانت السيارة قيد التشغيل." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"يتعلم openpilot القيادة بمشاهدة البشر، مثلك، يقودون.\n" -"\n" -"يتيح وضع Firehose زيادة تحميل بيانات التدريب لتحسين نماذج قيادة openpilot. " -"المزيد من البيانات يعني نماذج أكبر، مما يعني وضع تجربة أفضل." - -#: selfdrive/ui/layouts/settings/toggles.py:183 -#, python-format -msgid "openpilot longitudinal control may come in a future update." -msgstr "قد يأتي التحكم الطولي لـ openpilot في تحديث مستقبلي." - -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"يتطلب openpilot تركيب الجهاز ضمن 4° يساراً أو يميناً وضمن 5° للأعلى أو 9° " -"للأسفل." - -#: selfdrive/ui/layouts/settings/device.py:134 -#, python-format -msgid "right" -msgstr "يمين" - -#: system/ui/widgets/network.py:142 -#, python-format -msgid "unmetered" -msgstr "غير محدود" - -#: selfdrive/ui/layouts/settings/device.py:133 -#, python-format -msgid "up" -msgstr "أعلى" - -#: selfdrive/ui/layouts/settings/software.py:117 -#, python-format -msgid "up to date, last checked never" -msgstr "محدّث، آخر تحقق: أبداً" - -#: selfdrive/ui/layouts/settings/software.py:115 -#, python-format -msgid "up to date, last checked {}" -msgstr "محدّث، آخر تحقق {}" - -#: selfdrive/ui/layouts/settings/software.py:109 -#, python-format -msgid "update available" -msgstr "تحديث متاح" - -#: selfdrive/ui/layouts/home.py:169 -#, python-format -msgid "{} ALERT" -msgid_plural "{} ALERTS" -msgstr[0] "{} تنبيه" -msgstr[1] "{} تنبيه" -msgstr[2] "{} تنبيهان" -msgstr[3] "{} تنبيهات" -msgstr[4] "{} تنبيهات" -msgstr[5] "{} تنبيه" - -#: selfdrive/ui/layouts/settings/software.py:40 -#, python-format -msgid "{} day ago" -msgid_plural "{} days ago" -msgstr[0] "قبل {} يوم" -msgstr[1] "قبل {} يوم" -msgstr[2] "قبل {} يومين" -msgstr[3] "قبل {} أيام" -msgstr[4] "قبل {} أيام" -msgstr[5] "قبل {} يوم" - -#: selfdrive/ui/layouts/settings/software.py:37 -#, python-format -msgid "{} hour ago" -msgid_plural "{} hours ago" -msgstr[0] "قبل {} ساعة" -msgstr[1] "قبل {} ساعة" -msgstr[2] "قبل {} ساعتين" -msgstr[3] "قبل {} ساعات" -msgstr[4] "قبل {} ساعات" -msgstr[5] "قبل {} ساعة" - -#: selfdrive/ui/layouts/settings/software.py:34 -#, python-format -msgid "{} minute ago" -msgid_plural "{} minutes ago" -msgstr[0] "قبل {} دقيقة" -msgstr[1] "قبل {} دقيقة" -msgstr[2] "قبل {} دقيقتين" -msgstr[3] "قبل {} دقائق" -msgstr[4] "قبل {} دقائق" -msgstr[5] "قبل {} دقيقة" - -#: selfdrive/ui/layouts/settings/firehose.py:111 -#, python-format -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[1] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[2] "{} مقطعان من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[3] "{} مقاطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[4] "{} مقاطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." -msgstr[5] "{} مقطع من قيادتك ضمن مجموعة بيانات التدريب حتى الآن." - -#: selfdrive/ui/widgets/prime.py:62 -#, python-format -msgid "✓ SUBSCRIBED" -msgstr "✓ مشترك" - -#: selfdrive/ui/widgets/setup.py:22 -#, python-format -msgid "🔥 Firehose Mode 🔥" -msgstr "🔥 وضع Firehose 🔥" diff --git a/selfdrive/ui/translations/languages.json b/selfdrive/ui/translations/languages.json index 47e673ce89b..99d3aafe3a2 100644 --- a/selfdrive/ui/translations/languages.json +++ b/selfdrive/ui/translations/languages.json @@ -6,7 +6,6 @@ "Español": "es", "Türkçe": "tr", "Українська": "uk", - "العربية": "ar", "ไทย": "th", "中文(繁體)": "zh-CHT", "中文(简体)": "zh-CHS", diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 70de1e3d5c8..ad2a5f93997 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -16,7 +16,6 @@ LANGUAGES_FILE = TRANSLATIONS_DIR.joinpath("languages.json") UNIFONT_LANGUAGES = [ - "ar", "th", "zh-CHT", "zh-CHS", From 28795d3065a291d68b218739cb0508625beacc45 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Thu, 5 Feb 2026 16:30:03 -0500 Subject: [PATCH 116/685] bump opendbc (#37091) --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 7c78ee87b7b..39ee861197e 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 7c78ee87b7b54bb2179d86d5e28c1f65bbf96669 +Subproject commit 39ee861197edca894fc1b2e28d6fd1a38aea9ca1 From 7e959c5a3e7611a19b24c46729461805a5bb9f84 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 5 Feb 2026 15:55:03 -0800 Subject: [PATCH 117/685] long_mpc: use log.capnp source enum instead of list (#37093) --- .../controls/lib/longitudinal_mpc_lib/long_mpc.py | 11 +++++++++-- selfdrive/controls/lib/longitudinal_planner.py | 5 +++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 9408132c5b8..f6d2eba91f0 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -217,7 +217,7 @@ def __init__(self, dt=DT_MDL): self.dt = dt self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() - self.source = SOURCES[2] + self.source = log.LongitudinalPlan.LongitudinalPlanSource.cruise def reset(self): self.solver.reset() @@ -335,7 +335,14 @@ def update(self, radarstate, v_cruise, personality=log.LongitudinalPersonality.s cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) - self.source = SOURCES[np.argmin(x_obstacles[0])] + lead_idx = np.argmin(x_obstacles[0]) + match lead_idx: + case 0: + self.source = log.LongitudinalPlan.LongitudinalPlanSource.lead0 + case 1: + self.source = log.LongitudinalPlan.LongitudinalPlanSource.lead1 + case 2: + self.source = log.LongitudinalPlan.LongitudinalPlanSource.cruise self.yref[:,:] = 0.0 for i in range(N): diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index c5c03eba180..47228ada40f 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -3,13 +3,14 @@ import numpy as np import cereal.messaging as messaging +from cereal import log from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX from openpilot.common.constants import CV from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, SOURCES +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET @@ -156,7 +157,7 @@ def update(self, sm): output_a_target = min(output_a_target_e2e, output_a_target_mpc) self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc if output_a_target < output_a_target_mpc: - self.mpc.source = SOURCES[3] + self.mpc.source = log.LongitudinalPlan.LongitudinalPlanSource.e2e else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc From 64f74dad2702a11b6ad2c38e3c3ffc228514adbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 5 Feb 2026 16:23:28 -0800 Subject: [PATCH 118/685] Revert "long_mpc: use log.capnp source enum instead of list" (#37095) Revert "long_mpc: use log.capnp source enum instead of list (#37093)" This reverts commit 7e959c5a3e7611a19b24c46729461805a5bb9f84. --- .../controls/lib/longitudinal_mpc_lib/long_mpc.py | 11 ++--------- selfdrive/controls/lib/longitudinal_planner.py | 5 ++--- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index f6d2eba91f0..9408132c5b8 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -217,7 +217,7 @@ def __init__(self, dt=DT_MDL): self.dt = dt self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() - self.source = log.LongitudinalPlan.LongitudinalPlanSource.cruise + self.source = SOURCES[2] def reset(self): self.solver.reset() @@ -335,14 +335,7 @@ def update(self, radarstate, v_cruise, personality=log.LongitudinalPersonality.s cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) - lead_idx = np.argmin(x_obstacles[0]) - match lead_idx: - case 0: - self.source = log.LongitudinalPlan.LongitudinalPlanSource.lead0 - case 1: - self.source = log.LongitudinalPlan.LongitudinalPlanSource.lead1 - case 2: - self.source = log.LongitudinalPlan.LongitudinalPlanSource.cruise + self.source = SOURCES[np.argmin(x_obstacles[0])] self.yref[:,:] = 0.0 for i in range(N): diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 47228ada40f..c5c03eba180 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -3,14 +3,13 @@ import numpy as np import cereal.messaging as messaging -from cereal import log from opendbc.car.interfaces import ACCEL_MIN, ACCEL_MAX from openpilot.common.constants import CV from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, SOURCES from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET @@ -157,7 +156,7 @@ def update(self, sm): output_a_target = min(output_a_target_e2e, output_a_target_mpc) self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc if output_a_target < output_a_target_mpc: - self.mpc.source = log.LongitudinalPlan.LongitudinalPlanSource.e2e + self.mpc.source = SOURCES[3] else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc From 2b8e887e442df39df3bb5b00aab07a9700ce50dd Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Feb 2026 23:39:22 -0800 Subject: [PATCH 119/685] mici setup: remove unused functions --- system/ui/mici_setup.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index fac26f06eac..91e5f0fb9c3 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -590,15 +590,9 @@ def _render(self, rect: rl.Rectangle): def _custom_software_warning_back_button_callback(self): self._set_state(SetupState.SOFTWARE_SELECTION) - def _custom_software_warning_continue_button_callback(self): - self._set_state(SetupState.CUSTOM_SOFTWARE) - def _getting_started_button_callback(self): self._set_state(SetupState.SOFTWARE_SELECTION) - def _software_selection_back_button_callback(self): - self._set_state(SetupState.GETTING_STARTED) - def _software_selection_continue_button_callback(self): self.use_openpilot() From f5b84e74f4ddd9814e80630ecb718fca5392b6c0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Feb 2026 23:42:16 -0800 Subject: [PATCH 120/685] fix mici setup int enum --- system/ui/mici_setup.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 91e5f0fb9c3..78b6b5cecb0 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -94,12 +94,12 @@ def _run(self): class SetupState(IntEnum): GETTING_STARTED = 0 NETWORK_SETUP = 1 - NETWORK_SETUP_CUSTOM_SOFTWARE = 8 - SOFTWARE_SELECTION = 2 - CUSTOM_SOFTWARE = 3 - DOWNLOADING = 4 - DOWNLOAD_FAILED = 5 - CUSTOM_SOFTWARE_WARNING = 6 + NETWORK_SETUP_CUSTOM_SOFTWARE = 2 + SOFTWARE_SELECTION = 3 + CUSTOM_SOFTWARE = 4 + DOWNLOADING = 5 + DOWNLOAD_FAILED = 6 + CUSTOM_SOFTWARE_WARNING = 7 class StartPage(Widget): From cb670c2c3dd1564e9dd5d0fc9a1b1e005dc8183c Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:58:30 -0600 Subject: [PATCH 121/685] clips: improve overlays for mici (#37088) * adjust overlay sizes and wrap metadata text for mici * comment * adjust overlay rendering to dynamically calculate line height for wrapped metadata text * render time first so we can use width in margin calculation * update comment (to retry failed CI actually) * increase metadata size on mici --- tools/clip/run.py | 65 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index f2b871be69b..679bb241c14 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -238,25 +238,60 @@ def draw_text_box(rl, text, x, y, size, gui_app, font, font_scale, color=None, c rl.draw_text_ex(font, text, rl.Vector2(x, y), size, 0, text_color) -def render_overlays(rl, gui_app, font, font_scale, metadata, title, start_time, frame_idx, show_metadata, show_time): +def _wrap_text_by_delimiter(text: str, rl, font, font_size: int, font_scale: float, max_width: int, delimiter: str = ", ") -> list[str]: + """Wrap text by splitting on delimiter when it exceeds max_width.""" + words = text.split(delimiter) + lines: list[str] = [] + current_line: list[str] = [] + # Build lines word by word + for word in words: + current_line.append(word) + check_line = delimiter.join(current_line) + # Check if line exceeds max width + if rl.measure_text_ex(font, check_line, font_size * font_scale, 0).x > max_width: + current_line.pop() # Line is too long, move word to next line + if current_line: + lines.append(delimiter.join(current_line)) + current_line = [word] + # Add leftover words as last line + if current_line: + lines.append(delimiter.join(current_line)) + return lines + + +def render_overlays(rl, gui_app, font, font_scale, big, metadata, title, start_time, frame_idx, show_metadata, show_time): + metadata_size = 16 if big else 12 + title_size = 32 if big else 24 + time_size = 24 if big else 16 + + # Time overlay + time_width = 0 + if show_time: + t = start_time + frame_idx / FRAMERATE + time_text = f"{int(t) // 60:02d}:{int(t) % 60:02d}" + time_width = int(rl.measure_text_ex(font, time_text, time_size * font_scale, 0).x) + draw_text_box(rl, time_text, gui_app.width - time_width - 5, 0, time_size, gui_app, font, font_scale) + + # Metadata overlay (first 5 seconds) if show_metadata and metadata and frame_idx < FRAMERATE * 5: m = metadata text = ", ".join([f"openpilot v{m['version']}", f"route: {m['route']}", f"car: {m['car']}", f"origin: {m['origin']}", f"branch: {m['branch']}", f"commit: {m['commit']}", f"modified: {m['modified']}"]) - # Truncate if too wide (leave 20px margin on each side) - max_width = gui_app.width - 40 - while rl.measure_text_ex(font, text, 15 * font_scale, 0).x > max_width and len(text) > 20: - text = text[:-4] + "..." - draw_text_box(rl, text, 0, 8, 15, gui_app, font, font_scale, center=True) - + # Wrap text if too wide (leave margin on each side) + margin = 2 * (time_width + 10 if show_time else 20) # leave enough margin for time overlay + max_width = gui_app.width - margin + lines = _wrap_text_by_delimiter(text, rl, font, metadata_size, font_scale, max_width) + + # Draw wrapped metadata text + y_offset = 6 + for line in lines: + draw_text_box(rl, line, 0, y_offset, metadata_size, gui_app, font, font_scale, center=True) + line_height = int(rl.measure_text_ex(font, line, metadata_size * font_scale, 0).y) + 4 + y_offset += line_height + + # Title overlay if title: - draw_text_box(rl, title, 0, 60, 32, gui_app, font, font_scale, center=True) - - if show_time: - t = start_time + frame_idx / FRAMERATE - time_text = f"{int(t)//60:02d}:{int(t)%60:02d}" - time_width = int(rl.measure_text_ex(font, time_text, 24 * font_scale, 0).x) - draw_text_box(rl, time_text, gui_app.width - time_width - 45, 45, 24, gui_app, font, font_scale) + draw_text_box(rl, title, 0, 60, title_size, gui_app, font, font_scale, center=True) def clip(route: Route, output: str, start: int, end: int, headless: bool = True, big: bool = False, @@ -313,7 +348,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, ui_state.update() if should_render: road_view.render() - render_overlays(rl, gui_app, font, FONT_SCALE, metadata, title, start, frame_idx, show_metadata, show_time) + render_overlays(rl, gui_app, font, FONT_SCALE, big, metadata, title, start, frame_idx, show_metadata, show_time) frame_idx += 1 pbar.update(1) timer.lap("render") From 7099bd18e3ca5936cdd3c8de8a8c679cc7edb053 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Fri, 6 Feb 2026 10:35:54 -0800 Subject: [PATCH 122/685] longitudinal mpc tuning: behind if main (#37099) --- .../mpc_longitudinal_tuning_report.py | 483 +++++++++--------- 1 file changed, 242 insertions(+), 241 deletions(-) diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 8c1a60f5b76..51e38112f75 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -3,8 +3,8 @@ import markdown import numpy as np import matplotlib.pyplot as plt -from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance +from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver TIME = 0 LEAD_DISTANCE= 2 @@ -21,7 +21,6 @@ 'Lead distance (m)' ] - def get_html_from_results(results, labels, AXIS): fig, ax = plt.subplots(figsize=(16, 8)) for idx, speed in enumerate(list(results.keys())): @@ -38,242 +37,244 @@ def get_html_from_results(results, labels, AXIS): plt.close(fig) return fig_buffer.getvalue() + '
' - -htmls = [] - -results = {} -name = 'Resuming behind lead' -labels = [] -for lead_accel in np.linspace(1.0, 4.0, 4): - man = Maneuver( - '', - duration=11, - initial_speed=0.0, - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(0.0, 0.0), - speed_lead_values=[0.0, 10 * lead_accel], - cruise_values=[100, 100], - prob_lead_values=[1.0, 1.0], - breakpoints=[1., 11], - ) - valid, results[lead_accel] = man.evaluate() - labels.append(f'{lead_accel} m/s^2 lead acceleration') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) - - -results = {} -name = 'Approaching stopped car from 140m' -labels = [] -for speed in np.arange(0,45,5): - man = Maneuver( - name, - duration=30., - initial_speed=float(speed), - lead_relevancy=True, - initial_distance_lead=140., - speed_lead_values=[0.0, 0.], - breakpoints=[0., 30.], - ) - valid, results[speed] = man.evaluate() - results[speed][:,2] = results[speed][:,2] - results[speed][:,1] - labels.append(f'{speed} m/s approach speed') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, D_REL)) - - -results = {} -name = 'Following 5s oscillating lead' -labels = [] -speed = np.int64(10) -for oscil in np.arange(0, 10, 1): - man = Maneuver( - '', - duration=30., - initial_speed=float(speed), - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(speed, speed), - speed_lead_values=[speed, speed, speed - oscil, speed + oscil, speed - oscil, speed + oscil, speed - oscil], - breakpoints=[0.,2., 5, 8, 15, 18, 25.], - ) - valid, results[oscil] = man.evaluate() - labels.append(f'{oscil} m/s oscilliation size') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, D_REL)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) - - - -results = {} -name = 'Speed profile when converging to steady state lead at 30m/s' -labels = [] -for distance in np.arange(20, 140, 10): - man = Maneuver( - '', - duration=50, - initial_speed=30.0, - lead_relevancy=True, - initial_distance_lead=distance, - speed_lead_values=[30.0], - breakpoints=[0.], - ) - valid, results[distance] = man.evaluate() - results[distance][:,2] = results[distance][:,2] - results[distance][:,1] - labels.append(f'{distance} m initial distance') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, D_REL)) - - -results = {} -name = 'Speed profile when converging to steady state lead at 20m/s' -labels = [] -for distance in np.arange(20, 140, 10): - man = Maneuver( - '', - duration=50, - initial_speed=20.0, - lead_relevancy=True, - initial_distance_lead=distance, - speed_lead_values=[20.0], - breakpoints=[0.], - ) - valid, results[distance] = man.evaluate() - results[distance][:,2] = results[distance][:,2] - results[distance][:,1] - labels.append(f'{distance} m initial distance') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, D_REL)) - - -results = {} -name = 'Following car at 30m/s that comes to a stop' -labels = [] -for stop_time in np.arange(4, 14, 1): - man = Maneuver( - '', - duration=50, - initial_speed=30.0, - lead_relevancy=True, - initial_distance_lead=60.0, - speed_lead_values=[30.0, 30.0, 0.0, 0.0], - breakpoints=[0., 20., 20 + stop_time, 30 + stop_time], - ) - valid, results[stop_time] = man.evaluate() - results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] - labels.append(f'{stop_time} seconds stop time') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, D_REL)) - - -results = {} -name = 'Response to cut-in at half follow distance' -labels = [] -for speed in np.arange(0, 40, 5): - man = Maneuver( - '', - duration=10, - initial_speed=float(speed), - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(speed, speed)/2, - speed_lead_values=[speed, speed, speed], - cruise_values=[speed, speed, speed], - prob_lead_values=[0.0, 0.0, 1.0], - breakpoints=[0., 5.0, 5.01], - ) - valid, results[speed] = man.evaluate() - labels.append(f'{speed} m/s speed') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_A)) -htmls.append(get_html_from_results(results, labels, D_REL)) - - -results = {} -name = 'Follow a lead that accelerates at 2m/s^2 until steady state speed' -labels = [] -for speed in np.arange(0, 40, 5): - man = Maneuver( - '', - duration=50, - initial_speed=0.0, - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(0.0, 0.0), - speed_lead_values=[0.0, 0.0, speed], - prob_lead_values=[1.0, 1.0, 1.0], - breakpoints=[0., 1.0, speed/2], - ) - valid, results[speed] = man.evaluate() - labels.append(f'{speed} m/s speed') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) - - -results = {} -name = 'From stop to cruise' -labels = [] -for speed in np.arange(0, 40, 5): - man = Maneuver( - '', - duration=50, - initial_speed=0.0, - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(0.0, 0.0), - speed_lead_values=[0.0, 0.0], - cruise_values=[0.0, speed], - prob_lead_values=[0.0, 0.0], - breakpoints=[1., 1.01], - ) - valid, results[speed] = man.evaluate() - labels.append(f'{speed} m/s speed') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) - - -results = {} -name = 'From cruise to min' -labels = [] -for speed in np.arange(10, 40, 5): - man = Maneuver( - '', - duration=50, - initial_speed=float(speed), - lead_relevancy=True, - initial_distance_lead=desired_follow_distance(0.0, 0.0), - speed_lead_values=[0.0, 0.0], - cruise_values=[speed, 10.0], - prob_lead_values=[0.0, 0.0], - breakpoints=[1., 1.01], - ) - valid, results[speed] = man.evaluate() - labels.append(f'{speed} m/s speed') - -htmls.append(markdown.markdown('# ' + name)) -htmls.append(get_html_from_results(results, labels, EGO_V)) -htmls.append(get_html_from_results(results, labels, EGO_A)) - -if len(sys.argv) < 2: - file_name = 'long_mpc_tune_report.html' -else: - file_name = sys.argv[1] - -with open(file_name, 'w') as f: - f.write(markdown.markdown('# MPC longitudinal tuning report')) - -with open(file_name, 'a') as f: - for html in htmls: - f.write(html) +def generate_mpc_tuning_report(): + htmls = [] + + results = {} + name = 'Resuming behind lead' + labels = [] + for lead_accel in np.linspace(1.0, 4.0, 4): + man = Maneuver( + '', + duration=11, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 10 * lead_accel], + cruise_values=[100, 100], + prob_lead_values=[1.0, 1.0], + breakpoints=[1., 11], + ) + valid, results[lead_accel] = man.evaluate() + labels.append(f'{lead_accel} m/s^2 lead acceleration') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + + + results = {} + name = 'Approaching stopped car from 140m' + labels = [] + for speed in np.arange(0,45,5): + man = Maneuver( + name, + duration=30., + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=140., + speed_lead_values=[0.0, 0.], + breakpoints=[0., 30.], + ) + valid, results[speed] = man.evaluate() + results[speed][:,2] = results[speed][:,2] - results[speed][:,1] + labels.append(f'{speed} m/s approach speed') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, D_REL)) + + + results = {} + name = 'Following 5s (triangular) oscillating lead' + labels = [] + speed = np.int64(10) + for oscil in np.arange(0, 10, 1): + man = Maneuver( + '', + duration=30., + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed), + speed_lead_values=[speed, speed, speed - oscil, speed + oscil, speed - oscil, speed + oscil, speed - oscil], + breakpoints=[0.,2., 5, 8, 15, 18, 25.], + ) + valid, results[oscil] = man.evaluate() + labels.append(f'{oscil} m/s oscillation size') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + + + results = {} + name = 'Speed profile when converging to steady state lead at 30m/s' + labels = [] + for distance in np.arange(20, 140, 10): + man = Maneuver( + '', + duration=50, + initial_speed=30.0, + lead_relevancy=True, + initial_distance_lead=distance, + speed_lead_values=[30.0], + breakpoints=[0.], + ) + valid, results[distance] = man.evaluate() + results[distance][:,2] = results[distance][:,2] - results[distance][:,1] + labels.append(f'{distance} m initial distance') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, D_REL)) + + + results = {} + name = 'Speed profile when converging to steady state lead at 20m/s' + labels = [] + for distance in np.arange(20, 140, 10): + man = Maneuver( + '', + duration=50, + initial_speed=20.0, + lead_relevancy=True, + initial_distance_lead=distance, + speed_lead_values=[20.0], + breakpoints=[0.], + ) + valid, results[distance] = man.evaluate() + results[distance][:,2] = results[distance][:,2] - results[distance][:,1] + labels.append(f'{distance} m initial distance') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, D_REL)) + + + results = {} + name = 'Following car at 30m/s that comes to a stop' + labels = [] + for stop_time in np.arange(4, 14, 1): + man = Maneuver( + '', + duration=50, + initial_speed=30.0, + lead_relevancy=True, + initial_distance_lead=60.0, + speed_lead_values=[30.0, 30.0, 0.0, 0.0], + breakpoints=[0., 20., 20 + stop_time, 30 + stop_time], + ) + valid, results[stop_time] = man.evaluate() + results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] + labels.append(f'{stop_time} seconds stop time') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, D_REL)) + + + results = {} + name = 'Response to cut-in at half follow distance' + labels = [] + for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=10, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed)/2, + speed_lead_values=[speed, speed, speed], + cruise_values=[speed, speed, speed], + prob_lead_values=[0.0, 0.0, 1.0], + breakpoints=[0., 5.0, 5.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, D_REL)) + + + results = {} + name = 'Follow a lead that accelerates at 2m/s^2 until steady state speed' + labels = [] + for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0, speed], + prob_lead_values=[1.0, 1.0, 1.0], + breakpoints=[0., 1.0, speed/2], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + + + results = {} + name = 'From stop to cruise' + labels = [] + for speed in np.arange(0, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=0.0, + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0], + cruise_values=[0.0, speed], + prob_lead_values=[0.0, 0.0], + breakpoints=[1., 1.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + + + results = {} + name = 'From cruise to min' + labels = [] + for speed in np.arange(10, 40, 5): + man = Maneuver( + '', + duration=50, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(0.0, 0.0), + speed_lead_values=[0.0, 0.0], + cruise_values=[speed, 10.0], + prob_lead_values=[0.0, 0.0], + breakpoints=[1., 1.01], + ) + valid, results[speed] = man.evaluate() + labels.append(f'{speed} m/s speed') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + + return htmls + +if __name__ == '__main__': + htmls = generate_mpc_tuning_report() + + if len(sys.argv) < 2: + file_name = 'long_mpc_tune_report.html' + else: + file_name = sys.argv[1] + + with open(file_name, 'w') as f: + f.write(markdown.markdown('# MPC longitudinal tuning report')) + for html in htmls: + f.write(html) From 9755520b87fac634da3068561511b8df93626c9b Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:30:49 -0800 Subject: [PATCH 123/685] longitudinal mpc tuning report: add sinusoidal oscillation scenario (#37100) --- .../mpc_longitudinal_tuning_report.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 51e38112f75..547f45aa2e6 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -3,6 +3,7 @@ import markdown import numpy as np import matplotlib.pyplot as plt +from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver @@ -108,6 +109,33 @@ def generate_mpc_tuning_report(): htmls.append(get_html_from_results(results, labels, EGO_A)) + results = {} + name = 'Following 5s (sinusoidal) oscillating lead' + labels = [] + speed = np.int64(10) + duration = float(30) + f_osc = 1. / 5 + for oscil in np.arange(0, 10, 1): + bps = DT_MDL * np.arange(int(duration / DT_MDL)) + lead_speeds = speed + oscil * np.sin(2 * np.pi * f_osc * bps) + man = Maneuver( + '', + duration=duration, + initial_speed=float(speed), + lead_relevancy=True, + initial_distance_lead=desired_follow_distance(speed, speed), + speed_lead_values=lead_speeds, + breakpoints=bps, + ) + valid, results[oscil] = man.evaluate() + labels.append(f'{oscil} m/s oscilliation size') + + htmls.append(markdown.markdown('# ' + name)) + htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, EGO_V)) + htmls.append(get_html_from_results(results, labels, EGO_A)) + + results = {} name = 'Speed profile when converging to steady state lead at 30m/s' labels = [] From 187d3a079c45b2221cb46744bff4defc224f1fc5 Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:36:51 -0800 Subject: [PATCH 124/685] long_mpc: use log.capnp source enum (#37096) --- selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py | 7 ++++--- selfdrive/controls/lib/longitudinal_planner.py | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 9408132c5b8..e75705cb68b 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -22,7 +22,8 @@ EXPORT_DIR = os.path.join(LONG_MPC_DIR, "c_generated_code") JSON_FILE = os.path.join(LONG_MPC_DIR, "acados_ocp_long.json") -SOURCES = ['lead0', 'lead1', 'cruise', 'e2e'] +LongitudinalPlanSource = log.LongitudinalPlan.LongitudinalPlanSource +MPC_SOURCES = (LongitudinalPlanSource.lead0, LongitudinalPlanSource.lead1, LongitudinalPlanSource.cruise) X_DIM = 3 U_DIM = 1 @@ -217,7 +218,7 @@ def __init__(self, dt=DT_MDL): self.dt = dt self.solver = AcadosOcpSolverCython(MODEL_NAME, ACADOS_SOLVER_TYPE, N) self.reset() - self.source = SOURCES[2] + self.source = LongitudinalPlanSource.cruise def reset(self): self.solver.reset() @@ -335,7 +336,7 @@ def update(self, radarstate, v_cruise, personality=log.LongitudinalPersonality.s cruise_obstacle = np.cumsum(T_DIFFS * v_cruise_clipped) + get_safe_obstacle_distance(v_cruise_clipped, t_follow) x_obstacles = np.column_stack([lead_0_obstacle, lead_1_obstacle, cruise_obstacle]) - self.source = SOURCES[np.argmin(x_obstacles[0])] + self.source = MPC_SOURCES[np.argmin(x_obstacles[0])] self.yref[:,:] = 0.0 for i in range(N): diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index c5c03eba180..64de1a8fda1 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -9,7 +9,7 @@ from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState -from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, SOURCES +from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, LongitudinalPlanSource from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.drive_helpers import CONTROL_N, get_accel_from_plan from openpilot.selfdrive.car.cruise import V_CRUISE_MAX, V_CRUISE_UNSET @@ -156,7 +156,7 @@ def update(self, sm): output_a_target = min(output_a_target_e2e, output_a_target_mpc) self.output_should_stop = output_should_stop_e2e or output_should_stop_mpc if output_a_target < output_a_target_mpc: - self.mpc.source = SOURCES[3] + self.mpc.source = LongitudinalPlanSource.e2e else: output_a_target = output_a_target_mpc self.output_should_stop = output_should_stop_mpc From 593c3a0c8e69686ee821b2a0b2cfa9901b67dc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 6 Feb 2026 14:13:46 -0800 Subject: [PATCH 125/685] Calibrate in tg (#36621) * squash * bump tg * fix linmt * Ready to merge * cleaner * match modeld * more dead stuff --- selfdrive/modeld/SConscript | 49 ++--- selfdrive/modeld/compile_warp.py | 206 ++++++++++++++++++ selfdrive/modeld/dmonitoringmodeld.py | 27 ++- selfdrive/modeld/modeld.py | 47 ++-- selfdrive/modeld/models/commonmodel.cc | 64 ------ selfdrive/modeld/models/commonmodel.h | 97 --------- selfdrive/modeld/models/commonmodel.pxd | 27 --- selfdrive/modeld/models/commonmodel_pyx.pxd | 13 -- selfdrive/modeld/models/commonmodel_pyx.pyx | 74 ------- selfdrive/modeld/transforms/loadyuv.cc | 76 ------- selfdrive/modeld/transforms/loadyuv.cl | 47 ---- selfdrive/modeld/transforms/loadyuv.h | 20 -- selfdrive/modeld/transforms/transform.cc | 97 --------- selfdrive/modeld/transforms/transform.cl | 54 ----- selfdrive/modeld/transforms/transform.h | 25 --- selfdrive/test/process_replay/model_replay.py | 2 +- .../test/process_replay/process_replay.py | 15 +- 17 files changed, 280 insertions(+), 660 deletions(-) create mode 100755 selfdrive/modeld/compile_warp.py delete mode 100644 selfdrive/modeld/models/commonmodel.cc delete mode 100644 selfdrive/modeld/models/commonmodel.h delete mode 100644 selfdrive/modeld/models/commonmodel.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pyx delete mode 100644 selfdrive/modeld/transforms/loadyuv.cc delete mode 100644 selfdrive/modeld/transforms/loadyuv.cl delete mode 100644 selfdrive/modeld/transforms/loadyuv.h delete mode 100644 selfdrive/modeld/transforms/transform.cc delete mode 100644 selfdrive/modeld/transforms/transform.cl delete mode 100644 selfdrive/modeld/transforms/transform.h diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 91f3597447b..72d3f95c41f 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,35 +1,10 @@ import os import glob -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') +Import('env', 'arch') lenv = env.Clone() -lenvCython = envCython.Clone() -libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] -frameworks = [] - -common_src = [ - "models/commonmodel.cc", - "transforms/loadyuv.cc", - "transforms/transform.cc", -] - -# OpenCL is a framework on Mac -if arch == "Darwin": - frameworks += ['OpenCL'] -else: - libs += ['OpenCL'] - -# Set path definitions -for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): - for xenv in (lenv, lenvCython): - xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') - -# Compile cython -cython_libs = envCython["LIBS"] + libs -commonmodel_lib = lenv.Library('commonmodel', common_src) -lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) -tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) +tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x] # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: @@ -38,22 +13,30 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) +# compile warp +tg_flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env +}.get(arch, 'DEV=CPU CPU_LLVM=1') +image_flag = { + 'larch64': 'IMAGE=2', +}.get(arch, 'IMAGE=0') +script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] +cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +lenv.Command(fn + "warp_tinygrad.pkl", tinygrad_files + script_files, cmd) + def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath return lenv.Command( fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' ) # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: - flags = { - 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env - }.get(arch, 'DEV=CPU CPU_LLVM=1') - tg_compile(flags, model_name) + tg_compile(tg_flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py new file mode 100755 index 00000000000..5adb60e6240 --- /dev/null +++ b/selfdrive/modeld/compile_warp.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +import time +import pickle +import numpy as np +from pathlib import Path +from tinygrad.tensor import Tensor +from tinygrad.helpers import Context +from tinygrad.device import Device +from tinygrad.engine.jit import TinyJit + +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye + +MODELS_DIR = Path(__file__).parent / 'models' + +CAMERA_CONFIGS = [ + (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 + (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 +] + +UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) +UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) + +IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) + + +def warp_pkl_path(w, h): + return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + + +def dm_warp_pkl_path(w, h): + return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' + + +def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, ratio): + w_dst, h_dst = dst_shape + h_src, w_src = src_shape + + x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst) + y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst) + ones = Tensor.ones_like(x) + dst_coords = x.reshape(1, -1).cat(y.reshape(1, -1)).cat(ones.reshape(1, -1)) + + src_coords = M_inv @ dst_coords + src_coords = src_coords / src_coords[2:3, :] + + x_nn_clipped = Tensor.round(src_coords[0]).clip(0, w_src - 1).cast('int') + y_nn_clipped = Tensor.round(src_coords[1]).clip(0, h_src - 1).cast('int') + idx = y_nn_clipped * w_src + (y_nn_clipped * ratio).cast('int') * stride_pad + x_nn_clipped + + sampled = src_flat[idx] + return sampled + + +def frames_to_tensor(frames, model_w, model_h): + H = (frames.shape[0] * 2) // 3 + W = frames.shape[1] + in_img1 = Tensor.cat(frames[0:H:2, 0::2], + frames[1:H:2, 0::2], + frames[0:H:2, 1::2], + frames[1:H:2, 1::2], + frames[H:H+H//4].reshape((H//2, W//2)), + frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) + return in_img1 + + +def make_frame_prepare(cam_w, cam_h, model_w, model_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + uv_offset = stride * y_height + stride_pad = stride - cam_w + + def frame_prepare_tinygrad(input_frame, M_inv): + tg_scale = Tensor(UV_SCALE_MATRIX) + M_inv_uv = tg_scale @ M_inv @ Tensor(UV_SCALE_MATRIX_INV) + with Context(SPLIT_REDUCEOP=0): + y = warp_perspective_tinygrad(input_frame[:cam_h*stride], + M_inv, (model_w, model_h), + (cam_h, cam_w), stride_pad, 1).realize() + u = warp_perspective_tinygrad(input_frame[uv_offset:uv_offset + (cam_h//4)*stride], + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), stride_pad, 0.5).realize() + v = warp_perspective_tinygrad(input_frame[uv_offset + (cam_h//4)*stride:uv_offset + (cam_h//2)*stride], + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), stride_pad, 0.5).realize() + yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) + tensor = frames_to_tensor(yuv, model_w, model_h) + return tensor + return frame_prepare_tinygrad + + +def make_update_img_input(frame_prepare, model_w, model_h): + def update_img_input_tinygrad(tensor, frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + new_img = frame_prepare(frame, M_inv) + full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() + return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) + return update_img_input_tinygrad + + +def make_update_both_imgs(frame_prepare, model_w, model_h): + update_img = make_update_img_input(frame_prepare, model_w, model_h) + + def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, + calib_big_img_buffer, new_big_img, M_inv_big): + calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) + calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) + return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair + return update_both_imgs_tinygrad + + +def make_warp_dm(cam_w, cam_h, dm_w, dm_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + stride_pad = stride - cam_w + + def warp_dm(input_frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + with Context(SPLIT_REDUCEOP=0): + result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad, 1).reshape(-1, dm_h * dm_w) + return result + return warp_dm + + +def compile_modeld_warp(cam_w, cam_h): + model_w, model_h = MEDMODEL_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling modeld warp for {cam_w}x{cam_h}...") + + frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) + update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) + update_img_jit = TinyJit(update_both_imgs, prune=True) + + full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + + for i in range(10): + new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + img_inputs = [full_buffer, + Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + big_img_inputs = [big_full_buffer, + Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + inputs = img_inputs + big_img_inputs + Device.default.synchronize() + + inputs_np = [x.numpy() for x in inputs] + inputs_np[0] = full_buffer_np + inputs_np[3] = big_full_buffer_np + + st = time.perf_counter() + out = update_img_jit(*inputs) + full_buffer = out[0].contiguous().realize().clone() + big_full_buffer = out[2].contiguous().realize().clone() + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(update_img_jit, f) + print(f" Saved to {pkl_path}") + + jit = pickle.load(open(pkl_path, "rb")) + jit(*inputs) + + +def compile_dm_warp(cam_w, cam_h): + dm_w, dm_h = DM_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling DM warp for {cam_w}x{cam_h}...") + + warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) + warp_dm_jit = TinyJit(warp_dm, prune=True) + + for i in range(10): + inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + Device.default.synchronize() + st = time.perf_counter() + warp_dm_jit(*inputs) + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = dm_warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(warp_dm_jit, f) + print(f" Saved to {pkl_path}") + + +def run_and_save_pickle(): + for cam_w, cam_h in CAMERA_CONFIGS: + compile_modeld_warp(cam_w, cam_h) + compile_dm_warp(cam_w, cam_h) + + +if __name__ == "__main__": + run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index fca762c69bf..f12c13081b0 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,7 +3,6 @@ from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -12,19 +11,19 @@ from cereal import messaging from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from msgq.visionipc.visionipc_pyx import CLContext from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' - +MODELS_DIR = Path(__file__).parent / 'models' class ModelState: inputs: dict[str, np.ndarray] @@ -36,12 +35,15 @@ def __init__(self, cl_ctx): self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] - self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } + self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} + self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} + self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} + self.image_warp = None with open(MODEL_PKL_PATH, "rb") as f: self.model_run = pickle.load(f) @@ -50,14 +52,15 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple t1 = time.perf_counter() - input_img_cl = self.frame.prepare(buf, transform.flatten()) - if TICI: - # The imgs tensors are backed by opencl memory, only need init once - if 'input_img' not in self.tensor_inputs: - self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) - else: - self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() + if self.image_warp is None: + self.frame_buf_params = get_nv12_info(buf.width, buf.height) + warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.image_warp = pickle.load(f) + self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() + self.warp_inputs_np['transform'][:] = transform[:] + self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 1f347dc32a0..d9445c8d33d 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,7 +7,6 @@ os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -16,20 +15,20 @@ from pathlib import Path from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf +from msgq.visionipc.visionipc_pyx import CLContext from opendbc.car.car_helpers import get_demo_car_params from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.selfdrive.modeld.constants import ModelConstants, Plan -from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.modeld" @@ -39,11 +38,15 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' +MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 +IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) +assert IMG_QUEUE_SHAPE[0] == 30 + def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -136,7 +139,6 @@ def get(self, *names) -> dict[str, np.ndarray]: return out class ModelState: - frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse @@ -155,7 +157,6 @@ def __init__(self, context: CLContext): self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] - self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -166,11 +167,17 @@ def __init__(self, context: CLContext): self.full_input_queues.reset() # img buffers are managed in openCL transform code - self.vision_inputs: dict[str, Tensor] = {} + self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), + 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} + self.full_frames : dict[str, Tensor] = {} + self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} + self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() + self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} + self.update_imgs = None with open(VISION_PKL_PATH, "rb") as f: self.vision_run = pickle.load(f) @@ -188,23 +195,28 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] + if self.update_imgs is None: + for key in bufs.keys(): + w, h = bufs[key].width, bufs[key].height + self.frame_buf_params[key] = get_nv12_info(w, h) + warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.update_imgs = pickle.load(f) - imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} - if TICI and not USBGPU: - # The imgs tensors are backed by opencl memory, only need init once - for key in imgs_cl: - if key not in self.vision_inputs: - self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) - else: - for key in imgs_cl: - frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) - self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() + for key in bufs.keys(): + self.full_frames[key] = Tensor.from_blob(bufs[key].data.ctypes.data, (self.frame_buf_params[key][3],), dtype='uint8').realize() + self.transforms_np[key][:,:] = transforms[key][:,:] + + out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], + self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) + self.img_queues['img'], self.img_queues['big_img'], = out[0].realize(), out[2].realize() + vision_inputs = {'img': out[1], 'big_img': out[3]} if prepare_only: return None - self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -214,7 +226,6 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) - combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc deleted file mode 100644 index d3341e76ec3..00000000000 --- a/selfdrive/modeld/models/commonmodel.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "selfdrive/modeld/models/commonmodel.h" - -#include -#include - -#include "common/clutil.h" - -DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - temporal_skip = _temporal_skip; - input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); - region.origin = temporal_skip * frame_size_bytes; - region.size = frame_size_bytes; - last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); - - loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - - for (int i = 0; i < temporal_skip; i++) { - CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); - } - loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); - - copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); - copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); - - // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. - clFinish(q); - return &input_frames_cl; -} - -DrivingModelFrame::~DrivingModelFrame() { - deinit_transform(); - loadyuv_destroy(&loadyuv); - CL_CHECK(clReleaseMemObject(input_frames_cl)); - CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); - CL_CHECK(clReleaseMemObject(last_img_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} - - -MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - clFinish(q); - return &y_cl; -} - -MonitoringModelFrame::~MonitoringModelFrame() { - deinit_transform(); - CL_CHECK(clReleaseMemObject(input_frame_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h deleted file mode 100644 index 176d7eb6dcf..00000000000 --- a/selfdrive/modeld/models/commonmodel.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" -#include "selfdrive/modeld/transforms/loadyuv.h" -#include "selfdrive/modeld/transforms/transform.h" - -class ModelFrame { -public: - ModelFrame(cl_device_id device_id, cl_context context) { - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); - } - virtual ~ModelFrame() {} - virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } - uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { - CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); - clFinish(q); - return &input_frames[0]; - } - - int MODEL_WIDTH; - int MODEL_HEIGHT; - int MODEL_FRAME_SIZE; - int buf_size; - -protected: - cl_mem y_cl, u_cl, v_cl; - Transform transform; - cl_command_queue q; - std::unique_ptr input_frames; - - void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { - y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); - u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - transform_init(&transform, context, device_id); - } - - void deinit_transform() { - transform_destroy(&transform); - CL_CHECK(clReleaseMemObject(v_cl)); - CL_CHECK(clReleaseMemObject(u_cl)); - CL_CHECK(clReleaseMemObject(y_cl)); - } - - void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - transform_queue(&transform, q, - yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, - y_cl, u_cl, v_cl, model_width, model_height, projection); - } -}; - -class DrivingModelFrame : public ModelFrame { -public: - DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); - ~DrivingModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 512; - const int MODEL_HEIGHT = 256; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart - const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); - -private: - LoadYUVState loadyuv; - cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; - cl_buffer_region region; - int temporal_skip; -}; - -class MonitoringModelFrame : public ModelFrame { -public: - MonitoringModelFrame(cl_device_id device_id, cl_context context); - ~MonitoringModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 1440; - const int MODEL_HEIGHT = 960; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; - const int buf_size = MODEL_FRAME_SIZE; - -private: - cl_mem input_frame_cl; -}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd deleted file mode 100644 index 4ac64d91720..00000000000 --- a/selfdrive/modeld/models/commonmodel.pxd +++ /dev/null @@ -1,27 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem - -cdef extern from "common/mat.h": - cdef struct mat3: - float v[9] - -cdef extern from "common/clutil.h": - cdef unsigned long CL_DEVICE_TYPE_DEFAULT - cl_device_id cl_get_device_id(unsigned long) - cl_context cl_create_context(cl_device_id) - void cl_release_context(cl_context) - -cdef extern from "selfdrive/modeld/models/commonmodel.h": - cppclass ModelFrame: - int buf_size - unsigned char * buffer_from_cl(cl_mem*, int); - cl_mem * prepare(cl_mem, int, int, int, int, mat3) - - cppclass DrivingModelFrame: - int buf_size - DrivingModelFrame(cl_device_id, cl_context, int) - - cppclass MonitoringModelFrame: - int buf_size - MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd deleted file mode 100644 index 0bb798625be..00000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pxd +++ /dev/null @@ -1,13 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext - -cdef class CLContext(BaseCLContext): - pass - -cdef class CLMem: - cdef cl_mem * mem - - @staticmethod - cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx deleted file mode 100644 index 5b7d11bc71a..00000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pyx +++ /dev/null @@ -1,74 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -import numpy as np -cimport numpy as cnp -from libc.string cimport memcpy -from libc.stdint cimport uintptr_t - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext -from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context -from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame - - -cdef class CLContext(BaseCLContext): - def __cinit__(self): - self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) - self.context = cl_create_context(self.device_id) - - def __dealloc__(self): - if self.context: - cl_release_context(self.context) - -cdef class CLMem: - @staticmethod - cdef create(void * cmem): - mem = CLMem() - mem.mem = cmem - return mem - - @property - def mem_address(self): - return (self.mem) - -def cl_from_visionbuf(VisionBuf buf): - return CLMem.create(&buf.buf.buf_cl) - - -cdef class ModelFrame: - cdef cppModelFrame * frame - cdef int buf_size - - def __dealloc__(self): - del self.frame - - def prepare(self, VisionBuf buf, float[:] projection): - cdef mat3 cprojection - memcpy(cprojection.v, &projection[0], 9*sizeof(float)) - cdef cl_mem * data - data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) - return CLMem.create(data) - - def buffer_from_cl(self, CLMem in_frames): - cdef unsigned char * data2 - data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) - return np.asarray( data2) - - -cdef class DrivingModelFrame(ModelFrame): - cdef cppDrivingModelFrame * _frame - - def __cinit__(self, CLContext context, int temporal_skip): - self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - -cdef class MonitoringModelFrame(ModelFrame): - cdef cppMonitoringModelFrame * _frame - - def __cinit__(self, CLContext context): - self._frame = new cppMonitoringModelFrame(context.device_id, context.context) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc deleted file mode 100644 index c93f5cd0381..00000000000 --- a/selfdrive/modeld/transforms/loadyuv.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "selfdrive/modeld/transforms/loadyuv.h" - -#include -#include -#include - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { - memset(s, 0, sizeof(*s)); - - s->width = width; - s->height = height; - - char args[1024]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", - width, height); - cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); - - s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); - s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); - s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); - - // done with this - CL_CHECK(clReleaseProgram(prg)); -} - -void loadyuv_destroy(LoadYUVState* s) { - CL_CHECK(clReleaseKernel(s->loadys_krnl)); - CL_CHECK(clReleaseKernel(s->loaduv_krnl)); - CL_CHECK(clReleaseKernel(s->copy_krnl)); -} - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl) { - cl_int global_out_off = 0; - - CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); - - const size_t loadys_work_size = (s->width*s->height)/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, - &loadys_work_size, NULL, 0, 0, NULL)); - - const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; - global_out_off += (s->width*s->height); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); - - global_out_off += (s->width/2)*(s->height/2); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); -} - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size) { - CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); - const size_t copy_work_size = size/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, - ©_work_size, NULL, 0, 0, NULL)); -} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl deleted file mode 100644 index 970187a6d70..00000000000 --- a/selfdrive/modeld/transforms/loadyuv.cl +++ /dev/null @@ -1,47 +0,0 @@ -#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) - -__kernel void loadys(__global uchar8 const * const Y, - __global uchar * out, - int out_offset) -{ - const int gid = get_global_id(0); - const int ois = gid * 8; - const int oy = ois / TRANSFORMED_WIDTH; - const int ox = ois % TRANSFORMED_WIDTH; - - const uchar8 ys = Y[gid]; - - // 02 - // 13 - - __global uchar* outy0; - __global uchar* outy1; - if ((oy & 1) == 0) { - outy0 = out + out_offset; //y0 - outy1 = out + out_offset + UV_SIZE*2; //y2 - } else { - outy0 = out + out_offset + UV_SIZE; //y1 - outy1 = out + out_offset + UV_SIZE*3; //y3 - } - - vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); - vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); -} - -__kernel void loaduv(__global uchar8 const * const in, - __global uchar8 * out, - int out_offset) -{ - const int gid = get_global_id(0); - const uchar8 inv = in[gid]; - out[gid + out_offset / 8] = inv; -} - -__kernel void copy(__global uchar8 * in, - __global uchar8 * out, - int in_offset, - int out_offset) -{ - const int gid = get_global_id(0); - out[gid + out_offset / 8] = in[gid + in_offset / 8]; -} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h deleted file mode 100644 index 659059cd25e..00000000000 --- a/selfdrive/modeld/transforms/loadyuv.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "common/clutil.h" - -typedef struct { - int width, height; - cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; -} LoadYUVState; - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); - -void loadyuv_destroy(LoadYUVState* s); - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl); - - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc deleted file mode 100644 index 305643cf42e..00000000000 --- a/selfdrive/modeld/transforms/transform.cc +++ /dev/null @@ -1,97 +0,0 @@ -#include "selfdrive/modeld/transforms/transform.h" - -#include -#include - -#include "common/clutil.h" - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { - memset(s, 0, sizeof(*s)); - - cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); - s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); - // done with this - CL_CHECK(clReleaseProgram(prg)); - - s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); - s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); -} - -void transform_destroy(Transform* s) { - CL_CHECK(clReleaseMemObject(s->m_y_cl)); - CL_CHECK(clReleaseMemObject(s->m_uv_cl)); - CL_CHECK(clReleaseKernel(s->krnl)); -} - -void transform_queue(Transform* s, - cl_command_queue q, - cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection) { - const int zero = 0; - - // sampled using pixel center origin - // (because that's how fastcv and opencv does it) - - mat3 projection_y = projection; - - // in and out uv is half the size of y. - mat3 projection_uv = transform_scale_buffer(projection, 0.5); - - CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); - CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); - - const int in_y_width = in_width; - const int in_y_height = in_height; - const int in_y_px_stride = 1; - const int in_uv_width = in_width/2; - const int in_uv_height = in_height/2; - const int in_uv_px_stride = 2; - const int in_u_offset = in_uv_offset; - const int in_v_offset = in_uv_offset + 1; - - const int out_y_width = out_width; - const int out_y_height = out_height; - const int out_uv_width = out_width/2; - const int out_uv_height = out_height/2; - - CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src - CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M - - const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_y, NULL, 0, 0, NULL)); - - const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; - - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); -} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl deleted file mode 100644 index 2ca25920cd1..00000000000 --- a/selfdrive/modeld/transforms/transform.cl +++ /dev/null @@ -1,54 +0,0 @@ -#define INTER_BITS 5 -#define INTER_TAB_SIZE (1 << INTER_BITS) -#define INTER_SCALE 1.f / INTER_TAB_SIZE - -#define INTER_REMAP_COEF_BITS 15 -#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) - -__kernel void warpPerspective(__global const uchar * src, - int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, - __global uchar * dst, - int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, - __constant float * M) -{ - int dx = get_global_id(0); - int dy = get_global_id(1); - - if (dx < dst_cols && dy < dst_rows) - { - float X0 = M[0] * dx + M[1] * dy + M[2]; - float Y0 = M[3] * dx + M[4] * dy + M[5]; - float W = M[6] * dx + M[7] * dy + M[8]; - W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; - int X = rint(X0 * W), Y = rint(Y0 * W); - - int sx = convert_short_sat(X >> INTER_BITS); - int sy = convert_short_sat(Y >> INTER_BITS); - - short sx_clamp = clamp(sx, 0, src_cols - 1); - short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); - short sy_clamp = clamp(sy, 0, src_rows - 1); - short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); - int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - - short ay = (short)(Y & (INTER_TAB_SIZE - 1)); - short ax = (short)(X & (INTER_TAB_SIZE - 1)); - float taby = 1.f/INTER_TAB_SIZE*ay; - float tabx = 1.f/INTER_TAB_SIZE*ax; - - int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); - - int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); - int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); - - int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; - - uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); - dst[dst_index] = pix; - } -} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h deleted file mode 100644 index 771a7054b35..00000000000 --- a/selfdrive/modeld/transforms/transform.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" - -typedef struct { - cl_kernel krnl; - cl_mem m_y_cl, m_uv_cl; -} Transform; - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); - -void transform_destroy(Transform* transform); - -void transform_queue(Transform* s, cl_command_queue q, - cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection); diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 9ba599bac9c..c2423284007 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,7 +34,7 @@ EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.035, 0.025), + ("modelV2", 0.035, 0.028), ("driverStateV2", 0.02, 0.015), ] diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 8af72e5f4e7..5143334bc61 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,6 +4,7 @@ import copy import heapq import signal +import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -23,6 +24,7 @@ from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -203,7 +205,8 @@ def _setup_vision_ipc(self, all_msgs: LogIterable, frs: dict[str, Any]): if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - vipc_server.create_buffers(meta.stream, 2, *frame_size) + stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) + vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) vipc_server.start_listener() self.vipc_server = vipc_server @@ -300,7 +303,15 @@ def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), + + h, w = frs[m.which()].h, frs[m.which()].w + stride, y_height, _, yuv_size = get_nv12_info(w, h) + uv_offset = stride * y_height + padded_img = np.zeros((yuv_size), dtype=np.uint8).reshape((-1, stride)) + padded_img[:h, :w] = img[:h * w].reshape((-1, w)) + padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) + + self.vipc_server.send(camera_meta.stream, padded_img.flatten().tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] From 12597856dae9442357c45dd9857542ca6b4b8ada Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:26:20 -0800 Subject: [PATCH 126/685] long mpc: state name before subscript (#37101) --- .../controls/lib/longitudinal_mpc_lib/long_mpc.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index e75705cb68b..efdef9dd71b 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -108,10 +108,10 @@ def gen_long_model(): a_min = SX.sym('a_min') a_max = SX.sym('a_max') x_obstacle = SX.sym('x_obstacle') - prev_a = SX.sym('prev_a') + a_prev = SX.sym('a_prev') lead_t_follow = SX.sym('lead_t_follow') lead_danger_factor = SX.sym('lead_danger_factor') - model.p = vertcat(a_min, a_max, x_obstacle, prev_a, lead_t_follow, lead_danger_factor) + model.p = vertcat(a_min, a_max, x_obstacle, a_prev, lead_t_follow, lead_danger_factor) # dynamics model f_expl = vertcat(v_ego, a_ego, j_ego) @@ -143,7 +143,7 @@ def gen_long_ocp(): a_min, a_max = ocp.model.p[0], ocp.model.p[1] x_obstacle = ocp.model.p[2] - prev_a = ocp.model.p[3] + a_prev = ocp.model.p[3] lead_t_follow = ocp.model.p[4] lead_danger_factor = ocp.model.p[5] @@ -160,7 +160,7 @@ def gen_long_ocp(): x_ego, v_ego, a_ego, - a_ego - prev_a, + a_ego - a_prev, j_ego] ocp.model.cost_y_expr = vertcat(*costs) ocp.model.cost_y_expr_e = vertcat(*costs[:-1]) @@ -228,7 +228,7 @@ def reset(self): self.v_solution = np.zeros(N+1) self.a_solution = np.zeros(N+1) self.j_solution = np.zeros(N) - self.prev_a = np.array(self.a_solution) + self.a_prev = np.array(self.a_solution) self.yref = np.zeros((N+1, COST_DIM)) for i in range(N): @@ -346,7 +346,7 @@ def update(self, radarstate, v_cruise, personality=log.LongitudinalPersonality.s self.params[:,0] = ACCEL_MIN self.params[:,1] = ACCEL_MAX self.params[:,2] = np.min(x_obstacles, axis=1) - self.params[:,3] = np.copy(self.prev_a) + self.params[:,3] = np.copy(self.a_prev) self.params[:,4] = t_follow self.params[:,5] = LEAD_DANGER_FACTOR @@ -378,7 +378,7 @@ def run(self): self.a_solution = self.x_sol[:,2] self.j_solution = self.u_sol[:,0] - self.prev_a = np.interp(T_IDXS + self.dt, T_IDXS, self.a_solution) + self.a_prev = np.interp(T_IDXS + self.dt, T_IDXS, self.a_solution) t = time.monotonic() if self.solution_status != 0: From 96fded03992bd418099e0c19bb404d371b14966c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Feb 2026 15:13:08 -0800 Subject: [PATCH 127/685] clip: clean up imports (#37104) * wtf is this * don't break import timing * they are the same * clean up * good catch --- tools/clip/run.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 679bb241c14..13f591eb41e 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -226,11 +226,11 @@ def load_route_metadata(route): } -def draw_text_box(rl, text, x, y, size, gui_app, font, font_scale, color=None, center=False): +def draw_text_box(text, x, y, size, gui_app, font, color=None, center=False): + import pyray as rl + from openpilot.system.ui.lib.text_measure import measure_text_cached box_color, text_color = rl.Color(0, 0, 0, 85), color or rl.WHITE - # measure_text_ex is NOT auto-scaled, so multiply by font_scale - # draw_text_ex IS auto-scaled, so pass size directly - text_size = rl.measure_text_ex(font, text, size * font_scale, 0) + text_size = measure_text_cached(font, text, size) text_width, text_height = int(text_size.x), int(text_size.y) if center: x = (gui_app.width - text_width) // 2 @@ -238,7 +238,8 @@ def draw_text_box(rl, text, x, y, size, gui_app, font, font_scale, color=None, c rl.draw_text_ex(font, text, rl.Vector2(x, y), size, 0, text_color) -def _wrap_text_by_delimiter(text: str, rl, font, font_size: int, font_scale: float, max_width: int, delimiter: str = ", ") -> list[str]: +def _wrap_text_by_delimiter(text: str, font, font_size: int, max_width: int, delimiter: str = ", ") -> list[str]: + from openpilot.system.ui.lib.text_measure import measure_text_cached """Wrap text by splitting on delimiter when it exceeds max_width.""" words = text.split(delimiter) lines: list[str] = [] @@ -248,7 +249,7 @@ def _wrap_text_by_delimiter(text: str, rl, font, font_size: int, font_scale: flo current_line.append(word) check_line = delimiter.join(current_line) # Check if line exceeds max width - if rl.measure_text_ex(font, check_line, font_size * font_scale, 0).x > max_width: + if measure_text_cached(font, check_line, font_size).x > max_width: current_line.pop() # Line is too long, move word to next line if current_line: lines.append(delimiter.join(current_line)) @@ -259,7 +260,8 @@ def _wrap_text_by_delimiter(text: str, rl, font, font_size: int, font_scale: flo return lines -def render_overlays(rl, gui_app, font, font_scale, big, metadata, title, start_time, frame_idx, show_metadata, show_time): +def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, show_metadata, show_time): + from openpilot.system.ui.lib.text_measure import measure_text_cached metadata_size = 16 if big else 12 title_size = 32 if big else 24 time_size = 24 if big else 16 @@ -269,8 +271,8 @@ def render_overlays(rl, gui_app, font, font_scale, big, metadata, title, start_t if show_time: t = start_time + frame_idx / FRAMERATE time_text = f"{int(t) // 60:02d}:{int(t) % 60:02d}" - time_width = int(rl.measure_text_ex(font, time_text, time_size * font_scale, 0).x) - draw_text_box(rl, time_text, gui_app.width - time_width - 5, 0, time_size, gui_app, font, font_scale) + time_width = int(measure_text_cached(font, time_text, time_size).x) + draw_text_box(time_text, gui_app.width - time_width - 5, 0, time_size, gui_app, font) # Metadata overlay (first 5 seconds) if show_metadata and metadata and frame_idx < FRAMERATE * 5: @@ -280,18 +282,18 @@ def render_overlays(rl, gui_app, font, font_scale, big, metadata, title, start_t # Wrap text if too wide (leave margin on each side) margin = 2 * (time_width + 10 if show_time else 20) # leave enough margin for time overlay max_width = gui_app.width - margin - lines = _wrap_text_by_delimiter(text, rl, font, metadata_size, font_scale, max_width) + lines = _wrap_text_by_delimiter(text, font, metadata_size, max_width) # Draw wrapped metadata text y_offset = 6 for line in lines: - draw_text_box(rl, line, 0, y_offset, metadata_size, gui_app, font, font_scale, center=True) - line_height = int(rl.measure_text_ex(font, line, metadata_size * font_scale, 0).y) + 4 + draw_text_box(line, 0, y_offset, metadata_size, gui_app, font, center=True) + line_height = int(measure_text_cached(font, line, metadata_size).y) + 4 y_offset += line_height # Title overlay if title: - draw_text_box(rl, title, 0, 60, title_size, gui_app, font, font_scale, center=True) + draw_text_box(title, 0, 60, title_size, gui_app, font, center=True) def clip(route: Route, output: str, start: int, end: int, headless: bool = True, big: bool = False, @@ -304,7 +306,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, else: from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView # type: ignore[assignment] from openpilot.selfdrive.ui.ui_state import ui_state - from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE + from openpilot.system.ui.lib.application import gui_app, FontWeight timer.lap("import") logger.info(f"Clipping {route.name.canonical_name}, {start}s-{end}s ({duration}s)") @@ -348,7 +350,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, ui_state.update() if should_render: road_view.render() - render_overlays(rl, gui_app, font, FONT_SCALE, big, metadata, title, start, frame_idx, show_metadata, show_time) + render_overlays(gui_app, font, big, metadata, title, start, frame_idx, show_metadata, show_time) frame_idx += 1 pbar.update(1) timer.lap("render") From 4ce701150a3f932aca210e3ff372ffcdb2a23e83 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 6 Feb 2026 16:06:16 -0800 Subject: [PATCH 128/685] rm common/mat.h --- common/mat.h | 85 ---------------------------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 common/mat.h diff --git a/common/mat.h b/common/mat.h deleted file mode 100644 index 8e10d619717..00000000000 --- a/common/mat.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -typedef struct vec3 { - float v[3]; -} vec3; - -typedef struct vec4 { - float v[4]; -} vec4; - -typedef struct mat3 { - float v[3*3]; -} mat3; - -typedef struct mat4 { - float v[4*4]; -} mat4; - -static inline mat3 matmul3(const mat3 &a, const mat3 &b) { - mat3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - float v = 0.0; - for (int k=0; k<3; k++) { - v += a.v[r*3+k] * b.v[k*3+c]; - } - ret.v[r*3+c] = v; - } - } - return ret; -} - -static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { - vec3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - ret.v[r] += a.v[r*3+c] * b.v[c]; - } - } - return ret; -} - -static inline mat4 matmul(const mat4 &a, const mat4 &b) { - mat4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - float v = 0.0; - for (int k=0; k<4; k++) { - v += a.v[r*4+k] * b.v[k*4+c]; - } - ret.v[r*4+c] = v; - } - } - return ret; -} - -static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { - vec4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - ret.v[r] += a.v[r*4+c] * b.v[c]; - } - } - return ret; -} - -// scales the input and output space of a transformation matrix -// that assumes pixel-center origin. -static inline mat3 transform_scale_buffer(const mat3 &in, float s) { - // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s - - mat3 transform_out = {{ - 1.0f/s, 0.0f, 0.5f, - 0.0f, 1.0f/s, 0.5f, - 0.0f, 0.0f, 1.0f, - }}; - - mat3 transform_in = {{ - s, 0.0f, -0.5f*s, - 0.0f, s, -0.5f*s, - 0.0f, 0.0f, 1.0f, - }}; - - return matmul3(transform_in, matmul3(in, transform_out)); -} From d5cbb89d84212bfd99d5fdd8b19f76d90b4f40e0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 6 Feb 2026 16:36:47 -0800 Subject: [PATCH 129/685] Remove all the OpenCL (#37105) * Remove all the OpenCL * lil more * bump msgq --- Dockerfile.openpilot_base | 37 - SConstruct | 1 - common/SConscript | 1 - common/clutil.cc | 98 -- common/clutil.h | 28 - msgq_repo | 2 +- selfdrive/modeld/dmonitoringmodeld.py | 8 +- selfdrive/modeld/modeld.py | 14 +- selfdrive/modeld/runners/tinygrad_helpers.py | 8 - system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.cc | 5 +- system/camerad/cameras/camera_common.h | 2 +- system/camerad/cameras/camera_qcom2.cc | 22 +- system/camerad/cameras/spectra.cc | 4 +- system/camerad/cameras/spectra.h | 2 +- system/loggerd/SConscript | 5 +- third_party/opencl/include/CL/cl.h | 1452 ----------------- third_party/opencl/include/CL/cl_d3d10.h | 131 -- third_party/opencl/include/CL/cl_d3d11.h | 131 -- .../opencl/include/CL/cl_dx9_media_sharing.h | 132 -- third_party/opencl/include/CL/cl_egl.h | 136 -- third_party/opencl/include/CL/cl_ext.h | 391 ----- third_party/opencl/include/CL/cl_ext_qcom.h | 255 --- third_party/opencl/include/CL/cl_gl.h | 167 -- third_party/opencl/include/CL/cl_gl_ext.h | 74 - third_party/opencl/include/CL/cl_platform.h | 1333 --------------- third_party/opencl/include/CL/opencl.h | 59 - tools/cabana/SConscript | 2 - tools/install_ubuntu_dependencies.sh | 3 - tools/replay/SConscript | 5 - tools/webcam/README.md | 4 - 31 files changed, 22 insertions(+), 4492 deletions(-) delete mode 100644 common/clutil.cc delete mode 100644 common/clutil.h delete mode 100644 selfdrive/modeld/runners/tinygrad_helpers.py delete mode 100644 third_party/opencl/include/CL/cl.h delete mode 100644 third_party/opencl/include/CL/cl_d3d10.h delete mode 100644 third_party/opencl/include/CL/cl_d3d11.h delete mode 100644 third_party/opencl/include/CL/cl_dx9_media_sharing.h delete mode 100644 third_party/opencl/include/CL/cl_egl.h delete mode 100644 third_party/opencl/include/CL/cl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_ext_qcom.h delete mode 100644 third_party/opencl/include/CL/cl_gl.h delete mode 100644 third_party/opencl/include/CL/cl_gl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_platform.h delete mode 100644 third_party/opencl/include/CL/opencl.h diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 44d8d95e95d..8a604129938 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -18,43 +18,6 @@ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ cd /usr/lib/gcc/arm-none-eabi/* && \ rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp -# Add OpenCL -RUN apt-get update && apt-get install -y --no-install-recommends \ - apt-utils \ - alien \ - unzip \ - tar \ - curl \ - xz-utils \ - dbus \ - gcc-arm-none-eabi \ - tmux \ - vim \ - libx11-6 \ - wget \ - && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /tmp/opencl-driver-intel && \ - cd /tmp/opencl-driver-intel && \ - wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ - mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - mkdir -p /etc/OpenCL/vendors && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ - cd /opt/intel && \ - tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - mkdir -p /etc/ld.so.conf.d && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ - ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ - cd / && \ - rm -rf /tmp/opencl-driver-intel - ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute ENV QTWEBENGINE_DISABLE_SANDBOX=1 diff --git a/SConstruct b/SConstruct index ca5b7b6cb72..4f04be624cf 100644 --- a/SConstruct +++ b/SConstruct @@ -94,7 +94,6 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": - env.Append(CPPPATH=["#third_party/opencl/include"]) env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", diff --git a/common/SConscript b/common/SConscript index 1c68cf05c7a..15a0e5eff15 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,7 +5,6 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', - 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc deleted file mode 100644 index f8381a7e092..00000000000 --- a/common/clutil.cc +++ /dev/null @@ -1,98 +0,0 @@ -#include "common/clutil.h" - -#include -#include -#include - -#include "common/util.h" -#include "common/swaglog.h" - -namespace { // helper functions - -template -std::string get_info(Func get_info_func, Id id, Name param_name) { - size_t size = 0; - CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); - std::string info(size, '\0'); - CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); - return info; -} -inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } -inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } - -void cl_print_info(cl_platform_id platform, cl_device_id device) { - size_t work_group_size = 0; - cl_device_type device_type = 0; - clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); - clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); - const char *type_str = "Other..."; - switch (device_type) { - case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; - case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; - case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; - } - - LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); - LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); - LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); - LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); - LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); - LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); - LOGD("max work group size: %zu", work_group_size); - LOGD("type = %d, %s", (int)device_type, type_str); -} - -void cl_print_build_errors(cl_program program, cl_device_id device) { - cl_build_status status; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); - size_t log_size; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); - std::string log(log_size, '\0'); - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); - - LOGE("build failed; status=%d, log: %s", status, log.c_str()); -} - -} // namespace - -cl_device_id cl_get_device_id(cl_device_type device_type) { - cl_uint num_platforms = 0; - CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); - std::unique_ptr platform_ids = std::make_unique(num_platforms); - CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); - - for (size_t i = 0; i < num_platforms; ++i) { - LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); - - // Get first device - if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { - cl_print_info(platform_ids[i], device_id); - return device_id; - } - } - LOGE("No valid openCL platform found"); - assert(0); - return nullptr; -} - -cl_context cl_create_context(cl_device_id device_id) { - return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); -} - -void cl_release_context(cl_context context) { - clReleaseContext(context); -} - -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { - return cl_program_from_source(ctx, device_id, util::read_file(path), args); -} - -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { - const char *csrc = src.c_str(); - cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); - if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { - cl_print_build_errors(prg, device_id); - assert(0); - } - return prg; -} diff --git a/common/clutil.h b/common/clutil.h deleted file mode 100644 index b364e79d45b..00000000000 --- a/common/clutil.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include - -#define CL_CHECK(_expr) \ - do { \ - assert(CL_SUCCESS == (_expr)); \ - } while (0) - -#define CL_CHECK_ERR(_expr) \ - ({ \ - cl_int err = CL_INVALID_VALUE; \ - __typeof__(_expr) _ret = _expr; \ - assert(_ret&& err == CL_SUCCESS); \ - _ret; \ - }) - -cl_device_id cl_get_device_id(cl_device_type device_type); -cl_context cl_create_context(cl_device_id device_id); -void cl_release_context(cl_context context); -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/msgq_repo b/msgq_repo index 20f2493855e..f9ebdca885c 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 20f2493855ef32339b80f0ad76b3cb82210dc474 +Subproject commit f9ebdca885cfe25295d09de9fd57023a10758de5 diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index f12c13081b0..7d4df713c7f 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -11,7 +11,6 @@ from cereal import messaging from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf -from msgq.visionipc.visionipc_pyx import CLContext from openpilot.common.swaglog import cloudlog from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics @@ -29,7 +28,7 @@ class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self, cl_ctx): + def __init__(self): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] @@ -110,12 +109,11 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - cl_context = CLContext() - model = ModelState(cl_context) + model = ModelState() cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index d9445c8d33d..7fae3651095 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -15,7 +15,6 @@ from pathlib import Path from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf -from msgq.visionipc.visionipc_pyx import CLContext from opendbc.car.car_helpers import get_demo_car_params from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params @@ -143,7 +142,7 @@ class ModelState: output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self, context: CLContext): + def __init__(self): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -166,7 +165,6 @@ def __init__(self, context: CLContext): self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - # img buffers are managed in openCL transform code self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} self.full_frames : dict[str, Tensor] = {} @@ -242,10 +240,8 @@ def main(demo=False): config_realtime_process(7, 54) st = time.monotonic() - cloudlog.warning("setting up CL context") - cl_context = CLContext() - cloudlog.warning("CL context ready; loading model") - model = ModelState(cl_context) + cloudlog.warning("loading model") + model = ModelState() cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -258,8 +254,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py deleted file mode 100644 index 776381341cf..00000000000 --- a/selfdrive/modeld/runners/tinygrad_helpers.py +++ /dev/null @@ -1,8 +0,0 @@ - -from tinygrad.tensor import Tensor -from tinygrad.helpers import to_mv - -def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): - cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] - rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. - return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/system/camerad/SConscript b/system/camerad/SConscript index e288c6d8b02..c28330b32c4 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, 'OpenCL', messaging, visionipc] +libs = [common, messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 88bca7f775b..329192b63ae 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,9 +21,8 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera * const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); - camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d CL buffers", frame_buf_count); + LOGD("allocated %d buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index c26859cbc40..7f35e06a835 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ class CameraBuf { CameraBuf() = default; ~CameraBuf(); - void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index d741e13cf3b..6a7f599ab66 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,16 +12,8 @@ #include #include -#ifdef __TICI__ -#include "CL/cl_ext_qcom.h" -#else -#define CL_PRIORITY_HINT_HIGH_QCOM NULL -#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL -#endif - #include "media/cam_sensor_cmn_header.h" -#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -57,7 +49,7 @@ class CameraState { CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void init(VisionIpcServer *v); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -68,8 +60,8 @@ class CameraState { } }; -void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { - camera.camera_open(v, device_id, ctx); +void CameraState::init(VisionIpcServer *v) { + camera.camera_open(v); if (!camera.enabled) return; @@ -257,11 +249,7 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); - const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; - cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); - - VisionIpcServer v("camerad", device_id, ctx); + VisionIpcServer v("camerad"); // *** initial ISP init *** SpectraMaster m; @@ -271,7 +259,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v, device_id, ctx); + cam->init(&v); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 5c3e7a9d233..73e0a78da30 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { +void SpectraCamera::camera_open(VisionIpcServer *v) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); + buf.init(this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 13cb13f98f6..a02b8a6cac7 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ class SpectraCamera { SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void camera_open(VisionIpcServer *v); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cf169f4dc61..cc8ef7c88f6 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,16 +2,13 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'avutil', - 'yuv', 'OpenCL', 'pthread', 'zstd'] + 'yuv', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] if arch == "Darwin": - # fix OpenCL - del libs[libs.index('OpenCL')] - env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h deleted file mode 100644 index 0086319f5fa..00000000000 --- a/third_party/opencl/include/CL/cl.h +++ /dev/null @@ -1,1452 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_H -#define __OPENCL_CL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ - -typedef struct _cl_platform_id * cl_platform_id; -typedef struct _cl_device_id * cl_device_id; -typedef struct _cl_context * cl_context; -typedef struct _cl_command_queue * cl_command_queue; -typedef struct _cl_mem * cl_mem; -typedef struct _cl_program * cl_program; -typedef struct _cl_kernel * cl_kernel; -typedef struct _cl_event * cl_event; -typedef struct _cl_sampler * cl_sampler; - -typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ -typedef cl_ulong cl_bitfield; -typedef cl_bitfield cl_device_type; -typedef cl_uint cl_platform_info; -typedef cl_uint cl_device_info; -typedef cl_bitfield cl_device_fp_config; -typedef cl_uint cl_device_mem_cache_type; -typedef cl_uint cl_device_local_mem_type; -typedef cl_bitfield cl_device_exec_capabilities; -typedef cl_bitfield cl_device_svm_capabilities; -typedef cl_bitfield cl_command_queue_properties; -typedef intptr_t cl_device_partition_property; -typedef cl_bitfield cl_device_affinity_domain; - -typedef intptr_t cl_context_properties; -typedef cl_uint cl_context_info; -typedef cl_bitfield cl_queue_properties; -typedef cl_uint cl_command_queue_info; -typedef cl_uint cl_channel_order; -typedef cl_uint cl_channel_type; -typedef cl_bitfield cl_mem_flags; -typedef cl_bitfield cl_svm_mem_flags; -typedef cl_uint cl_mem_object_type; -typedef cl_uint cl_mem_info; -typedef cl_bitfield cl_mem_migration_flags; -typedef cl_uint cl_image_info; -typedef cl_uint cl_buffer_create_type; -typedef cl_uint cl_addressing_mode; -typedef cl_uint cl_filter_mode; -typedef cl_uint cl_sampler_info; -typedef cl_bitfield cl_map_flags; -typedef intptr_t cl_pipe_properties; -typedef cl_uint cl_pipe_info; -typedef cl_uint cl_program_info; -typedef cl_uint cl_program_build_info; -typedef cl_uint cl_program_binary_type; -typedef cl_int cl_build_status; -typedef cl_uint cl_kernel_info; -typedef cl_uint cl_kernel_arg_info; -typedef cl_uint cl_kernel_arg_address_qualifier; -typedef cl_uint cl_kernel_arg_access_qualifier; -typedef cl_bitfield cl_kernel_arg_type_qualifier; -typedef cl_uint cl_kernel_work_group_info; -typedef cl_uint cl_kernel_sub_group_info; -typedef cl_uint cl_event_info; -typedef cl_uint cl_command_type; -typedef cl_uint cl_profiling_info; -typedef cl_bitfield cl_sampler_properties; -typedef cl_uint cl_kernel_exec_info; - -typedef struct _cl_image_format { - cl_channel_order image_channel_order; - cl_channel_type image_channel_data_type; -} cl_image_format; - -typedef struct _cl_image_desc { - cl_mem_object_type image_type; - size_t image_width; - size_t image_height; - size_t image_depth; - size_t image_array_size; - size_t image_row_pitch; - size_t image_slice_pitch; - cl_uint num_mip_levels; - cl_uint num_samples; -#ifdef __GNUC__ - __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ -#endif - union { - cl_mem buffer; - cl_mem mem_object; - }; -} cl_image_desc; - -typedef struct _cl_buffer_region { - size_t origin; - size_t size; -} cl_buffer_region; - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_SUCCESS 0 -#define CL_DEVICE_NOT_FOUND -1 -#define CL_DEVICE_NOT_AVAILABLE -2 -#define CL_COMPILER_NOT_AVAILABLE -3 -#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 -#define CL_OUT_OF_RESOURCES -5 -#define CL_OUT_OF_HOST_MEMORY -6 -#define CL_PROFILING_INFO_NOT_AVAILABLE -7 -#define CL_MEM_COPY_OVERLAP -8 -#define CL_IMAGE_FORMAT_MISMATCH -9 -#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 -#define CL_BUILD_PROGRAM_FAILURE -11 -#define CL_MAP_FAILURE -12 -#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 -#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 -#define CL_COMPILE_PROGRAM_FAILURE -15 -#define CL_LINKER_NOT_AVAILABLE -16 -#define CL_LINK_PROGRAM_FAILURE -17 -#define CL_DEVICE_PARTITION_FAILED -18 -#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 - -#define CL_INVALID_VALUE -30 -#define CL_INVALID_DEVICE_TYPE -31 -#define CL_INVALID_PLATFORM -32 -#define CL_INVALID_DEVICE -33 -#define CL_INVALID_CONTEXT -34 -#define CL_INVALID_QUEUE_PROPERTIES -35 -#define CL_INVALID_COMMAND_QUEUE -36 -#define CL_INVALID_HOST_PTR -37 -#define CL_INVALID_MEM_OBJECT -38 -#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 -#define CL_INVALID_IMAGE_SIZE -40 -#define CL_INVALID_SAMPLER -41 -#define CL_INVALID_BINARY -42 -#define CL_INVALID_BUILD_OPTIONS -43 -#define CL_INVALID_PROGRAM -44 -#define CL_INVALID_PROGRAM_EXECUTABLE -45 -#define CL_INVALID_KERNEL_NAME -46 -#define CL_INVALID_KERNEL_DEFINITION -47 -#define CL_INVALID_KERNEL -48 -#define CL_INVALID_ARG_INDEX -49 -#define CL_INVALID_ARG_VALUE -50 -#define CL_INVALID_ARG_SIZE -51 -#define CL_INVALID_KERNEL_ARGS -52 -#define CL_INVALID_WORK_DIMENSION -53 -#define CL_INVALID_WORK_GROUP_SIZE -54 -#define CL_INVALID_WORK_ITEM_SIZE -55 -#define CL_INVALID_GLOBAL_OFFSET -56 -#define CL_INVALID_EVENT_WAIT_LIST -57 -#define CL_INVALID_EVENT -58 -#define CL_INVALID_OPERATION -59 -#define CL_INVALID_GL_OBJECT -60 -#define CL_INVALID_BUFFER_SIZE -61 -#define CL_INVALID_MIP_LEVEL -62 -#define CL_INVALID_GLOBAL_WORK_SIZE -63 -#define CL_INVALID_PROPERTY -64 -#define CL_INVALID_IMAGE_DESCRIPTOR -65 -#define CL_INVALID_COMPILER_OPTIONS -66 -#define CL_INVALID_LINKER_OPTIONS -67 -#define CL_INVALID_DEVICE_PARTITION_COUNT -68 -#define CL_INVALID_PIPE_SIZE -69 -#define CL_INVALID_DEVICE_QUEUE -70 - -/* OpenCL Version */ -#define CL_VERSION_1_0 1 -#define CL_VERSION_1_1 1 -#define CL_VERSION_1_2 1 -#define CL_VERSION_2_0 1 -#define CL_VERSION_2_1 1 - -/* cl_bool */ -#define CL_FALSE 0 -#define CL_TRUE 1 -#define CL_BLOCKING CL_TRUE -#define CL_NON_BLOCKING CL_FALSE - -/* cl_platform_info */ -#define CL_PLATFORM_PROFILE 0x0900 -#define CL_PLATFORM_VERSION 0x0901 -#define CL_PLATFORM_NAME 0x0902 -#define CL_PLATFORM_VENDOR 0x0903 -#define CL_PLATFORM_EXTENSIONS 0x0904 -#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 - -/* cl_device_type - bitfield */ -#define CL_DEVICE_TYPE_DEFAULT (1 << 0) -#define CL_DEVICE_TYPE_CPU (1 << 1) -#define CL_DEVICE_TYPE_GPU (1 << 2) -#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) -#define CL_DEVICE_TYPE_CUSTOM (1 << 4) -#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF - -/* cl_device_info */ -#define CL_DEVICE_TYPE 0x1000 -#define CL_DEVICE_VENDOR_ID 0x1001 -#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 -#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 -#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 -#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B -#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C -#define CL_DEVICE_ADDRESS_BITS 0x100D -#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E -#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F -#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 -#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 -#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 -#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 -#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 -#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 -#define CL_DEVICE_IMAGE_SUPPORT 0x1016 -#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 -#define CL_DEVICE_MAX_SAMPLERS 0x1018 -#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 -#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A -#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B -#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C -#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D -#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E -#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F -#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 -#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 -#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 -#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 -#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 -#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 -#define CL_DEVICE_ENDIAN_LITTLE 0x1026 -#define CL_DEVICE_AVAILABLE 0x1027 -#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 -#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 -#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ -#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A -#define CL_DEVICE_NAME 0x102B -#define CL_DEVICE_VENDOR 0x102C -#define CL_DRIVER_VERSION 0x102D -#define CL_DEVICE_PROFILE 0x102E -#define CL_DEVICE_VERSION 0x102F -#define CL_DEVICE_EXTENSIONS 0x1030 -#define CL_DEVICE_PLATFORM 0x1031 -#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 -/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 -#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C -#define CL_DEVICE_OPENCL_C_VERSION 0x103D -#define CL_DEVICE_LINKER_AVAILABLE 0x103E -#define CL_DEVICE_BUILT_IN_KERNELS 0x103F -#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 -#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 -#define CL_DEVICE_PARENT_DEVICE 0x1042 -#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 -#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 -#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 -#define CL_DEVICE_PARTITION_TYPE 0x1046 -#define CL_DEVICE_REFERENCE_COUNT 0x1047 -#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 -#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 -#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A -#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B -#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C -#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D -#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E -#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F -#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 -#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 -#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 -#define CL_DEVICE_SVM_CAPABILITIES 0x1053 -#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 -#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 -#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 -#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 -#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 -#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 -#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A -#define CL_DEVICE_IL_VERSION 0x105B -#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C -#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D - -/* cl_device_fp_config - bitfield */ -#define CL_FP_DENORM (1 << 0) -#define CL_FP_INF_NAN (1 << 1) -#define CL_FP_ROUND_TO_NEAREST (1 << 2) -#define CL_FP_ROUND_TO_ZERO (1 << 3) -#define CL_FP_ROUND_TO_INF (1 << 4) -#define CL_FP_FMA (1 << 5) -#define CL_FP_SOFT_FLOAT (1 << 6) -#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) - -/* cl_device_mem_cache_type */ -#define CL_NONE 0x0 -#define CL_READ_ONLY_CACHE 0x1 -#define CL_READ_WRITE_CACHE 0x2 - -/* cl_device_local_mem_type */ -#define CL_LOCAL 0x1 -#define CL_GLOBAL 0x2 - -/* cl_device_exec_capabilities - bitfield */ -#define CL_EXEC_KERNEL (1 << 0) -#define CL_EXEC_NATIVE_KERNEL (1 << 1) - -/* cl_command_queue_properties - bitfield */ -#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) -#define CL_QUEUE_PROFILING_ENABLE (1 << 1) -#define CL_QUEUE_ON_DEVICE (1 << 2) -#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) - -/* cl_context_info */ -#define CL_CONTEXT_REFERENCE_COUNT 0x1080 -#define CL_CONTEXT_DEVICES 0x1081 -#define CL_CONTEXT_PROPERTIES 0x1082 -#define CL_CONTEXT_NUM_DEVICES 0x1083 - -/* cl_context_properties */ -#define CL_CONTEXT_PLATFORM 0x1084 -#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 - -/* cl_device_partition_property */ -#define CL_DEVICE_PARTITION_EQUALLY 0x1086 -#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 -#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 -#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 - -/* cl_device_affinity_domain */ -#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) -#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) -#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) -#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) -#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) -#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) - -/* cl_device_svm_capabilities */ -#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) -#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) -#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) -#define CL_DEVICE_SVM_ATOMICS (1 << 3) - -/* cl_command_queue_info */ -#define CL_QUEUE_CONTEXT 0x1090 -#define CL_QUEUE_DEVICE 0x1091 -#define CL_QUEUE_REFERENCE_COUNT 0x1092 -#define CL_QUEUE_PROPERTIES 0x1093 -#define CL_QUEUE_SIZE 0x1094 -#define CL_QUEUE_DEVICE_DEFAULT 0x1095 - -/* cl_mem_flags and cl_svm_mem_flags - bitfield */ -#define CL_MEM_READ_WRITE (1 << 0) -#define CL_MEM_WRITE_ONLY (1 << 1) -#define CL_MEM_READ_ONLY (1 << 2) -#define CL_MEM_USE_HOST_PTR (1 << 3) -#define CL_MEM_ALLOC_HOST_PTR (1 << 4) -#define CL_MEM_COPY_HOST_PTR (1 << 5) -/* reserved (1 << 6) */ -#define CL_MEM_HOST_WRITE_ONLY (1 << 7) -#define CL_MEM_HOST_READ_ONLY (1 << 8) -#define CL_MEM_HOST_NO_ACCESS (1 << 9) -#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ -#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ -#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) - -/* cl_mem_migration_flags - bitfield */ -#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) -#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) - -/* cl_channel_order */ -#define CL_R 0x10B0 -#define CL_A 0x10B1 -#define CL_RG 0x10B2 -#define CL_RA 0x10B3 -#define CL_RGB 0x10B4 -#define CL_RGBA 0x10B5 -#define CL_BGRA 0x10B6 -#define CL_ARGB 0x10B7 -#define CL_INTENSITY 0x10B8 -#define CL_LUMINANCE 0x10B9 -#define CL_Rx 0x10BA -#define CL_RGx 0x10BB -#define CL_RGBx 0x10BC -#define CL_DEPTH 0x10BD -#define CL_DEPTH_STENCIL 0x10BE -#define CL_sRGB 0x10BF -#define CL_sRGBx 0x10C0 -#define CL_sRGBA 0x10C1 -#define CL_sBGRA 0x10C2 -#define CL_ABGR 0x10C3 - -/* cl_channel_type */ -#define CL_SNORM_INT8 0x10D0 -#define CL_SNORM_INT16 0x10D1 -#define CL_UNORM_INT8 0x10D2 -#define CL_UNORM_INT16 0x10D3 -#define CL_UNORM_SHORT_565 0x10D4 -#define CL_UNORM_SHORT_555 0x10D5 -#define CL_UNORM_INT_101010 0x10D6 -#define CL_SIGNED_INT8 0x10D7 -#define CL_SIGNED_INT16 0x10D8 -#define CL_SIGNED_INT32 0x10D9 -#define CL_UNSIGNED_INT8 0x10DA -#define CL_UNSIGNED_INT16 0x10DB -#define CL_UNSIGNED_INT32 0x10DC -#define CL_HALF_FLOAT 0x10DD -#define CL_FLOAT 0x10DE -#define CL_UNORM_INT24 0x10DF -#define CL_UNORM_INT_101010_2 0x10E0 - -/* cl_mem_object_type */ -#define CL_MEM_OBJECT_BUFFER 0x10F0 -#define CL_MEM_OBJECT_IMAGE2D 0x10F1 -#define CL_MEM_OBJECT_IMAGE3D 0x10F2 -#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 -#define CL_MEM_OBJECT_IMAGE1D 0x10F4 -#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 -#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 -#define CL_MEM_OBJECT_PIPE 0x10F7 - -/* cl_mem_info */ -#define CL_MEM_TYPE 0x1100 -#define CL_MEM_FLAGS 0x1101 -#define CL_MEM_SIZE 0x1102 -#define CL_MEM_HOST_PTR 0x1103 -#define CL_MEM_MAP_COUNT 0x1104 -#define CL_MEM_REFERENCE_COUNT 0x1105 -#define CL_MEM_CONTEXT 0x1106 -#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 -#define CL_MEM_OFFSET 0x1108 -#define CL_MEM_USES_SVM_POINTER 0x1109 - -/* cl_image_info */ -#define CL_IMAGE_FORMAT 0x1110 -#define CL_IMAGE_ELEMENT_SIZE 0x1111 -#define CL_IMAGE_ROW_PITCH 0x1112 -#define CL_IMAGE_SLICE_PITCH 0x1113 -#define CL_IMAGE_WIDTH 0x1114 -#define CL_IMAGE_HEIGHT 0x1115 -#define CL_IMAGE_DEPTH 0x1116 -#define CL_IMAGE_ARRAY_SIZE 0x1117 -#define CL_IMAGE_BUFFER 0x1118 -#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 -#define CL_IMAGE_NUM_SAMPLES 0x111A - -/* cl_pipe_info */ -#define CL_PIPE_PACKET_SIZE 0x1120 -#define CL_PIPE_MAX_PACKETS 0x1121 - -/* cl_addressing_mode */ -#define CL_ADDRESS_NONE 0x1130 -#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 -#define CL_ADDRESS_CLAMP 0x1132 -#define CL_ADDRESS_REPEAT 0x1133 -#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 - -/* cl_filter_mode */ -#define CL_FILTER_NEAREST 0x1140 -#define CL_FILTER_LINEAR 0x1141 - -/* cl_sampler_info */ -#define CL_SAMPLER_REFERENCE_COUNT 0x1150 -#define CL_SAMPLER_CONTEXT 0x1151 -#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 -#define CL_SAMPLER_ADDRESSING_MODE 0x1153 -#define CL_SAMPLER_FILTER_MODE 0x1154 -#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 -#define CL_SAMPLER_LOD_MIN 0x1156 -#define CL_SAMPLER_LOD_MAX 0x1157 - -/* cl_map_flags - bitfield */ -#define CL_MAP_READ (1 << 0) -#define CL_MAP_WRITE (1 << 1) -#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) - -/* cl_program_info */ -#define CL_PROGRAM_REFERENCE_COUNT 0x1160 -#define CL_PROGRAM_CONTEXT 0x1161 -#define CL_PROGRAM_NUM_DEVICES 0x1162 -#define CL_PROGRAM_DEVICES 0x1163 -#define CL_PROGRAM_SOURCE 0x1164 -#define CL_PROGRAM_BINARY_SIZES 0x1165 -#define CL_PROGRAM_BINARIES 0x1166 -#define CL_PROGRAM_NUM_KERNELS 0x1167 -#define CL_PROGRAM_KERNEL_NAMES 0x1168 -#define CL_PROGRAM_IL 0x1169 - -/* cl_program_build_info */ -#define CL_PROGRAM_BUILD_STATUS 0x1181 -#define CL_PROGRAM_BUILD_OPTIONS 0x1182 -#define CL_PROGRAM_BUILD_LOG 0x1183 -#define CL_PROGRAM_BINARY_TYPE 0x1184 -#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 - -/* cl_program_binary_type */ -#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 -#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 -#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 -#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 - -/* cl_build_status */ -#define CL_BUILD_SUCCESS 0 -#define CL_BUILD_NONE -1 -#define CL_BUILD_ERROR -2 -#define CL_BUILD_IN_PROGRESS -3 - -/* cl_kernel_info */ -#define CL_KERNEL_FUNCTION_NAME 0x1190 -#define CL_KERNEL_NUM_ARGS 0x1191 -#define CL_KERNEL_REFERENCE_COUNT 0x1192 -#define CL_KERNEL_CONTEXT 0x1193 -#define CL_KERNEL_PROGRAM 0x1194 -#define CL_KERNEL_ATTRIBUTES 0x1195 -#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 -#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA - -/* cl_kernel_arg_info */ -#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 -#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 -#define CL_KERNEL_ARG_TYPE_NAME 0x1198 -#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 -#define CL_KERNEL_ARG_NAME 0x119A - -/* cl_kernel_arg_address_qualifier */ -#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B -#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C -#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D -#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E - -/* cl_kernel_arg_access_qualifier */ -#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 -#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 -#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 -#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 - -/* cl_kernel_arg_type_qualifer */ -#define CL_KERNEL_ARG_TYPE_NONE 0 -#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) -#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) -#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) -#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) - -/* cl_kernel_work_group_info */ -#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 -#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 -#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 -#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 -#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 -#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 - -/* cl_kernel_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 -#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 - -/* cl_kernel_exec_info */ -#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 -#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 - -/* cl_event_info */ -#define CL_EVENT_COMMAND_QUEUE 0x11D0 -#define CL_EVENT_COMMAND_TYPE 0x11D1 -#define CL_EVENT_REFERENCE_COUNT 0x11D2 -#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 -#define CL_EVENT_CONTEXT 0x11D4 - -/* cl_command_type */ -#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 -#define CL_COMMAND_TASK 0x11F1 -#define CL_COMMAND_NATIVE_KERNEL 0x11F2 -#define CL_COMMAND_READ_BUFFER 0x11F3 -#define CL_COMMAND_WRITE_BUFFER 0x11F4 -#define CL_COMMAND_COPY_BUFFER 0x11F5 -#define CL_COMMAND_READ_IMAGE 0x11F6 -#define CL_COMMAND_WRITE_IMAGE 0x11F7 -#define CL_COMMAND_COPY_IMAGE 0x11F8 -#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 -#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA -#define CL_COMMAND_MAP_BUFFER 0x11FB -#define CL_COMMAND_MAP_IMAGE 0x11FC -#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD -#define CL_COMMAND_MARKER 0x11FE -#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF -#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 -#define CL_COMMAND_READ_BUFFER_RECT 0x1201 -#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 -#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 -#define CL_COMMAND_USER 0x1204 -#define CL_COMMAND_BARRIER 0x1205 -#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 -#define CL_COMMAND_FILL_BUFFER 0x1207 -#define CL_COMMAND_FILL_IMAGE 0x1208 -#define CL_COMMAND_SVM_FREE 0x1209 -#define CL_COMMAND_SVM_MEMCPY 0x120A -#define CL_COMMAND_SVM_MEMFILL 0x120B -#define CL_COMMAND_SVM_MAP 0x120C -#define CL_COMMAND_SVM_UNMAP 0x120D - -/* command execution status */ -#define CL_COMPLETE 0x0 -#define CL_RUNNING 0x1 -#define CL_SUBMITTED 0x2 -#define CL_QUEUED 0x3 - -/* cl_buffer_create_type */ -#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 - -/* cl_profiling_info */ -#define CL_PROFILING_COMMAND_QUEUED 0x1280 -#define CL_PROFILING_COMMAND_SUBMIT 0x1281 -#define CL_PROFILING_COMMAND_START 0x1282 -#define CL_PROFILING_COMMAND_END 0x1283 -#define CL_PROFILING_COMMAND_COMPLETE 0x1284 - -/********************************************************************************************************/ - -/* Platform API */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformIDs(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformInfo(cl_platform_id /* platform */, - cl_platform_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Device APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceIDs(cl_platform_id /* platform */, - cl_device_type /* device_type */, - cl_uint /* num_entries */, - cl_device_id * /* devices */, - cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceInfo(cl_device_id /* device */, - cl_device_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateSubDevices(cl_device_id /* in_device */, - const cl_device_partition_property * /* properties */, - cl_uint /* num_devices */, - cl_device_id * /* out_devices */, - cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetDefaultDeviceCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceAndHostTimer(cl_device_id /* device */, - cl_ulong* /* device_timestamp */, - cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetHostTimer(cl_device_id /* device */, - cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - - -/* Context APIs */ -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContext(const cl_context_properties * /* properties */, - cl_uint /* num_devices */, - const cl_device_id * /* devices */, - void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContextFromType(const cl_context_properties * /* properties */, - cl_device_type /* device_type */, - void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetContextInfo(cl_context /* context */, - cl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Command Queue APIs */ -extern CL_API_ENTRY cl_command_queue CL_API_CALL -clCreateCommandQueueWithProperties(cl_context /* context */, - cl_device_id /* device */, - const cl_queue_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetCommandQueueInfo(cl_command_queue /* command_queue */, - cl_command_queue_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Memory Object APIs */ -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - size_t /* size */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateSubBuffer(cl_mem /* buffer */, - cl_mem_flags /* flags */, - cl_buffer_create_type /* buffer_create_type */, - const void * /* buffer_create_info */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateImage(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - const cl_image_desc * /* image_desc */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreatePipe(cl_context /* context */, - cl_mem_flags /* flags */, - cl_uint /* pipe_packet_size */, - cl_uint /* pipe_max_packets */, - const cl_pipe_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSupportedImageFormats(cl_context /* context */, - cl_mem_flags /* flags */, - cl_mem_object_type /* image_type */, - cl_uint /* num_entries */, - cl_image_format * /* image_formats */, - cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetMemObjectInfo(cl_mem /* memobj */, - cl_mem_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetImageInfo(cl_mem /* image */, - cl_image_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPipeInfo(cl_mem /* pipe */, - cl_pipe_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetMemObjectDestructorCallback(cl_mem /* memobj */, - void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; - -/* SVM Allocation APIs */ -extern CL_API_ENTRY void * CL_API_CALL -clSVMAlloc(cl_context /* context */, - cl_svm_mem_flags /* flags */, - size_t /* size */, - cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY void CL_API_CALL -clSVMFree(cl_context /* context */, - void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; - -/* Sampler APIs */ -extern CL_API_ENTRY cl_sampler CL_API_CALL -clCreateSamplerWithProperties(cl_context /* context */, - const cl_sampler_properties * /* normalized_coords */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSamplerInfo(cl_sampler /* sampler */, - cl_sampler_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Program Object APIs */ -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithSource(cl_context /* context */, - cl_uint /* count */, - const char ** /* strings */, - const size_t * /* lengths */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBinary(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const size_t * /* lengths */, - const unsigned char ** /* binaries */, - cl_int * /* binary_status */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBuiltInKernels(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* kernel_names */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithIL(cl_context /* context */, - const void* /* il */, - size_t /* length */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clBuildProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCompileProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_headers */, - const cl_program * /* input_headers */, - const char ** /* header_include_names */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clLinkProgram(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_programs */, - const cl_program * /* input_programs */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */, - cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramInfo(cl_program /* program */, - cl_program_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramBuildInfo(cl_program /* program */, - cl_device_id /* device */, - cl_program_build_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Kernel Object APIs */ -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCreateKernel(cl_program /* program */, - const char * /* kernel_name */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateKernelsInProgram(cl_program /* program */, - cl_uint /* num_kernels */, - cl_kernel * /* kernels */, - cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCloneKernel(cl_kernel /* source_kernel */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArg(cl_kernel /* kernel */, - cl_uint /* arg_index */, - size_t /* arg_size */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArgSVMPointer(cl_kernel /* kernel */, - cl_uint /* arg_index */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelExecInfo(cl_kernel /* kernel */, - cl_kernel_exec_info /* param_name */, - size_t /* param_value_size */, - const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelInfo(cl_kernel /* kernel */, - cl_kernel_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelArgInfo(cl_kernel /* kernel */, - cl_uint /* arg_indx */, - cl_kernel_arg_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelWorkGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_work_group_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_sub_group_info /* param_name */, - size_t /* input_value_size */, - const void* /*input_value */, - size_t /* param_value_size */, - void* /* param_value */, - size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; - - -/* Event Object APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clWaitForEvents(cl_uint /* num_events */, - const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventInfo(cl_event /* event */, - cl_event_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateUserEvent(cl_context /* context */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetUserEventStatus(cl_event /* event */, - cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetEventCallback( cl_event /* event */, - cl_int /* command_exec_callback_type */, - void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; - -/* Profiling APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventProfilingInfo(cl_event /* event */, - cl_profiling_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Flush and Finish APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -/* Enqueued Commands APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - size_t /* offset */, - size_t /* size */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - size_t /* offset */, - size_t /* size */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - size_t /* src_offset */, - size_t /* dst_offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin */, - const size_t * /* dst_origin */, - const size_t * /* region */, - size_t /* src_row_pitch */, - size_t /* src_slice_pitch */, - size_t /* dst_row_pitch */, - size_t /* dst_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_read */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* row_pitch */, - size_t /* slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_write */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* input_row_pitch */, - size_t /* input_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - const void * /* fill_color */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImage(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_image */, - const size_t * /* src_origin[3] */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin[3] */, - const size_t * /* region[3] */, - size_t /* dst_offset */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_image */, - size_t /* src_offset */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t * /* image_row_pitch */, - size_t * /* image_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, - cl_mem /* memobj */, - void * /* mapped_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_objects */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* work_dim */, - const size_t * /* global_work_offset */, - const size_t * /* global_work_size */, - const size_t * /* local_work_size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNativeKernel(cl_command_queue /* command_queue */, - void (CL_CALLBACK * /*user_func*/)(void *), - void * /* args */, - size_t /* cb_args */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_list */, - const void ** /* args_mem_loc */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMFree(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void * /* user_data */), - void * /* user_data */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, - cl_bool /* blocking_copy */, - void * /* dst_ptr */, - const void * /* src_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemFill(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMap(cl_command_queue /* command_queue */, - cl_bool /* blocking_map */, - cl_map_flags /* flags */, - void * /* svm_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMUnmap(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - const void ** /* svm_pointers */, - const size_t * /* sizes */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; - - -/* Extension function access - * - * Returns the extension function address for the given function name, - * or NULL if a valid function can not be found. The client must - * check to make sure the address is not NULL, before using or - * calling the returned function address. - */ -extern CL_API_ENTRY void * CL_API_CALL -clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, - const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage2D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_row_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage3D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_depth */, - size_t /* image_row_pitch */, - size_t /* image_slice_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueMarker(cl_command_queue /* command_queue */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueWaitForEvents(cl_command_queue /* command_queue */, - cl_uint /* num_events */, - const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL -clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* Deprecated OpenCL 2.0 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL -clCreateCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue_properties /* properties */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL -clCreateSampler(cl_context /* context */, - cl_bool /* normalized_coords */, - cl_addressing_mode /* addressing_mode */, - cl_filter_mode /* filter_mode */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL -clEnqueueTask(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h deleted file mode 100644 index d5960a43f72..00000000000 --- a/third_party/opencl/include/CL/cl_d3d10.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D10_H -#define __OPENCL_CL_D3D10_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d10_sharing */ -#define cl_khr_d3d10_sharing 1 - -typedef cl_uint cl_d3d10_device_source_khr; -typedef cl_uint cl_d3d10_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D10_DEVICE_KHR -1002 -#define CL_INVALID_D3D10_RESOURCE_KHR -1003 -#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 -#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 - -/* cl_d3d10_device_source_nv */ -#define CL_D3D10_DEVICE_KHR 0x4010 -#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 - -/* cl_d3d10_device_set_nv */ -#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 -#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 - -/* cl_context_info */ -#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 -#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C - -/* cl_mem_info */ -#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 - -/* cl_image_info */ -#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 -#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( - cl_platform_id platform, - cl_d3d10_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d10_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D10_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h deleted file mode 100644 index 39f9072398a..00000000000 --- a/third_party/opencl/include/CL/cl_d3d11.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D11_H -#define __OPENCL_CL_D3D11_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d11_sharing */ -#define cl_khr_d3d11_sharing 1 - -typedef cl_uint cl_d3d11_device_source_khr; -typedef cl_uint cl_d3d11_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D11_DEVICE_KHR -1006 -#define CL_INVALID_D3D11_RESOURCE_KHR -1007 -#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 -#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 - -/* cl_d3d11_device_source */ -#define CL_D3D11_DEVICE_KHR 0x4019 -#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A - -/* cl_d3d11_device_set */ -#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B -#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C - -/* cl_context_info */ -#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D -#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D - -/* cl_mem_info */ -#define CL_MEM_D3D11_RESOURCE_KHR 0x401E - -/* cl_image_info */ -#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 -#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( - cl_platform_id platform, - cl_d3d11_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d11_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D11_H */ - diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h deleted file mode 100644 index 2729e8b9e89..00000000000 --- a/third_party/opencl/include/CL/cl_dx9_media_sharing.h +++ /dev/null @@ -1,132 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H -#define __OPENCL_CL_DX9_MEDIA_SHARING_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ -/* cl_khr_dx9_media_sharing */ -#define cl_khr_dx9_media_sharing 1 - -typedef cl_uint cl_dx9_media_adapter_type_khr; -typedef cl_uint cl_dx9_media_adapter_set_khr; - -#if defined(_WIN32) -#include -typedef struct _cl_dx9_surface_info_khr -{ - IDirect3DSurface9 *resource; - HANDLE shared_handle; -} cl_dx9_surface_info_khr; -#endif - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 -#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 -#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 -#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 - -/* cl_media_adapter_type_khr */ -#define CL_ADAPTER_D3D9_KHR 0x2020 -#define CL_ADAPTER_D3D9EX_KHR 0x2021 -#define CL_ADAPTER_DXVA_KHR 0x2022 - -/* cl_media_adapter_set_khr */ -#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 -#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 - -/* cl_context_info */ -#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 -#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 -#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 - -/* cl_mem_info */ -#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 -#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 - -/* cl_image_info */ -#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B -#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( - cl_platform_id platform, - cl_uint num_media_adapters, - cl_dx9_media_adapter_type_khr * media_adapter_type, - void * media_adapters, - cl_dx9_media_adapter_set_khr media_adapter_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( - cl_context context, - cl_mem_flags flags, - cl_dx9_media_adapter_type_khr adapter_type, - void * surface_info, - cl_uint plane, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ - diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h deleted file mode 100644 index a765bd5266c..00000000000 --- a/third_party/opencl/include/CL/cl_egl.h +++ /dev/null @@ -1,136 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_EGL_H -#define __OPENCL_CL_EGL_H - -#ifdef __APPLE__ - -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ -#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F -#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D -#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E - -/* Error type for clCreateFromEGLImageKHR */ -#define CL_INVALID_EGL_OBJECT_KHR -1093 -#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 - -/* CLeglImageKHR is an opaque handle to an EGLImage */ -typedef void* CLeglImageKHR; - -/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ -typedef void* CLeglDisplayKHR; - -/* CLeglSyncKHR is an opaque handle to an EGLSync object */ -typedef void* CLeglSyncKHR; - -/* properties passed to clCreateFromEGLImageKHR */ -typedef intptr_t cl_egl_image_properties_khr; - - -#define cl_khr_egl_image 1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageKHR(cl_context /* context */, - CLeglDisplayKHR /* egldisplay */, - CLeglImageKHR /* eglimage */, - cl_mem_flags /* flags */, - const cl_egl_image_properties_khr * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( - cl_context context, - CLeglDisplayKHR egldisplay, - CLeglImageKHR eglimage, - cl_mem_flags flags, - const cl_egl_image_properties_khr * properties, - cl_int * errcode_ret); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -#define cl_khr_egl_event 1 - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromEGLSyncKHR(cl_context /* context */, - CLeglSyncKHR /* sync */, - CLeglDisplayKHR /* display */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( - cl_context context, - CLeglSyncKHR sync, - CLeglDisplayKHR display, - cl_int * errcode_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h deleted file mode 100644 index 7941583895c..00000000000 --- a/third_party/opencl/include/CL/cl_ext.h +++ /dev/null @@ -1,391 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ - -/* cl_ext.h contains OpenCL extensions which don't have external */ -/* (OpenGL, D3D) dependencies. */ - -#ifndef __CL_EXT_H -#define __CL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include - #include -#else - #include -#endif - -/* cl_khr_fp16 extension - no extension #define since it has no functions */ -#define CL_DEVICE_HALF_FP_CONFIG 0x1033 - -/* Memory object destruction - * - * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR - * - * Registers a user callback function that will be called when the memory object is deleted and its resources - * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback - * stack associated with memobj. The registered user callback functions are called in the reverse order in - * which they were registered. The user callback functions are called and then the memory object is deleted - * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be - * notified when the memory referenced by host_ptr, specified when the memory object is created and used as - * the storage bits for the memory object, can be reused or freed. - * - * The application may not call CL api's with the cl_mem object passed to the pfn_notify. - * - * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - */ -#define cl_APPLE_SetMemObjectDestructor 1 -cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, - void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/* Context Logging Functions - * - * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). - * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - * - * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger - */ -#define cl_APPLE_ContextLoggingFunctions 1 -extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ -extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ -extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/************************ -* cl_khr_icd extension * -************************/ -#define cl_khr_icd 1 - -/* cl_platform_info */ -#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 - -/* Additional Error Codes */ -#define CL_PLATFORM_NOT_FOUND_KHR -1001 - -extern CL_API_ENTRY cl_int CL_API_CALL -clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( - cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - - -/* Extension: cl_khr_image2D_buffer - * - * This extension allows a 2D image to be created from a cl_mem buffer without a copy. - * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. - * Both the sampler and sampler-less read_image built-in functions are supported for 2D images - * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported - * for 2D images created from a buffer. - * - * When the 2D image from buffer is created, the client must specify the width, - * height, image format (i.e. channel order and channel data type) and optionally the row pitch - * - * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. - * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. - */ - -/************************************* - * cl_khr_initalize_memory extension * - *************************************/ - -#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 - - -/************************************** - * cl_khr_terminate_context extension * - **************************************/ - -#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 -#define CL_CONTEXT_TERMINATE_KHR 0x2032 - -#define cl_khr_terminate_context 1 -extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - - -/* - * Extension: cl_khr_spir - * - * This extension adds support to create an OpenCL program object from a - * Standard Portable Intermediate Representation (SPIR) instance - */ - -#define CL_DEVICE_SPIR_VERSIONS 0x40E0 -#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 - - -/****************************************** -* cl_nv_device_attribute_query extension * -******************************************/ -/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ -#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 -#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 -#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 -#define CL_DEVICE_WARP_SIZE_NV 0x4003 -#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 -#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 -#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 - -/********************************* -* cl_amd_device_attribute_query * -*********************************/ -#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 - -/********************************* -* cl_arm_printf extension -*********************************/ -#define CL_PRINTF_CALLBACK_ARM 0x40B0 -#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 - -#ifdef CL_VERSION_1_1 - /*********************************** - * cl_ext_device_fission extension * - ***********************************/ - #define cl_ext_device_fission 1 - - extern CL_API_ENTRY cl_int CL_API_CALL - clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - extern CL_API_ENTRY cl_int CL_API_CALL - clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef cl_ulong cl_device_partition_property_ext; - extern CL_API_ENTRY cl_int CL_API_CALL - clCreateSubDevicesEXT( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - /* cl_device_partition_property_ext */ - #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 - #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 - #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 - #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 - - /* clDeviceGetInfo selectors */ - #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 - #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 - #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 - #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 - #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 - - /* error codes */ - #define CL_DEVICE_PARTITION_FAILED_EXT -1057 - #define CL_INVALID_PARTITION_COUNT_EXT -1058 - #define CL_INVALID_PARTITION_NAME_EXT -1059 - - /* CL_AFFINITY_DOMAINs */ - #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 - #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 - #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 - #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 - #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 - #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 - - /* cl_device_partition_property_ext list terminators */ - #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) - -/********************************* -* cl_qcom_ext_host_ptr extension -*********************************/ - -#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) - -#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 -#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 -#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 -#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 -#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 -#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 -#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 -#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 - -typedef cl_uint cl_image_pitch_info_qcom; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceImageInfoQCOM(cl_device_id device, - size_t image_width, - size_t image_height, - const cl_image_format *image_format, - cl_image_pitch_info_qcom param_name, - size_t param_value_size, - void *param_value, - size_t *param_value_size_ret); - -typedef struct _cl_mem_ext_host_ptr -{ - /* Type of external memory allocation. */ - /* Legal values will be defined in layered extensions. */ - cl_uint allocation_type; - - /* Host cache policy for this external memory allocation. */ - cl_uint host_cache_policy; - -} cl_mem_ext_host_ptr; - -/********************************* -* cl_qcom_ion_host_ptr extension -*********************************/ - -#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 - -typedef struct _cl_mem_ion_host_ptr -{ - /* Type of external memory allocation. */ - /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ - cl_mem_ext_host_ptr ext_host_ptr; - - /* ION file descriptor */ - int ion_filedesc; - - /* Host pointer to the ION allocated memory */ - void* ion_hostptr; - -} cl_mem_ion_host_ptr; - -#endif /* CL_VERSION_1_1 */ - - -#ifdef CL_VERSION_2_0 -/********************************* -* cl_khr_sub_groups extension -*********************************/ -#define cl_khr_sub_groups 1 - -typedef cl_uint cl_kernel_sub_group_info_khr; - -/* cl_khr_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; - -typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; -#endif /* CL_VERSION_2_0 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_priority_hints extension -*********************************/ -#define cl_khr_priority_hints 1 - -typedef cl_uint cl_queue_priority_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_PRIORITY_KHR 0x1096 - -/* cl_queue_priority_khr */ -#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) -#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) -#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_throttle_hints extension -*********************************/ -#define cl_khr_throttle_hints 1 - -typedef cl_uint cl_queue_throttle_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_THROTTLE_KHR 0x1097 - -/* cl_queue_throttle_khr */ -#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) -#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) -#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef __cplusplus -} -#endif - - -#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h deleted file mode 100644 index 6328a1cd93a..00000000000 --- a/third_party/opencl/include/CL/cl_ext_qcom.h +++ /dev/null @@ -1,255 +0,0 @@ -/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. - * Qualcomm Technologies Proprietary and Confidential. - */ - -#ifndef __OPENCL_CL_EXT_QCOM_H -#define __OPENCL_CL_EXT_QCOM_H - -// Needed by cl_khr_egl_event extension -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -/************************************ - * cl_qcom_create_buffer_from_image * - ************************************/ - -#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 -#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBufferFromImageQCOM(cl_mem image, - cl_mem_flags flags, - cl_int *errcode_ret); - - -/************************************ - * cl_qcom_limited_printf extension * - ************************************/ - -/* Builtin printf function buffer size in bytes. */ -#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 - - -/************************************* - * cl_qcom_extended_images extension * - *************************************/ - -#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF - -/************************************* - * cl_qcom_perf_hint extension * - *************************************/ - -typedef cl_uint cl_perf_hint; - -#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 - -/*cl_perf_hint*/ -#define CL_PERF_HINT_HIGH_QCOM 0x40C3 -#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 -#define CL_PERF_HINT_LOW_QCOM 0x40C5 - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetPerfHintQCOM(cl_context context, - cl_perf_hint perf_hint); - -// This extension is published at Khronos, so its definitions are made in cl_ext.h. -// This duplication is for backward compatibility. - -#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/********************************* -* cl_qcom_android_native_buffer_host_ptr extension -*********************************/ - -#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 - - -typedef struct _cl_mem_android_native_buffer_host_ptr -{ - // Type of external memory allocation. - // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. - cl_mem_ext_host_ptr ext_host_ptr; - - // Virtual pointer to the android native buffer - void* anb_ptr; - -} cl_mem_android_native_buffer_host_ptr; - -#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/*********************************** -* cl_img_egl_image extension * -************************************/ -typedef void* CLeglImageIMG; -typedef void* CLeglDisplayIMG; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageIMG(cl_context context, - cl_mem_flags flags, - CLeglImageIMG image, - CLeglDisplayIMG display, - cl_int *errcode_ret); - - -/********************************* -* cl_qcom_other_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-standard images -#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) - -// cl_channel_type -#define CL_QCOM_UNORM_MIPI10 0x4159 -#define CL_QCOM_UNORM_MIPI12 0x415A -#define CL_QCOM_UNSIGNED_MIPI10 0x415B -#define CL_QCOM_UNSIGNED_MIPI12 0x415C -#define CL_QCOM_UNORM_INT10 0x415D -#define CL_QCOM_UNORM_INT12 0x415E -#define CL_QCOM_UNSIGNED_INT16 0x415F - -// cl_channel_order -// Dedicate 0x4130-0x415F range for QCOM extended image formats -// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format -#define CL_QCOM_BAYER 0x414E - -#define CL_QCOM_NV12 0x4133 -#define CL_QCOM_NV12_Y 0x4134 -#define CL_QCOM_NV12_UV 0x4135 - -#define CL_QCOM_TILED_NV12 0x4136 -#define CL_QCOM_TILED_NV12_Y 0x4137 -#define CL_QCOM_TILED_NV12_UV 0x4138 - -#define CL_QCOM_P010 0x413C -#define CL_QCOM_P010_Y 0x413D -#define CL_QCOM_P010_UV 0x413E - -#define CL_QCOM_TILED_P010 0x413F -#define CL_QCOM_TILED_P010_Y 0x4140 -#define CL_QCOM_TILED_P010_UV 0x4141 - - -#define CL_QCOM_TP10 0x4145 -#define CL_QCOM_TP10_Y 0x4146 -#define CL_QCOM_TP10_UV 0x4147 - -#define CL_QCOM_TILED_TP10 0x4148 -#define CL_QCOM_TILED_TP10_Y 0x4149 -#define CL_QCOM_TILED_TP10_UV 0x414A - -/********************************* -* cl_qcom_compressed_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-planar compressed images -#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) - -// Extended image format -// cl_channel_order -#define CL_QCOM_COMPRESSED_RGBA 0x4130 -#define CL_QCOM_COMPRESSED_RGBx 0x4131 - -#define CL_QCOM_COMPRESSED_NV12_Y 0x413A -#define CL_QCOM_COMPRESSED_NV12_UV 0x413B - -#define CL_QCOM_COMPRESSED_P010 0x4142 -#define CL_QCOM_COMPRESSED_P010_Y 0x4143 -#define CL_QCOM_COMPRESSED_P010_UV 0x4144 - -#define CL_QCOM_COMPRESSED_TP10 0x414B -#define CL_QCOM_COMPRESSED_TP10_Y 0x414C -#define CL_QCOM_COMPRESSED_TP10_UV 0x414D - -#define CL_QCOM_COMPRESSED_NV12_4R 0x414F -#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 -#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 -/********************************* -* cl_qcom_compressed_yuv_image_read extension -*********************************/ - -// Extended flag for creating/querying QCOM compressed images -#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) - -// Extended image format -#define CL_QCOM_COMPRESSED_NV12 0x10C4 - -// Extended flag for setting ION buffer allocation type -#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD -#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE - -/********************************* -* cl_qcom_accelerated_image_ops -*********************************/ -#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 -#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 - -//Extended flag for specifying weight image type -#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) - -// Box Filter -typedef struct _cl_box_filter_size_qcom -{ - // Width of box filter on X direction. - float box_filter_width; - - // Height of box filter on Y direction. - float box_filter_height; -} cl_box_filter_size_qcom; - -// HOF Weight Image Desc -typedef struct _cl_weight_desc_qcom -{ - /** Coordinate of the "center" point of the weight image, - based on the weight image's top-left corner as the origin. */ - size_t center_coord_x; - size_t center_coord_y; - cl_bitfield flags; -} cl_weight_desc_qcom; - -typedef struct _cl_weight_image_desc_qcom -{ - cl_image_desc image_desc; - cl_weight_desc_qcom weight_desc; -} cl_weight_image_desc_qcom; - -/************************************* - * cl_qcom_protected_context extension * - *************************************/ - -#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 -#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 - -/************************************* - * cl_qcom_priority_hint extension * - *************************************/ -#define CL_PRIORITY_HINT_NONE_QCOM 0 -typedef cl_uint cl_priority_hint; - -#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 - -/*cl_priority_hint*/ -#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA -#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB -#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h deleted file mode 100644 index 945daa83d7f..00000000000 --- a/third_party/opencl/include/CL/cl_gl.h +++ /dev/null @@ -1,167 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -#ifndef __OPENCL_CL_GL_H -#define __OPENCL_CL_GL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef cl_uint cl_gl_object_type; -typedef cl_uint cl_gl_texture_info; -typedef cl_uint cl_gl_platform_info; -typedef struct __GLsync *cl_GLsync; - -/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ -#define CL_GL_OBJECT_BUFFER 0x2000 -#define CL_GL_OBJECT_TEXTURE2D 0x2001 -#define CL_GL_OBJECT_TEXTURE3D 0x2002 -#define CL_GL_OBJECT_RENDERBUFFER 0x2003 -#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E -#define CL_GL_OBJECT_TEXTURE1D 0x200F -#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 -#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 - -/* cl_gl_texture_info */ -#define CL_GL_TEXTURE_TARGET 0x2004 -#define CL_GL_MIPMAP_LEVEL 0x2005 -#define CL_GL_NUM_SAMPLES 0x2012 - - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* bufobj */, - int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLTexture(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLRenderbuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* renderbuffer */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLObjectInfo(cl_mem /* memobj */, - cl_gl_object_type * /* gl_object_type */, - cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLTextureInfo(cl_mem /* memobj */, - cl_gl_texture_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture2D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture3D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* cl_khr_gl_sharing extension */ - -#define cl_khr_gl_sharing 1 - -typedef cl_uint cl_gl_context_info; - -/* Additional Error Codes */ -#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 - -/* cl_gl_context_info */ -#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 -#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 - -/* Additional cl_context_properties */ -#define CL_GL_CONTEXT_KHR 0x2008 -#define CL_EGL_DISPLAY_KHR 0x2009 -#define CL_GLX_DISPLAY_KHR 0x200A -#define CL_WGL_HDC_KHR 0x200B -#define CL_CGL_SHAREGROUP_KHR 0x200C - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLContextInfoKHR(const cl_context_properties * /* properties */, - cl_gl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( - const cl_context_properties * properties, - cl_gl_context_info param_name, - size_t param_value_size, - void * param_value, - size_t * param_value_size_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h deleted file mode 100644 index e3c14c6408c..00000000000 --- a/third_party/opencl/include/CL/cl_gl_ext.h +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ -/* OpenGL dependencies. */ - -#ifndef __OPENCL_CL_GL_EXT_H -#define __OPENCL_CL_GL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include -#else - #include -#endif - -/* - * For each extension, follow this template - * cl_VEN_extname extension */ -/* #define cl_VEN_extname 1 - * ... define new types, if any - * ... define new tokens, if any - * ... define new APIs, if any - * - * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header - * This allows us to avoid having to decide whether to include GL headers or GLES here. - */ - -/* - * cl_khr_gl_event extension - * See section 9.9 in the OpenCL 1.1 spec for more information - */ -#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromGLsyncKHR(cl_context /* context */, - cl_GLsync /* cl_GLsync */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h deleted file mode 100644 index 4e334a29183..00000000000 --- a/third_party/opencl/include/CL/cl_platform.h +++ /dev/null @@ -1,1333 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ - -#ifndef __CL_PLATFORM_H -#define __CL_PLATFORM_H - -#ifdef __APPLE__ - /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ - #include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(_WIN32) - #define CL_API_ENTRY - #define CL_API_CALL __stdcall - #define CL_CALLBACK __stdcall -#else - #define CL_API_ENTRY - #define CL_API_CALL - #define CL_CALLBACK -#endif - -/* - * Deprecation flags refer to the last version of the header in which the - * feature was not deprecated. - * - * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without - * deprecation but is deprecated in versions later than 1.1. - */ - -#ifdef __APPLE__ - #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) - #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 - - #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 - #else - #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #endif -#else - #define CL_EXTENSION_WEAK_LINK - #define CL_API_SUFFIX__VERSION_1_0 - #define CL_EXT_SUFFIX__VERSION_1_0 - #define CL_API_SUFFIX__VERSION_1_1 - #define CL_EXT_SUFFIX__VERSION_1_1 - #define CL_API_SUFFIX__VERSION_1_2 - #define CL_EXT_SUFFIX__VERSION_1_2 - #define CL_API_SUFFIX__VERSION_2_0 - #define CL_EXT_SUFFIX__VERSION_2_0 - #define CL_API_SUFFIX__VERSION_2_1 - #define CL_EXT_SUFFIX__VERSION_2_1 - - #ifdef __GNUC__ - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif - #elif _WIN32 - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) - #endif - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif -#endif - -#if (defined (_WIN32) && defined(_MSC_VER)) - -/* scalar types */ -typedef signed __int8 cl_char; -typedef unsigned __int8 cl_uchar; -typedef signed __int16 cl_short; -typedef unsigned __int16 cl_ushort; -typedef signed __int32 cl_int; -typedef unsigned __int32 cl_uint; -typedef signed __int64 cl_long; -typedef unsigned __int64 cl_ulong; - -typedef unsigned __int16 cl_half; -typedef float cl_float; -typedef double cl_double; - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 340282346638528859811704183484516925440.0f -#define CL_FLT_MIN 1.175494350822287507969e-38f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 -#define CL_DBL_MIN 2.225073858507201383090e-308 -#define CL_DBL_EPSILON 2.220446049250313080847e-16 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#define CL_NAN (CL_INFINITY - CL_INFINITY) -#define CL_HUGE_VALF ((cl_float) 1e50) -#define CL_HUGE_VAL ((cl_double) 1e500) -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#else - -#include - -/* scalar types */ -typedef int8_t cl_char; -typedef uint8_t cl_uchar; -typedef int16_t cl_short __attribute__((aligned(2))); -typedef uint16_t cl_ushort __attribute__((aligned(2))); -typedef int32_t cl_int __attribute__((aligned(4))); -typedef uint32_t cl_uint __attribute__((aligned(4))); -typedef int64_t cl_long __attribute__((aligned(8))); -typedef uint64_t cl_ulong __attribute__((aligned(8))); - -typedef uint16_t cl_half __attribute__((aligned(2))); -typedef float cl_float __attribute__((aligned(4))); -typedef double cl_double __attribute__((aligned(8))); - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 0x1.fffffep127f -#define CL_FLT_MIN 0x1.0p-126f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 0x1.fffffffffffffp1023 -#define CL_DBL_MIN 0x1.0p-1022 -#define CL_DBL_EPSILON 0x1.0p-52 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#if defined( __GNUC__ ) - #define CL_HUGE_VALF __builtin_huge_valf() - #define CL_HUGE_VAL __builtin_huge_val() - #define CL_NAN __builtin_nanf( "" ) -#else - #define CL_HUGE_VALF ((cl_float) 1e50) - #define CL_HUGE_VAL ((cl_double) 1e500) - float nanf( const char * ); - #define CL_NAN nanf( "" ) -#endif -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#endif - -#include - -/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ -typedef unsigned int cl_GLuint; -typedef int cl_GLint; -typedef unsigned int cl_GLenum; - -/* - * Vector types - * - * Note: OpenCL requires that all types be naturally aligned. - * This means that vector types must be naturally aligned. - * For example, a vector of four floats must be aligned to - * a 16 byte boundary (calculated as 4 * the natural 4-byte - * alignment of the float). The alignment qualifiers here - * will only function properly if your compiler supports them - * and if you don't actively work to defeat them. For example, - * in order for a cl_float4 to be 16 byte aligned in a struct, - * the start of the struct must itself be 16-byte aligned. - * - * Maintaining proper alignment is the user's responsibility. - */ - -/* Define basic vector types */ -#if defined( __VEC__ ) - #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ - typedef vector unsigned char __cl_uchar16; - typedef vector signed char __cl_char16; - typedef vector unsigned short __cl_ushort8; - typedef vector signed short __cl_short8; - typedef vector unsigned int __cl_uint4; - typedef vector signed int __cl_int4; - typedef vector float __cl_float4; - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_UINT4__ 1 - #define __CL_INT4__ 1 - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef float __cl_float4 __attribute__((vector_size(16))); - #else - typedef __m128 __cl_float4; - #endif - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE2__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); - typedef cl_char __cl_char16 __attribute__((vector_size(16))); - typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); - typedef cl_short __cl_short8 __attribute__((vector_size(16))); - typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); - typedef cl_int __cl_int4 __attribute__((vector_size(16))); - typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); - typedef cl_long __cl_long2 __attribute__((vector_size(16))); - typedef cl_double __cl_double2 __attribute__((vector_size(16))); - #else - typedef __m128i __cl_uchar16; - typedef __m128i __cl_char16; - typedef __m128i __cl_ushort8; - typedef __m128i __cl_short8; - typedef __m128i __cl_uint4; - typedef __m128i __cl_int4; - typedef __m128i __cl_ulong2; - typedef __m128i __cl_long2; - typedef __m128d __cl_double2; - #endif - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_INT4__ 1 - #define __CL_UINT4__ 1 - #define __CL_ULONG2__ 1 - #define __CL_LONG2__ 1 - #define __CL_DOUBLE2__ 1 -#endif - -#if defined( __MMX__ ) - #include - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); - typedef cl_char __cl_char8 __attribute__((vector_size(8))); - typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); - typedef cl_short __cl_short4 __attribute__((vector_size(8))); - typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); - typedef cl_int __cl_int2 __attribute__((vector_size(8))); - typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); - typedef cl_long __cl_long1 __attribute__((vector_size(8))); - typedef cl_float __cl_float2 __attribute__((vector_size(8))); - #else - typedef __m64 __cl_uchar8; - typedef __m64 __cl_char8; - typedef __m64 __cl_ushort4; - typedef __m64 __cl_short4; - typedef __m64 __cl_uint2; - typedef __m64 __cl_int2; - typedef __m64 __cl_ulong1; - typedef __m64 __cl_long1; - typedef __m64 __cl_float2; - #endif - #define __CL_UCHAR8__ 1 - #define __CL_CHAR8__ 1 - #define __CL_USHORT4__ 1 - #define __CL_SHORT4__ 1 - #define __CL_INT2__ 1 - #define __CL_UINT2__ 1 - #define __CL_ULONG1__ 1 - #define __CL_LONG1__ 1 - #define __CL_FLOAT2__ 1 -#endif - -#if defined( __AVX__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_float __cl_float8 __attribute__((vector_size(32))); - typedef cl_double __cl_double4 __attribute__((vector_size(32))); - #else - typedef __m256 __cl_float8; - typedef __m256d __cl_double4; - #endif - #define __CL_FLOAT8__ 1 - #define __CL_DOUBLE4__ 1 -#endif - -/* Define capabilities for anonymous struct members. */ -#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ __extension__ -#elif defined( _WIN32) && (_MSC_VER >= 1500) - /* Microsoft Developer Studio 2008 supports anonymous structs, but - * complains by default. */ -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ - /* Disable warning C4201: nonstandard extension used : nameless - * struct/union */ -#pragma warning( push ) -#pragma warning( disable : 4201 ) -#else -#define __CL_HAS_ANON_STRUCT__ 0 -#define __CL_ANON_STRUCT__ -#endif - -/* Define alignment keys */ -#if defined( __GNUC__ ) - #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) -#elif defined( _WIN32) && (_MSC_VER) - /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ - /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ - /* #include */ - /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ - #define CL_ALIGNED(_x) -#else - #warning Need to implement some method to align data here - #define CL_ALIGNED(_x) -#endif - -/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ -#if __CL_HAS_ANON_STRUCT__ - /* .xyzw and .s0123...{f|F} are supported */ - #define CL_HAS_NAMED_VECTOR_FIELDS 1 - /* .hi and .lo are supported */ - #define CL_HAS_HI_LO_VECTOR_FIELDS 1 -#endif - -/* Define cl_vector types */ - -/* ---- cl_charn ---- */ -typedef union -{ - cl_char CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2; -#endif -}cl_char2; - -typedef union -{ - cl_char CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[2]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4; -#endif -}cl_char4; - -/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ -typedef cl_char4 cl_char3; - -typedef union -{ - cl_char CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[4]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[2]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8; -#endif -}cl_char8; - -typedef union -{ - cl_char CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[8]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[4]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8[2]; -#endif -#if defined( __CL_CHAR16__ ) - __cl_char16 v16; -#endif -}cl_char16; - - -/* ---- cl_ucharn ---- */ -typedef union -{ - cl_uchar CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; -#endif -#if defined( __cl_uchar2__) - __cl_uchar2 v2; -#endif -}cl_uchar2; - -typedef union -{ - cl_uchar CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[2]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4; -#endif -}cl_uchar4; - -/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ -typedef cl_uchar4 cl_uchar3; - -typedef union -{ - cl_uchar CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[4]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[2]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8; -#endif -}cl_uchar8; - -typedef union -{ - cl_uchar CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[8]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[4]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8[2]; -#endif -#if defined( __CL_UCHAR16__ ) - __cl_uchar16 v16; -#endif -}cl_uchar16; - - -/* ---- cl_shortn ---- */ -typedef union -{ - cl_short CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2; -#endif -}cl_short2; - -typedef union -{ - cl_short CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[2]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4; -#endif -}cl_short4; - -/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ -typedef cl_short4 cl_short3; - -typedef union -{ - cl_short CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[4]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[2]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8; -#endif -}cl_short8; - -typedef union -{ - cl_short CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[8]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[4]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8[2]; -#endif -#if defined( __CL_SHORT16__ ) - __cl_short16 v16; -#endif -}cl_short16; - - -/* ---- cl_ushortn ---- */ -typedef union -{ - cl_ushort CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2; -#endif -}cl_ushort2; - -typedef union -{ - cl_ushort CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[2]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4; -#endif -}cl_ushort4; - -/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ -typedef cl_ushort4 cl_ushort3; - -typedef union -{ - cl_ushort CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[4]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[2]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8; -#endif -}cl_ushort8; - -typedef union -{ - cl_ushort CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[8]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[4]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8[2]; -#endif -#if defined( __CL_USHORT16__ ) - __cl_ushort16 v16; -#endif -}cl_ushort16; - -/* ---- cl_intn ---- */ -typedef union -{ - cl_int CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2; -#endif -}cl_int2; - -typedef union -{ - cl_int CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[2]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4; -#endif -}cl_int4; - -/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ -typedef cl_int4 cl_int3; - -typedef union -{ - cl_int CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[4]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[2]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8; -#endif -}cl_int8; - -typedef union -{ - cl_int CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[8]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[4]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8[2]; -#endif -#if defined( __CL_INT16__ ) - __cl_int16 v16; -#endif -}cl_int16; - - -/* ---- cl_uintn ---- */ -typedef union -{ - cl_uint CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2; -#endif -}cl_uint2; - -typedef union -{ - cl_uint CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[2]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4; -#endif -}cl_uint4; - -/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ -typedef cl_uint4 cl_uint3; - -typedef union -{ - cl_uint CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[4]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[2]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8; -#endif -}cl_uint8; - -typedef union -{ - cl_uint CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[8]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[4]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8[2]; -#endif -#if defined( __CL_UINT16__ ) - __cl_uint16 v16; -#endif -}cl_uint16; - -/* ---- cl_longn ---- */ -typedef union -{ - cl_long CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2; -#endif -}cl_long2; - -typedef union -{ - cl_long CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[2]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4; -#endif -}cl_long4; - -/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ -typedef cl_long4 cl_long3; - -typedef union -{ - cl_long CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[4]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[2]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8; -#endif -}cl_long8; - -typedef union -{ - cl_long CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[8]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[4]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8[2]; -#endif -#if defined( __CL_LONG16__ ) - __cl_long16 v16; -#endif -}cl_long16; - - -/* ---- cl_ulongn ---- */ -typedef union -{ - cl_ulong CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2; -#endif -}cl_ulong2; - -typedef union -{ - cl_ulong CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[2]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4; -#endif -}cl_ulong4; - -/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ -typedef cl_ulong4 cl_ulong3; - -typedef union -{ - cl_ulong CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[4]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[2]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8; -#endif -}cl_ulong8; - -typedef union -{ - cl_ulong CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[8]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[4]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8[2]; -#endif -#if defined( __CL_ULONG16__ ) - __cl_ulong16 v16; -#endif -}cl_ulong16; - - -/* --- cl_floatn ---- */ - -typedef union -{ - cl_float CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2; -#endif -}cl_float2; - -typedef union -{ - cl_float CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[2]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4; -#endif -}cl_float4; - -/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ -typedef cl_float4 cl_float3; - -typedef union -{ - cl_float CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[4]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[2]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8; -#endif -}cl_float8; - -typedef union -{ - cl_float CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[8]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[4]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8[2]; -#endif -#if defined( __CL_FLOAT16__ ) - __cl_float16 v16; -#endif -}cl_float16; - -/* --- cl_doublen ---- */ - -typedef union -{ - cl_double CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2; -#endif -}cl_double2; - -typedef union -{ - cl_double CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[2]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4; -#endif -}cl_double4; - -/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ -typedef cl_double4 cl_double3; - -typedef union -{ - cl_double CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[4]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[2]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8; -#endif -}cl_double8; - -typedef union -{ - cl_double CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[8]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[4]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8[2]; -#endif -#if defined( __CL_DOUBLE16__ ) - __cl_double16 v16; -#endif -}cl_double16; - -/* Macro to facilitate debugging - * Usage: - * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. - * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" - * Each line thereafter of OpenCL C source must end with: \n\ - * The last line ends in "; - * - * Example: - * - * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ - * kernel void foo( int a, float * b ) \n\ - * { \n\ - * // my comment \n\ - * *b[ get_global_id(0)] = a; \n\ - * } \n\ - * "; - * - * This should correctly set up the line, (column) and file information for your source - * string so you can do source level debugging. - */ -#define __CL_STRINGIFY( _x ) # _x -#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) -#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" - -#ifdef __cplusplus -} -#endif - -#undef __CL_HAS_ANON_STRUCT__ -#undef __CL_ANON_STRUCT__ -#if defined( _WIN32) && (_MSC_VER >= 1500) -#pragma warning( pop ) -#endif - -#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h deleted file mode 100644 index 9855cd75e7d..00000000000 --- a/third_party/opencl/include/CL/opencl.h +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_H -#define __OPENCL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - -#include -#include -#include -#include - -#else - -#include -#include -#include -#include - -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_H */ - diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d4cfc672801..b79d046fcaf 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -57,11 +57,9 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": - base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: - base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 5c2131d4bf8..f428e9972a8 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -58,9 +58,6 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ - opencl-headers \ - ocl-icd-libopencl1 \ - ocl-icd-opencl-dev \ portaudio19-dev \ qttools5-dev-tools \ libqt5svg5-dev \ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 136c4119f64..698ab9885d9 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,11 +6,6 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] -if arch == "Darwin": - base_frameworks.append('OpenCL') -else: - base_libs.append('OpenCL') - replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] if arch != "Darwin": diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 67ad2cc8cbc..6abbc47935e 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,10 +10,6 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements -- Install OpenCL Driver (Ubuntu) -``` -sudo apt install pocl-opencl-icd -``` ## Connect the hardware - Connect the camera first From 46f74753cd2ee9c72a23acfb1f310338149d3c48 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Feb 2026 17:11:17 -0800 Subject: [PATCH 130/685] clip: use wrap_text helper (#37102) * they are same * no duplication! * rm rstrip --- tools/clip/run.py | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 13f591eb41e..855ac6fea37 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -238,30 +238,9 @@ def draw_text_box(text, x, y, size, gui_app, font, color=None, center=False): rl.draw_text_ex(font, text, rl.Vector2(x, y), size, 0, text_color) -def _wrap_text_by_delimiter(text: str, font, font_size: int, max_width: int, delimiter: str = ", ") -> list[str]: - from openpilot.system.ui.lib.text_measure import measure_text_cached - """Wrap text by splitting on delimiter when it exceeds max_width.""" - words = text.split(delimiter) - lines: list[str] = [] - current_line: list[str] = [] - # Build lines word by word - for word in words: - current_line.append(word) - check_line = delimiter.join(current_line) - # Check if line exceeds max width - if measure_text_cached(font, check_line, font_size).x > max_width: - current_line.pop() # Line is too long, move word to next line - if current_line: - lines.append(delimiter.join(current_line)) - current_line = [word] - # Add leftover words as last line - if current_line: - lines.append(delimiter.join(current_line)) - return lines - - def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, show_metadata, show_time): from openpilot.system.ui.lib.text_measure import measure_text_cached + from openpilot.system.ui.lib.wrap_text import wrap_text metadata_size = 16 if big else 12 title_size = 32 if big else 24 time_size = 24 if big else 16 @@ -282,7 +261,7 @@ def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, # Wrap text if too wide (leave margin on each side) margin = 2 * (time_width + 10 if show_time else 20) # leave enough margin for time overlay max_width = gui_app.width - margin - lines = _wrap_text_by_delimiter(text, font, metadata_size, max_width) + lines = wrap_text(font, text, metadata_size, max_width) # Draw wrapped metadata text y_offset = 6 From e531f844f6ada4020850873e808ec7441fbcc2c5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Feb 2026 20:15:12 -0800 Subject: [PATCH 131/685] bump opendbc (#37108) bump --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 39ee861197e..05348982cb7 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 39ee861197edca894fc1b2e28d6fd1a38aea9ca1 +Subproject commit 05348982cb7b6bc6fed73611ac26ecda658194fc From efc23644c74714725ce8022f9dae0bcf7a293617 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Feb 2026 22:59:05 -0800 Subject: [PATCH 132/685] Delete less dialogs (#37111) * try * revert * this is fine --- selfdrive/ui/mici/layouts/settings/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index c12a92482c6..f8dba659d2c 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -326,7 +326,7 @@ def selected_language_callback(): driver_cam_btn.set_click_callback(self._show_driver_camera) driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) - review_training_guide_btn = BigButton("review\nntraining guide", "", "icons_mici/settings/device/info.png") + review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png") review_training_guide_btn.set_click_callback(self._on_review_training_guide) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) @@ -353,7 +353,7 @@ def selected_language_callback(): def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = MiciFccModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/mici_fcc.html")) - gui_app.set_modal_overlay(self._fcc_dialog, callback=setattr(self, '_fcc_dialog', None)) + gui_app.set_modal_overlay(self._fcc_dialog) def _offroad_transition(self): self._power_off_btn.set_visible(ui_state.is_offroad()) From 51312afd3da58ac73cc98d5c908cbebd70498ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sat, 7 Feb 2026 09:10:29 -0800 Subject: [PATCH 133/685] revert tg calib and opencl cleanup (#37113) * Revert "Remove all the OpenCL (#37105)" This reverts commit d5cbb89d84212bfd99d5fdd8b19f76d90b4f40e0. * Revert "rm common/mat.h" This reverts commit 4ce701150a3f932aca210e3ff372ffcdb2a23e83. * Revert "Calibrate in tg (#36621)" This reverts commit 593c3a0c8e69686ee821b2a0b2cfa9901b67dc27. --- Dockerfile.openpilot_base | 37 + SConstruct | 1 + common/SConscript | 1 + common/clutil.cc | 98 ++ common/clutil.h | 28 + common/mat.h | 85 + msgq_repo | 2 +- selfdrive/modeld/SConscript | 49 +- selfdrive/modeld/compile_warp.py | 206 --- selfdrive/modeld/dmonitoringmodeld.py | 33 +- selfdrive/modeld/modeld.py | 59 +- selfdrive/modeld/models/commonmodel.cc | 64 + selfdrive/modeld/models/commonmodel.h | 97 ++ selfdrive/modeld/models/commonmodel.pxd | 27 + selfdrive/modeld/models/commonmodel_pyx.pxd | 13 + selfdrive/modeld/models/commonmodel_pyx.pyx | 74 + selfdrive/modeld/runners/tinygrad_helpers.py | 8 + selfdrive/modeld/transforms/loadyuv.cc | 76 + selfdrive/modeld/transforms/loadyuv.cl | 47 + selfdrive/modeld/transforms/loadyuv.h | 20 + selfdrive/modeld/transforms/transform.cc | 97 ++ selfdrive/modeld/transforms/transform.cl | 54 + selfdrive/modeld/transforms/transform.h | 25 + selfdrive/test/process_replay/model_replay.py | 2 +- .../test/process_replay/process_replay.py | 15 +- system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.cc | 5 +- system/camerad/cameras/camera_common.h | 2 +- system/camerad/cameras/camera_qcom2.cc | 22 +- system/camerad/cameras/spectra.cc | 4 +- system/camerad/cameras/spectra.h | 2 +- system/loggerd/SConscript | 5 +- third_party/opencl/include/CL/cl.h | 1452 +++++++++++++++++ third_party/opencl/include/CL/cl_d3d10.h | 131 ++ third_party/opencl/include/CL/cl_d3d11.h | 131 ++ .../opencl/include/CL/cl_dx9_media_sharing.h | 132 ++ third_party/opencl/include/CL/cl_egl.h | 136 ++ third_party/opencl/include/CL/cl_ext.h | 391 +++++ third_party/opencl/include/CL/cl_ext_qcom.h | 255 +++ third_party/opencl/include/CL/cl_gl.h | 167 ++ third_party/opencl/include/CL/cl_gl_ext.h | 74 + third_party/opencl/include/CL/cl_platform.h | 1333 +++++++++++++++ third_party/opencl/include/CL/opencl.h | 59 + tools/cabana/SConscript | 2 + tools/install_ubuntu_dependencies.sh | 3 + tools/replay/SConscript | 5 + tools/webcam/README.md | 4 + 47 files changed, 5235 insertions(+), 300 deletions(-) create mode 100644 common/clutil.cc create mode 100644 common/clutil.h create mode 100644 common/mat.h delete mode 100755 selfdrive/modeld/compile_warp.py create mode 100644 selfdrive/modeld/models/commonmodel.cc create mode 100644 selfdrive/modeld/models/commonmodel.h create mode 100644 selfdrive/modeld/models/commonmodel.pxd create mode 100644 selfdrive/modeld/models/commonmodel_pyx.pxd create mode 100644 selfdrive/modeld/models/commonmodel_pyx.pyx create mode 100644 selfdrive/modeld/runners/tinygrad_helpers.py create mode 100644 selfdrive/modeld/transforms/loadyuv.cc create mode 100644 selfdrive/modeld/transforms/loadyuv.cl create mode 100644 selfdrive/modeld/transforms/loadyuv.h create mode 100644 selfdrive/modeld/transforms/transform.cc create mode 100644 selfdrive/modeld/transforms/transform.cl create mode 100644 selfdrive/modeld/transforms/transform.h create mode 100644 third_party/opencl/include/CL/cl.h create mode 100644 third_party/opencl/include/CL/cl_d3d10.h create mode 100644 third_party/opencl/include/CL/cl_d3d11.h create mode 100644 third_party/opencl/include/CL/cl_dx9_media_sharing.h create mode 100644 third_party/opencl/include/CL/cl_egl.h create mode 100644 third_party/opencl/include/CL/cl_ext.h create mode 100644 third_party/opencl/include/CL/cl_ext_qcom.h create mode 100644 third_party/opencl/include/CL/cl_gl.h create mode 100644 third_party/opencl/include/CL/cl_gl_ext.h create mode 100644 third_party/opencl/include/CL/cl_platform.h create mode 100644 third_party/opencl/include/CL/opencl.h diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 8a604129938..44d8d95e95d 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -18,6 +18,43 @@ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ cd /usr/lib/gcc/arm-none-eabi/* && \ rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp +# Add OpenCL +RUN apt-get update && apt-get install -y --no-install-recommends \ + apt-utils \ + alien \ + unzip \ + tar \ + curl \ + xz-utils \ + dbus \ + gcc-arm-none-eabi \ + tmux \ + vim \ + libx11-6 \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /tmp/opencl-driver-intel && \ + cd /tmp/opencl-driver-intel && \ + wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ + wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ + mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ + cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ + tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ + mkdir -p /etc/OpenCL/vendors && \ + echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ + cd /opt/intel && \ + tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + mkdir -p /etc/ld.so.conf.d && \ + echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ + ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ + cd / && \ + rm -rf /tmp/opencl-driver-intel + ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute ENV QTWEBENGINE_DISABLE_SANDBOX=1 diff --git a/SConstruct b/SConstruct index 4f04be624cf..ca5b7b6cb72 100644 --- a/SConstruct +++ b/SConstruct @@ -94,6 +94,7 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": + env.Append(CPPPATH=["#third_party/opencl/include"]) env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", diff --git a/common/SConscript b/common/SConscript index 15a0e5eff15..1c68cf05c7a 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,6 +5,7 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', + 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc new file mode 100644 index 00000000000..f8381a7e092 --- /dev/null +++ b/common/clutil.cc @@ -0,0 +1,98 @@ +#include "common/clutil.h" + +#include +#include +#include + +#include "common/util.h" +#include "common/swaglog.h" + +namespace { // helper functions + +template +std::string get_info(Func get_info_func, Id id, Name param_name) { + size_t size = 0; + CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); + std::string info(size, '\0'); + CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); + return info; +} +inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } +inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } + +void cl_print_info(cl_platform_id platform, cl_device_id device) { + size_t work_group_size = 0; + cl_device_type device_type = 0; + clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); + clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); + const char *type_str = "Other..."; + switch (device_type) { + case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; + case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; + case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; + } + + LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); + LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); + LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); + LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); + LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); + LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); + LOGD("max work group size: %zu", work_group_size); + LOGD("type = %d, %s", (int)device_type, type_str); +} + +void cl_print_build_errors(cl_program program, cl_device_id device) { + cl_build_status status; + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); + size_t log_size; + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); + std::string log(log_size, '\0'); + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); + + LOGE("build failed; status=%d, log: %s", status, log.c_str()); +} + +} // namespace + +cl_device_id cl_get_device_id(cl_device_type device_type) { + cl_uint num_platforms = 0; + CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); + std::unique_ptr platform_ids = std::make_unique(num_platforms); + CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); + + for (size_t i = 0; i < num_platforms; ++i) { + LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); + + // Get first device + if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { + cl_print_info(platform_ids[i], device_id); + return device_id; + } + } + LOGE("No valid openCL platform found"); + assert(0); + return nullptr; +} + +cl_context cl_create_context(cl_device_id device_id) { + return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); +} + +void cl_release_context(cl_context context) { + clReleaseContext(context); +} + +cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { + return cl_program_from_source(ctx, device_id, util::read_file(path), args); +} + +cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { + const char *csrc = src.c_str(); + cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); + if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { + cl_print_build_errors(prg, device_id); + assert(0); + } + return prg; +} diff --git a/common/clutil.h b/common/clutil.h new file mode 100644 index 00000000000..b364e79d45b --- /dev/null +++ b/common/clutil.h @@ -0,0 +1,28 @@ +#pragma once + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include + +#define CL_CHECK(_expr) \ + do { \ + assert(CL_SUCCESS == (_expr)); \ + } while (0) + +#define CL_CHECK_ERR(_expr) \ + ({ \ + cl_int err = CL_INVALID_VALUE; \ + __typeof__(_expr) _ret = _expr; \ + assert(_ret&& err == CL_SUCCESS); \ + _ret; \ + }) + +cl_device_id cl_get_device_id(cl_device_type device_type); +cl_context cl_create_context(cl_device_id device_id); +void cl_release_context(cl_context context); +cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); +cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/common/mat.h b/common/mat.h new file mode 100644 index 00000000000..8e10d619717 --- /dev/null +++ b/common/mat.h @@ -0,0 +1,85 @@ +#pragma once + +typedef struct vec3 { + float v[3]; +} vec3; + +typedef struct vec4 { + float v[4]; +} vec4; + +typedef struct mat3 { + float v[3*3]; +} mat3; + +typedef struct mat4 { + float v[4*4]; +} mat4; + +static inline mat3 matmul3(const mat3 &a, const mat3 &b) { + mat3 ret = {{0.0}}; + for (int r=0; r<3; r++) { + for (int c=0; c<3; c++) { + float v = 0.0; + for (int k=0; k<3; k++) { + v += a.v[r*3+k] * b.v[k*3+c]; + } + ret.v[r*3+c] = v; + } + } + return ret; +} + +static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { + vec3 ret = {{0.0}}; + for (int r=0; r<3; r++) { + for (int c=0; c<3; c++) { + ret.v[r] += a.v[r*3+c] * b.v[c]; + } + } + return ret; +} + +static inline mat4 matmul(const mat4 &a, const mat4 &b) { + mat4 ret = {{0.0}}; + for (int r=0; r<4; r++) { + for (int c=0; c<4; c++) { + float v = 0.0; + for (int k=0; k<4; k++) { + v += a.v[r*4+k] * b.v[k*4+c]; + } + ret.v[r*4+c] = v; + } + } + return ret; +} + +static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { + vec4 ret = {{0.0}}; + for (int r=0; r<4; r++) { + for (int c=0; c<4; c++) { + ret.v[r] += a.v[r*4+c] * b.v[c]; + } + } + return ret; +} + +// scales the input and output space of a transformation matrix +// that assumes pixel-center origin. +static inline mat3 transform_scale_buffer(const mat3 &in, float s) { + // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s + + mat3 transform_out = {{ + 1.0f/s, 0.0f, 0.5f, + 0.0f, 1.0f/s, 0.5f, + 0.0f, 0.0f, 1.0f, + }}; + + mat3 transform_in = {{ + s, 0.0f, -0.5f*s, + 0.0f, s, -0.5f*s, + 0.0f, 0.0f, 1.0f, + }}; + + return matmul3(transform_in, matmul3(in, transform_out)); +} diff --git a/msgq_repo b/msgq_repo index f9ebdca885c..20f2493855e 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit f9ebdca885cfe25295d09de9fd57023a10758de5 +Subproject commit 20f2493855ef32339b80f0ad76b3cb82210dc474 diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 72d3f95c41f..91f3597447b 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,10 +1,35 @@ import os import glob -Import('env', 'arch') +Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') lenv = env.Clone() +lenvCython = envCython.Clone() -tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x] +libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] +frameworks = [] + +common_src = [ + "models/commonmodel.cc", + "transforms/loadyuv.cc", + "transforms/transform.cc", +] + +# OpenCL is a framework on Mac +if arch == "Darwin": + frameworks += ['OpenCL'] +else: + libs += ['OpenCL'] + +# Set path definitions +for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): + for xenv in (lenv, lenvCython): + xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') + +# Compile cython +cython_libs = envCython["LIBS"] + libs +commonmodel_lib = lenv.Library('commonmodel', common_src) +lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) +tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: @@ -13,30 +38,22 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) -# compile warp -tg_flags = { - 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env -}.get(arch, 'DEV=CPU CPU_LLVM=1') -image_flag = { - 'larch64': 'IMAGE=2', -}.get(arch, 'IMAGE=0') -script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' -lenv.Command(fn + "warp_tinygrad.pkl", tinygrad_files + script_files, cmd) - def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath return lenv.Command( fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' ) # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: - tg_compile(tg_flags, model_name) + flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env + }.get(arch, 'DEV=CPU CPU_LLVM=1') + tg_compile(flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py deleted file mode 100755 index 5adb60e6240..00000000000 --- a/selfdrive/modeld/compile_warp.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python3 -import time -import pickle -import numpy as np -from pathlib import Path -from tinygrad.tensor import Tensor -from tinygrad.helpers import Context -from tinygrad.device import Device -from tinygrad.engine.jit import TinyJit - -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info -from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE -from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye - -MODELS_DIR = Path(__file__).parent / 'models' - -CAMERA_CONFIGS = [ - (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 - (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 -] - -UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) -UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) - -IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) - - -def warp_pkl_path(w, h): - return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - - -def dm_warp_pkl_path(w, h): - return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' - - -def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, ratio): - w_dst, h_dst = dst_shape - h_src, w_src = src_shape - - x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst) - y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst) - ones = Tensor.ones_like(x) - dst_coords = x.reshape(1, -1).cat(y.reshape(1, -1)).cat(ones.reshape(1, -1)) - - src_coords = M_inv @ dst_coords - src_coords = src_coords / src_coords[2:3, :] - - x_nn_clipped = Tensor.round(src_coords[0]).clip(0, w_src - 1).cast('int') - y_nn_clipped = Tensor.round(src_coords[1]).clip(0, h_src - 1).cast('int') - idx = y_nn_clipped * w_src + (y_nn_clipped * ratio).cast('int') * stride_pad + x_nn_clipped - - sampled = src_flat[idx] - return sampled - - -def frames_to_tensor(frames, model_w, model_h): - H = (frames.shape[0] * 2) // 3 - W = frames.shape[1] - in_img1 = Tensor.cat(frames[0:H:2, 0::2], - frames[1:H:2, 0::2], - frames[0:H:2, 1::2], - frames[1:H:2, 1::2], - frames[H:H+H//4].reshape((H//2, W//2)), - frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) - return in_img1 - - -def make_frame_prepare(cam_w, cam_h, model_w, model_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - uv_offset = stride * y_height - stride_pad = stride - cam_w - - def frame_prepare_tinygrad(input_frame, M_inv): - tg_scale = Tensor(UV_SCALE_MATRIX) - M_inv_uv = tg_scale @ M_inv @ Tensor(UV_SCALE_MATRIX_INV) - with Context(SPLIT_REDUCEOP=0): - y = warp_perspective_tinygrad(input_frame[:cam_h*stride], - M_inv, (model_w, model_h), - (cam_h, cam_w), stride_pad, 1).realize() - u = warp_perspective_tinygrad(input_frame[uv_offset:uv_offset + (cam_h//4)*stride], - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), stride_pad, 0.5).realize() - v = warp_perspective_tinygrad(input_frame[uv_offset + (cam_h//4)*stride:uv_offset + (cam_h//2)*stride], - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), stride_pad, 0.5).realize() - yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) - tensor = frames_to_tensor(yuv, model_w, model_h) - return tensor - return frame_prepare_tinygrad - - -def make_update_img_input(frame_prepare, model_w, model_h): - def update_img_input_tinygrad(tensor, frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - new_img = frame_prepare(frame, M_inv) - full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() - return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) - return update_img_input_tinygrad - - -def make_update_both_imgs(frame_prepare, model_w, model_h): - update_img = make_update_img_input(frame_prepare, model_w, model_h) - - def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, - calib_big_img_buffer, new_big_img, M_inv_big): - calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) - calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) - return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair - return update_both_imgs_tinygrad - - -def make_warp_dm(cam_w, cam_h, dm_w, dm_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - stride_pad = stride - cam_w - - def warp_dm(input_frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - with Context(SPLIT_REDUCEOP=0): - result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad, 1).reshape(-1, dm_h * dm_w) - return result - return warp_dm - - -def compile_modeld_warp(cam_w, cam_h): - model_w, model_h = MEDMODEL_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling modeld warp for {cam_w}x{cam_h}...") - - frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) - update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) - update_img_jit = TinyJit(update_both_imgs, prune=True) - - full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - - for i in range(10): - new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) - img_inputs = [full_buffer, - Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) - big_img_inputs = [big_full_buffer, - Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - inputs = img_inputs + big_img_inputs - Device.default.synchronize() - - inputs_np = [x.numpy() for x in inputs] - inputs_np[0] = full_buffer_np - inputs_np[3] = big_full_buffer_np - - st = time.perf_counter() - out = update_img_jit(*inputs) - full_buffer = out[0].contiguous().realize().clone() - big_full_buffer = out[2].contiguous().realize().clone() - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(update_img_jit, f) - print(f" Saved to {pkl_path}") - - jit = pickle.load(open(pkl_path, "rb")) - jit(*inputs) - - -def compile_dm_warp(cam_w, cam_h): - dm_w, dm_h = DM_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling DM warp for {cam_w}x{cam_h}...") - - warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) - warp_dm_jit = TinyJit(warp_dm, prune=True) - - for i in range(10): - inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - Device.default.synchronize() - st = time.perf_counter() - warp_dm_jit(*inputs) - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = dm_warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(warp_dm_jit, f) - print(f" Saved to {pkl_path}") - - -def run_and_save_pickle(): - for cam_w, cam_h in CAMERA_CONFIGS: - compile_modeld_warp(cam_w, cam_h) - compile_dm_warp(cam_w, cam_h) - - -if __name__ == "__main__": - run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 7d4df713c7f..fca762c69bf 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,6 +3,7 @@ from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor +from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -15,34 +16,32 @@ from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp +from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' -MODELS_DIR = Path(__file__).parent / 'models' + class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self): + def __init__(self, cl_ctx): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] + self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } - self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} - self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} - self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} - self.image_warp = None with open(MODEL_PKL_PATH, "rb") as f: self.model_run = pickle.load(f) @@ -51,15 +50,14 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple t1 = time.perf_counter() - if self.image_warp is None: - self.frame_buf_params = get_nv12_info(buf.width, buf.height) - warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.image_warp = pickle.load(f) - self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() + input_img_cl = self.frame.prepare(buf, transform.flatten()) + if TICI: + # The imgs tensors are backed by opencl memory, only need init once + if 'input_img' not in self.tensor_inputs: + self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) + else: + self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() - self.warp_inputs_np['transform'][:] = transform[:] - self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy() @@ -109,11 +107,12 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - model = ModelState() + cl_context = CLContext() + model = ModelState(cl_context) cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 7fae3651095..1f347dc32a0 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,6 +7,7 @@ os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor +from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -21,13 +22,14 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.selfdrive.modeld.constants import ModelConstants, Plan +from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext +from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.modeld" @@ -37,15 +39,11 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' -MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 -IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) -assert IMG_QUEUE_SHAPE[0] == 30 - def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -138,11 +136,12 @@ def get(self, *names) -> dict[str, np.ndarray]: return out class ModelState: + frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self): + def __init__(self, context: CLContext): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -156,6 +155,7 @@ def __init__(self): self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] + self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -165,17 +165,12 @@ def __init__(self): self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), - 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} - self.full_frames : dict[str, Tensor] = {} - self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} - self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} + # img buffers are managed in openCL transform code + self.vision_inputs: dict[str, Tensor] = {} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() - self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} - self.update_imgs = None with open(VISION_PKL_PATH, "rb") as f: self.vision_run = pickle.load(f) @@ -193,28 +188,23 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] - if self.update_imgs is None: - for key in bufs.keys(): - w, h = bufs[key].width, bufs[key].height - self.frame_buf_params[key] = get_nv12_info(w, h) - warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.update_imgs = pickle.load(f) - - for key in bufs.keys(): - self.full_frames[key] = Tensor.from_blob(bufs[key].data.ctypes.data, (self.frame_buf_params[key][3],), dtype='uint8').realize() - self.transforms_np[key][:,:] = transforms[key][:,:] + imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} - out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], - self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) - self.img_queues['img'], self.img_queues['big_img'], = out[0].realize(), out[2].realize() - vision_inputs = {'img': out[1], 'big_img': out[3]} + if TICI and not USBGPU: + # The imgs tensors are backed by opencl memory, only need init once + for key in imgs_cl: + if key not in self.vision_inputs: + self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) + else: + for key in imgs_cl: + frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) + self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() if prepare_only: return None - self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -224,6 +214,7 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) + combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) @@ -240,8 +231,10 @@ def main(demo=False): config_realtime_process(7, 54) st = time.monotonic() - cloudlog.warning("loading model") - model = ModelState() + cloudlog.warning("setting up CL context") + cl_context = CLContext() + cloudlog.warning("CL context ready; loading model") + model = ModelState(cl_context) cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -254,8 +247,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc new file mode 100644 index 00000000000..d3341e76ec3 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.cc @@ -0,0 +1,64 @@ +#include "selfdrive/modeld/models/commonmodel.h" + +#include +#include + +#include "common/clutil.h" + +DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { + input_frames = std::make_unique(buf_size); + temporal_skip = _temporal_skip; + input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); + img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); + region.origin = temporal_skip * frame_size_bytes; + region.size = frame_size_bytes; + last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); + + loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); + init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); +} + +cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); + + for (int i = 0; i < temporal_skip; i++) { + CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); + } + loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); + + copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); + copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); + + // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. + clFinish(q); + return &input_frames_cl; +} + +DrivingModelFrame::~DrivingModelFrame() { + deinit_transform(); + loadyuv_destroy(&loadyuv); + CL_CHECK(clReleaseMemObject(input_frames_cl)); + CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); + CL_CHECK(clReleaseMemObject(last_img_cl)); + CL_CHECK(clReleaseCommandQueue(q)); +} + + +MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { + input_frames = std::make_unique(buf_size); + input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); + + init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); +} + +cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); + clFinish(q); + return &y_cl; +} + +MonitoringModelFrame::~MonitoringModelFrame() { + deinit_transform(); + CL_CHECK(clReleaseMemObject(input_frame_cl)); + CL_CHECK(clReleaseCommandQueue(q)); +} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h new file mode 100644 index 00000000000..176d7eb6dcf --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include + +#include + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "common/mat.h" +#include "selfdrive/modeld/transforms/loadyuv.h" +#include "selfdrive/modeld/transforms/transform.h" + +class ModelFrame { +public: + ModelFrame(cl_device_id device_id, cl_context context) { + q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); + } + virtual ~ModelFrame() {} + virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } + uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { + CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); + clFinish(q); + return &input_frames[0]; + } + + int MODEL_WIDTH; + int MODEL_HEIGHT; + int MODEL_FRAME_SIZE; + int buf_size; + +protected: + cl_mem y_cl, u_cl, v_cl; + Transform transform; + cl_command_queue q; + std::unique_ptr input_frames; + + void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { + y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); + u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); + v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); + transform_init(&transform, context, device_id); + } + + void deinit_transform() { + transform_destroy(&transform); + CL_CHECK(clReleaseMemObject(v_cl)); + CL_CHECK(clReleaseMemObject(u_cl)); + CL_CHECK(clReleaseMemObject(y_cl)); + } + + void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + transform_queue(&transform, q, + yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, + y_cl, u_cl, v_cl, model_width, model_height, projection); + } +}; + +class DrivingModelFrame : public ModelFrame { +public: + DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); + ~DrivingModelFrame(); + cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); + + const int MODEL_WIDTH = 512; + const int MODEL_HEIGHT = 256; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; + const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart + const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); + +private: + LoadYUVState loadyuv; + cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; + cl_buffer_region region; + int temporal_skip; +}; + +class MonitoringModelFrame : public ModelFrame { +public: + MonitoringModelFrame(cl_device_id device_id, cl_context context); + ~MonitoringModelFrame(); + cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); + + const int MODEL_WIDTH = 1440; + const int MODEL_HEIGHT = 960; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; + const int buf_size = MODEL_FRAME_SIZE; + +private: + cl_mem input_frame_cl; +}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd new file mode 100644 index 00000000000..4ac64d91720 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.pxd @@ -0,0 +1,27 @@ +# distutils: language = c++ + +from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem + +cdef extern from "common/mat.h": + cdef struct mat3: + float v[9] + +cdef extern from "common/clutil.h": + cdef unsigned long CL_DEVICE_TYPE_DEFAULT + cl_device_id cl_get_device_id(unsigned long) + cl_context cl_create_context(cl_device_id) + void cl_release_context(cl_context) + +cdef extern from "selfdrive/modeld/models/commonmodel.h": + cppclass ModelFrame: + int buf_size + unsigned char * buffer_from_cl(cl_mem*, int); + cl_mem * prepare(cl_mem, int, int, int, int, mat3) + + cppclass DrivingModelFrame: + int buf_size + DrivingModelFrame(cl_device_id, cl_context, int) + + cppclass MonitoringModelFrame: + int buf_size + MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd new file mode 100644 index 00000000000..0bb798625be --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pxd @@ -0,0 +1,13 @@ +# distutils: language = c++ + +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext + +cdef class CLContext(BaseCLContext): + pass + +cdef class CLMem: + cdef cl_mem * mem + + @staticmethod + cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx new file mode 100644 index 00000000000..5b7d11bc71a --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pyx @@ -0,0 +1,74 @@ +# distutils: language = c++ +# cython: c_string_encoding=ascii, language_level=3 + +import numpy as np +cimport numpy as cnp +from libc.string cimport memcpy +from libc.stdint cimport uintptr_t + +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext +from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context +from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame + + +cdef class CLContext(BaseCLContext): + def __cinit__(self): + self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) + self.context = cl_create_context(self.device_id) + + def __dealloc__(self): + if self.context: + cl_release_context(self.context) + +cdef class CLMem: + @staticmethod + cdef create(void * cmem): + mem = CLMem() + mem.mem = cmem + return mem + + @property + def mem_address(self): + return (self.mem) + +def cl_from_visionbuf(VisionBuf buf): + return CLMem.create(&buf.buf.buf_cl) + + +cdef class ModelFrame: + cdef cppModelFrame * frame + cdef int buf_size + + def __dealloc__(self): + del self.frame + + def prepare(self, VisionBuf buf, float[:] projection): + cdef mat3 cprojection + memcpy(cprojection.v, &projection[0], 9*sizeof(float)) + cdef cl_mem * data + data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) + return CLMem.create(data) + + def buffer_from_cl(self, CLMem in_frames): + cdef unsigned char * data2 + data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) + return np.asarray( data2) + + +cdef class DrivingModelFrame(ModelFrame): + cdef cppDrivingModelFrame * _frame + + def __cinit__(self, CLContext context, int temporal_skip): + self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) + self.frame = (self._frame) + self.buf_size = self._frame.buf_size + +cdef class MonitoringModelFrame(ModelFrame): + cdef cppMonitoringModelFrame * _frame + + def __cinit__(self, CLContext context): + self._frame = new cppMonitoringModelFrame(context.device_id, context.context) + self.frame = (self._frame) + self.buf_size = self._frame.buf_size + diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py new file mode 100644 index 00000000000..776381341cf --- /dev/null +++ b/selfdrive/modeld/runners/tinygrad_helpers.py @@ -0,0 +1,8 @@ + +from tinygrad.tensor import Tensor +from tinygrad.helpers import to_mv + +def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): + cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] + rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. + return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc new file mode 100644 index 00000000000..c93f5cd0381 --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.cc @@ -0,0 +1,76 @@ +#include "selfdrive/modeld/transforms/loadyuv.h" + +#include +#include +#include + +void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { + memset(s, 0, sizeof(*s)); + + s->width = width; + s->height = height; + + char args[1024]; + snprintf(args, sizeof(args), + "-cl-fast-relaxed-math -cl-denorms-are-zero " + "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", + width, height); + cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); + + s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); + s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); + s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); + + // done with this + CL_CHECK(clReleaseProgram(prg)); +} + +void loadyuv_destroy(LoadYUVState* s) { + CL_CHECK(clReleaseKernel(s->loadys_krnl)); + CL_CHECK(clReleaseKernel(s->loaduv_krnl)); + CL_CHECK(clReleaseKernel(s->copy_krnl)); +} + +void loadyuv_queue(LoadYUVState* s, cl_command_queue q, + cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, + cl_mem out_cl) { + cl_int global_out_off = 0; + + CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); + CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); + + const size_t loadys_work_size = (s->width*s->height)/8; + CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, + &loadys_work_size, NULL, 0, 0, NULL)); + + const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; + global_out_off += (s->width*s->height); + + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); + + CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, + &loaduv_work_size, NULL, 0, 0, NULL)); + + global_out_off += (s->width/2)*(s->height/2); + + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); + + CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, + &loaduv_work_size, NULL, 0, 0, NULL)); +} + +void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, + size_t src_offset, size_t dst_offset, size_t size) { + CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); + const size_t copy_work_size = size/8; + CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, + ©_work_size, NULL, 0, 0, NULL)); +} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl new file mode 100644 index 00000000000..970187a6d70 --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.cl @@ -0,0 +1,47 @@ +#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) + +__kernel void loadys(__global uchar8 const * const Y, + __global uchar * out, + int out_offset) +{ + const int gid = get_global_id(0); + const int ois = gid * 8; + const int oy = ois / TRANSFORMED_WIDTH; + const int ox = ois % TRANSFORMED_WIDTH; + + const uchar8 ys = Y[gid]; + + // 02 + // 13 + + __global uchar* outy0; + __global uchar* outy1; + if ((oy & 1) == 0) { + outy0 = out + out_offset; //y0 + outy1 = out + out_offset + UV_SIZE*2; //y2 + } else { + outy0 = out + out_offset + UV_SIZE; //y1 + outy1 = out + out_offset + UV_SIZE*3; //y3 + } + + vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); + vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); +} + +__kernel void loaduv(__global uchar8 const * const in, + __global uchar8 * out, + int out_offset) +{ + const int gid = get_global_id(0); + const uchar8 inv = in[gid]; + out[gid + out_offset / 8] = inv; +} + +__kernel void copy(__global uchar8 * in, + __global uchar8 * out, + int in_offset, + int out_offset) +{ + const int gid = get_global_id(0); + out[gid + out_offset / 8] = in[gid + in_offset / 8]; +} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h new file mode 100644 index 00000000000..659059cd25e --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.h @@ -0,0 +1,20 @@ +#pragma once + +#include "common/clutil.h" + +typedef struct { + int width, height; + cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; +} LoadYUVState; + +void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); + +void loadyuv_destroy(LoadYUVState* s); + +void loadyuv_queue(LoadYUVState* s, cl_command_queue q, + cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, + cl_mem out_cl); + + +void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, + size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc new file mode 100644 index 00000000000..305643cf42e --- /dev/null +++ b/selfdrive/modeld/transforms/transform.cc @@ -0,0 +1,97 @@ +#include "selfdrive/modeld/transforms/transform.h" + +#include +#include + +#include "common/clutil.h" + +void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { + memset(s, 0, sizeof(*s)); + + cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); + s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); + // done with this + CL_CHECK(clReleaseProgram(prg)); + + s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); + s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); +} + +void transform_destroy(Transform* s) { + CL_CHECK(clReleaseMemObject(s->m_y_cl)); + CL_CHECK(clReleaseMemObject(s->m_uv_cl)); + CL_CHECK(clReleaseKernel(s->krnl)); +} + +void transform_queue(Transform* s, + cl_command_queue q, + cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, + cl_mem out_y, cl_mem out_u, cl_mem out_v, + int out_width, int out_height, + const mat3& projection) { + const int zero = 0; + + // sampled using pixel center origin + // (because that's how fastcv and opencv does it) + + mat3 projection_y = projection; + + // in and out uv is half the size of y. + mat3 projection_uv = transform_scale_buffer(projection, 0.5); + + CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); + CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); + + const int in_y_width = in_width; + const int in_y_height = in_height; + const int in_y_px_stride = 1; + const int in_uv_width = in_width/2; + const int in_uv_height = in_height/2; + const int in_uv_px_stride = 2; + const int in_u_offset = in_uv_offset; + const int in_v_offset = in_uv_offset + 1; + + const int out_y_width = out_width; + const int out_y_height = out_height; + const int out_uv_width = out_width/2; + const int out_uv_height = out_height/2; + + CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src + CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset + CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows + CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst + CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset + CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows + CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols + CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M + + const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_y, NULL, 0, 0, NULL)); + + const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; + + CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset + CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows + CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst + CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset + CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows + CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols + CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); +} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl new file mode 100644 index 00000000000..2ca25920cd1 --- /dev/null +++ b/selfdrive/modeld/transforms/transform.cl @@ -0,0 +1,54 @@ +#define INTER_BITS 5 +#define INTER_TAB_SIZE (1 << INTER_BITS) +#define INTER_SCALE 1.f / INTER_TAB_SIZE + +#define INTER_REMAP_COEF_BITS 15 +#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) + +__kernel void warpPerspective(__global const uchar * src, + int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, + __global uchar * dst, + int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, + __constant float * M) +{ + int dx = get_global_id(0); + int dy = get_global_id(1); + + if (dx < dst_cols && dy < dst_rows) + { + float X0 = M[0] * dx + M[1] * dy + M[2]; + float Y0 = M[3] * dx + M[4] * dy + M[5]; + float W = M[6] * dx + M[7] * dy + M[8]; + W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; + int X = rint(X0 * W), Y = rint(Y0 * W); + + int sx = convert_short_sat(X >> INTER_BITS); + int sy = convert_short_sat(Y >> INTER_BITS); + + short sx_clamp = clamp(sx, 0, src_cols - 1); + short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); + short sy_clamp = clamp(sy, 0, src_rows - 1); + short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); + int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + + short ay = (short)(Y & (INTER_TAB_SIZE - 1)); + short ax = (short)(X & (INTER_TAB_SIZE - 1)); + float taby = 1.f/INTER_TAB_SIZE*ay; + float tabx = 1.f/INTER_TAB_SIZE*ax; + + int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); + + int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); + int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); + int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); + int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); + + int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; + + uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); + dst[dst_index] = pix; + } +} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h new file mode 100644 index 00000000000..771a7054b35 --- /dev/null +++ b/selfdrive/modeld/transforms/transform.h @@ -0,0 +1,25 @@ +#pragma once + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "common/mat.h" + +typedef struct { + cl_kernel krnl; + cl_mem m_y_cl, m_uv_cl; +} Transform; + +void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); + +void transform_destroy(Transform* transform); + +void transform_queue(Transform* s, cl_command_queue q, + cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, + cl_mem out_y, cl_mem out_u, cl_mem out_v, + int out_width, int out_height, + const mat3& projection); diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index c2423284007..9ba599bac9c 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,7 +34,7 @@ EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.035, 0.028), + ("modelV2", 0.035, 0.025), ("driverStateV2", 0.02, 0.015), ] diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 5143334bc61..8af72e5f4e7 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,7 +4,6 @@ import copy import heapq import signal -import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -24,7 +23,6 @@ from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -205,8 +203,7 @@ def _setup_vision_ipc(self, all_msgs: LogIterable, frs: dict[str, Any]): if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) - vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) + vipc_server.create_buffers(meta.stream, 2, *frame_size) vipc_server.start_listener() self.vipc_server = vipc_server @@ -303,15 +300,7 @@ def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - - h, w = frs[m.which()].h, frs[m.which()].w - stride, y_height, _, yuv_size = get_nv12_info(w, h) - uv_offset = stride * y_height - padded_img = np.zeros((yuv_size), dtype=np.uint8).reshape((-1, stride)) - padded_img[:h, :w] = img[:h * w].reshape((-1, w)) - padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) - - self.vipc_server.send(camera_meta.stream, padded_img.flatten().tobytes(), + self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] diff --git a/system/camerad/SConscript b/system/camerad/SConscript index c28330b32c4..e288c6d8b02 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, messaging, visionipc] +libs = [common, 'OpenCL', messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 329192b63ae..88bca7f775b 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,8 +21,9 @@ void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, Vis const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); + camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d buffers", frame_buf_count); + LOGD("allocated %d CL buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 7f35e06a835..c26859cbc40 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ class CameraBuf { CameraBuf() = default; ~CameraBuf(); - void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 6a7f599ab66..d741e13cf3b 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,8 +12,16 @@ #include #include +#ifdef __TICI__ +#include "CL/cl_ext_qcom.h" +#else +#define CL_PRIORITY_HINT_HIGH_QCOM NULL +#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL +#endif + #include "media/cam_sensor_cmn_header.h" +#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -49,7 +57,7 @@ class CameraState { CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v); + void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -60,8 +68,8 @@ class CameraState { } }; -void CameraState::init(VisionIpcServer *v) { - camera.camera_open(v); +void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { + camera.camera_open(v, device_id, ctx); if (!camera.enabled) return; @@ -249,7 +257,11 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - VisionIpcServer v("camerad"); + cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); + const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; + cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); + + VisionIpcServer v("camerad", device_id, ctx); // *** initial ISP init *** SpectraMaster m; @@ -259,7 +271,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v); + cam->init(&v, device_id, ctx); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 73e0a78da30..5c3e7a9d233 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v) { +void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v) { linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(this, v, ife_buf_depth, cc.stream_type); + buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index a02b8a6cac7..13cb13f98f6 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ class SpectraCamera { SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v); + void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cc8ef7c88f6..cf169f4dc61 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,13 +2,16 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'avutil', - 'yuv', 'pthread', 'zstd'] + 'yuv', 'OpenCL', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] if arch == "Darwin": + # fix OpenCL + del libs[libs.index('OpenCL')] + env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h new file mode 100644 index 00000000000..0086319f5fa --- /dev/null +++ b/third_party/opencl/include/CL/cl.h @@ -0,0 +1,1452 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +#ifndef __OPENCL_CL_H +#define __OPENCL_CL_H + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ + +typedef struct _cl_platform_id * cl_platform_id; +typedef struct _cl_device_id * cl_device_id; +typedef struct _cl_context * cl_context; +typedef struct _cl_command_queue * cl_command_queue; +typedef struct _cl_mem * cl_mem; +typedef struct _cl_program * cl_program; +typedef struct _cl_kernel * cl_kernel; +typedef struct _cl_event * cl_event; +typedef struct _cl_sampler * cl_sampler; + +typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ +typedef cl_ulong cl_bitfield; +typedef cl_bitfield cl_device_type; +typedef cl_uint cl_platform_info; +typedef cl_uint cl_device_info; +typedef cl_bitfield cl_device_fp_config; +typedef cl_uint cl_device_mem_cache_type; +typedef cl_uint cl_device_local_mem_type; +typedef cl_bitfield cl_device_exec_capabilities; +typedef cl_bitfield cl_device_svm_capabilities; +typedef cl_bitfield cl_command_queue_properties; +typedef intptr_t cl_device_partition_property; +typedef cl_bitfield cl_device_affinity_domain; + +typedef intptr_t cl_context_properties; +typedef cl_uint cl_context_info; +typedef cl_bitfield cl_queue_properties; +typedef cl_uint cl_command_queue_info; +typedef cl_uint cl_channel_order; +typedef cl_uint cl_channel_type; +typedef cl_bitfield cl_mem_flags; +typedef cl_bitfield cl_svm_mem_flags; +typedef cl_uint cl_mem_object_type; +typedef cl_uint cl_mem_info; +typedef cl_bitfield cl_mem_migration_flags; +typedef cl_uint cl_image_info; +typedef cl_uint cl_buffer_create_type; +typedef cl_uint cl_addressing_mode; +typedef cl_uint cl_filter_mode; +typedef cl_uint cl_sampler_info; +typedef cl_bitfield cl_map_flags; +typedef intptr_t cl_pipe_properties; +typedef cl_uint cl_pipe_info; +typedef cl_uint cl_program_info; +typedef cl_uint cl_program_build_info; +typedef cl_uint cl_program_binary_type; +typedef cl_int cl_build_status; +typedef cl_uint cl_kernel_info; +typedef cl_uint cl_kernel_arg_info; +typedef cl_uint cl_kernel_arg_address_qualifier; +typedef cl_uint cl_kernel_arg_access_qualifier; +typedef cl_bitfield cl_kernel_arg_type_qualifier; +typedef cl_uint cl_kernel_work_group_info; +typedef cl_uint cl_kernel_sub_group_info; +typedef cl_uint cl_event_info; +typedef cl_uint cl_command_type; +typedef cl_uint cl_profiling_info; +typedef cl_bitfield cl_sampler_properties; +typedef cl_uint cl_kernel_exec_info; + +typedef struct _cl_image_format { + cl_channel_order image_channel_order; + cl_channel_type image_channel_data_type; +} cl_image_format; + +typedef struct _cl_image_desc { + cl_mem_object_type image_type; + size_t image_width; + size_t image_height; + size_t image_depth; + size_t image_array_size; + size_t image_row_pitch; + size_t image_slice_pitch; + cl_uint num_mip_levels; + cl_uint num_samples; +#ifdef __GNUC__ + __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ +#endif + union { + cl_mem buffer; + cl_mem mem_object; + }; +} cl_image_desc; + +typedef struct _cl_buffer_region { + size_t origin; + size_t size; +} cl_buffer_region; + + +/******************************************************************************/ + +/* Error Codes */ +#define CL_SUCCESS 0 +#define CL_DEVICE_NOT_FOUND -1 +#define CL_DEVICE_NOT_AVAILABLE -2 +#define CL_COMPILER_NOT_AVAILABLE -3 +#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 +#define CL_OUT_OF_RESOURCES -5 +#define CL_OUT_OF_HOST_MEMORY -6 +#define CL_PROFILING_INFO_NOT_AVAILABLE -7 +#define CL_MEM_COPY_OVERLAP -8 +#define CL_IMAGE_FORMAT_MISMATCH -9 +#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 +#define CL_BUILD_PROGRAM_FAILURE -11 +#define CL_MAP_FAILURE -12 +#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 +#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 +#define CL_COMPILE_PROGRAM_FAILURE -15 +#define CL_LINKER_NOT_AVAILABLE -16 +#define CL_LINK_PROGRAM_FAILURE -17 +#define CL_DEVICE_PARTITION_FAILED -18 +#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 + +#define CL_INVALID_VALUE -30 +#define CL_INVALID_DEVICE_TYPE -31 +#define CL_INVALID_PLATFORM -32 +#define CL_INVALID_DEVICE -33 +#define CL_INVALID_CONTEXT -34 +#define CL_INVALID_QUEUE_PROPERTIES -35 +#define CL_INVALID_COMMAND_QUEUE -36 +#define CL_INVALID_HOST_PTR -37 +#define CL_INVALID_MEM_OBJECT -38 +#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 +#define CL_INVALID_IMAGE_SIZE -40 +#define CL_INVALID_SAMPLER -41 +#define CL_INVALID_BINARY -42 +#define CL_INVALID_BUILD_OPTIONS -43 +#define CL_INVALID_PROGRAM -44 +#define CL_INVALID_PROGRAM_EXECUTABLE -45 +#define CL_INVALID_KERNEL_NAME -46 +#define CL_INVALID_KERNEL_DEFINITION -47 +#define CL_INVALID_KERNEL -48 +#define CL_INVALID_ARG_INDEX -49 +#define CL_INVALID_ARG_VALUE -50 +#define CL_INVALID_ARG_SIZE -51 +#define CL_INVALID_KERNEL_ARGS -52 +#define CL_INVALID_WORK_DIMENSION -53 +#define CL_INVALID_WORK_GROUP_SIZE -54 +#define CL_INVALID_WORK_ITEM_SIZE -55 +#define CL_INVALID_GLOBAL_OFFSET -56 +#define CL_INVALID_EVENT_WAIT_LIST -57 +#define CL_INVALID_EVENT -58 +#define CL_INVALID_OPERATION -59 +#define CL_INVALID_GL_OBJECT -60 +#define CL_INVALID_BUFFER_SIZE -61 +#define CL_INVALID_MIP_LEVEL -62 +#define CL_INVALID_GLOBAL_WORK_SIZE -63 +#define CL_INVALID_PROPERTY -64 +#define CL_INVALID_IMAGE_DESCRIPTOR -65 +#define CL_INVALID_COMPILER_OPTIONS -66 +#define CL_INVALID_LINKER_OPTIONS -67 +#define CL_INVALID_DEVICE_PARTITION_COUNT -68 +#define CL_INVALID_PIPE_SIZE -69 +#define CL_INVALID_DEVICE_QUEUE -70 + +/* OpenCL Version */ +#define CL_VERSION_1_0 1 +#define CL_VERSION_1_1 1 +#define CL_VERSION_1_2 1 +#define CL_VERSION_2_0 1 +#define CL_VERSION_2_1 1 + +/* cl_bool */ +#define CL_FALSE 0 +#define CL_TRUE 1 +#define CL_BLOCKING CL_TRUE +#define CL_NON_BLOCKING CL_FALSE + +/* cl_platform_info */ +#define CL_PLATFORM_PROFILE 0x0900 +#define CL_PLATFORM_VERSION 0x0901 +#define CL_PLATFORM_NAME 0x0902 +#define CL_PLATFORM_VENDOR 0x0903 +#define CL_PLATFORM_EXTENSIONS 0x0904 +#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 + +/* cl_device_type - bitfield */ +#define CL_DEVICE_TYPE_DEFAULT (1 << 0) +#define CL_DEVICE_TYPE_CPU (1 << 1) +#define CL_DEVICE_TYPE_GPU (1 << 2) +#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) +#define CL_DEVICE_TYPE_CUSTOM (1 << 4) +#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF + +/* cl_device_info */ +#define CL_DEVICE_TYPE 0x1000 +#define CL_DEVICE_VENDOR_ID 0x1001 +#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 +#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 +#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 +#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B +#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C +#define CL_DEVICE_ADDRESS_BITS 0x100D +#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E +#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F +#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 +#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 +#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 +#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 +#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 +#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 +#define CL_DEVICE_IMAGE_SUPPORT 0x1016 +#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 +#define CL_DEVICE_MAX_SAMPLERS 0x1018 +#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 +#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A +#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B +#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C +#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D +#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E +#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F +#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 +#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 +#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 +#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 +#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 +#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 +#define CL_DEVICE_ENDIAN_LITTLE 0x1026 +#define CL_DEVICE_AVAILABLE 0x1027 +#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 +#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 +#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ +#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A +#define CL_DEVICE_NAME 0x102B +#define CL_DEVICE_VENDOR 0x102C +#define CL_DRIVER_VERSION 0x102D +#define CL_DEVICE_PROFILE 0x102E +#define CL_DEVICE_VERSION 0x102F +#define CL_DEVICE_EXTENSIONS 0x1030 +#define CL_DEVICE_PLATFORM 0x1031 +#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 +/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 +#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C +#define CL_DEVICE_OPENCL_C_VERSION 0x103D +#define CL_DEVICE_LINKER_AVAILABLE 0x103E +#define CL_DEVICE_BUILT_IN_KERNELS 0x103F +#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 +#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 +#define CL_DEVICE_PARENT_DEVICE 0x1042 +#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 +#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 +#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 +#define CL_DEVICE_PARTITION_TYPE 0x1046 +#define CL_DEVICE_REFERENCE_COUNT 0x1047 +#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 +#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 +#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A +#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B +#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C +#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D +#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E +#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F +#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 +#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 +#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 +#define CL_DEVICE_SVM_CAPABILITIES 0x1053 +#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 +#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 +#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 +#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 +#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 +#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 +#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A +#define CL_DEVICE_IL_VERSION 0x105B +#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C +#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D + +/* cl_device_fp_config - bitfield */ +#define CL_FP_DENORM (1 << 0) +#define CL_FP_INF_NAN (1 << 1) +#define CL_FP_ROUND_TO_NEAREST (1 << 2) +#define CL_FP_ROUND_TO_ZERO (1 << 3) +#define CL_FP_ROUND_TO_INF (1 << 4) +#define CL_FP_FMA (1 << 5) +#define CL_FP_SOFT_FLOAT (1 << 6) +#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) + +/* cl_device_mem_cache_type */ +#define CL_NONE 0x0 +#define CL_READ_ONLY_CACHE 0x1 +#define CL_READ_WRITE_CACHE 0x2 + +/* cl_device_local_mem_type */ +#define CL_LOCAL 0x1 +#define CL_GLOBAL 0x2 + +/* cl_device_exec_capabilities - bitfield */ +#define CL_EXEC_KERNEL (1 << 0) +#define CL_EXEC_NATIVE_KERNEL (1 << 1) + +/* cl_command_queue_properties - bitfield */ +#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) +#define CL_QUEUE_PROFILING_ENABLE (1 << 1) +#define CL_QUEUE_ON_DEVICE (1 << 2) +#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) + +/* cl_context_info */ +#define CL_CONTEXT_REFERENCE_COUNT 0x1080 +#define CL_CONTEXT_DEVICES 0x1081 +#define CL_CONTEXT_PROPERTIES 0x1082 +#define CL_CONTEXT_NUM_DEVICES 0x1083 + +/* cl_context_properties */ +#define CL_CONTEXT_PLATFORM 0x1084 +#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 + +/* cl_device_partition_property */ +#define CL_DEVICE_PARTITION_EQUALLY 0x1086 +#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 +#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 +#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 + +/* cl_device_affinity_domain */ +#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) +#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) +#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) +#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) +#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) +#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) + +/* cl_device_svm_capabilities */ +#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) +#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) +#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) +#define CL_DEVICE_SVM_ATOMICS (1 << 3) + +/* cl_command_queue_info */ +#define CL_QUEUE_CONTEXT 0x1090 +#define CL_QUEUE_DEVICE 0x1091 +#define CL_QUEUE_REFERENCE_COUNT 0x1092 +#define CL_QUEUE_PROPERTIES 0x1093 +#define CL_QUEUE_SIZE 0x1094 +#define CL_QUEUE_DEVICE_DEFAULT 0x1095 + +/* cl_mem_flags and cl_svm_mem_flags - bitfield */ +#define CL_MEM_READ_WRITE (1 << 0) +#define CL_MEM_WRITE_ONLY (1 << 1) +#define CL_MEM_READ_ONLY (1 << 2) +#define CL_MEM_USE_HOST_PTR (1 << 3) +#define CL_MEM_ALLOC_HOST_PTR (1 << 4) +#define CL_MEM_COPY_HOST_PTR (1 << 5) +/* reserved (1 << 6) */ +#define CL_MEM_HOST_WRITE_ONLY (1 << 7) +#define CL_MEM_HOST_READ_ONLY (1 << 8) +#define CL_MEM_HOST_NO_ACCESS (1 << 9) +#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ +#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ +#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) + +/* cl_mem_migration_flags - bitfield */ +#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) +#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) + +/* cl_channel_order */ +#define CL_R 0x10B0 +#define CL_A 0x10B1 +#define CL_RG 0x10B2 +#define CL_RA 0x10B3 +#define CL_RGB 0x10B4 +#define CL_RGBA 0x10B5 +#define CL_BGRA 0x10B6 +#define CL_ARGB 0x10B7 +#define CL_INTENSITY 0x10B8 +#define CL_LUMINANCE 0x10B9 +#define CL_Rx 0x10BA +#define CL_RGx 0x10BB +#define CL_RGBx 0x10BC +#define CL_DEPTH 0x10BD +#define CL_DEPTH_STENCIL 0x10BE +#define CL_sRGB 0x10BF +#define CL_sRGBx 0x10C0 +#define CL_sRGBA 0x10C1 +#define CL_sBGRA 0x10C2 +#define CL_ABGR 0x10C3 + +/* cl_channel_type */ +#define CL_SNORM_INT8 0x10D0 +#define CL_SNORM_INT16 0x10D1 +#define CL_UNORM_INT8 0x10D2 +#define CL_UNORM_INT16 0x10D3 +#define CL_UNORM_SHORT_565 0x10D4 +#define CL_UNORM_SHORT_555 0x10D5 +#define CL_UNORM_INT_101010 0x10D6 +#define CL_SIGNED_INT8 0x10D7 +#define CL_SIGNED_INT16 0x10D8 +#define CL_SIGNED_INT32 0x10D9 +#define CL_UNSIGNED_INT8 0x10DA +#define CL_UNSIGNED_INT16 0x10DB +#define CL_UNSIGNED_INT32 0x10DC +#define CL_HALF_FLOAT 0x10DD +#define CL_FLOAT 0x10DE +#define CL_UNORM_INT24 0x10DF +#define CL_UNORM_INT_101010_2 0x10E0 + +/* cl_mem_object_type */ +#define CL_MEM_OBJECT_BUFFER 0x10F0 +#define CL_MEM_OBJECT_IMAGE2D 0x10F1 +#define CL_MEM_OBJECT_IMAGE3D 0x10F2 +#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 +#define CL_MEM_OBJECT_IMAGE1D 0x10F4 +#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 +#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 +#define CL_MEM_OBJECT_PIPE 0x10F7 + +/* cl_mem_info */ +#define CL_MEM_TYPE 0x1100 +#define CL_MEM_FLAGS 0x1101 +#define CL_MEM_SIZE 0x1102 +#define CL_MEM_HOST_PTR 0x1103 +#define CL_MEM_MAP_COUNT 0x1104 +#define CL_MEM_REFERENCE_COUNT 0x1105 +#define CL_MEM_CONTEXT 0x1106 +#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 +#define CL_MEM_OFFSET 0x1108 +#define CL_MEM_USES_SVM_POINTER 0x1109 + +/* cl_image_info */ +#define CL_IMAGE_FORMAT 0x1110 +#define CL_IMAGE_ELEMENT_SIZE 0x1111 +#define CL_IMAGE_ROW_PITCH 0x1112 +#define CL_IMAGE_SLICE_PITCH 0x1113 +#define CL_IMAGE_WIDTH 0x1114 +#define CL_IMAGE_HEIGHT 0x1115 +#define CL_IMAGE_DEPTH 0x1116 +#define CL_IMAGE_ARRAY_SIZE 0x1117 +#define CL_IMAGE_BUFFER 0x1118 +#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 +#define CL_IMAGE_NUM_SAMPLES 0x111A + +/* cl_pipe_info */ +#define CL_PIPE_PACKET_SIZE 0x1120 +#define CL_PIPE_MAX_PACKETS 0x1121 + +/* cl_addressing_mode */ +#define CL_ADDRESS_NONE 0x1130 +#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 +#define CL_ADDRESS_CLAMP 0x1132 +#define CL_ADDRESS_REPEAT 0x1133 +#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 + +/* cl_filter_mode */ +#define CL_FILTER_NEAREST 0x1140 +#define CL_FILTER_LINEAR 0x1141 + +/* cl_sampler_info */ +#define CL_SAMPLER_REFERENCE_COUNT 0x1150 +#define CL_SAMPLER_CONTEXT 0x1151 +#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 +#define CL_SAMPLER_ADDRESSING_MODE 0x1153 +#define CL_SAMPLER_FILTER_MODE 0x1154 +#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 +#define CL_SAMPLER_LOD_MIN 0x1156 +#define CL_SAMPLER_LOD_MAX 0x1157 + +/* cl_map_flags - bitfield */ +#define CL_MAP_READ (1 << 0) +#define CL_MAP_WRITE (1 << 1) +#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) + +/* cl_program_info */ +#define CL_PROGRAM_REFERENCE_COUNT 0x1160 +#define CL_PROGRAM_CONTEXT 0x1161 +#define CL_PROGRAM_NUM_DEVICES 0x1162 +#define CL_PROGRAM_DEVICES 0x1163 +#define CL_PROGRAM_SOURCE 0x1164 +#define CL_PROGRAM_BINARY_SIZES 0x1165 +#define CL_PROGRAM_BINARIES 0x1166 +#define CL_PROGRAM_NUM_KERNELS 0x1167 +#define CL_PROGRAM_KERNEL_NAMES 0x1168 +#define CL_PROGRAM_IL 0x1169 + +/* cl_program_build_info */ +#define CL_PROGRAM_BUILD_STATUS 0x1181 +#define CL_PROGRAM_BUILD_OPTIONS 0x1182 +#define CL_PROGRAM_BUILD_LOG 0x1183 +#define CL_PROGRAM_BINARY_TYPE 0x1184 +#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 + +/* cl_program_binary_type */ +#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 +#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 +#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 +#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 + +/* cl_build_status */ +#define CL_BUILD_SUCCESS 0 +#define CL_BUILD_NONE -1 +#define CL_BUILD_ERROR -2 +#define CL_BUILD_IN_PROGRESS -3 + +/* cl_kernel_info */ +#define CL_KERNEL_FUNCTION_NAME 0x1190 +#define CL_KERNEL_NUM_ARGS 0x1191 +#define CL_KERNEL_REFERENCE_COUNT 0x1192 +#define CL_KERNEL_CONTEXT 0x1193 +#define CL_KERNEL_PROGRAM 0x1194 +#define CL_KERNEL_ATTRIBUTES 0x1195 +#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 +#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA + +/* cl_kernel_arg_info */ +#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 +#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 +#define CL_KERNEL_ARG_TYPE_NAME 0x1198 +#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 +#define CL_KERNEL_ARG_NAME 0x119A + +/* cl_kernel_arg_address_qualifier */ +#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B +#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C +#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D +#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E + +/* cl_kernel_arg_access_qualifier */ +#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 +#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 +#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 +#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 + +/* cl_kernel_arg_type_qualifer */ +#define CL_KERNEL_ARG_TYPE_NONE 0 +#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) +#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) +#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) +#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) + +/* cl_kernel_work_group_info */ +#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 +#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 +#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 +#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 +#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 +#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 + +/* cl_kernel_sub_group_info */ +#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 +#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 +#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 + +/* cl_kernel_exec_info */ +#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 +#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 + +/* cl_event_info */ +#define CL_EVENT_COMMAND_QUEUE 0x11D0 +#define CL_EVENT_COMMAND_TYPE 0x11D1 +#define CL_EVENT_REFERENCE_COUNT 0x11D2 +#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 +#define CL_EVENT_CONTEXT 0x11D4 + +/* cl_command_type */ +#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 +#define CL_COMMAND_TASK 0x11F1 +#define CL_COMMAND_NATIVE_KERNEL 0x11F2 +#define CL_COMMAND_READ_BUFFER 0x11F3 +#define CL_COMMAND_WRITE_BUFFER 0x11F4 +#define CL_COMMAND_COPY_BUFFER 0x11F5 +#define CL_COMMAND_READ_IMAGE 0x11F6 +#define CL_COMMAND_WRITE_IMAGE 0x11F7 +#define CL_COMMAND_COPY_IMAGE 0x11F8 +#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 +#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA +#define CL_COMMAND_MAP_BUFFER 0x11FB +#define CL_COMMAND_MAP_IMAGE 0x11FC +#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD +#define CL_COMMAND_MARKER 0x11FE +#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF +#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 +#define CL_COMMAND_READ_BUFFER_RECT 0x1201 +#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 +#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 +#define CL_COMMAND_USER 0x1204 +#define CL_COMMAND_BARRIER 0x1205 +#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 +#define CL_COMMAND_FILL_BUFFER 0x1207 +#define CL_COMMAND_FILL_IMAGE 0x1208 +#define CL_COMMAND_SVM_FREE 0x1209 +#define CL_COMMAND_SVM_MEMCPY 0x120A +#define CL_COMMAND_SVM_MEMFILL 0x120B +#define CL_COMMAND_SVM_MAP 0x120C +#define CL_COMMAND_SVM_UNMAP 0x120D + +/* command execution status */ +#define CL_COMPLETE 0x0 +#define CL_RUNNING 0x1 +#define CL_SUBMITTED 0x2 +#define CL_QUEUED 0x3 + +/* cl_buffer_create_type */ +#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 + +/* cl_profiling_info */ +#define CL_PROFILING_COMMAND_QUEUED 0x1280 +#define CL_PROFILING_COMMAND_SUBMIT 0x1281 +#define CL_PROFILING_COMMAND_START 0x1282 +#define CL_PROFILING_COMMAND_END 0x1283 +#define CL_PROFILING_COMMAND_COMPLETE 0x1284 + +/********************************************************************************************************/ + +/* Platform API */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPlatformIDs(cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPlatformInfo(cl_platform_id /* platform */, + cl_platform_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Device APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceIDs(cl_platform_id /* platform */, + cl_device_type /* device_type */, + cl_uint /* num_entries */, + cl_device_id * /* devices */, + cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceInfo(cl_device_id /* device */, + cl_device_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCreateSubDevices(cl_device_id /* in_device */, + const cl_device_partition_property * /* properties */, + cl_uint /* num_devices */, + cl_device_id * /* out_devices */, + cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetDefaultDeviceCommandQueue(cl_context /* context */, + cl_device_id /* device */, + cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceAndHostTimer(cl_device_id /* device */, + cl_ulong* /* device_timestamp */, + cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetHostTimer(cl_device_id /* device */, + cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; + + +/* Context APIs */ +extern CL_API_ENTRY cl_context CL_API_CALL +clCreateContext(const cl_context_properties * /* properties */, + cl_uint /* num_devices */, + const cl_device_id * /* devices */, + void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), + void * /* user_data */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_context CL_API_CALL +clCreateContextFromType(const cl_context_properties * /* properties */, + cl_device_type /* device_type */, + void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), + void * /* user_data */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetContextInfo(cl_context /* context */, + cl_context_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Command Queue APIs */ +extern CL_API_ENTRY cl_command_queue CL_API_CALL +clCreateCommandQueueWithProperties(cl_context /* context */, + cl_device_id /* device */, + const cl_queue_properties * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetCommandQueueInfo(cl_command_queue /* command_queue */, + cl_command_queue_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Memory Object APIs */ +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateBuffer(cl_context /* context */, + cl_mem_flags /* flags */, + size_t /* size */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateSubBuffer(cl_mem /* buffer */, + cl_mem_flags /* flags */, + cl_buffer_create_type /* buffer_create_type */, + const void * /* buffer_create_info */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateImage(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + const cl_image_desc * /* image_desc */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreatePipe(cl_context /* context */, + cl_mem_flags /* flags */, + cl_uint /* pipe_packet_size */, + cl_uint /* pipe_max_packets */, + const cl_pipe_properties * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetSupportedImageFormats(cl_context /* context */, + cl_mem_flags /* flags */, + cl_mem_object_type /* image_type */, + cl_uint /* num_entries */, + cl_image_format * /* image_formats */, + cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetMemObjectInfo(cl_mem /* memobj */, + cl_mem_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetImageInfo(cl_mem /* image */, + cl_image_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPipeInfo(cl_mem /* pipe */, + cl_pipe_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetMemObjectDestructorCallback(cl_mem /* memobj */, + void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), + void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; + +/* SVM Allocation APIs */ +extern CL_API_ENTRY void * CL_API_CALL +clSVMAlloc(cl_context /* context */, + cl_svm_mem_flags /* flags */, + size_t /* size */, + cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY void CL_API_CALL +clSVMFree(cl_context /* context */, + void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; + +/* Sampler APIs */ +extern CL_API_ENTRY cl_sampler CL_API_CALL +clCreateSamplerWithProperties(cl_context /* context */, + const cl_sampler_properties * /* normalized_coords */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetSamplerInfo(cl_sampler /* sampler */, + cl_sampler_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Program Object APIs */ +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithSource(cl_context /* context */, + cl_uint /* count */, + const char ** /* strings */, + const size_t * /* lengths */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithBinary(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const size_t * /* lengths */, + const unsigned char ** /* binaries */, + cl_int * /* binary_status */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithBuiltInKernels(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* kernel_names */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithIL(cl_context /* context */, + const void* /* il */, + size_t /* length */, + cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clBuildProgram(cl_program /* program */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCompileProgram(cl_program /* program */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + cl_uint /* num_input_headers */, + const cl_program * /* input_headers */, + const char ** /* header_include_names */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_program CL_API_CALL +clLinkProgram(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + cl_uint /* num_input_programs */, + const cl_program * /* input_programs */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */, + cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetProgramInfo(cl_program /* program */, + cl_program_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetProgramBuildInfo(cl_program /* program */, + cl_device_id /* device */, + cl_program_build_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Kernel Object APIs */ +extern CL_API_ENTRY cl_kernel CL_API_CALL +clCreateKernel(cl_program /* program */, + const char * /* kernel_name */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCreateKernelsInProgram(cl_program /* program */, + cl_uint /* num_kernels */, + cl_kernel * /* kernels */, + cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_kernel CL_API_CALL +clCloneKernel(cl_kernel /* source_kernel */, + cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelArg(cl_kernel /* kernel */, + cl_uint /* arg_index */, + size_t /* arg_size */, + const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelArgSVMPointer(cl_kernel /* kernel */, + cl_uint /* arg_index */, + const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelExecInfo(cl_kernel /* kernel */, + cl_kernel_exec_info /* param_name */, + size_t /* param_value_size */, + const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelInfo(cl_kernel /* kernel */, + cl_kernel_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelArgInfo(cl_kernel /* kernel */, + cl_uint /* arg_indx */, + cl_kernel_arg_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelWorkGroupInfo(cl_kernel /* kernel */, + cl_device_id /* device */, + cl_kernel_work_group_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelSubGroupInfo(cl_kernel /* kernel */, + cl_device_id /* device */, + cl_kernel_sub_group_info /* param_name */, + size_t /* input_value_size */, + const void* /*input_value */, + size_t /* param_value_size */, + void* /* param_value */, + size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; + + +/* Event Object APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clWaitForEvents(cl_uint /* num_events */, + const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetEventInfo(cl_event /* event */, + cl_event_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateUserEvent(cl_context /* context */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetUserEventStatus(cl_event /* event */, + cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetEventCallback( cl_event /* event */, + cl_int /* command_exec_callback_type */, + void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; + +/* Profiling APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetEventProfilingInfo(cl_event /* event */, + cl_profiling_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Flush and Finish APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +/* Enqueued Commands APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_read */, + size_t /* offset */, + size_t /* size */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadBufferRect(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_read */, + const size_t * /* buffer_offset */, + const size_t * /* host_offset */, + const size_t * /* region */, + size_t /* buffer_row_pitch */, + size_t /* buffer_slice_pitch */, + size_t /* host_row_pitch */, + size_t /* host_slice_pitch */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_write */, + size_t /* offset */, + size_t /* size */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_write */, + const size_t * /* buffer_offset */, + const size_t * /* host_offset */, + const size_t * /* region */, + size_t /* buffer_row_pitch */, + size_t /* buffer_slice_pitch */, + size_t /* host_row_pitch */, + size_t /* host_slice_pitch */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueFillBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + const void * /* pattern */, + size_t /* pattern_size */, + size_t /* offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBuffer(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_buffer */, + size_t /* src_offset */, + size_t /* dst_offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_buffer */, + const size_t * /* src_origin */, + const size_t * /* dst_origin */, + const size_t * /* region */, + size_t /* src_row_pitch */, + size_t /* src_slice_pitch */, + size_t /* dst_row_pitch */, + size_t /* dst_slice_pitch */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_read */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t /* row_pitch */, + size_t /* slice_pitch */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_write */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t /* input_row_pitch */, + size_t /* input_slice_pitch */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueFillImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + const void * /* fill_color */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyImage(cl_command_queue /* command_queue */, + cl_mem /* src_image */, + cl_mem /* dst_image */, + const size_t * /* src_origin[3] */, + const size_t * /* dst_origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, + cl_mem /* src_image */, + cl_mem /* dst_buffer */, + const size_t * /* src_origin[3] */, + const size_t * /* region[3] */, + size_t /* dst_offset */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_image */, + size_t /* src_offset */, + const size_t * /* dst_origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY void * CL_API_CALL +clEnqueueMapBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_map */, + cl_map_flags /* map_flags */, + size_t /* offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY void * CL_API_CALL +clEnqueueMapImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_map */, + cl_map_flags /* map_flags */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t * /* image_row_pitch */, + size_t * /* image_slice_pitch */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, + cl_mem /* memobj */, + void * /* mapped_ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, + cl_uint /* num_mem_objects */, + const cl_mem * /* mem_objects */, + cl_mem_migration_flags /* flags */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, + cl_kernel /* kernel */, + cl_uint /* work_dim */, + const size_t * /* global_work_offset */, + const size_t * /* global_work_size */, + const size_t * /* local_work_size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueNativeKernel(cl_command_queue /* command_queue */, + void (CL_CALLBACK * /*user_func*/)(void *), + void * /* args */, + size_t /* cb_args */, + cl_uint /* num_mem_objects */, + const cl_mem * /* mem_list */, + const void ** /* args_mem_loc */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMFree(cl_command_queue /* command_queue */, + cl_uint /* num_svm_pointers */, + void *[] /* svm_pointers[] */, + void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, + cl_uint /* num_svm_pointers */, + void *[] /* svm_pointers[] */, + void * /* user_data */), + void * /* user_data */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, + cl_bool /* blocking_copy */, + void * /* dst_ptr */, + const void * /* src_ptr */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMemFill(cl_command_queue /* command_queue */, + void * /* svm_ptr */, + const void * /* pattern */, + size_t /* pattern_size */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMap(cl_command_queue /* command_queue */, + cl_bool /* blocking_map */, + cl_map_flags /* flags */, + void * /* svm_ptr */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMUnmap(cl_command_queue /* command_queue */, + void * /* svm_ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, + cl_uint /* num_svm_pointers */, + const void ** /* svm_pointers */, + const size_t * /* sizes */, + cl_mem_migration_flags /* flags */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; + + +/* Extension function access + * + * Returns the extension function address for the given function name, + * or NULL if a valid function can not be found. The client must + * check to make sure the address is not NULL, before using or + * calling the returned function address. + */ +extern CL_API_ENTRY void * CL_API_CALL +clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, + const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; + + +/* Deprecated OpenCL 1.1 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateImage2D(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + size_t /* image_width */, + size_t /* image_height */, + size_t /* image_row_pitch */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateImage3D(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + size_t /* image_width */, + size_t /* image_height */, + size_t /* image_depth */, + size_t /* image_row_pitch */, + size_t /* image_slice_pitch */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueMarker(cl_command_queue /* command_queue */, + cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueWaitForEvents(cl_command_queue /* command_queue */, + cl_uint /* num_events */, + const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL +clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +/* Deprecated OpenCL 2.0 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL +clCreateCommandQueue(cl_context /* context */, + cl_device_id /* device */, + cl_command_queue_properties /* properties */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL +clCreateSampler(cl_context /* context */, + cl_bool /* normalized_coords */, + cl_addressing_mode /* addressing_mode */, + cl_filter_mode /* filter_mode */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL +clEnqueueTask(cl_command_queue /* command_queue */, + cl_kernel /* kernel */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_H */ + diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h new file mode 100644 index 00000000000..d5960a43f72 --- /dev/null +++ b/third_party/opencl/include/CL/cl_d3d10.h @@ -0,0 +1,131 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_D3D10_H +#define __OPENCL_CL_D3D10_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * cl_khr_d3d10_sharing */ +#define cl_khr_d3d10_sharing 1 + +typedef cl_uint cl_d3d10_device_source_khr; +typedef cl_uint cl_d3d10_device_set_khr; + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_D3D10_DEVICE_KHR -1002 +#define CL_INVALID_D3D10_RESOURCE_KHR -1003 +#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 +#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 + +/* cl_d3d10_device_source_nv */ +#define CL_D3D10_DEVICE_KHR 0x4010 +#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 + +/* cl_d3d10_device_set_nv */ +#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 +#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 + +/* cl_context_info */ +#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 +#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C + +/* cl_mem_info */ +#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 + +/* cl_image_info */ +#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 +#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( + cl_platform_id platform, + cl_d3d10_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d10_device_set_khr d3d_device_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Buffer * resource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Texture2D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Texture3D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_0; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_D3D10_H */ + diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h new file mode 100644 index 00000000000..39f9072398a --- /dev/null +++ b/third_party/opencl/include/CL/cl_d3d11.h @@ -0,0 +1,131 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_D3D11_H +#define __OPENCL_CL_D3D11_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * cl_khr_d3d11_sharing */ +#define cl_khr_d3d11_sharing 1 + +typedef cl_uint cl_d3d11_device_source_khr; +typedef cl_uint cl_d3d11_device_set_khr; + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_D3D11_DEVICE_KHR -1006 +#define CL_INVALID_D3D11_RESOURCE_KHR -1007 +#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 +#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 + +/* cl_d3d11_device_source */ +#define CL_D3D11_DEVICE_KHR 0x4019 +#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A + +/* cl_d3d11_device_set */ +#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B +#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C + +/* cl_context_info */ +#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D +#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D + +/* cl_mem_info */ +#define CL_MEM_D3D11_RESOURCE_KHR 0x401E + +/* cl_image_info */ +#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 +#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( + cl_platform_id platform, + cl_d3d11_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d11_device_set_khr d3d_device_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Buffer * resource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Texture2D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Texture3D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_D3D11_H */ + diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h new file mode 100644 index 00000000000..2729e8b9e89 --- /dev/null +++ b/third_party/opencl/include/CL/cl_dx9_media_sharing.h @@ -0,0 +1,132 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H +#define __OPENCL_CL_DX9_MEDIA_SHARING_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ +/* cl_khr_dx9_media_sharing */ +#define cl_khr_dx9_media_sharing 1 + +typedef cl_uint cl_dx9_media_adapter_type_khr; +typedef cl_uint cl_dx9_media_adapter_set_khr; + +#if defined(_WIN32) +#include +typedef struct _cl_dx9_surface_info_khr +{ + IDirect3DSurface9 *resource; + HANDLE shared_handle; +} cl_dx9_surface_info_khr; +#endif + + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 +#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 +#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 +#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 + +/* cl_media_adapter_type_khr */ +#define CL_ADAPTER_D3D9_KHR 0x2020 +#define CL_ADAPTER_D3D9EX_KHR 0x2021 +#define CL_ADAPTER_DXVA_KHR 0x2022 + +/* cl_media_adapter_set_khr */ +#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 +#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 + +/* cl_context_info */ +#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 +#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 +#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 + +/* cl_mem_info */ +#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 +#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 + +/* cl_image_info */ +#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B +#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( + cl_platform_id platform, + cl_uint num_media_adapters, + cl_dx9_media_adapter_type_khr * media_adapter_type, + void * media_adapters, + cl_dx9_media_adapter_set_khr media_adapter_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( + cl_context context, + cl_mem_flags flags, + cl_dx9_media_adapter_type_khr adapter_type, + void * surface_info, + cl_uint plane, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ + diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h new file mode 100644 index 00000000000..a765bd5266c --- /dev/null +++ b/third_party/opencl/include/CL/cl_egl.h @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +#ifndef __OPENCL_CL_EGL_H +#define __OPENCL_CL_EGL_H + +#ifdef __APPLE__ + +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ +#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F +#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D +#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E + +/* Error type for clCreateFromEGLImageKHR */ +#define CL_INVALID_EGL_OBJECT_KHR -1093 +#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 + +/* CLeglImageKHR is an opaque handle to an EGLImage */ +typedef void* CLeglImageKHR; + +/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ +typedef void* CLeglDisplayKHR; + +/* CLeglSyncKHR is an opaque handle to an EGLSync object */ +typedef void* CLeglSyncKHR; + +/* properties passed to clCreateFromEGLImageKHR */ +typedef intptr_t cl_egl_image_properties_khr; + + +#define cl_khr_egl_image 1 + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromEGLImageKHR(cl_context /* context */, + CLeglDisplayKHR /* egldisplay */, + CLeglImageKHR /* eglimage */, + cl_mem_flags /* flags */, + const cl_egl_image_properties_khr * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( + cl_context context, + CLeglDisplayKHR egldisplay, + CLeglImageKHR eglimage, + cl_mem_flags flags, + const cl_egl_image_properties_khr * properties, + cl_int * errcode_ret); + + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event); + + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event); + + +#define cl_khr_egl_event 1 + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateEventFromEGLSyncKHR(cl_context /* context */, + CLeglSyncKHR /* sync */, + CLeglDisplayKHR /* display */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( + cl_context context, + CLeglSyncKHR sync, + CLeglDisplayKHR display, + cl_int * errcode_ret); + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h new file mode 100644 index 00000000000..7941583895c --- /dev/null +++ b/third_party/opencl/include/CL/cl_ext.h @@ -0,0 +1,391 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ + +/* cl_ext.h contains OpenCL extensions which don't have external */ +/* (OpenGL, D3D) dependencies. */ + +#ifndef __CL_EXT_H +#define __CL_EXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + #include + #include +#else + #include +#endif + +/* cl_khr_fp16 extension - no extension #define since it has no functions */ +#define CL_DEVICE_HALF_FP_CONFIG 0x1033 + +/* Memory object destruction + * + * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR + * + * Registers a user callback function that will be called when the memory object is deleted and its resources + * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback + * stack associated with memobj. The registered user callback functions are called in the reverse order in + * which they were registered. The user callback functions are called and then the memory object is deleted + * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be + * notified when the memory referenced by host_ptr, specified when the memory object is created and used as + * the storage bits for the memory object, can be reused or freed. + * + * The application may not call CL api's with the cl_mem object passed to the pfn_notify. + * + * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) + * before using. + */ +#define cl_APPLE_SetMemObjectDestructor 1 +cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, + void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), + void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + + +/* Context Logging Functions + * + * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). + * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) + * before using. + * + * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger + */ +#define cl_APPLE_ContextLoggingFunctions 1 +extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + +/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ +extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + +/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ +extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + + +/************************ +* cl_khr_icd extension * +************************/ +#define cl_khr_icd 1 + +/* cl_platform_info */ +#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 + +/* Additional Error Codes */ +#define CL_PLATFORM_NOT_FOUND_KHR -1001 + +extern CL_API_ENTRY cl_int CL_API_CALL +clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */); + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( + cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */); + + +/* Extension: cl_khr_image2D_buffer + * + * This extension allows a 2D image to be created from a cl_mem buffer without a copy. + * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. + * Both the sampler and sampler-less read_image built-in functions are supported for 2D images + * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported + * for 2D images created from a buffer. + * + * When the 2D image from buffer is created, the client must specify the width, + * height, image format (i.e. channel order and channel data type) and optionally the row pitch + * + * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. + * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. + */ + +/************************************* + * cl_khr_initalize_memory extension * + *************************************/ + +#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 + + +/************************************** + * cl_khr_terminate_context extension * + **************************************/ + +#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 +#define CL_CONTEXT_TERMINATE_KHR 0x2032 + +#define cl_khr_terminate_context 1 +extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; + + +/* + * Extension: cl_khr_spir + * + * This extension adds support to create an OpenCL program object from a + * Standard Portable Intermediate Representation (SPIR) instance + */ + +#define CL_DEVICE_SPIR_VERSIONS 0x40E0 +#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 + + +/****************************************** +* cl_nv_device_attribute_query extension * +******************************************/ +/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ +#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 +#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 +#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 +#define CL_DEVICE_WARP_SIZE_NV 0x4003 +#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 +#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 +#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 + +/********************************* +* cl_amd_device_attribute_query * +*********************************/ +#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 + +/********************************* +* cl_arm_printf extension +*********************************/ +#define CL_PRINTF_CALLBACK_ARM 0x40B0 +#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 + +#ifdef CL_VERSION_1_1 + /*********************************** + * cl_ext_device_fission extension * + ***********************************/ + #define cl_ext_device_fission 1 + + extern CL_API_ENTRY cl_int CL_API_CALL + clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + extern CL_API_ENTRY cl_int CL_API_CALL + clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef cl_ulong cl_device_partition_property_ext; + extern CL_API_ENTRY cl_int CL_API_CALL + clCreateSubDevicesEXT( cl_device_id /*in_device*/, + const cl_device_partition_property_ext * /* properties */, + cl_uint /*num_entries*/, + cl_device_id * /*out_devices*/, + cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, + const cl_device_partition_property_ext * /* properties */, + cl_uint /*num_entries*/, + cl_device_id * /*out_devices*/, + cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + /* cl_device_partition_property_ext */ + #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 + #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 + #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 + #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 + + /* clDeviceGetInfo selectors */ + #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 + #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 + #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 + #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 + #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 + + /* error codes */ + #define CL_DEVICE_PARTITION_FAILED_EXT -1057 + #define CL_INVALID_PARTITION_COUNT_EXT -1058 + #define CL_INVALID_PARTITION_NAME_EXT -1059 + + /* CL_AFFINITY_DOMAINs */ + #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 + #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 + #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 + #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 + #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 + #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 + + /* cl_device_partition_property_ext list terminators */ + #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) + #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) + #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) + +/********************************* +* cl_qcom_ext_host_ptr extension +*********************************/ + +#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) + +#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 +#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 +#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 +#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 +#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 +#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 +#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 +#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 + +typedef cl_uint cl_image_pitch_info_qcom; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceImageInfoQCOM(cl_device_id device, + size_t image_width, + size_t image_height, + const cl_image_format *image_format, + cl_image_pitch_info_qcom param_name, + size_t param_value_size, + void *param_value, + size_t *param_value_size_ret); + +typedef struct _cl_mem_ext_host_ptr +{ + /* Type of external memory allocation. */ + /* Legal values will be defined in layered extensions. */ + cl_uint allocation_type; + + /* Host cache policy for this external memory allocation. */ + cl_uint host_cache_policy; + +} cl_mem_ext_host_ptr; + +/********************************* +* cl_qcom_ion_host_ptr extension +*********************************/ + +#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 + +typedef struct _cl_mem_ion_host_ptr +{ + /* Type of external memory allocation. */ + /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ + cl_mem_ext_host_ptr ext_host_ptr; + + /* ION file descriptor */ + int ion_filedesc; + + /* Host pointer to the ION allocated memory */ + void* ion_hostptr; + +} cl_mem_ion_host_ptr; + +#endif /* CL_VERSION_1_1 */ + + +#ifdef CL_VERSION_2_0 +/********************************* +* cl_khr_sub_groups extension +*********************************/ +#define cl_khr_sub_groups 1 + +typedef cl_uint cl_kernel_sub_group_info_khr; + +/* cl_khr_sub_group_info */ +#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 +#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, + cl_device_id /*in_device*/, + cl_kernel_sub_group_info_khr /* param_name */, + size_t /*input_value_size*/, + const void * /*input_value*/, + size_t /*param_value_size*/, + void* /*param_value*/, + size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; + +typedef CL_API_ENTRY cl_int + ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, + cl_device_id /*in_device*/, + cl_kernel_sub_group_info_khr /* param_name */, + size_t /*input_value_size*/, + const void * /*input_value*/, + size_t /*param_value_size*/, + void* /*param_value*/, + size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; +#endif /* CL_VERSION_2_0 */ + +#ifdef CL_VERSION_2_1 +/********************************* +* cl_khr_priority_hints extension +*********************************/ +#define cl_khr_priority_hints 1 + +typedef cl_uint cl_queue_priority_khr; + +/* cl_command_queue_properties */ +#define CL_QUEUE_PRIORITY_KHR 0x1096 + +/* cl_queue_priority_khr */ +#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) +#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) +#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) + +#endif /* CL_VERSION_2_1 */ + +#ifdef CL_VERSION_2_1 +/********************************* +* cl_khr_throttle_hints extension +*********************************/ +#define cl_khr_throttle_hints 1 + +typedef cl_uint cl_queue_throttle_khr; + +/* cl_command_queue_properties */ +#define CL_QUEUE_THROTTLE_KHR 0x1097 + +/* cl_queue_throttle_khr */ +#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) +#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) +#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) + +#endif /* CL_VERSION_2_1 */ + +#ifdef __cplusplus +} +#endif + + +#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h new file mode 100644 index 00000000000..6328a1cd93a --- /dev/null +++ b/third_party/opencl/include/CL/cl_ext_qcom.h @@ -0,0 +1,255 @@ +/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. + * Qualcomm Technologies Proprietary and Confidential. + */ + +#ifndef __OPENCL_CL_EXT_QCOM_H +#define __OPENCL_CL_EXT_QCOM_H + +// Needed by cl_khr_egl_event extension +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/************************************ + * cl_qcom_create_buffer_from_image * + ************************************/ + +#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 +#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateBufferFromImageQCOM(cl_mem image, + cl_mem_flags flags, + cl_int *errcode_ret); + + +/************************************ + * cl_qcom_limited_printf extension * + ************************************/ + +/* Builtin printf function buffer size in bytes. */ +#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 + + +/************************************* + * cl_qcom_extended_images extension * + *************************************/ + +#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA +#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB +#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF + +/************************************* + * cl_qcom_perf_hint extension * + *************************************/ + +typedef cl_uint cl_perf_hint; + +#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 + +/*cl_perf_hint*/ +#define CL_PERF_HINT_HIGH_QCOM 0x40C3 +#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 +#define CL_PERF_HINT_LOW_QCOM 0x40C5 + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetPerfHintQCOM(cl_context context, + cl_perf_hint perf_hint); + +// This extension is published at Khronos, so its definitions are made in cl_ext.h. +// This duplication is for backward compatibility. + +#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM + +/********************************* +* cl_qcom_android_native_buffer_host_ptr extension +*********************************/ + +#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 + + +typedef struct _cl_mem_android_native_buffer_host_ptr +{ + // Type of external memory allocation. + // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. + cl_mem_ext_host_ptr ext_host_ptr; + + // Virtual pointer to the android native buffer + void* anb_ptr; + +} cl_mem_android_native_buffer_host_ptr; + +#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM + +/*********************************** +* cl_img_egl_image extension * +************************************/ +typedef void* CLeglImageIMG; +typedef void* CLeglDisplayIMG; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromEGLImageIMG(cl_context context, + cl_mem_flags flags, + CLeglImageIMG image, + CLeglDisplayIMG display, + cl_int *errcode_ret); + + +/********************************* +* cl_qcom_other_image extension +*********************************/ + +// Extended flag for creating/querying QCOM non-standard images +#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) + +// cl_channel_type +#define CL_QCOM_UNORM_MIPI10 0x4159 +#define CL_QCOM_UNORM_MIPI12 0x415A +#define CL_QCOM_UNSIGNED_MIPI10 0x415B +#define CL_QCOM_UNSIGNED_MIPI12 0x415C +#define CL_QCOM_UNORM_INT10 0x415D +#define CL_QCOM_UNORM_INT12 0x415E +#define CL_QCOM_UNSIGNED_INT16 0x415F + +// cl_channel_order +// Dedicate 0x4130-0x415F range for QCOM extended image formats +// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format +#define CL_QCOM_BAYER 0x414E + +#define CL_QCOM_NV12 0x4133 +#define CL_QCOM_NV12_Y 0x4134 +#define CL_QCOM_NV12_UV 0x4135 + +#define CL_QCOM_TILED_NV12 0x4136 +#define CL_QCOM_TILED_NV12_Y 0x4137 +#define CL_QCOM_TILED_NV12_UV 0x4138 + +#define CL_QCOM_P010 0x413C +#define CL_QCOM_P010_Y 0x413D +#define CL_QCOM_P010_UV 0x413E + +#define CL_QCOM_TILED_P010 0x413F +#define CL_QCOM_TILED_P010_Y 0x4140 +#define CL_QCOM_TILED_P010_UV 0x4141 + + +#define CL_QCOM_TP10 0x4145 +#define CL_QCOM_TP10_Y 0x4146 +#define CL_QCOM_TP10_UV 0x4147 + +#define CL_QCOM_TILED_TP10 0x4148 +#define CL_QCOM_TILED_TP10_Y 0x4149 +#define CL_QCOM_TILED_TP10_UV 0x414A + +/********************************* +* cl_qcom_compressed_image extension +*********************************/ + +// Extended flag for creating/querying QCOM non-planar compressed images +#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) + +// Extended image format +// cl_channel_order +#define CL_QCOM_COMPRESSED_RGBA 0x4130 +#define CL_QCOM_COMPRESSED_RGBx 0x4131 + +#define CL_QCOM_COMPRESSED_NV12_Y 0x413A +#define CL_QCOM_COMPRESSED_NV12_UV 0x413B + +#define CL_QCOM_COMPRESSED_P010 0x4142 +#define CL_QCOM_COMPRESSED_P010_Y 0x4143 +#define CL_QCOM_COMPRESSED_P010_UV 0x4144 + +#define CL_QCOM_COMPRESSED_TP10 0x414B +#define CL_QCOM_COMPRESSED_TP10_Y 0x414C +#define CL_QCOM_COMPRESSED_TP10_UV 0x414D + +#define CL_QCOM_COMPRESSED_NV12_4R 0x414F +#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 +#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 +/********************************* +* cl_qcom_compressed_yuv_image_read extension +*********************************/ + +// Extended flag for creating/querying QCOM compressed images +#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) + +// Extended image format +#define CL_QCOM_COMPRESSED_NV12 0x10C4 + +// Extended flag for setting ION buffer allocation type +#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD +#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE + +/********************************* +* cl_qcom_accelerated_image_ops +*********************************/ +#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 +#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 +#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 +#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 +#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 +#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 + +//Extended flag for specifying weight image type +#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) + +// Box Filter +typedef struct _cl_box_filter_size_qcom +{ + // Width of box filter on X direction. + float box_filter_width; + + // Height of box filter on Y direction. + float box_filter_height; +} cl_box_filter_size_qcom; + +// HOF Weight Image Desc +typedef struct _cl_weight_desc_qcom +{ + /** Coordinate of the "center" point of the weight image, + based on the weight image's top-left corner as the origin. */ + size_t center_coord_x; + size_t center_coord_y; + cl_bitfield flags; +} cl_weight_desc_qcom; + +typedef struct _cl_weight_image_desc_qcom +{ + cl_image_desc image_desc; + cl_weight_desc_qcom weight_desc; +} cl_weight_image_desc_qcom; + +/************************************* + * cl_qcom_protected_context extension * + *************************************/ + +#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 +#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 + +/************************************* + * cl_qcom_priority_hint extension * + *************************************/ +#define CL_PRIORITY_HINT_NONE_QCOM 0 +typedef cl_uint cl_priority_hint; + +#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 + +/*cl_priority_hint*/ +#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA +#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB +#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h new file mode 100644 index 00000000000..945daa83d7f --- /dev/null +++ b/third_party/opencl/include/CL/cl_gl.h @@ -0,0 +1,167 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +#ifndef __OPENCL_CL_GL_H +#define __OPENCL_CL_GL_H + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef cl_uint cl_gl_object_type; +typedef cl_uint cl_gl_texture_info; +typedef cl_uint cl_gl_platform_info; +typedef struct __GLsync *cl_GLsync; + +/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ +#define CL_GL_OBJECT_BUFFER 0x2000 +#define CL_GL_OBJECT_TEXTURE2D 0x2001 +#define CL_GL_OBJECT_TEXTURE3D 0x2002 +#define CL_GL_OBJECT_RENDERBUFFER 0x2003 +#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E +#define CL_GL_OBJECT_TEXTURE1D 0x200F +#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 +#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 + +/* cl_gl_texture_info */ +#define CL_GL_TEXTURE_TARGET 0x2004 +#define CL_GL_MIPMAP_LEVEL 0x2005 +#define CL_GL_NUM_SAMPLES 0x2012 + + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLBuffer(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLuint /* bufobj */, + int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLTexture(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLRenderbuffer(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLuint /* renderbuffer */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLObjectInfo(cl_mem /* memobj */, + cl_gl_object_type * /* gl_object_type */, + cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLTextureInfo(cl_mem /* memobj */, + cl_gl_texture_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + + +/* Deprecated OpenCL 1.1 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateFromGLTexture2D(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateFromGLTexture3D(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +/* cl_khr_gl_sharing extension */ + +#define cl_khr_gl_sharing 1 + +typedef cl_uint cl_gl_context_info; + +/* Additional Error Codes */ +#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 + +/* cl_gl_context_info */ +#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 +#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 + +/* Additional cl_context_properties */ +#define CL_GL_CONTEXT_KHR 0x2008 +#define CL_EGL_DISPLAY_KHR 0x2009 +#define CL_GLX_DISPLAY_KHR 0x200A +#define CL_WGL_HDC_KHR 0x200B +#define CL_CGL_SHAREGROUP_KHR 0x200C + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLContextInfoKHR(const cl_context_properties * /* properties */, + cl_gl_context_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( + const cl_context_properties * properties, + cl_gl_context_info param_name, + size_t param_value_size, + void * param_value, + size_t * param_value_size_ret); + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h new file mode 100644 index 00000000000..e3c14c6408c --- /dev/null +++ b/third_party/opencl/include/CL/cl_gl_ext.h @@ -0,0 +1,74 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ +/* OpenGL dependencies. */ + +#ifndef __OPENCL_CL_GL_EXT_H +#define __OPENCL_CL_GL_EXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + #include +#else + #include +#endif + +/* + * For each extension, follow this template + * cl_VEN_extname extension */ +/* #define cl_VEN_extname 1 + * ... define new types, if any + * ... define new tokens, if any + * ... define new APIs, if any + * + * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header + * This allows us to avoid having to decide whether to include GL headers or GLES here. + */ + +/* + * cl_khr_gl_event extension + * See section 9.9 in the OpenCL 1.1 spec for more information + */ +#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateEventFromGLsyncKHR(cl_context /* context */, + cl_GLsync /* cl_GLsync */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h new file mode 100644 index 00000000000..4e334a29183 --- /dev/null +++ b/third_party/opencl/include/CL/cl_platform.h @@ -0,0 +1,1333 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ + +#ifndef __CL_PLATFORM_H +#define __CL_PLATFORM_H + +#ifdef __APPLE__ + /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ + #include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) + #define CL_API_ENTRY + #define CL_API_CALL __stdcall + #define CL_CALLBACK __stdcall +#else + #define CL_API_ENTRY + #define CL_API_CALL + #define CL_CALLBACK +#endif + +/* + * Deprecation flags refer to the last version of the header in which the + * feature was not deprecated. + * + * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without + * deprecation but is deprecated in versions later than 1.1. + */ + +#ifdef __APPLE__ + #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) + #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER + #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 + + #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 + #else + #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! + #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #endif +#else + #define CL_EXTENSION_WEAK_LINK + #define CL_API_SUFFIX__VERSION_1_0 + #define CL_EXT_SUFFIX__VERSION_1_0 + #define CL_API_SUFFIX__VERSION_1_1 + #define CL_EXT_SUFFIX__VERSION_1_1 + #define CL_API_SUFFIX__VERSION_1_2 + #define CL_EXT_SUFFIX__VERSION_1_2 + #define CL_API_SUFFIX__VERSION_2_0 + #define CL_EXT_SUFFIX__VERSION_2_0 + #define CL_API_SUFFIX__VERSION_2_1 + #define CL_EXT_SUFFIX__VERSION_2_1 + + #ifdef __GNUC__ + #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #endif + #elif _WIN32 + #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) + #endif + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #endif +#endif + +#if (defined (_WIN32) && defined(_MSC_VER)) + +/* scalar types */ +typedef signed __int8 cl_char; +typedef unsigned __int8 cl_uchar; +typedef signed __int16 cl_short; +typedef unsigned __int16 cl_ushort; +typedef signed __int32 cl_int; +typedef unsigned __int32 cl_uint; +typedef signed __int64 cl_long; +typedef unsigned __int64 cl_ulong; + +typedef unsigned __int16 cl_half; +typedef float cl_float; +typedef double cl_double; + +/* Macro names and corresponding values defined by OpenCL */ +#define CL_CHAR_BIT 8 +#define CL_SCHAR_MAX 127 +#define CL_SCHAR_MIN (-127-1) +#define CL_CHAR_MAX CL_SCHAR_MAX +#define CL_CHAR_MIN CL_SCHAR_MIN +#define CL_UCHAR_MAX 255 +#define CL_SHRT_MAX 32767 +#define CL_SHRT_MIN (-32767-1) +#define CL_USHRT_MAX 65535 +#define CL_INT_MAX 2147483647 +#define CL_INT_MIN (-2147483647-1) +#define CL_UINT_MAX 0xffffffffU +#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) +#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) +#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) + +#define CL_FLT_DIG 6 +#define CL_FLT_MANT_DIG 24 +#define CL_FLT_MAX_10_EXP +38 +#define CL_FLT_MAX_EXP +128 +#define CL_FLT_MIN_10_EXP -37 +#define CL_FLT_MIN_EXP -125 +#define CL_FLT_RADIX 2 +#define CL_FLT_MAX 340282346638528859811704183484516925440.0f +#define CL_FLT_MIN 1.175494350822287507969e-38f +#define CL_FLT_EPSILON 0x1.0p-23f + +#define CL_DBL_DIG 15 +#define CL_DBL_MANT_DIG 53 +#define CL_DBL_MAX_10_EXP +308 +#define CL_DBL_MAX_EXP +1024 +#define CL_DBL_MIN_10_EXP -307 +#define CL_DBL_MIN_EXP -1021 +#define CL_DBL_RADIX 2 +#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 +#define CL_DBL_MIN 2.225073858507201383090e-308 +#define CL_DBL_EPSILON 2.220446049250313080847e-16 + +#define CL_M_E 2.718281828459045090796 +#define CL_M_LOG2E 1.442695040888963387005 +#define CL_M_LOG10E 0.434294481903251816668 +#define CL_M_LN2 0.693147180559945286227 +#define CL_M_LN10 2.302585092994045901094 +#define CL_M_PI 3.141592653589793115998 +#define CL_M_PI_2 1.570796326794896557999 +#define CL_M_PI_4 0.785398163397448278999 +#define CL_M_1_PI 0.318309886183790691216 +#define CL_M_2_PI 0.636619772367581382433 +#define CL_M_2_SQRTPI 1.128379167095512558561 +#define CL_M_SQRT2 1.414213562373095145475 +#define CL_M_SQRT1_2 0.707106781186547572737 + +#define CL_M_E_F 2.71828174591064f +#define CL_M_LOG2E_F 1.44269502162933f +#define CL_M_LOG10E_F 0.43429449200630f +#define CL_M_LN2_F 0.69314718246460f +#define CL_M_LN10_F 2.30258512496948f +#define CL_M_PI_F 3.14159274101257f +#define CL_M_PI_2_F 1.57079637050629f +#define CL_M_PI_4_F 0.78539818525314f +#define CL_M_1_PI_F 0.31830987334251f +#define CL_M_2_PI_F 0.63661974668503f +#define CL_M_2_SQRTPI_F 1.12837922573090f +#define CL_M_SQRT2_F 1.41421353816986f +#define CL_M_SQRT1_2_F 0.70710676908493f + +#define CL_NAN (CL_INFINITY - CL_INFINITY) +#define CL_HUGE_VALF ((cl_float) 1e50) +#define CL_HUGE_VAL ((cl_double) 1e500) +#define CL_MAXFLOAT CL_FLT_MAX +#define CL_INFINITY CL_HUGE_VALF + +#else + +#include + +/* scalar types */ +typedef int8_t cl_char; +typedef uint8_t cl_uchar; +typedef int16_t cl_short __attribute__((aligned(2))); +typedef uint16_t cl_ushort __attribute__((aligned(2))); +typedef int32_t cl_int __attribute__((aligned(4))); +typedef uint32_t cl_uint __attribute__((aligned(4))); +typedef int64_t cl_long __attribute__((aligned(8))); +typedef uint64_t cl_ulong __attribute__((aligned(8))); + +typedef uint16_t cl_half __attribute__((aligned(2))); +typedef float cl_float __attribute__((aligned(4))); +typedef double cl_double __attribute__((aligned(8))); + +/* Macro names and corresponding values defined by OpenCL */ +#define CL_CHAR_BIT 8 +#define CL_SCHAR_MAX 127 +#define CL_SCHAR_MIN (-127-1) +#define CL_CHAR_MAX CL_SCHAR_MAX +#define CL_CHAR_MIN CL_SCHAR_MIN +#define CL_UCHAR_MAX 255 +#define CL_SHRT_MAX 32767 +#define CL_SHRT_MIN (-32767-1) +#define CL_USHRT_MAX 65535 +#define CL_INT_MAX 2147483647 +#define CL_INT_MIN (-2147483647-1) +#define CL_UINT_MAX 0xffffffffU +#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) +#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) +#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) + +#define CL_FLT_DIG 6 +#define CL_FLT_MANT_DIG 24 +#define CL_FLT_MAX_10_EXP +38 +#define CL_FLT_MAX_EXP +128 +#define CL_FLT_MIN_10_EXP -37 +#define CL_FLT_MIN_EXP -125 +#define CL_FLT_RADIX 2 +#define CL_FLT_MAX 0x1.fffffep127f +#define CL_FLT_MIN 0x1.0p-126f +#define CL_FLT_EPSILON 0x1.0p-23f + +#define CL_DBL_DIG 15 +#define CL_DBL_MANT_DIG 53 +#define CL_DBL_MAX_10_EXP +308 +#define CL_DBL_MAX_EXP +1024 +#define CL_DBL_MIN_10_EXP -307 +#define CL_DBL_MIN_EXP -1021 +#define CL_DBL_RADIX 2 +#define CL_DBL_MAX 0x1.fffffffffffffp1023 +#define CL_DBL_MIN 0x1.0p-1022 +#define CL_DBL_EPSILON 0x1.0p-52 + +#define CL_M_E 2.718281828459045090796 +#define CL_M_LOG2E 1.442695040888963387005 +#define CL_M_LOG10E 0.434294481903251816668 +#define CL_M_LN2 0.693147180559945286227 +#define CL_M_LN10 2.302585092994045901094 +#define CL_M_PI 3.141592653589793115998 +#define CL_M_PI_2 1.570796326794896557999 +#define CL_M_PI_4 0.785398163397448278999 +#define CL_M_1_PI 0.318309886183790691216 +#define CL_M_2_PI 0.636619772367581382433 +#define CL_M_2_SQRTPI 1.128379167095512558561 +#define CL_M_SQRT2 1.414213562373095145475 +#define CL_M_SQRT1_2 0.707106781186547572737 + +#define CL_M_E_F 2.71828174591064f +#define CL_M_LOG2E_F 1.44269502162933f +#define CL_M_LOG10E_F 0.43429449200630f +#define CL_M_LN2_F 0.69314718246460f +#define CL_M_LN10_F 2.30258512496948f +#define CL_M_PI_F 3.14159274101257f +#define CL_M_PI_2_F 1.57079637050629f +#define CL_M_PI_4_F 0.78539818525314f +#define CL_M_1_PI_F 0.31830987334251f +#define CL_M_2_PI_F 0.63661974668503f +#define CL_M_2_SQRTPI_F 1.12837922573090f +#define CL_M_SQRT2_F 1.41421353816986f +#define CL_M_SQRT1_2_F 0.70710676908493f + +#if defined( __GNUC__ ) + #define CL_HUGE_VALF __builtin_huge_valf() + #define CL_HUGE_VAL __builtin_huge_val() + #define CL_NAN __builtin_nanf( "" ) +#else + #define CL_HUGE_VALF ((cl_float) 1e50) + #define CL_HUGE_VAL ((cl_double) 1e500) + float nanf( const char * ); + #define CL_NAN nanf( "" ) +#endif +#define CL_MAXFLOAT CL_FLT_MAX +#define CL_INFINITY CL_HUGE_VALF + +#endif + +#include + +/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ +typedef unsigned int cl_GLuint; +typedef int cl_GLint; +typedef unsigned int cl_GLenum; + +/* + * Vector types + * + * Note: OpenCL requires that all types be naturally aligned. + * This means that vector types must be naturally aligned. + * For example, a vector of four floats must be aligned to + * a 16 byte boundary (calculated as 4 * the natural 4-byte + * alignment of the float). The alignment qualifiers here + * will only function properly if your compiler supports them + * and if you don't actively work to defeat them. For example, + * in order for a cl_float4 to be 16 byte aligned in a struct, + * the start of the struct must itself be 16-byte aligned. + * + * Maintaining proper alignment is the user's responsibility. + */ + +/* Define basic vector types */ +#if defined( __VEC__ ) + #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ + typedef vector unsigned char __cl_uchar16; + typedef vector signed char __cl_char16; + typedef vector unsigned short __cl_ushort8; + typedef vector signed short __cl_short8; + typedef vector unsigned int __cl_uint4; + typedef vector signed int __cl_int4; + typedef vector float __cl_float4; + #define __CL_UCHAR16__ 1 + #define __CL_CHAR16__ 1 + #define __CL_USHORT8__ 1 + #define __CL_SHORT8__ 1 + #define __CL_UINT4__ 1 + #define __CL_INT4__ 1 + #define __CL_FLOAT4__ 1 +#endif + +#if defined( __SSE__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef float __cl_float4 __attribute__((vector_size(16))); + #else + typedef __m128 __cl_float4; + #endif + #define __CL_FLOAT4__ 1 +#endif + +#if defined( __SSE2__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); + typedef cl_char __cl_char16 __attribute__((vector_size(16))); + typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); + typedef cl_short __cl_short8 __attribute__((vector_size(16))); + typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); + typedef cl_int __cl_int4 __attribute__((vector_size(16))); + typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); + typedef cl_long __cl_long2 __attribute__((vector_size(16))); + typedef cl_double __cl_double2 __attribute__((vector_size(16))); + #else + typedef __m128i __cl_uchar16; + typedef __m128i __cl_char16; + typedef __m128i __cl_ushort8; + typedef __m128i __cl_short8; + typedef __m128i __cl_uint4; + typedef __m128i __cl_int4; + typedef __m128i __cl_ulong2; + typedef __m128i __cl_long2; + typedef __m128d __cl_double2; + #endif + #define __CL_UCHAR16__ 1 + #define __CL_CHAR16__ 1 + #define __CL_USHORT8__ 1 + #define __CL_SHORT8__ 1 + #define __CL_INT4__ 1 + #define __CL_UINT4__ 1 + #define __CL_ULONG2__ 1 + #define __CL_LONG2__ 1 + #define __CL_DOUBLE2__ 1 +#endif + +#if defined( __MMX__ ) + #include + #if defined( __GNUC__ ) + typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); + typedef cl_char __cl_char8 __attribute__((vector_size(8))); + typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); + typedef cl_short __cl_short4 __attribute__((vector_size(8))); + typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); + typedef cl_int __cl_int2 __attribute__((vector_size(8))); + typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); + typedef cl_long __cl_long1 __attribute__((vector_size(8))); + typedef cl_float __cl_float2 __attribute__((vector_size(8))); + #else + typedef __m64 __cl_uchar8; + typedef __m64 __cl_char8; + typedef __m64 __cl_ushort4; + typedef __m64 __cl_short4; + typedef __m64 __cl_uint2; + typedef __m64 __cl_int2; + typedef __m64 __cl_ulong1; + typedef __m64 __cl_long1; + typedef __m64 __cl_float2; + #endif + #define __CL_UCHAR8__ 1 + #define __CL_CHAR8__ 1 + #define __CL_USHORT4__ 1 + #define __CL_SHORT4__ 1 + #define __CL_INT2__ 1 + #define __CL_UINT2__ 1 + #define __CL_ULONG1__ 1 + #define __CL_LONG1__ 1 + #define __CL_FLOAT2__ 1 +#endif + +#if defined( __AVX__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef cl_float __cl_float8 __attribute__((vector_size(32))); + typedef cl_double __cl_double4 __attribute__((vector_size(32))); + #else + typedef __m256 __cl_float8; + typedef __m256d __cl_double4; + #endif + #define __CL_FLOAT8__ 1 + #define __CL_DOUBLE4__ 1 +#endif + +/* Define capabilities for anonymous struct members. */ +#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) +#define __CL_HAS_ANON_STRUCT__ 1 +#define __CL_ANON_STRUCT__ __extension__ +#elif defined( _WIN32) && (_MSC_VER >= 1500) + /* Microsoft Developer Studio 2008 supports anonymous structs, but + * complains by default. */ +#define __CL_HAS_ANON_STRUCT__ 1 +#define __CL_ANON_STRUCT__ + /* Disable warning C4201: nonstandard extension used : nameless + * struct/union */ +#pragma warning( push ) +#pragma warning( disable : 4201 ) +#else +#define __CL_HAS_ANON_STRUCT__ 0 +#define __CL_ANON_STRUCT__ +#endif + +/* Define alignment keys */ +#if defined( __GNUC__ ) + #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) +#elif defined( _WIN32) && (_MSC_VER) + /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ + /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ + /* #include */ + /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ + #define CL_ALIGNED(_x) +#else + #warning Need to implement some method to align data here + #define CL_ALIGNED(_x) +#endif + +/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ +#if __CL_HAS_ANON_STRUCT__ + /* .xyzw and .s0123...{f|F} are supported */ + #define CL_HAS_NAMED_VECTOR_FIELDS 1 + /* .hi and .lo are supported */ + #define CL_HAS_HI_LO_VECTOR_FIELDS 1 +#endif + +/* Define cl_vector types */ + +/* ---- cl_charn ---- */ +typedef union +{ + cl_char CL_ALIGNED(2) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2; +#endif +}cl_char2; + +typedef union +{ + cl_char CL_ALIGNED(4) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[2]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4; +#endif +}cl_char4; + +/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ +typedef cl_char4 cl_char3; + +typedef union +{ + cl_char CL_ALIGNED(8) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[4]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4[2]; +#endif +#if defined( __CL_CHAR8__ ) + __cl_char8 v8; +#endif +}cl_char8; + +typedef union +{ + cl_char CL_ALIGNED(16) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[8]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4[4]; +#endif +#if defined( __CL_CHAR8__ ) + __cl_char8 v8[2]; +#endif +#if defined( __CL_CHAR16__ ) + __cl_char16 v16; +#endif +}cl_char16; + + +/* ---- cl_ucharn ---- */ +typedef union +{ + cl_uchar CL_ALIGNED(2) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; +#endif +#if defined( __cl_uchar2__) + __cl_uchar2 v2; +#endif +}cl_uchar2; + +typedef union +{ + cl_uchar CL_ALIGNED(4) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[2]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4; +#endif +}cl_uchar4; + +/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ +typedef cl_uchar4 cl_uchar3; + +typedef union +{ + cl_uchar CL_ALIGNED(8) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[4]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4[2]; +#endif +#if defined( __CL_UCHAR8__ ) + __cl_uchar8 v8; +#endif +}cl_uchar8; + +typedef union +{ + cl_uchar CL_ALIGNED(16) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[8]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4[4]; +#endif +#if defined( __CL_UCHAR8__ ) + __cl_uchar8 v8[2]; +#endif +#if defined( __CL_UCHAR16__ ) + __cl_uchar16 v16; +#endif +}cl_uchar16; + + +/* ---- cl_shortn ---- */ +typedef union +{ + cl_short CL_ALIGNED(4) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2; +#endif +}cl_short2; + +typedef union +{ + cl_short CL_ALIGNED(8) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[2]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4; +#endif +}cl_short4; + +/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ +typedef cl_short4 cl_short3; + +typedef union +{ + cl_short CL_ALIGNED(16) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[4]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4[2]; +#endif +#if defined( __CL_SHORT8__ ) + __cl_short8 v8; +#endif +}cl_short8; + +typedef union +{ + cl_short CL_ALIGNED(32) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[8]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4[4]; +#endif +#if defined( __CL_SHORT8__ ) + __cl_short8 v8[2]; +#endif +#if defined( __CL_SHORT16__ ) + __cl_short16 v16; +#endif +}cl_short16; + + +/* ---- cl_ushortn ---- */ +typedef union +{ + cl_ushort CL_ALIGNED(4) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2; +#endif +}cl_ushort2; + +typedef union +{ + cl_ushort CL_ALIGNED(8) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[2]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4; +#endif +}cl_ushort4; + +/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ +typedef cl_ushort4 cl_ushort3; + +typedef union +{ + cl_ushort CL_ALIGNED(16) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[4]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4[2]; +#endif +#if defined( __CL_USHORT8__ ) + __cl_ushort8 v8; +#endif +}cl_ushort8; + +typedef union +{ + cl_ushort CL_ALIGNED(32) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[8]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4[4]; +#endif +#if defined( __CL_USHORT8__ ) + __cl_ushort8 v8[2]; +#endif +#if defined( __CL_USHORT16__ ) + __cl_ushort16 v16; +#endif +}cl_ushort16; + +/* ---- cl_intn ---- */ +typedef union +{ + cl_int CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2; +#endif +}cl_int2; + +typedef union +{ + cl_int CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[2]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4; +#endif +}cl_int4; + +/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ +typedef cl_int4 cl_int3; + +typedef union +{ + cl_int CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[4]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4[2]; +#endif +#if defined( __CL_INT8__ ) + __cl_int8 v8; +#endif +}cl_int8; + +typedef union +{ + cl_int CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[8]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4[4]; +#endif +#if defined( __CL_INT8__ ) + __cl_int8 v8[2]; +#endif +#if defined( __CL_INT16__ ) + __cl_int16 v16; +#endif +}cl_int16; + + +/* ---- cl_uintn ---- */ +typedef union +{ + cl_uint CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2; +#endif +}cl_uint2; + +typedef union +{ + cl_uint CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[2]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4; +#endif +}cl_uint4; + +/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ +typedef cl_uint4 cl_uint3; + +typedef union +{ + cl_uint CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[4]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4[2]; +#endif +#if defined( __CL_UINT8__ ) + __cl_uint8 v8; +#endif +}cl_uint8; + +typedef union +{ + cl_uint CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[8]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4[4]; +#endif +#if defined( __CL_UINT8__ ) + __cl_uint8 v8[2]; +#endif +#if defined( __CL_UINT16__ ) + __cl_uint16 v16; +#endif +}cl_uint16; + +/* ---- cl_longn ---- */ +typedef union +{ + cl_long CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2; +#endif +}cl_long2; + +typedef union +{ + cl_long CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[2]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4; +#endif +}cl_long4; + +/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ +typedef cl_long4 cl_long3; + +typedef union +{ + cl_long CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[4]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4[2]; +#endif +#if defined( __CL_LONG8__ ) + __cl_long8 v8; +#endif +}cl_long8; + +typedef union +{ + cl_long CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[8]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4[4]; +#endif +#if defined( __CL_LONG8__ ) + __cl_long8 v8[2]; +#endif +#if defined( __CL_LONG16__ ) + __cl_long16 v16; +#endif +}cl_long16; + + +/* ---- cl_ulongn ---- */ +typedef union +{ + cl_ulong CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2; +#endif +}cl_ulong2; + +typedef union +{ + cl_ulong CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[2]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4; +#endif +}cl_ulong4; + +/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ +typedef cl_ulong4 cl_ulong3; + +typedef union +{ + cl_ulong CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[4]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4[2]; +#endif +#if defined( __CL_ULONG8__ ) + __cl_ulong8 v8; +#endif +}cl_ulong8; + +typedef union +{ + cl_ulong CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[8]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4[4]; +#endif +#if defined( __CL_ULONG8__ ) + __cl_ulong8 v8[2]; +#endif +#if defined( __CL_ULONG16__ ) + __cl_ulong16 v16; +#endif +}cl_ulong16; + + +/* --- cl_floatn ---- */ + +typedef union +{ + cl_float CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2; +#endif +}cl_float2; + +typedef union +{ + cl_float CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[2]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4; +#endif +}cl_float4; + +/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ +typedef cl_float4 cl_float3; + +typedef union +{ + cl_float CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[4]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4[2]; +#endif +#if defined( __CL_FLOAT8__ ) + __cl_float8 v8; +#endif +}cl_float8; + +typedef union +{ + cl_float CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[8]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4[4]; +#endif +#if defined( __CL_FLOAT8__ ) + __cl_float8 v8[2]; +#endif +#if defined( __CL_FLOAT16__ ) + __cl_float16 v16; +#endif +}cl_float16; + +/* --- cl_doublen ---- */ + +typedef union +{ + cl_double CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2; +#endif +}cl_double2; + +typedef union +{ + cl_double CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[2]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4; +#endif +}cl_double4; + +/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ +typedef cl_double4 cl_double3; + +typedef union +{ + cl_double CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[4]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4[2]; +#endif +#if defined( __CL_DOUBLE8__ ) + __cl_double8 v8; +#endif +}cl_double8; + +typedef union +{ + cl_double CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[8]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4[4]; +#endif +#if defined( __CL_DOUBLE8__ ) + __cl_double8 v8[2]; +#endif +#if defined( __CL_DOUBLE16__ ) + __cl_double16 v16; +#endif +}cl_double16; + +/* Macro to facilitate debugging + * Usage: + * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. + * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" + * Each line thereafter of OpenCL C source must end with: \n\ + * The last line ends in "; + * + * Example: + * + * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ + * kernel void foo( int a, float * b ) \n\ + * { \n\ + * // my comment \n\ + * *b[ get_global_id(0)] = a; \n\ + * } \n\ + * "; + * + * This should correctly set up the line, (column) and file information for your source + * string so you can do source level debugging. + */ +#define __CL_STRINGIFY( _x ) # _x +#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) +#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" + +#ifdef __cplusplus +} +#endif + +#undef __CL_HAS_ANON_STRUCT__ +#undef __CL_ANON_STRUCT__ +#if defined( _WIN32) && (_MSC_VER >= 1500) +#pragma warning( pop ) +#endif + +#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h new file mode 100644 index 00000000000..9855cd75e7d --- /dev/null +++ b/third_party/opencl/include/CL/opencl.h @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_H +#define __OPENCL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + +#include +#include +#include +#include + +#else + +#include +#include +#include +#include + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_H */ + diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index b79d046fcaf..d4cfc672801 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -57,9 +57,11 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": + base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: + base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index f428e9972a8..5c2131d4bf8 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -58,6 +58,9 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ + opencl-headers \ + ocl-icd-libopencl1 \ + ocl-icd-opencl-dev \ portaudio19-dev \ qttools5-dev-tools \ libqt5svg5-dev \ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 698ab9885d9..136c4119f64 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,6 +6,11 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] +if arch == "Darwin": + base_frameworks.append('OpenCL') +else: + base_libs.append('OpenCL') + replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] if arch != "Darwin": diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 6abbc47935e..67ad2cc8cbc 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,6 +10,10 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements +- Install OpenCL Driver (Ubuntu) +``` +sudo apt install pocl-opencl-icd +``` ## Connect the hardware - Connect the camera first From db8cd9f411f08d75696b36c5fd0fa6b6cb0bfb2d Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sat, 7 Feb 2026 12:52:28 -0700 Subject: [PATCH 134/685] fix: route fetch metadata when first log isnt uploaded (#37114) * fix: route fetch metadata when first log isnt uploaded * default * simple --------- Co-authored-by: Trey Moen --- tools/clip/run.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 855ac6fea37..d338f097d66 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -208,7 +208,10 @@ def stop(self): def load_route_metadata(route): from openpilot.common.params import Params, UnknownKeyName - lr = LogReader(route.log_paths()[0]) + path = next((item for item in route.log_paths() if item), None) + if not path: + raise Exception('error getting route metadata: cannot find any uploaded logs') + lr = LogReader(path) init_data, car_params = lr.first('initData'), lr.first('carParams') params = Params() From 0ce28803eccb443a4f90eceaaf1037776366293a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 12:20:34 -0800 Subject: [PATCH 135/685] gitignore .context/ --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e2a30fb70ab..1fef0a12552 100644 --- a/.gitignore +++ b/.gitignore @@ -97,5 +97,6 @@ Pipfile .ionide .claude/ +.context/ PLAN.md TASK.md From bcb13a7229d6c583da7c6f0fdd52785c5cfa993c Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Sat, 7 Feb 2026 16:19:08 -0600 Subject: [PATCH 136/685] ui diff replay: remove unused frame_data list for individual frame display (#37117) Remove unused base64 encoding and simplify frame data handling in diff video report --- selfdrive/ui/tests/diff/diff.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/selfdrive/ui/tests/diff/diff.py b/selfdrive/ui/tests/diff/diff.py index be7af5438a7..a581f687477 100755 --- a/selfdrive/ui/tests/diff/diff.py +++ b/selfdrive/ui/tests/diff/diff.py @@ -3,7 +3,6 @@ import sys import subprocess import tempfile -import base64 import webbrowser import argparse from pathlib import Path @@ -25,12 +24,6 @@ def compare_frames(frame1_path, frame2_path): return result.returncode == 0 -def frame_to_data_url(frame_path): - with open(frame_path, 'rb') as f: - data = f.read() - return f"data:image/png;base64,{base64.b64encode(data).decode()}" - - def create_diff_video(video1, video2, output_path): """Create a diff video using ffmpeg blend filter with difference mode.""" print("Creating diff video...") @@ -60,20 +53,16 @@ def find_differences(video1, video2): print(f"Comparing {len(frames1)} frames...") different_frames = [] - frame_data = [] for i, (f1, f2) in enumerate(zip(frames1, frames2, strict=False)): is_different = not compare_frames(f1, f2) if is_different: different_frames.append(i) - if i < 10 or i >= len(frames1) - 10 or is_different: - frame_data.append({'index': i, 'different': is_different, 'frame1_url': frame_to_data_url(f1), 'frame2_url': frame_to_data_url(f2)}) - - return different_frames, frame_data, len(frames1) + return different_frames, len(frames1) -def generate_html_report(video1, video2, basedir, different_frames, frame_data, total_frames): +def generate_html_report(video1, video2, basedir, different_frames, total_frames): chunks = [] if different_frames: current_chunk = [different_frames[0]] @@ -177,14 +166,14 @@ def main(): diff_video_path = os.path.join(os.path.dirname(args.output), DIFF_OUT_DIR / "diff.mp4") create_diff_video(args.video1, args.video2, diff_video_path) - different_frames, frame_data, total_frames = find_differences(args.video1, args.video2) + different_frames, total_frames = find_differences(args.video1, args.video2) if different_frames is None: sys.exit(1) print() print("Generating HTML report...") - html = generate_html_report(args.video1, args.video2, args.basedir, different_frames, frame_data, total_frames) + html = generate_html_report(args.video1, args.video2, args.basedir, different_frames, total_frames) with open(DIFF_OUT_DIR / args.output, 'w') as f: f.write(html) From ac17c35cfe898681f7b5cf9e57b98c4a844d7459 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 15:18:00 -0800 Subject: [PATCH 137/685] bridge: move ZMQ handling over (#37118) --- cereal/SConscript | 2 +- cereal/messaging/bridge.cc | 8 +- cereal/messaging/bridge_zmq.cc | 170 ++++++++++++++++++++++++++++++++ cereal/messaging/bridge_zmq.h | 72 ++++++++++++++ cereal/messaging/msgq_to_zmq.cc | 10 +- cereal/messaging/msgq_to_zmq.h | 9 +- 6 files changed, 256 insertions(+), 15 deletions(-) create mode 100644 cereal/messaging/bridge_zmq.cc create mode 100644 cereal/messaging/bridge_zmq.h diff --git a/cereal/SConscript b/cereal/SConscript index a58a9490ce6..73dc61844b3 100644 --- a/cereal/SConscript +++ b/cereal/SConscript @@ -13,7 +13,7 @@ cereal = env.Library('cereal', [f'gen/cpp/{s}.c++' for s in schema_files]) # Build messaging services_h = env.Command(['services.h'], ['services.py'], 'python3 ' + cereal_dir.path + '/services.py > $TARGET') -env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc'], LIBS=[msgq, common, 'pthread']) +env.Program('messaging/bridge', ['messaging/bridge.cc', 'messaging/msgq_to_zmq.cc', 'messaging/bridge_zmq.cc'], LIBS=[msgq, common, 'pthread']) socketmaster = env.Library('socketmaster', ['messaging/socketmaster.cc']) diff --git a/cereal/messaging/bridge.cc b/cereal/messaging/bridge.cc index fb92c575c94..2eb3de32c0d 100644 --- a/cereal/messaging/bridge.cc +++ b/cereal/messaging/bridge.cc @@ -25,14 +25,14 @@ void msgq_to_zmq(const std::vector &endpoints, const std::string &i } void zmq_to_msgq(const std::vector &endpoints, const std::string &ip) { - auto poller = std::make_unique(); + auto poller = std::make_unique(); auto pub_context = std::make_unique(); - auto sub_context = std::make_unique(); - std::map sub2pub; + auto sub_context = std::make_unique(); + std::map sub2pub; for (auto endpoint : endpoints) { auto pub_sock = new MSGQPubSocket(); - auto sub_sock = new ZMQSubSocket(); + auto sub_sock = new BridgeZmqSubSocket(); size_t queue_size = services.at(endpoint).queue_size; pub_sock->connect(pub_context.get(), endpoint, true, queue_size); sub_sock->connect(sub_context.get(), endpoint, ip, false); diff --git a/cereal/messaging/bridge_zmq.cc b/cereal/messaging/bridge_zmq.cc new file mode 100644 index 00000000000..5c56673b472 --- /dev/null +++ b/cereal/messaging/bridge_zmq.cc @@ -0,0 +1,170 @@ +#include "cereal/messaging/bridge_zmq.h" + +#include +#include +#include + +static size_t fnv1a_hash(const std::string &str) { + const size_t fnv_prime = 0x100000001b3; + size_t hash_value = 0xcbf29ce484222325; + for (char c : str) { + hash_value ^= (unsigned char)c; + hash_value *= fnv_prime; + } + return hash_value; +} + +// FIXME: This is a hack to get the port number from the socket name, might have collisions. +static int get_port(std::string endpoint) { + size_t hash_value = fnv1a_hash(endpoint); + int start_port = 8023; + int max_port = 65535; + return start_port + (hash_value % (max_port - start_port)); +} + +BridgeZmqContext::BridgeZmqContext() { + context = zmq_ctx_new(); +} + +BridgeZmqContext::~BridgeZmqContext() { + if (context != nullptr) { + zmq_ctx_term(context); + } +} + +void BridgeZmqMessage::init(size_t sz) { + size = sz; + data = new char[size]; +} + +void BridgeZmqMessage::init(char *d, size_t sz) { + size = sz; + data = new char[size]; + memcpy(data, d, size); +} + +void BridgeZmqMessage::close() { + if (size > 0) { + delete[] data; + } + data = nullptr; + size = 0; +} + +BridgeZmqMessage::~BridgeZmqMessage() { + close(); +} + +int BridgeZmqSubSocket::connect(BridgeZmqContext *context, std::string endpoint, std::string address, bool conflate, bool check_endpoint) { + sock = zmq_socket(context->getRawContext(), ZMQ_SUB); + if (sock == nullptr) { + return -1; + } + + zmq_setsockopt(sock, ZMQ_SUBSCRIBE, "", 0); + + if (conflate) { + int arg = 1; + zmq_setsockopt(sock, ZMQ_CONFLATE, &arg, sizeof(int)); + } + + int reconnect_ivl = 500; + zmq_setsockopt(sock, ZMQ_RECONNECT_IVL_MAX, &reconnect_ivl, sizeof(reconnect_ivl)); + + full_endpoint = "tcp://" + address + ":"; + if (check_endpoint) { + full_endpoint += std::to_string(get_port(endpoint)); + } else { + full_endpoint += endpoint; + } + + return zmq_connect(sock, full_endpoint.c_str()); +} + +void BridgeZmqSubSocket::setTimeout(int timeout) { + zmq_setsockopt(sock, ZMQ_RCVTIMEO, &timeout, sizeof(int)); +} + +Message *BridgeZmqSubSocket::receive(bool non_blocking) { + zmq_msg_t msg; + assert(zmq_msg_init(&msg) == 0); + + int flags = non_blocking ? ZMQ_DONTWAIT : 0; + int rc = zmq_msg_recv(&msg, sock, flags); + + Message *ret = nullptr; + if (rc >= 0) { + ret = new BridgeZmqMessage; + ret->init((char *)zmq_msg_data(&msg), zmq_msg_size(&msg)); + } + + zmq_msg_close(&msg); + return ret; +} + +BridgeZmqSubSocket::~BridgeZmqSubSocket() { + if (sock != nullptr) { + zmq_close(sock); + } +} + +int BridgeZmqPubSocket::connect(BridgeZmqContext *context, std::string endpoint, bool check_endpoint) { + sock = zmq_socket(context->getRawContext(), ZMQ_PUB); + if (sock == nullptr) { + return -1; + } + + full_endpoint = "tcp://*:"; + if (check_endpoint) { + full_endpoint += std::to_string(get_port(endpoint)); + } else { + full_endpoint += endpoint; + } + + // ZMQ pub sockets cannot be shared between processes, so we need to ensure pid stays the same. + pid = getpid(); + + return zmq_bind(sock, full_endpoint.c_str()); +} + +int BridgeZmqPubSocket::sendMessage(Message *message) { + assert(pid == getpid()); + return zmq_send(sock, message->getData(), message->getSize(), ZMQ_DONTWAIT); +} + +int BridgeZmqPubSocket::send(char *data, size_t size) { + assert(pid == getpid()); + return zmq_send(sock, data, size, ZMQ_DONTWAIT); +} + +BridgeZmqPubSocket::~BridgeZmqPubSocket() { + if (sock != nullptr) { + zmq_close(sock); + } +} + +void BridgeZmqPoller::registerSocket(BridgeZmqSubSocket *socket) { + assert(num_polls + 1 < (sizeof(polls) / sizeof(polls[0]))); + polls[num_polls].socket = socket->getRawSocket(); + polls[num_polls].events = ZMQ_POLLIN; + + sockets.push_back(socket); + num_polls++; +} + +std::vector BridgeZmqPoller::poll(int timeout) { + std::vector ret; + + int rc = zmq_poll(polls, num_polls, timeout); + if (rc < 0) { + return ret; + } + + for (size_t i = 0; i < num_polls; i++) { + if (polls[i].revents) { + ret.push_back(sockets[i]); + } + } + + return ret; +} diff --git a/cereal/messaging/bridge_zmq.h b/cereal/messaging/bridge_zmq.h new file mode 100644 index 00000000000..eab48a91e8c --- /dev/null +++ b/cereal/messaging/bridge_zmq.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +#include + +#include "msgq/ipc.h" + +class BridgeZmqContext { +public: + BridgeZmqContext(); + void *getRawContext() { return context; } + ~BridgeZmqContext(); + +private: + void *context = nullptr; +}; + +class BridgeZmqMessage : public Message { +public: + void init(size_t size) override; + void init(char *data, size_t size) override; + void close() override; + size_t getSize() override { return size; } + char *getData() override { return data; } + ~BridgeZmqMessage() override; + +private: + char *data = nullptr; + size_t size = 0; +}; + +class BridgeZmqSubSocket { +public: + int connect(BridgeZmqContext *context, std::string endpoint, std::string address, bool conflate = false, bool check_endpoint = true); + void setTimeout(int timeout); + Message *receive(bool non_blocking = false); + void *getRawSocket() { return sock; } + ~BridgeZmqSubSocket(); + +private: + void *sock = nullptr; + std::string full_endpoint; +}; + +class BridgeZmqPubSocket { +public: + int connect(BridgeZmqContext *context, std::string endpoint, bool check_endpoint = true); + int sendMessage(Message *message); + int send(char *data, size_t size); + void *getRawSocket() { return sock; } + ~BridgeZmqPubSocket(); + +private: + void *sock = nullptr; + std::string full_endpoint; + int pid = -1; +}; + +class BridgeZmqPoller { +public: + void registerSocket(BridgeZmqSubSocket *socket); + std::vector poll(int timeout); + +private: + static constexpr size_t MAX_BRIDGE_ZMQ_POLLERS = 128; + std::vector sockets; + zmq_pollitem_t polls[MAX_BRIDGE_ZMQ_POLLERS] = {}; + size_t num_polls = 0; +}; diff --git a/cereal/messaging/msgq_to_zmq.cc b/cereal/messaging/msgq_to_zmq.cc index 7f8c738d4d2..930b617e7b0 100644 --- a/cereal/messaging/msgq_to_zmq.cc +++ b/cereal/messaging/msgq_to_zmq.cc @@ -22,14 +22,14 @@ static std::string recv_zmq_msg(void *sock) { } void MsgqToZmq::run(const std::vector &endpoints, const std::string &ip) { - zmq_context = std::make_unique(); + zmq_context = std::make_unique(); msgq_context = std::make_unique(); // Create ZMQPubSockets for each endpoint for (const auto &endpoint : endpoints) { auto &socket_pair = socket_pairs.emplace_back(); socket_pair.endpoint = endpoint; - socket_pair.pub_sock = std::make_unique(); + socket_pair.pub_sock = std::make_unique(); int ret = socket_pair.pub_sock->connect(zmq_context.get(), endpoint); if (ret != 0) { printf("Failed to create ZMQ publisher for [%s]: %s\n", endpoint.c_str(), zmq_strerror(zmq_errno())); @@ -49,7 +49,7 @@ void MsgqToZmq::run(const std::vector &endpoints, const std::string for (auto sub_sock : msgq_poller->poll(100)) { // Process messages for each socket - ZMQPubSocket *pub_sock = sub2pub.at(sub_sock); + BridgeZmqPubSocket *pub_sock = sub2pub.at(sub_sock); for (int i = 0; i < MAX_MESSAGES_PER_SOCKET; ++i) { auto msg = std::unique_ptr(sub_sock->receive(true)); if (!msg) break; @@ -72,7 +72,7 @@ void MsgqToZmq::zmqMonitorThread() { // Set up ZMQ monitor for each pub socket for (int i = 0; i < socket_pairs.size(); ++i) { std::string addr = "inproc://op-bridge-monitor-" + std::to_string(i); - zmq_socket_monitor(socket_pairs[i].pub_sock->sock, addr.c_str(), ZMQ_EVENT_ACCEPTED | ZMQ_EVENT_DISCONNECTED); + zmq_socket_monitor(socket_pairs[i].pub_sock->getRawSocket(), addr.c_str(), ZMQ_EVENT_ACCEPTED | ZMQ_EVENT_DISCONNECTED); void *monitor_socket = zmq_socket(zmq_context->getRawContext(), ZMQ_PAIR); zmq_connect(monitor_socket, addr.c_str()); @@ -130,7 +130,7 @@ void MsgqToZmq::zmqMonitorThread() { // Clean up monitor sockets for (int i = 0; i < pollitems.size(); ++i) { - zmq_socket_monitor(socket_pairs[i].pub_sock->sock, nullptr, 0); + zmq_socket_monitor(socket_pairs[i].pub_sock->getRawSocket(), nullptr, 0); zmq_close(pollitems[i].socket); } cv.notify_one(); diff --git a/cereal/messaging/msgq_to_zmq.h b/cereal/messaging/msgq_to_zmq.h index ebdbe5df690..ac92631fe7c 100644 --- a/cereal/messaging/msgq_to_zmq.h +++ b/cereal/messaging/msgq_to_zmq.h @@ -7,9 +7,8 @@ #include #include -#define private public #include "msgq/impl_msgq.h" -#include "msgq/impl_zmq.h" +#include "cereal/messaging/bridge_zmq.h" class MsgqToZmq { public: @@ -22,16 +21,16 @@ class MsgqToZmq { struct SocketPair { std::string endpoint; - std::unique_ptr pub_sock; + std::unique_ptr pub_sock; std::unique_ptr sub_sock; int connected_clients = 0; }; std::unique_ptr msgq_context; - std::unique_ptr zmq_context; + std::unique_ptr zmq_context; std::mutex mutex; std::condition_variable cv; std::unique_ptr msgq_poller; - std::map sub2pub; + std::map sub2pub; std::vector socket_pairs; }; From 55a31d76571afca6a07b8efdc945d009c31d19ae Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 18:27:16 -0800 Subject: [PATCH 138/685] replace tabulate with simple helper (#37122) --- common/utils.py | 86 +++ pyproject.toml | 1 - selfdrive/test/process_replay/model_replay.py | 2 +- selfdrive/test/test_onroad.py | 2 +- system/hardware/tici/tests/test_power_draw.py | 2 +- .../longitudinal_maneuvers/generate_report.py | 2 +- uv.lock | 515 +----------------- 7 files changed, 91 insertions(+), 519 deletions(-) diff --git a/common/utils.py b/common/utils.py index ccc6719f5f2..faaa96ecbcd 100644 --- a/common/utils.py +++ b/common/utils.py @@ -167,6 +167,92 @@ def managed_proc(cmd: list[str], env: dict[str, str]): proc.kill() +def tabulate(tabular_data, headers=(), tablefmt="simple", floatfmt="g", stralign="left", numalign=None): + rows = [list(row) for row in tabular_data] + + def fmt(val): + if isinstance(val, str): + return val + if isinstance(val, (bool, int)): + return str(val) + try: + return format(val, floatfmt) + except (TypeError, ValueError): + return str(val) + + formatted = [[fmt(c) for c in row] for row in rows] + hdrs = [str(h) for h in headers] if headers else None + + ncols = max((len(r) for r in formatted), default=0) + if hdrs: + ncols = max(ncols, len(hdrs)) + if ncols == 0: + return "" + + for r in formatted: + r.extend([""] * (ncols - len(r))) + if hdrs: + hdrs.extend([""] * (ncols - len(hdrs))) + + widths = [0] * ncols + if hdrs: + for i in range(ncols): + widths[i] = len(hdrs[i]) + for row in formatted: + for i in range(ncols): + widths[i] = max(widths[i], max(len(ln) for ln in row[i].split('\n'))) + + def _align(s, w): + if stralign == "center": + return s.center(w) + return s.ljust(w) + + if tablefmt == "html": + parts = [""] + if hdrs: + parts.append("") + parts.append("" + "".join(f"" for h in hdrs) + "") + parts.append("") + parts.append("") + for row in formatted: + parts.append("" + "".join(f"" for c in row) + "") + parts.append("") + parts.append("
{h}
{c}
") + return "\n".join(parts) + + if tablefmt == "simple_grid": + def _sep(left, mid, right): + return left + mid.join("\u2500" * (w + 2) for w in widths) + right + + top, mid_sep, bot = _sep("\u250c", "\u252c", "\u2510"), _sep("\u251c", "\u253c", "\u2524"), _sep("\u2514", "\u2534", "\u2518") + + def _fmt_row(cells): + split = [c.split('\n') for c in cells] + nlines = max(len(s) for s in split) + for s in split: + s.extend([""] * (nlines - len(s))) + return ["\u2502" + "\u2502".join(f" {_align(split[i][li], widths[i])} " for i in range(ncols)) + "\u2502" for li in range(nlines)] + + lines = [top] + if hdrs: + lines.extend(_fmt_row(hdrs)) + lines.append(mid_sep) + for ri, row in enumerate(formatted): + lines.extend(_fmt_row(row)) + lines.append(mid_sep if ri < len(formatted) - 1 else bot) + return "\n".join(lines) + + # simple + gap = " " + lines = [] + if hdrs: + lines.append(gap.join(h.ljust(w) for h, w in zip(hdrs, widths, strict=True))) + lines.append(gap.join("-" * w for w in widths)) + for row in formatted: + lines.append(gap.join(_align(row[i], widths[i]) for i in range(ncols))) + return "\n".join(lines) + + def retry(attempts=3, delay=1.0, ignore_failure=False): def decorator(func): @functools.wraps(func) diff --git a/pyproject.toml b/pyproject.toml index 2400364c706..8b0f4004d68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,6 @@ dev = [ "parameterized >=0.8, <0.9", "pyautogui", "pywinctl", - "tabulate", ] tools = [ diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 9ba599bac9c..d35474f3732 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -9,7 +9,7 @@ import matplotlib.pyplot as plt import numpy as np -from tabulate import tabulate +from openpilot.common.utils import tabulate from openpilot.common.git import get_commit from openpilot.system.hardware import PC diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index f57751c0674..33e8685a384 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -8,7 +8,7 @@ import numpy as np from collections import Counter, defaultdict from pathlib import Path -from tabulate import tabulate +from openpilot.common.utils import tabulate from cereal import log import cereal.messaging as messaging diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index 4fbde816736..a92e4c8de8a 100644 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -3,7 +3,7 @@ import time import numpy as np from dataclasses import dataclass -from tabulate import tabulate +from openpilot.common.utils import tabulate import cereal.messaging as messaging from cereal.services import SERVICE_LIST diff --git a/tools/longitudinal_maneuvers/generate_report.py b/tools/longitudinal_maneuvers/generate_report.py index 8c16e30d56a..32bdb5b1c4c 100755 --- a/tools/longitudinal_maneuvers/generate_report.py +++ b/tools/longitudinal_maneuvers/generate_report.py @@ -9,7 +9,7 @@ from collections import defaultdict from pathlib import Path import matplotlib.pyplot as plt -from tabulate import tabulate +from openpilot.common.utils import tabulate from openpilot.tools.lib.logreader import LogReader from openpilot.system.hardware.hw import Paths diff --git a/uv.lock b/uv.lock index 054a67cc9a2..81e2d913602 100644 --- a/uv.lock +++ b/uv.lock @@ -1,10 +1,6 @@ version = 1 revision = 3 -requires-python = ">=3.11, <3.13" -resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version < '3.12'", -] +requires-python = ">=3.12.3, <3.13" [[package]] name = "aiohappyeyeballs" @@ -30,23 +26,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/4c/a164164834f03924d9a29dc3acd9e7ee58f95857e0b467f6d04298594ebb/aiohttp-3.13.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b6073099fb654e0a068ae678b10feff95c5cae95bbfcbfa7af669d361a8aa6b", size = 746051, upload-time = "2026-01-03T17:29:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/82/71/d5c31390d18d4f58115037c432b7e0348c60f6f53b727cad33172144a112/aiohttp-3.13.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cb93e166e6c28716c8c6aeb5f99dfb6d5ccf482d29fe9bf9a794110e6d0ab64", size = 499234, upload-time = "2026-01-03T17:29:44.822Z" }, - { url = "https://files.pythonhosted.org/packages/0e/c9/741f8ac91e14b1d2e7100690425a5b2b919a87a5075406582991fb7de920/aiohttp-3.13.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28e027cf2f6b641693a09f631759b4d9ce9165099d2b5d92af9bd4e197690eea", size = 494979, upload-time = "2026-01-03T17:29:46.405Z" }, - { url = "https://files.pythonhosted.org/packages/75/b5/31d4d2e802dfd59f74ed47eba48869c1c21552c586d5e81a9d0d5c2ad640/aiohttp-3.13.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3b61b7169ababd7802f9568ed96142616a9118dd2be0d1866e920e77ec8fa92a", size = 1748297, upload-time = "2026-01-03T17:29:48.083Z" }, - { url = "https://files.pythonhosted.org/packages/1a/3e/eefad0ad42959f226bb79664826883f2687d602a9ae2941a18e0484a74d3/aiohttp-3.13.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:80dd4c21b0f6237676449c6baaa1039abae86b91636b6c91a7f8e61c87f89540", size = 1707172, upload-time = "2026-01-03T17:29:49.648Z" }, - { url = "https://files.pythonhosted.org/packages/c5/3a/54a64299fac2891c346cdcf2aa6803f994a2e4beeaf2e5a09dcc54acc842/aiohttp-3.13.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:65d2ccb7eabee90ce0503c17716fc77226be026dcc3e65cce859a30db715025b", size = 1805405, upload-time = "2026-01-03T17:29:51.244Z" }, - { url = "https://files.pythonhosted.org/packages/6c/70/ddc1b7169cf64075e864f64595a14b147a895a868394a48f6a8031979038/aiohttp-3.13.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5b179331a481cb5529fca8b432d8d3c7001cb217513c94cd72d668d1248688a3", size = 1899449, upload-time = "2026-01-03T17:29:53.938Z" }, - { url = "https://files.pythonhosted.org/packages/a1/7e/6815aab7d3a56610891c76ef79095677b8b5be6646aaf00f69b221765021/aiohttp-3.13.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9d4c940f02f49483b18b079d1c27ab948721852b281f8b015c058100e9421dd1", size = 1748444, upload-time = "2026-01-03T17:29:55.484Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f2/073b145c4100da5511f457dc0f7558e99b2987cf72600d42b559db856fbc/aiohttp-3.13.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f9444f105664c4ce47a2a7171a2418bce5b7bae45fb610f4e2c36045d85911d3", size = 1606038, upload-time = "2026-01-03T17:29:57.179Z" }, - { url = "https://files.pythonhosted.org/packages/0a/c1/778d011920cae03ae01424ec202c513dc69243cf2db303965615b81deeea/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:694976222c711d1d00ba131904beb60534f93966562f64440d0c9d41b8cdb440", size = 1724156, upload-time = "2026-01-03T17:29:58.914Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cb/3419eabf4ec1e9ec6f242c32b689248365a1cf621891f6f0386632525494/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f33ed1a2bf1997a36661874b017f5c4b760f41266341af36febaf271d179f6d7", size = 1722340, upload-time = "2026-01-03T17:30:01.962Z" }, - { url = "https://files.pythonhosted.org/packages/7a/e5/76cf77bdbc435bf233c1f114edad39ed4177ccbfab7c329482b179cff4f4/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e636b3c5f61da31a92bf0d91da83e58fdfa96f178ba682f11d24f31944cdd28c", size = 1783041, upload-time = "2026-01-03T17:30:03.609Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d4/dd1ca234c794fd29c057ce8c0566b8ef7fd6a51069de5f06fa84b9a1971c/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:5d2d94f1f5fcbe40838ac51a6ab5704a6f9ea42e72ceda48de5e6b898521da51", size = 1596024, upload-time = "2026-01-03T17:30:05.132Z" }, - { url = "https://files.pythonhosted.org/packages/55/58/4345b5f26661a6180afa686c473620c30a66afdf120ed3dd545bbc809e85/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2be0e9ccf23e8a94f6f0650ce06042cefc6ac703d0d7ab6c7a917289f2539ad4", size = 1804590, upload-time = "2026-01-03T17:30:07.135Z" }, - { url = "https://files.pythonhosted.org/packages/7b/06/05950619af6c2df7e0a431d889ba2813c9f0129cec76f663e547a5ad56f2/aiohttp-3.13.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9af5e68ee47d6534d36791bbe9b646d2a7c7deb6fc24d7943628edfbb3581f29", size = 1740355, upload-time = "2026-01-03T17:30:09.083Z" }, - { url = "https://files.pythonhosted.org/packages/3e/80/958f16de79ba0422d7c1e284b2abd0c84bc03394fbe631d0a39ffa10e1eb/aiohttp-3.13.3-cp311-cp311-win32.whl", hash = "sha256:a2212ad43c0833a873d0fb3c63fa1bacedd4cf6af2fee62bf4b739ceec3ab239", size = 433701, upload-time = "2026-01-03T17:30:10.869Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f2/27cdf04c9851712d6c1b99df6821a6623c3c9e55956d4b1e318c337b5a48/aiohttp-3.13.3-cp311-cp311-win_amd64.whl", hash = "sha256:642f752c3eb117b105acbd87e2c143de710987e09860d674e068c4c2c441034f", size = 457678, upload-time = "2026-01-03T17:30:12.719Z" }, { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, @@ -132,12 +111,6 @@ version = "13.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/0c/9d/486d31e76784cc0ad943f420c5e05867263b32b37e2f4b0f7f22fdc1ca3a/av-13.1.0.tar.gz", hash = "sha256:d3da736c55847d8596eb8c26c60e036f193001db3bc5c10da8665622d906c17e", size = 3957908, upload-time = "2024-10-06T04:54:57.507Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/54/c4227080c9700384db90072ace70d89b6a288b3748bd2ec0e32580a49e7f/av-13.1.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:867385e6701464a5c95903e24d2e0df1c7e0dbf211ed91d0ce639cd687373e10", size = 24255112, upload-time = "2024-10-06T04:52:48.49Z" }, - { url = "https://files.pythonhosted.org/packages/32/4a/eb9348231655ca99b200b380f4edbceff7358c927a285badcc84b18fb1c9/av-13.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cb7a3f319401a46b0017771268ff4928501e77cf00b1a2aa0721e20b2fd1146e", size = 19467930, upload-time = "2024-10-06T04:52:52.118Z" }, - { url = "https://files.pythonhosted.org/packages/14/c7/48c80252bdbc3a75a54dd205a7fab8f613914009b9e5416202757208e040/av-13.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad904f860147bceaca65b0d3174a8153f35c570d465161d210f1879970b15559", size = 32207671, upload-time = "2024-10-06T04:52:55.82Z" }, - { url = "https://files.pythonhosted.org/packages/f9/66/3332c7fa8c43b65680a94f279ea3e832b5500de3a1392bac6112881e984b/av-13.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a906e017b29d0eb80d9ccf7a98d19268122da792dbb68eb741cfebba156e6aed", size = 31520911, upload-time = "2024-10-06T04:52:59.231Z" }, - { url = "https://files.pythonhosted.org/packages/e5/bb/2e03acb9b27591d97f700a3a6c27cfd1bc53fa148177747eda8a70cca1e9/av-13.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ce894d7847897da7be63277a0875bd93c51327134ac226c67978de014c7979f", size = 34048399, upload-time = "2024-10-06T04:53:03.934Z" }, - { url = "https://files.pythonhosted.org/packages/85/44/527aa3b65947d42cfe829326026edf0cd1a8c459390076034be275616c36/av-13.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:384bcdb5fc3238a263a5a25cc9efc690859fa4148cc4b07e00fae927178db22a", size = 25779569, upload-time = "2024-10-06T04:53:07.582Z" }, { url = "https://files.pythonhosted.org/packages/9b/aa/4bdd8ce59173574fc6e0c282c71ee6f96fca82643d97bf172bc4cb5a5674/av-13.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:261dbc3f4b55f4f8f3375b10b2258fca7f2ab7a6365c01bc65e77a0d5327a195", size = 24268674, upload-time = "2024-10-06T04:53:11.251Z" }, { url = "https://files.pythonhosted.org/packages/17/b4/b267dd5bad99eed49ec6731827c6bcb5ab03864bf732a7ebb81e3df79911/av-13.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83d259ef86b9054eb914bc7c6a7f6092a6d75cb939295e70ee979cfd92a67b99", size = 19475617, upload-time = "2024-10-06T04:53:13.832Z" }, { url = "https://files.pythonhosted.org/packages/68/32/4209e51f54d7b54a1feb576d309c671ed1ff437b54fcc4ec68c239199e0a/av-13.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b4d3ca159eceab97e3c0fb08fe756520fb95508417f76e48198fda2a5b0806", size = 32468873, upload-time = "2024-10-06T04:53:17.639Z" }, @@ -199,12 +172,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/92/62/1e98662024915ecb09c6894c26a3f497f4afa66570af3f53db4651fc45f1/casadi-3.7.2.tar.gz", hash = "sha256:b4d7bd8acdc4180306903ae1c9eddaf41be2a3ae2fa7154c57174ae64acdc60d", size = 6053600, upload-time = "2025-09-10T10:05:49.521Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/01/d5e3058775ec8e24a01eb74d36099493b872536ef9e39f1e49624b977778/casadi-3.7.2-cp311-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:f43b0562d05a5e6e81f1885fc4ae426c382e36eebfd8d27f1baff6052178a9b0", size = 47115880, upload-time = "2025-09-10T07:52:24.399Z" }, - { url = "https://files.pythonhosted.org/packages/0e/cf/4af27e010d599a5419129d34fdde41637029a1cca2a40bef0965d6d52228/casadi-3.7.2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:70add3334b437b60a9bc0f864d094350f1a4fcbf9e8bafec870b61aed64674df", size = 42293337, upload-time = "2025-09-10T08:03:32.556Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4c/d1a50cc840103e00effcbaf8e911b6b3fb6ba2c8f4025466f524854968ed/casadi-3.7.2-cp311-none-manylinux2014_aarch64.whl", hash = "sha256:392d3367a4b33cf223013dad8122a0e549da40b1702a5375f82f85b563e5c0cf", size = 47277175, upload-time = "2025-09-10T08:04:08.811Z" }, - { url = "https://files.pythonhosted.org/packages/be/29/6e5714d124e6ddafbccc3ed774ca603081caa1175c7f0e1c52484184dfb3/casadi-3.7.2-cp311-none-manylinux2014_i686.whl", hash = "sha256:2ce09e0ced6df33048dccd582b5cfa2c9ff5193b12858b2584078afc17761905", size = 72438460, upload-time = "2025-09-10T08:05:02.769Z" }, - { url = "https://files.pythonhosted.org/packages/23/32/ac1f3999273aa4aae48516f6f4b7b267e0cc70d8527866989798cb81312f/casadi-3.7.2-cp311-none-manylinux2014_x86_64.whl", hash = "sha256:5086799a46d10ba884b72fd02c21be09dae52cbc189272354a5d424791b55f37", size = 75574474, upload-time = "2025-09-10T08:06:00.709Z" }, - { url = "https://files.pythonhosted.org/packages/68/78/7fd10709504c1757f70db3893870a891fcb9f1ec9f05e8ef2e3f3b9d7e2f/casadi-3.7.2-cp311-none-win_amd64.whl", hash = "sha256:72aa5727417d781ed216f16b5e93c6ddca5db27d83b0015a729e8ad570cdc465", size = 50994144, upload-time = "2025-09-10T08:06:42.384Z" }, { url = "https://files.pythonhosted.org/packages/65/c8/689d085447b1966f42bdb8aa4fbebef49a09697dbee32ab02a865c17ac1b/casadi-3.7.2-cp312-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:309ea41a69c9230390d349b0dd899c6a19504d1904c0756bef463e47fb5c8f9a", size = 47116756, upload-time = "2025-09-10T07:53:00.931Z" }, { url = "https://files.pythonhosted.org/packages/1e/c0/3c4704394a6fd4dfb2123a4fd71ba64a001f340670a3eba45be7a19ac736/casadi-3.7.2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6033381234db810b2247d16c6352e679a009ec4365d04008fc768866e011ed58", size = 42293718, upload-time = "2025-09-10T08:07:16.415Z" }, { url = "https://files.pythonhosted.org/packages/f3/24/4cf05469ddf8544da5e92f359f96d716a97e7482999f085a632bc4ef344a/casadi-3.7.2-cp312-none-manylinux2014_aarch64.whl", hash = "sha256:732f2804d0766454bb75596339e4f2da6662ffb669621da0f630ed4af9e83d6a", size = 47276175, upload-time = "2025-09-10T08:08:09.29Z" }, @@ -231,19 +198,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/4a/3dfd5f7850cbf0d06dc84ba9aa00db766b52ca38d8b86e3a38314d52498c/cffi-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:b4c854ef3adc177950a8dfc81a86f5115d2abd545751a304c5bcf2c2c7283cfe", size = 184344, upload-time = "2025-09-08T23:22:26.456Z" }, - { url = "https://files.pythonhosted.org/packages/4f/8b/f0e4c441227ba756aafbe78f117485b25bb26b1c059d01f137fa6d14896b/cffi-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2de9a304e27f7596cd03d16f1b7c72219bd944e99cc52b84d0145aefb07cbd3c", size = 180560, upload-time = "2025-09-08T23:22:28.197Z" }, - { url = "https://files.pythonhosted.org/packages/b1/b7/1200d354378ef52ec227395d95c2576330fd22a869f7a70e88e1447eb234/cffi-2.0.0-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:baf5215e0ab74c16e2dd324e8ec067ef59e41125d3eade2b863d294fd5035c92", size = 209613, upload-time = "2025-09-08T23:22:29.475Z" }, - { url = "https://files.pythonhosted.org/packages/b8/56/6033f5e86e8cc9bb629f0077ba71679508bdf54a9a5e112a3c0b91870332/cffi-2.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:730cacb21e1bdff3ce90babf007d0a0917cc3e6492f336c2f0134101e0944f93", size = 216476, upload-time = "2025-09-08T23:22:31.063Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/55fecd70f7ece178db2f26128ec41430d8720f2d12ca97bf8f0a628207d5/cffi-2.0.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6824f87845e3396029f3820c206e459ccc91760e8fa24422f8b0c3d1731cbec5", size = 203374, upload-time = "2025-09-08T23:22:32.507Z" }, - { url = "https://files.pythonhosted.org/packages/84/ef/a7b77c8bdc0f77adc3b46888f1ad54be8f3b7821697a7b89126e829e676a/cffi-2.0.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:9de40a7b0323d889cf8d23d1ef214f565ab154443c42737dfe52ff82cf857664", size = 202597, upload-time = "2025-09-08T23:22:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8941aaadaf67246224cee8c3803777eed332a19d909b47e29c9842ef1e79ac26", size = 215574, upload-time = "2025-09-08T23:22:35.443Z" }, - { url = "https://files.pythonhosted.org/packages/44/64/58f6255b62b101093d5df22dcb752596066c7e89dd725e0afaed242a61be/cffi-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a05d0c237b3349096d3981b727493e22147f934b20f6f125a3eba8f994bec4a9", size = 218971, upload-time = "2025-09-08T23:22:36.805Z" }, - { url = "https://files.pythonhosted.org/packages/ab/49/fa72cebe2fd8a55fbe14956f9970fe8eb1ac59e5df042f603ef7c8ba0adc/cffi-2.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94698a9c5f91f9d138526b48fe26a199609544591f859c870d477351dc7b2414", size = 211972, upload-time = "2025-09-08T23:22:38.436Z" }, - { url = "https://files.pythonhosted.org/packages/0b/28/dd0967a76aab36731b6ebfe64dec4e981aff7e0608f60c2d46b46982607d/cffi-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5fed36fccc0612a53f1d4d9a816b50a36702c28a2aa880cb8a122b3466638743", size = 217078, upload-time = "2025-09-08T23:22:39.776Z" }, - { url = "https://files.pythonhosted.org/packages/2b/c0/015b25184413d7ab0a410775fdb4a50fca20f5589b5dab1dbbfa3baad8ce/cffi-2.0.0-cp311-cp311-win32.whl", hash = "sha256:c649e3a33450ec82378822b3dad03cc228b8f5963c0c12fc3b1e0ab940f768a5", size = 172076, upload-time = "2025-09-08T23:22:40.95Z" }, - { url = "https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:66f011380d0e49ed280c789fbd08ff0d40968ee7b665575489afa95c98196ab5", size = 182820, upload-time = "2025-09-08T23:22:42.463Z" }, - { url = "https://files.pythonhosted.org/packages/95/5c/1b493356429f9aecfd56bc171285a4c4ac8697f76e9bbbbb105e537853a1/cffi-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c6638687455baf640e37344fe26d37c404db8b80d037c3d29f58fe8d1c3b194d", size = 177635, upload-time = "2025-09-08T23:22:43.623Z" }, { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, @@ -264,22 +218,6 @@ version = "3.4.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/27/c6491ff4954e58a10f69ad90aca8a1b6fe9c5d3c6f380907af3c37435b59/charset_normalizer-3.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", size = 206988, upload-time = "2025-10-14T04:40:33.79Z" }, - { url = "https://files.pythonhosted.org/packages/94/59/2e87300fe67ab820b5428580a53cad894272dbb97f38a7a814a2a1ac1011/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", size = 147324, upload-time = "2025-10-14T04:40:34.961Z" }, - { url = "https://files.pythonhosted.org/packages/07/fb/0cf61dc84b2b088391830f6274cb57c82e4da8bbc2efeac8c025edb88772/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", size = 142742, upload-time = "2025-10-14T04:40:36.105Z" }, - { url = "https://files.pythonhosted.org/packages/62/8b/171935adf2312cd745d290ed93cf16cf0dfe320863ab7cbeeae1dcd6535f/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", size = 160863, upload-time = "2025-10-14T04:40:37.188Z" }, - { url = "https://files.pythonhosted.org/packages/09/73/ad875b192bda14f2173bfc1bc9a55e009808484a4b256748d931b6948442/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", size = 157837, upload-time = "2025-10-14T04:40:38.435Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fc/de9cce525b2c5b94b47c70a4b4fb19f871b24995c728e957ee68ab1671ea/charset_normalizer-3.4.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", size = 151550, upload-time = "2025-10-14T04:40:40.053Z" }, - { url = "https://files.pythonhosted.org/packages/55/c2/43edd615fdfba8c6f2dfbd459b25a6b3b551f24ea21981e23fb768503ce1/charset_normalizer-3.4.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", size = 149162, upload-time = "2025-10-14T04:40:41.163Z" }, - { url = "https://files.pythonhosted.org/packages/03/86/bde4ad8b4d0e9429a4e82c1e8f5c659993a9a863ad62c7df05cf7b678d75/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", size = 150019, upload-time = "2025-10-14T04:40:42.276Z" }, - { url = "https://files.pythonhosted.org/packages/1f/86/a151eb2af293a7e7bac3a739b81072585ce36ccfb4493039f49f1d3cae8c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", size = 143310, upload-time = "2025-10-14T04:40:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/b5/fe/43dae6144a7e07b87478fdfc4dbe9efd5defb0e7ec29f5f58a55aeef7bf7/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", size = 162022, upload-time = "2025-10-14T04:40:44.547Z" }, - { url = "https://files.pythonhosted.org/packages/80/e6/7aab83774f5d2bca81f42ac58d04caf44f0cc2b65fc6db2b3b2e8a05f3b3/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", size = 149383, upload-time = "2025-10-14T04:40:46.018Z" }, - { url = "https://files.pythonhosted.org/packages/4f/e8/b289173b4edae05c0dde07f69f8db476a0b511eac556dfe0d6bda3c43384/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", size = 159098, upload-time = "2025-10-14T04:40:47.081Z" }, - { url = "https://files.pythonhosted.org/packages/d8/df/fe699727754cae3f8478493c7f45f777b17c3ef0600e28abfec8619eb49c/charset_normalizer-3.4.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", size = 152991, upload-time = "2025-10-14T04:40:48.246Z" }, - { url = "https://files.pythonhosted.org/packages/1a/86/584869fe4ddb6ffa3bd9f491b87a01568797fb9bd8933f557dba9771beaf/charset_normalizer-3.4.4-cp311-cp311-win32.whl", hash = "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", size = 99456, upload-time = "2025-10-14T04:40:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/65/f6/62fdd5feb60530f50f7e38b4f6a1d5203f4d16ff4f9f0952962c044e919a/charset_normalizer-3.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", size = 106978, upload-time = "2025-10-14T04:40:50.844Z" }, - { url = "https://files.pythonhosted.org/packages/7a/9d/0710916e6c82948b3be62d9d398cb4fcf4e97b56d6a6aeccd66c4b2f2bd5/charset_normalizer-3.4.4-cp311-cp311-win_arm64.whl", hash = "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", size = 99969, upload-time = "2025-10-14T04:40:52.272Z" }, { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, @@ -347,17 +285,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/58/01/1253e6698a07380cd31a736d248a3f2a50a7c88779a1813da27503cadc2a/contourpy-1.3.3.tar.gz", hash = "sha256:083e12155b210502d0bca491432bb04d56dc3432f95a979b429f2848c3dbe880", size = 13466174, upload-time = "2025-07-26T12:03:12.549Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/2e/c4390a31919d8a78b90e8ecf87cd4b4c4f05a5b48d05ec17db8e5404c6f4/contourpy-1.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:709a48ef9a690e1343202916450bc48b9e51c049b089c7f79a267b46cffcdaa1", size = 288773, upload-time = "2025-07-26T12:01:02.277Z" }, - { url = "https://files.pythonhosted.org/packages/0d/44/c4b0b6095fef4dc9c420e041799591e3b63e9619e3044f7f4f6c21c0ab24/contourpy-1.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:23416f38bfd74d5d28ab8429cc4d63fa67d5068bd711a85edb1c3fb0c3e2f381", size = 270149, upload-time = "2025-07-26T12:01:04.072Z" }, - { url = "https://files.pythonhosted.org/packages/30/2e/dd4ced42fefac8470661d7cb7e264808425e6c5d56d175291e93890cce09/contourpy-1.3.3-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:929ddf8c4c7f348e4c0a5a3a714b5c8542ffaa8c22954862a46ca1813b667ee7", size = 329222, upload-time = "2025-07-26T12:01:05.688Z" }, - { url = "https://files.pythonhosted.org/packages/f2/74/cc6ec2548e3d276c71389ea4802a774b7aa3558223b7bade3f25787fafc2/contourpy-1.3.3-cp311-cp311-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:9e999574eddae35f1312c2b4b717b7885d4edd6cb46700e04f7f02db454e67c1", size = 377234, upload-time = "2025-07-26T12:01:07.054Z" }, - { url = "https://files.pythonhosted.org/packages/03/b3/64ef723029f917410f75c09da54254c5f9ea90ef89b143ccadb09df14c15/contourpy-1.3.3-cp311-cp311-manylinux_2_26_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0bf67e0e3f482cb69779dd3061b534eb35ac9b17f163d851e2a547d56dba0a3a", size = 380555, upload-time = "2025-07-26T12:01:08.801Z" }, - { url = "https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:51e79c1f7470158e838808d4a996fa9bac72c498e93d8ebe5119bc1e6becb0db", size = 355238, upload-time = "2025-07-26T12:01:10.319Z" }, - { url = "https://files.pythonhosted.org/packages/98/56/f914f0dd678480708a04cfd2206e7c382533249bc5001eb9f58aa693e200/contourpy-1.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:598c3aaece21c503615fd59c92a3598b428b2f01bfb4b8ca9c4edeecc2438620", size = 1326218, upload-time = "2025-07-26T12:01:12.659Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d7/4a972334a0c971acd5172389671113ae82aa7527073980c38d5868ff1161/contourpy-1.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:322ab1c99b008dad206d406bb61d014cf0174df491ae9d9d0fac6a6fda4f977f", size = 1392867, upload-time = "2025-07-26T12:01:15.533Z" }, - { url = "https://files.pythonhosted.org/packages/75/3e/f2cc6cd56dc8cff46b1a56232eabc6feea52720083ea71ab15523daab796/contourpy-1.3.3-cp311-cp311-win32.whl", hash = "sha256:fd907ae12cd483cd83e414b12941c632a969171bf90fc937d0c9f268a31cafff", size = 183677, upload-time = "2025-07-26T12:01:17.088Z" }, - { url = "https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:3519428f6be58431c56581f1694ba8e50626f2dd550af225f82fb5f5814d2a42", size = 225234, upload-time = "2025-07-26T12:01:18.256Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b6/71771e02c2e004450c12b1120a5f488cad2e4d5b590b1af8bad060360fe4/contourpy-1.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:15ff10bfada4bf92ec8b31c62bf7c1834c244019b4a33095a68000d7075df470", size = 193123, upload-time = "2025-07-26T12:01:19.848Z" }, { url = "https://files.pythonhosted.org/packages/be/45/adfee365d9ea3d853550b2e735f9d66366701c65db7855cd07621732ccfc/contourpy-1.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b08a32ea2f8e42cf1d4be3169a98dd4be32bafe4f22b6c4cb4ba810fa9e5d2cb", size = 293419, upload-time = "2025-07-26T12:01:21.16Z" }, { url = "https://files.pythonhosted.org/packages/53/3e/405b59cfa13021a56bba395a6b3aca8cec012b45bf177b0eaf7a202cde2c/contourpy-1.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:556dba8fb6f5d8742f2923fe9457dbdd51e1049c4a43fd3986a0b14a1d815fc6", size = 273979, upload-time = "2025-07-26T12:01:22.448Z" }, { url = "https://files.pythonhosted.org/packages/d4/1c/a12359b9b2ca3a845e8f7f9ac08bdf776114eb931392fcad91743e2ea17b/contourpy-1.3.3-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92d9abc807cf7d0e047b95ca5d957cf4792fcd04e920ca70d48add15c1a90ea7", size = 332653, upload-time = "2025-07-26T12:01:24.155Z" }, @@ -369,11 +296,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cf/8f/5847f44a7fddf859704217a99a23a4f6417b10e5ab1256a179264561540e/contourpy-1.3.3-cp312-cp312-win32.whl", hash = "sha256:023b44101dfe49d7d53932be418477dba359649246075c996866106da069af69", size = 185018, upload-time = "2025-07-26T12:01:35.64Z" }, { url = "https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:8153b8bfc11e1e4d75bcb0bff1db232f9e10b274e0929de9d608027e0d34ff8b", size = 226567, upload-time = "2025-07-26T12:01:36.804Z" }, { url = "https://files.pythonhosted.org/packages/d1/e2/f05240d2c39a1ed228d8328a78b6f44cd695f7ef47beb3e684cf93604f86/contourpy-1.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:07ce5ed73ecdc4a03ffe3e1b3e3c1166db35ae7584be76f65dbbe28a7791b0cc", size = 193655, upload-time = "2025-07-26T12:01:37.999Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/8dcfe16f0107943fa92388c23f6e05cff0ba58058c4c95b00280d4c75a14/contourpy-1.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cd5dfcaeb10f7b7f9dc8941717c6c2ade08f587be2226222c12b25f0483ed497", size = 278809, upload-time = "2025-07-26T12:02:52.74Z" }, - { url = "https://files.pythonhosted.org/packages/85/a9/8b37ef4f7dafeb335daee3c8254645ef5725be4d9c6aa70b50ec46ef2f7e/contourpy-1.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:0c1fc238306b35f246d61a1d416a627348b5cf0648648a031e14bb8705fcdfe8", size = 261593, upload-time = "2025-07-26T12:02:54.037Z" }, - { url = "https://files.pythonhosted.org/packages/0a/59/ebfb8c677c75605cc27f7122c90313fd2f375ff3c8d19a1694bda74aaa63/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70f9aad7de812d6541d29d2bbf8feb22ff7e1c299523db288004e3157ff4674e", size = 302202, upload-time = "2025-07-26T12:02:55.947Z" }, - { url = "https://files.pythonhosted.org/packages/3c/37/21972a15834d90bfbfb009b9d004779bd5a07a0ec0234e5ba8f64d5736f4/contourpy-1.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5ed3657edf08512fc3fe81b510e35c2012fbd3081d2e26160f27ca28affec989", size = 329207, upload-time = "2025-07-26T12:02:57.468Z" }, - { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, ] [[package]] @@ -382,19 +304,6 @@ version = "7.13.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/01/abca50583a8975bb6e1c59eff67ed8e48bb127c07dad5c28d9e96ccc09ec/coverage-7.13.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:060ebf6f2c51aff5ba38e1f43a2095e087389b1c69d559fde6049a4b0001320e", size = 218971, upload-time = "2026-01-25T12:57:36.953Z" }, - { url = "https://files.pythonhosted.org/packages/eb/0e/b6489f344d99cd1e5b4d5e1be52dfd3f8a3dc5112aa6c33948da8cabad4e/coverage-7.13.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1ea8ca9db5e7469cd364552985e15911548ea5b69c48a17291f0cac70484b2e", size = 219473, upload-time = "2026-01-25T12:57:38.934Z" }, - { url = "https://files.pythonhosted.org/packages/17/11/db2f414915a8e4ec53f60b17956c27f21fb68fcf20f8a455ce7c2ccec638/coverage-7.13.2-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b780090d15fd58f07cf2011943e25a5f0c1c894384b13a216b6c86c8a8a7c508", size = 249896, upload-time = "2026-01-25T12:57:40.365Z" }, - { url = "https://files.pythonhosted.org/packages/80/06/0823fe93913663c017e508e8810c998c8ebd3ec2a5a85d2c3754297bdede/coverage-7.13.2-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:88a800258d83acb803c38175b4495d293656d5fac48659c953c18e5f539a274b", size = 251810, upload-time = "2026-01-25T12:57:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/61/dc/b151c3cc41b28cdf7f0166c5fa1271cbc305a8ec0124cce4b04f74791a18/coverage-7.13.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6326e18e9a553e674d948536a04a80d850a5eeefe2aae2e6d7cf05d54046c01b", size = 253920, upload-time = "2026-01-25T12:57:44.026Z" }, - { url = "https://files.pythonhosted.org/packages/2d/35/e83de0556e54a4729a2b94ea816f74ce08732e81945024adee46851c2264/coverage-7.13.2-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59562de3f797979e1ff07c587e2ac36ba60ca59d16c211eceaa579c266c5022f", size = 250025, upload-time = "2026-01-25T12:57:45.624Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/af2eb9c3926ce3ea0d58a0d2516fcbdacf7a9fc9559fe63076beaf3f2596/coverage-7.13.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:27ba1ed6f66b0e2d61bfa78874dffd4f8c3a12f8e2b5410e515ab345ba7bc9c3", size = 251612, upload-time = "2026-01-25T12:57:47.713Z" }, - { url = "https://files.pythonhosted.org/packages/26/62/5be2e25f3d6c711d23b71296f8b44c978d4c8b4e5b26871abfc164297502/coverage-7.13.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8be48da4d47cc68754ce643ea50b3234557cbefe47c2f120495e7bd0a2756f2b", size = 249670, upload-time = "2026-01-25T12:57:49.378Z" }, - { url = "https://files.pythonhosted.org/packages/b3/51/400d1b09a8344199f9b6a6fc1868005d766b7ea95e7882e494fa862ca69c/coverage-7.13.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2a47a4223d3361b91176aedd9d4e05844ca67d7188456227b6bf5e436630c9a1", size = 249395, upload-time = "2026-01-25T12:57:50.86Z" }, - { url = "https://files.pythonhosted.org/packages/e0/36/f02234bc6e5230e2f0a63fd125d0a2093c73ef20fdf681c7af62a140e4e7/coverage-7.13.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c6f141b468740197d6bd38f2b26ade124363228cc3f9858bd9924ab059e00059", size = 250298, upload-time = "2026-01-25T12:57:52.287Z" }, - { url = "https://files.pythonhosted.org/packages/b0/06/713110d3dd3151b93611c9cbfc65c15b4156b44f927fced49ac0b20b32a4/coverage-7.13.2-cp311-cp311-win32.whl", hash = "sha256:89567798404af067604246e01a49ef907d112edf2b75ef814b1364d5ce267031", size = 221485, upload-time = "2026-01-25T12:57:53.876Z" }, - { url = "https://files.pythonhosted.org/packages/16/0c/3ae6255fa1ebcb7dec19c9a59e85ef5f34566d1265c70af5b2fc981da834/coverage-7.13.2-cp311-cp311-win_amd64.whl", hash = "sha256:21dd57941804ae2ac7e921771a5e21bbf9aabec317a041d164853ad0a96ce31e", size = 222421, upload-time = "2026-01-25T12:57:55.433Z" }, - { url = "https://files.pythonhosted.org/packages/b5/37/fabc3179af4d61d89ea47bd04333fec735cd5e8b59baad44fed9fc4170d7/coverage-7.13.2-cp311-cp311-win_arm64.whl", hash = "sha256:10758e0586c134a0bafa28f2d37dd2cdb5e4a90de25c0fc0c77dabbad46eca28", size = 221088, upload-time = "2026-01-25T12:57:57.41Z" }, { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, @@ -425,10 +334,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/ba/501ef1b02119402cf1a31c01eb2cb8399660bca863c2f4dd3dc060220284/crcmod_plus-2.3.1-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9166bc3c9b5e7b07b4e6854cac392b4a451b31d58d3950e48c140ab7b5d05394", size = 27135, upload-time = "2025-10-10T22:13:51.889Z" }, { url = "https://files.pythonhosted.org/packages/49/90/d4556c9db69c83e726c5b88da3d656fdaac7d60c4d27b43cb939bed80069/crcmod_plus-2.3.1-cp311-abi3-win32.whl", hash = "sha256:cb99b694cce5c862560cf332a8b5e793620e28f0de3726995608bbd6f9b6e09a", size = 22384, upload-time = "2025-10-10T22:13:53.016Z" }, { url = "https://files.pythonhosted.org/packages/4d/7e/57bb97a8c7b4e19900744f58b67dc83bc9c83aaac670deeede9fb3bfab6a/crcmod_plus-2.3.1-cp311-abi3-win_amd64.whl", hash = "sha256:82b0f7e968c430c5a80fe0fc59e75cb54f2e84df2ed0cee5a3ff9cadfbf8a220", size = 22912, upload-time = "2025-10-10T22:13:53.849Z" }, - { url = "https://files.pythonhosted.org/packages/76/66/419ae3991bb68943cb752e2f4d317c555e3f02a298dd498f26113874ee59/crcmod_plus-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:9397324da1be2729f894744d9031a21ed97584c17fb0289e69e0c3c60916fc5f", size = 19880, upload-time = "2025-10-10T22:14:17.269Z" }, - { url = "https://files.pythonhosted.org/packages/18/f0/d10c9b859927b2cdc38eafc33c8b66e4ede02eaa174df4575681dab5a0f1/crcmod_plus-2.3.1-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:073c7a3b832652e66c41c8b8705eaecda704d1cbe850b9fa05fdee36cd50745a", size = 21120, upload-time = "2025-10-10T22:14:18.117Z" }, - { url = "https://files.pythonhosted.org/packages/6c/68/cbd8f1707b37b80f9a0bf643e04747b0196f69cf065b52ed56639afbecef/crcmod_plus-2.3.1-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e5f4c62553f772ea7ae12d9484801b752622c9c288e49ee7ea34a20b94e4920", size = 21698, upload-time = "2025-10-10T22:14:20.044Z" }, - { url = "https://files.pythonhosted.org/packages/41/1b/4ab1681ecbfc48d7e4641fb178c97374eb475ae4109255bdd832110cbbe2/crcmod_plus-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:5e80a9860f66f339956f540d86a768f4fe8c8bfcb139811f14be864425c48d64", size = 23289, upload-time = "2025-10-10T22:14:20.875Z" }, ] [[package]] @@ -475,10 +380,6 @@ version = "3.2.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/91/85/7574c9cd44b69a27210444b6650f6477f56c75fee1b70d7672d3e4166167/cython-3.2.4.tar.gz", hash = "sha256:84226ecd313b233da27dc2eb3601b4f222b8209c3a7216d8733b031da1dc64e6", size = 3280291, upload-time = "2026-01-04T14:14:14.473Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/85/cc/8f06145ec3efa121c8b1b67f06a640386ddacd77ee3e574da582a21b14ee/cython-3.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff9af2134c05e3734064808db95b4dd7341a39af06e8945d05ea358e1741aaed", size = 2953769, upload-time = "2026-01-04T14:15:00.361Z" }, - { url = "https://files.pythonhosted.org/packages/55/b0/706cf830eddd831666208af1b3058c2e0758ae157590909c1f634b53bed9/cython-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67922c9de058a0bfb72d2e75222c52d09395614108c68a76d9800f150296ddb3", size = 3243841, upload-time = "2026-01-04T14:15:02.066Z" }, - { url = "https://files.pythonhosted.org/packages/ac/25/58893afd4ef45f79e3d4db82742fa4ff874b936d67a83c92939053920ccd/cython-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b362819d155fff1482575e804e43e3a8825332d32baa15245f4642022664a3f4", size = 3378083, upload-time = "2026-01-04T14:15:04.248Z" }, - { url = "https://files.pythonhosted.org/packages/32/e4/424a004d7c0d8a4050c81846ebbd22272ececfa9a498cb340aa44fccbec2/cython-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:1a64a112a34ec719b47c01395647e54fb4cf088a511613f9a3a5196694e8e382", size = 2769990, upload-time = "2026-01-04T14:15:06.53Z" }, { url = "https://files.pythonhosted.org/packages/91/4d/1eb0c7c196a136b1926f4d7f0492a96c6fabd604d77e6cd43b56a3a16d83/cython-3.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64d7f71be3dd6d6d4a4c575bb3a4674ea06d1e1e5e4cd1b9882a2bc40ed3c4c9", size = 2970064, upload-time = "2026-01-04T14:15:08.567Z" }, { url = "https://files.pythonhosted.org/packages/03/1c/46e34b08bea19a1cdd1e938a4c123e6299241074642db9d81983cef95e9f/cython-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:869487ea41d004f8b92171f42271fbfadb1ec03bede3158705d16cd570d6b891", size = 3226757, upload-time = "2026-01-04T14:15:10.812Z" }, { url = "https://files.pythonhosted.org/packages/12/33/3298a44d201c45bcf0d769659725ae70e9c6c42adf8032f6d89c8241098d/cython-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:55b6c44cd30821f0b25220ceba6fe636ede48981d2a41b9bbfe3c7902ce44ea7", size = 3388969, upload-time = "2026-01-04T14:15:12.45Z" }, @@ -500,10 +401,6 @@ name = "dearpygui" version = "2.1.1" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/b5/d5bae262633d05f82aeeb3f84ae6240fe3f2955ab6fb4509ebf8048a0b9e/dearpygui-2.1.1-cp311-cp311-macosx_10_6_x86_64.whl", hash = "sha256:8a7c8608b365f4b380b7326679023595fecd04b78c514f2cfd349b0a1108bd0e", size = 2100934, upload-time = "2025-11-14T14:47:38.172Z" }, - { url = "https://files.pythonhosted.org/packages/93/86/75fa9a0f0a7b4b62810cc6f1e8ebaea3df0a825c0adf27d2024aaac2d178/dearpygui-2.1.1-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:ee87153fdd494ccead8c2345553acd7a9ee61c031e3223f4160aa560709248a3", size = 1895473, upload-time = "2025-11-14T14:47:47.064Z" }, - { url = "https://files.pythonhosted.org/packages/ab/7a/e109e06f8f4379d41a4e672c49aba42e7fcf0eec88056fa06185f4e52c98/dearpygui-2.1.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:964fbb3735017b919efa58104b2d7e9b84a168ff5c1031ae0652d5bc0a48bf5b", size = 2640408, upload-time = "2025-11-14T14:47:53.124Z" }, - { url = "https://files.pythonhosted.org/packages/f3/b5/2ec29d9b47c30ecee96c6f6a0cf229f2898ce3e133a1a0e5b0cd5db82e6b/dearpygui-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:6141184ff59fa4b8df1b81b077cb8cc2b2ef9c0ff92e69c6063062b6d251f426", size = 1808736, upload-time = "2025-11-14T14:47:26.46Z" }, { url = "https://files.pythonhosted.org/packages/79/41/2146e8d03d28b5a66d5282beb26ffd9ab68a729a29d31e2fe91809271bf5/dearpygui-2.1.1-cp312-cp312-macosx_10_6_x86_64.whl", hash = "sha256:238aea7b4be7376f564dae8edd563b280ec1483a03786022969938507691e017", size = 2101529, upload-time = "2025-11-14T14:47:39.646Z" }, { url = "https://files.pythonhosted.org/packages/b0/c5/fcc37ef834fe225241aa4f18d77aaa2903134f283077978d65a901c624c6/dearpygui-2.1.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:c27ca6ecd4913555b717f3bb341c0b6a27d6c9fdc9932f0b3c31ae2ef893ae35", size = 1895555, upload-time = "2025-11-14T14:47:48.149Z" }, { url = "https://files.pythonhosted.org/packages/74/66/19f454ba02d5f03a847cc1dfee4a849cd2307d97add5ba26fecdca318adb/dearpygui-2.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:8c071e9c165d89217bdcdaf769c6069252fcaee50bf369489add524107932273", size = 2641509, upload-time = "2025-11-14T14:47:54.581Z" }, @@ -573,14 +470,6 @@ version = "4.61.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/12/bf9f4eaa2fad039356cc627587e30ed008c03f1cebd3034376b5ee8d1d44/fonttools-4.61.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c6604b735bb12fef8e0efd5578c9fb5d3d8532d5001ea13a19cddf295673ee09", size = 2852213, upload-time = "2025-12-12T17:29:46.675Z" }, - { url = "https://files.pythonhosted.org/packages/ac/49/4138d1acb6261499bedde1c07f8c2605d1d8f9d77a151e5507fd3ef084b6/fonttools-4.61.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5ce02f38a754f207f2f06557523cd39a06438ba3aafc0639c477ac409fc64e37", size = 2401689, upload-time = "2025-12-12T17:29:48.769Z" }, - { url = "https://files.pythonhosted.org/packages/e5/fe/e6ce0fe20a40e03aef906af60aa87668696f9e4802fa283627d0b5ed777f/fonttools-4.61.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77efb033d8d7ff233385f30c62c7c79271c8885d5c9657d967ede124671bbdfb", size = 5058809, upload-time = "2025-12-12T17:29:51.701Z" }, - { url = "https://files.pythonhosted.org/packages/79/61/1ca198af22f7dd22c17ab86e9024ed3c06299cfdb08170640e9996d501a0/fonttools-4.61.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:75c1a6dfac6abd407634420c93864a1e274ebc1c7531346d9254c0d8f6ca00f9", size = 5036039, upload-time = "2025-12-12T17:29:53.659Z" }, - { url = "https://files.pythonhosted.org/packages/99/cc/fa1801e408586b5fce4da9f5455af8d770f4fc57391cd5da7256bb364d38/fonttools-4.61.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0de30bfe7745c0d1ffa2b0b7048fb7123ad0d71107e10ee090fa0b16b9452e87", size = 5034714, upload-time = "2025-12-12T17:29:55.592Z" }, - { url = "https://files.pythonhosted.org/packages/bf/aa/b7aeafe65adb1b0a925f8f25725e09f078c635bc22754f3fecb7456955b0/fonttools-4.61.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58b0ee0ab5b1fc9921eccfe11d1435added19d6494dde14e323f25ad2bc30c56", size = 5158648, upload-time = "2025-12-12T17:29:57.861Z" }, - { url = "https://files.pythonhosted.org/packages/99/f9/08ea7a38663328881384c6e7777bbefc46fd7d282adfd87a7d2b84ec9d50/fonttools-4.61.1-cp311-cp311-win32.whl", hash = "sha256:f79b168428351d11e10c5aeb61a74e1851ec221081299f4cf56036a95431c43a", size = 2280681, upload-time = "2025-12-12T17:29:59.943Z" }, - { url = "https://files.pythonhosted.org/packages/07/ad/37dd1ae5fa6e01612a1fbb954f0927681f282925a86e86198ccd7b15d515/fonttools-4.61.1-cp311-cp311-win_amd64.whl", hash = "sha256:fe2efccb324948a11dd09d22136fe2ac8a97d6c1347cf0b58a911dcd529f66b7", size = 2331951, upload-time = "2025-12-12T17:30:02.254Z" }, { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, @@ -598,22 +487,6 @@ version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/2d/f5/c831fac6cc817d26fd54c7eaccd04ef7e0288806943f7cc5bbf69f3ac1f0/frozenlist-1.8.0.tar.gz", hash = "sha256:3ede829ed8d842f6cd48fc7081d7a41001a56f1f38603f9d49bf3020d59a31ad", size = 45875, upload-time = "2025-10-06T05:38:17.865Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/03/077f869d540370db12165c0aa51640a873fb661d8b315d1d4d67b284d7ac/frozenlist-1.8.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:09474e9831bc2b2199fad6da3c14c7b0fbdd377cce9d3d77131be28906cb7d84", size = 86912, upload-time = "2025-10-06T05:35:45.98Z" }, - { url = "https://files.pythonhosted.org/packages/df/b5/7610b6bd13e4ae77b96ba85abea1c8cb249683217ef09ac9e0ae93f25a91/frozenlist-1.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:17c883ab0ab67200b5f964d2b9ed6b00971917d5d8a92df149dc2c9779208ee9", size = 50046, upload-time = "2025-10-06T05:35:47.009Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ef/0e8f1fe32f8a53dd26bdd1f9347efe0778b0fddf62789ea683f4cc7d787d/frozenlist-1.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fa47e444b8ba08fffd1c18e8cdb9a75db1b6a27f17507522834ad13ed5922b93", size = 50119, upload-time = "2025-10-06T05:35:48.38Z" }, - { url = "https://files.pythonhosted.org/packages/11/b1/71a477adc7c36e5fb628245dfbdea2166feae310757dea848d02bd0689fd/frozenlist-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2552f44204b744fba866e573be4c1f9048d6a324dfe14475103fd51613eb1d1f", size = 231067, upload-time = "2025-10-06T05:35:49.97Z" }, - { url = "https://files.pythonhosted.org/packages/45/7e/afe40eca3a2dc19b9904c0f5d7edfe82b5304cb831391edec0ac04af94c2/frozenlist-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e7c38f250991e48a9a73e6423db1bb9dd14e722a10f6b8bb8e16a0f55f695", size = 233160, upload-time = "2025-10-06T05:35:51.729Z" }, - { url = "https://files.pythonhosted.org/packages/a6/aa/7416eac95603ce428679d273255ffc7c998d4132cfae200103f164b108aa/frozenlist-1.8.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:8585e3bb2cdea02fc88ffa245069c36555557ad3609e83be0ec71f54fd4abb52", size = 228544, upload-time = "2025-10-06T05:35:53.246Z" }, - { url = "https://files.pythonhosted.org/packages/8b/3d/2a2d1f683d55ac7e3875e4263d28410063e738384d3adc294f5ff3d7105e/frozenlist-1.8.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:edee74874ce20a373d62dc28b0b18b93f645633c2943fd90ee9d898550770581", size = 243797, upload-time = "2025-10-06T05:35:54.497Z" }, - { url = "https://files.pythonhosted.org/packages/78/1e/2d5565b589e580c296d3bb54da08d206e797d941a83a6fdea42af23be79c/frozenlist-1.8.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c9a63152fe95756b85f31186bddf42e4c02c6321207fd6601a1c89ebac4fe567", size = 247923, upload-time = "2025-10-06T05:35:55.861Z" }, - { url = "https://files.pythonhosted.org/packages/aa/c3/65872fcf1d326a7f101ad4d86285c403c87be7d832b7470b77f6d2ed5ddc/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6db2185db9be0a04fecf2f241c70b63b1a242e2805be291855078f2b404dd6b", size = 230886, upload-time = "2025-10-06T05:35:57.399Z" }, - { url = "https://files.pythonhosted.org/packages/a0/76/ac9ced601d62f6956f03cc794f9e04c81719509f85255abf96e2510f4265/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:f4be2e3d8bc8aabd566f8d5b8ba7ecc09249d74ba3c9ed52e54dc23a293f0b92", size = 245731, upload-time = "2025-10-06T05:35:58.563Z" }, - { url = "https://files.pythonhosted.org/packages/b9/49/ecccb5f2598daf0b4a1415497eba4c33c1e8ce07495eb07d2860c731b8d5/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c8d1634419f39ea6f5c427ea2f90ca85126b54b50837f31497f3bf38266e853d", size = 241544, upload-time = "2025-10-06T05:35:59.719Z" }, - { url = "https://files.pythonhosted.org/packages/53/4b/ddf24113323c0bbcc54cb38c8b8916f1da7165e07b8e24a717b4a12cbf10/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:1a7fa382a4a223773ed64242dbe1c9c326ec09457e6b8428efb4118c685c3dfd", size = 241806, upload-time = "2025-10-06T05:36:00.959Z" }, - { url = "https://files.pythonhosted.org/packages/a7/fb/9b9a084d73c67175484ba2789a59f8eebebd0827d186a8102005ce41e1ba/frozenlist-1.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:11847b53d722050808926e785df837353bd4d75f1d494377e59b23594d834967", size = 229382, upload-time = "2025-10-06T05:36:02.22Z" }, - { url = "https://files.pythonhosted.org/packages/95/a3/c8fb25aac55bf5e12dae5c5aa6a98f85d436c1dc658f21c3ac73f9fa95e5/frozenlist-1.8.0-cp311-cp311-win32.whl", hash = "sha256:27c6e8077956cf73eadd514be8fb04d77fc946a7fe9f7fe167648b0b9085cc25", size = 39647, upload-time = "2025-10-06T05:36:03.409Z" }, - { url = "https://files.pythonhosted.org/packages/0a/f5/603d0d6a02cfd4c8f2a095a54672b3cf967ad688a60fb9faf04fc4887f65/frozenlist-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac913f8403b36a2c8610bbfd25b8013488533e71e62b4b4adce9c86c8cea905b", size = 44064, upload-time = "2025-10-06T05:36:04.368Z" }, - { url = "https://files.pythonhosted.org/packages/5d/16/c2c9ab44e181f043a86f9a8f84d5124b62dbcb3a02c0977ec72b9ac1d3e0/frozenlist-1.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:d4d3214a0f8394edfa3e303136d0575eece0745ff2b47bd2cb2e66dd92d4351a", size = 39937, upload-time = "2025-10-06T05:36:05.669Z" }, { url = "https://files.pythonhosted.org/packages/69/29/948b9aa87e75820a38650af445d2ef2b6b8a6fab1a23b6bb9e4ef0be2d59/frozenlist-1.8.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:78f7b9e5d6f2fdb88cdde9440dc147259b62b9d3b019924def9f6478be254ac1", size = 87782, upload-time = "2025-10-06T05:36:06.649Z" }, { url = "https://files.pythonhosted.org/packages/64/80/4f6e318ee2a7c0750ed724fa33a4bdf1eacdc5a39a7a24e818a773cd91af/frozenlist-1.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:229bf37d2e4acdaf808fd3f06e854a4a7a3661e871b10dc1f8f1896a3b05f18b", size = 50594, upload-time = "2025-10-06T05:36:07.69Z" }, { url = "https://files.pythonhosted.org/packages/2b/94/5c8a2b50a496b11dd519f4a24cb5496cf125681dd99e94c604ccdea9419a/frozenlist-1.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f833670942247a14eafbb675458b4e61c82e002a148f49e68257b79296e865c4", size = 50448, upload-time = "2025-10-06T05:36:08.78Z" }, @@ -651,18 +524,11 @@ version = "1.8.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/03/41/4b9c02f99e4c5fb477122cd5437403b552873f014616ac1d19ac8221a58d/google_crc32c-1.8.0.tar.gz", hash = "sha256:a428e25fb7691024de47fecfbff7ff957214da51eddded0da0ae0e0f03a2cf79", size = 14192, upload-time = "2025-12-16T00:35:25.142Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/ef/21ccfaab3d5078d41efe8612e0ed0bfc9ce22475de074162a91a25f7980d/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:014a7e68d623e9a4222d663931febc3033c5c7c9730785727de2a81f87d5bab8", size = 31298, upload-time = "2025-12-16T00:20:32.241Z" }, - { url = "https://files.pythonhosted.org/packages/c5/b8/f8413d3f4b676136e965e764ceedec904fe38ae8de0cdc52a12d8eb1096e/google_crc32c-1.8.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:86cfc00fe45a0ac7359e5214a1704e51a99e757d0272554874f419f79838c5f7", size = 30872, upload-time = "2025-12-16T00:33:58.785Z" }, - { url = "https://files.pythonhosted.org/packages/f6/fd/33aa4ec62b290477181c55bb1c9302c9698c58c0ce9a6ab4874abc8b0d60/google_crc32c-1.8.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:19b40d637a54cb71e0829179f6cb41835f0fbd9e8eb60552152a8b52c36cbe15", size = 33243, upload-time = "2025-12-16T00:40:21.46Z" }, - { url = "https://files.pythonhosted.org/packages/71/03/4820b3bd99c9653d1a5210cb32f9ba4da9681619b4d35b6a052432df4773/google_crc32c-1.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:17446feb05abddc187e5441a45971b8394ea4c1b6efd88ab0af393fd9e0a156a", size = 33608, upload-time = "2025-12-16T00:40:22.204Z" }, - { url = "https://files.pythonhosted.org/packages/7c/43/acf61476a11437bf9733fb2f70599b1ced11ec7ed9ea760fdd9a77d0c619/google_crc32c-1.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:71734788a88f551fbd6a97be9668a0020698e07b2bf5b3aa26a36c10cdfb27b2", size = 34439, upload-time = "2025-12-16T00:35:20.458Z" }, { url = "https://files.pythonhosted.org/packages/e9/5f/7307325b1198b59324c0fa9807cafb551afb65e831699f2ce211ad5c8240/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:4b8286b659c1335172e39563ab0a768b8015e88e08329fa5321f774275fc3113", size = 31300, upload-time = "2025-12-16T00:21:56.723Z" }, { url = "https://files.pythonhosted.org/packages/21/8e/58c0d5d86e2220e6a37befe7e6a94dd2f6006044b1a33edf1ff6d9f7e319/google_crc32c-1.8.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:2a3dc3318507de089c5384cc74d54318401410f82aa65b2d9cdde9d297aca7cb", size = 30867, upload-time = "2025-12-16T00:38:31.302Z" }, { url = "https://files.pythonhosted.org/packages/ce/a9/a780cc66f86335a6019f557a8aaca8fbb970728f0efd2430d15ff1beae0e/google_crc32c-1.8.0-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14f87e04d613dfa218d6135e81b78272c3b904e2a7053b841481b38a7d901411", size = 33364, upload-time = "2025-12-16T00:40:22.96Z" }, { url = "https://files.pythonhosted.org/packages/21/3f/3457ea803db0198c9aaca2dd373750972ce28a26f00544b6b85088811939/google_crc32c-1.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb5c869c2923d56cb0c8e6bcdd73c009c36ae39b652dbe46a05eb4ef0ad01454", size = 33740, upload-time = "2025-12-16T00:40:23.96Z" }, { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, - { url = "https://files.pythonhosted.org/packages/52/c5/c171e4d8c44fec1422d801a6d2e5d7ddabd733eeda505c79730ee9607f07/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:87fa445064e7db928226b2e6f0d5304ab4cd0339e664a4e9a25029f384d9bb93", size = 28615, upload-time = "2025-12-16T00:40:29.298Z" }, - { url = "https://files.pythonhosted.org/packages/9c/97/7d75fe37a7a6ed171a2cf17117177e7aab7e6e0d115858741b41e9dd4254/google_crc32c-1.8.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f639065ea2042d5c034bf258a9f085eaa7af0cd250667c0635a3118e8f92c69c", size = 28800, upload-time = "2025-12-16T00:40:30.322Z" }, ] [[package]] @@ -774,19 +640,6 @@ version = "1.4.9" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, - { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, - { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, @@ -800,11 +653,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, - { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, ] [[package]] @@ -824,22 +672,6 @@ version = "6.0.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/d5/becbe1e2569b474a23f0c672ead8a29ac50b2dc1d5b9de184831bda8d14c/lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607", size = 8634365, upload-time = "2025-09-22T04:00:45.672Z" }, - { url = "https://files.pythonhosted.org/packages/28/66/1ced58f12e804644426b85d0bb8a4478ca77bc1761455da310505f1a3526/lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938", size = 4650793, upload-time = "2025-09-22T04:00:47.783Z" }, - { url = "https://files.pythonhosted.org/packages/11/84/549098ffea39dfd167e3f174b4ce983d0eed61f9d8d25b7bf2a57c3247fc/lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d", size = 4944362, upload-time = "2025-09-22T04:00:49.845Z" }, - { url = "https://files.pythonhosted.org/packages/ac/bd/f207f16abf9749d2037453d56b643a7471d8fde855a231a12d1e095c4f01/lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438", size = 5083152, upload-time = "2025-09-22T04:00:51.709Z" }, - { url = "https://files.pythonhosted.org/packages/15/ae/bd813e87d8941d52ad5b65071b1affb48da01c4ed3c9c99e40abb266fbff/lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964", size = 5023539, upload-time = "2025-09-22T04:00:53.593Z" }, - { url = "https://files.pythonhosted.org/packages/02/cd/9bfef16bd1d874fbe0cb51afb00329540f30a3283beb9f0780adbb7eec03/lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d", size = 5344853, upload-time = "2025-09-22T04:00:55.524Z" }, - { url = "https://files.pythonhosted.org/packages/b8/89/ea8f91594bc5dbb879734d35a6f2b0ad50605d7fb419de2b63d4211765cc/lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7", size = 5225133, upload-time = "2025-09-22T04:00:57.269Z" }, - { url = "https://files.pythonhosted.org/packages/b9/37/9c735274f5dbec726b2db99b98a43950395ba3d4a1043083dba2ad814170/lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178", size = 4677944, upload-time = "2025-09-22T04:00:59.052Z" }, - { url = "https://files.pythonhosted.org/packages/20/28/7dfe1ba3475d8bfca3878365075abe002e05d40dfaaeb7ec01b4c587d533/lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553", size = 5284535, upload-time = "2025-09-22T04:01:01.335Z" }, - { url = "https://files.pythonhosted.org/packages/e7/cf/5f14bc0de763498fc29510e3532bf2b4b3a1c1d5d0dff2e900c16ba021ef/lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb", size = 5067343, upload-time = "2025-09-22T04:01:03.13Z" }, - { url = "https://files.pythonhosted.org/packages/1c/b0/bb8275ab5472f32b28cfbbcc6db7c9d092482d3439ca279d8d6fa02f7025/lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a", size = 4725419, upload-time = "2025-09-22T04:01:05.013Z" }, - { url = "https://files.pythonhosted.org/packages/25/4c/7c222753bc72edca3b99dbadba1b064209bc8ed4ad448af990e60dcce462/lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c", size = 5275008, upload-time = "2025-09-22T04:01:07.327Z" }, - { url = "https://files.pythonhosted.org/packages/6c/8c/478a0dc6b6ed661451379447cdbec77c05741a75736d97e5b2b729687828/lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7", size = 5248906, upload-time = "2025-09-22T04:01:09.452Z" }, - { url = "https://files.pythonhosted.org/packages/2d/d9/5be3a6ab2784cdf9accb0703b65e1b64fcdd9311c9f007630c7db0cfcce1/lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46", size = 3610357, upload-time = "2025-09-22T04:01:11.102Z" }, - { url = "https://files.pythonhosted.org/packages/e2/7d/ca6fb13349b473d5732fb0ee3eec8f6c80fc0688e76b7d79c1008481bf1f/lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078", size = 4036583, upload-time = "2025-09-22T04:01:12.766Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a2/51363b5ecd3eab46563645f3a2c3836a2fc67d01a1b87c5017040f39f567/lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285", size = 3680591, upload-time = "2025-09-22T04:01:14.874Z" }, { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, @@ -858,12 +690,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, - { url = "https://files.pythonhosted.org/packages/0b/11/29d08bc103a62c0eba8016e7ed5aeebbf1e4312e83b0b1648dd203b0e87d/lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700", size = 3949829, upload-time = "2025-09-22T04:04:45.608Z" }, - { url = "https://files.pythonhosted.org/packages/12/b3/52ab9a3b31e5ab8238da241baa19eec44d2ab426532441ee607165aebb52/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee", size = 4226277, upload-time = "2025-09-22T04:04:47.754Z" }, - { url = "https://files.pythonhosted.org/packages/a0/33/1eaf780c1baad88224611df13b1c2a9dfa460b526cacfe769103ff50d845/lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f", size = 4330433, upload-time = "2025-09-22T04:04:49.907Z" }, - { url = "https://files.pythonhosted.org/packages/7a/c1/27428a2ff348e994ab4f8777d3a0ad510b6b92d37718e5887d2da99952a2/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9", size = 4272119, upload-time = "2025-09-22T04:04:51.801Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d0/3020fa12bcec4ab62f97aab026d57c2f0cfd480a558758d9ca233bb6a79d/lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a", size = 4417314, upload-time = "2025-09-22T04:04:55.024Z" }, - { url = "https://files.pythonhosted.org/packages/6c/77/d7f491cbc05303ac6801651aabeb262d43f319288c1ea96c66b1d2692ff3/lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e", size = 3518768, upload-time = "2025-09-22T04:04:57.097Z" }, ] [[package]] @@ -875,15 +701,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/bc/7b/bbf6b00488662be5d2eb7a188222c264b6f713bac10dc4a77bf37a4cb4b6/mapbox_earcut-2.0.0.tar.gz", hash = "sha256:81eab6b86cf99551deb698b98e3f7502c57900e5c479df15e1bdaf1a57f0f9d6", size = 39934, upload-time = "2025-11-16T18:41:27.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/9f/fbd15d9e348e75e986d6912c4eab99888106b7e5fb0a01e765422f7cd464/mapbox_earcut-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:9b5040e79e3783295e99c90277f31c1cbaddd3335297275331995ba5680e3649", size = 55773, upload-time = "2025-11-16T18:40:20.045Z" }, - { url = "https://files.pythonhosted.org/packages/72/40/be761298704fbbaa81c5618bb306f1510fb068e482f6a1c8b3b6c1b31479/mapbox_earcut-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf43baafec3ef1e967319d9b5da96bc6ddf3dbb204b6f3535275eda4b519a72", size = 52444, upload-time = "2025-11-16T18:40:21.501Z" }, - { url = "https://files.pythonhosted.org/packages/5a/0b/0c0c08db9663238ffb82c48259582dc0047a3255d98c0ac83c48026b7544/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a283531847f603dd9d69afb75b21bd009d385ca9485fcd3e5a7fa5db1ccd913", size = 56803, upload-time = "2025-11-16T18:40:22.891Z" }, - { url = "https://files.pythonhosted.org/packages/f0/4a/86796859383d7d11fa5d4bcf1983f94c6cbb9eeb60fb3bab527fec4b32fa/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab697676f4cec4572d4e941b7a3429a6687bf2ac6e8db3f3781024e3239ae3a0", size = 59403, upload-time = "2025-11-16T18:40:24.021Z" }, - { url = "https://files.pythonhosted.org/packages/6c/db/adaf981ab3bcfcf993ef317636b1f27210d6834bb1e8d63db6ad7c08214a/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1bdac76e048f4299accf4eaf797079ddfc330442e7231c15535ed198100d6c5", size = 152876, upload-time = "2025-11-16T18:40:25.588Z" }, - { url = "https://files.pythonhosted.org/packages/d2/83/86417974039e7554c9e1e55c852a7e9c2a1390d64675eb85d70e5fa7eb37/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a6945b23f859bef11ce3194303d17bd371c86b637e7029f81b1feaff3db3758", size = 157548, upload-time = "2025-11-16T18:40:27.202Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4c/c82a292bb21e5c651d81334123db2d654c5c9d19b2197080d3429dc1e49a/mapbox_earcut-2.0.0-cp311-cp311-win32.whl", hash = "sha256:8e119524c29406afb5eaa15e933f297d35679293a3ca62ced22f97a14c484cb5", size = 51424, upload-time = "2025-11-16T18:40:28.415Z" }, - { url = "https://files.pythonhosted.org/packages/30/57/6c39d7db81f72a3e4814ef152c8fb8dfe275dc4b03c9bfa073d251e3755f/mapbox_earcut-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:378bbbb3304e446023752db8f44ecd6e7ef965bcbda36541d2ae64442ba94254", size = 56662, upload-time = "2025-11-16T18:40:29.863Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d6/a1ef6e196b3d6968bf6546d4f7e54c559f9cff8991fdb880df0ba1618f52/mapbox_earcut-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:6d249a431abd6bbff36f1fd0493247a86de962244cc4081b4d5050b02ed48fb1", size = 50505, upload-time = "2025-11-16T18:40:30.992Z" }, { url = "https://files.pythonhosted.org/packages/8d/93/846804029d955c3c841d8efff77c2b0e8d9aab057d3a077dc8e3f88b5ea4/mapbox_earcut-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db55ce18e698bc9d90914ee7d4f8c3e4d23827456ece7c5d7a1ec91e90c7122b", size = 55623, upload-time = "2025-11-16T18:40:32.113Z" }, { url = "https://files.pythonhosted.org/packages/d3/f6/cc9ece104bc3876b350dba6fef7f34fb7b20ecc028d2cdbdbecb436b1ed1/mapbox_earcut-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01dd6099d16123baf582a11b2bd1d59ce848498cf0cdca3812fd1f8b20ff33b7", size = 52028, upload-time = "2025-11-16T18:40:33.516Z" }, { url = "https://files.pythonhosted.org/packages/88/6e/230da4aabcc56c99e9bddb4c43ce7d4ba3609c0caf2d316fb26535d7c60c/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5a098aae26a52282bc981a38e7bf6b889d2ea7442f2cd1903d2ba842f4ff07", size = 56351, upload-time = "2025-11-16T18:40:35.217Z" }, @@ -910,17 +727,6 @@ version = "3.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, - { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, - { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, - { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, - { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, - { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, - { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, - { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, - { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, - { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, @@ -951,13 +757,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/8a/76/d3c6e3a13fe484ebe7718d14e269c9569c4eb0020a968a327acb3b9a8fe6/matplotlib-3.10.8.tar.gz", hash = "sha256:2299372c19d56bcd35cf05a2738308758d32b9eaed2371898d8f5bd33f084aa3", size = 34806269, upload-time = "2025-12-10T22:56:51.155Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/86/de7e3a1cdcfc941483af70609edc06b83e7c8a0e0dc9ac325200a3f4d220/matplotlib-3.10.8-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6be43b667360fef5c754dda5d25a32e6307a03c204f3c0fc5468b78fa87b4160", size = 8251215, upload-time = "2025-12-10T22:55:16.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/14/baad3222f424b19ce6ad243c71de1ad9ec6b2e4eb1e458a48fdc6d120401/matplotlib-3.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a2b336e2d91a3d7006864e0990c83b216fcdca64b5a6484912902cef87313d78", size = 8139625, upload-time = "2025-12-10T22:55:17.712Z" }, - { url = "https://files.pythonhosted.org/packages/8f/a0/7024215e95d456de5883e6732e708d8187d9753a21d32f8ddb3befc0c445/matplotlib-3.10.8-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:efb30e3baaea72ce5928e32bab719ab4770099079d66726a62b11b1ef7273be4", size = 8712614, upload-time = "2025-12-10T22:55:20.8Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f4/b8347351da9a5b3f41e26cf547252d861f685c6867d179a7c9d60ad50189/matplotlib-3.10.8-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d56a1efd5bfd61486c8bc968fa18734464556f0fb8e51690f4ac25d85cbbbbc2", size = 9540997, upload-time = "2025-12-10T22:55:23.258Z" }, - { url = "https://files.pythonhosted.org/packages/9e/c0/c7b914e297efe0bc36917bf216b2acb91044b91e930e878ae12981e461e5/matplotlib-3.10.8-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238b7ce5717600615c895050239ec955d91f321c209dd110db988500558e70d6", size = 9596825, upload-time = "2025-12-10T22:55:25.217Z" }, - { url = "https://files.pythonhosted.org/packages/6f/d3/a4bbc01c237ab710a1f22b4da72f4ff6d77eb4c7735ea9811a94ae239067/matplotlib-3.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:18821ace09c763ec93aef5eeff087ee493a24051936d7b9ebcad9662f66501f9", size = 8135090, upload-time = "2025-12-10T22:55:27.162Z" }, - { url = "https://files.pythonhosted.org/packages/89/dd/a0b6588f102beab33ca6f5218b31725216577b2a24172f327eaf6417d5c9/matplotlib-3.10.8-cp311-cp311-win_arm64.whl", hash = "sha256:bab485bcf8b1c7d2060b4fcb6fc368a9e6f4cd754c9c2fea281f4be21df394a2", size = 8012377, upload-time = "2025-12-10T22:55:29.185Z" }, { url = "https://files.pythonhosted.org/packages/9e/67/f997cdcbb514012eb0d10cd2b4b332667997fb5ebe26b8d41d04962fa0e6/matplotlib-3.10.8-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:64fcc24778ca0404ce0cb7b6b77ae1f4c7231cdd60e6778f999ee05cbd581b9a", size = 8260453, upload-time = "2025-12-10T22:55:30.709Z" }, { url = "https://files.pythonhosted.org/packages/7e/65/07d5f5c7f7c994f12c768708bd2e17a4f01a2b0f44a1c9eccad872433e2e/matplotlib-3.10.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b9a5ca4ac220a0cdd1ba6bcba3608547117d30468fefce49bb26f55c1a3d5c58", size = 8148321, upload-time = "2025-12-10T22:55:33.265Z" }, { url = "https://files.pythonhosted.org/packages/3e/f3/c5195b1ae57ef85339fd7285dfb603b22c8b4e79114bae5f4f0fcf688677/matplotlib-3.10.8-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3ab4aabc72de4ff77b3ec33a6d78a68227bf1123465887f9905ba79184a1cc04", size = 8716944, upload-time = "2025-12-10T22:55:34.922Z" }, @@ -965,9 +764,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/57/61/78cd5920d35b29fd2a0fe894de8adf672ff52939d2e9b43cb83cd5ce1bc7/matplotlib-3.10.8-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:99eefd13c0dc3b3c1b4d561c1169e65fe47aab7b8158754d7c084088e2329466", size = 9613040, upload-time = "2025-12-10T22:55:38.715Z" }, { url = "https://files.pythonhosted.org/packages/30/4e/c10f171b6e2f44d9e3a2b96efa38b1677439d79c99357600a62cc1e9594e/matplotlib-3.10.8-cp312-cp312-win_amd64.whl", hash = "sha256:dd80ecb295460a5d9d260df63c43f4afbdd832d725a531f008dad1664f458adf", size = 8142717, upload-time = "2025-12-10T22:55:41.103Z" }, { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, - { url = "https://files.pythonhosted.org/packages/04/30/3afaa31c757f34b7725ab9d2ba8b48b5e89c2019c003e7d0ead143aabc5a/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6da7c2ce169267d0d066adcf63758f0604aa6c3eebf67458930f9d9b79ad1db1", size = 8249198, upload-time = "2025-12-10T22:56:45.584Z" }, - { url = "https://files.pythonhosted.org/packages/48/2f/6334aec331f57485a642a7c8be03cb286f29111ae71c46c38b363230063c/matplotlib-3.10.8-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:9153c3292705be9f9c64498a8872118540c3f4123d1a1c840172edf262c8be4a", size = 8136817, upload-time = "2025-12-10T22:56:47.339Z" }, - { url = "https://files.pythonhosted.org/packages/73/e4/6d6f14b2a759c622f191b2d67e9075a3f56aaccb3be4bb9bb6890030d0a0/matplotlib-3.10.8-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ae029229a57cd1e8fe542485f27e7ca7b23aa9e8944ddb4985d0bc444f1eca2", size = 8713867, upload-time = "2025-12-10T22:56:48.954Z" }, ] [[package]] @@ -1080,11 +876,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/5e/712092cfe7e5eb667b8ad9ca7c54442f21ed7ca8979745f1000e24cf8737/ml_dtypes-0.5.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c7ecb74c4bd71db68a6bea1edf8da8c34f3d9fe218f038814fd1d310ac76c90", size = 679734, upload-time = "2025-11-17T22:31:39.223Z" }, - { url = "https://files.pythonhosted.org/packages/4f/cf/912146dfd4b5c0eea956836c01dcd2fce6c9c844b2691f5152aca196ce4f/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc11d7e8c44a65115d05e2ab9989d1e045125d7be8e05a071a48bc76eb6d6040", size = 5056165, upload-time = "2025-11-17T22:31:41.071Z" }, - { url = "https://files.pythonhosted.org/packages/a9/80/19189ea605017473660e43762dc853d2797984b3c7bf30ce656099add30c/ml_dtypes-0.5.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:19b9a53598f21e453ea2fbda8aa783c20faff8e1eeb0d7ab899309a0053f1483", size = 5034975, upload-time = "2025-11-17T22:31:42.758Z" }, - { url = "https://files.pythonhosted.org/packages/b4/24/70bd59276883fdd91600ca20040b41efd4902a923283c4d6edcb1de128d2/ml_dtypes-0.5.4-cp311-cp311-win_amd64.whl", hash = "sha256:7c23c54a00ae43edf48d44066a7ec31e05fdc2eee0be2b8b50dd1903a1db94bb", size = 210742, upload-time = "2025-11-17T22:31:44.068Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c9/64230ef14e40aa3f1cb254ef623bf812735e6bec7772848d19131111ac0d/ml_dtypes-0.5.4-cp311-cp311-win_arm64.whl", hash = "sha256:557a31a390b7e9439056644cb80ed0735a6e3e3bb09d67fd5687e4b04238d1de", size = 160709, upload-time = "2025-11-17T22:31:46.557Z" }, { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, @@ -1144,24 +935,6 @@ version = "6.7.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/1a/c2/c2d94cbe6ac1753f3fc980da97b3d930efe1da3af3c9f5125354436c073d/multidict-6.7.1.tar.gz", hash = "sha256:ec6652a1bee61c53a3e5776b6049172c53b6aaba34f18c9ad04f82712bac623d", size = 102010, upload-time = "2026-01-26T02:46:45.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/f1/a90635c4f88fb913fbf4ce660b83b7445b7a02615bda034b2f8eb38fd597/multidict-6.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7ff981b266af91d7b4b3793ca3382e53229088d193a85dfad6f5f4c27fc73e5d", size = 76626, upload-time = "2026-01-26T02:43:26.485Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9b/267e64eaf6fc637a15b35f5de31a566634a2740f97d8d094a69d34f524a4/multidict-6.7.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:844c5bca0b5444adb44a623fb0a1310c2f4cd41f402126bb269cd44c9b3f3e1e", size = 44706, upload-time = "2026-01-26T02:43:27.607Z" }, - { url = "https://files.pythonhosted.org/packages/dd/a4/d45caf2b97b035c57267791ecfaafbd59c68212004b3842830954bb4b02e/multidict-6.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f2a0a924d4c2e9afcd7ec64f9de35fcd96915149b2216e1cb2c10a56df483855", size = 44356, upload-time = "2026-01-26T02:43:28.661Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d2/0a36c8473f0cbaeadd5db6c8b72d15bbceeec275807772bfcd059bef487d/multidict-6.7.1-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8be1802715a8e892c784c0197c2ace276ea52702a0ede98b6310c8f255a5afb3", size = 244355, upload-time = "2026-01-26T02:43:31.165Z" }, - { url = "https://files.pythonhosted.org/packages/5d/16/8c65be997fd7dd311b7d39c7b6e71a0cb449bad093761481eccbbe4b42a2/multidict-6.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2e2d2ed645ea29f31c4c7ea1552fcfd7cb7ba656e1eafd4134a6620c9f5fdd9e", size = 246433, upload-time = "2026-01-26T02:43:32.581Z" }, - { url = "https://files.pythonhosted.org/packages/01/fb/4dbd7e848d2799c6a026ec88ad39cf2b8416aa167fcc903baa55ecaa045c/multidict-6.7.1-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:95922cee9a778659e91db6497596435777bd25ed116701a4c034f8e46544955a", size = 225376, upload-time = "2026-01-26T02:43:34.417Z" }, - { url = "https://files.pythonhosted.org/packages/b6/8a/4a3a6341eac3830f6053062f8fbc9a9e54407c80755b3f05bc427295c2d0/multidict-6.7.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6b83cabdc375ffaaa15edd97eb7c0c672ad788e2687004990074d7d6c9b140c8", size = 257365, upload-time = "2026-01-26T02:43:35.741Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a2/dd575a69c1aa206e12d27d0770cdf9b92434b48a9ef0cd0d1afdecaa93c4/multidict-6.7.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:38fb49540705369bab8484db0689d86c0a33a0a9f2c1b197f506b71b4b6c19b0", size = 254747, upload-time = "2026-01-26T02:43:36.976Z" }, - { url = "https://files.pythonhosted.org/packages/5a/56/21b27c560c13822ed93133f08aa6372c53a8e067f11fbed37b4adcdac922/multidict-6.7.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:439cbebd499f92e9aa6793016a8acaa161dfa749ae86d20960189f5398a19144", size = 246293, upload-time = "2026-01-26T02:43:38.258Z" }, - { url = "https://files.pythonhosted.org/packages/5a/a4/23466059dc3854763423d0ad6c0f3683a379d97673b1b89ec33826e46728/multidict-6.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6d3bc717b6fe763b8be3f2bee2701d3c8eb1b2a8ae9f60910f1b2860c82b6c49", size = 242962, upload-time = "2026-01-26T02:43:40.034Z" }, - { url = "https://files.pythonhosted.org/packages/1f/67/51dd754a3524d685958001e8fa20a0f5f90a6a856e0a9dcabff69be3dbb7/multidict-6.7.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:619e5a1ac57986dbfec9f0b301d865dddf763696435e2962f6d9cf2fdff2bb71", size = 237360, upload-time = "2026-01-26T02:43:41.752Z" }, - { url = "https://files.pythonhosted.org/packages/64/3f/036dfc8c174934d4b55d86ff4f978e558b0e585cef70cfc1ad01adc6bf18/multidict-6.7.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0b38ebffd9be37c1170d33bc0f36f4f262e0a09bc1aac1c34c7aa51a7293f0b3", size = 245940, upload-time = "2026-01-26T02:43:43.042Z" }, - { url = "https://files.pythonhosted.org/packages/3d/20/6214d3c105928ebc353a1c644a6ef1408bc5794fcb4f170bb524a3c16311/multidict-6.7.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:10ae39c9cfe6adedcdb764f5e8411d4a92b055e35573a2eaa88d3323289ef93c", size = 253502, upload-time = "2026-01-26T02:43:44.371Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e2/c653bc4ae1be70a0f836b82172d643fcf1dade042ba2676ab08ec08bff0f/multidict-6.7.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:25167cc263257660290fba06b9318d2026e3c910be240a146e1f66dd114af2b0", size = 247065, upload-time = "2026-01-26T02:43:45.745Z" }, - { url = "https://files.pythonhosted.org/packages/c8/11/a854b4154cd3bd8b1fd375e8a8ca9d73be37610c361543d56f764109509b/multidict-6.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:128441d052254f42989ef98b7b6a6ecb1e6f708aa962c7984235316db59f50fa", size = 241870, upload-time = "2026-01-26T02:43:47.054Z" }, - { url = "https://files.pythonhosted.org/packages/13/bf/9676c0392309b5fdae322333d22a829715b570edb9baa8016a517b55b558/multidict-6.7.1-cp311-cp311-win32.whl", hash = "sha256:d62b7f64ffde3b99d06b707a280db04fb3855b55f5a06df387236051d0668f4a", size = 41302, upload-time = "2026-01-26T02:43:48.753Z" }, - { url = "https://files.pythonhosted.org/packages/c9/68/f16a3a8ba6f7b6dc92a1f19669c0810bd2c43fc5a02da13b1cbf8e253845/multidict-6.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdbf9f3b332abd0cdb306e7c2113818ab1e922dc84b8f8fd06ec89ed2a19ab8b", size = 45981, upload-time = "2026-01-26T02:43:49.921Z" }, - { url = "https://files.pythonhosted.org/packages/ac/ad/9dd5305253fa00cd3c7555dbef69d5bf4133debc53b87ab8d6a44d411665/multidict-6.7.1-cp311-cp311-win_arm64.whl", hash = "sha256:b8c990b037d2fff2f4e33d3f21b9b531c5745b33a49a7d6dbe7a177266af44f6", size = 43159, upload-time = "2026-01-26T02:43:51.635Z" }, { url = "https://files.pythonhosted.org/packages/8d/9c/f20e0e2cf80e4b2e4b1c365bf5fe104ee633c751a724246262db8f1a0b13/multidict-6.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a90f75c956e32891a4eda3639ce6dd86e87105271f43d43442a3aedf3cddf172", size = 76893, upload-time = "2026-01-26T02:43:52.754Z" }, { url = "https://files.pythonhosted.org/packages/fe/cf/18ef143a81610136d3da8193da9d80bfe1cb548a1e2d1c775f26b23d024a/multidict-6.7.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fccb473e87eaa1382689053e4a4618e7ba7b9b9b8d6adf2027ee474597128cd", size = 45456, upload-time = "2026-01-26T02:43:53.893Z" }, { url = "https://files.pythonhosted.org/packages/a9/65/1caac9d4cd32e8433908683446eebc953e82d22b03d10d41a5f0fefe991b/multidict-6.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0fa96985700739c4c7853a43c0b3e169360d6855780021bfc6d0f1ce7c123e7", size = 43872, upload-time = "2026-01-26T02:43:55.041Z" }, @@ -1189,17 +962,6 @@ version = "2.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/34/2b1bc18424f3ad9af577f6ce23600319968a70575bd7db31ce66731bbef9/numpy-2.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0cce2a669e3c8ba02ee563c7835f92c153cf02edff1ae05e1823f1dde21b16a5", size = 16944563, upload-time = "2026-01-10T06:42:14.615Z" }, - { url = "https://files.pythonhosted.org/packages/2c/57/26e5f97d075aef3794045a6ca9eada6a4ed70eb9a40e7a4a93f9ac80d704/numpy-2.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:899d2c18024984814ac7e83f8f49d8e8180e2fbe1b2e252f2e7f1d06bea92425", size = 12645658, upload-time = "2026-01-10T06:42:17.298Z" }, - { url = "https://files.pythonhosted.org/packages/8e/ba/80fc0b1e3cb2fd5c6143f00f42eb67762aa043eaa05ca924ecc3222a7849/numpy-2.4.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:09aa8a87e45b55a1c2c205d42e2808849ece5c484b2aab11fecabec3841cafba", size = 5474132, upload-time = "2026-01-10T06:42:19.637Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/0a5b9a397f0e865ec171187c78d9b57e5588afc439a04ba9cab1ebb2c945/numpy-2.4.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:edee228f76ee2dab4579fad6f51f6a305de09d444280109e0f75df247ff21501", size = 6804159, upload-time = "2026-01-10T06:42:21.44Z" }, - { url = "https://files.pythonhosted.org/packages/86/9c/841c15e691c7085caa6fd162f063eff494099c8327aeccd509d1ab1e36ab/numpy-2.4.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a92f227dbcdc9e4c3e193add1a189a9909947d4f8504c576f4a732fd0b54240a", size = 14708058, upload-time = "2026-01-10T06:42:23.546Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9d/7862db06743f489e6a502a3b93136d73aea27d97b2cf91504f70a27501d6/numpy-2.4.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:538bf4ec353709c765ff75ae616c34d3c3dca1a68312727e8f2676ea644f8509", size = 16651501, upload-time = "2026-01-10T06:42:25.909Z" }, - { url = "https://files.pythonhosted.org/packages/a6/9c/6fc34ebcbd4015c6e5f0c0ce38264010ce8a546cb6beacb457b84a75dfc8/numpy-2.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ac08c63cb7779b85e9d5318e6c3518b424bc1f364ac4cb2c6136f12e5ff2dccc", size = 16492627, upload-time = "2026-01-10T06:42:28.938Z" }, - { url = "https://files.pythonhosted.org/packages/aa/63/2494a8597502dacda439f61b3c0db4da59928150e62be0e99395c3ad23c5/numpy-2.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f9c360ecef085e5841c539a9a12b883dff005fbd7ce46722f5e9cef52634d82", size = 18585052, upload-time = "2026-01-10T06:42:31.312Z" }, - { url = "https://files.pythonhosted.org/packages/6a/93/098e1162ae7522fc9b618d6272b77404c4656c72432ecee3abc029aa3de0/numpy-2.4.1-cp311-cp311-win32.whl", hash = "sha256:0f118ce6b972080ba0758c6087c3617b5ba243d806268623dc34216d69099ba0", size = 6236575, upload-time = "2026-01-10T06:42:33.872Z" }, - { url = "https://files.pythonhosted.org/packages/8c/de/f5e79650d23d9e12f38a7bc6b03ea0835b9575494f8ec94c11c6e773b1b1/numpy-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:18e14c4d09d55eef39a6ab5b08406e84bc6869c1e34eef45564804f90b7e0574", size = 12604479, upload-time = "2026-01-10T06:42:35.778Z" }, - { url = "https://files.pythonhosted.org/packages/dd/65/e1097a7047cff12ce3369bd003811516b20ba1078dbdec135e1cd7c16c56/numpy-2.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:6461de5113088b399d655d45c3897fa188766415d0f568f175ab071c8873bd73", size = 10578325, upload-time = "2026-01-10T06:42:38.518Z" }, { url = "https://files.pythonhosted.org/packages/78/7f/ec53e32bf10c813604edf07a3682616bd931d026fcde7b6d13195dfb684a/numpy-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2", size = 16656888, upload-time = "2026-01-10T06:42:40.913Z" }, { url = "https://files.pythonhosted.org/packages/b8/e0/1f9585d7dae8f14864e948fd7fa86c6cb72dee2676ca2748e63b1c5acfe0/numpy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8", size = 12373956, upload-time = "2026-01-10T06:42:43.091Z" }, { url = "https://files.pythonhosted.org/packages/8e/43/9762e88909ff2326f5e7536fa8cb3c49fb03a7d92705f23e6e7f553d9cb3/numpy-2.4.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a", size = 5202567, upload-time = "2026-01-10T06:42:45.107Z" }, @@ -1211,13 +973,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/4c/14cb9d86240bd8c386c881bafbe43f001284b7cce3bc01623ac9475da163/numpy-2.4.1-cp312-cp312-win32.whl", hash = "sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f", size = 5959015, upload-time = "2026-01-10T06:42:59.631Z" }, { url = "https://files.pythonhosted.org/packages/51/cf/52a703dbeb0c65807540d29699fef5fda073434ff61846a564d5c296420f/numpy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87", size = 12310730, upload-time = "2026-01-10T06:43:01.627Z" }, { url = "https://files.pythonhosted.org/packages/69/80/a828b2d0ade5e74a9fe0f4e0a17c30fdc26232ad2bc8c9f8b3197cf7cf18/numpy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8", size = 10312166, upload-time = "2026-01-10T06:43:03.673Z" }, - { url = "https://files.pythonhosted.org/packages/1e/48/d86f97919e79314a1cdee4c832178763e6e98e623e123d0bada19e92c15a/numpy-2.4.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:8ad35f20be147a204e28b6a0575fbf3540c5e5f802634d4258d55b1ff5facce1", size = 16822202, upload-time = "2026-01-10T06:44:43.738Z" }, - { url = "https://files.pythonhosted.org/packages/51/e9/1e62a7f77e0f37dcfb0ad6a9744e65df00242b6ea37dfafb55debcbf5b55/numpy-2.4.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:8097529164c0f3e32bb89412a0905d9100bf434d9692d9fc275e18dcf53c9344", size = 12569985, upload-time = "2026-01-10T06:44:45.945Z" }, - { url = "https://files.pythonhosted.org/packages/c7/7e/914d54f0c801342306fdcdce3e994a56476f1b818c46c47fc21ae968088c/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ea66d2b41ca4a1630aae5507ee0a71647d3124d1741980138aa8f28f44dac36e", size = 5398484, upload-time = "2026-01-10T06:44:48.012Z" }, - { url = "https://files.pythonhosted.org/packages/1c/d8/9570b68584e293a33474e7b5a77ca404f1dcc655e40050a600dee81d27fb/numpy-2.4.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d3f8f0df9f4b8be57b3bf74a1d087fec68f927a2fab68231fdb442bf2c12e426", size = 6713216, upload-time = "2026-01-10T06:44:49.725Z" }, - { url = "https://files.pythonhosted.org/packages/33/9b/9dd6e2db8d49eb24f86acaaa5258e5f4c8ed38209a4ee9de2d1a0ca25045/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2023ef86243690c2791fd6353e5b4848eedaa88ca8a2d129f462049f6d484696", size = 14538937, upload-time = "2026-01-10T06:44:51.498Z" }, - { url = "https://files.pythonhosted.org/packages/53/87/d5bd995b0f798a37105b876350d346eea5838bd8f77ea3d7a48392f3812b/numpy-2.4.1-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8361ea4220d763e54cff2fbe7d8c93526b744f7cd9ddab47afeff7e14e8503be", size = 16479830, upload-time = "2026-01-10T06:44:53.931Z" }, - { url = "https://files.pythonhosted.org/packages/5b/c7/b801bf98514b6ae6475e941ac05c58e6411dd863ea92916bfd6d510b08c1/numpy-2.4.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:4f1b68ff47680c2925f8063402a693ede215f0257f02596b1318ecdfb1d79e33", size = 12492579, upload-time = "2026-01-10T06:44:57.094Z" }, ] [[package]] @@ -1232,12 +987,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0c/38/1a0e74d586c08833404100f5c052f92732fb5be417c0b2d7cb0838443bfe/onnx-1.20.1-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:53426e1b458641e7a537e9f176330012ff59d90206cac1c1a9d03cdd73ed3095", size = 17904965, upload-time = "2026-01-10T01:39:13.532Z" }, - { url = "https://files.pythonhosted.org/packages/96/25/64b076e9684d17335f80b15b3bf502f7a8e1a89f08a6b208d4f2861b3011/onnx-1.20.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca7281f8c576adf396c338cf43fff26faee8d4d2e2577b8e73738f37ceccf945", size = 17415179, upload-time = "2026-01-10T01:39:16.516Z" }, - { url = "https://files.pythonhosted.org/packages/ac/d5/6743b409421ced20ad5af1b3a7b4c4e568689ffaca86db431692fca409a6/onnx-1.20.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2297f428c51c7fc6d8fad0cf34384284dfeff3f86799f8e83ef905451348ade0", size = 17513672, upload-time = "2026-01-10T01:39:19.35Z" }, - { url = "https://files.pythonhosted.org/packages/9a/6b/dae82e6fdb2043302f29adca37522312ea2be55b75907b59be06fbdffe87/onnx-1.20.1-cp311-cp311-win32.whl", hash = "sha256:63d9cbcab8c96841eadeb7c930e07bfab4dde8081eb76fb68e0dfb222706b81e", size = 16239336, upload-time = "2026-01-10T01:39:22.506Z" }, - { url = "https://files.pythonhosted.org/packages/8e/17/a0d7863390c1f2067d7c02dcc1477034965c32aaa1407bfcf775305ffee4/onnx-1.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:d78cde72d7ca8356a2d99c5dc0dbf67264254828cae2c5780184486c0cd7b3bf", size = 16392120, upload-time = "2026-01-10T01:39:25.106Z" }, - { url = "https://files.pythonhosted.org/packages/aa/72/9b879a46eb7a3322223791f36bf9c25d95da9ed93779eabb75a560f22e5b/onnx-1.20.1-cp311-cp311-win_arm64.whl", hash = "sha256:0104bb2d4394c179bcea3df7599a45a2932b80f4633840896fcf0d7d8daecea2", size = 16346923, upload-time = "2026-01-10T01:39:27.782Z" }, { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, @@ -1317,7 +1066,6 @@ dev = [ { name = "parameterized" }, { name = "pyautogui" }, { name = "pywinctl" }, - { name = "tabulate" }, ] docs = [ { name = "jinja2" }, @@ -1403,7 +1151,6 @@ requires-dist = [ { name = "sounddevice" }, { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, - { name = "tabulate", marker = "extra == 'dev'" }, { name = "tqdm" }, { name = "ty", marker = "extra == 'testing'" }, { name = "websocket-client" }, @@ -1426,11 +1173,6 @@ name = "panda3d" version = "1.10.14" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/9a/31d07e3d7c1b40335e8418c540d63f4d33c571648ed8d69896ab778e65c3/panda3d-1.10.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:54b8ef9fe3684960a2c7d47b0d63c0354c17bc516795e59db6c1e5bda8c16c1c", size = 67700752, upload-time = "2024-01-08T19:05:55.559Z" }, - { url = "https://files.pythonhosted.org/packages/61/05/fce327535d8907ac01f43813c980f30ea86d37db62c340847519ea2ab222/panda3d-1.10.14-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:93414675894b18eea8d27edc1bbd1dc719eae207d45ec263d47195504bc4705f", size = 118966179, upload-time = "2024-01-08T19:06:03.165Z" }, - { url = "https://files.pythonhosted.org/packages/8a/54/24e205231e7b1bced58ba9620fbec7114673d821fc7ad9ed1804cab556b4/panda3d-1.10.14-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:d1bc0d926f90c8fa14a1587fa9dbe5f89a4eda8c9684fa183a8eaa35fc8e891a", size = 55145295, upload-time = "2024-01-08T19:06:10.319Z" }, - { url = "https://files.pythonhosted.org/packages/06/d3/38e989822292935d7473d35117099f71481cc6b104cb2dd048cb8058a5a8/panda3d-1.10.14-cp311-cp311-win32.whl", hash = "sha256:1039340a4a7965fe4c3e904edb4fff691584df435a154fecccf534587cd07a34", size = 53137177, upload-time = "2024-01-08T19:06:15.901Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/b16c81661ed0d8ad62976004d81845baa321e53314e253ef0841155be770/panda3d-1.10.14-cp311-cp311-win_amd64.whl", hash = "sha256:1ddf01040b9c5497fb8659e3c5ef793a26c869cfdfb1b75e6d04d6fba0c03b73", size = 64447666, upload-time = "2024-01-08T19:06:22.105Z" }, { url = "https://files.pythonhosted.org/packages/5a/d4/90e98993b1a3f3c9fae83267f8c51186e676a8c1365c4180dfc65cd7ba62/panda3d-1.10.14-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1bfbcee77779f12ecce6a3d5a856e573b25d6343f8c4b107e814d9702e70a65d", size = 67839196, upload-time = "2024-01-08T19:01:00.417Z" }, { url = "https://files.pythonhosted.org/packages/dc/e5/862821575073863ce49cc57b8349b47cb25ce11feae0e419b3d023ac1a69/panda3d-1.10.14-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:bc6540c5559d7e14a8992eff7de0157b7c42406b7ba221941ed224289496841c", size = 119271341, upload-time = "2024-01-08T19:01:09.455Z" }, { url = "https://files.pythonhosted.org/packages/f4/20/f16d91805777825e530037177d9075c83da7384f12b778b133e3164a31f3/panda3d-1.10.14-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:143daab1ce6bedcba711ea3f6cab0ebe5082f22c5f43e7178fadd2dd01209da7", size = 47604077, upload-time = "2024-05-28T20:25:37.118Z" }, @@ -1489,17 +1231,6 @@ version = "12.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/43/c4/bf8328039de6cc22182c3ef007a2abfbbdab153661c0a9aa78af8d706391/pillow-12.1.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:a83e0850cb8f5ac975291ebfc4170ba481f41a28065277f7f735c202cd8e0af3", size = 5304057, upload-time = "2026-01-02T09:10:46.627Z" }, - { url = "https://files.pythonhosted.org/packages/43/06/7264c0597e676104cc22ca73ee48f752767cd4b1fe084662620b17e10120/pillow-12.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6e53e82ec2db0717eabb276aa56cf4e500c9a7cec2c2e189b55c24f65a3e8c0", size = 4657811, upload-time = "2026-01-02T09:10:49.548Z" }, - { url = "https://files.pythonhosted.org/packages/72/64/f9189e44474610daf83da31145fa56710b627b5c4c0b9c235e34058f6b31/pillow-12.1.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:40a8e3b9e8773876d6e30daed22f016509e3987bab61b3b7fe309d7019a87451", size = 6232243, upload-time = "2026-01-02T09:10:51.62Z" }, - { url = "https://files.pythonhosted.org/packages/ef/30/0df458009be6a4caca4ca2c52975e6275c387d4e5c95544e34138b41dc86/pillow-12.1.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:800429ac32c9b72909c671aaf17ecd13110f823ddb7db4dfef412a5587c2c24e", size = 8037872, upload-time = "2026-01-02T09:10:53.446Z" }, - { url = "https://files.pythonhosted.org/packages/e4/86/95845d4eda4f4f9557e25381d70876aa213560243ac1a6d619c46caaedd9/pillow-12.1.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b022eaaf709541b391ee069f0022ee5b36c709df71986e3f7be312e46f42c84", size = 6345398, upload-time = "2026-01-02T09:10:55.426Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1f/8e66ab9be3aaf1435bc03edd1ebdf58ffcd17f7349c1d970cafe87af27d9/pillow-12.1.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1f345e7bc9d7f368887c712aa5054558bad44d2a301ddf9248599f4161abc7c0", size = 7034667, upload-time = "2026-01-02T09:10:57.11Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f6/683b83cb9b1db1fb52b87951b1c0b99bdcfceaa75febf11406c19f82cb5e/pillow-12.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d70347c8a5b7ccd803ec0c85c8709f036e6348f1e6a5bf048ecd9c64d3550b8b", size = 6458743, upload-time = "2026-01-02T09:10:59.331Z" }, - { url = "https://files.pythonhosted.org/packages/9a/7d/de833d63622538c1d58ce5395e7c6cb7e7dce80decdd8bde4a484e095d9f/pillow-12.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1fcc52d86ce7a34fd17cb04e87cfdb164648a3662a6f20565910a99653d66c18", size = 7159342, upload-time = "2026-01-02T09:11:01.82Z" }, - { url = "https://files.pythonhosted.org/packages/8c/40/50d86571c9e5868c42b81fe7da0c76ca26373f3b95a8dd675425f4a92ec1/pillow-12.1.0-cp311-cp311-win32.whl", hash = "sha256:3ffaa2f0659e2f740473bcf03c702c39a8d4b2b7ffc629052028764324842c64", size = 6328655, upload-time = "2026-01-02T09:11:04.556Z" }, - { url = "https://files.pythonhosted.org/packages/6c/af/b1d7e301c4cd26cd45d4af884d9ee9b6fab893b0ad2450d4746d74a6968c/pillow-12.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:806f3987ffe10e867bab0ddad45df1148a2b98221798457fa097ad85d6e8bc75", size = 7031469, upload-time = "2026-01-02T09:11:06.538Z" }, - { url = "https://files.pythonhosted.org/packages/48/36/d5716586d887fb2a810a4a61518a327a1e21c8b7134c89283af272efe84b/pillow-12.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:9f5fefaca968e700ad1a4a9de98bf0869a94e397fe3524c4c9450c1445252304", size = 2452515, upload-time = "2026-01-02T09:11:08.226Z" }, { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, @@ -1511,13 +1242,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, - { url = "https://files.pythonhosted.org/packages/8b/bc/224b1d98cffd7164b14707c91aac83c07b047fbd8f58eba4066a3e53746a/pillow-12.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ca94b6aac0d7af2a10ba08c0f888b3d5114439b6b3ef39968378723622fed377", size = 5228605, upload-time = "2026-01-02T09:13:14.084Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ca/49ca7769c4550107de049ed85208240ba0f330b3f2e316f24534795702ce/pillow-12.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:351889afef0f485b84078ea40fe33727a0492b9af3904661b0abbafee0355b72", size = 4622245, upload-time = "2026-01-02T09:13:15.964Z" }, - { url = "https://files.pythonhosted.org/packages/73/48/fac807ce82e5955bcc2718642b94b1bd22a82a6d452aea31cbb678cddf12/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb0984b30e973f7e2884362b7d23d0a348c7143ee559f38ef3eaab640144204c", size = 5247593, upload-time = "2026-01-02T09:13:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/d2/95/3e0742fe358c4664aed4fd05d5f5373dcdad0b27af52aa0972568541e3f4/pillow-12.1.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:84cabc7095dd535ca934d57e9ce2a72ffd216e435a84acb06b2277b1de2689bd", size = 6989008, upload-time = "2026-01-02T09:13:20.083Z" }, - { url = "https://files.pythonhosted.org/packages/5a/74/fe2ac378e4e202e56d50540d92e1ef4ff34ed687f3c60f6a121bcf99437e/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53d8b764726d3af1a138dd353116f774e3862ec7e3794e0c8781e30db0f35dfc", size = 5313824, upload-time = "2026-01-02T09:13:22.405Z" }, - { url = "https://files.pythonhosted.org/packages/f3/77/2a60dee1adee4e2655ac328dd05c02a955c1cd683b9f1b82ec3feb44727c/pillow-12.1.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5da841d81b1a05ef940a8567da92decaa15bc4d7dedb540a8c219ad83d91808a", size = 5963278, upload-time = "2026-01-02T09:13:24.706Z" }, - { url = "https://files.pythonhosted.org/packages/2d/71/64e9b1c7f04ae0027f788a248e6297d7fcc29571371fe7d45495a78172c0/pillow-12.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:75af0b4c229ac519b155028fa1be632d812a519abba9b46b20e50c6caa184f19", size = 7029809, upload-time = "2026-01-02T09:13:26.541Z" }, ] [[package]] @@ -1562,21 +1286,6 @@ version = "0.4.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/9e/da/e9fc233cf63743258bff22b3dfa7ea5baef7b5bc324af47a0ad89b8ffc6f/propcache-0.4.1.tar.gz", hash = "sha256:f48107a8c637e80362555f37ecf49abe20370e557cc4ab374f04ec4423c97c3d", size = 46442, upload-time = "2025-10-08T19:49:02.291Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/d4/4e2c9aaf7ac2242b9358f98dccd8f90f2605402f5afeff6c578682c2c491/propcache-0.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:60a8fda9644b7dfd5dece8c61d8a85e271cb958075bfc4e01083c148b61a7caf", size = 80208, upload-time = "2025-10-08T19:46:24.597Z" }, - { url = "https://files.pythonhosted.org/packages/c2/21/d7b68e911f9c8e18e4ae43bdbc1e1e9bbd971f8866eb81608947b6f585ff/propcache-0.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c30b53e7e6bda1d547cabb47c825f3843a0a1a42b0496087bb58d8fedf9f41b5", size = 45777, upload-time = "2025-10-08T19:46:25.733Z" }, - { url = "https://files.pythonhosted.org/packages/d3/1d/11605e99ac8ea9435651ee71ab4cb4bf03f0949586246476a25aadfec54a/propcache-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6918ecbd897443087a3b7cd978d56546a812517dcaaca51b49526720571fa93e", size = 47647, upload-time = "2025-10-08T19:46:27.304Z" }, - { url = "https://files.pythonhosted.org/packages/58/1a/3c62c127a8466c9c843bccb503d40a273e5cc69838805f322e2826509e0d/propcache-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d902a36df4e5989763425a8ab9e98cd8ad5c52c823b34ee7ef307fd50582566", size = 214929, upload-time = "2025-10-08T19:46:28.62Z" }, - { url = "https://files.pythonhosted.org/packages/56/b9/8fa98f850960b367c4b8fe0592e7fc341daa7a9462e925228f10a60cf74f/propcache-0.4.1-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a9695397f85973bb40427dedddf70d8dc4a44b22f1650dd4af9eedf443d45165", size = 221778, upload-time = "2025-10-08T19:46:30.358Z" }, - { url = "https://files.pythonhosted.org/packages/46/a6/0ab4f660eb59649d14b3d3d65c439421cf2f87fe5dd68591cbe3c1e78a89/propcache-0.4.1-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2bb07ffd7eaad486576430c89f9b215f9e4be68c4866a96e97db9e97fead85dc", size = 228144, upload-time = "2025-10-08T19:46:32.607Z" }, - { url = "https://files.pythonhosted.org/packages/52/6a/57f43e054fb3d3a56ac9fc532bc684fc6169a26c75c353e65425b3e56eef/propcache-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd6f30fdcf9ae2a70abd34da54f18da086160e4d7d9251f81f3da0ff84fc5a48", size = 210030, upload-time = "2025-10-08T19:46:33.969Z" }, - { url = "https://files.pythonhosted.org/packages/40/e2/27e6feebb5f6b8408fa29f5efbb765cd54c153ac77314d27e457a3e993b7/propcache-0.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fc38cba02d1acba4e2869eef1a57a43dfbd3d49a59bf90dda7444ec2be6a5570", size = 208252, upload-time = "2025-10-08T19:46:35.309Z" }, - { url = "https://files.pythonhosted.org/packages/9e/f8/91c27b22ccda1dbc7967f921c42825564fa5336a01ecd72eb78a9f4f53c2/propcache-0.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:67fad6162281e80e882fb3ec355398cf72864a54069d060321f6cd0ade95fe85", size = 202064, upload-time = "2025-10-08T19:46:36.993Z" }, - { url = "https://files.pythonhosted.org/packages/f2/26/7f00bd6bd1adba5aafe5f4a66390f243acab58eab24ff1a08bebb2ef9d40/propcache-0.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f10207adf04d08bec185bae14d9606a1444715bc99180f9331c9c02093e1959e", size = 212429, upload-time = "2025-10-08T19:46:38.398Z" }, - { url = "https://files.pythonhosted.org/packages/84/89/fd108ba7815c1117ddca79c228f3f8a15fc82a73bca8b142eb5de13b2785/propcache-0.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e9b0d8d0845bbc4cfcdcbcdbf5086886bc8157aa963c31c777ceff7846c77757", size = 216727, upload-time = "2025-10-08T19:46:39.732Z" }, - { url = "https://files.pythonhosted.org/packages/79/37/3ec3f7e3173e73f1d600495d8b545b53802cbf35506e5732dd8578db3724/propcache-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:981333cb2f4c1896a12f4ab92a9cc8f09ea664e9b7dbdc4eff74627af3a11c0f", size = 205097, upload-time = "2025-10-08T19:46:41.025Z" }, - { url = "https://files.pythonhosted.org/packages/61/b0/b2631c19793f869d35f47d5a3a56fb19e9160d3c119f15ac7344fc3ccae7/propcache-0.4.1-cp311-cp311-win32.whl", hash = "sha256:f1d2f90aeec838a52f1c1a32fe9a619fefd5e411721a9117fbf82aea638fe8a1", size = 38084, upload-time = "2025-10-08T19:46:42.693Z" }, - { url = "https://files.pythonhosted.org/packages/f4/78/6cce448e2098e9f3bfc91bb877f06aa24b6ccace872e39c53b2f707c4648/propcache-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:364426a62660f3f699949ac8c621aad6977be7126c5807ce48c0aeb8e7333ea6", size = 41637, upload-time = "2025-10-08T19:46:43.778Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e9/754f180cccd7f51a39913782c74717c581b9cc8177ad0e949f4d51812383/propcache-0.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:e53f3a38d3510c11953f3e6a33f205c6d1b001129f972805ca9b42fc308bc239", size = 38064, upload-time = "2025-10-08T19:46:44.872Z" }, { url = "https://files.pythonhosted.org/packages/a2/0f/f17b1b2b221d5ca28b4b876e8bb046ac40466513960646bda8e1853cdfa2/propcache-0.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e153e9cd40cc8945138822807139367f256f89c6810c2634a4f6902b52d3b4e2", size = 80061, upload-time = "2025-10-08T19:46:46.075Z" }, { url = "https://files.pythonhosted.org/packages/76/47/8ccf75935f51448ba9a16a71b783eb7ef6b9ee60f5d14c7f8a8a79fbeed7/propcache-0.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cd547953428f7abb73c5ad82cbb32109566204260d98e41e5dfdc682eb7f8403", size = 46037, upload-time = "2025-10-08T19:46:47.23Z" }, { url = "https://files.pythonhosted.org/packages/0a/b6/5c9a0e42df4d00bfb4a3cbbe5cf9f54260300c88a0e9af1f47ca5ce17ac0/propcache-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f048da1b4f243fc44f205dfd320933a951b8d89e0afd4c7cacc762a8b9165207", size = 47324, upload-time = "2025-10-08T19:46:48.384Z" }, @@ -1632,8 +1341,6 @@ version = "0.2.14" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/26/1d/8878c7752febb0f6716a7e1a52cb92ac98871c5aa522cba181878091607c/PyAudio-0.2.14.tar.gz", hash = "sha256:78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87", size = 47066, upload-time = "2023-11-07T07:11:48.806Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/f0/b0eab89eafa70a86b7b566a4df2f94c7880a2d483aa8de1c77d335335b5b/PyAudio-0.2.14-cp311-cp311-win32.whl", hash = "sha256:506b32a595f8693811682ab4b127602d404df7dfc453b499c91a80d0f7bad289", size = 144624, upload-time = "2023-11-07T07:11:36.94Z" }, - { url = "https://files.pythonhosted.org/packages/82/d8/f043c854aad450a76e476b0cf9cda1956419e1dacf1062eb9df3c0055abe/PyAudio-0.2.14-cp311-cp311-win_amd64.whl", hash = "sha256:bbeb01d36a2f472ae5ee5e1451cacc42112986abe622f735bb870a5db77cf903", size = 164070, upload-time = "2023-11-07T07:11:38.579Z" }, { url = "https://files.pythonhosted.org/packages/8d/45/8d2b76e8f6db783f9326c1305f3f816d4a12c8eda5edc6a2e1d03c097c3b/PyAudio-0.2.14-cp312-cp312-win32.whl", hash = "sha256:5fce4bcdd2e0e8c063d835dbe2860dac46437506af509353c7f8114d4bacbd5b", size = 144750, upload-time = "2023-11-07T07:11:40.142Z" }, { url = "https://files.pythonhosted.org/packages/b0/6a/d25812e5f79f06285767ec607b39149d02aa3b31d50c2269768f48768930/PyAudio-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3", size = 164126, upload-time = "2023-11-07T07:11:41.539Z" }, ] @@ -1660,18 +1367,6 @@ version = "2.1.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/15/86/a57e3c92acd3e1d2fc3dcad683ada191f722e4ac927e1a384b228ec2780a/pycapnp-2.1.0.tar.gz", hash = "sha256:69cc3d861fee1c9b26c73ad2e8a5d51e76ad87e4ff9be33a4fd2fc72f5846aec", size = 689734, upload-time = "2025-09-05T03:50:40.851Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/7c/934750a0ca77431a22e68e11521dcc6b801bea3ff37331d6a519e5ad142e/pycapnp-2.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:efacc439ec287d9e8a0ebf01a515404eff795659401e65ba6f1819c7b24f4380", size = 1628855, upload-time = "2025-09-05T03:48:32.317Z" }, - { url = "https://files.pythonhosted.org/packages/2e/a2/fd2c10b3f2e5010c747aa946b27fe09f665d65d5dc2afdd31838a3ef2f5d/pycapnp-2.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f3d8af535a8b44dfd71731a191386c6b821b8a4915806948893d18c79f547a8e", size = 1496942, upload-time = "2025-09-05T03:48:34.905Z" }, - { url = "https://files.pythonhosted.org/packages/0b/8a/42bd0e4c094ef534ac6890d34adae580cbbf5b0497fc0a6340bea833a617/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:117d1d5ebfc08cc189aca4f771b34fedc1291a3f9417167bd2d9b2a4e607e640", size = 5200170, upload-time = "2025-09-05T03:48:36.502Z" }, - { url = "https://files.pythonhosted.org/packages/af/30/2e92268383135082191c3dea4a9ad184d20b7fb2dda1477fd6ee520fd88e/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_ppc64le.whl", hash = "sha256:d881ccc69e381863a88c7b6c7092a6baecb6dfc8c5558d66bc967c7f778fe7bc", size = 5684026, upload-time = "2025-09-05T03:48:38.063Z" }, - { url = "https://files.pythonhosted.org/packages/46/9c/bca1cbd7711c9c0f0f62ca95a49835369a61c4f6527a6900c8982045bf2f/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_s390x.whl", hash = "sha256:8a4ea330e38ba83f6f03fbdc1f58642eb53e6f6f66734a426fa592dc988d70e9", size = 5709307, upload-time = "2025-09-05T03:48:40.127Z" }, - { url = "https://files.pythonhosted.org/packages/2d/29/cd14676d992c7b166baa7e022b369c15240d408b202410d105b23b25f737/pycapnp-2.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:fb2563de4619d359820de9d47b4704e4f7eda193ffc4a56e39cdcd2c8301c336", size = 5386505, upload-time = "2025-09-05T03:48:41.785Z" }, - { url = "https://files.pythonhosted.org/packages/ae/dd/2fc57cebe9be7e4cd3d6aec0b9c8a0db9772c1b17c37cfe4f04c050422cf/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5265d1ae34f9c089fa6983f6c1be404ce480c82b927017290bd703328fa3f5df", size = 6095180, upload-time = "2025-09-05T03:48:43.795Z" }, - { url = "https://files.pythonhosted.org/packages/5a/16/da8c1ada7770a532c859df475533eec5a1b2f5e81a269466a2fe670c5747/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b0a56370a868f752375a785bfb7e06b55cbe71605972615d1220c380bc452380", size = 6603414, upload-time = "2025-09-05T03:48:45.457Z" }, - { url = "https://files.pythonhosted.org/packages/f0/e6/a36eacaf2da6a5ac9c6565600e559edf95115ff990aa3379aee8dd7ba4fe/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5d7403c25275cf4badf6f9d0c07b1cb94fcdd599df81aba9b621c32b3dcefae9", size = 6621440, upload-time = "2025-09-05T03:48:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/81/54/9150c03638cf4ecdf1664867382d0049146c658d6de30f189817c502df1a/pycapnp-2.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dea5d0d250fe4851b42cd380a207d773ebae76a990e542a888a5f1442f4c247e", size = 6354219, upload-time = "2025-09-05T03:48:49.336Z" }, - { url = "https://files.pythonhosted.org/packages/66/3e/e49ba2d74456d53b570c8d30a660c3b29ecfea075d5dd663132ff9049f19/pycapnp-2.1.0-cp311-cp311-win32.whl", hash = "sha256:593844c3cd92937eb5e7cd47ea3a62cde2d49a1fc05dba644f513c68f60f1318", size = 1053647, upload-time = "2025-09-05T03:48:51.108Z" }, - { url = "https://files.pythonhosted.org/packages/53/de/2b61908dc6abf25b17fed6b5a3b42a2226ec09467a3944f1d845ac29ef9b/pycapnp-2.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:ac13dd30062bb9985ae9ec4feca106af2b4fdac6468a09c7b74ad754f3921a06", size = 1208911, upload-time = "2025-09-05T03:48:53.219Z" }, { url = "https://files.pythonhosted.org/packages/74/0e/66b41ba600e5f2523e900b7cc0d2e8496b397a1f2d6a5b7b323ab83418b7/pycapnp-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d2ec561bc948d11f64f43bf9601bede5d6a603d105ae311bd5583c7130624a4", size = 1619223, upload-time = "2025-09-05T03:48:54.64Z" }, { url = "https://files.pythonhosted.org/packages/40/6e/9bcb30180bd40cb0534124ff7f8ba8746a735018d593f608bf40c97821c0/pycapnp-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132cd97f57f6b6636323ca9b68d389dd90b96e87af38cde31e2b5c5a064f277e", size = 1484321, upload-time = "2025-09-05T03:48:55.85Z" }, { url = "https://files.pythonhosted.org/packages/14/0a/9ee1c9ecaff499e4fd1df2f0335bc20f666ec6ce5cd80f8ab055007f3c9b/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:568e79268ba7c02a71fe558a8aec1ae3c0f0e6aff809ff618a46afe4964957d2", size = 5143502, upload-time = "2025-09-05T03:48:57.733Z" }, @@ -1982,7 +1677,6 @@ version = "12.1" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/df/d2b290708e9da86d6e7a9a2a2022b91915cf2e712a5a82e306cb6ee99792/pyobjc_core-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c918ebca280925e7fcb14c5c43ce12dcb9574a33cccb889be7c8c17f3bcce8b6", size = 671263, upload-time = "2025-11-14T09:31:35.231Z" }, { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, ] @@ -1997,7 +1691,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/2d/87/8ca40428d05a668fecc638f2f47dba86054dbdc35351d247f039749de955/pyobjc_framework_accessibility-12.1.tar.gz", hash = "sha256:5ff362c3425edc242d49deec11f5f3e26e565cefb6a2872eda59ab7362149772", size = 29800, upload-time = "2025-11-14T10:08:31.949Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/00/182c57584ad8e5946a82dacdc83c9791567e10bffdea1fe92272b3fdec14/pyobjc_framework_accessibility-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5e29dac0ce8327cd5a8b9a5a8bd8aa83e4070018b93699e97ac0c3af09b42a9a", size = 11301, upload-time = "2025-11-14T09:35:28.678Z" }, { url = "https://files.pythonhosted.org/packages/cc/95/9ea0d1c16316b4b5babf4b0515e9a133ac64269d3ec031f15ee9c7c2a8c1/pyobjc_framework_accessibility-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:537691a0b28fedb8385cd093df069a6e5d7e027629671fc47b50210404eca20b", size = 11335, upload-time = "2025-11-14T09:35:30.81Z" }, ] @@ -2024,7 +1717,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/18/28/0404af2a1c6fa8fd266df26fb6196a8f3fb500d6fe3dab94701949247bea/pyobjc_framework_addressbook-12.1.tar.gz", hash = "sha256:c48b740cf981103cef1743d0804a226d86481fcb839bd84b80e9a586187e8000", size = 44359, upload-time = "2025-11-14T10:08:37.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/5a/2ecaa94e5f56c6631f0820ec4209f8075c1b7561fe37495e2d024de1c8df/pyobjc_framework_addressbook-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:681755ada6c95bd4a096bc2b9f9c24661ffe6bff19a96963ee3fad34f3d61d2b", size = 12879, upload-time = "2025-11-14T09:35:45.21Z" }, { url = "https://files.pythonhosted.org/packages/b6/33/da709c69cbb60df9522cd614d5c23c15b649b72e5d62fed1048e75c70e7b/pyobjc_framework_addressbook-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7893dd784322f4674299fb3ca40cb03385e5eddb78defd38f08c0b730813b56c", size = 12894, upload-time = "2025-11-14T09:35:47.498Z" }, ] @@ -2092,7 +1784,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/17/86/d07eff705ff909a0ffa96d14fc14026e9fc9dd716233648c53dfd5056b8e/pyobjc_framework_applicationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bdddd492eeac6d14ff2f5bd342aba29e30dffa72a2d358c08444da22129890e2", size = 32784, upload-time = "2025-11-14T09:36:08.755Z" }, { url = "https://files.pythonhosted.org/packages/37/a7/55fa88def5c02732c4b747606ff1cbce6e1f890734bbd00f5596b21eaa02/pyobjc_framework_applicationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8f6e2fb3b3e9214ab4864ef04eee18f592b46a986c86ea0113448b310520532", size = 32835, upload-time = "2025-11-14T09:36:11.855Z" }, ] @@ -2132,7 +1823,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9f/51/f81581e7a3c5cb6c9254c6f1e1ee1d614930493761dec491b5b0d49544b9/pyobjc_framework_audiovideobridging-12.1.tar.gz", hash = "sha256:6230ace6bec1f38e8a727c35d054a7be54e039b3053f98e6dd8d08d6baee2625", size = 38457, upload-time = "2025-11-14T10:09:01.122Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/f8/c614630fa382720bbd42a0ff567378630c36d10f114476d6c70b73f73b49/pyobjc_framework_audiovideobridging-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6bc24a7063b08c7d9f1749a4641430d363b6dba642c04d09b58abcee7a5260cb", size = 11037, upload-time = "2025-11-14T09:36:32.583Z" }, { url = "https://files.pythonhosted.org/packages/f3/8e/a28badfcc6c731696e3d3a8a83927bd844d992f9152f903c2fee355702ca/pyobjc_framework_audiovideobridging-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:010021502649e2cca4e999a7c09358d48c6b0ed83530bbc0b85bba6834340e4b", size = 11052, upload-time = "2025-11-14T09:36:34.475Z" }, ] @@ -2146,7 +1836,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6c/18/86218de3bf67fc1d810065f353d9df70c740de567ebee8550d476cb23862/pyobjc_framework_authenticationservices-12.1.tar.gz", hash = "sha256:cef71faeae2559f5c0ff9a81c9ceea1c81108e2f4ec7de52a98c269feff7a4b6", size = 58683, upload-time = "2025-11-14T10:09:06.003Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/16/2f19d8a95f0cf8e940f7b7fb506ced805d5522b4118336c8e640c34517ae/pyobjc_framework_authenticationservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c15bb81282356f3f062ac79ff4166c93097448edc44b17dcf686e1dac78cc832", size = 20636, upload-time = "2025-11-14T09:36:48.35Z" }, { url = "https://files.pythonhosted.org/packages/f1/1d/e9f296fe1ee9a074ff6c45ce9eb109fc3b45696de000f373265c8e42fd47/pyobjc_framework_authenticationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6fd5ce10fe5359cbbfe03eb12cab3e01992b32ab65653c579b00ac93cf674985", size = 20738, upload-time = "2025-11-14T09:36:51.094Z" }, ] @@ -2160,7 +1849,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/e4/24/080afe8189c47c4bb3daa191ccfd962400ca31a67c14b0f7c2d002c2e249/pyobjc_framework_automaticassessmentconfiguration-12.1.tar.gz", hash = "sha256:2b732c02d9097682ca16e48f5d3b10056b740bc091e217ee4d5715194c8970b1", size = 21895, upload-time = "2025-11-14T10:09:08.779Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/c9/4d2785565cc470daa222f93f3d332af97de600aef6bd23507ec07501999d/pyobjc_framework_automaticassessmentconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d94a4a3beb77b3b2ab7b610c4b41e28593d15571724a9e6ab196b82acc98dc13", size = 9316, upload-time = "2025-11-14T09:37:05.052Z" }, { url = "https://files.pythonhosted.org/packages/f2/b2/fbec3d649bf275d7a9604e5f56015be02ef8dcf002f4ae4d760436b8e222/pyobjc_framework_automaticassessmentconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c2e22ea67d7e6d6a84d968169f83d92b59857a49ab12132de07345adbfea8a62", size = 9332, upload-time = "2025-11-14T09:37:07.083Z" }, ] @@ -2174,7 +1862,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/7e/08/362bf6ac2bba393c46cf56078d4578b692b56857c385e47690637a72f0dd/pyobjc_framework_automator-12.1.tar.gz", hash = "sha256:7491a99347bb30da3a3f744052a03434ee29bee3e2ae520576f7e796740e4ba7", size = 186068, upload-time = "2025-11-14T10:09:20.82Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/99/480e07eef053a2ad2a5cf1e15f71982f21d7f4119daafac338fa0352309c/pyobjc_framework_automator-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f3d96da10d28c5c197193a9d805a13157b1cb694b6c535983f8572f5f8746ea", size = 10016, upload-time = "2025-11-14T09:37:18.621Z" }, { url = "https://files.pythonhosted.org/packages/e3/36/2e8c36ddf20d501f9d344ed694e39021190faffc44b596f3a430bf437174/pyobjc_framework_automator-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4df9aec77f0fbca66cd3534d1b8398fe6f3e3c2748c0fc12fec2546c7f2e3ffd", size = 10034, upload-time = "2025-11-14T09:37:20.293Z" }, ] @@ -2191,7 +1878,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cd/42/c026ab308edc2ed5582d8b4b93da6b15d1b6557c0086914a4aabedd1f032/pyobjc_framework_avfoundation-12.1.tar.gz", hash = "sha256:eda0bb60be380f9ba2344600c4231dd58a3efafa99fdc65d3673ecfbb83f6fcb", size = 310047, upload-time = "2025-11-14T10:09:40.069Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/5a/4ef36b309138840ff8cd85364f66c29e27023f291004c335a99f6e87e599/pyobjc_framework_avfoundation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:82cc2c2d9ab6cc04feeb4700ff251d00f1fcafff573c63d4e87168ff80adb926", size = 83328, upload-time = "2025-11-14T09:37:40.808Z" }, { url = "https://files.pythonhosted.org/packages/a6/00/ca471e5dd33f040f69320832e45415d00440260bf7f8221a9df4c4662659/pyobjc_framework_avfoundation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bf634f89265b4d93126153200d885b6de4859ed6b3bc65e69ff75540bc398406", size = 83375, upload-time = "2025-11-14T09:37:47.262Z" }, ] @@ -2206,7 +1892,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/34/a9/e44db1a1f26e2882c140f1d502d508b1f240af9048909dcf1e1a687375b4/pyobjc_framework_avkit-12.1.tar.gz", hash = "sha256:a5c0ddb0cb700f9b09c8afeca2c58952d554139e9bb078236d2355b1fddfb588", size = 28473, upload-time = "2025-11-14T10:09:43.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/68/409ee30f3418b76573c70aa05fa4c38e9b8b1d4864093edcc781d66019c2/pyobjc_framework_avkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:78bd31a8aed48644e5407b444dec8b1e15ff77af765607b52edf88b8f1213ac7", size = 11583, upload-time = "2025-11-14T09:38:17.569Z" }, { url = "https://files.pythonhosted.org/packages/75/34/e77b18f7ed0bd707afd388702e910bdf2d0acee39d1139e8619c916d3eb4/pyobjc_framework_avkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eef2c0a51465de025a4509db05ef18ca2b678bb00ee0a8fbad7fd470edfd58f9", size = 11613, upload-time = "2025-11-14T09:38:19.78Z" }, ] @@ -2220,7 +1905,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6e/83/15bf6c28ec100dae7f92d37c9e117b3b4ee6b4873db062833e16f1cfd6c4/pyobjc_framework_avrouting-12.1.tar.gz", hash = "sha256:6a6c5e583d14f6501df530a9d0559a32269a821fc8140e3646015f097155cd1c", size = 20031, upload-time = "2025-11-14T10:09:45.701Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/a7/5c5725db9c91b492ffbd4ae3e40025deeb9e60fcc7c8fbd5279b52280b95/pyobjc_framework_avrouting-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a79f05fb66e337cabc19a9d949c8b29a5145c879f42e29ba02b601b7700d1bb", size = 8431, upload-time = "2025-11-14T09:38:33.018Z" }, { url = "https://files.pythonhosted.org/packages/68/54/fa24f666525c1332a11b2de959c9877b0fe08f00f29ecf96964b24246c13/pyobjc_framework_avrouting-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c0fb0d3d260527320377a70c87688ca5e4a208b09fddcae2b4257d7fe9b1e18", size = 8450, upload-time = "2025-11-14T09:38:34.941Z" }, ] @@ -2234,7 +1918,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/34/d1/e917fba82790495152fd3508c5053827658881cf7e9887ba60def5e3f221/pyobjc_framework_backgroundassets-12.1.tar.gz", hash = "sha256:8da34df9ae4519c360c429415477fdaf3fbba5addbc647b3340b8783454eb419", size = 26210, upload-time = "2025-11-14T10:09:48.792Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/49/33c1c3eaf26a7d89dd414e14939d4f02063d66252d0f51c02082350223e0/pyobjc_framework_backgroundassets-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:17de7990b5ea8047d447339f9e9e6f54b954ffc06647c830932a1688c4743fea", size = 10763, upload-time = "2025-11-14T09:38:46.671Z" }, { url = "https://files.pythonhosted.org/packages/de/34/bbba61f0e8ecb0fe0da7aa2c9ea15f7cb0dca2fb2914fcdcd77b782b5c11/pyobjc_framework_backgroundassets-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2c11cb98650c1a4bc68eeb4b040541ba96613434c5957e98e9bb363413b23c91", size = 10786, upload-time = "2025-11-14T09:38:48.341Z" }, ] @@ -2251,7 +1934,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/5d/b9/39f9de1730e6f8e73be0e4f0c6087cd9439cbe11645b8052d22e1fb8e69b/pyobjc_framework_browserenginekit-12.1.tar.gz", hash = "sha256:6a1a34a155778ab55ab5f463e885f2a3b4680231264e1fe078e62ddeccce49ed", size = 29120, upload-time = "2025-11-14T10:09:51.582Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/a4/2d576d71b2e4b3e1a9aa9fd62eb73167d90cdc2e07b425bbaba8edd32ff5/pyobjc_framework_browserenginekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:41229c766fb3e5bba2de5e580776388297303b4d63d3065fef3f67b77ec46c3f", size = 11526, upload-time = "2025-11-14T09:38:58.861Z" }, { url = "https://files.pythonhosted.org/packages/46/e0/8d2cebbfcfd6aacb805ae0ae7ba931f6a39140540b2e1e96719e7be28359/pyobjc_framework_browserenginekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d15766bb841b081447015c9626e2a766febfe651f487893d29c5d72bef976b94", size = 11545, upload-time = "2025-11-14T09:39:00.988Z" }, ] @@ -2291,7 +1973,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/1a/c0/1859d4532d39254df085309aff55b85323576f00a883626325af40da4653/pyobjc_framework_callkit-12.1.tar.gz", hash = "sha256:fd6dc9688b785aab360139d683be56f0844bf68bf5e45d0eb770cb68221083cc", size = 29171, upload-time = "2025-11-14T10:10:01.336Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/f6/aafd14b31e00d59d830f9a8e8e46c4f41a249f0370499d5b017599362cf1/pyobjc_framework_callkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e73beae08e6a32bcced8d5bdb45b52d6a0866dd1485eaaddba6063f17d41fcb0", size = 11273, upload-time = "2025-11-14T09:39:16.837Z" }, { url = "https://files.pythonhosted.org/packages/2e/b7/b3a498b14751b4be6af5272c9be9ded718aa850ebf769b052c7d610a142a/pyobjc_framework_callkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12adc0ace464a057f8908187698e1d417c6c53619797a69d096f4329bffb1089", size = 11334, upload-time = "2025-11-14T09:39:18.622Z" }, ] @@ -2318,7 +1999,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d2/6a/f5f0f191956e187db85312cbffcc41bf863670d121b9190b4a35f0d36403/pyobjc_framework_cfnetwork-12.1.tar.gz", hash = "sha256:2d16e820f2d43522c793f55833fda89888139d7a84ca5758548ba1f3a325a88d", size = 44383, upload-time = "2025-11-14T10:10:08.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/7e/82aca783499b690163dd19d5ccbba580398970874a3431bfd7c14ceddbb3/pyobjc_framework_cfnetwork-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3bf93c0f3d262f629e72f8dd43384d0930ed8e610b3fc5ff555c0c1a1e05334a", size = 18949, upload-time = "2025-11-14T09:39:32.924Z" }, { url = "https://files.pythonhosted.org/packages/f9/0b/28034e63f3a25b30ede814469c3f57d44268cbced19664c84a8664200f9d/pyobjc_framework_cfnetwork-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:92760da248c757085fc39bce4388a0f6f0b67540e51edf60a92ad60ca907d071", size = 19135, upload-time = "2025-11-14T09:39:36.382Z" }, ] @@ -2348,7 +2028,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ac/ef/67815278023b344a79c7e95f748f647245d6f5305136fc80615254ad447c/pyobjc_framework_classkit-12.1.tar.gz", hash = "sha256:8d1e9dd75c3d14938ff533d88b72bca2d34918e4461f418ea323bfb2498473b4", size = 26298, upload-time = "2025-11-14T10:10:13.406Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/14/e2/67bd062fbc9761c34b9911ed099ee50ccddc3032779ce420ca40083ee15c/pyobjc_framework_classkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bd90aacc68eff3412204a9040fa81eb18348cbd88ed56d33558349f3e51bff52", size = 8857, upload-time = "2025-11-14T09:39:53.283Z" }, { url = "https://files.pythonhosted.org/packages/87/5e/cf43c647af872499fc8e80cc6ac6e9ad77d9c77861dc2e62bdd9b01473ce/pyobjc_framework_classkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c027a3cd9be5fee3f605589118b8b278297c384a271f224c1a98b224e0c087e6", size = 8877, upload-time = "2025-11-14T09:39:54.979Z" }, ] @@ -2377,7 +2056,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/07/5760735c0fffc65107e648eaf7e0991f46da442ac4493501be5380e6d9d4/pyobjc_framework_cocoa-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52228bcf38da64b77328787967d464e28b981492b33a7675585141e1b0a01e6", size = 383812, upload-time = "2025-11-14T09:40:53.169Z" }, { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, ] @@ -2431,7 +2109,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/4b/a0/ce0542d211d4ea02f5cbcf72ee0a16b66b0d477a4ba5c32e00117703f2f0/pyobjc_framework_contacts-12.1.tar.gz", hash = "sha256:89bca3c5cf31404b714abaa1673577e1aaad6f2ef49d4141c6dbcc0643a789ad", size = 42378, upload-time = "2025-11-14T10:13:14.203Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/f5/5d2c03cf5219f2e35f3f908afa11868e9096aff33b29b41d63f2de3595f2/pyobjc_framework_contacts-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8ab86070895a005239256d207e18209b1a79d35335b6604db160e8375a7165e6", size = 12086, upload-time = "2025-11-14T09:43:03.225Z" }, { url = "https://files.pythonhosted.org/packages/32/c8/2c4638c0d06447886a34070eebb9ba57407d4dd5f0fcb7ab642568272b88/pyobjc_framework_contacts-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2e5ce33b686eb9c0a39351938a756442ea8dea88f6ae2f16bff5494a8569c687", size = 12165, upload-time = "2025-11-14T09:43:05.119Z" }, ] @@ -2446,7 +2123,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cd/0c/7bb7f898456a81d88d06a1084a42e374519d2e40a668a872b69b11f8c1f9/pyobjc_framework_contactsui-12.1.tar.gz", hash = "sha256:aaeca7c9e0c9c4e224d73636f9a558f9368c2c7422155a41fd4d7a13613a77c1", size = 18769, upload-time = "2025-11-14T10:13:16.301Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/e3/8d330640bf0337289834334c54c599fec2dad38a8a3b736d40bcb5d8db6e/pyobjc_framework_contactsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:10e7ce3b105795919605be89ebeecffd656e82dbf1bafa5db6d51d6def2265ee", size = 7871, upload-time = "2025-11-14T09:43:16.973Z" }, { url = "https://files.pythonhosted.org/packages/f3/ab/319aa52dfe6f836f4dc542282c2c13996222d4f5c9ea7ff8f391b12dac83/pyobjc_framework_contactsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:057f40d2f6eb1b169a300675ec75cc7a747cddcbcee8ece133e652a7086c5ab5", size = 7888, upload-time = "2025-11-14T09:43:18.502Z" }, ] @@ -2460,7 +2136,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/84/d1/0b884c5564ab952ff5daa949128c64815300556019c1bba0cf2ca752a1a0/pyobjc_framework_coreaudio-12.1.tar.gz", hash = "sha256:a9e72925fcc1795430496ce0bffd4ddaa92c22460a10308a7283ade830089fe1", size = 75077, upload-time = "2025-11-14T10:13:22.345Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/25/491ff549fd9a40be4416793d335bff1911d3d1d1e1635e3b0defbd2cf585/pyobjc_framework_coreaudio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a452de6b509fa4a20160c0410b72330ac871696cd80237883955a5b3a4de8f2a", size = 35327, upload-time = "2025-11-14T09:43:32.523Z" }, { url = "https://files.pythonhosted.org/packages/a9/48/05b5192122e23140cf583eac99ccc5bf615591d6ff76483ba986c38ee750/pyobjc_framework_coreaudio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a5ad6309779663f846ab36fe6c49647e470b7e08473c3e48b4f004017bdb68a4", size = 36908, upload-time = "2025-11-14T09:43:36.108Z" }, ] @@ -2475,7 +2150,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/41/1c/5c7e39b9361d4eec99b9115b593edd9825388acd594cb3b4519f8f1ac12c/pyobjc_framework_coreaudiokit-12.1.tar.gz", hash = "sha256:b83624f8de3068ab2ca279f786be0804da5cf904ff9979d96007b69ef4869e1e", size = 20137, upload-time = "2025-11-14T10:13:24.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/53/e4233fbe5b94b124f5612e1edc130a9280c4674a1d1bf42079ea14b816e1/pyobjc_framework_coreaudiokit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e1144c272f8d6429a34a6757700048f4631eb067c4b08d4768ddc28c371a7014", size = 7250, upload-time = "2025-11-14T09:43:53.208Z" }, { url = "https://files.pythonhosted.org/packages/19/d7/f171c04c6496afeaad2ab658b0c810682c8407127edc94d4b3f3b90c2bb1/pyobjc_framework_coreaudiokit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:97d5dd857e73d5b597cfc980972b021314b760e2f5bdde7bbba0334fbf404722", size = 7273, upload-time = "2025-11-14T09:43:55.411Z" }, ] @@ -2489,7 +2163,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/4b/25/d21d6cb3fd249c2c2aa96ee54279f40876a0c93e7161b3304bf21cbd0bfe/pyobjc_framework_corebluetooth-12.1.tar.gz", hash = "sha256:8060c1466d90bbb9100741a1091bb79975d9ba43911c9841599879fc45c2bbe0", size = 33157, upload-time = "2025-11-14T10:13:28.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/7a/26ae106beb97e9c4745065edb3ce3c2bdd91d81f5b52b8224f82ce9d5fb9/pyobjc_framework_corebluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:37e6456c8a076bd5a2bdd781d0324edd5e7397ef9ac9234a97433b522efb13cf", size = 13189, upload-time = "2025-11-14T09:44:06.229Z" }, { url = "https://files.pythonhosted.org/packages/2a/56/01fef62a479cdd6ff9ee40b6e062a205408ff386ce5ba56d7e14a71fcf73/pyobjc_framework_corebluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe72c9732ee6c5c793b9543f08c1f5bdd98cd95dfc9d96efd5708ec9d6eeb213", size = 13209, upload-time = "2025-11-14T09:44:08.203Z" }, ] @@ -2503,7 +2176,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3e/c5/8cd46cd4f1b7cf88bdeed3848f830ea9cdcc4e55cd0287a968a2838033fb/pyobjc_framework_coredata-12.1.tar.gz", hash = "sha256:1e47d3c5e51fdc87a90da62b97cae1bc49931a2bb064db1305827028e1fc0ffa", size = 124348, upload-time = "2025-11-14T10:13:36.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/a8/4c694c85365071baef36013a7460850dcf6ebfea0ba239e52d7293cdcb93/pyobjc_framework_coredata-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c861dc42b786243cbd96d9ea07d74023787d03637ef69a2f75a1191a2f16d9d6", size = 16395, upload-time = "2025-11-14T09:44:21.105Z" }, { url = "https://files.pythonhosted.org/packages/a3/29/fe24dc81e0f154805534923a56fe572c3b296092f086cf5a239fccc2d46a/pyobjc_framework_coredata-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3ee3581ca23ead0b152257e98622fe0bf7e7948f30a62a25a17cafe28fe015e", size = 16409, upload-time = "2025-11-14T09:44:23.582Z" }, ] @@ -2530,7 +2202,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cc/79/b75885e0d75397dc2fe1ed9ca80be2b64c18b817f5fb924277cb1bf7b163/pyobjc_framework_corelocation-12.1.tar.gz", hash = "sha256:3674e9353f949d91dde6230ad68f6d5748a7f0424751e08a2c09d06050d66231", size = 53511, upload-time = "2025-11-14T10:13:43.384Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/ac/44b6cb414ce647da8328d0ed39f0a8b6eb54e72189ce9049678ce2cb04c3/pyobjc_framework_corelocation-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ffc96b9ba504b35fe3e0fcfb0153e68fdfca6fe71663d240829ceab2d7122588", size = 12700, upload-time = "2025-11-14T09:44:38.717Z" }, { url = "https://files.pythonhosted.org/packages/71/57/1b670890fbf650f1a00afe5ee897ea3856a4a1417c2304c633ee2e978ed0/pyobjc_framework_corelocation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c35ad29a062fea7d417fd8997a9309660ba7963f2847c004e670efbe6bb5b00", size = 12721, upload-time = "2025-11-14T09:44:41.185Z" }, ] @@ -2544,7 +2215,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/da/7d/5ad600ff7aedfef8ba8f51b11d9aaacdf247b870bd14045d6e6f232e3df9/pyobjc_framework_coremedia-12.1.tar.gz", hash = "sha256:166c66a9c01e7a70103f3ca44c571431d124b9070612ef63a1511a4e6d9d84a7", size = 89566, upload-time = "2025-11-14T10:13:49.788Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/bc/e66de468b3777d8fece69279cf6d2af51d2263e9a1ccad21b90c35c74b1b/pyobjc_framework_coremedia-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ee7b822c9bb674b5b0a70bfb133410acae354e9241b6983f075395f3562f3c46", size = 29503, upload-time = "2025-11-14T09:44:54.716Z" }, { url = "https://files.pythonhosted.org/packages/c8/ae/f773cdc33c34a3f9ce6db829dbf72661b65c28ea9efaec8940364185b977/pyobjc_framework_coremedia-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:161a627f5c8cd30a5ebb935189f740e21e6cd94871a9afd463efdb5d51e255fa", size = 29396, upload-time = "2025-11-14T09:44:57.563Z" }, ] @@ -2558,7 +2228,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/08/8e/23baee53ccd6c011c965cff62eb55638b4088c3df27d2bf05004105d6190/pyobjc_framework_coremediaio-12.1.tar.gz", hash = "sha256:880b313b28f00b27775d630174d09e0b53d1cdbadb74216618c9dd5b3eb6806a", size = 51100, upload-time = "2025-11-14T10:13:54.277Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/6c/88514f8938719f74aa13abb9fd5492499f1834391133809b4e125c3e7150/pyobjc_framework_coremediaio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3da79c5b9785c5ccc1f5982de61d4d0f1ba29717909eb6720734076ccdc0633c", size = 17218, upload-time = "2025-11-14T09:45:15.294Z" }, { url = "https://files.pythonhosted.org/packages/d4/0c/9425c53c9a8c26e468e065ba12ef076bab20197ff7c82052a6dddd46d42b/pyobjc_framework_coremediaio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1108f8a278928fbca465f95123ea4a56456bd6571c1dc8b91793e6c61d624517", size = 17277, upload-time = "2025-11-14T09:45:17.457Z" }, ] @@ -2572,7 +2241,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/75/96/2d583060a71a73c8a7e6d92f2a02675621b63c1f489f2639e020fae34792/pyobjc_framework_coremidi-12.1.tar.gz", hash = "sha256:3c6f1fd03997c3b0f20ab8545126b1ce5f0cddcc1587dffacad876c161da8c54", size = 55587, upload-time = "2025-11-14T10:13:58.903Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/d5/49b8720ec86f64e3dc3c804bd7e16fabb2a234a9a8b1b6753332ed343b4e/pyobjc_framework_coremidi-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:af3cdf195e8d5e30d1203889cc4107bebc6eb901aaa81bf3faf15e9ffaca0735", size = 24282, upload-time = "2025-11-14T09:45:32.288Z" }, { url = "https://files.pythonhosted.org/packages/e3/2d/99520f6f1685e4cad816e55cbf6d85f8ce6ea908107950e2d37dc17219d8/pyobjc_framework_coremidi-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e84ffc1de59691c04201b0872e184fe55b5589f3a14876bd14460f3b5f3cd109", size = 24317, upload-time = "2025-11-14T09:45:34.92Z" }, ] @@ -2586,7 +2254,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/0f/f55369da4a33cfe1db38a3512aac4487602783d3a1d572d2c8c4ccce6abc/pyobjc_framework_coreml-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:16dafcfb123f022e62f47a590a7eccf7d0cb5957a77fd5f062b5ee751cb5a423", size = 11331, upload-time = "2025-11-14T09:45:50.445Z" }, { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356, upload-time = "2025-11-14T09:45:52.271Z" }, ] @@ -2600,7 +2267,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/2c/eb/abef7d405670cf9c844befc2330a46ee59f6ff7bac6f199bf249561a2ca6/pyobjc_framework_coremotion-12.1.tar.gz", hash = "sha256:8e1b094d34084cc8cf07bedc0630b4ee7f32b0215011f79c9e3cd09d205a27c7", size = 33851, upload-time = "2025-11-14T10:14:05.619Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/77/fd/0d24796779e4d8187abbce5d06cfd7614496d57a68081c5ff1e978b398f9/pyobjc_framework_coremotion-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed8cb67927985d97b1dd23ab6a4a1b716fc7c409c35349816108781efdcbb5b6", size = 10382, upload-time = "2025-11-14T09:46:03.438Z" }, { url = "https://files.pythonhosted.org/packages/bc/75/89fa4aab818aeca21ac0a60b7ceb89a9e685df0ddd3828d36a6f84a0cff0/pyobjc_framework_coremotion-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a77908ab83c422030f913a2a761d196359ab47f6d1e7c76f21de2c6c05ea2f5f", size = 10406, upload-time = "2025-11-14T09:46:05.076Z" }, ] @@ -2615,7 +2281,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/4c/b3/52338a3ff41713f7d7bccaf63bef4ba4a8f2ce0c7eaff39a3629d022a79a/pyobjc_framework_coreservices-12.1.tar.gz", hash = "sha256:fc6a9f18fc6da64c166fe95f2defeb7ac8a9836b3b03bb6a891d36035260dbaa", size = 366150, upload-time = "2025-11-14T10:14:28.133Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/55/56/c905deb5ab6f7f758faac3f2cbc6f62fde89f8364837b626801bba0975c3/pyobjc_framework_coreservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b6ef07bcf99e941395491f1efcf46e99e5fb83eb6bfa12ae5371135d83f731e1", size = 30196, upload-time = "2025-11-14T09:46:19.356Z" }, { url = "https://files.pythonhosted.org/packages/61/6c/33984caaf497fc5a6f86350d7ca4fac8abeb2bc33203edc96955a21e8c05/pyobjc_framework_coreservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8751dc2edcb7cfa248bf8a274c4d6493e8d53ef28a843827a4fc9a0a8b04b8be", size = 30206, upload-time = "2025-11-14T09:46:22.732Z" }, ] @@ -2629,7 +2294,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/99/d0/88ca73b0cf23847af463334989dd8f98e44f801b811e7e1d8a5627ec20b4/pyobjc_framework_corespotlight-12.1.tar.gz", hash = "sha256:57add47380cd0bbb9793f50a4a4b435a90d4ebd2a33698e058cb353ddfb0d068", size = 38002, upload-time = "2025-11-14T10:14:31.948Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/37/1e7bacb9307a8df52234923e054b7303783e7a48a4637d44ce390b015921/pyobjc_framework_corespotlight-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:404a1e362fe19f0dff477edc1665d8ad90aada928246802da777399f7c06b22e", size = 9976, upload-time = "2025-11-14T09:46:45.221Z" }, { url = "https://files.pythonhosted.org/packages/f6/3b/d3031eddff8029859de6d92b1f741625b1c233748889141a6a5a89b96f0e/pyobjc_framework_corespotlight-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bfcea64ab3250e2886d202b8731be3817b5ac0c8c9f43e77d0d5a0b6602e71a7", size = 9996, upload-time = "2025-11-14T09:46:47.157Z" }, ] @@ -2644,7 +2308,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/81/7b8efc41e743adfa2d74b92dec263c91bcebfb188d2a8f5eea1886a195ff/pyobjc_framework_coretext-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4f6742ba5b0bb7629c345e99eff928fbfd9e9d3d667421ac1a2a43bdb7ba9833", size = 29990, upload-time = "2025-11-14T09:47:01.206Z" }, { url = "https://files.pythonhosted.org/packages/cd/0f/ddf45bf0e3ba4fbdc7772de4728fd97ffc34a0b5a15e1ab1115b202fe4ae/pyobjc_framework_coretext-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d246fa654bdbf43bae3969887d58f0b336c29b795ad55a54eb76397d0e62b93c", size = 30108, upload-time = "2025-11-14T09:47:04.228Z" }, ] @@ -2658,7 +2321,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/88/71/739a5d023566b506b3fd3d2412983faa95a8c16226c0dcd0f67a9294a342/pyobjc_framework_corewlan-12.1.tar.gz", hash = "sha256:a9d82ec71ef61f37e1d611caf51a4203f3dbd8caf827e98128a1afaa0fd2feb5", size = 32417, upload-time = "2025-11-14T10:14:41.921Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f5/74/4d8a52b930a276f6f9b4f3b1e07cd518cb6d923cb512e39c935e3adb0b86/pyobjc_framework_corewlan-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3e3f2614eb37dfd6860d6a0683877c2f3b909758ef78b68e5f6b7ea9c858cc51", size = 9931, upload-time = "2025-11-14T09:47:20.849Z" }, { url = "https://files.pythonhosted.org/packages/4e/31/3e9cf2c0ac3c979062958eae7a275b602515c9c76fd30680e1ee0fea82ae/pyobjc_framework_corewlan-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cba04c0550fc777767cd3a5471e4ed837406ab182d7d5c273bc5ce6ea237bfe", size = 9958, upload-time = "2025-11-14T09:47:22.474Z" }, ] @@ -2672,7 +2334,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6b/7c/d03ff4f74054578577296f33bc669fce16c7827eb1a553bb372b5aab30ca/pyobjc_framework_cryptotokenkit-12.1.tar.gz", hash = "sha256:c95116b4b7a41bf5b54aff823a4ef6f4d9da4d0441996d6d2c115026a42d82f5", size = 32716, upload-time = "2025-11-14T10:14:45.024Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/90/1623b60d6189db08f642777374fd32287b06932c51dfeb1e9ed5bbf67f35/pyobjc_framework_cryptotokenkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d84b75569054fa0886e3e341c00d7179d5fe287e6d1509630dd698ee60ec5af1", size = 12598, upload-time = "2025-11-14T09:47:33.798Z" }, { url = "https://files.pythonhosted.org/packages/0f/c7/aecba253cf21303b2c9f3ce03fc0e987523609d7839ea8e0a688ae816c96/pyobjc_framework_cryptotokenkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef51a86c1d0125fabdfad0b3efa51098fb03660d8dad2787d82e8b71c9f189de", size = 12633, upload-time = "2025-11-14T09:47:35.707Z" }, ] @@ -2738,7 +2399,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c4/87/8bd4544793bfcdf507174abddd02b1f077b48fab0004b3db9a63142ce7e9/pyobjc_framework_discrecording-12.1.tar.gz", hash = "sha256:6defc8ea97fb33b4d43870c673710c04c3dc48be30cdf78ba28191a922094990", size = 55607, upload-time = "2025-11-14T10:14:58.276Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/ce/89df4d53a0a5e3a590d6e735eca4f0ba4d1ccc0e0acfbc14164026a3c502/pyobjc_framework_discrecording-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f7d815f28f781e20de0bf278aaa10b0de7e5ea1189aa17676c0bf5b99e9e0d52", size = 14540, upload-time = "2025-11-14T09:47:55.442Z" }, { url = "https://files.pythonhosted.org/packages/c8/70/14a5aa348a5eba16e8773bb56698575cf114aa55aa303037b7000fc53959/pyobjc_framework_discrecording-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:865f1551e58459da6073360afc8f2cc452472c676ba83dcaa9b0c44e7775e4b5", size = 14566, upload-time = "2025-11-14T09:47:57.503Z" }, ] @@ -2831,7 +2491,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/9a/d4/e9b1f74d29ad9dea3d60468d59b80e14ed3a19f9f7a25afcbc10d29c8a1e/pyobjc_framework_extensionkit-12.1.tar.gz", hash = "sha256:773987353e8aba04223dbba3149253db944abfb090c35318b3a770195b75da6d", size = 18694, upload-time = "2025-11-14T10:15:14.104Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/02/3d1df48f838dc9d64f03bedd29f0fdac6c31945251c9818c3e34083eb731/pyobjc_framework_extensionkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9139c064e1c7f21455411848eb39f092af6085a26cad322aa26309260e7929d9", size = 7919, upload-time = "2025-11-14T09:48:22.14Z" }, { url = "https://files.pythonhosted.org/packages/9c/d9/8064dad6114a489e5439cc20d9fb0dd64cfc406d875b4a3c87015b3f6266/pyobjc_framework_extensionkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e01d705c7ac6d080ae34a81db6d9b81875eabefa63fd6eafbfa30f676dd780b", size = 7932, upload-time = "2025-11-14T09:48:23.653Z" }, ] @@ -2845,7 +2504,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/8e/35/86c097ae2fdf912c61c1276e80f3e090a3fc898c75effdf51d86afec456b/pyobjc_framework_externalaccessory-12.1.tar.gz", hash = "sha256:079f770a115d517a6ab87db1b8a62ca6cdf6c35ae65f45eecc21b491e78776c0", size = 20958, upload-time = "2025-11-14T10:15:16.419Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/01/2a83b63e82ce58722277a00521c3aeec58ac5abb3086704554e47f8becf3/pyobjc_framework_externalaccessory-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:32208e05c9448c8f41b3efdd35dbea4a8b119af190f7a2db0d580be8a5cf962e", size = 8911, upload-time = "2025-11-14T09:48:35.349Z" }, { url = "https://files.pythonhosted.org/packages/ec/52/984034396089766b6e5ff3be0f93470e721c420fa9d1076398557532234f/pyobjc_framework_externalaccessory-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dedbf7a09375ac19668156c1417bd7829565b164a246b714e225b9cbb6a351ad", size = 8932, upload-time = "2025-11-14T09:48:37.393Z" }, ] @@ -2859,7 +2517,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/cc/9a/724b1fae5709f8860f06a6a2a46de568f9bb8bdb2e2aae45b4e010368f51/pyobjc_framework_fileprovider-12.1.tar.gz", hash = "sha256:45034e0d00ae153c991aa01cb1fd41874650a30093e77ba73401dcce5534c8ad", size = 43071, upload-time = "2025-11-14T10:15:19.989Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/37/2f56167e9f43d3b25a5ed073305ca0cfbfc66bedec7aae9e1f2c9c337265/pyobjc_framework_fileprovider-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9d527c417f06d27c4908e51d4e6ccce0adcd80c054f19e709626e55c511dc963", size = 20970, upload-time = "2025-11-14T09:48:50.557Z" }, { url = "https://files.pythonhosted.org/packages/2c/f5/56f0751a2988b2caca89d6800c8f29246828d1a7498bb676ef1ab28000b7/pyobjc_framework_fileprovider-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:89b140ea8369512ddf4164b007cbe35b4d97d1dcb8affa12a7264c0ab8d56e45", size = 21003, upload-time = "2025-11-14T09:48:53.128Z" }, ] @@ -2899,7 +2556,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/43/17/21f45d2bca2efc72b975f2dfeae7a163dbeabb1236c1f188578403fd4f09/pyobjc_framework_fsevents-12.1.tar.gz", hash = "sha256:a22350e2aa789dec59b62da869c1b494a429f8c618854b1383d6473f4c065a02", size = 26487, upload-time = "2025-11-14T10:15:26.796Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/3f/a7fe5656b205ee3a9fd828e342157b91e643ee3e5c0d50b12bd4c737f683/pyobjc_framework_fsevents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:459cc0aac9850c489d238ba778379d09f073bbc3626248855e78c4bc4d97fe46", size = 13059, upload-time = "2025-11-14T09:49:09.814Z" }, { url = "https://files.pythonhosted.org/packages/2d/e3/2c5eeea390c0b053e2d73b223af3ec87a3e99a8106e8d3ee79942edb0822/pyobjc_framework_fsevents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2949358513fd7bc622fb362b5c4af4fc24fc6307320070ca410885e5e13d975", size = 13141, upload-time = "2025-11-14T09:49:11.947Z" }, ] @@ -2913,7 +2569,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a1/55/d00246d6e6d9756e129e1d94bc131c99eece2daa84b2696f6442b8a22177/pyobjc_framework_fskit-12.1.tar.gz", hash = "sha256:ec54e941cdb0b7d800616c06ca76a93685bd7119b8aa6eb4e7a3ee27658fc7ba", size = 42372, upload-time = "2025-11-14T10:15:30.411Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/1a/5a0b6b8dc18b9dbcb7d1ef7bebdd93f12560097dafa6d7c4b3c15649afba/pyobjc_framework_fskit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:95b9135eea81eeed319dcca32c9db04b38688301586180b86c4585fef6b0e9cd", size = 20228, upload-time = "2025-11-14T09:49:25.324Z" }, { url = "https://files.pythonhosted.org/packages/6d/a9/0c47469fe80fa14bc698bb0a5b772b44283cc3aca0f67e7f70ab45e09b24/pyobjc_framework_fskit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:50972897adea86508cfee33ec4c23aa91dede97e9da1640ea2fe74702b065be1", size = 20250, upload-time = "2025-11-14T09:49:28.065Z" }, ] @@ -2927,7 +2582,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d2/f8/b5fd86f6b722d4259228922e125b50e0a6975120a1c4d957e990fb84e42c/pyobjc_framework_gamecenter-12.1.tar.gz", hash = "sha256:de4118f14c9cf93eb0316d49da410faded3609ce9cd63425e9ef878cebb7ea72", size = 31473, upload-time = "2025-11-14T10:15:33.38Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/17/6491f9e96664e05ec00af7942a6c2f69217771522c9d1180524273cac7cb/pyobjc_framework_gamecenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:30943512f2aa8cb129f8e1abf951bf06922ca20b868e918b26c19202f4ee5cc4", size = 18824, upload-time = "2025-11-14T09:49:42.543Z" }, { url = "https://files.pythonhosted.org/packages/16/ee/b496cc4248c5b901e159d6d9a437da9b86a3105fc3999a66744ba2b2c884/pyobjc_framework_gamecenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8d6d10b868be7c00c2d5a0944cc79315945735dcf17eaa3fec1a7986d26be9b", size = 18868, upload-time = "2025-11-14T09:49:44.767Z" }, ] @@ -2941,7 +2595,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/21/14/353bb1fe448cd833839fd199ab26426c0248088753e63c22fe19dc07530f/pyobjc_framework_gamecontroller-12.1.tar.gz", hash = "sha256:64ed3cc4844b67f1faeb540c7cc8d512c84f70b3a4bafdb33d4663a2b2a2b1d8", size = 54554, upload-time = "2025-11-14T10:15:37.591Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/dc/1d8bd4845a46cb5a5c1f860d85394e64729b2447bbe149bb33301bc99056/pyobjc_framework_gamecontroller-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2633c2703fb30ce068b2f5ce145edbd10fd574d2670b5cdee77a9a126f154fec", size = 20913, upload-time = "2025-11-14T09:49:58.863Z" }, { url = "https://files.pythonhosted.org/packages/06/28/9f03d0ef7c78340441f78b19fb2d2c952af04a240da5ed30c7cf2d0d0f4e/pyobjc_framework_gamecontroller-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:878aa6590c1510e91bfc8710d6c880e7a8f3656a7b7b6f4f3af487a6f677ccd5", size = 20949, upload-time = "2025-11-14T09:50:01.608Z" }, ] @@ -2956,7 +2609,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/52/7b/d625c0937557f7e2e64200fdbeb867d2f6f86b2f148b8d6bfe085e32d872/pyobjc_framework_gamekit-12.1.tar.gz", hash = "sha256:014d032c3484093f1409f8f631ba8a0fd2ff7a3ae23fd9d14235340889854c16", size = 63833, upload-time = "2025-11-14T10:15:42.842Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/47/d3b78cf57bc2d62dc1408aaad226b776d167832063bbaa0c7cc7a9a6fa12/pyobjc_framework_gamekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb263e90a6af3d7294bc1b1ea5907f8e33bb77d62fb806696f8df7e14806ccad", size = 22463, upload-time = "2025-11-14T09:50:16.444Z" }, { url = "https://files.pythonhosted.org/packages/c4/05/1c49e1030dc9f2812fa8049442158be76c32f271075f4571f94e4389ea86/pyobjc_framework_gamekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eee796d5781157f2c5684f7ef4c2a7ace9d9b408a26a9e7e92e8adf5a3f63d7", size = 22493, upload-time = "2025-11-14T09:50:19.129Z" }, ] @@ -2971,7 +2623,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/e2/11/c310bbc2526f95cce662cc1f1359bb11e2458eab0689737b4850d0f6acb7/pyobjc_framework_gameplaykit-12.1.tar.gz", hash = "sha256:935ebd806d802888969357946245d35a304c530c86f1ffe584e2cf21f0a608a8", size = 41511, upload-time = "2025-11-14T10:15:46.529Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/84/7a4a2c358770f5ffdb6bdabb74dcefdfa248b17c250a7c0f9d16d3b8d987/pyobjc_framework_gameplaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b2fb27f9f48c3279937e938a0456a5231b5c89e53e3199b9d54009a0bbd1228a", size = 13125, upload-time = "2025-11-14T09:50:34.384Z" }, { url = "https://files.pythonhosted.org/packages/35/1f/e5fe404f92ec0f9c8c37b00d6cb3ba96ee396c7f91b0a41a39b64bfc2743/pyobjc_framework_gameplaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:309b0d7479f702830c9be92dbe5855ac2557a9d23f05f063caf9d9fdb85ff5f0", size = 13150, upload-time = "2025-11-14T09:50:36.884Z" }, ] @@ -2998,7 +2649,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/af/67/436630d00ba1028ea33cc9df2fc28e081481433e5075600f2ea1ff00f45e/pyobjc_framework_healthkit-12.1.tar.gz", hash = "sha256:29c5e5de54b41080b7a4b0207698ac6f600dcb9149becc9c6b3a69957e200e5c", size = 91802, upload-time = "2025-11-14T10:15:54.661Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/37/b23d3c04ee37cbb94ff92caedc3669cd259be0344fcf6bdf1ff75ff0a078/pyobjc_framework_healthkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67bce41f8f63c11000394c6ce1dc694655d9ff0458771340d2c782f9eafcc6e", size = 20785, upload-time = "2025-11-14T09:50:52.152Z" }, { url = "https://files.pythonhosted.org/packages/65/87/bb1c438de51c4fa733a99ce4d3301e585f14d7efd94031a97707c0be2b46/pyobjc_framework_healthkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:15b6fc958ff5de42888b18dffdec839cb36d2dd8b82076ed2f21a51db5271109", size = 20799, upload-time = "2025-11-14T09:50:54.531Z" }, ] @@ -3012,7 +2662,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6d/a1/39347381fc7d3cd5ab942d86af347b25c73f0ddf6f5227d8b4d8f5328016/pyobjc_framework_imagecapturecore-12.1.tar.gz", hash = "sha256:c4776c59f4db57727389d17e1ffd9c567b854b8db52198b3ccc11281711074e5", size = 46397, upload-time = "2025-11-14T10:15:58.541Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/6b/b34d5c9041e90b8a82d87025a1854b60a8ec2d88d9ef9e715f3a40109ed5/pyobjc_framework_imagecapturecore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:64d1eb677fe5b658a6b6ed734b7120998ea738ca038ec18c4f9c776e90bd9402", size = 15983, upload-time = "2025-11-14T09:51:09.978Z" }, { url = "https://files.pythonhosted.org/packages/50/13/632957b284dec3743d73fb30dbdf03793b3cf1b4c62e61e6484d870f3879/pyobjc_framework_imagecapturecore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2777e17ff71fb5a327a897e48c5c7b5a561723a80f990d26e6ed5a1b8748816", size = 16012, upload-time = "2025-11-14T09:51:12.058Z" }, ] @@ -3026,7 +2675,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/5d/b8/d33dd8b7306029bbbd80525bf833fc547e6a223c494bf69a534487283a28/pyobjc_framework_inputmethodkit-12.1.tar.gz", hash = "sha256:f63b6fe2fa7f1412eae63baea1e120e7865e3b68ccfb7d8b0a4aadb309f2b278", size = 23054, upload-time = "2025-11-14T10:16:01.464Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/04/1315f84dba5704a4976ea0185f877f0f33f28781473a817010cee209a8f0/pyobjc_framework_inputmethodkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4e02f49816799a31d558866492048d69e8086178770b76f4c511295610e02ab", size = 9502, upload-time = "2025-11-14T09:51:24.708Z" }, { url = "https://files.pythonhosted.org/packages/01/c2/59bea66405784b25f5d4e821467ba534a0b92dfc98e07257c971e2a8ed73/pyobjc_framework_inputmethodkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b7d813d46a060572fc0c14ef832e4fe538ebf64e5cab80ee955191792ce0110", size = 9506, upload-time = "2025-11-14T09:51:26.924Z" }, ] @@ -3067,7 +2715,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f1/a1/3bab6139e94b97eca098e1562f5d6840e3ff10ea1f7fd704a17111a97d5b/pyobjc_framework_intents-12.1.tar.gz", hash = "sha256:bd688c3ab34a18412f56e459e9dae29e1f4152d3c2048fcacdef5fc49dfb9765", size = 132262, upload-time = "2025-11-14T10:16:16.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/25/648db47b9c3879fa50c65ab7cc5fbe0dd400cc97141ac2658ef2e196c0b6/pyobjc_framework_intents-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:dc68dc49f1f8d9f8d2ffbc0f57ad25caac35312ddc444899707461e596024fec", size = 32134, upload-time = "2025-11-14T09:51:46.369Z" }, { url = "https://files.pythonhosted.org/packages/7a/90/e9489492ae90b4c1ffd02c1221c0432b8768d475787e7887f79032c2487a/pyobjc_framework_intents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ea9f3e79bf4baf6c7b0fd2d2797184ed51a372bf7f32974b4424f9bd067ef50", size = 32156, upload-time = "2025-11-14T09:51:49.438Z" }, ] @@ -3081,7 +2728,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/19/cf/f0e385b9cfbf153d68efe8d19e5ae672b59acbbfc1f9b58faaefc5ec8c9e/pyobjc_framework_intentsui-12.1.tar.gz", hash = "sha256:16bdf4b7b91c0d1ec9d5513a1182861f1b5b7af95d4f4218ff7cf03032d57f99", size = 19784, upload-time = "2025-11-14T10:16:18.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/cc/7678f901cbf5bca8ccace568ae85ee7baddcd93d78754ac43a3bb5e5a7ac/pyobjc_framework_intentsui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a877555e313d74ac3b10f7b4e738647eea9f744c00a227d1238935ac3f9d7968", size = 8961, upload-time = "2025-11-14T09:52:05.595Z" }, { url = "https://files.pythonhosted.org/packages/f1/17/06812542a9028f5b2dcce56f52f25633c08b638faacd43bad862aad1b41d/pyobjc_framework_intentsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cb894fcc4c9ea613a424dcf6fb48142d51174559b82cfdafac8cb47555c842cf", size = 8983, upload-time = "2025-11-14T09:52:07.667Z" }, ] @@ -3095,7 +2741,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/e4/aa/ca3944bbdfead4201b4ae6b51510942c5a7d8e5e2dc3139a071c74061fdf/pyobjc_framework_iobluetooth-12.1.tar.gz", hash = "sha256:8a434118812f4c01dfc64339d41fe8229516864a59d2803e9094ee4cbe2b7edd", size = 155241, upload-time = "2025-11-14T10:16:28.896Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/ab/ad6b36f574c3d52b5e935b1d57ab0f14f4e4cd328cc922d2b6ba6428c12d/pyobjc_framework_iobluetooth-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:77959f2ecf379aa41eb0848fdb25da7c322f9f4a82429965c87c4bc147137953", size = 40415, upload-time = "2025-11-14T09:52:22.069Z" }, { url = "https://files.pythonhosted.org/packages/0b/b6/933b56afb5e84c3c35c074c9e30d7b701c6038989d4867867bdaa7ab618b/pyobjc_framework_iobluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:111a6e54be9e9dcf77fa2bf84fdac09fae339aa33087d8647ea7ffbd34765d4c", size = 40439, upload-time = "2025-11-14T09:52:26.071Z" }, ] @@ -3187,7 +2832,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/26/e8/75b6b9b3c88b37723c237e5a7600384ea2d84874548671139db02e76652b/pyobjc_framework_libdispatch-12.1.tar.gz", hash = "sha256:4035535b4fae1b5e976f3e0e38b6e3442ffea1b8aa178d0ca89faa9b8ecdea41", size = 38277, upload-time = "2025-11-14T10:16:46.235Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/75/c4aeab6ce7268373d4ceabbc5c406c4bbf557038649784384910932985f8/pyobjc_framework_libdispatch-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:954cc2d817b71383bd267cc5cd27d83536c5f879539122353ca59f1c945ac706", size = 20463, upload-time = "2025-11-14T09:52:55.703Z" }, { url = "https://files.pythonhosted.org/packages/83/6f/96e15c7b2f7b51fc53252216cd0bed0c3541bc0f0aeb32756fefd31bed7d/pyobjc_framework_libdispatch-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e9570d7a9a3136f54b0b834683bf3f206acd5df0e421c30f8fd4f8b9b556789", size = 15650, upload-time = "2025-11-14T09:52:59.284Z" }, ] @@ -3201,7 +2845,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/16/e4/364db7dc26f235e3d7eaab2f92057f460b39800bffdec3128f113388ac9f/pyobjc_framework_libxpc-12.1.tar.gz", hash = "sha256:e46363a735f3ecc9a2f91637750623f90ee74f9938a4e7c833e01233174af44d", size = 35186, upload-time = "2025-11-14T10:16:49.503Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/c9/701630d025407497b7af50a795ddb6202c184da7f12b46aa683dae3d3552/pyobjc_framework_libxpc-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8d7201db995e5dcd38775fd103641d8fb69b8577d8e6a405c5562e6c0bb72fd1", size = 19620, upload-time = "2025-11-14T09:53:12.529Z" }, { url = "https://files.pythonhosted.org/packages/82/7f/fdec72430f90921b154517a6f9bbeefa7bacfb16b91320742eb16a5955c5/pyobjc_framework_libxpc-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ba93e91e9ca79603dd265382e9f80e9bd32309cd09c8ac3e6489fc5b233676c8", size = 19730, upload-time = "2025-11-14T09:53:17.113Z" }, ] @@ -3230,7 +2873,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/8d/0e/7e5d9a58bb3d5b79a75d925557ef68084171526191b1c0929a887a553d4f/pyobjc_framework_localauthentication-12.1.tar.gz", hash = "sha256:2284f587d8e1206166e4495b33f420c1de486c36c28c4921d09eec858a699d05", size = 29947, upload-time = "2025-11-14T10:16:54.923Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/cb/cf9d13943e13dc868a68844448a7714c16f4ee6ecac384d21aaa5ac43796/pyobjc_framework_localauthentication-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2d7e1b3f987dc387361517525c2c38550dc44dfb3ba42dec3a9fbf35015831a6", size = 10762, upload-time = "2025-11-14T09:53:32.035Z" }, { url = "https://files.pythonhosted.org/packages/05/93/91761ad4e5fa1c3ec25819865d1ccfbee033987147087bff4fcce67a4dc4/pyobjc_framework_localauthentication-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3af1acd287d830cc7f912f46cde0dab054952bde0adaf66c8e8524311a68d279", size = 10773, upload-time = "2025-11-14T09:53:34.074Z" }, ] @@ -3273,7 +2915,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/36/bb/2a668203c20e509a648c35e803d79d0c7f7816dacba74eb5ad8acb186790/pyobjc_framework_mapkit-12.1.tar.gz", hash = "sha256:dbc32dc48e821aaa9b4294402c240adbc1c6834e658a07677b7c19b7990533c5", size = 63520, upload-time = "2025-11-14T10:17:04.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/8f/411067e5c5cd23b9fe4c5edfb02ed94417b94eefe56562d36e244edc70ff/pyobjc_framework_mapkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e8aa82d4aae81765c05dcd53fd362af615aa04159fc7a1df1d0eac9c252cb7d5", size = 22493, upload-time = "2025-11-14T09:53:50.112Z" }, { url = "https://files.pythonhosted.org/packages/11/00/a3de41cdf3e6cd7a144e38999fe1ea9777ad19e19d863f2da862e7affe7b/pyobjc_framework_mapkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84ad7766271c114bdc423e4e2ff5433e5fc6771a3338b5f8e4b54d0340775800", size = 22518, upload-time = "2025-11-14T09:53:52.727Z" }, ] @@ -3302,7 +2943,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/d6/aa/1e8015711df1cdb5e4a0aa0ed4721409d39971ae6e1e71915e3ab72423a3/pyobjc_framework_mediaextension-12.1.tar.gz", hash = "sha256:44409d63cc7d74e5724a68e3f9252cb62fd0fd3ccf0ca94c6a33e5c990149953", size = 39425, upload-time = "2025-11-14T10:17:11.486Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8e/6f/60b63edf5d27acf450e4937b7193c1a2bd6195fee18e15df6a5734dedb71/pyobjc_framework_mediaextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9555f937f2508bd2b6264cba088e2c2e516b2f94a6c804aee40e33fd89c2fb78", size = 38957, upload-time = "2025-11-14T09:54:13.22Z" }, { url = "https://files.pythonhosted.org/packages/2b/ed/99038bcf72ec68e452709af10a087c1377c2d595ba4e66d7a2b0775145d2/pyobjc_framework_mediaextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:442bc3a759efb5c154cb75d643a5e182297093533fcdd1c24be6f64f68b93371", size = 38973, upload-time = "2025-11-14T09:54:16.701Z" }, ] @@ -3343,7 +2983,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a3/71/be5879380a161f98212a336b432256f307d1dcbaaaeb8ec988aea2ada2cd/pyobjc_framework_mediatoolbox-12.1.tar.gz", hash = "sha256:385b48746a5f08756ee87afc14037e552954c427ed5745d7ece31a21a7bad5ab", size = 22305, upload-time = "2025-11-14T10:17:22.501Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/7a/f20ebd3c590b2cc85cde3e608e49309bfccf9312e4aca7b7ea60908d36d7/pyobjc_framework_mediatoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:74de0cb2d5aaa77e81f8b97eab0f39cd2fab5bf6fa7c6fb5546740cbfb1f8c1f", size = 12656, upload-time = "2025-11-14T09:54:39.215Z" }, { url = "https://files.pythonhosted.org/packages/9c/94/d5ee221f2afbc64b2a7074efe25387cd8700e8116518904b28091ea6ad74/pyobjc_framework_mediatoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d7bcfeeff3fbf7e9e556ecafd8eaed2411df15c52baf134efa7480494e6faf6d", size = 12818, upload-time = "2025-11-14T09:54:41.251Z" }, ] @@ -3357,7 +2996,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/e7/06/a84f7eb8561d5631954b9458cfca04b690b80b5b85ce70642bc89335f52a/pyobjc_framework_metal-12.1.tar.gz", hash = "sha256:bb554877d5ee2bf3f340ad88e8fe1b85baab7b5ec4bd6ae0f4f7604147e3eae7", size = 181847, upload-time = "2025-11-14T10:17:34.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/cf/edbb8b6dd084df3d235b74dbeb1fc5daf4d063ee79d13fa3bc1cb1779177/pyobjc_framework_metal-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:59e10f9b36d2e409f80f42b6175457a07b18a21ca57ff268f4bc519cd30db202", size = 75920, upload-time = "2025-11-14T09:55:01.048Z" }, { url = "https://files.pythonhosted.org/packages/d0/48/9286d06e1b14c11b65d3fea1555edc0061d9ebe11898dff8a14089e3a4c9/pyobjc_framework_metal-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38ab566b5a2979a43e13593d3eb12000a45e574576fe76996a5e1eb75ad7ac78", size = 75841, upload-time = "2025-11-14T09:55:06.801Z" }, ] @@ -3371,7 +3009,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f1/09/ce5c74565677fde66de3b9d35389066b19e5d1bfef9d9a4ad80f0c858c0c/pyobjc_framework_metalfx-12.1.tar.gz", hash = "sha256:1551b686fb80083a97879ce0331bdb1d4c9b94557570b7ecc35ebf40ff65c90b", size = 29470, upload-time = "2025-11-14T10:17:37.16Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/e5/5494639c927085bbba1a310e73662e0bda44b90cdff67fa03a4e1c24d4c4/pyobjc_framework_metalfx-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ec3f7ab036eae45e067fbf209676f98075892aa307d73bb9394304960746cd2", size = 15026, upload-time = "2025-11-14T09:55:35.239Z" }, { url = "https://files.pythonhosted.org/packages/2a/0b/508e3af499694f4eec74cc3ab0530e38db76e43a27db9ecb98c50c68f5f9/pyobjc_framework_metalfx-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a4418ae5c2eb77ec00695fa720a547638dc252dfd77ecb6feb88f713f5a948fd", size = 15062, upload-time = "2025-11-14T09:55:37.352Z" }, ] @@ -3386,7 +3023,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/14/15/5091147aae12d4011a788b93971c3376aaaf9bf32aa935a2c9a06a71e18b/pyobjc_framework_metalkit-12.1.tar.gz", hash = "sha256:14cc5c256f0e3471b412a5b3582cb2a0d36d3d57401a8aa09e433252d1c34824", size = 25473, upload-time = "2025-11-14T10:17:39.721Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/c5/f72cbc3a5e83211cbfa33b60611abcebbe893854d0f2b28ff6f444f97549/pyobjc_framework_metalkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:28636454f222d9b20cb61f6e8dc1ebd48237903feb4d0dbdf9d7904c542475e5", size = 8735, upload-time = "2025-11-14T09:55:50.053Z" }, { url = "https://files.pythonhosted.org/packages/bf/c0/c8b5b060895cd51493afe3f09909b7e34893b1161cf4d93bc8e3cd306129/pyobjc_framework_metalkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c4869076571d94788fe539fabfdd568a5c8e340936c7726d2551196640bd152", size = 8755, upload-time = "2025-11-14T09:55:51.683Z" }, ] @@ -3400,7 +3036,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c5/68/58da38e54aa0d8c19f0d3084d8c84e92d54cc8c9254041f07119d86aa073/pyobjc_framework_metalperformanceshaders-12.1.tar.gz", hash = "sha256:b198e755b95a1de1525e63c3b14327ae93ef1d88359e6be1ce554a3493755b50", size = 137301, upload-time = "2025-11-14T10:17:49.554Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/0f/6dc06a08599a3bc211852a5e6dcb4ed65dfbf1066958feb367ba7702798a/pyobjc_framework_metalperformanceshaders-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0159a6f731dc0fd126481a26490683586864e9d47c678900049a8ffe0135f56", size = 32988, upload-time = "2025-11-14T09:56:05.323Z" }, { url = "https://files.pythonhosted.org/packages/62/84/d505496fca9341e0cb11258ace7640cd986fe3e831f8b4749035e9f82109/pyobjc_framework_metalperformanceshaders-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c00e786c352b3ff5d86cf0cf3a830dc9f6fc32a03ae1a7539d20d11324adb2e8", size = 33242, upload-time = "2025-11-14T09:56:09.354Z" }, ] @@ -3427,7 +3062,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ba/13/5576ddfbc0b174810a49171e2dbe610bdafd3b701011c6ecd9b3a461de8a/pyobjc_framework_metrickit-12.1.tar.gz", hash = "sha256:77841daf6b36ba0c19df88545fd910c0516acf279e6b7b4fa0a712a046eaa9f1", size = 27627, upload-time = "2025-11-14T10:17:56.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/b0/e57c60af3e9214e05309dca201abb82e10e8cf91952d90d572b641d62027/pyobjc_framework_metrickit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da6650afd9523cf7a9cae177f4bbd1ad45cc122d97784785fa1482847485142c", size = 8102, upload-time = "2025-11-14T09:56:27.194Z" }, { url = "https://files.pythonhosted.org/packages/b7/04/8da5126e47306438c99750f1dfed430d7cc388f6b7f420ae748f3060ab96/pyobjc_framework_metrickit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3ec96e9ec7dc37fbce57dae277f0d89c66ffe1c3fa2feaca1b7125f8b2b29d87", size = 8120, upload-time = "2025-11-14T09:56:28.73Z" }, ] @@ -3455,7 +3089,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b4/11/32c358111b623b4a0af9e90470b198fffc068b45acac74e1ba711aee7199/pyobjc_framework_modelio-12.1.tar.gz", hash = "sha256:d041d7bca7c2a4526344d3e593347225b7a2e51a499b3aa548895ba516d1bdbb", size = 66482, upload-time = "2025-11-14T10:18:04.92Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/c0/c67b806f3f2bb6264a4f7778a2aa82c7b0f50dfac40f6a60366ffc5afaf5/pyobjc_framework_modelio-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1c2c99d47a7e4956a75ce19bddbe2d8ada7d7ce9e2f56ff53fc2898367187749", size = 20180, upload-time = "2025-11-14T09:56:41.924Z" }, { url = "https://files.pythonhosted.org/packages/f6/0e/b8331100f0d658ecb3e87e75c108e2ae8ac7c78b521fd5ad0205b60a2584/pyobjc_framework_modelio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:68d971917c289fdddf69094c74915d2ccb746b42b150e0bdc16d8161e6164022", size = 20193, upload-time = "2025-11-14T09:56:44.296Z" }, ] @@ -3469,7 +3102,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/87/35/0d0bb6881004cb238cfd7bf74f4b2e42601a1accdf27b2189ec61cf3a2dc/pyobjc_framework_multipeerconnectivity-12.1.tar.gz", hash = "sha256:7123f734b7174cacbe92a51a62b4645cc9033f6b462ff945b504b62e1b9e6c1c", size = 22816, upload-time = "2025-11-14T10:18:07.363Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/eb/e3e4ba158167696498f6491f91a8ac7e24f1ebbab5042cd34318e5d2035c/pyobjc_framework_multipeerconnectivity-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7372e505ed050286aeb83d7e158fda65ad379eae12e1526f32da0a260a8b7d06", size = 11981, upload-time = "2025-11-14T09:56:58.858Z" }, { url = "https://files.pythonhosted.org/packages/33/8d/0646ff7db36942829f0e84be18ba44bc5cd96d6a81651f8e7dc0974821c1/pyobjc_framework_multipeerconnectivity-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c3bd254a16debed321debf4858f9c9b7d41572ddf1058a4bacf6a5bcfedeeff", size = 12001, upload-time = "2025-11-14T09:57:01.027Z" }, ] @@ -3509,7 +3141,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/38/13/a71270a1b0a9ec979e68b8ec84b0f960e908b17b51cb3cac246a74d52b6b/pyobjc_framework_network-12.1.tar.gz", hash = "sha256:dbf736ff84d1caa41224e86ff84d34b4e9eb6918ae4e373a44d3cb597648a16a", size = 56990, upload-time = "2025-11-14T10:18:16.714Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/7c/4f9fc6b94be3e949b7579128cbb9171943e27d1d7841db12d66b76aeadc3/pyobjc_framework_network-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d1ad948b9b977f432bf05363381d7d85a7021246ebf9d50771b35bf8d4548d2b", size = 19593, upload-time = "2025-11-14T09:57:17.027Z" }, { url = "https://files.pythonhosted.org/packages/9d/ef/a53f04f43e93932817f2ea71689dcc8afe3b908d631c21d11ec30c7b2e87/pyobjc_framework_network-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e53aad64eae2933fe12d49185d66aca62fb817abf8a46f86b01e436ce1b79e4", size = 19613, upload-time = "2025-11-14T09:57:19.571Z" }, ] @@ -3523,7 +3154,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/bf/3e/ac51dbb2efa16903e6af01f3c1f5a854c558661a7a5375c3e8767ac668e8/pyobjc_framework_networkextension-12.1.tar.gz", hash = "sha256:36abc339a7f214ab6a05cb2384a9df912f247163710741e118662bd049acfa2e", size = 62796, upload-time = "2025-11-14T10:18:21.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/4e/aa34fc983f001cdb1afbbb4d08b42fd019fc9816caca0bf0b166db1688c1/pyobjc_framework_networkextension-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c3082c29f94ca3a05cd1f3219999ca3af9b6dece1302ccf789f347e612bb9303", size = 14368, upload-time = "2025-11-14T09:57:33.748Z" }, { url = "https://files.pythonhosted.org/packages/f6/14/4934b10ade5ad0518001bfc25260d926816b9c7d08d85ef45e8a61fdef1b/pyobjc_framework_networkextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:adc9baacfc532944d67018e381c7645f66a9fa0064939a5a841476d81422cdcc", size = 14376, upload-time = "2025-11-14T09:57:36.132Z" }, ] @@ -3537,7 +3167,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c6/12/ae0fe82fb1e02365c9fe9531c9de46322f7af09e3659882212c6bf24d75e/pyobjc_framework_notificationcenter-12.1.tar.gz", hash = "sha256:2d09f5ab9dc39770bae4fa0c7cfe961e6c440c8fc465191d403633dccc941094", size = 21282, upload-time = "2025-11-14T10:18:24.51Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/aa/03526fc0cc285c0f8cf31c74ce3a7a464011cc8fa82a35a1637d9878c788/pyobjc_framework_notificationcenter-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e254f2a56ff5372793dea938a2b2683dd0bc40c5107fede76f9c2c1f6641a2", size = 9871, upload-time = "2025-11-14T09:57:49.208Z" }, { url = "https://files.pythonhosted.org/packages/d8/05/3168637dd425257df5693c2ceafecf92d2e6833c0aaa6594d894a528d797/pyobjc_framework_notificationcenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82a735bd63f315f0a56abd206373917b7d09a0ae35fd99f1639a0fac4c525c0a", size = 9895, upload-time = "2025-11-14T09:57:51.151Z" }, ] @@ -3579,7 +3208,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/12/42/805c9b4ac6ad25deb4215989d8fc41533d01e07ffd23f31b65620bade546/pyobjc_framework_oslog-12.1.tar.gz", hash = "sha256:d0ec6f4e3d1689d5e4341bc1130c6f24cb4ad619939f6c14d11a7e80c0ac4553", size = 21193, upload-time = "2025-11-14T10:18:33.645Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/d5/8d37c2e733bd8a9a16546ceca07809d14401a059f8433cdc13579cd6a41a/pyobjc_framework_oslog-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8dd03386331fbb6b39df8941d99071da0bfeda7d10f6434d1daa1c69f0e7bb14", size = 7802, upload-time = "2025-11-14T09:58:05.619Z" }, { url = "https://files.pythonhosted.org/packages/ee/60/0b742347d484068e9d6867cd95dedd1810c790b6aca45f6ef1d0f089f1f5/pyobjc_framework_oslog-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:072a41d36fcf780a070f13ac2569f8bafbb5ae4792fab4136b1a4d602dd9f5b4", size = 7813, upload-time = "2025-11-14T09:58:07.768Z" }, ] @@ -3593,7 +3221,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/6c/d4/2afb59fb0f99eb2f03888850887e536f1ef64b303fd756283679471a5189/pyobjc_framework_passkit-12.1.tar.gz", hash = "sha256:d8c27c352e86a3549bf696504e6b25af5f2134b173d9dd60d66c6d3da53bb078", size = 53835, upload-time = "2025-11-14T10:18:37.906Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/e6/dabd6b99bdadc50aa0306495d8d0afe4b9b3475c2bafdad182721401a724/pyobjc_framework_passkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cb5c8f0fdc46db6b91c51ee1f41a2b81e9a482c96a0c91c096dcb78a012b740a", size = 14087, upload-time = "2025-11-14T09:58:18.991Z" }, { url = "https://files.pythonhosted.org/packages/d8/dc/9cb27e8b7b00649af5e802815ffa8928bd8a619f2984a1bea7dabd28f741/pyobjc_framework_passkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e95a484ec529dbf1d44f5f7f1406502a77bda733511e117856e3dca9fa29c5c", size = 14102, upload-time = "2025-11-14T09:58:20.903Z" }, ] @@ -3633,7 +3260,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b8/53/f8a3dc7f711034d2283e289cd966fb7486028ea132a24260290ff32d3525/pyobjc_framework_photos-12.1.tar.gz", hash = "sha256:adb68aaa29e186832d3c36a0b60b0592a834e24c5263e9d78c956b2b77dce563", size = 47034, upload-time = "2025-11-14T10:18:47.27Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/e0/8824f7cb167934a8aa1c088b7e6f1b5a9342b14694e76eda95fc736282b2/pyobjc_framework_photos-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f28db92602daac9d760067449fc9bf940594536e65ad542aec47d52b56f51959", size = 12319, upload-time = "2025-11-14T09:58:36.324Z" }, { url = "https://files.pythonhosted.org/packages/13/38/e6f25aec46a1a9d0a310795606cc43f9823d41c3e152114b814b597835a8/pyobjc_framework_photos-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eda8a584a851506a1ebbb2ee8de2cb1ed9e3431e6a642ef6a9543e32117d17b9", size = 12358, upload-time = "2025-11-14T09:58:38.131Z" }, ] @@ -3647,7 +3273,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/40/a5/14c538828ed1a420e047388aedc4a2d7d9292030d81bf6b1ced2ec27b6e9/pyobjc_framework_photosui-12.1.tar.gz", hash = "sha256:9141234bb9d17687f1e8b66303158eccdd45132341fbe5e892174910035f029a", size = 29886, upload-time = "2025-11-14T10:18:50.238Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/6c/d678767bbeafa932b91c88bc8bb3a586a1b404b5564b0dc791702eb376c3/pyobjc_framework_photosui-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:02ca941187b2a2dcbbd4964d7b2a05de869653ed8484dc059a51cc70f520cd07", size = 11688, upload-time = "2025-11-14T09:58:51.84Z" }, { url = "https://files.pythonhosted.org/packages/16/a2/b5afca8039b1a659a2a979bb1bdbdddfdf9b1d2724a2cc4633dca2573d5f/pyobjc_framework_photosui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:713e3bb25feb5ea891e67260c2c0769cab44a7f11b252023bfcf9f8c29dd1206", size = 11714, upload-time = "2025-11-14T09:58:53.674Z" }, ] @@ -3674,7 +3299,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/a9/45/de756b62709add6d0615f86e48291ee2bee40223e7dde7bbe68a952593f0/pyobjc_framework_pushkit-12.1.tar.gz", hash = "sha256:829a2fc8f4780e75fc2a41217290ee0ff92d4ade43c42def4d7e5af436d8ae82", size = 19465, upload-time = "2025-11-14T10:18:57.727Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/b2/d92045e0d4399feda83ee56a9fd685b5c5c175f7ac8423e2cd9b3d52a9da/pyobjc_framework_pushkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:03f41be8b27d06302ea487a6b250aaf811917a0e7d648cd4043fac759d027210", size = 8158, upload-time = "2025-11-14T09:59:09.593Z" }, { url = "https://files.pythonhosted.org/packages/b9/01/74cf1dd0764c590de05dc1e87d168031e424f834721940b7bb02c67fe821/pyobjc_framework_pushkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7bdf472a55ac65154e03f54ae0bcad64c4cf45e9b1acba62f15107f2bc994d69", size = 8177, upload-time = "2025-11-14T09:59:11.155Z" }, ] @@ -3688,7 +3312,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ef/dcd22b743e38b3c430fce4788176c2c5afa8bfb01085b8143b02d1e75201/pyobjc_framework_quartz-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:19f99ac49a0b15dd892e155644fe80242d741411a9ed9c119b18b7466048625a", size = 217795, upload-time = "2025-11-14T09:59:46.922Z" }, { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, ] @@ -3716,7 +3339,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/35/f8/b92af879734d91c1726227e7a03b9e68ab8d9d2bb1716d1a5c29254087f2/pyobjc_framework_replaykit-12.1.tar.gz", hash = "sha256:95801fd35c329d7302b2541f2754e6574bf36547ab869fbbf41e408dfa07268a", size = 23312, upload-time = "2025-11-14T10:21:29.18Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/b1/fab264c6a82a78cd050a773c61dec397c5df7e7969eba3c57e17c8964ea3/pyobjc_framework_replaykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3a2f9da6939d7695fa40de9c560c20948d31b0cc2f892fdd611fc566a6b83606", size = 10090, upload-time = "2025-11-14T10:01:06.321Z" }, { url = "https://files.pythonhosted.org/packages/6b/fc/c68d2111b2655148d88574959d3d8b21d3a003573013301d4d2a7254c1af/pyobjc_framework_replaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b0528c2a6188440fdc2017f0924c0a0f15d0a2f6aa295f1d1c2d6b3894c22f1d", size = 10120, upload-time = "2025-11-14T10:01:08.397Z" }, ] @@ -3730,7 +3352,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3e/4b/8f896bafbdbfa180a5ba1e21a6f5dc63150c09cba69d85f68708e02866ae/pyobjc_framework_safariservices-12.1.tar.gz", hash = "sha256:6a56f71c1e692bca1f48fe7c40e4c5a41e148b4e3c6cfb185fd80a4d4a951897", size = 25165, upload-time = "2025-11-14T10:21:32.041Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/bb/da1059bfad021c417e090058c0a155419b735b4891a7eedc03177b376012/pyobjc_framework_safariservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae709cf7a72ac7b95d2f131349f852d5d7a1729a8d760ea3308883f8269a4c37", size = 7281, upload-time = "2025-11-14T10:01:19.294Z" }, { url = "https://files.pythonhosted.org/packages/67/3a/8c525562fd782c88bc44e8c07fc2c073919f98dead08fffd50f280ef1afa/pyobjc_framework_safariservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b475abc82504fc1c0801096a639562d6a6d37370193e8e4a406de9199a7cea13", size = 7281, upload-time = "2025-11-14T10:01:21.238Z" }, ] @@ -3745,7 +3366,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f4/bf/ad6bf60ceb61614c9c9f5758190971e9b90c45b1c7a244e45db64138b6c2/pyobjc_framework_safetykit-12.1.tar.gz", hash = "sha256:0cd4850659fb9b5632fd8ad21f2de6863e8303ff0d51c5cc9c0034aac5db08d8", size = 20086, upload-time = "2025-11-14T10:21:34.212Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/68/77f17fba082de7c65176e0d74aacbce5c9c9066d6d6edcde5a537c8c140a/pyobjc_framework_safetykit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c63bcd5d571bba149e28c49c8db06073e54e073b08589e94b850b39a43e52b0", size = 8539, upload-time = "2025-11-14T10:01:31.201Z" }, { url = "https://files.pythonhosted.org/packages/b7/0c/08a20fb7516405186c0fe7299530edd4aa22c24f73290198312447f26c8c/pyobjc_framework_safetykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e4977f7069a23252053d1a42b1a053aefc19b85c960a5214b05daf3c037a6f16", size = 8550, upload-time = "2025-11-14T10:01:32.885Z" }, ] @@ -3760,7 +3380,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/94/8c/1f4005cf0cb68f84dd98b93bbc0974ee7851bb33d976791c85e042dc2278/pyobjc_framework_scenekit-12.1.tar.gz", hash = "sha256:1bd5b866f31fd829f26feac52e807ed942254fd248115c7c742cfad41d949426", size = 101212, upload-time = "2025-11-14T10:21:41.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/7f/eda261013dc41cc70f3157d1a750712dc29b64fc05be84232006b5cd57e5/pyobjc_framework_scenekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:01bf1336a7a8bdc96fabde8f3506aa7a7d1905e20a5c46030a57daf0ce2cbd16", size = 33542, upload-time = "2025-11-14T10:01:47.613Z" }, { url = "https://files.pythonhosted.org/packages/d2/f1/4986bd96e0ba0f60bff482a6b135b9d6db65d56578d535751f18f88190f0/pyobjc_framework_scenekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:40aea10098893f0b06191f1e79d7b25e12e36a9265549d324238bdb25c7e6df0", size = 33597, upload-time = "2025-11-14T10:01:51.297Z" }, ] @@ -3775,7 +3394,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/2d/7f/73458db1361d2cb408f43821a1e3819318a0f81885f833d78d93bdc698e0/pyobjc_framework_screencapturekit-12.1.tar.gz", hash = "sha256:50992c6128b35ab45d9e336f0993ddd112f58b8c8c8f0892a9cb42d61bd1f4c9", size = 32573, upload-time = "2025-11-14T10:21:44.497Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/92/fe66408f4bd74f6b6da75977d534a7091efe988301d13da4f009bf54ab71/pyobjc_framework_screencapturekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae412d397eedf189e763defe3497fcb8dffa5e0b54f62390cb33bf9b1cfb864a", size = 11473, upload-time = "2025-11-14T10:02:09.177Z" }, { url = "https://files.pythonhosted.org/packages/05/a8/533acdbf26e0a908ff640d3a445481f3c948682ca887be6711b5fcf82682/pyobjc_framework_screencapturekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:27df138ce2dfa9d4aae5106d4877e9ed694b5a174643c058f1c48678ffc7001a", size = 11504, upload-time = "2025-11-14T10:02:11.36Z" }, ] @@ -3789,7 +3407,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/7d/99/7cfbce880cea61253a44eed594dce66c2b2fbf29e37eaedcd40cffa949e9/pyobjc_framework_screensaver-12.1.tar.gz", hash = "sha256:c4ca111317c5a3883b7eace0a9e7dd72bc6ffaa2ca954bdec918c3ab7c65c96f", size = 22229, upload-time = "2025-11-14T10:21:47.299Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/8d/87ca0fa0a9eda3097a0f4f2eef1544bf1d984697939fbef7cda7495fddb9/pyobjc_framework_screensaver-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5bd10809005fbe0d68fe651f32a393ce059e90da38e74b6b3cd055ed5b23eaa9", size = 8480, upload-time = "2025-11-14T10:02:22.798Z" }, { url = "https://files.pythonhosted.org/packages/5a/a4/2481711f2e9557b90bac74fa8bf821162cf7b65835732ae560fd52e9037e/pyobjc_framework_screensaver-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3c90c2299eac6d01add81427ae2f90d7724f15d676261e838d7a7750f812322", size = 8422, upload-time = "2025-11-14T10:02:24.49Z" }, ] @@ -3816,7 +3433,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/0c/cb/adc0a09e8c4755c2281bd12803a87f36e0832a8fc853a2d663433dbb72ce/pyobjc_framework_scriptingbridge-12.1.tar.gz", hash = "sha256:0e90f866a7e6a8aeaf723d04c826657dd528c8c1b91e7a605f8bb947c74ad082", size = 20339, upload-time = "2025-11-14T10:21:51.769Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/de/0943ee8d7f1a7d8467df6e2ea017a6d5041caff2fb0283f37fea4c4ce370/pyobjc_framework_scriptingbridge-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e6e37e69760d6ac9d813decf135d107760d33e1cdf7335016522235607f6f31b", size = 8335, upload-time = "2025-11-14T10:02:36.654Z" }, { url = "https://files.pythonhosted.org/packages/51/46/e0b07d2b3ff9effb8b1179a6cc681a953d3dfbf0eb8b1d6a0e54cef2e922/pyobjc_framework_scriptingbridge-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8083cd68c559c55a3787b2e74fc983c8665e5078571475aaeabf4f34add36b62", size = 8356, upload-time = "2025-11-14T10:02:38.559Z" }, ] @@ -3843,7 +3459,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/3d/8d3a39cd292d7c76ab76233498189bc7170fc80f573b415308464f68c7ee/pyobjc_framework_security-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1b2d8819f0fb7b619ec7627a0d8c1cac1a57c5143579ce8ac21548165680684b", size = 41287, upload-time = "2025-11-14T10:02:54.491Z" }, { url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" }, ] @@ -3872,7 +3487,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/f9/64/bf5b5d82655112a2314422ee649f1e1e73d4381afa87e1651ce7e8444694/pyobjc_framework_securityinterface-12.1.tar.gz", hash = "sha256:deef11ad03be8d9ff77db6e7ac40f6b641ee2d72eaafcf91040537942472e88b", size = 25552, upload-time = "2025-11-14T10:22:12.098Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/1c/a01fd56765792d1614eb5e8dc0a7d5467564be6a2056b417c9ec7efc648f/pyobjc_framework_securityinterface-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed599be750122376392e95c2407d57bd94644e8320ddef1d67660e16e96b0d06", size = 10719, upload-time = "2025-11-14T10:03:18.353Z" }, { url = "https://files.pythonhosted.org/packages/59/3e/17889a6de03dc813606bb97887dc2c4c2d4e7c8f266bc439548bae756e90/pyobjc_framework_securityinterface-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cb5e79a73ea17663ebd29e350401162d93e42343da7d96c77efb38ae64ff01f", size = 10783, upload-time = "2025-11-14T10:03:20.202Z" }, ] @@ -3927,7 +3541,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/0f/8b/8ab209a143c11575a857e2111acc5427fb4986b84708b21324cbcbf5591b/pyobjc_framework_sharedwithyou-12.1.tar.gz", hash = "sha256:167d84794a48f408ee51f885210c616fda1ec4bff3dd8617a4b5547f61b05caf", size = 24791, upload-time = "2025-11-14T10:22:21.248Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/69/3ad9b344808c5619adc253b665f8677829dfb978888227e07233d120cfab/pyobjc_framework_sharedwithyou-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:359c03096a6988371ea89921806bb81483ea509c9aa7114f9cd20efd511b3576", size = 8739, upload-time = "2025-11-14T10:03:36.48Z" }, { url = "https://files.pythonhosted.org/packages/ec/ee/e5113ce985a480d13a0fa3d41a242c8068dc09b3c13210557cf5cc6a544a/pyobjc_framework_sharedwithyou-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a99a6ebc6b6de7bc8663b1f07332fab9560b984a57ce344dc5703f25258f258d", size = 8763, upload-time = "2025-11-14T10:03:38.467Z" }, ] @@ -3941,7 +3554,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/55/ef/84059c5774fd5435551ab7ab40b51271cfb9997b0d21f491c6b429fe57a8/pyobjc_framework_sharedwithyoucore-12.1.tar.gz", hash = "sha256:0813149eeb755d718b146ec9365eb4ca3262b6af9ff9ba7db2f7b6f4fd104518", size = 22350, upload-time = "2025-11-14T10:22:23.611Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/a1/83e58eca8827a1a9975a9c5de7f8c0bdc73b5f53ee79768d1fdbec6747de/pyobjc_framework_sharedwithyoucore-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f4f9f7fed0768ebbbc2d24248365da2cf5f014b8822b2a1fbbce5fa920f410f1", size = 8512, upload-time = "2025-11-14T10:03:49.176Z" }, { url = "https://files.pythonhosted.org/packages/dd/0e/0c2b0591ebc72d437dccca7a1e7164c5f11dde2189d4f4c707a132bab740/pyobjc_framework_sharedwithyoucore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed928266ae9d577ff73de72a03bebc66a751918eb59ca660a9eca157392f17be", size = 8530, upload-time = "2025-11-14T10:03:50.839Z" }, ] @@ -3955,7 +3567,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/ed/2c/8d82c5066cc376de68ad8c1454b7c722c7a62215e5c2f9dac5b33a6c3d42/pyobjc_framework_shazamkit-12.1.tar.gz", hash = "sha256:71db2addd016874639a224ed32b2000b858802b0370c595a283cce27f76883fe", size = 22518, upload-time = "2025-11-14T10:22:25.996Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/12/09d83a8ac51dc11a574449dea48ffa99b3a7c9baf74afeedb487394d110d/pyobjc_framework_shazamkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0c10ba22de524fbedf06270a71bb0a3dbd4a3853b7002ddf54394589c3be6939", size = 8555, upload-time = "2025-11-14T10:04:02.552Z" }, { url = "https://files.pythonhosted.org/packages/04/5e/7d60d8e7b036b20d0e94cd7c4563e7414653344482e85fbc7facffabc95f/pyobjc_framework_shazamkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e184dd0f61a604b1cfcf44418eb95b943e7b8f536058a29e4b81acadd27a9420", size = 8577, upload-time = "2025-11-14T10:04:04.182Z" }, ] @@ -3995,7 +3606,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/8d/3d/194cf19fe7a56c2be5dfc28f42b3b597a62ebb1e1f52a7dd9c55b917ac6c/pyobjc_framework_speech-12.1.tar.gz", hash = "sha256:2a2a546ba6c52d5dd35ddcfee3fd9226a428043d1719597e8701851a6566afdd", size = 25218, upload-time = "2025-11-14T10:22:32.505Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/54/77e12e4c23a98fc49d874f9703c9f8fd0257d64bb0c6ae329b91fc7a99e3/pyobjc_framework_speech-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0301bfae5d0d09b6e69bd4dbabc5631209e291cc40bda223c69ed0c618f8f2dc", size = 9248, upload-time = "2025-11-14T10:04:19.73Z" }, { url = "https://files.pythonhosted.org/packages/f9/1b/224cb98c9c32a6d5e68072f89d26444095be54c6f461efe4fefe9d1330a5/pyobjc_framework_speech-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cae4b88ef9563157a6c9e66b37778fc4022ee44dd1a2a53081c2adbb69698945", size = 9254, upload-time = "2025-11-14T10:04:21.361Z" }, ] @@ -4010,7 +3620,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b6/78/d683ebe0afb49f46d2d21d38c870646e7cb3c2e83251f264e79d357b1b74/pyobjc_framework_spritekit-12.1.tar.gz", hash = "sha256:a851f4ef5aa65cc9e08008644a528e83cb31021a1c0f17ebfce4de343764d403", size = 64470, upload-time = "2025-11-14T10:22:37.569Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/6a/e8e44fc690d898394093f3a1c5fe90110d1fbcc6e3f486764437c022b0f8/pyobjc_framework_spritekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26fd12944684713ae1e3cdd229348609c1142e60802624161ca0c3540eec3ffa", size = 17736, upload-time = "2025-11-14T10:04:33.202Z" }, { url = "https://files.pythonhosted.org/packages/3b/38/97c3b6c3437e3e9267fb4e1cd86e0da4eff07e0abe7cd6923644d2dfc878/pyobjc_framework_spritekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1649e57c25145795d04bb6a1ec44c20ef7cf0af7c60a9f6f5bc7998dd269db1e", size = 17802, upload-time = "2025-11-14T10:04:35.346Z" }, ] @@ -4024,7 +3633,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/00/87/8a66a145feb026819775d44975c71c1c64df4e5e9ea20338f01456a61208/pyobjc_framework_storekit-12.1.tar.gz", hash = "sha256:818452e67e937a10b5c8451758274faa44ad5d4329df0fa85735115fb0608da9", size = 34574, upload-time = "2025-11-14T10:22:40.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/41/af2afc4d27bde026cfd3b725ee1b082b2838dcaa9880ab719226957bc7cd/pyobjc_framework_storekit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a29f45bcba9dee4cf73dae05ab0f94d06a32fb052e31414d0c23791c1ec7931c", size = 12810, upload-time = "2025-11-14T10:04:48.693Z" }, { url = "https://files.pythonhosted.org/packages/8a/9f/938985e506de0cc3a543e44e1f9990e9e2fb8980b8f3bcfc8f7921d09061/pyobjc_framework_storekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9fe2d65a2b644bb6b4fdd3002292cba153560917de3dd6cf969431fa32d21dd0", size = 12819, upload-time = "2025-11-14T10:04:50.945Z" }, ] @@ -4052,7 +3660,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/21/91/6d03a988831ddb0fb001b13573560e9a5bcccde575b99350f98fe56a2dd4/pyobjc_framework_syncservices-12.1.tar.gz", hash = "sha256:6a213e93d9ce15128810987e4c5de8c73cfab1564ac8d273e6b437a49965e976", size = 31032, upload-time = "2025-11-14T10:22:45.902Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4a/9b/25c117f8ffe15aa6cc447da7f5c179627ebafb2b5ec30dfb5e70fede2549/pyobjc_framework_syncservices-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e81a38c2eb7617cb0ecfc4406c1ae2a97c60e95af42e863b2b0f1f6facd9b0da", size = 13380, upload-time = "2025-11-14T10:05:05.814Z" }, { url = "https://files.pythonhosted.org/packages/54/ac/a83cdd120e279ee905e9085afda90992159ed30c6a728b2c56fa2d36b6ea/pyobjc_framework_syncservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cd629bea95692aad2d26196657cde2fbadedae252c7846964228661a600b900", size = 13411, upload-time = "2025-11-14T10:05:07.741Z" }, ] @@ -4066,7 +3673,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/90/7d/50848df8e1c6b5e13967dee9fb91d3391fe1f2399d2d0797d2fc5edb32ba/pyobjc_framework_systemconfiguration-12.1.tar.gz", hash = "sha256:90fe04aa059876a21626931c71eaff742a27c79798a46347fd053d7008ec496e", size = 59158, upload-time = "2025-11-14T10:22:53.056Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/7b/9126a7af1b798998837027390a20b981e0298e51c4c55eed6435967145cb/pyobjc_framework_systemconfiguration-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:796390a80500cc7fde86adc71b11cdc41d09507dd69103d3443fbb60e94fb438", size = 21663, upload-time = "2025-11-14T10:05:21.259Z" }, { url = "https://files.pythonhosted.org/packages/d3/d3/bb935c3d4bae9e6ce4a52638e30eea7039c480dd96bc4f0777c9fabda21b/pyobjc_framework_systemconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e5bb9103d39483964431db7125195c59001b7bff2961869cfe157b4c861e52d", size = 21578, upload-time = "2025-11-14T10:05:25.572Z" }, ] @@ -4080,7 +3686,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/12/01/8a706cd3f7dfcb9a5017831f2e6f9e5538298e90052db3bb8163230cbc4f/pyobjc_framework_systemextensions-12.1.tar.gz", hash = "sha256:243e043e2daee4b5c46cd90af5fff46b34596aac25011bab8ba8a37099685eeb", size = 20701, upload-time = "2025-11-14T10:22:58.257Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/a1/f8df6d59e06bc4b5989a76724e8551935e5b99aff6a21d3592e5ced91f1c/pyobjc_framework_systemextensions-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a4e82160e43c0b1aa17e6d4435e840a655737fbe534e00e37fc1961fbf3bebd", size = 9156, upload-time = "2025-11-14T10:05:39.744Z" }, { url = "https://files.pythonhosted.org/packages/0a/cc/a42883d6ad0ae257a7fa62660b4dd13be15f8fa657922f9a5b6697f26e28/pyobjc_framework_systemextensions-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:01fac4f8d88c0956d9fc714d24811cd070e67200ba811904317d91e849e38233", size = 9166, upload-time = "2025-11-14T10:05:41.479Z" }, ] @@ -4120,7 +3725,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/90/cd/e0253072f221fa89a42fe53f1a2650cc9bf415eb94ae455235bd010ee12e/pyobjc_framework_usernotifications-12.1.tar.gz", hash = "sha256:019ccdf2d400f9a428769df7dba4ea97c02453372bc5f8b75ce7ae54dfe130f9", size = 29749, upload-time = "2025-11-14T10:23:05.364Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/96/aa25bb0727e661a352d1c52e7288e25c12fe77047f988bb45557c17cf2d7/pyobjc_framework_usernotifications-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c62e8d7153d72c4379071e34258aa8b7263fa59212cfffd2f137013667e50381", size = 9632, upload-time = "2025-11-14T10:05:55.166Z" }, { url = "https://files.pythonhosted.org/packages/61/ad/c95053a475246464cba686e16269b0973821601910d1947d088b855a8dac/pyobjc_framework_usernotifications-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:412afb2bf5fe0049f9c4e732e81a8a35d5ebf97c30a5a6abd276259d020c82ac", size = 9644, upload-time = "2025-11-14T10:05:56.801Z" }, ] @@ -4163,7 +3767,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/b3/5f/6995ee40dc0d1a3460ee183f696e5254c0ad14a25b5bc5fd9bd7266c077b/pyobjc_framework_videotoolbox-12.1.tar.gz", hash = "sha256:7adc8670f3b94b086aed6e86c3199b388892edab4f02933c2e2d9b1657561bef", size = 57825, upload-time = "2025-11-14T10:23:13.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/42/53d57b09fd4879988084ec0d9b74c645c9fdd322be594c9601f6cf265dd0/pyobjc_framework_videotoolbox-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a1eb1eb41c0ffdd8dcc6a9b68ab2b5bc50824a85820c8a7802a94a22dfbb4f91", size = 18781, upload-time = "2025-11-14T10:06:11.89Z" }, { url = "https://files.pythonhosted.org/packages/94/a5/91c6c95416f41c412c2079950527cb746c0712ec319c51a6c728c8d6b231/pyobjc_framework_videotoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eb6ce6837344ee319122066c16ada4beb913e7bfd62188a8d14b1ecbb5a89234", size = 18908, upload-time = "2025-11-14T10:06:14.087Z" }, ] @@ -4177,7 +3780,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/3b/6a/9d110b5521d9b898fad10928818c9f55d66a4af9ac097426c65a9878b095/pyobjc_framework_virtualization-12.1.tar.gz", hash = "sha256:e96afd8e801e92c6863da0921e40a3b68f724804f888bce43791330658abdb0f", size = 40682, upload-time = "2025-11-14T10:23:17.456Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/ee/e18d0d9014c42758d7169144acb2d37eb5ff19bf959db74b20eac706bd8c/pyobjc_framework_virtualization-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a88a307dc96885afc227ceda4067f1af787f024063f4ccf453d59e7afd47cda8", size = 13099, upload-time = "2025-11-14T10:06:27.403Z" }, { url = "https://files.pythonhosted.org/packages/c6/f2/0da47e91f3f8eeda9a8b4bb0d3a0c54a18925009e99b66a8226b9e06ce1e/pyobjc_framework_virtualization-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d5724b38e64b39ab5ec3b45993afa29fc88b307d99ee2c7a1c0fd770e9b4b21", size = 13131, upload-time = "2025-11-14T10:06:29.337Z" }, ] @@ -4193,7 +3795,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/37/e30cf4eef2b4c7e20ccadc1249117c77305fbc38b2e5904eb42e3753f63c/pyobjc_framework_vision-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1edbf2fc18ce3b31108f845901a88f2236783ae6bf0bc68438d7ece572dc2a29", size = 21432, upload-time = "2025-11-14T10:06:42.373Z" }, { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625, upload-time = "2025-11-14T10:06:44.422Z" }, ] @@ -4207,7 +3808,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/37/5082a0bbe12e48d4ffa53b0c0f09c77a4a6ffcfa119e26fa8dd77c08dc1c/pyobjc_framework_webkit-12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3db734877025614eaef4504fadc0fbbe1279f68686a6f106f2e614e89e0d1a9d", size = 49970, upload-time = "2025-11-14T10:07:01.413Z" }, { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, ] @@ -4251,9 +3851,6 @@ sdist = { url = "https://files.pythonhosted.org/packages/cb/04/2ba023d5f771b645f name = "pyscreeze" version = "1.0.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pillow", marker = "python_full_version < '3.12'" }, -] sdist = { url = "https://files.pythonhosted.org/packages/ee/f0/cb456ac4f1a73723d5b866933b7986f02bacea27516629c00f8e7da94c2d/pyscreeze-1.0.1.tar.gz", hash = "sha256:cf1662710f1b46aa5ff229ee23f367da9e20af4a78e6e365bee973cad0ead4be", size = 27826, upload-time = "2024-08-20T23:03:07.291Z" } [[package]] @@ -4405,9 +4002,6 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/af/449a6a91e5d6db51420875c54f6aff7c97a86a3b13a0b4f1a5c13b988de3/pywin32-311-cp311-cp311-win32.whl", hash = "sha256:184eb5e436dea364dcd3d2316d577d625c0351bf237c4e9a5fabbcfa5a58b151", size = 8697031, upload-time = "2025-07-14T20:13:13.266Z" }, - { url = "https://files.pythonhosted.org/packages/51/8f/9bb81dd5bb77d22243d33c8397f09377056d5c687aa6d4042bea7fbf8364/pywin32-311-cp311-cp311-win_amd64.whl", hash = "sha256:3ce80b34b22b17ccbd937a6e78e7225d80c52f5ab9940fe0506a1a16f3dab503", size = 9508308, upload-time = "2025-07-14T20:13:15.147Z" }, - { url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" }, { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, @@ -4451,15 +4045,6 @@ version = "6.0.3" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, @@ -4493,16 +4078,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/5d/305323ba86b284e6fcb0d842d6adaa2999035f70f8c38a9b6d21ad28c3d4/pyzmq-27.1.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:226b091818d461a3bef763805e75685e478ac17e9008f49fce2d3e52b3d58b86", size = 1333328, upload-time = "2025-09-08T23:07:45.946Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a0/fc7e78a23748ad5443ac3275943457e8452da67fda347e05260261108cbc/pyzmq-27.1.0-cp311-cp311-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:0790a0161c281ca9723f804871b4027f2e8b5a528d357c8952d08cd1a9c15581", size = 908803, upload-time = "2025-09-08T23:07:47.551Z" }, - { url = "https://files.pythonhosted.org/packages/7e/22/37d15eb05f3bdfa4abea6f6d96eb3bb58585fbd3e4e0ded4e743bc650c97/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c895a6f35476b0c3a54e3eb6ccf41bf3018de937016e6e18748317f25d4e925f", size = 668836, upload-time = "2025-09-08T23:07:49.436Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bbf8d3630bf96550b3be8e1fc0fea5cbdc8d5466c1192887bd94869da17a63e", size = 857038, upload-time = "2025-09-08T23:07:51.234Z" }, - { url = "https://files.pythonhosted.org/packages/cb/eb/bfdcb41d0db9cd233d6fb22dc131583774135505ada800ebf14dfb0a7c40/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15c8bd0fe0dabf808e2d7a681398c4e5ded70a551ab47482067a572c054c8e2e", size = 1657531, upload-time = "2025-09-08T23:07:52.795Z" }, - { url = "https://files.pythonhosted.org/packages/ab/21/e3180ca269ed4a0de5c34417dfe71a8ae80421198be83ee619a8a485b0c7/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:bafcb3dd171b4ae9f19ee6380dfc71ce0390fefaf26b504c0e5f628d7c8c54f2", size = 2034786, upload-time = "2025-09-08T23:07:55.047Z" }, - { url = "https://files.pythonhosted.org/packages/3b/b1/5e21d0b517434b7f33588ff76c177c5a167858cc38ef740608898cd329f2/pyzmq-27.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e829529fcaa09937189178115c49c504e69289abd39967cd8a4c215761373394", size = 1894220, upload-time = "2025-09-08T23:07:57.172Z" }, - { url = "https://files.pythonhosted.org/packages/03/f2/44913a6ff6941905efc24a1acf3d3cb6146b636c546c7406c38c49c403d4/pyzmq-27.1.0-cp311-cp311-win32.whl", hash = "sha256:6df079c47d5902af6db298ec92151db82ecb557af663098b92f2508c398bb54f", size = 567155, upload-time = "2025-09-08T23:07:59.05Z" }, - { url = "https://files.pythonhosted.org/packages/23/6d/d8d92a0eb270a925c9b4dd039c0b4dc10abc2fcbc48331788824ef113935/pyzmq-27.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:190cbf120fbc0fc4957b56866830def56628934a9d112aec0e2507aa6a032b97", size = 633428, upload-time = "2025-09-08T23:08:00.663Z" }, - { url = "https://files.pythonhosted.org/packages/ae/14/01afebc96c5abbbd713ecfc7469cfb1bc801c819a74ed5c9fad9a48801cb/pyzmq-27.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:eca6b47df11a132d1745eb3b5b5e557a7dae2c303277aa0e69c6ba91b8736e07", size = 559497, upload-time = "2025-09-08T23:08:02.15Z" }, { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, @@ -4513,11 +4088,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, - { url = "https://files.pythonhosted.org/packages/4c/c6/c4dcdecdbaa70969ee1fdced6d7b8f60cfabe64d25361f27ac4665a70620/pyzmq-27.1.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:18770c8d3563715387139060d37859c02ce40718d1faf299abddcdcc6a649066", size = 836265, upload-time = "2025-09-08T23:09:49.376Z" }, - { url = "https://files.pythonhosted.org/packages/3e/79/f38c92eeaeb03a2ccc2ba9866f0439593bb08c5e3b714ac1d553e5c96e25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:ac25465d42f92e990f8d8b0546b01c391ad431c3bf447683fdc40565941d0604", size = 800208, upload-time = "2025-09-08T23:09:51.073Z" }, - { url = "https://files.pythonhosted.org/packages/49/0e/3f0d0d335c6b3abb9b7b723776d0b21fa7f3a6c819a0db6097059aada160/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53b40f8ae006f2734ee7608d59ed661419f087521edbfc2149c3932e9c14808c", size = 567747, upload-time = "2025-09-08T23:09:52.698Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/f2b3784d536250ffd4be70e049f3b60981235d70c6e8ce7e3ef21e1adb25/pyzmq-27.1.0-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f605d884e7c8be8fe1aa94e0a783bf3f591b84c24e4bc4f3e7564c82ac25e271", size = 747371, upload-time = "2025-09-08T23:09:54.563Z" }, - { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, ] [[package]] @@ -4541,13 +4111,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/14/98a78b819d7374dab309525ce45cd591d0d62db7f6ed2d5ed32b8f55d62b/raylib-5.5.0.4-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:09717ed32c9ec1c574370e2e2d30e9bc13876f7e2f2dd6e04dc366dae23e0994", size = 1632797, upload-time = "2025-12-11T15:27:15.429Z" }, - { url = "https://files.pythonhosted.org/packages/1e/f4/ec949f45274cf266875b30b67f8cb7243ecced05080cec54bf65ec73a8b2/raylib-5.5.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cef7b0e238eafc80a3be7e3c656a3ddc94cc523790758b7130df1957ba4ad4ad", size = 1550301, upload-time = "2025-12-11T15:27:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5a/8f60d367147019acef342746f20121b2341ec6596acd5c7941cb36bda02e/raylib-5.5.0.4-cp311-cp311-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:bdaa119b767f380caf6dd4f9d42ab3bf8596d8fb98737d2951b36924a5a83ac0", size = 2036797, upload-time = "2025-12-11T15:27:20.044Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ad/97dd93c389263c61a3057065f0f70db5fdc3c5768fa383a9b3e989ddb6a7/raylib-5.5.0.4-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:6a5cdeeb803d081342961eb1f7c4161af27e951d9ecf2b56d469d5730fcc6213", size = 2188009, upload-time = "2025-12-11T18:50:05.612Z" }, - { url = "https://files.pythonhosted.org/packages/42/6a/55be04012f3459842389689326910204f985cffcb8989a92475221f5660a/raylib-5.5.0.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4067fa8a6ed3eb78a1162fc2d40ce8c26c26c5ee544019d1902accf21ec22add", size = 2187633, upload-time = "2025-12-11T15:27:22.345Z" }, - { url = "https://files.pythonhosted.org/packages/6b/18/b69d9ad9f4064785ad29c73672d40b36c59c3b3efd1dee264cdff4b48bf6/raylib-5.5.0.4-cp311-cp311-win32.whl", hash = "sha256:f01a769bb0797ab4f6e1efc950d5d8aca53548e97da7f527190a1ca5f671c389", size = 1456775, upload-time = "2025-12-11T15:27:26.776Z" }, - { url = "https://files.pythonhosted.org/packages/1a/7a/4025d9ceeee8e3ae4748b0f6c356c5ce97628bd5da8a056b6782c87f7e65/raylib-5.5.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:34771dea34a30fa4657f35b344d5ebf9eb11d9b62b23d9349742db5c5f3992bd", size = 1705555, upload-time = "2025-12-11T15:27:28.888Z" }, { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, @@ -4555,9 +4118,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ba/fee7e6ae0be850f6581d4084ea97825b7895c8866fa8b2df347d408c8293/raylib-5.5.0.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:c318357ce721c62a6848b6d84b26574cd77267e5758cfa2dbc01d4deb2a2b0b8", size = 1211520, upload-time = "2025-12-11T15:28:30.266Z" }, - { url = "https://files.pythonhosted.org/packages/80/a0/847066c6d824f535068112ed362d41c499f9a4aca52b82b74d9dfb1bdfc7/raylib-5.5.0.4-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82a0ea2859d04f3b5b441910881ec48789484463856168fa8f35c7165e11d44c", size = 1433828, upload-time = "2025-12-11T15:28:32.204Z" }, - { url = "https://files.pythonhosted.org/packages/40/c6/a2cfb01d63246602ce49111f08d8716e1c7c2994efe4e14d87450176393c/raylib-5.5.0.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:871e77547cd3f78d98a47bef491821cd25c879b3b3b79f1973d8fb3f8841cdfb", size = 1572456, upload-time = "2025-12-11T15:28:34.333Z" }, ] [[package]] @@ -4647,16 +4207,6 @@ version = "1.3.7" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8d/48/49393a96a2eef1ab418b17475fb92b8fcfad83d099e678751b05472e69de/setproctitle-1.3.7.tar.gz", hash = "sha256:bc2bc917691c1537d5b9bca1468437176809c7e11e5694ca79a9ca12345dcb9e", size = 27002, upload-time = "2025-09-05T12:51:25.278Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/cd/1b7ba5cad635510720ce19d7122154df96a2387d2a74217be552887c93e5/setproctitle-1.3.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a600eeb4145fb0ee6c287cb82a2884bd4ec5bbb076921e287039dcc7b7cc6dd0", size = 18085, upload-time = "2025-09-05T12:49:22.183Z" }, - { url = "https://files.pythonhosted.org/packages/8f/1a/b2da0a620490aae355f9d72072ac13e901a9fec809a6a24fc6493a8f3c35/setproctitle-1.3.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:97a090fed480471bb175689859532709e28c085087e344bca45cf318034f70c4", size = 13097, upload-time = "2025-09-05T12:49:23.322Z" }, - { url = "https://files.pythonhosted.org/packages/18/2e/bd03ff02432a181c1787f6fc2a678f53b7dacdd5ded69c318fe1619556e8/setproctitle-1.3.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1607b963e7b53e24ec8a2cb4e0ab3ae591d7c6bf0a160feef0551da63452b37f", size = 32191, upload-time = "2025-09-05T12:49:24.567Z" }, - { url = "https://files.pythonhosted.org/packages/28/78/1e62fc0937a8549f2220445ed2175daacee9b6764c7963b16148119b016d/setproctitle-1.3.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a20fb1a3974e2dab857870cf874b325b8705605cb7e7e8bcbb915bca896f52a9", size = 33203, upload-time = "2025-09-05T12:49:25.871Z" }, - { url = "https://files.pythonhosted.org/packages/a0/3c/65edc65db3fa3df400cf13b05e9d41a3c77517b4839ce873aa6b4043184f/setproctitle-1.3.7-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f8d961bba676e07d77665204f36cffaa260f526e7b32d07ab3df6a2c1dfb44ba", size = 34963, upload-time = "2025-09-05T12:49:27.044Z" }, - { url = "https://files.pythonhosted.org/packages/a1/32/89157e3de997973e306e44152522385f428e16f92f3cf113461489e1e2ee/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:db0fd964fbd3a9f8999b502f65bd2e20883fdb5b1fae3a424e66db9a793ed307", size = 32398, upload-time = "2025-09-05T12:49:28.909Z" }, - { url = "https://files.pythonhosted.org/packages/4a/18/77a765a339ddf046844cb4513353d8e9dcd8183da9cdba6e078713e6b0b2/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:db116850fcf7cca19492030f8d3b4b6e231278e8fe097a043957d22ce1bdf3ee", size = 33657, upload-time = "2025-09-05T12:49:30.323Z" }, - { url = "https://files.pythonhosted.org/packages/6b/63/f0b6205c64d74d2a24a58644a38ec77bdbaa6afc13747e75973bf8904932/setproctitle-1.3.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:316664d8b24a5c91ee244460bdaf7a74a707adaa9e14fbe0dc0a53168bb9aba1", size = 31836, upload-time = "2025-09-05T12:49:32.309Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/e1277f9ba302f1a250bbd3eedbbee747a244b3cc682eb58fb9733968f6d8/setproctitle-1.3.7-cp311-cp311-win32.whl", hash = "sha256:b74774ca471c86c09b9d5037c8451fff06bb82cd320d26ae5a01c758088c0d5d", size = 12556, upload-time = "2025-09-05T12:49:33.529Z" }, - { url = "https://files.pythonhosted.org/packages/b6/7b/822a23f17e9003dfdee92cd72758441ca2a3680388da813a371b716fb07f/setproctitle-1.3.7-cp311-cp311-win_amd64.whl", hash = "sha256:acb9097213a8dd3410ed9f0dc147840e45ca9797785272928d4be3f0e69e3be4", size = 13243, upload-time = "2025-09-05T12:49:34.553Z" }, { url = "https://files.pythonhosted.org/packages/fb/f0/2dc88e842077719d7384d86cc47403e5102810492b33680e7dadcee64cd8/setproctitle-1.3.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2dc99aec591ab6126e636b11035a70991bc1ab7a261da428491a40b84376654e", size = 18049, upload-time = "2025-09-05T12:49:36.241Z" }, { url = "https://files.pythonhosted.org/packages/f0/b4/50940504466689cda65680c9e9a1e518e5750c10490639fa687489ac7013/setproctitle-1.3.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cdd8aa571b7aa39840fdbea620e308a19691ff595c3a10231e9ee830339dd798", size = 13079, upload-time = "2025-09-05T12:49:38.088Z" }, { url = "https://files.pythonhosted.org/packages/d0/99/71630546b9395b095f4082be41165d1078204d1696c2d9baade3de3202d0/setproctitle-1.3.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2906b6c7959cdb75f46159bf0acd8cc9906cf1361c9e1ded0d065fe8f9039629", size = 32932, upload-time = "2025-09-05T12:49:39.271Z" }, @@ -4667,9 +4217,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c8/b7/06145c238c0a6d2c4bc881f8be230bb9f36d2bf51aff7bddcb796d5eed67/setproctitle-1.3.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d8828b356114f6b308b04afe398ed93803d7fca4a955dd3abe84430e28d33739", size = 32795, upload-time = "2025-09-05T12:49:46.419Z" }, { url = "https://files.pythonhosted.org/packages/ef/dc/ef76a81fac9bf27b84ed23df19c1f67391a753eed6e3c2254ebcb5133f56/setproctitle-1.3.7-cp312-cp312-win32.whl", hash = "sha256:b0304f905efc845829ac2bc791ddebb976db2885f6171f4a3de678d7ee3f7c9f", size = 12552, upload-time = "2025-09-05T12:49:47.635Z" }, { url = "https://files.pythonhosted.org/packages/e2/5b/a9fe517912cd6e28cf43a212b80cb679ff179a91b623138a99796d7d18a0/setproctitle-1.3.7-cp312-cp312-win_amd64.whl", hash = "sha256:9888ceb4faea3116cf02a920ff00bfbc8cc899743e4b4ac914b03625bdc3c300", size = 13247, upload-time = "2025-09-05T12:49:49.16Z" }, - { url = "https://files.pythonhosted.org/packages/c3/5b/5e1c117ac84e3cefcf8d7a7f6b2461795a87e20869da065a5c087149060b/setproctitle-1.3.7-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:b1cac6a4b0252b8811d60b6d8d0f157c0fdfed379ac89c25a914e6346cf355a1", size = 12587, upload-time = "2025-09-05T12:51:21.195Z" }, - { url = "https://files.pythonhosted.org/packages/73/02/b9eadc226195dcfa90eed37afe56b5dd6fa2f0e5220ab8b7867b8862b926/setproctitle-1.3.7-pp311-pypy311_pp73-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f1704c9e041f2b1dc38f5be4552e141e1432fba3dd52c72eeffd5bc2db04dc65", size = 14286, upload-time = "2025-09-05T12:51:22.61Z" }, - { url = "https://files.pythonhosted.org/packages/28/26/1be1d2a53c2a91ec48fa2ff4a409b395f836798adf194d99de9c059419ea/setproctitle-1.3.7-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b08b61976ffa548bd5349ce54404bf6b2d51bd74d4f1b241ed1b0f25bce09c3a", size = 13282, upload-time = "2025-09-05T12:51:24.094Z" }, ] [[package]] @@ -4690,14 +4237,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/8d/1ff672dea9ec6a7b5d422eb6d095ed886e2e523733329f75fdcb14ee1149/shapely-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:91121757b0a36c9aac3427a651a7e6567110a4a67c97edf04f8d55d4765f6618", size = 1820038, upload-time = "2025-09-24T13:50:15.628Z" }, - { url = "https://files.pythonhosted.org/packages/4f/ce/28fab8c772ce5db23a0d86bf0adaee0c4c79d5ad1db766055fa3dab442e2/shapely-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:16a9c722ba774cf50b5d4541242b4cce05aafd44a015290c82ba8a16931ff63d", size = 1626039, upload-time = "2025-09-24T13:50:16.881Z" }, - { url = "https://files.pythonhosted.org/packages/70/8b/868b7e3f4982f5006e9395c1e12343c66a8155c0374fdc07c0e6a1ab547d/shapely-2.1.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cc4f7397459b12c0b196c9efe1f9d7e92463cbba142632b4cc6d8bbbbd3e2b09", size = 3001519, upload-time = "2025-09-24T13:50:18.606Z" }, - { url = "https://files.pythonhosted.org/packages/13/02/58b0b8d9c17c93ab6340edd8b7308c0c5a5b81f94ce65705819b7416dba5/shapely-2.1.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:136ab87b17e733e22f0961504d05e77e7be8c9b5a8184f685b4a91a84efe3c26", size = 3110842, upload-time = "2025-09-24T13:50:21.77Z" }, - { url = "https://files.pythonhosted.org/packages/af/61/8e389c97994d5f331dcffb25e2fa761aeedfb52b3ad9bcdd7b8671f4810a/shapely-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:16c5d0fc45d3aa0a69074979f4f1928ca2734fb2e0dde8af9611e134e46774e7", size = 4021316, upload-time = "2025-09-24T13:50:23.626Z" }, - { url = "https://files.pythonhosted.org/packages/d3/d4/9b2a9fe6039f9e42ccf2cb3e84f219fd8364b0c3b8e7bbc857b5fbe9c14c/shapely-2.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ddc759f72b5b2b0f54a7e7cde44acef680a55019eb52ac63a7af2cf17cb9cd2", size = 4178586, upload-time = "2025-09-24T13:50:25.443Z" }, - { url = "https://files.pythonhosted.org/packages/16/f6/9840f6963ed4decf76b08fd6d7fed14f8779fb7a62cb45c5617fa8ac6eab/shapely-2.1.2-cp311-cp311-win32.whl", hash = "sha256:2fa78b49485391224755a856ed3b3bd91c8455f6121fee0db0e71cefb07d0ef6", size = 1543961, upload-time = "2025-09-24T13:50:26.968Z" }, - { url = "https://files.pythonhosted.org/packages/38/1e/3f8ea46353c2a33c1669eb7327f9665103aa3a8dfe7f2e4ef714c210b2c2/shapely-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:c64d5c97b2f47e3cd9b712eaced3b061f2b71234b3fc263e0fcf7d889c6559dc", size = 1722856, upload-time = "2025-09-24T13:50:28.497Z" }, { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, @@ -4760,15 +4299,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, ] -[[package]] -name = "tabulate" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/fe/802052aecb21e3797b8f7902564ab6ea0d60ff8ca23952079064155d1ae1/tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c", size = 81090, upload-time = "2022-10-06T17:21:48.54Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, -] - [[package]] name = "tqdm" version = "4.67.1" @@ -4829,9 +4359,6 @@ version = "6.0.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/24/d9be5cd6642a6aa68352ded4b4b10fb0d7889cb7f45814fb92cecd35f101/watchdog-6.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6eb11feb5a0d452ee41f824e271ca311a09e250441c262ca2fd7ebcf2461a06c", size = 96393, upload-time = "2024-11-01T14:06:31.756Z" }, - { url = "https://files.pythonhosted.org/packages/63/7a/6013b0d8dbc56adca7fdd4f0beed381c59f6752341b12fa0886fa7afc78b/watchdog-6.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef810fbf7b781a5a593894e4f439773830bdecb885e6880d957d5b9382a960d2", size = 88392, upload-time = "2024-11-01T14:06:32.99Z" }, - { url = "https://files.pythonhosted.org/packages/d1/40/b75381494851556de56281e053700e46bff5b37bf4c7267e858640af5a7f/watchdog-6.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:afd0fe1b2270917c5e23c2a65ce50c2a4abb63daafb0d419fde368e272a76b7c", size = 89019, upload-time = "2024-11-01T14:06:34.963Z" }, { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, @@ -4865,13 +4392,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/08/d5/25f7b19af3a2cb4000cac4f9e5525a40bec79f4f5d0ac9b517c0544586a0/xattr-1.3.0.tar.gz", hash = "sha256:30439fabd7de0787b27e9a6e1d569c5959854cb322f64ce7380fedbfa5035036", size = 17148, upload-time = "2025-10-13T22:16:47.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/64/292426ad5653e72c6e1325bbff22868a20077290d967cebb9c0624ad08b6/xattr-1.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:331a51bf8f20c27822f44054b0d760588462d3ed472d5e52ba135cf0bea510e8", size = 23448, upload-time = "2025-10-13T22:15:59.229Z" }, - { url = "https://files.pythonhosted.org/packages/63/84/6539fbe620da8e5927406e76b9c8abad8953025d5f578d792747c38a8c0e/xattr-1.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:196360f068b74fa0132a8c6001ce1333f095364b8f43b6fd8cdaf2f18741ef89", size = 18553, upload-time = "2025-10-13T22:16:00.151Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bb/c1c2e24a49f8d13ff878fb85aabc42ea1b2f98ce08d8205b9661d517a9cc/xattr-1.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:405d2e4911d37f2b9400fa501acd920fe0c97fe2b2ec252cb23df4b59c000811", size = 18848, upload-time = "2025-10-13T22:16:01.046Z" }, - { url = "https://files.pythonhosted.org/packages/02/c2/a60aad150322b217dfe33695d8d9f32bc01e8f300641b6ba4b73f4b3c03f/xattr-1.3.0-cp311-cp311-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4ae3a66ae1effd40994f64defeeaa97da369406485e60bfb421f2d781be3b75d", size = 38547, upload-time = "2025-10-13T22:16:01.973Z" }, - { url = "https://files.pythonhosted.org/packages/c6/58/2eca142bad4ea0a2be6b58d3122d0acce310c4e53fa7defd168202772178/xattr-1.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:69cd3bfe779f7ba87abe6473fdfa428460cf9e78aeb7e390cfd737b784edf1b5", size = 38753, upload-time = "2025-10-13T22:16:03.244Z" }, - { url = "https://files.pythonhosted.org/packages/2b/50/d032e5254c2c27d36bdb02abdf2735db6768a441f0e3d0f139e0f9f56638/xattr-1.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c5742ca61761a99ae0c522f90a39d5fb8139280f27b254e3128482296d1df2db", size = 38054, upload-time = "2025-10-13T22:16:04.656Z" }, - { url = "https://files.pythonhosted.org/packages/04/24/458a306439aabe0083ca0a7b14c3e6a800ab9782b5ec0bdcec4ec9f3dc6c/xattr-1.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a04ada131e9bdfd32db3ab1efa9f852646f4f7c9d6fde0596c3825c67161be3", size = 37562, upload-time = "2025-10-13T22:16:05.97Z" }, { url = "https://files.pythonhosted.org/packages/bf/78/00bdc9290066173e53e1e734d8d8e1a84a6faa9c66aee9df81e4d9aeec1c/xattr-1.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dd4e63614722d183e81842cb237fd1cc978d43384166f9fe22368bfcb187ebe5", size = 23476, upload-time = "2025-10-13T22:16:06.942Z" }, { url = "https://files.pythonhosted.org/packages/53/16/5243722294eb982514fa7b6b87a29dfb7b29b8e5e1486500c5babaf6e4b3/xattr-1.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:995843ef374af73e3370b0c107319611f3cdcdb6d151d629449efecad36be4c4", size = 18556, upload-time = "2025-10-13T22:16:08.209Z" }, { url = "https://files.pythonhosted.org/packages/d6/5c/d7ab0e547bea885b55f097206459bd612cefb652c5fc1f747130cbc0d42c/xattr-1.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fa23a25220e29d956cedf75746e3df6cc824cc1553326d6516479967c540e386", size = 18869, upload-time = "2025-10-13T22:16:10.319Z" }, @@ -4904,22 +4424,6 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/27/5ab13fc84c76a0250afd3d26d5936349a35be56ce5785447d6c423b26d92/yarl-1.22.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:1ab72135b1f2db3fed3997d7e7dc1b80573c67138023852b6efb336a5eae6511", size = 141607, upload-time = "2025-10-06T14:09:16.298Z" }, - { url = "https://files.pythonhosted.org/packages/6a/a1/d065d51d02dc02ce81501d476b9ed2229d9a990818332242a882d5d60340/yarl-1.22.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:669930400e375570189492dc8d8341301578e8493aec04aebc20d4717f899dd6", size = 94027, upload-time = "2025-10-06T14:09:17.786Z" }, - { url = "https://files.pythonhosted.org/packages/c1/da/8da9f6a53f67b5106ffe902c6fa0164e10398d4e150d85838b82f424072a/yarl-1.22.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:792a2af6d58177ef7c19cbf0097aba92ca1b9cb3ffdd9c7470e156c8f9b5e028", size = 94963, upload-time = "2025-10-06T14:09:19.662Z" }, - { url = "https://files.pythonhosted.org/packages/68/fe/2c1f674960c376e29cb0bec1249b117d11738db92a6ccc4a530b972648db/yarl-1.22.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ea66b1c11c9150f1372f69afb6b8116f2dd7286f38e14ea71a44eee9ec51b9d", size = 368406, upload-time = "2025-10-06T14:09:21.402Z" }, - { url = "https://files.pythonhosted.org/packages/95/26/812a540e1c3c6418fec60e9bbd38e871eaba9545e94fa5eff8f4a8e28e1e/yarl-1.22.0-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3e2daa88dc91870215961e96a039ec73e4937da13cf77ce17f9cad0c18df3503", size = 336581, upload-time = "2025-10-06T14:09:22.98Z" }, - { url = "https://files.pythonhosted.org/packages/0b/f5/5777b19e26fdf98563985e481f8be3d8a39f8734147a6ebf459d0dab5a6b/yarl-1.22.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba440ae430c00eee41509353628600212112cd5018d5def7e9b05ea7ac34eb65", size = 388924, upload-time = "2025-10-06T14:09:24.655Z" }, - { url = "https://files.pythonhosted.org/packages/86/08/24bd2477bd59c0bbd994fe1d93b126e0472e4e3df5a96a277b0a55309e89/yarl-1.22.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e6438cc8f23a9c1478633d216b16104a586b9761db62bfacb6425bac0a36679e", size = 392890, upload-time = "2025-10-06T14:09:26.617Z" }, - { url = "https://files.pythonhosted.org/packages/46/00/71b90ed48e895667ecfb1eaab27c1523ee2fa217433ed77a73b13205ca4b/yarl-1.22.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4c52a6e78aef5cf47a98ef8e934755abf53953379b7d53e68b15ff4420e6683d", size = 365819, upload-time = "2025-10-06T14:09:28.544Z" }, - { url = "https://files.pythonhosted.org/packages/30/2d/f715501cae832651d3282387c6a9236cd26bd00d0ff1e404b3dc52447884/yarl-1.22.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3b06bcadaac49c70f4c88af4ffcfbe3dc155aab3163e75777818092478bcbbe7", size = 363601, upload-time = "2025-10-06T14:09:30.568Z" }, - { url = "https://files.pythonhosted.org/packages/f8/f9/a678c992d78e394e7126ee0b0e4e71bd2775e4334d00a9278c06a6cce96a/yarl-1.22.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6944b2dc72c4d7f7052683487e3677456050ff77fcf5e6204e98caf785ad1967", size = 358072, upload-time = "2025-10-06T14:09:32.528Z" }, - { url = "https://files.pythonhosted.org/packages/2c/d1/b49454411a60edb6fefdcad4f8e6dbba7d8019e3a508a1c5836cba6d0781/yarl-1.22.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d5372ca1df0f91a86b047d1277c2aaf1edb32d78bbcefffc81b40ffd18f027ed", size = 385311, upload-time = "2025-10-06T14:09:34.634Z" }, - { url = "https://files.pythonhosted.org/packages/87/e5/40d7a94debb8448c7771a916d1861d6609dddf7958dc381117e7ba36d9e8/yarl-1.22.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:51af598701f5299012b8416486b40fceef8c26fc87dc6d7d1f6fc30609ea0aa6", size = 381094, upload-time = "2025-10-06T14:09:36.268Z" }, - { url = "https://files.pythonhosted.org/packages/35/d8/611cc282502381ad855448643e1ad0538957fc82ae83dfe7762c14069e14/yarl-1.22.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b266bd01fedeffeeac01a79ae181719ff848a5a13ce10075adbefc8f1daee70e", size = 370944, upload-time = "2025-10-06T14:09:37.872Z" }, - { url = "https://files.pythonhosted.org/packages/2d/df/fadd00fb1c90e1a5a8bd731fa3d3de2e165e5a3666a095b04e31b04d9cb6/yarl-1.22.0-cp311-cp311-win32.whl", hash = "sha256:a9b1ba5610a4e20f655258d5a1fdc7ebe3d837bb0e45b581398b99eb98b1f5ca", size = 81804, upload-time = "2025-10-06T14:09:39.359Z" }, - { url = "https://files.pythonhosted.org/packages/b5/f7/149bb6f45f267cb5c074ac40c01c6b3ea6d8a620d34b337f6321928a1b4d/yarl-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:078278b9b0b11568937d9509b589ee83ef98ed6d561dfe2020e24a9fd08eaa2b", size = 86858, upload-time = "2025-10-06T14:09:41.068Z" }, - { url = "https://files.pythonhosted.org/packages/2b/13/88b78b93ad3f2f0b78e13bfaaa24d11cbc746e93fe76d8c06bf139615646/yarl-1.22.0-cp311-cp311-win_arm64.whl", hash = "sha256:b6a6f620cfe13ccec221fa312139135166e47ae169f8253f72a0abc0dae94376", size = 81637, upload-time = "2025-10-06T14:09:42.712Z" }, { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, @@ -4945,23 +4449,6 @@ version = "0.25.0" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/83/c3ca27c363d104980f1c9cee1101cc8ba724ac8c28a033ede6aab89585b1/zstandard-0.25.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:933b65d7680ea337180733cf9e87293cc5500cc0eb3fc8769f4d3c88d724ec5c", size = 795254, upload-time = "2025-09-14T22:16:26.137Z" }, - { url = "https://files.pythonhosted.org/packages/ac/4d/e66465c5411a7cf4866aeadc7d108081d8ceba9bc7abe6b14aa21c671ec3/zstandard-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3f79487c687b1fc69f19e487cd949bf3aae653d181dfb5fde3bf6d18894706f", size = 640559, upload-time = "2025-09-14T22:16:27.973Z" }, - { url = "https://files.pythonhosted.org/packages/12/56/354fe655905f290d3b147b33fe946b0f27e791e4b50a5f004c802cb3eb7b/zstandard-0.25.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0bbc9a0c65ce0eea3c34a691e3c4b6889f5f3909ba4822ab385fab9057099431", size = 5348020, upload-time = "2025-09-14T22:16:29.523Z" }, - { url = "https://files.pythonhosted.org/packages/3b/13/2b7ed68bd85e69a2069bcc72141d378f22cae5a0f3b353a2c8f50ef30c1b/zstandard-0.25.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:01582723b3ccd6939ab7b3a78622c573799d5d8737b534b86d0e06ac18dbde4a", size = 5058126, upload-time = "2025-09-14T22:16:31.811Z" }, - { url = "https://files.pythonhosted.org/packages/c9/dd/fdaf0674f4b10d92cb120ccff58bbb6626bf8368f00ebfd2a41ba4a0dc99/zstandard-0.25.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5f1ad7bf88535edcf30038f6919abe087f606f62c00a87d7e33e7fc57cb69fcc", size = 5405390, upload-time = "2025-09-14T22:16:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/0f/67/354d1555575bc2490435f90d67ca4dd65238ff2f119f30f72d5cde09c2ad/zstandard-0.25.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:06acb75eebeedb77b69048031282737717a63e71e4ae3f77cc0c3b9508320df6", size = 5452914, upload-time = "2025-09-14T22:16:35.277Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/e9cfd801a3f9190bf3e759c422bbfd2247db9d7f3d54a56ecde70137791a/zstandard-0.25.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9300d02ea7c6506f00e627e287e0492a5eb0371ec1670ae852fefffa6164b072", size = 5559635, upload-time = "2025-09-14T22:16:37.141Z" }, - { url = "https://files.pythonhosted.org/packages/21/88/5ba550f797ca953a52d708c8e4f380959e7e3280af029e38fbf47b55916e/zstandard-0.25.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bfd06b1c5584b657a2892a6014c2f4c20e0db0208c159148fa78c65f7e0b0277", size = 5048277, upload-time = "2025-09-14T22:16:38.807Z" }, - { url = "https://files.pythonhosted.org/packages/46/c0/ca3e533b4fa03112facbe7fbe7779cb1ebec215688e5df576fe5429172e0/zstandard-0.25.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f373da2c1757bb7f1acaf09369cdc1d51d84131e50d5fa9863982fd626466313", size = 5574377, upload-time = "2025-09-14T22:16:40.523Z" }, - { url = "https://files.pythonhosted.org/packages/12/9b/3fb626390113f272abd0799fd677ea33d5fc3ec185e62e6be534493c4b60/zstandard-0.25.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c0e5a65158a7946e7a7affa6418878ef97ab66636f13353b8502d7ea03c8097", size = 4961493, upload-time = "2025-09-14T22:16:43.3Z" }, - { url = "https://files.pythonhosted.org/packages/cb/d3/23094a6b6a4b1343b27ae68249daa17ae0651fcfec9ed4de09d14b940285/zstandard-0.25.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c8e167d5adf59476fa3e37bee730890e389410c354771a62e3c076c86f9f7778", size = 5269018, upload-time = "2025-09-14T22:16:45.292Z" }, - { url = "https://files.pythonhosted.org/packages/8c/a7/bb5a0c1c0f3f4b5e9d5b55198e39de91e04ba7c205cc46fcb0f95f0383c1/zstandard-0.25.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:98750a309eb2f020da61e727de7d7ba3c57c97cf6213f6f6277bb7fb42a8e065", size = 5443672, upload-time = "2025-09-14T22:16:47.076Z" }, - { url = "https://files.pythonhosted.org/packages/27/22/503347aa08d073993f25109c36c8d9f029c7d5949198050962cb568dfa5e/zstandard-0.25.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:22a086cff1b6ceca18a8dd6096ec631e430e93a8e70a9ca5efa7561a00f826fa", size = 5822753, upload-time = "2025-09-14T22:16:49.316Z" }, - { url = "https://files.pythonhosted.org/packages/e2/be/94267dc6ee64f0f8ba2b2ae7c7a2df934a816baaa7291db9e1aa77394c3c/zstandard-0.25.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:72d35d7aa0bba323965da807a462b0966c91608ef3a48ba761678cb20ce5d8b7", size = 5366047, upload-time = "2025-09-14T22:16:51.328Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a3/732893eab0a3a7aecff8b99052fecf9f605cf0fb5fb6d0290e36beee47a4/zstandard-0.25.0-cp311-cp311-win32.whl", hash = "sha256:f5aeea11ded7320a84dcdd62a3d95b5186834224a9e55b92ccae35d21a8b63d4", size = 436484, upload-time = "2025-09-14T22:16:55.005Z" }, - { url = "https://files.pythonhosted.org/packages/43/a3/c6155f5c1cce691cb80dfd38627046e50af3ee9ddc5d0b45b9b063bfb8c9/zstandard-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:daab68faadb847063d0c56f361a289c4f268706b598afbf9ad113cbe5c38b6b2", size = 506183, upload-time = "2025-09-14T22:16:52.753Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/8945ab86a0820cc0e0cdbf38086a92868a9172020fdab8a03ac19662b0e5/zstandard-0.25.0-cp311-cp311-win_arm64.whl", hash = "sha256:22a06c5df3751bb7dc67406f5374734ccee8ed37fc5981bf1ad7041831fa1137", size = 462533, upload-time = "2025-09-14T22:16:53.878Z" }, { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, From c65cf18c7564d985837dbc7bb054556002bb5953 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 21:00:56 -0800 Subject: [PATCH 139/685] Better memory usage debugging (#37120) --- cereal/log.capnp | 5 + selfdrive/debug/mem_usage.py | 238 ++++++++++++++++++++++++++++++++++ selfdrive/test/test_onroad.py | 7 +- system/proclogd.py | 59 +++++++++ 4 files changed, 307 insertions(+), 2 deletions(-) create mode 100755 selfdrive/debug/mem_usage.py diff --git a/cereal/log.capnp b/cereal/log.capnp index 12bef17b950..119cf299999 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -1478,6 +1478,11 @@ struct ProcLog { cmdline @15 :List(Text); exe @16 :Text; + + # from /proc//smaps_rollup (proportional/private memory) + memPss @17 :UInt64; # Pss — shared pages split by mapper count + memPssAnon @18 :UInt64; # Pss_Anon — private anonymous (heap, stack) + memPssShmem @19 :UInt64; # Pss_Shmem — proportional MSGQ/tmpfs share } struct CPUTimes { diff --git a/selfdrive/debug/mem_usage.py b/selfdrive/debug/mem_usage.py new file mode 100755 index 00000000000..9bed566e19b --- /dev/null +++ b/selfdrive/debug/mem_usage.py @@ -0,0 +1,238 @@ +#!/usr/bin/env python3 +import argparse +import os +from collections import defaultdict + +import numpy as np +from tabulate import tabulate + +from openpilot.tools.lib.logreader import LogReader + +DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +MB = 1024 * 1024 +TABULATE_OPTS = dict(tablefmt="simple_grid", stralign="center", numalign="center") + + +def _get_procs(): + from openpilot.selfdrive.test.test_onroad import PROCS + return PROCS + + +def is_openpilot_proc(name): + if any(p in name for p in _get_procs()): + return True + # catch openpilot processes not in PROCS (athenad, manager, etc.) + return 'openpilot' in name or name.startswith(('selfdrive.', 'system.')) + + +def get_proc_name(proc): + if len(proc.cmdline) > 0: + return list(proc.cmdline)[0] + return proc.name + + +def pct(val_mb, total_mb): + return val_mb / total_mb * 100 if total_mb else 0 + + +def has_pss(proc_logs): + """Check if logs contain PSS data (new field, not in old logs).""" + try: + for proc in proc_logs[-1].procLog.procs: + if proc.memPss > 0: + return True + except AttributeError: + pass + return False + + +def print_summary(proc_logs, device_states): + mem = proc_logs[-1].procLog.mem + total = mem.total / MB + used = (mem.total - mem.available) / MB + cached = mem.cached / MB + shared = mem.shared / MB + buffers = mem.buffers / MB + + lines = [ + f" Total: {total:.0f} MB", + f" Used (total-avail): {used:.0f} MB ({pct(used, total):.0f}%)", + f" Cached: {cached:.0f} MB ({pct(cached, total):.0f}%) Buffers: {buffers:.0f} MB ({pct(buffers, total):.0f}%)", + f" Shared/MSGQ: {shared:.0f} MB ({pct(shared, total):.0f}%)", + ] + + if device_states: + mem_pcts = [m.deviceState.memoryUsagePercent for m in device_states] + lines.append(f" deviceState memory: {np.min(mem_pcts)}-{np.max(mem_pcts)}% (avg {np.mean(mem_pcts):.0f}%)") + + print("\n-- Memory Summary --") + print("\n".join(lines)) + return total + + +def collect_per_process_mem(proc_logs, use_pss): + """Collect per-process memory samples. Returns {name: {metric: [values_per_sample_in_MB]}}.""" + by_proc = defaultdict(lambda: defaultdict(list)) + + for msg in proc_logs: + sample = defaultdict(lambda: defaultdict(float)) + for proc in msg.procLog.procs: + name = get_proc_name(proc) + sample[name]['rss'] += proc.memRss / MB + if use_pss: + sample[name]['pss'] += proc.memPss / MB + sample[name]['pss_anon'] += proc.memPssAnon / MB + sample[name]['pss_shmem'] += proc.memPssShmem / MB + + for name, metrics in sample.items(): + for metric, val in metrics.items(): + by_proc[name][metric].append(val) + + return by_proc + + +def _has_pss_detail(by_proc) -> bool: + """Check if any process has non-zero pss_anon/pss_shmem (unavailable on some kernels).""" + return any(sum(v.get('pss_anon', [])) > 0 or sum(v.get('pss_shmem', [])) > 0 for v in by_proc.values()) + + +def process_table_rows(by_proc, total_mb, use_pss, show_detail): + """Build table rows. Returns (rows, total_row).""" + mem_key = 'pss' if use_pss else 'rss' + rows = [] + for name in sorted(by_proc, key=lambda n: np.mean(by_proc[n][mem_key]), reverse=True): + m = by_proc[name] + vals = m[mem_key] + avg = round(np.mean(vals)) + row = [name, f"{avg} MB", f"{round(np.max(vals))} MB", f"{round(pct(avg, total_mb), 1)}%"] + if show_detail: + row.append(f"{round(np.mean(m['pss_anon']))} MB") + row.append(f"{round(np.mean(m['pss_shmem']))} MB") + rows.append(row) + + # Total row + total_row = None + if by_proc: + max_samples = max(len(v[mem_key]) for v in by_proc.values()) + totals = [] + for i in range(max_samples): + s = sum(v[mem_key][i] for v in by_proc.values() if i < len(v[mem_key])) + totals.append(s) + avg_total = round(np.mean(totals)) + total_row = ["TOTAL", f"{avg_total} MB", f"{round(np.max(totals))} MB", f"{round(pct(avg_total, total_mb), 1)}%"] + if show_detail: + total_row.append(f"{round(sum(np.mean(v['pss_anon']) for v in by_proc.values()))} MB") + total_row.append(f"{round(sum(np.mean(v['pss_shmem']) for v in by_proc.values()))} MB") + + return rows, total_row + + +def print_process_tables(op_procs, other_procs, total_mb, use_pss): + all_procs = {**op_procs, **other_procs} + show_detail = use_pss and _has_pss_detail(all_procs) + + header = ["process", "avg", "max", "%"] + if show_detail: + header += ["anon", "shmem"] + + op_rows, op_total = process_table_rows(op_procs, total_mb, use_pss, show_detail) + # filter other: >5MB avg and not bare interpreter paths (test infra noise) + other_filtered = {n: v for n, v in other_procs.items() + if np.mean(v['pss' if use_pss else 'rss']) > 5.0 + and os.path.basename(n.split()[0]) not in ('python', 'python3')} + other_rows, other_total = process_table_rows(other_filtered, total_mb, use_pss, show_detail) + + rows = op_rows + if op_total: + rows.append(op_total) + if other_rows: + sep_width = len(header) + rows.append([""] * sep_width) + rows.extend(other_rows) + if other_total: + other_total[0] = "TOTAL (other)" + rows.append(other_total) + + metric = "PSS (no shared double-count)" if use_pss else "RSS (includes shared, overcounts)" + print(f"\n-- Per-Process Memory: {metric} --") + print(tabulate(rows, header, **TABULATE_OPTS)) + + +def print_memory_accounting(proc_logs, op_procs, other_procs, total_mb, use_pss): + last = proc_logs[-1].procLog.mem + used = (last.total - last.available) / MB + shared = last.shared / MB + cached_buf = (last.buffers + last.cached) / MB - shared # shared (MSGQ) is in Cached; separate it + msgq = shared + + mem_key = 'pss' if use_pss else 'rss' + op_total = sum(v[mem_key][-1] for v in op_procs.values()) if op_procs else 0 + other_total = sum(v[mem_key][-1] for v in other_procs.values()) if other_procs else 0 + proc_sum = op_total + other_total + remainder = used - (cached_buf + msgq) - proc_sum + + if not use_pss: + # RSS double-counts shared; add back once to partially correct + remainder += shared + + header = ["", "MB", "%", ""] + label = "PSS" if use_pss else "RSS*" + rows = [ + ["Used (total - avail)", f"{used:.0f}", f"{pct(used, total_mb):.1f}", "memory in use by the system"], + [" Cached + Buffers", f"{cached_buf:.0f}", f"{pct(cached_buf, total_mb):.1f}", "pagecache + fs metadata, reclaimable"], + [" MSGQ (shared)", f"{msgq:.0f}", f"{pct(msgq, total_mb):.1f}", "/dev/shm tmpfs, also in process PSS"], + [f" openpilot {label}", f"{op_total:.0f}", f"{pct(op_total, total_mb):.1f}", "sum of openpilot process memory"], + [f" other {label}", f"{other_total:.0f}", f"{pct(other_total, total_mb):.1f}", "sum of non-openpilot process memory"], + [" kernel/ION/GPU", f"{remainder:.0f}", f"{pct(remainder, total_mb):.1f}", "slab, ION/DMA-BUF, GPU, page tables"], + ] + note = "" if use_pss else " (*RSS overcounts shared mem)" + print(f"\n-- Memory Accounting (last sample){note} --") + print(tabulate(rows, header, tablefmt="simple_grid", stralign="right")) + + +def print_report(proc_logs, device_states=None): + """Print full memory analysis report. Can be called from tests or CLI.""" + if not proc_logs: + print("No procLog messages found") + return + + print(f"{len(proc_logs)} procLog samples, {len(device_states or [])} deviceState samples") + + use_pss = has_pss(proc_logs) + if not use_pss: + print(" (no PSS data — re-record with updated proclogd for accurate numbers)") + + total_mb = print_summary(proc_logs, device_states or []) + + by_proc = collect_per_process_mem(proc_logs, use_pss) + op_procs = {n: v for n, v in by_proc.items() if is_openpilot_proc(n)} + other_procs = {n: v for n, v in by_proc.items() if not is_openpilot_proc(n)} + + print_process_tables(op_procs, other_procs, total_mb, use_pss) + print_memory_accounting(proc_logs, op_procs, other_procs, total_mb, use_pss) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Analyze memory usage from route logs") + parser.add_argument("route", nargs="?", default=None, help="route ID or local rlog path") + parser.add_argument("--demo", action="store_true", help=f"use demo route ({DEMO_ROUTE})") + args = parser.parse_args() + + if args.demo: + route = DEMO_ROUTE + elif args.route: + route = args.route + else: + parser.error("provide a route or use --demo") + + print(f"Reading logs from: {route}") + + proc_logs = [] + device_states = [] + for msg in LogReader(route): + if msg.which() == 'procLog': + proc_logs.append(msg) + elif msg.which() == 'deviceState': + device_states.append(msg) + + print_report(proc_logs, device_states) diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 33e8685a384..008b8ebe7f1 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -56,7 +56,7 @@ "selfdrive.ui.soundd": 3.0, "selfdrive.ui.feedback.feedbackd": 1.0, "selfdrive.monitoring.dmonitoringd": 4.0, - "system.proclogd": 3.0, + "system.proclogd": 7.0, "system.logmessaged": 1.0, "system.tombstoned": 0, "system.journald": 1.0, @@ -282,9 +282,12 @@ def test_memory_usage(self): print("\n------------------------------------------------") print("--------------- Memory Usage -------------------") print("------------------------------------------------") + + from openpilot.selfdrive.debug.mem_usage import print_report + print_report(self.msgs['procLog'], self.msgs['deviceState']) + offset = int(SERVICE_LIST['deviceState'].frequency * LOG_OFFSET) mems = [m.deviceState.memoryUsagePercent for m in self.msgs['deviceState'][offset:]] - print("Overall memory usage: ", mems) print("MSGQ (/dev/shm/) usage: ", subprocess.check_output(["du", "-hs", "/dev/shm"]).split()[0].decode()) # check for big leaks. note that memory usage is diff --git a/system/proclogd.py b/system/proclogd.py index 3279425b7b3..b008f8ed9bc 100755 --- a/system/proclogd.py +++ b/system/proclogd.py @@ -115,6 +115,55 @@ def _parse_proc_stat(stat: str) -> ProcStat | None: cloudlog.exception("failed to parse /proc//stat") return None +class SmapsData(TypedDict): + pss: int # bytes + pss_anon: int # bytes + pss_shmem: int # bytes + + +_SMAPS_KEYS = {b'Pss:', b'Pss_Anon:', b'Pss_Shmem:'} + +# smaps_rollup (kernel 4.14+) is ideal but missing on some BSP kernels; +# fall back to per-VMA smaps (any kernel). Pss_Anon/Pss_Shmem only in 5.x+. +_smaps_path: str | None = None # auto-detected on first call + +# per-VMA smaps is expensive (kernel walks page tables for every VMA). +# cache results and only refresh every N cycles to keep CPU low. +_smaps_cache: dict[int, SmapsData] = {} +_smaps_cycle = 0 +_SMAPS_EVERY = 20 # refresh every 20th cycle (40s at 0.5Hz) + + +def _read_smaps(pid: int) -> SmapsData: + global _smaps_path + try: + if _smaps_path is None: + _smaps_path = 'smaps_rollup' if os.path.exists(f'/proc/{pid}/smaps_rollup') else 'smaps' + + result: SmapsData = {'pss': 0, 'pss_anon': 0, 'pss_shmem': 0} + with open(f'/proc/{pid}/{_smaps_path}', 'rb') as f: + for line in f: + parts = line.split() + if len(parts) >= 2 and parts[0] in _SMAPS_KEYS: + val = int(parts[1]) * 1024 # kB -> bytes + if parts[0] == b'Pss:': + result['pss'] += val + elif parts[0] == b'Pss_Anon:': + result['pss_anon'] += val + elif parts[0] == b'Pss_Shmem:': + result['pss_shmem'] += val + return result + except (FileNotFoundError, PermissionError, ProcessLookupError, OSError): + return {'pss': 0, 'pss_anon': 0, 'pss_shmem': 0} + + +def _get_smaps_cached(pid: int) -> SmapsData: + """Return cached smaps data, refreshing every _SMAPS_EVERY cycles.""" + if _smaps_cycle == 0 or pid not in _smaps_cache: + _smaps_cache[pid] = _read_smaps(pid) + return _smaps_cache.get(pid, {'pss': 0, 'pss_anon': 0, 'pss_shmem': 0}) + + class ProcExtra(TypedDict): pid: int name: str @@ -189,6 +238,13 @@ def build_proc_log_message(msg) -> None: for j, arg in enumerate(extra['cmdline']): cmdline[j] = arg + # smaps is expensive (kernel walks page tables); skip small processes, use cache + if r['rss'] * PAGE_SIZE > 5 * 1024 * 1024: + smaps = _get_smaps_cached(r['pid']) + proc.memPss = smaps['pss'] + proc.memPssAnon = smaps['pss_anon'] + proc.memPssShmem = smaps['pss_shmem'] + cpu_times = _cpu_times() cpu_list = pl.init('cpuTimes', len(cpu_times)) for i, ct in enumerate(cpu_times): @@ -212,6 +268,9 @@ def build_proc_log_message(msg) -> None: pl.mem.inactive = mem_info["Inactive:"] pl.mem.shared = mem_info["Shmem:"] + global _smaps_cycle + _smaps_cycle = (_smaps_cycle + 1) % _SMAPS_EVERY + def main() -> NoReturn: pm = messaging.PubMaster(['procLog']) From 667f3bb32f696b8bdcd93bce9143cd4171e436a3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 21:36:44 -0800 Subject: [PATCH 140/685] Revert "revert tg calib and opencl cleanup (#37113)" (#37115) * Revert "revert tg calib and opencl cleanup (#37113)" This reverts commit 51312afd3da58ac73cc98d5c908cbebd70498ba5. * power draw is a lil higher * just don't miss a cycle * fix warp targets * fix tinygrad dep --- Dockerfile.openpilot_base | 37 - SConstruct | 1 - common/SConscript | 1 - common/clutil.cc | 98 -- common/clutil.h | 28 - common/mat.h | 85 - msgq_repo | 2 +- selfdrive/modeld/SConscript | 56 +- selfdrive/modeld/compile_warp.py | 206 +++ selfdrive/modeld/dmonitoringmodeld.py | 33 +- selfdrive/modeld/modeld.py | 59 +- selfdrive/modeld/models/commonmodel.cc | 64 - selfdrive/modeld/models/commonmodel.h | 97 -- selfdrive/modeld/models/commonmodel.pxd | 27 - selfdrive/modeld/models/commonmodel_pyx.pxd | 13 - selfdrive/modeld/models/commonmodel_pyx.pyx | 74 - selfdrive/modeld/runners/tinygrad_helpers.py | 8 - selfdrive/modeld/transforms/loadyuv.cc | 76 - selfdrive/modeld/transforms/loadyuv.cl | 47 - selfdrive/modeld/transforms/loadyuv.h | 20 - selfdrive/modeld/transforms/transform.cc | 97 -- selfdrive/modeld/transforms/transform.cl | 54 - selfdrive/modeld/transforms/transform.h | 25 - selfdrive/test/process_replay/model_replay.py | 4 +- .../test/process_replay/process_replay.py | 15 +- system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.cc | 5 +- system/camerad/cameras/camera_common.h | 2 +- system/camerad/cameras/camera_qcom2.cc | 22 +- system/camerad/cameras/spectra.cc | 4 +- system/camerad/cameras/spectra.h | 2 +- system/hardware/tici/tests/test_power_draw.py | 2 +- system/loggerd/SConscript | 5 +- third_party/opencl/include/CL/cl.h | 1452 ----------------- third_party/opencl/include/CL/cl_d3d10.h | 131 -- third_party/opencl/include/CL/cl_d3d11.h | 131 -- .../opencl/include/CL/cl_dx9_media_sharing.h | 132 -- third_party/opencl/include/CL/cl_egl.h | 136 -- third_party/opencl/include/CL/cl_ext.h | 391 ----- third_party/opencl/include/CL/cl_ext_qcom.h | 255 --- third_party/opencl/include/CL/cl_gl.h | 167 -- third_party/opencl/include/CL/cl_gl_ext.h | 74 - third_party/opencl/include/CL/cl_platform.h | 1333 --------------- third_party/opencl/include/CL/opencl.h | 59 - tools/cabana/SConscript | 2 - tools/install_ubuntu_dependencies.sh | 3 - tools/replay/SConscript | 5 - tools/webcam/README.md | 4 - 48 files changed, 309 insertions(+), 5237 deletions(-) delete mode 100644 common/clutil.cc delete mode 100644 common/clutil.h delete mode 100644 common/mat.h create mode 100755 selfdrive/modeld/compile_warp.py delete mode 100644 selfdrive/modeld/models/commonmodel.cc delete mode 100644 selfdrive/modeld/models/commonmodel.h delete mode 100644 selfdrive/modeld/models/commonmodel.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pyx delete mode 100644 selfdrive/modeld/runners/tinygrad_helpers.py delete mode 100644 selfdrive/modeld/transforms/loadyuv.cc delete mode 100644 selfdrive/modeld/transforms/loadyuv.cl delete mode 100644 selfdrive/modeld/transforms/loadyuv.h delete mode 100644 selfdrive/modeld/transforms/transform.cc delete mode 100644 selfdrive/modeld/transforms/transform.cl delete mode 100644 selfdrive/modeld/transforms/transform.h delete mode 100644 third_party/opencl/include/CL/cl.h delete mode 100644 third_party/opencl/include/CL/cl_d3d10.h delete mode 100644 third_party/opencl/include/CL/cl_d3d11.h delete mode 100644 third_party/opencl/include/CL/cl_dx9_media_sharing.h delete mode 100644 third_party/opencl/include/CL/cl_egl.h delete mode 100644 third_party/opencl/include/CL/cl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_ext_qcom.h delete mode 100644 third_party/opencl/include/CL/cl_gl.h delete mode 100644 third_party/opencl/include/CL/cl_gl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_platform.h delete mode 100644 third_party/opencl/include/CL/opencl.h diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 44d8d95e95d..8a604129938 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -18,43 +18,6 @@ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ cd /usr/lib/gcc/arm-none-eabi/* && \ rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp -# Add OpenCL -RUN apt-get update && apt-get install -y --no-install-recommends \ - apt-utils \ - alien \ - unzip \ - tar \ - curl \ - xz-utils \ - dbus \ - gcc-arm-none-eabi \ - tmux \ - vim \ - libx11-6 \ - wget \ - && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /tmp/opencl-driver-intel && \ - cd /tmp/opencl-driver-intel && \ - wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ - mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - mkdir -p /etc/OpenCL/vendors && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ - cd /opt/intel && \ - tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - mkdir -p /etc/ld.so.conf.d && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ - ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ - cd / && \ - rm -rf /tmp/opencl-driver-intel - ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute ENV QTWEBENGINE_DISABLE_SANDBOX=1 diff --git a/SConstruct b/SConstruct index ca5b7b6cb72..4f04be624cf 100644 --- a/SConstruct +++ b/SConstruct @@ -94,7 +94,6 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": - env.Append(CPPPATH=["#third_party/opencl/include"]) env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", diff --git a/common/SConscript b/common/SConscript index 1c68cf05c7a..15a0e5eff15 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,7 +5,6 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', - 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc deleted file mode 100644 index f8381a7e092..00000000000 --- a/common/clutil.cc +++ /dev/null @@ -1,98 +0,0 @@ -#include "common/clutil.h" - -#include -#include -#include - -#include "common/util.h" -#include "common/swaglog.h" - -namespace { // helper functions - -template -std::string get_info(Func get_info_func, Id id, Name param_name) { - size_t size = 0; - CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); - std::string info(size, '\0'); - CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); - return info; -} -inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } -inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } - -void cl_print_info(cl_platform_id platform, cl_device_id device) { - size_t work_group_size = 0; - cl_device_type device_type = 0; - clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); - clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); - const char *type_str = "Other..."; - switch (device_type) { - case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; - case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; - case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; - } - - LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); - LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); - LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); - LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); - LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); - LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); - LOGD("max work group size: %zu", work_group_size); - LOGD("type = %d, %s", (int)device_type, type_str); -} - -void cl_print_build_errors(cl_program program, cl_device_id device) { - cl_build_status status; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); - size_t log_size; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); - std::string log(log_size, '\0'); - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); - - LOGE("build failed; status=%d, log: %s", status, log.c_str()); -} - -} // namespace - -cl_device_id cl_get_device_id(cl_device_type device_type) { - cl_uint num_platforms = 0; - CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); - std::unique_ptr platform_ids = std::make_unique(num_platforms); - CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); - - for (size_t i = 0; i < num_platforms; ++i) { - LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); - - // Get first device - if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { - cl_print_info(platform_ids[i], device_id); - return device_id; - } - } - LOGE("No valid openCL platform found"); - assert(0); - return nullptr; -} - -cl_context cl_create_context(cl_device_id device_id) { - return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); -} - -void cl_release_context(cl_context context) { - clReleaseContext(context); -} - -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { - return cl_program_from_source(ctx, device_id, util::read_file(path), args); -} - -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { - const char *csrc = src.c_str(); - cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); - if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { - cl_print_build_errors(prg, device_id); - assert(0); - } - return prg; -} diff --git a/common/clutil.h b/common/clutil.h deleted file mode 100644 index b364e79d45b..00000000000 --- a/common/clutil.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include - -#define CL_CHECK(_expr) \ - do { \ - assert(CL_SUCCESS == (_expr)); \ - } while (0) - -#define CL_CHECK_ERR(_expr) \ - ({ \ - cl_int err = CL_INVALID_VALUE; \ - __typeof__(_expr) _ret = _expr; \ - assert(_ret&& err == CL_SUCCESS); \ - _ret; \ - }) - -cl_device_id cl_get_device_id(cl_device_type device_type); -cl_context cl_create_context(cl_device_id device_id); -void cl_release_context(cl_context context); -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/common/mat.h b/common/mat.h deleted file mode 100644 index 8e10d619717..00000000000 --- a/common/mat.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -typedef struct vec3 { - float v[3]; -} vec3; - -typedef struct vec4 { - float v[4]; -} vec4; - -typedef struct mat3 { - float v[3*3]; -} mat3; - -typedef struct mat4 { - float v[4*4]; -} mat4; - -static inline mat3 matmul3(const mat3 &a, const mat3 &b) { - mat3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - float v = 0.0; - for (int k=0; k<3; k++) { - v += a.v[r*3+k] * b.v[k*3+c]; - } - ret.v[r*3+c] = v; - } - } - return ret; -} - -static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { - vec3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - ret.v[r] += a.v[r*3+c] * b.v[c]; - } - } - return ret; -} - -static inline mat4 matmul(const mat4 &a, const mat4 &b) { - mat4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - float v = 0.0; - for (int k=0; k<4; k++) { - v += a.v[r*4+k] * b.v[k*4+c]; - } - ret.v[r*4+c] = v; - } - } - return ret; -} - -static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { - vec4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - ret.v[r] += a.v[r*4+c] * b.v[c]; - } - } - return ret; -} - -// scales the input and output space of a transformation matrix -// that assumes pixel-center origin. -static inline mat3 transform_scale_buffer(const mat3 &in, float s) { - // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s - - mat3 transform_out = {{ - 1.0f/s, 0.0f, 0.5f, - 0.0f, 1.0f/s, 0.5f, - 0.0f, 0.0f, 1.0f, - }}; - - mat3 transform_in = {{ - s, 0.0f, -0.5f*s, - 0.0f, s, -0.5f*s, - 0.0f, 0.0f, 1.0f, - }}; - - return matmul3(transform_in, matmul3(in, transform_out)); -} diff --git a/msgq_repo b/msgq_repo index 20f2493855e..f9ebdca885c 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 20f2493855ef32339b80f0ad76b3cb82210dc474 +Subproject commit f9ebdca885cfe25295d09de9fd57023a10758de5 diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 91f3597447b..84e94df4a5d 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,35 +1,12 @@ import os import glob -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') +Import('env', 'arch') lenv = env.Clone() -lenvCython = envCython.Clone() -libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] -frameworks = [] - -common_src = [ - "models/commonmodel.cc", - "transforms/loadyuv.cc", - "transforms/transform.cc", -] - -# OpenCL is a framework on Mac -if arch == "Darwin": - frameworks += ['OpenCL'] -else: - libs += ['OpenCL'] - -# Set path definitions -for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): - for xenv in (lenv, lenvCython): - xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') - -# Compile cython -cython_libs = envCython["LIBS"] + libs -commonmodel_lib = lenv.Library('commonmodel', common_src) -lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) -tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) +tinygrad_root = env.Dir("#").abspath +tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) + if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: @@ -38,22 +15,35 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) +# compile warp +tg_flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env +}.get(arch, 'DEV=CPU CPU_LLVM=1') +image_flag = { + 'larch64': 'IMAGE=2', +}.get(arch, 'IMAGE=0') +script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] +cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye +warp_targets = [] +for cam in [_ar_ox_fisheye, _os_fisheye]: + w, h = cam.width, cam.height + warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] +lenv.Command(warp_targets, tinygrad_files + script_files, cmd) + def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath return lenv.Command( fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' ) # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: - flags = { - 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env - }.get(arch, 'DEV=CPU CPU_LLVM=1') - tg_compile(flags, model_name) + tg_compile(tg_flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py new file mode 100755 index 00000000000..5adb60e6240 --- /dev/null +++ b/selfdrive/modeld/compile_warp.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3 +import time +import pickle +import numpy as np +from pathlib import Path +from tinygrad.tensor import Tensor +from tinygrad.helpers import Context +from tinygrad.device import Device +from tinygrad.engine.jit import TinyJit + +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye + +MODELS_DIR = Path(__file__).parent / 'models' + +CAMERA_CONFIGS = [ + (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 + (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 +] + +UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) +UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) + +IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) + + +def warp_pkl_path(w, h): + return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + + +def dm_warp_pkl_path(w, h): + return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' + + +def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, ratio): + w_dst, h_dst = dst_shape + h_src, w_src = src_shape + + x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst) + y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst) + ones = Tensor.ones_like(x) + dst_coords = x.reshape(1, -1).cat(y.reshape(1, -1)).cat(ones.reshape(1, -1)) + + src_coords = M_inv @ dst_coords + src_coords = src_coords / src_coords[2:3, :] + + x_nn_clipped = Tensor.round(src_coords[0]).clip(0, w_src - 1).cast('int') + y_nn_clipped = Tensor.round(src_coords[1]).clip(0, h_src - 1).cast('int') + idx = y_nn_clipped * w_src + (y_nn_clipped * ratio).cast('int') * stride_pad + x_nn_clipped + + sampled = src_flat[idx] + return sampled + + +def frames_to_tensor(frames, model_w, model_h): + H = (frames.shape[0] * 2) // 3 + W = frames.shape[1] + in_img1 = Tensor.cat(frames[0:H:2, 0::2], + frames[1:H:2, 0::2], + frames[0:H:2, 1::2], + frames[1:H:2, 1::2], + frames[H:H+H//4].reshape((H//2, W//2)), + frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) + return in_img1 + + +def make_frame_prepare(cam_w, cam_h, model_w, model_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + uv_offset = stride * y_height + stride_pad = stride - cam_w + + def frame_prepare_tinygrad(input_frame, M_inv): + tg_scale = Tensor(UV_SCALE_MATRIX) + M_inv_uv = tg_scale @ M_inv @ Tensor(UV_SCALE_MATRIX_INV) + with Context(SPLIT_REDUCEOP=0): + y = warp_perspective_tinygrad(input_frame[:cam_h*stride], + M_inv, (model_w, model_h), + (cam_h, cam_w), stride_pad, 1).realize() + u = warp_perspective_tinygrad(input_frame[uv_offset:uv_offset + (cam_h//4)*stride], + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), stride_pad, 0.5).realize() + v = warp_perspective_tinygrad(input_frame[uv_offset + (cam_h//4)*stride:uv_offset + (cam_h//2)*stride], + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), stride_pad, 0.5).realize() + yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) + tensor = frames_to_tensor(yuv, model_w, model_h) + return tensor + return frame_prepare_tinygrad + + +def make_update_img_input(frame_prepare, model_w, model_h): + def update_img_input_tinygrad(tensor, frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + new_img = frame_prepare(frame, M_inv) + full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() + return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) + return update_img_input_tinygrad + + +def make_update_both_imgs(frame_prepare, model_w, model_h): + update_img = make_update_img_input(frame_prepare, model_w, model_h) + + def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, + calib_big_img_buffer, new_big_img, M_inv_big): + calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) + calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) + return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair + return update_both_imgs_tinygrad + + +def make_warp_dm(cam_w, cam_h, dm_w, dm_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + stride_pad = stride - cam_w + + def warp_dm(input_frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + with Context(SPLIT_REDUCEOP=0): + result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad, 1).reshape(-1, dm_h * dm_w) + return result + return warp_dm + + +def compile_modeld_warp(cam_w, cam_h): + model_w, model_h = MEDMODEL_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling modeld warp for {cam_w}x{cam_h}...") + + frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) + update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) + update_img_jit = TinyJit(update_both_imgs, prune=True) + + full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + + for i in range(10): + new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + img_inputs = [full_buffer, + Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + big_img_inputs = [big_full_buffer, + Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + inputs = img_inputs + big_img_inputs + Device.default.synchronize() + + inputs_np = [x.numpy() for x in inputs] + inputs_np[0] = full_buffer_np + inputs_np[3] = big_full_buffer_np + + st = time.perf_counter() + out = update_img_jit(*inputs) + full_buffer = out[0].contiguous().realize().clone() + big_full_buffer = out[2].contiguous().realize().clone() + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(update_img_jit, f) + print(f" Saved to {pkl_path}") + + jit = pickle.load(open(pkl_path, "rb")) + jit(*inputs) + + +def compile_dm_warp(cam_w, cam_h): + dm_w, dm_h = DM_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling DM warp for {cam_w}x{cam_h}...") + + warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) + warp_dm_jit = TinyJit(warp_dm, prune=True) + + for i in range(10): + inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + Device.default.synchronize() + st = time.perf_counter() + warp_dm_jit(*inputs) + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = dm_warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(warp_dm_jit, f) + print(f" Saved to {pkl_path}") + + +def run_and_save_pickle(): + for cam_w, cam_h in CAMERA_CONFIGS: + compile_modeld_warp(cam_w, cam_h) + compile_dm_warp(cam_w, cam_h) + + +if __name__ == "__main__": + run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index fca762c69bf..7d4df713c7f 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,7 +3,6 @@ from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -16,32 +15,34 @@ from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' - +MODELS_DIR = Path(__file__).parent / 'models' class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self, cl_ctx): + def __init__(self): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] - self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } + self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} + self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} + self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} + self.image_warp = None with open(MODEL_PKL_PATH, "rb") as f: self.model_run = pickle.load(f) @@ -50,14 +51,15 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple t1 = time.perf_counter() - input_img_cl = self.frame.prepare(buf, transform.flatten()) - if TICI: - # The imgs tensors are backed by opencl memory, only need init once - if 'input_img' not in self.tensor_inputs: - self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) - else: - self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() + if self.image_warp is None: + self.frame_buf_params = get_nv12_info(buf.width, buf.height) + warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.image_warp = pickle.load(f) + self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() + self.warp_inputs_np['transform'][:] = transform[:] + self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy() @@ -107,12 +109,11 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - cl_context = CLContext() - model = ModelState(cl_context) + model = ModelState() cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 1f347dc32a0..7fae3651095 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,7 +7,6 @@ os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -22,14 +21,13 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.selfdrive.modeld.constants import ModelConstants, Plan -from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.modeld" @@ -39,11 +37,15 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' +MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 +IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) +assert IMG_QUEUE_SHAPE[0] == 30 + def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -136,12 +138,11 @@ def get(self, *names) -> dict[str, np.ndarray]: return out class ModelState: - frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self, context: CLContext): + def __init__(self): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -155,7 +156,6 @@ def __init__(self, context: CLContext): self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] - self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -165,12 +165,17 @@ def __init__(self, context: CLContext): self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - # img buffers are managed in openCL transform code - self.vision_inputs: dict[str, Tensor] = {} + self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), + 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} + self.full_frames : dict[str, Tensor] = {} + self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} + self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() + self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} + self.update_imgs = None with open(VISION_PKL_PATH, "rb") as f: self.vision_run = pickle.load(f) @@ -188,23 +193,28 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] + if self.update_imgs is None: + for key in bufs.keys(): + w, h = bufs[key].width, bufs[key].height + self.frame_buf_params[key] = get_nv12_info(w, h) + warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.update_imgs = pickle.load(f) - imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} - if TICI and not USBGPU: - # The imgs tensors are backed by opencl memory, only need init once - for key in imgs_cl: - if key not in self.vision_inputs: - self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) - else: - for key in imgs_cl: - frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) - self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() + for key in bufs.keys(): + self.full_frames[key] = Tensor.from_blob(bufs[key].data.ctypes.data, (self.frame_buf_params[key][3],), dtype='uint8').realize() + self.transforms_np[key][:,:] = transforms[key][:,:] + + out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], + self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) + self.img_queues['img'], self.img_queues['big_img'], = out[0].realize(), out[2].realize() + vision_inputs = {'img': out[1], 'big_img': out[3]} if prepare_only: return None - self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -214,7 +224,6 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) - combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) @@ -231,10 +240,8 @@ def main(demo=False): config_realtime_process(7, 54) st = time.monotonic() - cloudlog.warning("setting up CL context") - cl_context = CLContext() - cloudlog.warning("CL context ready; loading model") - model = ModelState(cl_context) + cloudlog.warning("loading model") + model = ModelState() cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -247,8 +254,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc deleted file mode 100644 index d3341e76ec3..00000000000 --- a/selfdrive/modeld/models/commonmodel.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "selfdrive/modeld/models/commonmodel.h" - -#include -#include - -#include "common/clutil.h" - -DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - temporal_skip = _temporal_skip; - input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); - region.origin = temporal_skip * frame_size_bytes; - region.size = frame_size_bytes; - last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); - - loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - - for (int i = 0; i < temporal_skip; i++) { - CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); - } - loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); - - copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); - copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); - - // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. - clFinish(q); - return &input_frames_cl; -} - -DrivingModelFrame::~DrivingModelFrame() { - deinit_transform(); - loadyuv_destroy(&loadyuv); - CL_CHECK(clReleaseMemObject(input_frames_cl)); - CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); - CL_CHECK(clReleaseMemObject(last_img_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} - - -MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - clFinish(q); - return &y_cl; -} - -MonitoringModelFrame::~MonitoringModelFrame() { - deinit_transform(); - CL_CHECK(clReleaseMemObject(input_frame_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h deleted file mode 100644 index 176d7eb6dcf..00000000000 --- a/selfdrive/modeld/models/commonmodel.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" -#include "selfdrive/modeld/transforms/loadyuv.h" -#include "selfdrive/modeld/transforms/transform.h" - -class ModelFrame { -public: - ModelFrame(cl_device_id device_id, cl_context context) { - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); - } - virtual ~ModelFrame() {} - virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } - uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { - CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); - clFinish(q); - return &input_frames[0]; - } - - int MODEL_WIDTH; - int MODEL_HEIGHT; - int MODEL_FRAME_SIZE; - int buf_size; - -protected: - cl_mem y_cl, u_cl, v_cl; - Transform transform; - cl_command_queue q; - std::unique_ptr input_frames; - - void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { - y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); - u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - transform_init(&transform, context, device_id); - } - - void deinit_transform() { - transform_destroy(&transform); - CL_CHECK(clReleaseMemObject(v_cl)); - CL_CHECK(clReleaseMemObject(u_cl)); - CL_CHECK(clReleaseMemObject(y_cl)); - } - - void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - transform_queue(&transform, q, - yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, - y_cl, u_cl, v_cl, model_width, model_height, projection); - } -}; - -class DrivingModelFrame : public ModelFrame { -public: - DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); - ~DrivingModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 512; - const int MODEL_HEIGHT = 256; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart - const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); - -private: - LoadYUVState loadyuv; - cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; - cl_buffer_region region; - int temporal_skip; -}; - -class MonitoringModelFrame : public ModelFrame { -public: - MonitoringModelFrame(cl_device_id device_id, cl_context context); - ~MonitoringModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 1440; - const int MODEL_HEIGHT = 960; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; - const int buf_size = MODEL_FRAME_SIZE; - -private: - cl_mem input_frame_cl; -}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd deleted file mode 100644 index 4ac64d91720..00000000000 --- a/selfdrive/modeld/models/commonmodel.pxd +++ /dev/null @@ -1,27 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem - -cdef extern from "common/mat.h": - cdef struct mat3: - float v[9] - -cdef extern from "common/clutil.h": - cdef unsigned long CL_DEVICE_TYPE_DEFAULT - cl_device_id cl_get_device_id(unsigned long) - cl_context cl_create_context(cl_device_id) - void cl_release_context(cl_context) - -cdef extern from "selfdrive/modeld/models/commonmodel.h": - cppclass ModelFrame: - int buf_size - unsigned char * buffer_from_cl(cl_mem*, int); - cl_mem * prepare(cl_mem, int, int, int, int, mat3) - - cppclass DrivingModelFrame: - int buf_size - DrivingModelFrame(cl_device_id, cl_context, int) - - cppclass MonitoringModelFrame: - int buf_size - MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd deleted file mode 100644 index 0bb798625be..00000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pxd +++ /dev/null @@ -1,13 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext - -cdef class CLContext(BaseCLContext): - pass - -cdef class CLMem: - cdef cl_mem * mem - - @staticmethod - cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx deleted file mode 100644 index 5b7d11bc71a..00000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pyx +++ /dev/null @@ -1,74 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -import numpy as np -cimport numpy as cnp -from libc.string cimport memcpy -from libc.stdint cimport uintptr_t - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext -from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context -from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame - - -cdef class CLContext(BaseCLContext): - def __cinit__(self): - self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) - self.context = cl_create_context(self.device_id) - - def __dealloc__(self): - if self.context: - cl_release_context(self.context) - -cdef class CLMem: - @staticmethod - cdef create(void * cmem): - mem = CLMem() - mem.mem = cmem - return mem - - @property - def mem_address(self): - return (self.mem) - -def cl_from_visionbuf(VisionBuf buf): - return CLMem.create(&buf.buf.buf_cl) - - -cdef class ModelFrame: - cdef cppModelFrame * frame - cdef int buf_size - - def __dealloc__(self): - del self.frame - - def prepare(self, VisionBuf buf, float[:] projection): - cdef mat3 cprojection - memcpy(cprojection.v, &projection[0], 9*sizeof(float)) - cdef cl_mem * data - data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) - return CLMem.create(data) - - def buffer_from_cl(self, CLMem in_frames): - cdef unsigned char * data2 - data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) - return np.asarray( data2) - - -cdef class DrivingModelFrame(ModelFrame): - cdef cppDrivingModelFrame * _frame - - def __cinit__(self, CLContext context, int temporal_skip): - self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - -cdef class MonitoringModelFrame(ModelFrame): - cdef cppMonitoringModelFrame * _frame - - def __cinit__(self, CLContext context): - self._frame = new cppMonitoringModelFrame(context.device_id, context.context) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py deleted file mode 100644 index 776381341cf..00000000000 --- a/selfdrive/modeld/runners/tinygrad_helpers.py +++ /dev/null @@ -1,8 +0,0 @@ - -from tinygrad.tensor import Tensor -from tinygrad.helpers import to_mv - -def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): - cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] - rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. - return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc deleted file mode 100644 index c93f5cd0381..00000000000 --- a/selfdrive/modeld/transforms/loadyuv.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "selfdrive/modeld/transforms/loadyuv.h" - -#include -#include -#include - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { - memset(s, 0, sizeof(*s)); - - s->width = width; - s->height = height; - - char args[1024]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", - width, height); - cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); - - s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); - s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); - s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); - - // done with this - CL_CHECK(clReleaseProgram(prg)); -} - -void loadyuv_destroy(LoadYUVState* s) { - CL_CHECK(clReleaseKernel(s->loadys_krnl)); - CL_CHECK(clReleaseKernel(s->loaduv_krnl)); - CL_CHECK(clReleaseKernel(s->copy_krnl)); -} - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl) { - cl_int global_out_off = 0; - - CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); - - const size_t loadys_work_size = (s->width*s->height)/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, - &loadys_work_size, NULL, 0, 0, NULL)); - - const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; - global_out_off += (s->width*s->height); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); - - global_out_off += (s->width/2)*(s->height/2); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); -} - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size) { - CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); - const size_t copy_work_size = size/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, - ©_work_size, NULL, 0, 0, NULL)); -} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl deleted file mode 100644 index 970187a6d70..00000000000 --- a/selfdrive/modeld/transforms/loadyuv.cl +++ /dev/null @@ -1,47 +0,0 @@ -#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) - -__kernel void loadys(__global uchar8 const * const Y, - __global uchar * out, - int out_offset) -{ - const int gid = get_global_id(0); - const int ois = gid * 8; - const int oy = ois / TRANSFORMED_WIDTH; - const int ox = ois % TRANSFORMED_WIDTH; - - const uchar8 ys = Y[gid]; - - // 02 - // 13 - - __global uchar* outy0; - __global uchar* outy1; - if ((oy & 1) == 0) { - outy0 = out + out_offset; //y0 - outy1 = out + out_offset + UV_SIZE*2; //y2 - } else { - outy0 = out + out_offset + UV_SIZE; //y1 - outy1 = out + out_offset + UV_SIZE*3; //y3 - } - - vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); - vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); -} - -__kernel void loaduv(__global uchar8 const * const in, - __global uchar8 * out, - int out_offset) -{ - const int gid = get_global_id(0); - const uchar8 inv = in[gid]; - out[gid + out_offset / 8] = inv; -} - -__kernel void copy(__global uchar8 * in, - __global uchar8 * out, - int in_offset, - int out_offset) -{ - const int gid = get_global_id(0); - out[gid + out_offset / 8] = in[gid + in_offset / 8]; -} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h deleted file mode 100644 index 659059cd25e..00000000000 --- a/selfdrive/modeld/transforms/loadyuv.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "common/clutil.h" - -typedef struct { - int width, height; - cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; -} LoadYUVState; - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); - -void loadyuv_destroy(LoadYUVState* s); - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl); - - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc deleted file mode 100644 index 305643cf42e..00000000000 --- a/selfdrive/modeld/transforms/transform.cc +++ /dev/null @@ -1,97 +0,0 @@ -#include "selfdrive/modeld/transforms/transform.h" - -#include -#include - -#include "common/clutil.h" - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { - memset(s, 0, sizeof(*s)); - - cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); - s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); - // done with this - CL_CHECK(clReleaseProgram(prg)); - - s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); - s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); -} - -void transform_destroy(Transform* s) { - CL_CHECK(clReleaseMemObject(s->m_y_cl)); - CL_CHECK(clReleaseMemObject(s->m_uv_cl)); - CL_CHECK(clReleaseKernel(s->krnl)); -} - -void transform_queue(Transform* s, - cl_command_queue q, - cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection) { - const int zero = 0; - - // sampled using pixel center origin - // (because that's how fastcv and opencv does it) - - mat3 projection_y = projection; - - // in and out uv is half the size of y. - mat3 projection_uv = transform_scale_buffer(projection, 0.5); - - CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); - CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); - - const int in_y_width = in_width; - const int in_y_height = in_height; - const int in_y_px_stride = 1; - const int in_uv_width = in_width/2; - const int in_uv_height = in_height/2; - const int in_uv_px_stride = 2; - const int in_u_offset = in_uv_offset; - const int in_v_offset = in_uv_offset + 1; - - const int out_y_width = out_width; - const int out_y_height = out_height; - const int out_uv_width = out_width/2; - const int out_uv_height = out_height/2; - - CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src - CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M - - const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_y, NULL, 0, 0, NULL)); - - const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; - - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); -} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl deleted file mode 100644 index 2ca25920cd1..00000000000 --- a/selfdrive/modeld/transforms/transform.cl +++ /dev/null @@ -1,54 +0,0 @@ -#define INTER_BITS 5 -#define INTER_TAB_SIZE (1 << INTER_BITS) -#define INTER_SCALE 1.f / INTER_TAB_SIZE - -#define INTER_REMAP_COEF_BITS 15 -#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) - -__kernel void warpPerspective(__global const uchar * src, - int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, - __global uchar * dst, - int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, - __constant float * M) -{ - int dx = get_global_id(0); - int dy = get_global_id(1); - - if (dx < dst_cols && dy < dst_rows) - { - float X0 = M[0] * dx + M[1] * dy + M[2]; - float Y0 = M[3] * dx + M[4] * dy + M[5]; - float W = M[6] * dx + M[7] * dy + M[8]; - W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; - int X = rint(X0 * W), Y = rint(Y0 * W); - - int sx = convert_short_sat(X >> INTER_BITS); - int sy = convert_short_sat(Y >> INTER_BITS); - - short sx_clamp = clamp(sx, 0, src_cols - 1); - short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); - short sy_clamp = clamp(sy, 0, src_rows - 1); - short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); - int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - - short ay = (short)(Y & (INTER_TAB_SIZE - 1)); - short ax = (short)(X & (INTER_TAB_SIZE - 1)); - float taby = 1.f/INTER_TAB_SIZE*ay; - float tabx = 1.f/INTER_TAB_SIZE*ax; - - int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); - - int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); - int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); - - int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; - - uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); - dst[dst_index] = pix; - } -} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h deleted file mode 100644 index 771a7054b35..00000000000 --- a/selfdrive/modeld/transforms/transform.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" - -typedef struct { - cl_kernel krnl; - cl_mem m_y_cl, m_uv_cl; -} Transform; - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); - -void transform_destroy(Transform* transform); - -void transform_queue(Transform* s, cl_command_queue q, - cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection); diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index d35474f3732..3f671f610d2 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,8 +34,8 @@ EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.035, 0.025), - ("driverStateV2", 0.02, 0.015), + ("modelV2", 0.05, 0.028), + ("driverStateV2", 0.05, 0.015), ] def get_log_fn(test_route, ref="master"): diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 8af72e5f4e7..5143334bc61 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,6 +4,7 @@ import copy import heapq import signal +import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -23,6 +24,7 @@ from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -203,7 +205,8 @@ def _setup_vision_ipc(self, all_msgs: LogIterable, frs: dict[str, Any]): if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - vipc_server.create_buffers(meta.stream, 2, *frame_size) + stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) + vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) vipc_server.start_listener() self.vipc_server = vipc_server @@ -300,7 +303,15 @@ def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), + + h, w = frs[m.which()].h, frs[m.which()].w + stride, y_height, _, yuv_size = get_nv12_info(w, h) + uv_offset = stride * y_height + padded_img = np.zeros((yuv_size), dtype=np.uint8).reshape((-1, stride)) + padded_img[:h, :w] = img[:h * w].reshape((-1, w)) + padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) + + self.vipc_server.send(camera_meta.stream, padded_img.flatten().tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] diff --git a/system/camerad/SConscript b/system/camerad/SConscript index e288c6d8b02..c28330b32c4 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, 'OpenCL', messaging, visionipc] +libs = [common, messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 88bca7f775b..329192b63ae 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,9 +21,8 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera * const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); - camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d CL buffers", frame_buf_count); + LOGD("allocated %d buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index c26859cbc40..7f35e06a835 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ class CameraBuf { CameraBuf() = default; ~CameraBuf(); - void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index d741e13cf3b..6a7f599ab66 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,16 +12,8 @@ #include #include -#ifdef __TICI__ -#include "CL/cl_ext_qcom.h" -#else -#define CL_PRIORITY_HINT_HIGH_QCOM NULL -#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL -#endif - #include "media/cam_sensor_cmn_header.h" -#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -57,7 +49,7 @@ class CameraState { CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void init(VisionIpcServer *v); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -68,8 +60,8 @@ class CameraState { } }; -void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { - camera.camera_open(v, device_id, ctx); +void CameraState::init(VisionIpcServer *v) { + camera.camera_open(v); if (!camera.enabled) return; @@ -257,11 +249,7 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); - const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; - cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); - - VisionIpcServer v("camerad", device_id, ctx); + VisionIpcServer v("camerad"); // *** initial ISP init *** SpectraMaster m; @@ -271,7 +259,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v, device_id, ctx); + cam->init(&v); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 5c3e7a9d233..73e0a78da30 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { +void SpectraCamera::camera_open(VisionIpcServer *v) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); + buf.init(this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 13cb13f98f6..a02b8a6cac7 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ class SpectraCamera { SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void camera_open(VisionIpcServer *v); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index a92e4c8de8a..c4401c9583c 100644 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -32,7 +32,7 @@ def name(self): PROCS = [ Proc(['camerad'], 1.65, atol=0.4, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), - Proc(['modeld'], 1.24, atol=0.2, msgs=['modelV2']), + Proc(['modeld'], 1.5, atol=0.2, msgs=['modelV2']), Proc(['dmonitoringmodeld'], 0.65, atol=0.35, msgs=['driverStateV2']), Proc(['encoderd'], 0.23, msgs=[]), ] diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cf169f4dc61..cc8ef7c88f6 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,16 +2,13 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'avutil', - 'yuv', 'OpenCL', 'pthread', 'zstd'] + 'yuv', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] if arch == "Darwin": - # fix OpenCL - del libs[libs.index('OpenCL')] - env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h deleted file mode 100644 index 0086319f5fa..00000000000 --- a/third_party/opencl/include/CL/cl.h +++ /dev/null @@ -1,1452 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_H -#define __OPENCL_CL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ - -typedef struct _cl_platform_id * cl_platform_id; -typedef struct _cl_device_id * cl_device_id; -typedef struct _cl_context * cl_context; -typedef struct _cl_command_queue * cl_command_queue; -typedef struct _cl_mem * cl_mem; -typedef struct _cl_program * cl_program; -typedef struct _cl_kernel * cl_kernel; -typedef struct _cl_event * cl_event; -typedef struct _cl_sampler * cl_sampler; - -typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ -typedef cl_ulong cl_bitfield; -typedef cl_bitfield cl_device_type; -typedef cl_uint cl_platform_info; -typedef cl_uint cl_device_info; -typedef cl_bitfield cl_device_fp_config; -typedef cl_uint cl_device_mem_cache_type; -typedef cl_uint cl_device_local_mem_type; -typedef cl_bitfield cl_device_exec_capabilities; -typedef cl_bitfield cl_device_svm_capabilities; -typedef cl_bitfield cl_command_queue_properties; -typedef intptr_t cl_device_partition_property; -typedef cl_bitfield cl_device_affinity_domain; - -typedef intptr_t cl_context_properties; -typedef cl_uint cl_context_info; -typedef cl_bitfield cl_queue_properties; -typedef cl_uint cl_command_queue_info; -typedef cl_uint cl_channel_order; -typedef cl_uint cl_channel_type; -typedef cl_bitfield cl_mem_flags; -typedef cl_bitfield cl_svm_mem_flags; -typedef cl_uint cl_mem_object_type; -typedef cl_uint cl_mem_info; -typedef cl_bitfield cl_mem_migration_flags; -typedef cl_uint cl_image_info; -typedef cl_uint cl_buffer_create_type; -typedef cl_uint cl_addressing_mode; -typedef cl_uint cl_filter_mode; -typedef cl_uint cl_sampler_info; -typedef cl_bitfield cl_map_flags; -typedef intptr_t cl_pipe_properties; -typedef cl_uint cl_pipe_info; -typedef cl_uint cl_program_info; -typedef cl_uint cl_program_build_info; -typedef cl_uint cl_program_binary_type; -typedef cl_int cl_build_status; -typedef cl_uint cl_kernel_info; -typedef cl_uint cl_kernel_arg_info; -typedef cl_uint cl_kernel_arg_address_qualifier; -typedef cl_uint cl_kernel_arg_access_qualifier; -typedef cl_bitfield cl_kernel_arg_type_qualifier; -typedef cl_uint cl_kernel_work_group_info; -typedef cl_uint cl_kernel_sub_group_info; -typedef cl_uint cl_event_info; -typedef cl_uint cl_command_type; -typedef cl_uint cl_profiling_info; -typedef cl_bitfield cl_sampler_properties; -typedef cl_uint cl_kernel_exec_info; - -typedef struct _cl_image_format { - cl_channel_order image_channel_order; - cl_channel_type image_channel_data_type; -} cl_image_format; - -typedef struct _cl_image_desc { - cl_mem_object_type image_type; - size_t image_width; - size_t image_height; - size_t image_depth; - size_t image_array_size; - size_t image_row_pitch; - size_t image_slice_pitch; - cl_uint num_mip_levels; - cl_uint num_samples; -#ifdef __GNUC__ - __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ -#endif - union { - cl_mem buffer; - cl_mem mem_object; - }; -} cl_image_desc; - -typedef struct _cl_buffer_region { - size_t origin; - size_t size; -} cl_buffer_region; - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_SUCCESS 0 -#define CL_DEVICE_NOT_FOUND -1 -#define CL_DEVICE_NOT_AVAILABLE -2 -#define CL_COMPILER_NOT_AVAILABLE -3 -#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 -#define CL_OUT_OF_RESOURCES -5 -#define CL_OUT_OF_HOST_MEMORY -6 -#define CL_PROFILING_INFO_NOT_AVAILABLE -7 -#define CL_MEM_COPY_OVERLAP -8 -#define CL_IMAGE_FORMAT_MISMATCH -9 -#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 -#define CL_BUILD_PROGRAM_FAILURE -11 -#define CL_MAP_FAILURE -12 -#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 -#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 -#define CL_COMPILE_PROGRAM_FAILURE -15 -#define CL_LINKER_NOT_AVAILABLE -16 -#define CL_LINK_PROGRAM_FAILURE -17 -#define CL_DEVICE_PARTITION_FAILED -18 -#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 - -#define CL_INVALID_VALUE -30 -#define CL_INVALID_DEVICE_TYPE -31 -#define CL_INVALID_PLATFORM -32 -#define CL_INVALID_DEVICE -33 -#define CL_INVALID_CONTEXT -34 -#define CL_INVALID_QUEUE_PROPERTIES -35 -#define CL_INVALID_COMMAND_QUEUE -36 -#define CL_INVALID_HOST_PTR -37 -#define CL_INVALID_MEM_OBJECT -38 -#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 -#define CL_INVALID_IMAGE_SIZE -40 -#define CL_INVALID_SAMPLER -41 -#define CL_INVALID_BINARY -42 -#define CL_INVALID_BUILD_OPTIONS -43 -#define CL_INVALID_PROGRAM -44 -#define CL_INVALID_PROGRAM_EXECUTABLE -45 -#define CL_INVALID_KERNEL_NAME -46 -#define CL_INVALID_KERNEL_DEFINITION -47 -#define CL_INVALID_KERNEL -48 -#define CL_INVALID_ARG_INDEX -49 -#define CL_INVALID_ARG_VALUE -50 -#define CL_INVALID_ARG_SIZE -51 -#define CL_INVALID_KERNEL_ARGS -52 -#define CL_INVALID_WORK_DIMENSION -53 -#define CL_INVALID_WORK_GROUP_SIZE -54 -#define CL_INVALID_WORK_ITEM_SIZE -55 -#define CL_INVALID_GLOBAL_OFFSET -56 -#define CL_INVALID_EVENT_WAIT_LIST -57 -#define CL_INVALID_EVENT -58 -#define CL_INVALID_OPERATION -59 -#define CL_INVALID_GL_OBJECT -60 -#define CL_INVALID_BUFFER_SIZE -61 -#define CL_INVALID_MIP_LEVEL -62 -#define CL_INVALID_GLOBAL_WORK_SIZE -63 -#define CL_INVALID_PROPERTY -64 -#define CL_INVALID_IMAGE_DESCRIPTOR -65 -#define CL_INVALID_COMPILER_OPTIONS -66 -#define CL_INVALID_LINKER_OPTIONS -67 -#define CL_INVALID_DEVICE_PARTITION_COUNT -68 -#define CL_INVALID_PIPE_SIZE -69 -#define CL_INVALID_DEVICE_QUEUE -70 - -/* OpenCL Version */ -#define CL_VERSION_1_0 1 -#define CL_VERSION_1_1 1 -#define CL_VERSION_1_2 1 -#define CL_VERSION_2_0 1 -#define CL_VERSION_2_1 1 - -/* cl_bool */ -#define CL_FALSE 0 -#define CL_TRUE 1 -#define CL_BLOCKING CL_TRUE -#define CL_NON_BLOCKING CL_FALSE - -/* cl_platform_info */ -#define CL_PLATFORM_PROFILE 0x0900 -#define CL_PLATFORM_VERSION 0x0901 -#define CL_PLATFORM_NAME 0x0902 -#define CL_PLATFORM_VENDOR 0x0903 -#define CL_PLATFORM_EXTENSIONS 0x0904 -#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 - -/* cl_device_type - bitfield */ -#define CL_DEVICE_TYPE_DEFAULT (1 << 0) -#define CL_DEVICE_TYPE_CPU (1 << 1) -#define CL_DEVICE_TYPE_GPU (1 << 2) -#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) -#define CL_DEVICE_TYPE_CUSTOM (1 << 4) -#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF - -/* cl_device_info */ -#define CL_DEVICE_TYPE 0x1000 -#define CL_DEVICE_VENDOR_ID 0x1001 -#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 -#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 -#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 -#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B -#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C -#define CL_DEVICE_ADDRESS_BITS 0x100D -#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E -#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F -#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 -#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 -#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 -#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 -#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 -#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 -#define CL_DEVICE_IMAGE_SUPPORT 0x1016 -#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 -#define CL_DEVICE_MAX_SAMPLERS 0x1018 -#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 -#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A -#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B -#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C -#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D -#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E -#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F -#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 -#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 -#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 -#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 -#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 -#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 -#define CL_DEVICE_ENDIAN_LITTLE 0x1026 -#define CL_DEVICE_AVAILABLE 0x1027 -#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 -#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 -#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ -#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A -#define CL_DEVICE_NAME 0x102B -#define CL_DEVICE_VENDOR 0x102C -#define CL_DRIVER_VERSION 0x102D -#define CL_DEVICE_PROFILE 0x102E -#define CL_DEVICE_VERSION 0x102F -#define CL_DEVICE_EXTENSIONS 0x1030 -#define CL_DEVICE_PLATFORM 0x1031 -#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 -/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 -#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C -#define CL_DEVICE_OPENCL_C_VERSION 0x103D -#define CL_DEVICE_LINKER_AVAILABLE 0x103E -#define CL_DEVICE_BUILT_IN_KERNELS 0x103F -#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 -#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 -#define CL_DEVICE_PARENT_DEVICE 0x1042 -#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 -#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 -#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 -#define CL_DEVICE_PARTITION_TYPE 0x1046 -#define CL_DEVICE_REFERENCE_COUNT 0x1047 -#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 -#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 -#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A -#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B -#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C -#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D -#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E -#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F -#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 -#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 -#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 -#define CL_DEVICE_SVM_CAPABILITIES 0x1053 -#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 -#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 -#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 -#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 -#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 -#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 -#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A -#define CL_DEVICE_IL_VERSION 0x105B -#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C -#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D - -/* cl_device_fp_config - bitfield */ -#define CL_FP_DENORM (1 << 0) -#define CL_FP_INF_NAN (1 << 1) -#define CL_FP_ROUND_TO_NEAREST (1 << 2) -#define CL_FP_ROUND_TO_ZERO (1 << 3) -#define CL_FP_ROUND_TO_INF (1 << 4) -#define CL_FP_FMA (1 << 5) -#define CL_FP_SOFT_FLOAT (1 << 6) -#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) - -/* cl_device_mem_cache_type */ -#define CL_NONE 0x0 -#define CL_READ_ONLY_CACHE 0x1 -#define CL_READ_WRITE_CACHE 0x2 - -/* cl_device_local_mem_type */ -#define CL_LOCAL 0x1 -#define CL_GLOBAL 0x2 - -/* cl_device_exec_capabilities - bitfield */ -#define CL_EXEC_KERNEL (1 << 0) -#define CL_EXEC_NATIVE_KERNEL (1 << 1) - -/* cl_command_queue_properties - bitfield */ -#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) -#define CL_QUEUE_PROFILING_ENABLE (1 << 1) -#define CL_QUEUE_ON_DEVICE (1 << 2) -#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) - -/* cl_context_info */ -#define CL_CONTEXT_REFERENCE_COUNT 0x1080 -#define CL_CONTEXT_DEVICES 0x1081 -#define CL_CONTEXT_PROPERTIES 0x1082 -#define CL_CONTEXT_NUM_DEVICES 0x1083 - -/* cl_context_properties */ -#define CL_CONTEXT_PLATFORM 0x1084 -#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 - -/* cl_device_partition_property */ -#define CL_DEVICE_PARTITION_EQUALLY 0x1086 -#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 -#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 -#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 - -/* cl_device_affinity_domain */ -#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) -#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) -#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) -#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) -#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) -#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) - -/* cl_device_svm_capabilities */ -#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) -#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) -#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) -#define CL_DEVICE_SVM_ATOMICS (1 << 3) - -/* cl_command_queue_info */ -#define CL_QUEUE_CONTEXT 0x1090 -#define CL_QUEUE_DEVICE 0x1091 -#define CL_QUEUE_REFERENCE_COUNT 0x1092 -#define CL_QUEUE_PROPERTIES 0x1093 -#define CL_QUEUE_SIZE 0x1094 -#define CL_QUEUE_DEVICE_DEFAULT 0x1095 - -/* cl_mem_flags and cl_svm_mem_flags - bitfield */ -#define CL_MEM_READ_WRITE (1 << 0) -#define CL_MEM_WRITE_ONLY (1 << 1) -#define CL_MEM_READ_ONLY (1 << 2) -#define CL_MEM_USE_HOST_PTR (1 << 3) -#define CL_MEM_ALLOC_HOST_PTR (1 << 4) -#define CL_MEM_COPY_HOST_PTR (1 << 5) -/* reserved (1 << 6) */ -#define CL_MEM_HOST_WRITE_ONLY (1 << 7) -#define CL_MEM_HOST_READ_ONLY (1 << 8) -#define CL_MEM_HOST_NO_ACCESS (1 << 9) -#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ -#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ -#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) - -/* cl_mem_migration_flags - bitfield */ -#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) -#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) - -/* cl_channel_order */ -#define CL_R 0x10B0 -#define CL_A 0x10B1 -#define CL_RG 0x10B2 -#define CL_RA 0x10B3 -#define CL_RGB 0x10B4 -#define CL_RGBA 0x10B5 -#define CL_BGRA 0x10B6 -#define CL_ARGB 0x10B7 -#define CL_INTENSITY 0x10B8 -#define CL_LUMINANCE 0x10B9 -#define CL_Rx 0x10BA -#define CL_RGx 0x10BB -#define CL_RGBx 0x10BC -#define CL_DEPTH 0x10BD -#define CL_DEPTH_STENCIL 0x10BE -#define CL_sRGB 0x10BF -#define CL_sRGBx 0x10C0 -#define CL_sRGBA 0x10C1 -#define CL_sBGRA 0x10C2 -#define CL_ABGR 0x10C3 - -/* cl_channel_type */ -#define CL_SNORM_INT8 0x10D0 -#define CL_SNORM_INT16 0x10D1 -#define CL_UNORM_INT8 0x10D2 -#define CL_UNORM_INT16 0x10D3 -#define CL_UNORM_SHORT_565 0x10D4 -#define CL_UNORM_SHORT_555 0x10D5 -#define CL_UNORM_INT_101010 0x10D6 -#define CL_SIGNED_INT8 0x10D7 -#define CL_SIGNED_INT16 0x10D8 -#define CL_SIGNED_INT32 0x10D9 -#define CL_UNSIGNED_INT8 0x10DA -#define CL_UNSIGNED_INT16 0x10DB -#define CL_UNSIGNED_INT32 0x10DC -#define CL_HALF_FLOAT 0x10DD -#define CL_FLOAT 0x10DE -#define CL_UNORM_INT24 0x10DF -#define CL_UNORM_INT_101010_2 0x10E0 - -/* cl_mem_object_type */ -#define CL_MEM_OBJECT_BUFFER 0x10F0 -#define CL_MEM_OBJECT_IMAGE2D 0x10F1 -#define CL_MEM_OBJECT_IMAGE3D 0x10F2 -#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 -#define CL_MEM_OBJECT_IMAGE1D 0x10F4 -#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 -#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 -#define CL_MEM_OBJECT_PIPE 0x10F7 - -/* cl_mem_info */ -#define CL_MEM_TYPE 0x1100 -#define CL_MEM_FLAGS 0x1101 -#define CL_MEM_SIZE 0x1102 -#define CL_MEM_HOST_PTR 0x1103 -#define CL_MEM_MAP_COUNT 0x1104 -#define CL_MEM_REFERENCE_COUNT 0x1105 -#define CL_MEM_CONTEXT 0x1106 -#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 -#define CL_MEM_OFFSET 0x1108 -#define CL_MEM_USES_SVM_POINTER 0x1109 - -/* cl_image_info */ -#define CL_IMAGE_FORMAT 0x1110 -#define CL_IMAGE_ELEMENT_SIZE 0x1111 -#define CL_IMAGE_ROW_PITCH 0x1112 -#define CL_IMAGE_SLICE_PITCH 0x1113 -#define CL_IMAGE_WIDTH 0x1114 -#define CL_IMAGE_HEIGHT 0x1115 -#define CL_IMAGE_DEPTH 0x1116 -#define CL_IMAGE_ARRAY_SIZE 0x1117 -#define CL_IMAGE_BUFFER 0x1118 -#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 -#define CL_IMAGE_NUM_SAMPLES 0x111A - -/* cl_pipe_info */ -#define CL_PIPE_PACKET_SIZE 0x1120 -#define CL_PIPE_MAX_PACKETS 0x1121 - -/* cl_addressing_mode */ -#define CL_ADDRESS_NONE 0x1130 -#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 -#define CL_ADDRESS_CLAMP 0x1132 -#define CL_ADDRESS_REPEAT 0x1133 -#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 - -/* cl_filter_mode */ -#define CL_FILTER_NEAREST 0x1140 -#define CL_FILTER_LINEAR 0x1141 - -/* cl_sampler_info */ -#define CL_SAMPLER_REFERENCE_COUNT 0x1150 -#define CL_SAMPLER_CONTEXT 0x1151 -#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 -#define CL_SAMPLER_ADDRESSING_MODE 0x1153 -#define CL_SAMPLER_FILTER_MODE 0x1154 -#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 -#define CL_SAMPLER_LOD_MIN 0x1156 -#define CL_SAMPLER_LOD_MAX 0x1157 - -/* cl_map_flags - bitfield */ -#define CL_MAP_READ (1 << 0) -#define CL_MAP_WRITE (1 << 1) -#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) - -/* cl_program_info */ -#define CL_PROGRAM_REFERENCE_COUNT 0x1160 -#define CL_PROGRAM_CONTEXT 0x1161 -#define CL_PROGRAM_NUM_DEVICES 0x1162 -#define CL_PROGRAM_DEVICES 0x1163 -#define CL_PROGRAM_SOURCE 0x1164 -#define CL_PROGRAM_BINARY_SIZES 0x1165 -#define CL_PROGRAM_BINARIES 0x1166 -#define CL_PROGRAM_NUM_KERNELS 0x1167 -#define CL_PROGRAM_KERNEL_NAMES 0x1168 -#define CL_PROGRAM_IL 0x1169 - -/* cl_program_build_info */ -#define CL_PROGRAM_BUILD_STATUS 0x1181 -#define CL_PROGRAM_BUILD_OPTIONS 0x1182 -#define CL_PROGRAM_BUILD_LOG 0x1183 -#define CL_PROGRAM_BINARY_TYPE 0x1184 -#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 - -/* cl_program_binary_type */ -#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 -#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 -#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 -#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 - -/* cl_build_status */ -#define CL_BUILD_SUCCESS 0 -#define CL_BUILD_NONE -1 -#define CL_BUILD_ERROR -2 -#define CL_BUILD_IN_PROGRESS -3 - -/* cl_kernel_info */ -#define CL_KERNEL_FUNCTION_NAME 0x1190 -#define CL_KERNEL_NUM_ARGS 0x1191 -#define CL_KERNEL_REFERENCE_COUNT 0x1192 -#define CL_KERNEL_CONTEXT 0x1193 -#define CL_KERNEL_PROGRAM 0x1194 -#define CL_KERNEL_ATTRIBUTES 0x1195 -#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 -#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA - -/* cl_kernel_arg_info */ -#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 -#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 -#define CL_KERNEL_ARG_TYPE_NAME 0x1198 -#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 -#define CL_KERNEL_ARG_NAME 0x119A - -/* cl_kernel_arg_address_qualifier */ -#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B -#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C -#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D -#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E - -/* cl_kernel_arg_access_qualifier */ -#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 -#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 -#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 -#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 - -/* cl_kernel_arg_type_qualifer */ -#define CL_KERNEL_ARG_TYPE_NONE 0 -#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) -#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) -#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) -#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) - -/* cl_kernel_work_group_info */ -#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 -#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 -#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 -#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 -#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 -#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 - -/* cl_kernel_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 -#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 - -/* cl_kernel_exec_info */ -#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 -#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 - -/* cl_event_info */ -#define CL_EVENT_COMMAND_QUEUE 0x11D0 -#define CL_EVENT_COMMAND_TYPE 0x11D1 -#define CL_EVENT_REFERENCE_COUNT 0x11D2 -#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 -#define CL_EVENT_CONTEXT 0x11D4 - -/* cl_command_type */ -#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 -#define CL_COMMAND_TASK 0x11F1 -#define CL_COMMAND_NATIVE_KERNEL 0x11F2 -#define CL_COMMAND_READ_BUFFER 0x11F3 -#define CL_COMMAND_WRITE_BUFFER 0x11F4 -#define CL_COMMAND_COPY_BUFFER 0x11F5 -#define CL_COMMAND_READ_IMAGE 0x11F6 -#define CL_COMMAND_WRITE_IMAGE 0x11F7 -#define CL_COMMAND_COPY_IMAGE 0x11F8 -#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 -#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA -#define CL_COMMAND_MAP_BUFFER 0x11FB -#define CL_COMMAND_MAP_IMAGE 0x11FC -#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD -#define CL_COMMAND_MARKER 0x11FE -#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF -#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 -#define CL_COMMAND_READ_BUFFER_RECT 0x1201 -#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 -#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 -#define CL_COMMAND_USER 0x1204 -#define CL_COMMAND_BARRIER 0x1205 -#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 -#define CL_COMMAND_FILL_BUFFER 0x1207 -#define CL_COMMAND_FILL_IMAGE 0x1208 -#define CL_COMMAND_SVM_FREE 0x1209 -#define CL_COMMAND_SVM_MEMCPY 0x120A -#define CL_COMMAND_SVM_MEMFILL 0x120B -#define CL_COMMAND_SVM_MAP 0x120C -#define CL_COMMAND_SVM_UNMAP 0x120D - -/* command execution status */ -#define CL_COMPLETE 0x0 -#define CL_RUNNING 0x1 -#define CL_SUBMITTED 0x2 -#define CL_QUEUED 0x3 - -/* cl_buffer_create_type */ -#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 - -/* cl_profiling_info */ -#define CL_PROFILING_COMMAND_QUEUED 0x1280 -#define CL_PROFILING_COMMAND_SUBMIT 0x1281 -#define CL_PROFILING_COMMAND_START 0x1282 -#define CL_PROFILING_COMMAND_END 0x1283 -#define CL_PROFILING_COMMAND_COMPLETE 0x1284 - -/********************************************************************************************************/ - -/* Platform API */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformIDs(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformInfo(cl_platform_id /* platform */, - cl_platform_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Device APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceIDs(cl_platform_id /* platform */, - cl_device_type /* device_type */, - cl_uint /* num_entries */, - cl_device_id * /* devices */, - cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceInfo(cl_device_id /* device */, - cl_device_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateSubDevices(cl_device_id /* in_device */, - const cl_device_partition_property * /* properties */, - cl_uint /* num_devices */, - cl_device_id * /* out_devices */, - cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetDefaultDeviceCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceAndHostTimer(cl_device_id /* device */, - cl_ulong* /* device_timestamp */, - cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetHostTimer(cl_device_id /* device */, - cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - - -/* Context APIs */ -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContext(const cl_context_properties * /* properties */, - cl_uint /* num_devices */, - const cl_device_id * /* devices */, - void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContextFromType(const cl_context_properties * /* properties */, - cl_device_type /* device_type */, - void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetContextInfo(cl_context /* context */, - cl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Command Queue APIs */ -extern CL_API_ENTRY cl_command_queue CL_API_CALL -clCreateCommandQueueWithProperties(cl_context /* context */, - cl_device_id /* device */, - const cl_queue_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetCommandQueueInfo(cl_command_queue /* command_queue */, - cl_command_queue_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Memory Object APIs */ -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - size_t /* size */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateSubBuffer(cl_mem /* buffer */, - cl_mem_flags /* flags */, - cl_buffer_create_type /* buffer_create_type */, - const void * /* buffer_create_info */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateImage(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - const cl_image_desc * /* image_desc */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreatePipe(cl_context /* context */, - cl_mem_flags /* flags */, - cl_uint /* pipe_packet_size */, - cl_uint /* pipe_max_packets */, - const cl_pipe_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSupportedImageFormats(cl_context /* context */, - cl_mem_flags /* flags */, - cl_mem_object_type /* image_type */, - cl_uint /* num_entries */, - cl_image_format * /* image_formats */, - cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetMemObjectInfo(cl_mem /* memobj */, - cl_mem_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetImageInfo(cl_mem /* image */, - cl_image_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPipeInfo(cl_mem /* pipe */, - cl_pipe_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetMemObjectDestructorCallback(cl_mem /* memobj */, - void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; - -/* SVM Allocation APIs */ -extern CL_API_ENTRY void * CL_API_CALL -clSVMAlloc(cl_context /* context */, - cl_svm_mem_flags /* flags */, - size_t /* size */, - cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY void CL_API_CALL -clSVMFree(cl_context /* context */, - void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; - -/* Sampler APIs */ -extern CL_API_ENTRY cl_sampler CL_API_CALL -clCreateSamplerWithProperties(cl_context /* context */, - const cl_sampler_properties * /* normalized_coords */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSamplerInfo(cl_sampler /* sampler */, - cl_sampler_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Program Object APIs */ -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithSource(cl_context /* context */, - cl_uint /* count */, - const char ** /* strings */, - const size_t * /* lengths */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBinary(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const size_t * /* lengths */, - const unsigned char ** /* binaries */, - cl_int * /* binary_status */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBuiltInKernels(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* kernel_names */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithIL(cl_context /* context */, - const void* /* il */, - size_t /* length */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clBuildProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCompileProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_headers */, - const cl_program * /* input_headers */, - const char ** /* header_include_names */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clLinkProgram(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_programs */, - const cl_program * /* input_programs */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */, - cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramInfo(cl_program /* program */, - cl_program_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramBuildInfo(cl_program /* program */, - cl_device_id /* device */, - cl_program_build_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Kernel Object APIs */ -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCreateKernel(cl_program /* program */, - const char * /* kernel_name */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateKernelsInProgram(cl_program /* program */, - cl_uint /* num_kernels */, - cl_kernel * /* kernels */, - cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCloneKernel(cl_kernel /* source_kernel */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArg(cl_kernel /* kernel */, - cl_uint /* arg_index */, - size_t /* arg_size */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArgSVMPointer(cl_kernel /* kernel */, - cl_uint /* arg_index */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelExecInfo(cl_kernel /* kernel */, - cl_kernel_exec_info /* param_name */, - size_t /* param_value_size */, - const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelInfo(cl_kernel /* kernel */, - cl_kernel_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelArgInfo(cl_kernel /* kernel */, - cl_uint /* arg_indx */, - cl_kernel_arg_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelWorkGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_work_group_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_sub_group_info /* param_name */, - size_t /* input_value_size */, - const void* /*input_value */, - size_t /* param_value_size */, - void* /* param_value */, - size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; - - -/* Event Object APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clWaitForEvents(cl_uint /* num_events */, - const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventInfo(cl_event /* event */, - cl_event_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateUserEvent(cl_context /* context */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetUserEventStatus(cl_event /* event */, - cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetEventCallback( cl_event /* event */, - cl_int /* command_exec_callback_type */, - void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; - -/* Profiling APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventProfilingInfo(cl_event /* event */, - cl_profiling_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Flush and Finish APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -/* Enqueued Commands APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - size_t /* offset */, - size_t /* size */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - size_t /* offset */, - size_t /* size */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - size_t /* src_offset */, - size_t /* dst_offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin */, - const size_t * /* dst_origin */, - const size_t * /* region */, - size_t /* src_row_pitch */, - size_t /* src_slice_pitch */, - size_t /* dst_row_pitch */, - size_t /* dst_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_read */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* row_pitch */, - size_t /* slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_write */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* input_row_pitch */, - size_t /* input_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - const void * /* fill_color */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImage(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_image */, - const size_t * /* src_origin[3] */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin[3] */, - const size_t * /* region[3] */, - size_t /* dst_offset */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_image */, - size_t /* src_offset */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t * /* image_row_pitch */, - size_t * /* image_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, - cl_mem /* memobj */, - void * /* mapped_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_objects */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* work_dim */, - const size_t * /* global_work_offset */, - const size_t * /* global_work_size */, - const size_t * /* local_work_size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNativeKernel(cl_command_queue /* command_queue */, - void (CL_CALLBACK * /*user_func*/)(void *), - void * /* args */, - size_t /* cb_args */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_list */, - const void ** /* args_mem_loc */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMFree(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void * /* user_data */), - void * /* user_data */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, - cl_bool /* blocking_copy */, - void * /* dst_ptr */, - const void * /* src_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemFill(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMap(cl_command_queue /* command_queue */, - cl_bool /* blocking_map */, - cl_map_flags /* flags */, - void * /* svm_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMUnmap(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - const void ** /* svm_pointers */, - const size_t * /* sizes */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; - - -/* Extension function access - * - * Returns the extension function address for the given function name, - * or NULL if a valid function can not be found. The client must - * check to make sure the address is not NULL, before using or - * calling the returned function address. - */ -extern CL_API_ENTRY void * CL_API_CALL -clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, - const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage2D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_row_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage3D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_depth */, - size_t /* image_row_pitch */, - size_t /* image_slice_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueMarker(cl_command_queue /* command_queue */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueWaitForEvents(cl_command_queue /* command_queue */, - cl_uint /* num_events */, - const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL -clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* Deprecated OpenCL 2.0 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL -clCreateCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue_properties /* properties */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL -clCreateSampler(cl_context /* context */, - cl_bool /* normalized_coords */, - cl_addressing_mode /* addressing_mode */, - cl_filter_mode /* filter_mode */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL -clEnqueueTask(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h deleted file mode 100644 index d5960a43f72..00000000000 --- a/third_party/opencl/include/CL/cl_d3d10.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D10_H -#define __OPENCL_CL_D3D10_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d10_sharing */ -#define cl_khr_d3d10_sharing 1 - -typedef cl_uint cl_d3d10_device_source_khr; -typedef cl_uint cl_d3d10_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D10_DEVICE_KHR -1002 -#define CL_INVALID_D3D10_RESOURCE_KHR -1003 -#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 -#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 - -/* cl_d3d10_device_source_nv */ -#define CL_D3D10_DEVICE_KHR 0x4010 -#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 - -/* cl_d3d10_device_set_nv */ -#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 -#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 - -/* cl_context_info */ -#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 -#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C - -/* cl_mem_info */ -#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 - -/* cl_image_info */ -#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 -#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( - cl_platform_id platform, - cl_d3d10_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d10_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D10_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h deleted file mode 100644 index 39f9072398a..00000000000 --- a/third_party/opencl/include/CL/cl_d3d11.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D11_H -#define __OPENCL_CL_D3D11_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d11_sharing */ -#define cl_khr_d3d11_sharing 1 - -typedef cl_uint cl_d3d11_device_source_khr; -typedef cl_uint cl_d3d11_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D11_DEVICE_KHR -1006 -#define CL_INVALID_D3D11_RESOURCE_KHR -1007 -#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 -#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 - -/* cl_d3d11_device_source */ -#define CL_D3D11_DEVICE_KHR 0x4019 -#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A - -/* cl_d3d11_device_set */ -#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B -#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C - -/* cl_context_info */ -#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D -#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D - -/* cl_mem_info */ -#define CL_MEM_D3D11_RESOURCE_KHR 0x401E - -/* cl_image_info */ -#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 -#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( - cl_platform_id platform, - cl_d3d11_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d11_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D11_H */ - diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h deleted file mode 100644 index 2729e8b9e89..00000000000 --- a/third_party/opencl/include/CL/cl_dx9_media_sharing.h +++ /dev/null @@ -1,132 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H -#define __OPENCL_CL_DX9_MEDIA_SHARING_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ -/* cl_khr_dx9_media_sharing */ -#define cl_khr_dx9_media_sharing 1 - -typedef cl_uint cl_dx9_media_adapter_type_khr; -typedef cl_uint cl_dx9_media_adapter_set_khr; - -#if defined(_WIN32) -#include -typedef struct _cl_dx9_surface_info_khr -{ - IDirect3DSurface9 *resource; - HANDLE shared_handle; -} cl_dx9_surface_info_khr; -#endif - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 -#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 -#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 -#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 - -/* cl_media_adapter_type_khr */ -#define CL_ADAPTER_D3D9_KHR 0x2020 -#define CL_ADAPTER_D3D9EX_KHR 0x2021 -#define CL_ADAPTER_DXVA_KHR 0x2022 - -/* cl_media_adapter_set_khr */ -#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 -#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 - -/* cl_context_info */ -#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 -#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 -#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 - -/* cl_mem_info */ -#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 -#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 - -/* cl_image_info */ -#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B -#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( - cl_platform_id platform, - cl_uint num_media_adapters, - cl_dx9_media_adapter_type_khr * media_adapter_type, - void * media_adapters, - cl_dx9_media_adapter_set_khr media_adapter_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( - cl_context context, - cl_mem_flags flags, - cl_dx9_media_adapter_type_khr adapter_type, - void * surface_info, - cl_uint plane, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ - diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h deleted file mode 100644 index a765bd5266c..00000000000 --- a/third_party/opencl/include/CL/cl_egl.h +++ /dev/null @@ -1,136 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_EGL_H -#define __OPENCL_CL_EGL_H - -#ifdef __APPLE__ - -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ -#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F -#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D -#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E - -/* Error type for clCreateFromEGLImageKHR */ -#define CL_INVALID_EGL_OBJECT_KHR -1093 -#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 - -/* CLeglImageKHR is an opaque handle to an EGLImage */ -typedef void* CLeglImageKHR; - -/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ -typedef void* CLeglDisplayKHR; - -/* CLeglSyncKHR is an opaque handle to an EGLSync object */ -typedef void* CLeglSyncKHR; - -/* properties passed to clCreateFromEGLImageKHR */ -typedef intptr_t cl_egl_image_properties_khr; - - -#define cl_khr_egl_image 1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageKHR(cl_context /* context */, - CLeglDisplayKHR /* egldisplay */, - CLeglImageKHR /* eglimage */, - cl_mem_flags /* flags */, - const cl_egl_image_properties_khr * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( - cl_context context, - CLeglDisplayKHR egldisplay, - CLeglImageKHR eglimage, - cl_mem_flags flags, - const cl_egl_image_properties_khr * properties, - cl_int * errcode_ret); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -#define cl_khr_egl_event 1 - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromEGLSyncKHR(cl_context /* context */, - CLeglSyncKHR /* sync */, - CLeglDisplayKHR /* display */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( - cl_context context, - CLeglSyncKHR sync, - CLeglDisplayKHR display, - cl_int * errcode_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h deleted file mode 100644 index 7941583895c..00000000000 --- a/third_party/opencl/include/CL/cl_ext.h +++ /dev/null @@ -1,391 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ - -/* cl_ext.h contains OpenCL extensions which don't have external */ -/* (OpenGL, D3D) dependencies. */ - -#ifndef __CL_EXT_H -#define __CL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include - #include -#else - #include -#endif - -/* cl_khr_fp16 extension - no extension #define since it has no functions */ -#define CL_DEVICE_HALF_FP_CONFIG 0x1033 - -/* Memory object destruction - * - * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR - * - * Registers a user callback function that will be called when the memory object is deleted and its resources - * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback - * stack associated with memobj. The registered user callback functions are called in the reverse order in - * which they were registered. The user callback functions are called and then the memory object is deleted - * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be - * notified when the memory referenced by host_ptr, specified when the memory object is created and used as - * the storage bits for the memory object, can be reused or freed. - * - * The application may not call CL api's with the cl_mem object passed to the pfn_notify. - * - * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - */ -#define cl_APPLE_SetMemObjectDestructor 1 -cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, - void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/* Context Logging Functions - * - * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). - * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - * - * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger - */ -#define cl_APPLE_ContextLoggingFunctions 1 -extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ -extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ -extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/************************ -* cl_khr_icd extension * -************************/ -#define cl_khr_icd 1 - -/* cl_platform_info */ -#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 - -/* Additional Error Codes */ -#define CL_PLATFORM_NOT_FOUND_KHR -1001 - -extern CL_API_ENTRY cl_int CL_API_CALL -clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( - cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - - -/* Extension: cl_khr_image2D_buffer - * - * This extension allows a 2D image to be created from a cl_mem buffer without a copy. - * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. - * Both the sampler and sampler-less read_image built-in functions are supported for 2D images - * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported - * for 2D images created from a buffer. - * - * When the 2D image from buffer is created, the client must specify the width, - * height, image format (i.e. channel order and channel data type) and optionally the row pitch - * - * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. - * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. - */ - -/************************************* - * cl_khr_initalize_memory extension * - *************************************/ - -#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 - - -/************************************** - * cl_khr_terminate_context extension * - **************************************/ - -#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 -#define CL_CONTEXT_TERMINATE_KHR 0x2032 - -#define cl_khr_terminate_context 1 -extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - - -/* - * Extension: cl_khr_spir - * - * This extension adds support to create an OpenCL program object from a - * Standard Portable Intermediate Representation (SPIR) instance - */ - -#define CL_DEVICE_SPIR_VERSIONS 0x40E0 -#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 - - -/****************************************** -* cl_nv_device_attribute_query extension * -******************************************/ -/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ -#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 -#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 -#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 -#define CL_DEVICE_WARP_SIZE_NV 0x4003 -#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 -#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 -#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 - -/********************************* -* cl_amd_device_attribute_query * -*********************************/ -#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 - -/********************************* -* cl_arm_printf extension -*********************************/ -#define CL_PRINTF_CALLBACK_ARM 0x40B0 -#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 - -#ifdef CL_VERSION_1_1 - /*********************************** - * cl_ext_device_fission extension * - ***********************************/ - #define cl_ext_device_fission 1 - - extern CL_API_ENTRY cl_int CL_API_CALL - clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - extern CL_API_ENTRY cl_int CL_API_CALL - clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef cl_ulong cl_device_partition_property_ext; - extern CL_API_ENTRY cl_int CL_API_CALL - clCreateSubDevicesEXT( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - /* cl_device_partition_property_ext */ - #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 - #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 - #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 - #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 - - /* clDeviceGetInfo selectors */ - #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 - #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 - #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 - #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 - #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 - - /* error codes */ - #define CL_DEVICE_PARTITION_FAILED_EXT -1057 - #define CL_INVALID_PARTITION_COUNT_EXT -1058 - #define CL_INVALID_PARTITION_NAME_EXT -1059 - - /* CL_AFFINITY_DOMAINs */ - #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 - #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 - #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 - #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 - #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 - #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 - - /* cl_device_partition_property_ext list terminators */ - #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) - -/********************************* -* cl_qcom_ext_host_ptr extension -*********************************/ - -#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) - -#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 -#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 -#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 -#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 -#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 -#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 -#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 -#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 - -typedef cl_uint cl_image_pitch_info_qcom; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceImageInfoQCOM(cl_device_id device, - size_t image_width, - size_t image_height, - const cl_image_format *image_format, - cl_image_pitch_info_qcom param_name, - size_t param_value_size, - void *param_value, - size_t *param_value_size_ret); - -typedef struct _cl_mem_ext_host_ptr -{ - /* Type of external memory allocation. */ - /* Legal values will be defined in layered extensions. */ - cl_uint allocation_type; - - /* Host cache policy for this external memory allocation. */ - cl_uint host_cache_policy; - -} cl_mem_ext_host_ptr; - -/********************************* -* cl_qcom_ion_host_ptr extension -*********************************/ - -#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 - -typedef struct _cl_mem_ion_host_ptr -{ - /* Type of external memory allocation. */ - /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ - cl_mem_ext_host_ptr ext_host_ptr; - - /* ION file descriptor */ - int ion_filedesc; - - /* Host pointer to the ION allocated memory */ - void* ion_hostptr; - -} cl_mem_ion_host_ptr; - -#endif /* CL_VERSION_1_1 */ - - -#ifdef CL_VERSION_2_0 -/********************************* -* cl_khr_sub_groups extension -*********************************/ -#define cl_khr_sub_groups 1 - -typedef cl_uint cl_kernel_sub_group_info_khr; - -/* cl_khr_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; - -typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; -#endif /* CL_VERSION_2_0 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_priority_hints extension -*********************************/ -#define cl_khr_priority_hints 1 - -typedef cl_uint cl_queue_priority_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_PRIORITY_KHR 0x1096 - -/* cl_queue_priority_khr */ -#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) -#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) -#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_throttle_hints extension -*********************************/ -#define cl_khr_throttle_hints 1 - -typedef cl_uint cl_queue_throttle_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_THROTTLE_KHR 0x1097 - -/* cl_queue_throttle_khr */ -#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) -#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) -#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef __cplusplus -} -#endif - - -#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h deleted file mode 100644 index 6328a1cd93a..00000000000 --- a/third_party/opencl/include/CL/cl_ext_qcom.h +++ /dev/null @@ -1,255 +0,0 @@ -/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. - * Qualcomm Technologies Proprietary and Confidential. - */ - -#ifndef __OPENCL_CL_EXT_QCOM_H -#define __OPENCL_CL_EXT_QCOM_H - -// Needed by cl_khr_egl_event extension -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -/************************************ - * cl_qcom_create_buffer_from_image * - ************************************/ - -#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 -#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBufferFromImageQCOM(cl_mem image, - cl_mem_flags flags, - cl_int *errcode_ret); - - -/************************************ - * cl_qcom_limited_printf extension * - ************************************/ - -/* Builtin printf function buffer size in bytes. */ -#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 - - -/************************************* - * cl_qcom_extended_images extension * - *************************************/ - -#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF - -/************************************* - * cl_qcom_perf_hint extension * - *************************************/ - -typedef cl_uint cl_perf_hint; - -#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 - -/*cl_perf_hint*/ -#define CL_PERF_HINT_HIGH_QCOM 0x40C3 -#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 -#define CL_PERF_HINT_LOW_QCOM 0x40C5 - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetPerfHintQCOM(cl_context context, - cl_perf_hint perf_hint); - -// This extension is published at Khronos, so its definitions are made in cl_ext.h. -// This duplication is for backward compatibility. - -#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/********************************* -* cl_qcom_android_native_buffer_host_ptr extension -*********************************/ - -#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 - - -typedef struct _cl_mem_android_native_buffer_host_ptr -{ - // Type of external memory allocation. - // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. - cl_mem_ext_host_ptr ext_host_ptr; - - // Virtual pointer to the android native buffer - void* anb_ptr; - -} cl_mem_android_native_buffer_host_ptr; - -#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/*********************************** -* cl_img_egl_image extension * -************************************/ -typedef void* CLeglImageIMG; -typedef void* CLeglDisplayIMG; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageIMG(cl_context context, - cl_mem_flags flags, - CLeglImageIMG image, - CLeglDisplayIMG display, - cl_int *errcode_ret); - - -/********************************* -* cl_qcom_other_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-standard images -#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) - -// cl_channel_type -#define CL_QCOM_UNORM_MIPI10 0x4159 -#define CL_QCOM_UNORM_MIPI12 0x415A -#define CL_QCOM_UNSIGNED_MIPI10 0x415B -#define CL_QCOM_UNSIGNED_MIPI12 0x415C -#define CL_QCOM_UNORM_INT10 0x415D -#define CL_QCOM_UNORM_INT12 0x415E -#define CL_QCOM_UNSIGNED_INT16 0x415F - -// cl_channel_order -// Dedicate 0x4130-0x415F range for QCOM extended image formats -// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format -#define CL_QCOM_BAYER 0x414E - -#define CL_QCOM_NV12 0x4133 -#define CL_QCOM_NV12_Y 0x4134 -#define CL_QCOM_NV12_UV 0x4135 - -#define CL_QCOM_TILED_NV12 0x4136 -#define CL_QCOM_TILED_NV12_Y 0x4137 -#define CL_QCOM_TILED_NV12_UV 0x4138 - -#define CL_QCOM_P010 0x413C -#define CL_QCOM_P010_Y 0x413D -#define CL_QCOM_P010_UV 0x413E - -#define CL_QCOM_TILED_P010 0x413F -#define CL_QCOM_TILED_P010_Y 0x4140 -#define CL_QCOM_TILED_P010_UV 0x4141 - - -#define CL_QCOM_TP10 0x4145 -#define CL_QCOM_TP10_Y 0x4146 -#define CL_QCOM_TP10_UV 0x4147 - -#define CL_QCOM_TILED_TP10 0x4148 -#define CL_QCOM_TILED_TP10_Y 0x4149 -#define CL_QCOM_TILED_TP10_UV 0x414A - -/********************************* -* cl_qcom_compressed_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-planar compressed images -#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) - -// Extended image format -// cl_channel_order -#define CL_QCOM_COMPRESSED_RGBA 0x4130 -#define CL_QCOM_COMPRESSED_RGBx 0x4131 - -#define CL_QCOM_COMPRESSED_NV12_Y 0x413A -#define CL_QCOM_COMPRESSED_NV12_UV 0x413B - -#define CL_QCOM_COMPRESSED_P010 0x4142 -#define CL_QCOM_COMPRESSED_P010_Y 0x4143 -#define CL_QCOM_COMPRESSED_P010_UV 0x4144 - -#define CL_QCOM_COMPRESSED_TP10 0x414B -#define CL_QCOM_COMPRESSED_TP10_Y 0x414C -#define CL_QCOM_COMPRESSED_TP10_UV 0x414D - -#define CL_QCOM_COMPRESSED_NV12_4R 0x414F -#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 -#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 -/********************************* -* cl_qcom_compressed_yuv_image_read extension -*********************************/ - -// Extended flag for creating/querying QCOM compressed images -#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) - -// Extended image format -#define CL_QCOM_COMPRESSED_NV12 0x10C4 - -// Extended flag for setting ION buffer allocation type -#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD -#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE - -/********************************* -* cl_qcom_accelerated_image_ops -*********************************/ -#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 -#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 - -//Extended flag for specifying weight image type -#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) - -// Box Filter -typedef struct _cl_box_filter_size_qcom -{ - // Width of box filter on X direction. - float box_filter_width; - - // Height of box filter on Y direction. - float box_filter_height; -} cl_box_filter_size_qcom; - -// HOF Weight Image Desc -typedef struct _cl_weight_desc_qcom -{ - /** Coordinate of the "center" point of the weight image, - based on the weight image's top-left corner as the origin. */ - size_t center_coord_x; - size_t center_coord_y; - cl_bitfield flags; -} cl_weight_desc_qcom; - -typedef struct _cl_weight_image_desc_qcom -{ - cl_image_desc image_desc; - cl_weight_desc_qcom weight_desc; -} cl_weight_image_desc_qcom; - -/************************************* - * cl_qcom_protected_context extension * - *************************************/ - -#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 -#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 - -/************************************* - * cl_qcom_priority_hint extension * - *************************************/ -#define CL_PRIORITY_HINT_NONE_QCOM 0 -typedef cl_uint cl_priority_hint; - -#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 - -/*cl_priority_hint*/ -#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA -#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB -#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h deleted file mode 100644 index 945daa83d7f..00000000000 --- a/third_party/opencl/include/CL/cl_gl.h +++ /dev/null @@ -1,167 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -#ifndef __OPENCL_CL_GL_H -#define __OPENCL_CL_GL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef cl_uint cl_gl_object_type; -typedef cl_uint cl_gl_texture_info; -typedef cl_uint cl_gl_platform_info; -typedef struct __GLsync *cl_GLsync; - -/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ -#define CL_GL_OBJECT_BUFFER 0x2000 -#define CL_GL_OBJECT_TEXTURE2D 0x2001 -#define CL_GL_OBJECT_TEXTURE3D 0x2002 -#define CL_GL_OBJECT_RENDERBUFFER 0x2003 -#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E -#define CL_GL_OBJECT_TEXTURE1D 0x200F -#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 -#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 - -/* cl_gl_texture_info */ -#define CL_GL_TEXTURE_TARGET 0x2004 -#define CL_GL_MIPMAP_LEVEL 0x2005 -#define CL_GL_NUM_SAMPLES 0x2012 - - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* bufobj */, - int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLTexture(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLRenderbuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* renderbuffer */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLObjectInfo(cl_mem /* memobj */, - cl_gl_object_type * /* gl_object_type */, - cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLTextureInfo(cl_mem /* memobj */, - cl_gl_texture_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture2D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture3D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* cl_khr_gl_sharing extension */ - -#define cl_khr_gl_sharing 1 - -typedef cl_uint cl_gl_context_info; - -/* Additional Error Codes */ -#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 - -/* cl_gl_context_info */ -#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 -#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 - -/* Additional cl_context_properties */ -#define CL_GL_CONTEXT_KHR 0x2008 -#define CL_EGL_DISPLAY_KHR 0x2009 -#define CL_GLX_DISPLAY_KHR 0x200A -#define CL_WGL_HDC_KHR 0x200B -#define CL_CGL_SHAREGROUP_KHR 0x200C - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLContextInfoKHR(const cl_context_properties * /* properties */, - cl_gl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( - const cl_context_properties * properties, - cl_gl_context_info param_name, - size_t param_value_size, - void * param_value, - size_t * param_value_size_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h deleted file mode 100644 index e3c14c6408c..00000000000 --- a/third_party/opencl/include/CL/cl_gl_ext.h +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ -/* OpenGL dependencies. */ - -#ifndef __OPENCL_CL_GL_EXT_H -#define __OPENCL_CL_GL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include -#else - #include -#endif - -/* - * For each extension, follow this template - * cl_VEN_extname extension */ -/* #define cl_VEN_extname 1 - * ... define new types, if any - * ... define new tokens, if any - * ... define new APIs, if any - * - * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header - * This allows us to avoid having to decide whether to include GL headers or GLES here. - */ - -/* - * cl_khr_gl_event extension - * See section 9.9 in the OpenCL 1.1 spec for more information - */ -#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromGLsyncKHR(cl_context /* context */, - cl_GLsync /* cl_GLsync */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h deleted file mode 100644 index 4e334a29183..00000000000 --- a/third_party/opencl/include/CL/cl_platform.h +++ /dev/null @@ -1,1333 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ - -#ifndef __CL_PLATFORM_H -#define __CL_PLATFORM_H - -#ifdef __APPLE__ - /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ - #include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(_WIN32) - #define CL_API_ENTRY - #define CL_API_CALL __stdcall - #define CL_CALLBACK __stdcall -#else - #define CL_API_ENTRY - #define CL_API_CALL - #define CL_CALLBACK -#endif - -/* - * Deprecation flags refer to the last version of the header in which the - * feature was not deprecated. - * - * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without - * deprecation but is deprecated in versions later than 1.1. - */ - -#ifdef __APPLE__ - #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) - #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 - - #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 - #else - #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #endif -#else - #define CL_EXTENSION_WEAK_LINK - #define CL_API_SUFFIX__VERSION_1_0 - #define CL_EXT_SUFFIX__VERSION_1_0 - #define CL_API_SUFFIX__VERSION_1_1 - #define CL_EXT_SUFFIX__VERSION_1_1 - #define CL_API_SUFFIX__VERSION_1_2 - #define CL_EXT_SUFFIX__VERSION_1_2 - #define CL_API_SUFFIX__VERSION_2_0 - #define CL_EXT_SUFFIX__VERSION_2_0 - #define CL_API_SUFFIX__VERSION_2_1 - #define CL_EXT_SUFFIX__VERSION_2_1 - - #ifdef __GNUC__ - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif - #elif _WIN32 - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) - #endif - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif -#endif - -#if (defined (_WIN32) && defined(_MSC_VER)) - -/* scalar types */ -typedef signed __int8 cl_char; -typedef unsigned __int8 cl_uchar; -typedef signed __int16 cl_short; -typedef unsigned __int16 cl_ushort; -typedef signed __int32 cl_int; -typedef unsigned __int32 cl_uint; -typedef signed __int64 cl_long; -typedef unsigned __int64 cl_ulong; - -typedef unsigned __int16 cl_half; -typedef float cl_float; -typedef double cl_double; - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 340282346638528859811704183484516925440.0f -#define CL_FLT_MIN 1.175494350822287507969e-38f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 -#define CL_DBL_MIN 2.225073858507201383090e-308 -#define CL_DBL_EPSILON 2.220446049250313080847e-16 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#define CL_NAN (CL_INFINITY - CL_INFINITY) -#define CL_HUGE_VALF ((cl_float) 1e50) -#define CL_HUGE_VAL ((cl_double) 1e500) -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#else - -#include - -/* scalar types */ -typedef int8_t cl_char; -typedef uint8_t cl_uchar; -typedef int16_t cl_short __attribute__((aligned(2))); -typedef uint16_t cl_ushort __attribute__((aligned(2))); -typedef int32_t cl_int __attribute__((aligned(4))); -typedef uint32_t cl_uint __attribute__((aligned(4))); -typedef int64_t cl_long __attribute__((aligned(8))); -typedef uint64_t cl_ulong __attribute__((aligned(8))); - -typedef uint16_t cl_half __attribute__((aligned(2))); -typedef float cl_float __attribute__((aligned(4))); -typedef double cl_double __attribute__((aligned(8))); - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 0x1.fffffep127f -#define CL_FLT_MIN 0x1.0p-126f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 0x1.fffffffffffffp1023 -#define CL_DBL_MIN 0x1.0p-1022 -#define CL_DBL_EPSILON 0x1.0p-52 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#if defined( __GNUC__ ) - #define CL_HUGE_VALF __builtin_huge_valf() - #define CL_HUGE_VAL __builtin_huge_val() - #define CL_NAN __builtin_nanf( "" ) -#else - #define CL_HUGE_VALF ((cl_float) 1e50) - #define CL_HUGE_VAL ((cl_double) 1e500) - float nanf( const char * ); - #define CL_NAN nanf( "" ) -#endif -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#endif - -#include - -/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ -typedef unsigned int cl_GLuint; -typedef int cl_GLint; -typedef unsigned int cl_GLenum; - -/* - * Vector types - * - * Note: OpenCL requires that all types be naturally aligned. - * This means that vector types must be naturally aligned. - * For example, a vector of four floats must be aligned to - * a 16 byte boundary (calculated as 4 * the natural 4-byte - * alignment of the float). The alignment qualifiers here - * will only function properly if your compiler supports them - * and if you don't actively work to defeat them. For example, - * in order for a cl_float4 to be 16 byte aligned in a struct, - * the start of the struct must itself be 16-byte aligned. - * - * Maintaining proper alignment is the user's responsibility. - */ - -/* Define basic vector types */ -#if defined( __VEC__ ) - #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ - typedef vector unsigned char __cl_uchar16; - typedef vector signed char __cl_char16; - typedef vector unsigned short __cl_ushort8; - typedef vector signed short __cl_short8; - typedef vector unsigned int __cl_uint4; - typedef vector signed int __cl_int4; - typedef vector float __cl_float4; - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_UINT4__ 1 - #define __CL_INT4__ 1 - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef float __cl_float4 __attribute__((vector_size(16))); - #else - typedef __m128 __cl_float4; - #endif - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE2__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); - typedef cl_char __cl_char16 __attribute__((vector_size(16))); - typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); - typedef cl_short __cl_short8 __attribute__((vector_size(16))); - typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); - typedef cl_int __cl_int4 __attribute__((vector_size(16))); - typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); - typedef cl_long __cl_long2 __attribute__((vector_size(16))); - typedef cl_double __cl_double2 __attribute__((vector_size(16))); - #else - typedef __m128i __cl_uchar16; - typedef __m128i __cl_char16; - typedef __m128i __cl_ushort8; - typedef __m128i __cl_short8; - typedef __m128i __cl_uint4; - typedef __m128i __cl_int4; - typedef __m128i __cl_ulong2; - typedef __m128i __cl_long2; - typedef __m128d __cl_double2; - #endif - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_INT4__ 1 - #define __CL_UINT4__ 1 - #define __CL_ULONG2__ 1 - #define __CL_LONG2__ 1 - #define __CL_DOUBLE2__ 1 -#endif - -#if defined( __MMX__ ) - #include - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); - typedef cl_char __cl_char8 __attribute__((vector_size(8))); - typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); - typedef cl_short __cl_short4 __attribute__((vector_size(8))); - typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); - typedef cl_int __cl_int2 __attribute__((vector_size(8))); - typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); - typedef cl_long __cl_long1 __attribute__((vector_size(8))); - typedef cl_float __cl_float2 __attribute__((vector_size(8))); - #else - typedef __m64 __cl_uchar8; - typedef __m64 __cl_char8; - typedef __m64 __cl_ushort4; - typedef __m64 __cl_short4; - typedef __m64 __cl_uint2; - typedef __m64 __cl_int2; - typedef __m64 __cl_ulong1; - typedef __m64 __cl_long1; - typedef __m64 __cl_float2; - #endif - #define __CL_UCHAR8__ 1 - #define __CL_CHAR8__ 1 - #define __CL_USHORT4__ 1 - #define __CL_SHORT4__ 1 - #define __CL_INT2__ 1 - #define __CL_UINT2__ 1 - #define __CL_ULONG1__ 1 - #define __CL_LONG1__ 1 - #define __CL_FLOAT2__ 1 -#endif - -#if defined( __AVX__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_float __cl_float8 __attribute__((vector_size(32))); - typedef cl_double __cl_double4 __attribute__((vector_size(32))); - #else - typedef __m256 __cl_float8; - typedef __m256d __cl_double4; - #endif - #define __CL_FLOAT8__ 1 - #define __CL_DOUBLE4__ 1 -#endif - -/* Define capabilities for anonymous struct members. */ -#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ __extension__ -#elif defined( _WIN32) && (_MSC_VER >= 1500) - /* Microsoft Developer Studio 2008 supports anonymous structs, but - * complains by default. */ -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ - /* Disable warning C4201: nonstandard extension used : nameless - * struct/union */ -#pragma warning( push ) -#pragma warning( disable : 4201 ) -#else -#define __CL_HAS_ANON_STRUCT__ 0 -#define __CL_ANON_STRUCT__ -#endif - -/* Define alignment keys */ -#if defined( __GNUC__ ) - #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) -#elif defined( _WIN32) && (_MSC_VER) - /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ - /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ - /* #include */ - /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ - #define CL_ALIGNED(_x) -#else - #warning Need to implement some method to align data here - #define CL_ALIGNED(_x) -#endif - -/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ -#if __CL_HAS_ANON_STRUCT__ - /* .xyzw and .s0123...{f|F} are supported */ - #define CL_HAS_NAMED_VECTOR_FIELDS 1 - /* .hi and .lo are supported */ - #define CL_HAS_HI_LO_VECTOR_FIELDS 1 -#endif - -/* Define cl_vector types */ - -/* ---- cl_charn ---- */ -typedef union -{ - cl_char CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2; -#endif -}cl_char2; - -typedef union -{ - cl_char CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[2]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4; -#endif -}cl_char4; - -/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ -typedef cl_char4 cl_char3; - -typedef union -{ - cl_char CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[4]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[2]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8; -#endif -}cl_char8; - -typedef union -{ - cl_char CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[8]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[4]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8[2]; -#endif -#if defined( __CL_CHAR16__ ) - __cl_char16 v16; -#endif -}cl_char16; - - -/* ---- cl_ucharn ---- */ -typedef union -{ - cl_uchar CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; -#endif -#if defined( __cl_uchar2__) - __cl_uchar2 v2; -#endif -}cl_uchar2; - -typedef union -{ - cl_uchar CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[2]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4; -#endif -}cl_uchar4; - -/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ -typedef cl_uchar4 cl_uchar3; - -typedef union -{ - cl_uchar CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[4]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[2]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8; -#endif -}cl_uchar8; - -typedef union -{ - cl_uchar CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[8]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[4]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8[2]; -#endif -#if defined( __CL_UCHAR16__ ) - __cl_uchar16 v16; -#endif -}cl_uchar16; - - -/* ---- cl_shortn ---- */ -typedef union -{ - cl_short CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2; -#endif -}cl_short2; - -typedef union -{ - cl_short CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[2]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4; -#endif -}cl_short4; - -/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ -typedef cl_short4 cl_short3; - -typedef union -{ - cl_short CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[4]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[2]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8; -#endif -}cl_short8; - -typedef union -{ - cl_short CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[8]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[4]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8[2]; -#endif -#if defined( __CL_SHORT16__ ) - __cl_short16 v16; -#endif -}cl_short16; - - -/* ---- cl_ushortn ---- */ -typedef union -{ - cl_ushort CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2; -#endif -}cl_ushort2; - -typedef union -{ - cl_ushort CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[2]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4; -#endif -}cl_ushort4; - -/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ -typedef cl_ushort4 cl_ushort3; - -typedef union -{ - cl_ushort CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[4]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[2]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8; -#endif -}cl_ushort8; - -typedef union -{ - cl_ushort CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[8]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[4]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8[2]; -#endif -#if defined( __CL_USHORT16__ ) - __cl_ushort16 v16; -#endif -}cl_ushort16; - -/* ---- cl_intn ---- */ -typedef union -{ - cl_int CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2; -#endif -}cl_int2; - -typedef union -{ - cl_int CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[2]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4; -#endif -}cl_int4; - -/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ -typedef cl_int4 cl_int3; - -typedef union -{ - cl_int CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[4]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[2]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8; -#endif -}cl_int8; - -typedef union -{ - cl_int CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[8]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[4]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8[2]; -#endif -#if defined( __CL_INT16__ ) - __cl_int16 v16; -#endif -}cl_int16; - - -/* ---- cl_uintn ---- */ -typedef union -{ - cl_uint CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2; -#endif -}cl_uint2; - -typedef union -{ - cl_uint CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[2]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4; -#endif -}cl_uint4; - -/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ -typedef cl_uint4 cl_uint3; - -typedef union -{ - cl_uint CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[4]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[2]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8; -#endif -}cl_uint8; - -typedef union -{ - cl_uint CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[8]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[4]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8[2]; -#endif -#if defined( __CL_UINT16__ ) - __cl_uint16 v16; -#endif -}cl_uint16; - -/* ---- cl_longn ---- */ -typedef union -{ - cl_long CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2; -#endif -}cl_long2; - -typedef union -{ - cl_long CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[2]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4; -#endif -}cl_long4; - -/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ -typedef cl_long4 cl_long3; - -typedef union -{ - cl_long CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[4]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[2]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8; -#endif -}cl_long8; - -typedef union -{ - cl_long CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[8]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[4]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8[2]; -#endif -#if defined( __CL_LONG16__ ) - __cl_long16 v16; -#endif -}cl_long16; - - -/* ---- cl_ulongn ---- */ -typedef union -{ - cl_ulong CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2; -#endif -}cl_ulong2; - -typedef union -{ - cl_ulong CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[2]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4; -#endif -}cl_ulong4; - -/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ -typedef cl_ulong4 cl_ulong3; - -typedef union -{ - cl_ulong CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[4]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[2]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8; -#endif -}cl_ulong8; - -typedef union -{ - cl_ulong CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[8]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[4]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8[2]; -#endif -#if defined( __CL_ULONG16__ ) - __cl_ulong16 v16; -#endif -}cl_ulong16; - - -/* --- cl_floatn ---- */ - -typedef union -{ - cl_float CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2; -#endif -}cl_float2; - -typedef union -{ - cl_float CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[2]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4; -#endif -}cl_float4; - -/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ -typedef cl_float4 cl_float3; - -typedef union -{ - cl_float CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[4]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[2]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8; -#endif -}cl_float8; - -typedef union -{ - cl_float CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[8]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[4]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8[2]; -#endif -#if defined( __CL_FLOAT16__ ) - __cl_float16 v16; -#endif -}cl_float16; - -/* --- cl_doublen ---- */ - -typedef union -{ - cl_double CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2; -#endif -}cl_double2; - -typedef union -{ - cl_double CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[2]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4; -#endif -}cl_double4; - -/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ -typedef cl_double4 cl_double3; - -typedef union -{ - cl_double CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[4]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[2]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8; -#endif -}cl_double8; - -typedef union -{ - cl_double CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[8]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[4]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8[2]; -#endif -#if defined( __CL_DOUBLE16__ ) - __cl_double16 v16; -#endif -}cl_double16; - -/* Macro to facilitate debugging - * Usage: - * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. - * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" - * Each line thereafter of OpenCL C source must end with: \n\ - * The last line ends in "; - * - * Example: - * - * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ - * kernel void foo( int a, float * b ) \n\ - * { \n\ - * // my comment \n\ - * *b[ get_global_id(0)] = a; \n\ - * } \n\ - * "; - * - * This should correctly set up the line, (column) and file information for your source - * string so you can do source level debugging. - */ -#define __CL_STRINGIFY( _x ) # _x -#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) -#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" - -#ifdef __cplusplus -} -#endif - -#undef __CL_HAS_ANON_STRUCT__ -#undef __CL_ANON_STRUCT__ -#if defined( _WIN32) && (_MSC_VER >= 1500) -#pragma warning( pop ) -#endif - -#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h deleted file mode 100644 index 9855cd75e7d..00000000000 --- a/third_party/opencl/include/CL/opencl.h +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_H -#define __OPENCL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - -#include -#include -#include -#include - -#else - -#include -#include -#include -#include - -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_H */ - diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d4cfc672801..b79d046fcaf 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -57,11 +57,9 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": - base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: - base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 5c2131d4bf8..f428e9972a8 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -58,9 +58,6 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ - opencl-headers \ - ocl-icd-libopencl1 \ - ocl-icd-opencl-dev \ portaudio19-dev \ qttools5-dev-tools \ libqt5svg5-dev \ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 136c4119f64..698ab9885d9 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,11 +6,6 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] -if arch == "Darwin": - base_frameworks.append('OpenCL') -else: - base_libs.append('OpenCL') - replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] if arch != "Darwin": diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 67ad2cc8cbc..6abbc47935e 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,10 +10,6 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements -- Install OpenCL Driver (Ubuntu) -``` -sudo apt install pocl-opencl-icd -``` ## Connect the hardware - Connect the camera first From 46d65095af09d372735676539c9cabcc37453ccc Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 7 Feb 2026 23:04:01 -0800 Subject: [PATCH 141/685] CI: garbage collect tmp jenkins branches (#37125) --- .github/workflows/jenkins-pr-trigger.yaml | 39 ++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/.github/workflows/jenkins-pr-trigger.yaml b/.github/workflows/jenkins-pr-trigger.yaml index f8a53c5ae0c..fdd26253fad 100644 --- a/.github/workflows/jenkins-pr-trigger.yaml +++ b/.github/workflows/jenkins-pr-trigger.yaml @@ -5,7 +5,44 @@ on: types: [created, edited] jobs: - # TODO: gc old branches in a separate job in this workflow + cleanup-branches: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Delete stale Jenkins branches + uses: actions/github-script@v8 + with: + script: | + const cutoff = Date.now() - 24 * 60 * 60 * 1000; + const prefixes = ['tmp-jenkins', '__jenkins']; + + for await (const response of github.paginate.iterator(github.rest.repos.listBranches, { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, + })) { + for (const branch of response.data) { + if (!prefixes.some(p => branch.name.startsWith(p))) continue; + + const { data: commit } = await github.rest.repos.getCommit({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: branch.commit.sha, + }); + + const commitDate = new Date(commit.commit.committer.date).getTime(); + if (commitDate < cutoff) { + console.log(`Deleting branch: ${branch.name} (last commit: ${commit.commit.committer.date})`); + await github.rest.git.deleteRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `heads/${branch.name}`, + }); + } + } + } + scan-comments: runs-on: ubuntu-latest if: ${{ github.event.issue.pull_request }} From ac087d085ec599a3ed8a8d487ccec2b8fc56c75e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Feb 2026 09:59:33 -0800 Subject: [PATCH 142/685] Build vendored artifacts in CI (#37127) * Build vendored artifacts in CI * parallel * deterministic * fix up * fix gitignores * lil more * lil more consistency --- .github/workflows/vendor_third_party.yaml | 51 ++++++++++++++++++ third_party/acados/.gitignore | 6 ++- .../acados/acados_template/gnsf/__init__.py | 1 + third_party/acados/build.sh | 3 ++ third_party/acados/x86_64/lib/libacados.so | 4 +- third_party/acados/x86_64/lib/libblasfeo.so | 4 +- third_party/acados/x86_64/lib/libhpipm.so | 4 +- .../acados/x86_64/lib/libqpOASES_e.so.3.1 | 4 +- third_party/acados/x86_64/t_renderer | 4 +- third_party/build.sh | 53 +++++++++++++++++++ third_party/libyuv/.gitignore | 3 +- third_party/libyuv/build.sh | 3 ++ third_party/libyuv/larch64/lib/libyuv.a | 2 +- third_party/libyuv/x86_64/include | 1 - third_party/libyuv/x86_64/lib/libyuv.a | 4 +- third_party/raylib/.gitignore | 1 + third_party/raylib/Darwin/libraylib.a | 4 +- third_party/raylib/build.sh | 3 ++ third_party/raylib/larch64/libraylib.a | 2 +- third_party/raylib/x86_64/libraylib.a | 4 +- 20 files changed, 140 insertions(+), 21 deletions(-) create mode 100644 .github/workflows/vendor_third_party.yaml create mode 100644 third_party/acados/acados_template/gnsf/__init__.py create mode 100755 third_party/build.sh delete mode 120000 third_party/libyuv/x86_64/include diff --git a/.github/workflows/vendor_third_party.yaml b/.github/workflows/vendor_third_party.yaml new file mode 100644 index 00000000000..df50cfad233 --- /dev/null +++ b/.github/workflows/vendor_third_party.yaml @@ -0,0 +1,51 @@ +name: vendor third_party + +on: + workflow_dispatch: + +jobs: + build: + if: github.ref != 'refs/heads/master' + strategy: + matrix: + os: [ubuntu-24.04, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + with: + submodules: true + - name: Build + run: third_party/build.sh + - name: Package artifacts + run: | + git add -A third_party/ + git diff --cached --name-only -- third_party/ | tar -cf /tmp/third_party_build.tar -T - + - uses: actions/upload-artifact@v4 + with: + name: third-party-${{ runner.os }} + path: /tmp/third_party_build.tar + + commit: + needs: build + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + - uses: actions/download-artifact@v4 + with: + path: /tmp/artifacts + - name: Commit vendored libraries + run: | + for f in /tmp/artifacts/*/third_party_build.tar; do + tar xf "$f" + done + git add third_party/ + if git diff --cached --quiet; then + echo "No changes to commit" + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git commit -m "third_party: rebuild vendor libraries" + git push diff --git a/third_party/acados/.gitignore b/third_party/acados/.gitignore index 68858c62e43..0ae664dff6a 100644 --- a/third_party/acados/.gitignore +++ b/third_party/acados/.gitignore @@ -1,5 +1,9 @@ acados_repo/ -lib +/lib !x86_64/ !larch64/ !aarch64/ +!Darwin/ +!*.so +!*.so.* +!*.dylib diff --git a/third_party/acados/acados_template/gnsf/__init__.py b/third_party/acados/acados_template/gnsf/__init__.py new file mode 100644 index 00000000000..8b137891791 --- /dev/null +++ b/third_party/acados/acados_template/gnsf/__init__.py @@ -0,0 +1 @@ + diff --git a/third_party/acados/build.sh b/third_party/acados/build.sh index 2b803ef6b24..2c4d839ae72 100755 --- a/third_party/acados/build.sh +++ b/third_party/acados/build.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -e +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" ARCHNAME="x86_64" diff --git a/third_party/acados/x86_64/lib/libacados.so b/third_party/acados/x86_64/lib/libacados.so index 4e80f7c76ba..50d647a8621 100644 --- a/third_party/acados/x86_64/lib/libacados.so +++ b/third_party/acados/x86_64/lib/libacados.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:821ce18f417d211c4845b60482d465b809f90dc7d04f023d652d8221e87679b1 -size 553544 +oid sha256:05a1ba3cf37fa929cdd56f892608b2f89c35a05ef1b07fedb86b2f0d76607263 +size 540488 diff --git a/third_party/acados/x86_64/lib/libblasfeo.so b/third_party/acados/x86_64/lib/libblasfeo.so index 26d5a3dbe91..a98f45abd2e 100644 --- a/third_party/acados/x86_64/lib/libblasfeo.so +++ b/third_party/acados/x86_64/lib/libblasfeo.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3feea7927d004064bbc5a13c3287467669ce801cb0a3c616cf9e089816da5a0b -size 2155088 +oid sha256:c0bf22898d9c59b672d3d0961f5f4c804b9957478125d99eb297de3091bedd15 +size 2416112 diff --git a/third_party/acados/x86_64/lib/libhpipm.so b/third_party/acados/x86_64/lib/libhpipm.so index 40e2e4e7d47..f40cb487cd7 100644 --- a/third_party/acados/x86_64/lib/libhpipm.so +++ b/third_party/acados/x86_64/lib/libhpipm.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a042716f515913786581dff39799eb71fc66caddfa18b1c9f0d54f00c1568fd2 -size 1572648 +oid sha256:5b6875fb47940764d4ebb916c2373cb0e04929229feb654b290676c28d48fa9d +size 1531024 diff --git a/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 b/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 index cf5e550faa9..81afd059f7e 100644 --- a/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 +++ b/third_party/acados/x86_64/lib/libqpOASES_e.so.3.1 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a6abea4815e3f03cff06fe8a9602e97f9acf102f18f803571460a94595b93be4 -size 262824 +oid sha256:04be908c3f707e5c968022b9cdd79ab75ae7af46e7fa019ceee98f854ddd3f64 +size 262464 diff --git a/third_party/acados/x86_64/t_renderer b/third_party/acados/x86_64/t_renderer index e995a209b79..d41f6c37255 100755 --- a/third_party/acados/x86_64/t_renderer +++ b/third_party/acados/x86_64/t_renderer @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7a360d4b53826b91ada3358156d44a14d497bdd8ace88707fd4b386ed6d194c7 -size 17503920 +oid sha256:a53ae46650c4df5b0ddb87a658f59a0422e41743e8bc2d822da0aefd1d280791 +size 5088536 diff --git a/third_party/build.sh b/third_party/build.sh new file mode 100755 index 00000000000..d3a9c6579c5 --- /dev/null +++ b/third_party/build.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + +# Reproducible builds: pin timestamps to epoch +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + +pids=() +names=() +logs=() + +for script in "$DIR"/*/build.sh; do + [ -f "$script" ] || continue + name=$(basename "$(dirname "$script")") + log=$(mktemp) + names+=("$name") + logs+=("$log") + (cd "$(dirname "$script")" && bash "$(basename "$script")") >"$log" 2>&1 & + pids+=($!) +done + +failed=0 +for i in "${!pids[@]}"; do + echo "--- ${names[$i]} ---" + if wait "${pids[$i]}"; then + echo "OK" + else + echo "FAILED (exit $?)" + failed=1 + fi + cat "${logs[$i]}" + rm -f "${logs[$i]}" + echo +done + +[ $failed -ne 0 ] && exit $failed + +# Repack ar archives with deterministic headers (zero timestamps/uid/gid) +# Skip foreign-platform archives that ar can't read (e.g. Mach-O on Linux) +while IFS= read -r -d '' lib; do + tmpdir=$(mktemp -d) + lib=$(realpath "$lib") + if (cd "$tmpdir" && ar x "$lib" 2>/dev/null); then + (cd "$tmpdir" && ar Drcs repacked.a * && mv repacked.a "$lib") + fi + rm -rf "$tmpdir" +done < <(find "$DIR" -name '*.a' \ + \( -path '*/x86_64/*' -o -path '*/Darwin/*' -o -path '*/larch64/*' -o -path '*/aarch64/*' \) \ + -print0) + +echo -e "\033[32mAll third_party builds succeeded.\033[0m" diff --git a/third_party/libyuv/.gitignore b/third_party/libyuv/.gitignore index 450712e47d2..1e943ae6c6d 100644 --- a/third_party/libyuv/.gitignore +++ b/third_party/libyuv/.gitignore @@ -1 +1,2 @@ -libyuv/ +/libyuv/ +!*.a diff --git a/third_party/libyuv/build.sh b/third_party/libyuv/build.sh index b960f60ef5f..35a7d947a29 100755 --- a/third_party/libyuv/build.sh +++ b/third_party/libyuv/build.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -e +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" ARCHNAME=$(uname -m) diff --git a/third_party/libyuv/larch64/lib/libyuv.a b/third_party/libyuv/larch64/lib/libyuv.a index 1c91250231f..9c4a32bcdbe 100644 --- a/third_party/libyuv/larch64/lib/libyuv.a +++ b/third_party/libyuv/larch64/lib/libyuv.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:320bef5a75a62dd2731a496040921d5000f1ed237ae70fd7aeb6c010a1534363 +oid sha256:adafce26582e425164df7af36253ce58e3ed1dba9965650745c93bd96e42e976 size 462482 diff --git a/third_party/libyuv/x86_64/include b/third_party/libyuv/x86_64/include deleted file mode 120000 index f5030fe8899..00000000000 --- a/third_party/libyuv/x86_64/include +++ /dev/null @@ -1 +0,0 @@ -../include \ No newline at end of file diff --git a/third_party/libyuv/x86_64/lib/libyuv.a b/third_party/libyuv/x86_64/lib/libyuv.a index 8915f167dcf..391b1c87698 100644 --- a/third_party/libyuv/x86_64/lib/libyuv.a +++ b/third_party/libyuv/x86_64/lib/libyuv.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e21a3bd8df01cf4ce5461e7bf6654239196036c3f829255145265c7bf31a791d -size 511974 +oid sha256:00f9759c67c6fa21657fabde9e096478ea5809716989599f673f638f039431e5 +size 504790 diff --git a/third_party/raylib/.gitignore b/third_party/raylib/.gitignore index c4afad9c38a..6b1d3ad7482 100644 --- a/third_party/raylib/.gitignore +++ b/third_party/raylib/.gitignore @@ -1,3 +1,4 @@ /raylib_repo/ /raylib_python_repo/ /wheel/ +!*.a diff --git a/third_party/raylib/Darwin/libraylib.a b/third_party/raylib/Darwin/libraylib.a index 837ad8818e4..dd2e9b33f12 100644 --- a/third_party/raylib/Darwin/libraylib.a +++ b/third_party/raylib/Darwin/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ffe1fc6497f0c111fc507988e94fd29ce4db53a4876dc82ab9267895ad82584 -size 6515352 +oid sha256:fd045c1d4bca5c9b2ad044ea730826ff6cedeef0b64451b123717b136f1cd702 +size 6392532 diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index 7f2ce5951f0..d20f9d33af1 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -1,6 +1,9 @@ #!/usr/bin/env bash set -e +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + SUDO="" # Use sudo if not root diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a index 4e810c8b7b3..fa538e52143 100644 --- a/third_party/raylib/larch64/libraylib.a +++ b/third_party/raylib/larch64/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91e9a07513e84f7b553da01b34b24e12fe7130131ef73ebdb3dac3b838db815b +oid sha256:f760af8b4693cf60e3760341e5275890d78d933da2354c4bad0572ec575b970a size 2001860 diff --git a/third_party/raylib/x86_64/libraylib.a b/third_party/raylib/x86_64/libraylib.a index cf69482563e..ea124c1bcfe 100644 --- a/third_party/raylib/x86_64/libraylib.a +++ b/third_party/raylib/x86_64/libraylib.a @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f0b8f59758fe1291be82a8bda7a7ca05629c7addb0683936dd404ed08e19e143 -size 2769684 +oid sha256:3c928e849b51b04d8e3603cd649184299efed0e9e0fb02201612b967b31efd73 +size 2771092 From 9aca13cf2de5ef8b8db7f536d79bef7ee4909fb1 Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Mon, 9 Feb 2026 19:36:04 +0200 Subject: [PATCH 143/685] remove get_mcu_type from pandad.py (#37132) --- selfdrive/pandad/pandad.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index d75af283f2c..d5c58ddd6d1 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -6,16 +6,16 @@ import signal import subprocess -from panda import Panda, PandaDFU, PandaProtocolMismatch, FW_PATH +from panda import Panda, PandaDFU, PandaProtocolMismatch, McuType, FW_PATH from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE from openpilot.common.swaglog import cloudlog -def get_expected_signature(panda: Panda) -> bytes: +def get_expected_signature() -> bytes: try: - fn = os.path.join(FW_PATH, panda.get_mcu_type().config.app_fn) + fn = os.path.join(FW_PATH, McuType.H7.config.app_fn) return Panda.get_signature_from_firmware(fn) except Exception: cloudlog.exception("Error computing expected signature") @@ -29,7 +29,7 @@ def flash_panda(panda_serial: str) -> Panda: HARDWARE.recover_internal_panda() raise - fw_signature = get_expected_signature(panda) + fw_signature = get_expected_signature() internal_panda = panda.is_internal() panda_version = "bootstub" if panda.bootstub else panda.get_version() From a941e8f78f36f15d54645d70ab5289dffc509fbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 9 Feb 2026 15:29:50 -0800 Subject: [PATCH 144/685] Chunk big model files (#37134) * file chunking * try this * more cleanup * cleaner --- .gitignore | 2 +- common/file_chunker.py | 27 +++++++++++++++++++++++++++ selfdrive/modeld/SConscript | 12 ++++++++---- selfdrive/modeld/dmonitoringmodeld.py | 7 +++---- selfdrive/modeld/modeld.py | 11 ++++------- 5 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 common/file_chunker.py diff --git a/.gitignore b/.gitignore index 1fef0a12552..062801d7874 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,7 @@ flycheck_* cppcheck_report.txt comma*.sh -selfdrive/modeld/models/*.pkl +selfdrive/modeld/models/*.pkl* # openpilot log files *.bz2 diff --git a/common/file_chunker.py b/common/file_chunker.py new file mode 100644 index 00000000000..2a78963d1ef --- /dev/null +++ b/common/file_chunker.py @@ -0,0 +1,27 @@ +import glob +import os +import sys +from pathlib import Path + +CHUNK_SIZE = 49 * 1024 * 1024 # 49MB, under GitHub's 50MB limit + + +def chunk_file(path): + with open(path, 'rb') as f: + data = f.read() + for i in range(0, len(data), CHUNK_SIZE): + with open(f"{path}.chunk{i // CHUNK_SIZE:02d}", 'wb') as f: + f.write(data[i:i + CHUNK_SIZE]) + os.remove(path) + + +def read_file_chunked(path): + files = sorted(glob.glob(f"{path}.chunk*")) or ([path] if os.path.isfile(path) else []) + if not files: + raise FileNotFoundError(path) + return b''.join(Path(f).read_bytes() for f in files) + + +if __name__ == "__main__": + for path in sys.argv[1:]: + chunk_file(path) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 84e94df4a5d..a48c7957092 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -8,6 +8,8 @@ tinygrad_root = env.Dir("#").abspath tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] +chunk_cmd = f'python3 {Dir("#common").abspath}/file_chunker.py' + # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath @@ -24,21 +26,23 @@ image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +compile_warp_cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye warp_targets = [] for cam in [_ar_ox_fisheye, _os_fisheye]: w, h = cam.width, cam.height warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command(warp_targets, tinygrad_files + script_files, cmd) +lenv.Command([t + ".chunk00" for t in warp_targets], tinygrad_files + script_files, + f'{compile_warp_cmd} && {chunk_cmd} {" ".join(warp_targets)}') def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath + pkl = fn + "_tinygrad.pkl" return lenv.Command( - fn + "_tinygrad.pkl", + pkl + ".chunk00", [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl} && {chunk_cmd} {pkl}' ) # Compile small models diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 7d4df713c7f..b5cf2f71e92 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -16,6 +16,7 @@ from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" @@ -43,8 +44,7 @@ def __init__(self): self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.image_warp = None - with open(MODEL_PKL_PATH, "rb") as f: - self.model_run = pickle.load(f) + self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH))) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib @@ -54,8 +54,7 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple if self.image_warp is None: self.frame_buf_params = get_nv12_info(buf.width, buf.height) warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.image_warp = pickle.load(f) + self.image_warp = pickle.loads(read_file_chunked(str(warp_path))) self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() self.warp_inputs_np['transform'][:] = transform[:] diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 7fae3651095..f76d5b0810f 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -27,6 +27,7 @@ from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan @@ -177,11 +178,8 @@ def __init__(self): self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None - with open(VISION_PKL_PATH, "rb") as f: - self.vision_run = pickle.load(f) - - with open(POLICY_PKL_PATH, "rb") as f: - self.policy_run = pickle.load(f) + self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) + self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -198,8 +196,7 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], w, h = bufs[key].width, bufs[key].height self.frame_buf_params[key] = get_nv12_info(w, h) warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.update_imgs = pickle.load(f) + self.update_imgs = pickle.loads(read_file_chunked(str(warp_path))) for key in bufs.keys(): From a1202eaf2af9c240bb7e115b17607df86f5fc774 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Feb 2026 17:16:36 -0800 Subject: [PATCH 145/685] ui: delay nav bar animation (#37137) * from da bounce * try this * you can get it to clean up wow --- system/ui/widgets/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 5d474e8aedf..b3b1276a58f 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -242,6 +242,7 @@ def __init__(self): self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) self._playing_dismiss_animation = False self._trigger_animate_in = False + self._nav_bar_show_time = 0.0 self._back_enabled: bool | Callable[[], bool] = True self._nav_bar = NavBar() @@ -330,6 +331,7 @@ def _update_state(self): if self._trigger_animate_in: self._pos_filter.x = self._rect.height self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + self._nav_bar_show_time = rl.get_time() self._trigger_animate_in = False new_y = 0.0 @@ -366,8 +368,14 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: if self.back_enabled: bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 + nav_bar_delayed = rl.get_time() - self._nav_bar_show_time < 0.4 + # User dragging or dismissing, nav bar follows NavWidget if self._back_button_start_pos is not None or self._playing_dismiss_animation: self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._pos_filter.x + # Waiting to show + elif nav_bar_delayed: + self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + # Animate back to top else: self._nav_bar_y_filter.update(NAV_BAR_MARGIN) From 73f720220bf326d52e9ae644e4cabf634abb06f4 Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Mon, 9 Feb 2026 20:24:25 -0800 Subject: [PATCH 146/685] modeld: simplify model run processing (#37138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hi! The point of this pr is to make the model run easier to read. On the latest tinygrad numpy().flatten() empirically does the same thing as the internal contiguous().realize().uop.base.buffer.numpy(). numpy() is also documented (docstrings), which can assist new contributors in learning what each potential execution does. Torq_boi or yassine, I know you want proof in the code base, so here it is. As of tinygrad commit 2f55005: in tinygrad_repo/tinygrad/tensor.py Lines 316-318 (def _buffer): ensure the tenso is contiguous() and realized() before accessing the raw buffer. Line 378 (def numpy): Wraps the buffer access and adds a reshape to match the tensor shape. self._buffer() is what executes contiguous().realize() and returns the buffer object. Calling numpy() on that buffer object returns a 1D array (defined in tinygrad/device.py:193 via np.frombuffer). The reshape(self.shape) at the end of Tensor.numpy() then adds dimensions to that 1D array. The added .flatten() removes those dimensions, flattening it back to a 1D array. Effectively the same as what is currently done, but less complex. --- selfdrive/modeld/dmonitoringmodeld.py | 2 +- selfdrive/modeld/modeld.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index b5cf2f71e92..7cac17fa8ad 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -60,7 +60,7 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple self.warp_inputs_np['transform'][:] = transform[:] self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() - output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy() + output = self.model_run(**self.tensor_inputs).numpy().flatten() t2 = time.perf_counter() return output, t2 - t1 diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index f76d5b0810f..01052840ad1 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -211,7 +211,7 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], if prepare_only: return None - self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**vision_inputs).numpy().flatten() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -219,7 +219,7 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k] self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention'] - self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() + self.policy_output = self.policy_run(**self.policy_inputs).numpy().flatten() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: From 3d11e8ef362155cea1494b1a75bdb369c1c73d6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 9 Feb 2026 20:58:22 -0800 Subject: [PATCH 147/685] Revert "Chunk big model files (#37134)" (#37139) This reverts commit a941e8f78f36f15d54645d70ab5289dffc509fbb. --- .gitignore | 2 +- common/file_chunker.py | 27 --------------------------- selfdrive/modeld/SConscript | 12 ++++-------- selfdrive/modeld/dmonitoringmodeld.py | 7 ++++--- selfdrive/modeld/modeld.py | 11 +++++++---- 5 files changed, 16 insertions(+), 43 deletions(-) delete mode 100644 common/file_chunker.py diff --git a/.gitignore b/.gitignore index 062801d7874..1fef0a12552 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,7 @@ flycheck_* cppcheck_report.txt comma*.sh -selfdrive/modeld/models/*.pkl* +selfdrive/modeld/models/*.pkl # openpilot log files *.bz2 diff --git a/common/file_chunker.py b/common/file_chunker.py deleted file mode 100644 index 2a78963d1ef..00000000000 --- a/common/file_chunker.py +++ /dev/null @@ -1,27 +0,0 @@ -import glob -import os -import sys -from pathlib import Path - -CHUNK_SIZE = 49 * 1024 * 1024 # 49MB, under GitHub's 50MB limit - - -def chunk_file(path): - with open(path, 'rb') as f: - data = f.read() - for i in range(0, len(data), CHUNK_SIZE): - with open(f"{path}.chunk{i // CHUNK_SIZE:02d}", 'wb') as f: - f.write(data[i:i + CHUNK_SIZE]) - os.remove(path) - - -def read_file_chunked(path): - files = sorted(glob.glob(f"{path}.chunk*")) or ([path] if os.path.isfile(path) else []) - if not files: - raise FileNotFoundError(path) - return b''.join(Path(f).read_bytes() for f in files) - - -if __name__ == "__main__": - for path in sys.argv[1:]: - chunk_file(path) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index a48c7957092..84e94df4a5d 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -8,8 +8,6 @@ tinygrad_root = env.Dir("#").abspath tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] -chunk_cmd = f'python3 {Dir("#common").abspath}/file_chunker.py' - # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath @@ -26,23 +24,21 @@ image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -compile_warp_cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye warp_targets = [] for cam in [_ar_ox_fisheye, _os_fisheye]: w, h = cam.width, cam.height warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command([t + ".chunk00" for t in warp_targets], tinygrad_files + script_files, - f'{compile_warp_cmd} && {chunk_cmd} {" ".join(warp_targets)}') +lenv.Command(warp_targets, tinygrad_files + script_files, cmd) def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath - pkl = fn + "_tinygrad.pkl" return lenv.Command( - pkl + ".chunk00", + fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl} && {chunk_cmd} {pkl}' + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' ) # Compile small models diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 7cac17fa8ad..bc3adffffa8 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -16,7 +16,6 @@ from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.system.camerad.cameras.nv12_info import get_nv12_info -from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" @@ -44,7 +43,8 @@ def __init__(self): self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.image_warp = None - self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH))) + with open(MODEL_PKL_PATH, "rb") as f: + self.model_run = pickle.load(f) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib @@ -54,7 +54,8 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple if self.image_warp is None: self.frame_buf_params = get_nv12_info(buf.width, buf.height) warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - self.image_warp = pickle.loads(read_file_chunked(str(warp_path))) + with open(warp_path, "rb") as f: + self.image_warp = pickle.load(f) self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() self.warp_inputs_np['transform'][:] = transform[:] diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 01052840ad1..6f3c481ea23 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -27,7 +27,6 @@ from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState -from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan @@ -178,8 +177,11 @@ def __init__(self): self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None - self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) - self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) + with open(VISION_PKL_PATH, "rb") as f: + self.vision_run = pickle.load(f) + + with open(POLICY_PKL_PATH, "rb") as f: + self.policy_run = pickle.load(f) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -196,7 +198,8 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], w, h = bufs[key].width, bufs[key].height self.frame_buf_params[key] = get_nv12_info(w, h) warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - self.update_imgs = pickle.loads(read_file_chunked(str(warp_path))) + with open(warp_path, "rb") as f: + self.update_imgs = pickle.load(f) for key in bufs.keys(): From e35a1eca9931c342c1f9c0e6167f969a2748a545 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Mon, 9 Feb 2026 21:37:20 -0800 Subject: [PATCH 148/685] Process replay: move refs to ci-artifacts (#37049) * rm upload * use ci-artifacts * sanitize * rm ref_commit * add ci * handle exept * bootstrap * always * fix * replay * keep ref_commit fork compatibility * remove upload-only * apply comments * safe diffs in master * Revert "safe diffs in master" This reverts commit 369fccac786a67799193e9152488813c6df20414. * continue on master diff * Update .github/workflows/tests.yaml Co-authored-by: Shane Smiskol --------- Co-authored-by: Shane Smiskol --- .github/workflows/tests.yaml | 27 +++++++--- selfdrive/test/process_replay/README.md | 3 +- selfdrive/test/process_replay/ref_commit | 1 - .../test/process_replay/test_processes.py | 54 +++++++------------ 4 files changed, 41 insertions(+), 44 deletions(-) delete mode 100644 selfdrive/test/process_replay/ref_commit diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4ade42b665c..fe7c8ef3360 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -20,8 +20,6 @@ concurrency: env: PYTHONWARNINGS: error BASE_IMAGE: openpilot-base - AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }} - DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: selfdrive/test/docker_build.sh base @@ -185,12 +183,13 @@ jobs: uses: actions/cache@v5 with: path: .ci_cache/comma_download_cache - key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/ref_commit', 'selfdrive/test/process_replay/test_processes.py') }} + key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/test_processes.py') }} - name: Build openpilot run: | ${{ env.RUN }} "scons -j$(nproc)" - name: Run replay timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }} + continue-on-error: ${{ github.ref == 'refs/heads/master' }} run: | ${{ env.RUN }} "selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ chmod -R 777 /tmp/comma_download_cache" @@ -204,10 +203,26 @@ jobs: with: name: process_replay_diff.txt path: selfdrive/test/process_replay/diff.txt - - name: Upload reference logs - if: false # TODO: move this to github instead of azure + - name: Checkout ci-artifacts + if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' + uses: actions/checkout@v4 + with: + repository: commaai/ci-artifacts + ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} + path: ${{ github.workspace }}/ci-artifacts + - name: Push refs + if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' + working-directory: ${{ github.workspace }}/ci-artifacts run: | - ${{ env.RUN }} "unset PYTHONWARNINGS && AZURE_TOKEN='$AZURE_TOKEN' python3 selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" + git checkout --orphan process-replay + git rm -rf . + git config user.name "GitHub Actions Bot" + git config user.email "<>" + cp ${{ github.workspace }}/selfdrive/test/process_replay/fakedata/*.zst . + echo "${{ github.sha }}" > ref_commit + git add . + git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" + git push origin process-replay --force - name: Run regen if: false timeout-minutes: 4 diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index 8e279c71cd8..28f3b7cd2a5 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -22,7 +22,7 @@ Currently the following processes are tested: ### Usage ``` Usage: test_processes.py [-h] [--whitelist-procs PROCS] [--whitelist-cars CARS] [--blacklist-procs PROCS] - [--blacklist-cars CARS] [--ignore-fields FIELDS] [--ignore-msgs MSGS] [--update-refs] [--upload-only] + [--blacklist-cars CARS] [--ignore-fields FIELDS] [--ignore-msgs MSGS] [--update-refs] Regression test to identify changes in a process's output optional arguments: -h, --help show this help message and exit @@ -33,7 +33,6 @@ optional arguments: --ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. driverMonitoringState.events) --ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. onroadEvents) --update-refs Updates reference logs using current commit - --upload-only Skips testing processes and uploads logs from previous test run ``` ## Forks diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit deleted file mode 100644 index 85b79391c3a..00000000000 --- a/selfdrive/test/process_replay/ref_commit +++ /dev/null @@ -1 +0,0 @@ -67f3daf309dc6cbb6844fcbaeb83e6596637e551 \ No newline at end of file diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 59e1ae054e3..9f79c8ddcb5 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -9,12 +9,13 @@ from opendbc.car.car_helpers import interface_names from openpilot.common.git import get_commit -from openpilot.tools.lib.openpilotci import get_url, upload_file +from openpilot.tools.lib.openpilotci import get_url from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process, \ check_most_messages_valid from openpilot.tools.lib.filereader import FileReader from openpilot.tools.lib.logreader import LogReader, save_log +from openpilot.tools.lib.url_file import URLFile source_segments = [ ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.HYUNDAI_SONATA @@ -64,25 +65,17 @@ # dashcamOnly makes don't need to be tested until a full port is done excluded_interfaces = ["mock", "body", "psa"] -BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" +BASE_URL = "https://raw.githubusercontent.com/commaai/ci-artifacts/refs/heads/process-replay/" REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit") EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"} def run_test_process(data): segment, cfg, args, cur_log_fn, ref_log_path, lr_dat = data - res = None - if not args.upload_only: - lr = LogReader.from_bytes(lr_dat) - res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) - # save logs so we can upload when updating refs - save_log(cur_log_fn, log_msgs) - - if args.update_refs or args.upload_only: - print(f'Uploading: {os.path.basename(cur_log_fn)}') - assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}" - upload_file(cur_log_fn, os.path.basename(cur_log_fn)) - os.remove(cur_log_fn) + lr = LogReader.from_bytes(lr_dat) + res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) + # save logs so we can update refs + save_log(cur_log_fn, log_msgs) return (segment, cfg.proc_name, res) @@ -142,8 +135,6 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non help="Msgs to ignore (e.g. carEvents)") parser.add_argument("--update-refs", action="store_true", help="Updates reference logs using current commit") - parser.add_argument("--upload-only", action="store_true", - help="Skips testing processes and uploads logs from previous test run") parser.add_argument("-j", "--jobs", type=int, default=max(cpu_count - 2, 1), help="Max amount of parallel jobs") args = parser.parse_args() @@ -153,18 +144,16 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non tested_cars = {c.upper() for c in tested_cars} full_test = (tested_procs == all_procs) and (tested_cars == all_cars) and all(len(x) == 0 for x in (args.ignore_fields, args.ignore_msgs)) - upload = args.update_refs or args.upload_only os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True) - if upload: + if args.update_refs: assert full_test, "Need to run full test when updating refs" try: with open(REF_COMMIT_FN) as f: ref_commit = f.read().strip() except FileNotFoundError: - print("Couldn't find reference commit") - sys.exit(1) + ref_commit = URLFile(BASE_URL + "ref_commit", cache=False).read().decode().strip() cur_commit = get_commit() if not cur_commit: @@ -179,12 +168,11 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non log_paths: defaultdict[str, dict[str, dict[str, str]]] = defaultdict(lambda: defaultdict(dict)) with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: - if not args.upload_only: - download_segments = [seg for car, seg in segments if car in tested_cars] - log_data: dict[str, LogReader] = {} - p1 = pool.map(get_log_data, download_segments) - for segment, lr in tqdm(p1, desc="Getting Logs", total=len(download_segments)): - log_data[segment] = lr + download_segments = [seg for car, seg in segments if car in tested_cars] + log_data: dict[str, LogReader] = {} + p1 = pool.map(get_log_data, download_segments) + for segment, lr in tqdm(p1, desc="Getting Logs", total=len(download_segments)): + log_data[segment] = lr pool_args: Any = [] for car_brand, segment in segments: @@ -199,15 +187,14 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non if cfg.proc_name not in ('card', 'controlsd', 'lagd') and car_brand not in ('HYUNDAI', 'TOYOTA'): continue - cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst") + cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst".replace("|", "_")) if args.update_refs: # reference logs will not exist if routes were just regenerated ref_log_path = get_url(*segment.rsplit("--", 1,), "rlog.zst") else: - ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.zst") + ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.zst".replace("|", "_")) ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn) - dat = None if args.upload_only else log_data[segment] - pool_args.append((segment, cfg, args, cur_log_fn, ref_log_path, dat)) + pool_args.append((segment, cfg, args, cur_log_fn, ref_log_path, log_data[segment])) log_paths[segment][cfg.proc_name]['ref'] = ref_log_path log_paths[segment][cfg.proc_name]['new'] = cur_log_fn @@ -215,19 +202,16 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non results: Any = defaultdict(dict) p2 = pool.map(run_test_process, pool_args) for (segment, proc, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)): - if not args.upload_only: - results[segment][proc] = result + results[segment][proc] = result diff_short, diff_long, failed = format_diff(results, log_paths, ref_commit) - if not upload: + if not args.update_refs: with open(os.path.join(PROC_REPLAY_DIR, "diff.txt"), "w") as f: f.write(diff_long) print(diff_short) if failed: print("TEST FAILED") - print("\n\nTo push the new reference logs for this commit run:") - print("./test_processes.py --upload-only") else: print("TEST SUCCEEDED") From 053441fbb3b4a6f400fccecfd47f3fddbca04f09 Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Tue, 10 Feb 2026 19:19:37 +0200 Subject: [PATCH 149/685] fix first-interaction action inputs for v3 (#37144) v3 renamed inputs from kebab-case to snake_case (repo-token -> repo_token, pr-message -> pr_message). The old names were silently ignored, causing "Input required and not supplied: issue_message" errors. --- .github/workflows/auto_pr_review.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 725154d21fa..cf12360e6e1 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -39,8 +39,8 @@ jobs: uses: actions/first-interaction@v3 if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - pr-message: | + repo_token: ${{ secrets.GITHUB_TOKEN }} + pr_message: | Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following: * Convert your PR to a draft unless it's ready to review From 9476a8a7f609e251263908d5f9a2f31b71c15697 Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Tue, 10 Feb 2026 19:19:56 +0200 Subject: [PATCH 150/685] bump create-pull-request action to v8.1.0 (#37143) The pinned SHA was v6.0.4, which is incompatible with actions/checkout@v6 and causes a "Duplicate header: Authorization" 400 error during git remote operations. See peter-evans/create-pull-request#4272. --- .github/workflows/repo-maintenance.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 810b602d711..f88db3eaf84 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -21,7 +21,7 @@ jobs: run: | ${{ env.RUN }} "python3 selfdrive/ui/update_translations.py --vanish" - name: Create Pull Request - uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 with: author: Vehicle Researcher commit-message: "Update translations" @@ -59,7 +59,7 @@ jobs: python selfdrive/car/docs.py git add docs/CARS.md - name: Create Pull Request - uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 with: author: Vehicle Researcher token: ${{ secrets.ACTIONS_CREATE_PR_PAT }} From 43edc51cb6591b63fd1994c91a2a7ec977e36bfa Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Tue, 10 Feb 2026 19:20:52 +0200 Subject: [PATCH 151/685] bump numpy to 2.4.2 (#37145) --- uv.lock | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/uv.lock b/uv.lock index 81e2d913602..83805c646f6 100644 --- a/uv.lock +++ b/uv.lock @@ -958,21 +958,21 @@ wheels = [ [[package]] name = "numpy" -version = "2.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/62/ae72ff66c0f1fd959925b4c11f8c2dea61f47f6acaea75a08512cdfe3fed/numpy-2.4.1.tar.gz", hash = "sha256:a1ceafc5042451a858231588a104093474c6a5c57dcc724841f5c888d237d690", size = 20721320, upload-time = "2026-01-10T06:44:59.619Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/7f/ec53e32bf10c813604edf07a3682616bd931d026fcde7b6d13195dfb684a/numpy-2.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d3703409aac693fa82c0aee023a1ae06a6e9d065dba10f5e8e80f642f1e9d0a2", size = 16656888, upload-time = "2026-01-10T06:42:40.913Z" }, - { url = "https://files.pythonhosted.org/packages/b8/e0/1f9585d7dae8f14864e948fd7fa86c6cb72dee2676ca2748e63b1c5acfe0/numpy-2.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7211b95ca365519d3596a1d8688a95874cc94219d417504d9ecb2df99fa7bfa8", size = 12373956, upload-time = "2026-01-10T06:42:43.091Z" }, - { url = "https://files.pythonhosted.org/packages/8e/43/9762e88909ff2326f5e7536fa8cb3c49fb03a7d92705f23e6e7f553d9cb3/numpy-2.4.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:5adf01965456a664fc727ed69cc71848f28d063217c63e1a0e200a118d5eec9a", size = 5202567, upload-time = "2026-01-10T06:42:45.107Z" }, - { url = "https://files.pythonhosted.org/packages/4b/ee/34b7930eb61e79feb4478800a4b95b46566969d837546aa7c034c742ef98/numpy-2.4.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:26f0bcd9c79a00e339565b303badc74d3ea2bd6d52191eeca5f95936cad107d0", size = 6549459, upload-time = "2026-01-10T06:42:48.152Z" }, - { url = "https://files.pythonhosted.org/packages/79/e3/5f115fae982565771be994867c89bcd8d7208dbfe9469185497d70de5ddf/numpy-2.4.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0093e85df2960d7e4049664b26afc58b03236e967fb942354deef3208857a04c", size = 14404859, upload-time = "2026-01-10T06:42:49.947Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7d/9c8a781c88933725445a859cac5d01b5871588a15969ee6aeb618ba99eee/numpy-2.4.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7ad270f438cbdd402c364980317fb6b117d9ec5e226fff5b4148dd9aa9fc6e02", size = 16371419, upload-time = "2026-01-10T06:42:52.409Z" }, - { url = "https://files.pythonhosted.org/packages/a6/d2/8aa084818554543f17cf4162c42f162acbd3bb42688aefdba6628a859f77/numpy-2.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:297c72b1b98100c2e8f873d5d35fb551fce7040ade83d67dd51d38c8d42a2162", size = 16182131, upload-time = "2026-01-10T06:42:54.694Z" }, - { url = "https://files.pythonhosted.org/packages/60/db/0425216684297c58a8df35f3284ef56ec4a043e6d283f8a59c53562caf1b/numpy-2.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cf6470d91d34bf669f61d515499859fa7a4c2f7c36434afb70e82df7217933f9", size = 18295342, upload-time = "2026-01-10T06:42:56.991Z" }, - { url = "https://files.pythonhosted.org/packages/31/4c/14cb9d86240bd8c386c881bafbe43f001284b7cce3bc01623ac9475da163/numpy-2.4.1-cp312-cp312-win32.whl", hash = "sha256:b6bcf39112e956594b3331316d90c90c90fb961e39696bda97b89462f5f3943f", size = 5959015, upload-time = "2026-01-10T06:42:59.631Z" }, - { url = "https://files.pythonhosted.org/packages/51/cf/52a703dbeb0c65807540d29699fef5fda073434ff61846a564d5c296420f/numpy-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:e1a27bb1b2dee45a2a53f5ca6ff2d1a7f135287883a1689e930d44d1ff296c87", size = 12310730, upload-time = "2026-01-10T06:43:01.627Z" }, - { url = "https://files.pythonhosted.org/packages/69/80/a828b2d0ade5e74a9fe0f4e0a17c30fdc26232ad2bc8c9f8b3197cf7cf18/numpy-2.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:0e6e8f9d9ecf95399982019c01223dc130542960a12edfa8edd1122dfa66a8a8", size = 10312166, upload-time = "2026-01-10T06:43:03.673Z" }, +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, ] [[package]] From 7d2563880afd61351d3462367df35d224df4cac2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Feb 2026 09:31:50 -0800 Subject: [PATCH 152/685] show dependency tree in weekly uv lock job (#37146) --- .github/workflows/repo-maintenance.yaml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index f88db3eaf84..ec361536d82 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -47,6 +47,12 @@ jobs: python3 -m ensurepip --upgrade pip3 install uv uv lock --upgrade + - name: uv pip tree + id: pip_tree + run: | + echo 'PIP_TREE<> $GITHUB_OUTPUT + uv pip tree >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT - name: bump submodules run: | git config --global --add safe.directory '*' @@ -68,5 +74,10 @@ jobs: branch: auto-package-updates base: master delete-branch: true - body: 'Automatic PR from repo-maintenance -> package_updates' + body: | + Automatic PR from repo-maintenance -> package_updates + + ``` + ${{ steps.pip_tree.outputs.PIP_TREE }} + ``` labels: bot From e946e9de0b5a7c1be296d88336fad37155f683f8 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 10 Feb 2026 13:56:07 -0800 Subject: [PATCH 153/685] Revert "DM: Ford GT model" (#37148) Revert "DM: Ford GT model (#37013)" This reverts commit 1459d3519da2fdb2d981baf7811c2eaa2127eb80. --- cereal/log.capnp | 4 +-- .../modeld/models/dmonitoring_model.onnx | 4 +-- selfdrive/monitoring/helpers.py | 25 ++++++++++++++++--- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 119cf299999..afb710e808f 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2232,9 +2232,9 @@ struct DriverMonitoringState @0xb83cda094a1da284 { isActiveMode @16 :Bool; isRHD @4 :Bool; uncertainCount @19 :UInt32; + phoneProbOffset @20 :Float32; + phoneProbValidCount @21 :UInt32; - phoneProbOffsetDEPRECATED @20 :Float32; - phoneProbValidCountDEPRECATED @21 :UInt32; isPreviewDEPRECATED @15 :Bool; rhdCheckedDEPRECATED @5 :Bool; eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED); diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 4052a154818..9b1c4a18347 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:35e4a5d4c4d481f915e42358af4665b2c92b8f5c1efd1c0731f21b876ad1d856 -size 6954249 +oid sha256:3446bf8b22e50e47669a25bf32460ae8baf8547037f346753e19ecbfcf6d4e59 +size 6954368 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 0b54504b647..3377ce6c683 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -35,7 +35,14 @@ def __init__(self, device_type): self._EYE_THRESHOLD = 0.65 self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - self._PHONE_THRESH = 0.5 + + self._PHONE_THRESH = 0.75 if device_type == 'mici' else 0.4 + self._PHONE_THRESH2 = 15.0 + self._PHONE_MAX_OFFSET = 0.06 + self._PHONE_MIN_OFFSET = 0.025 + self._PHONE_DATA_AVG = 0.05 + self._PHONE_DATA_VAR = 3*0.005 + self._PHONE_MAX_COUNT = int(360 / self._DT_DMON) self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 @@ -145,10 +152,11 @@ def __init__(self, rhd_saved=False, settings=None, always_on=False): # init driver status wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) + phone_filter_raw_priors = (self.settings._PHONE_DATA_AVG, self.settings._PHONE_DATA_VAR, 2) self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) + self.phone = DriverProb(raw_priors=phone_filter_raw_priors, max_trackable=self.settings._PHONE_MAX_COUNT) self.pose = DriverPose(settings=self.settings) self.blink = DriverBlink() - self.phone_prob = 0. self.always_on = always_on self.distracted_types = [] @@ -249,7 +257,12 @@ def _get_distracted_types(self): if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) - if self.phone_prob > self.settings._PHONE_THRESH: + if self.phone.prob_calibrated: + using_phone = self.phone.prob > max(min(self.phone.prob_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ + * self.settings._PHONE_THRESH2 + else: + using_phone = self.phone.prob > self.settings._PHONE_THRESH + if using_phone: distracted_types.append(DistractedType.DISTRACTED_PHONE) return distracted_types @@ -288,7 +301,7 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.phone_prob = driver_data.phoneProb + self.phone.prob = driver_data.phoneProb self.distracted_types = self._get_distracted_types() self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types @@ -302,9 +315,11 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) + self.phone.prob_offseter.push_and_update(self.phone.prob) self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT + self.phone.prob_calibrated = self.phone.prob_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: @@ -410,6 +425,8 @@ def get_state_packet(self, valid=True): "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, + "phoneProbOffset": self.phone.prob_offseter.filtered_stat.mean(), + "phoneProbValidCount": self.phone.prob_offseter.filtered_stat.n, "stepChange": self.step_change, "awarenessActive": self.awareness_active, "awarenessPassive": self.awareness_passive, From ba67e468abb681ab73f2a7e4fd1cdf80bd6d2fdf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 14:53:25 -0800 Subject: [PATCH 154/685] remove dead multilang for mici (#37150) --- selfdrive/ui/mici/layouts/settings/device.py | 23 +------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index f8dba659d2c..f4b6217ac53 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -1,6 +1,5 @@ import os import threading -import json import pyray as rl from enum import IntEnum from collections.abc import Callable @@ -11,7 +10,7 @@ from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton -from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide @@ -303,22 +302,6 @@ def uninstall_openpilot_callback(): self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True, icon_size=(64, 66)) self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) - self._load_languages() - - def language_callback(): - def selected_language_callback(): - selected_language = dlg.get_selected_option() - ui_state.params.put("LanguageSetting", self._languages[selected_language]) - - current_language_name = ui_state.params.get("LanguageSetting") - current_language = next(name for name, lang in self._languages.items() if lang == current_language_name) - - dlg = BigMultiOptionDialog(list(self._languages), default=current_language, right_btn_callback=selected_language_callback) - gui_app.set_modal_overlay(dlg) - - # lang_button = BigButton("change language", "", "icons_mici/settings/device/language.png") - # lang_button.set_click_callback(language_callback) - regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") regulatory_btn.set_click_callback(self._on_regulatory) @@ -371,10 +354,6 @@ def completed_callback(): self._training_guide = TrainingGuide(completed_callback=completed_callback) gui_app.set_modal_overlay(self._training_guide, callback=lambda result: setattr(self, '_training_guide', None)) - def _load_languages(self): - with open(os.path.join(BASEDIR, "selfdrive/ui/translations/languages.json")) as f: - self._languages = json.load(f) - def show_event(self): super().show_event() self._scroller.show_event() From 4d3aeaba6d03d0e7e28907e77d167366093c6d5b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 15:04:36 -0800 Subject: [PATCH 155/685] ui: remove dead side button (#37151) * rm side button * fix * fix --- .../icons_mici/buttons/button_side_back.png | 3 -- .../buttons/button_side_back_pressed.png | 3 -- .../icons_mici/buttons/button_side_check.png | 3 -- .../buttons/button_side_check_pressed.png | 3 -- .../mici/layouts/settings/network/wifi_ui.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 35 ++++--------------- selfdrive/ui/mici/widgets/side_button.py | 31 ---------------- 7 files changed, 7 insertions(+), 73 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/buttons/button_side_back.png delete mode 100644 selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png delete mode 100644 selfdrive/assets/icons_mici/buttons/button_side_check.png delete mode 100644 selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png delete mode 100644 selfdrive/ui/mici/widgets/side_button.py diff --git a/selfdrive/assets/icons_mici/buttons/button_side_back.png b/selfdrive/assets/icons_mici/buttons/button_side_back.png deleted file mode 100644 index 3d648d34f1a..00000000000 --- a/selfdrive/assets/icons_mici/buttons/button_side_back.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9df44871e9f5fa910622b0b92205b92a54d137dbdc3827b92e8622d85ff2e08e -size 5189 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png b/selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png deleted file mode 100644 index e431cb0c739..00000000000 --- a/selfdrive/assets/icons_mici/buttons/button_side_back_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:013b368b38b17d9b2ef6aaf0f498f672deed95888084b7287f42bdfba617cbb6 -size 10142 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_check.png b/selfdrive/assets/icons_mici/buttons/button_side_check.png deleted file mode 100644 index 820b2360665..00000000000 --- a/selfdrive/assets/icons_mici/buttons/button_side_check.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8fd563eec78d5ce4a8204c2f596789e1090cb3e26a35b4ffeacee4ab61968538 -size 8303 diff --git a/selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png b/selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png deleted file mode 100644 index 6c38508af95..00000000000 --- a/selfdrive/assets/icons_mici/buttons/button_side_check_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0be8d5eddcd9f87acbf1daccf446be6218522120f64aee1ee0a3c0b31560f076 -size 15761 diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 7791f18cf73..db68632c0e7 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -318,7 +318,7 @@ class WifiUIMici(BigMultiOptionDialog): INACTIVITY_TIMEOUT = 1 def __init__(self, wifi_manager: WifiManager, back_callback: Callable): - super().__init__([], None, None, right_btn_callback=None) + super().__init__([], None) # Set up back navigation self.set_back_callback(back_callback) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 49d73f7b0d4..b88ac204941 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -14,7 +14,6 @@ from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.ui.mici.widgets.button import BigButton -from openpilot.selfdrive.ui.mici.widgets.side_button import SideButton DEBUG = False @@ -22,32 +21,17 @@ class BigDialogBase(NavWidget, abc.ABC): - def __init__(self, right_btn: str | None = None, right_btn_callback: Callable | None = None): + def __init__(self): super().__init__() self._ret = DialogResult.NO_ACTION self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) self.set_back_callback(lambda: setattr(self, '_ret', DialogResult.CANCEL)) - self._right_btn = None - if right_btn: - def right_btn_callback_wrapper(): - gui_app.set_modal_overlay(None) - if right_btn_callback: - right_btn_callback() - - self._right_btn = SideButton(right_btn) - self._right_btn.set_click_callback(right_btn_callback_wrapper) - # move to right side - self._right_btn._rect.x = self._rect.x + self._rect.width - self._right_btn._rect.width - def _render(self, _) -> DialogResult: """ Allows `gui_app.set_modal_overlay(BigDialog(...))`. The overlay runner keeps calling until result != NO_ACTION. """ - if self._right_btn: - self._right_btn.set_position(self._right_btn._rect.x, self._rect.y) - self._right_btn.render() return self._ret @@ -55,10 +39,8 @@ def _render(self, _) -> DialogResult: class BigDialog(BigDialogBase): def __init__(self, title: str, - description: str, - right_btn: str | None = None, - right_btn_callback: Callable | None = None): - super().__init__(right_btn, right_btn_callback) + description: str): + super().__init__() self._title = title self._description = description @@ -70,8 +52,6 @@ def _render(self, _) -> DialogResult: # TODO: coming up with these numbers manually is a pain and not scalable # TODO: no clue what any of these numbers mean. VBox and HBox would remove all of this shite max_width = self._rect.width - PADDING * 2 - if self._right_btn: - max_width -= self._right_btn._rect.width title_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.BOLD), self._title, 50, int(max_width))) title_size = measure_text_cached(gui_app.font(FontWeight.BOLD), title_wrapped, 50) @@ -139,7 +119,7 @@ def __init__(self, default_text: str = "", minimum_length: int = 1, confirm_callback: Callable[[str], None] | None = None): - super().__init__(None, None) + super().__init__() self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)), font_weight=FontWeight.MEDIUM) self._keyboard = MiciKeyboard() @@ -310,9 +290,8 @@ def _render(self, _): class BigMultiOptionDialog(BigDialogBase): BACK_TOUCH_AREA_PERCENTAGE = 0.1 - def __init__(self, options: list[str], default: str | None, - right_btn: str | None = 'check', right_btn_callback: Callable[[], None] | None = None): - super().__init__(right_btn, right_btn_callback=right_btn_callback) + def __init__(self, options: list[str], default: str | None): + super().__init__() self._options = options if default is not None: assert default in options @@ -325,8 +304,6 @@ def __init__(self, options: list[str], default: str | None, self._can_click = True self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) - if self._right_btn is not None: - self._scroller.set_enabled(lambda: not cast(Widget, self._right_btn).is_pressed) for option in options: self._scroller.add_widget(BigDialogOptionButton(option)) diff --git a/selfdrive/ui/mici/widgets/side_button.py b/selfdrive/ui/mici/widgets/side_button.py deleted file mode 100644 index 4803b6d208c..00000000000 --- a/selfdrive/ui/mici/widgets/side_button.py +++ /dev/null @@ -1,31 +0,0 @@ -import pyray as rl -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.lib.application import gui_app - -# --------------------------------------------------------------------------- -# Constants extracted from the original Qt style -# --------------------------------------------------------------------------- -# TODO: this should be corrected, but Scroller relies on this being incorrect :/ -WIDTH, HEIGHT = 112, 240 - - -class SideButton(Widget): - def __init__(self, btn_type: str): - super().__init__() - self.type = btn_type - self.set_rect(rl.Rectangle(0, 0, WIDTH, HEIGHT)) - - # load pre-rendered button images - if btn_type not in ("check", "back"): - btn_type = "back" - btn_img_path = f"icons_mici/buttons/button_side_{btn_type}.png" - btn_img_pressed_path = f"icons_mici/buttons/button_side_{btn_type}_pressed.png" - self._txt_btn, self._txt_btn_back = gui_app.texture(btn_img_path, 100, 224), gui_app.texture(btn_img_pressed_path, 100, 224) - - def _render(self, _) -> bool: - x = int(self._rect.x + 12) - y = int(self._rect.y + (self._rect.height - self._txt_btn.height) / 2) - rl.draw_texture(self._txt_btn if not self.is_pressed else self._txt_btn_back, - x, y, rl.WHITE) - - return False From 46ae67b6071c3983eb4cb0dc46bb3af832c88e1d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 17:15:59 -0800 Subject: [PATCH 156/685] BigButton: fix alignment and style (#37153) * correct from bottom alignment * temp * fix scale animation w/ btn_y * home settings are always 64 * cleanup * some clean up * make 23 const * rev * more --- selfdrive/ui/mici/layouts/settings/device.py | 3 ++ .../ui/mici/layouts/settings/settings.py | 15 ++++--- selfdrive/ui/mici/widgets/button.py | 44 +++++++++---------- 3 files changed, 35 insertions(+), 27 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index f4b6217ac53..60eb7e5b011 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -120,6 +120,9 @@ class PairBigButton(BigButton): def __init__(self): super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png", icon_size=(33, 60)) + def _get_label_font_size(self): + return 64 + def _update_state(self): if ui_state.prime_state.is_paired(): self.set_text("paired") diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 3917899032e..a6f59a0389a 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -30,22 +30,27 @@ class PanelInfo: instance: Widget +class SettingsBigButton(BigButton): + def _get_label_font_size(self): + return 64 + + class SettingsLayout(NavWidget): def __init__(self): super().__init__() self._params = Params() self._current_panel = None # PanelType.DEVICE - toggles_btn = BigButton("toggles", "", "icons_mici/settings.png") + toggles_btn = SettingsBigButton("toggles", "", "icons_mici/settings.png") toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES)) - network_btn = BigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) + network_btn = SettingsBigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK)) - device_btn = BigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) + device_btn = SettingsBigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) device_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVICE)) - developer_btn = BigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) + developer_btn = SettingsBigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) developer_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVELOPER)) - firehose_btn = BigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) + firehose_btn = SettingsBigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) firehose_btn.set_click_callback(lambda: self._set_current_panel(PanelType.FIREHOSE)) self._scroller = Scroller([ diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index a5f7ee686ed..56dd3f7a23a 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -17,6 +17,7 @@ COMPLICATION_SIZE = 36 LABEL_COLOR = rl.Color(255, 255, 255, int(255 * 0.9)) LABEL_HORIZONTAL_PADDING = 40 +LABEL_VERTICAL_PADDING = 23 # visually matches 30 in figma COMPLICATION_GREY = rl.Color(0xAA, 0xAA, 0xAA, 255) PRESSED_SCALE = 1.15 if DO_ZOOM else 1.07 @@ -118,14 +119,12 @@ def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "" self._rotate_icon_t: float | None = None - self._label_font = gui_app.font(FontWeight.DISPLAY) - self._value_font = gui_app.font(FontWeight.ROMAN) - - self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.DISPLAY, + self._label = UnifiedLabel(text, font_size=self._get_label_font_size(), font_weight=FontWeight.BOLD, text_color=LABEL_COLOR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, scroll=scroll, line_height=0.9) self._sub_label = UnifiedLabel(value, font_size=COMPLICATION_SIZE, font_weight=FontWeight.ROMAN, text_color=COMPLICATION_GREY, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + self._update_label_layout() self._load_images() @@ -149,25 +148,27 @@ def _width_hint(self) -> int: return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): - if len(self.text) < 12: - font_size = 64 - elif len(self.text) < 17: - font_size = 48 + if len(self.text) <= 18: + return 48 else: - font_size = 42 + return 42 + def _update_label_layout(self): + self._label.set_font_size(self._get_label_font_size()) if self.value: - font_size -= 20 - - return font_size + self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP) + else: + self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) def set_text(self, text: str): self.text = text self._label.set_text(text) + self._update_label_layout() def set_value(self, value: str): self.value = value self._sub_label.set_text(value) + self._update_label_layout() def get_value(self) -> str: return self.value @@ -189,21 +190,20 @@ def _render(self, _): rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) # LABEL ------------------------------------------------------------------ - lx = self._rect.x + LABEL_HORIZONTAL_PADDING - ly = btn_y + self._rect.height - 33 # - 40# - self._get_label_font_size() / 2 - - if self.value: - sub_label_height = self._sub_label.get_content_height(self._width_hint()) - sub_label_rect = rl.Rectangle(lx, ly - sub_label_height, self._width_hint(), sub_label_height) - self._sub_label.render(sub_label_rect) - ly -= sub_label_height + label_x = self._rect.x + LABEL_HORIZONTAL_PADDING label_color = LABEL_COLOR if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) self._label.set_color(label_color) - label_height = self._label.get_content_height(self._width_hint()) - label_rect = rl.Rectangle(lx, ly - label_height, self._width_hint(), label_height) + label_rect = rl.Rectangle(label_x, btn_y + LABEL_VERTICAL_PADDING, self._width_hint(), + self._rect.height - LABEL_VERTICAL_PADDING * 2) self._label.render(label_rect) + if self.value: + label_y = btn_y + self._rect.height - LABEL_VERTICAL_PADDING + sub_label_height = self._sub_label.get_content_height(self._width_hint()) + sub_label_rect = rl.Rectangle(label_x, label_y - sub_label_height, self._width_hint(), sub_label_height) + self._sub_label.render(sub_label_rect) + # ICON ------------------------------------------------------------------- if self._txt_icon: rotation = 0 From a18ddf12eb8b2d38894415bc5f07ea8049e28e8f Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 10 Feb 2026 17:51:09 -0800 Subject: [PATCH 157/685] remove azure deps (#37084) * remove azure deps * fix lint * restore scripts --- pyproject.toml | 2 - .../test/process_replay/test_processes.py | 3 +- tools/lib/openpilotci.py | 14 +-- uv.lock | 88 ------------------- 4 files changed, 5 insertions(+), 102 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8b0f4004d68..b1295ef8137 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,8 +97,6 @@ testing = [ dev = [ "av", - "azure-identity", - "azure-storage-blob", "dictdiffer", "matplotlib", "opencv-python-headless", diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 9f79c8ddcb5..fbe300a7c9a 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -189,7 +189,8 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non cur_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{cur_commit}.zst".replace("|", "_")) if args.update_refs: # reference logs will not exist if routes were just regenerated - ref_log_path = get_url(*segment.rsplit("--", 1,), "rlog.zst") + route, seg_num = segment.rsplit("--", 1) + ref_log_path = get_url(route, seg_num, "rlog.zst") else: ref_log_fn = os.path.join(FAKEDATA, f"{segment}_{cfg.proc_name}_{ref_commit}.zst".replace("|", "_")) ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn) diff --git a/tools/lib/openpilotci.py b/tools/lib/openpilotci.py index 1c1e1f171b7..6b484dfdada 100644 --- a/tools/lib/openpilotci.py +++ b/tools/lib/openpilotci.py @@ -1,12 +1,4 @@ -from openpilot.tools.lib.openpilotcontainers import OpenpilotCIContainer +BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/" -def get_url(*args, **kwargs): - return OpenpilotCIContainer.get_url(*args, **kwargs) - -def upload_file(*args, **kwargs): - return OpenpilotCIContainer.upload_file(*args, **kwargs) - -def upload_bytes(*args, **kwargs): - return OpenpilotCIContainer.upload_bytes(*args, **kwargs) - -BASE_URL = OpenpilotCIContainer.BASE_URL +def get_url(route_name: str, segment_num, filename: str) -> str: + return BASE_URL + f"{route_name.replace('|', '/')}/{segment_num}/{filename}" diff --git a/uv.lock b/uv.lock index 83805c646f6..d4c75f2e563 100644 --- a/uv.lock +++ b/uv.lock @@ -119,50 +119,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/ff/48fa68888b8d5bae36d915556ff18f9e5fdc6b5ff5ae23dc4904c9713168/av-13.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ea0deab0e6a739cb742fba2a3983d8102f7516a3cdf3c46669f3cac0ed1f351", size = 25781343, upload-time = "2024-10-06T04:53:29.577Z" }, ] -[[package]] -name = "azure-core" -version = "1.38.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "requests" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/dc/1b/e503e08e755ea94e7d3419c9242315f888fc664211c90d032e40479022bf/azure_core-1.38.0.tar.gz", hash = "sha256:8194d2682245a3e4e3151a667c686464c3786fed7918b394d035bdcd61bb5993", size = 363033, upload-time = "2026-01-12T17:03:05.535Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/d8/b8fcba9464f02b121f39de2db2bf57f0b216fe11d014513d666e8634380d/azure_core-1.38.0-py3-none-any.whl", hash = "sha256:ab0c9b2cd71fecb1842d52c965c95285d3cfb38902f6766e4a471f1cd8905335", size = 217825, upload-time = "2026-01-12T17:03:07.291Z" }, -] - -[[package]] -name = "azure-identity" -version = "1.25.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "msal" }, - { name = "msal-extensions" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/06/8d/1a6c41c28a37eab26dc85ab6c86992c700cd3f4a597d9ed174b0e9c69489/azure_identity-1.25.1.tar.gz", hash = "sha256:87ca8328883de6036443e1c37b40e8dc8fb74898240f61071e09d2e369361456", size = 279826, upload-time = "2025-10-06T20:30:02.194Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/7b/5652771e24fff12da9dde4c20ecf4682e606b104f26419d139758cc935a6/azure_identity-1.25.1-py3-none-any.whl", hash = "sha256:e9edd720af03dff020223cd269fa3a61e8f345ea75443858273bcb44844ab651", size = 191317, upload-time = "2025-10-06T20:30:04.251Z" }, -] - -[[package]] -name = "azure-storage-blob" -version = "12.28.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "azure-core" }, - { name = "cryptography" }, - { name = "isodate" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/24/072ba8e27b0e2d8fec401e9969b429d4f5fc4c8d4f0f05f4661e11f7234a/azure_storage_blob-12.28.0.tar.gz", hash = "sha256:e7d98ea108258d29aa0efbfd591b2e2075fa1722a2fae8699f0b3c9de11eff41", size = 604225, upload-time = "2026-01-06T23:48:57.282Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/3a/6ef2047a072e54e1142718d433d50e9514c999a58f51abfff7902f3a72f8/azure_storage_blob-12.28.0-py3-none-any.whl", hash = "sha256:00fb1db28bf6a7b7ecaa48e3b1d5c83bfadacc5a678b77826081304bd87d6461", size = 431499, upload-time = "2026-01-06T23:48:58.995Z" }, -] - [[package]] name = "casadi" version = "3.7.2" @@ -595,15 +551,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/94/040a0d9c81f018c39bd887b7b825013b024deb0a6c795f9524797e2cd41b/inputs-0.5-py2.py3-none-any.whl", hash = "sha256:13f894564e52134cf1e3862b1811da034875eb1f2b62e6021e3776e9669a96ec", size = 33630, upload-time = "2018-10-05T22:38:28.28Z" }, ] -[[package]] -name = "isodate" -version = "0.7.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, -] - [[package]] name = "jeepney" version = "0.9.0" @@ -903,32 +850,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, ] -[[package]] -name = "msal" -version = "1.34.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "pyjwt", extra = ["crypto"] }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" }, -] - -[[package]] -name = "msal-extensions" -version = "1.3.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "msal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315, upload-time = "2025-03-14T23:51:03.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" }, -] - [[package]] name = "multidict" version = "6.7.1" @@ -1058,8 +979,6 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "av" }, - { name = "azure-identity" }, - { name = "azure-storage-blob" }, { name = "dictdiffer" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, @@ -1097,8 +1016,6 @@ requires-dist = [ { name = "aiohttp" }, { name = "aiortc" }, { name = "av", marker = "extra == 'dev'" }, - { name = "azure-identity", marker = "extra == 'dev'" }, - { name = "azure-storage-blob", marker = "extra == 'dev'" }, { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, @@ -1448,11 +1365,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, ] -[package.optional-dependencies] -crypto = [ - { name = "cryptography" }, -] - [[package]] name = "pylibsrtp" version = "1.0.0" From 6892b62761d9009c9028f51ab5b90d9460386ed7 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Tue, 10 Feb 2026 20:48:34 -0800 Subject: [PATCH 158/685] [bot] Update Python packages (#37147) * Update Python packages * fix * bump panda * revert tinygrad --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- cereal/messaging/bridge.cc | 6 +- cereal/messaging/bridge_zmq.h | 12 +- cereal/messaging/msgq_to_zmq.cc | 2 +- cereal/messaging/msgq_to_zmq.h | 2 +- msgq_repo | 2 +- opendbc_repo | 2 +- panda | 2 +- uv.lock | 209 ++++++++++++++++---------------- 8 files changed, 119 insertions(+), 118 deletions(-) diff --git a/cereal/messaging/bridge.cc b/cereal/messaging/bridge.cc index 2eb3de32c0d..77823c413ac 100644 --- a/cereal/messaging/bridge.cc +++ b/cereal/messaging/bridge.cc @@ -26,12 +26,12 @@ void msgq_to_zmq(const std::vector &endpoints, const std::string &i void zmq_to_msgq(const std::vector &endpoints, const std::string &ip) { auto poller = std::make_unique(); - auto pub_context = std::make_unique(); + auto pub_context = std::make_unique(); auto sub_context = std::make_unique(); - std::map sub2pub; + std::map sub2pub; for (auto endpoint : endpoints) { - auto pub_sock = new MSGQPubSocket(); + auto pub_sock = new PubSocket(); auto sub_sock = new BridgeZmqSubSocket(); size_t queue_size = services.at(endpoint).queue_size; pub_sock->connect(pub_context.get(), endpoint, true, queue_size); diff --git a/cereal/messaging/bridge_zmq.h b/cereal/messaging/bridge_zmq.h index eab48a91e8c..ebdbc56c245 100644 --- a/cereal/messaging/bridge_zmq.h +++ b/cereal/messaging/bridge_zmq.h @@ -20,12 +20,12 @@ class BridgeZmqContext { class BridgeZmqMessage : public Message { public: - void init(size_t size) override; - void init(char *data, size_t size) override; - void close() override; - size_t getSize() override { return size; } - char *getData() override { return data; } - ~BridgeZmqMessage() override; + void init(size_t size); + void init(char *data, size_t size); + void close(); + size_t getSize() { return size; } + char *getData() { return data; } + ~BridgeZmqMessage(); private: char *data = nullptr; diff --git a/cereal/messaging/msgq_to_zmq.cc b/cereal/messaging/msgq_to_zmq.cc index 930b617e7b0..5e7ea222739 100644 --- a/cereal/messaging/msgq_to_zmq.cc +++ b/cereal/messaging/msgq_to_zmq.cc @@ -23,7 +23,7 @@ static std::string recv_zmq_msg(void *sock) { void MsgqToZmq::run(const std::vector &endpoints, const std::string &ip) { zmq_context = std::make_unique(); - msgq_context = std::make_unique(); + msgq_context = std::make_unique(); // Create ZMQPubSockets for each endpoint for (const auto &endpoint : endpoints) { diff --git a/cereal/messaging/msgq_to_zmq.h b/cereal/messaging/msgq_to_zmq.h index ac92631fe7c..64f3a2173e7 100644 --- a/cereal/messaging/msgq_to_zmq.h +++ b/cereal/messaging/msgq_to_zmq.h @@ -26,7 +26,7 @@ class MsgqToZmq { int connected_clients = 0; }; - std::unique_ptr msgq_context; + std::unique_ptr msgq_context; std::unique_ptr zmq_context; std::mutex mutex; std::condition_variable cv; diff --git a/msgq_repo b/msgq_repo index f9ebdca885c..2c191c1a72a 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit f9ebdca885cfe25295d09de9fd57023a10758de5 +Subproject commit 2c191c1a72ae8119b93b49e1bc12d4a99b751855 diff --git a/opendbc_repo b/opendbc_repo index 05348982cb7..143e87f3e45 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 05348982cb7b6bc6fed73611ac26ecda658194fc +Subproject commit 143e87f3e4565abd1d2d70c32adcb3167d9c64ca diff --git a/panda b/panda index 81615ad9d53..b99d7969240 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 81615ad9d53aef5583e064f340e9cdeb23d4119c +Subproject commit b99d79692409514d296d85f367897fa2f86daaa5 diff --git a/uv.lock b/uv.lock index d4c75f2e563..816733d12ef 100644 --- a/uv.lock +++ b/uv.lock @@ -256,24 +256,26 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.2" +version = "7.13.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ad/49/349848445b0e53660e258acbcc9b0d014895b6739237920886672240f84b/coverage-7.13.2.tar.gz", hash = "sha256:044c6951ec37146b72a50cc81ef02217d27d4c3640efd2640311393cbbf143d3", size = 826523, upload-time = "2026-01-25T13:00:04.889Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/39/e92a35f7800222d3f7b2cbb7bbc3b65672ae8d501cb31801b2d2bd7acdf1/coverage-7.13.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f106b2af193f965d0d3234f3f83fc35278c7fb935dfbde56ae2da3dd2c03b84d", size = 219142, upload-time = "2026-01-25T12:58:00.448Z" }, - { url = "https://files.pythonhosted.org/packages/45/7a/8bf9e9309c4c996e65c52a7c5a112707ecdd9fbaf49e10b5a705a402bbb4/coverage-7.13.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f45d21dc4d5d6bd29323f0320089ef7eae16e4bef712dff79d184fa7330af3", size = 219503, upload-time = "2026-01-25T12:58:02.451Z" }, - { url = "https://files.pythonhosted.org/packages/87/93/17661e06b7b37580923f3f12406ac91d78aeed293fb6da0b69cc7957582f/coverage-7.13.2-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:fae91dfecd816444c74531a9c3d6ded17a504767e97aa674d44f638107265b99", size = 251006, upload-time = "2026-01-25T12:58:04.059Z" }, - { url = "https://files.pythonhosted.org/packages/12/f0/f9e59fb8c310171497f379e25db060abef9fa605e09d63157eebec102676/coverage-7.13.2-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:264657171406c114787b441484de620e03d8f7202f113d62fcd3d9688baa3e6f", size = 253750, upload-time = "2026-01-25T12:58:05.574Z" }, - { url = "https://files.pythonhosted.org/packages/e5/b1/1935e31add2232663cf7edd8269548b122a7d100047ff93475dbaaae673e/coverage-7.13.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae47d8dcd3ded0155afbb59c62bd8ab07ea0fd4902e1c40567439e6db9dcaf2f", size = 254862, upload-time = "2026-01-25T12:58:07.647Z" }, - { url = "https://files.pythonhosted.org/packages/af/59/b5e97071ec13df5f45da2b3391b6cdbec78ba20757bc92580a5b3d5fa53c/coverage-7.13.2-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8a0b33e9fd838220b007ce8f299114d406c1e8edb21336af4c97a26ecfd185aa", size = 251420, upload-time = "2026-01-25T12:58:09.309Z" }, - { url = "https://files.pythonhosted.org/packages/3f/75/9495932f87469d013dc515fb0ce1aac5fa97766f38f6b1a1deb1ee7b7f3a/coverage-7.13.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b3becbea7f3ce9a2d4d430f223ec15888e4deb31395840a79e916368d6004cce", size = 252786, upload-time = "2026-01-25T12:58:10.909Z" }, - { url = "https://files.pythonhosted.org/packages/6a/59/af550721f0eb62f46f7b8cb7e6f1860592189267b1c411a4e3a057caacee/coverage-7.13.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f819c727a6e6eeb8711e4ce63d78c620f69630a2e9d53bc95ca5379f57b6ba94", size = 250928, upload-time = "2026-01-25T12:58:12.449Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b1/21b4445709aae500be4ab43bbcfb4e53dc0811c3396dcb11bf9f23fd0226/coverage-7.13.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:4f7b71757a3ab19f7ba286e04c181004c1d61be921795ee8ba6970fd0ec91da5", size = 250496, upload-time = "2026-01-25T12:58:14.047Z" }, - { url = "https://files.pythonhosted.org/packages/ba/b1/0f5d89dfe0392990e4f3980adbde3eb34885bc1effb2dc369e0bf385e389/coverage-7.13.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b7fc50d2afd2e6b4f6f2f403b70103d280a8e0cb35320cbbe6debcda02a1030b", size = 252373, upload-time = "2026-01-25T12:58:15.976Z" }, - { url = "https://files.pythonhosted.org/packages/01/c9/0cf1a6a57a9968cc049a6b896693faa523c638a5314b1fc374eb2b2ac904/coverage-7.13.2-cp312-cp312-win32.whl", hash = "sha256:292250282cf9bcf206b543d7608bda17ca6fc151f4cbae949fc7e115112fbd41", size = 221696, upload-time = "2026-01-25T12:58:17.517Z" }, - { url = "https://files.pythonhosted.org/packages/4d/05/d7540bf983f09d32803911afed135524570f8c47bb394bf6206c1dc3a786/coverage-7.13.2-cp312-cp312-win_amd64.whl", hash = "sha256:eeea10169fac01549a7921d27a3e517194ae254b542102267bef7a93ed38c40e", size = 222504, upload-time = "2026-01-25T12:58:19.115Z" }, - { url = "https://files.pythonhosted.org/packages/15/8b/1a9f037a736ced0a12aacf6330cdaad5008081142a7070bc58b0f7930cbc/coverage-7.13.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a5b567f0b635b592c917f96b9a9cb3dbd4c320d03f4bf94e9084e494f2e8894", size = 221120, upload-time = "2026-01-25T12:58:21.334Z" }, - { url = "https://files.pythonhosted.org/packages/d2/db/d291e30fdf7ea617a335531e72294e0c723356d7fdde8fba00610a76bda9/coverage-7.13.2-py3-none-any.whl", hash = "sha256:40ce1ea1e25125556d8e76bd0b61500839a07944cc287ac21d5626f3e620cad5", size = 210943, upload-time = "2026-01-25T13:00:02.388Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [[package]] @@ -661,11 +663,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.10.1" +version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b7/b1/af95bcae8549f1f3fd70faacb29075826a0d689a27f232e8cee315efa053/markdown-3.10.1.tar.gz", hash = "sha256:1c19c10bd5c14ac948c53d0d762a04e2fa35a6d58a6b7b1e6bfcbe6fefc0001a", size = 365402, upload-time = "2026-01-21T18:09:28.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/1b/6ef961f543593969d25b2afe57a3564200280528caa9bd1082eecdd7b3bc/markdown-3.10.1-py3-none-any.whl", hash = "sha256:867d788939fe33e4b736426f5b9f651ad0c0ae0ecf89df0ca5d1176c70812fe3", size = 107684, upload-time = "2026-01-21T18:09:27.203Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] @@ -918,20 +920,20 @@ wheels = [ [[package]] name = "opencv-python-headless" -version = "4.13.0.90" +version = "4.13.0.92" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/76/38c4cbb5ccfce7aaf36fd9be9fc74a15c85a48ef90bfaca2049b486e10c5/opencv_python_headless-4.13.0.90-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:12a28674f215542c9bf93338de1b5bffd76996d32da9acb9e739fdb9c8bbd738", size = 46020414, upload-time = "2026-01-18T09:07:10.801Z" }, - { url = "https://files.pythonhosted.org/packages/93/c5/4b40daa5003b45aa8397f160324a091ed323733e2446dc0bdf3655e77b84/opencv_python_headless-4.13.0.90-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:32255203040dc98803be96362e13f9e4bce20146898222d2e5c242f80de50da5", size = 32568519, upload-time = "2026-01-18T09:07:52.368Z" }, - { url = "https://files.pythonhosted.org/packages/da/65/920e64a7f03cf5917cd2c6a3046293843c1a16ad89f0ed0f1c683979c9de/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e13790342591557050157713af17a7435ac1b50c65282715093c9297fa045d8f", size = 35191272, upload-time = "2026-01-18T09:08:49.235Z" }, - { url = "https://files.pythonhosted.org/packages/fc/13/af150685be342dc09bfb0824e2a280020ccf1c7fc64e15a31d9209016aa9/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dbc1f4625e5af3a80ebdbd84380227c0f445228588f2521b11af47710caca1ba", size = 57683677, upload-time = "2026-01-18T09:10:23.588Z" }, - { url = "https://files.pythonhosted.org/packages/cd/47/baab2a3b6d8da8c52e73d00207d1ed3155601c2c332ea855455b3fbc8ff4/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eba38bc255d0b7d1969c5bcc90a060ca2b61a3403b613872c750bfa5dfe9e03b", size = 36590019, upload-time = "2026-01-18T09:10:49.053Z" }, - { url = "https://files.pythonhosted.org/packages/81/a1/facfe2801a861b424c4221d66e1281cf19735c00e07f063a337a208c11b5/opencv_python_headless-4.13.0.90-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f46b17ea0aa7e4124ca6ad71143f89233ae9557f61d2326bcdb34329a1ddf9bd", size = 62535926, upload-time = "2026-01-18T09:12:47.229Z" }, - { url = "https://files.pythonhosted.org/packages/06/d2/5e9ee7512306c1caa518be929d1f44bb1c189f342f538f73bea6fb94919f/opencv_python_headless-4.13.0.90-cp37-abi3-win32.whl", hash = "sha256:96060fc57a1abb1144b0b8129e2ff3bfcdd0ccd8e8bd05bd85256ff4ed587d3b", size = 30811665, upload-time = "2026-01-18T09:13:44.517Z" }, - { url = "https://files.pythonhosted.org/packages/a0/09/0a4d832448dccd03b2b1bdee70b9fc2e02c147cc7e06975e9cd729569d90/opencv_python_headless-4.13.0.90-cp37-abi3-win_amd64.whl", hash = "sha256:0e0c8c9f620802fddc4fa7f471a1d263c7b0dca16cd9e7e2f996bb8bd2128c0c", size = 40070035, upload-time = "2026-01-18T09:15:14.652Z" }, + { url = "https://files.pythonhosted.org/packages/79/42/2310883be3b8826ac58c3f2787b9358a2d46923d61f88fedf930bc59c60c/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_13_0_arm64.whl", hash = "sha256:1a7d040ac656c11b8c38677cc8cccdc149f98535089dbe5b081e80a4e5903209", size = 46247192, upload-time = "2026-02-05T07:01:35.187Z" }, + { url = "https://files.pythonhosted.org/packages/2d/1e/6f9e38005a6f7f22af785df42a43139d0e20f169eb5787ce8be37ee7fcc9/opencv_python_headless-4.13.0.92-cp37-abi3-macosx_14_0_x86_64.whl", hash = "sha256:3e0a6f0a37994ec6ce5f59e936be21d5d6384a4556f2d2da9c2f9c5dc948394c", size = 32568914, upload-time = "2026-02-05T07:01:51.989Z" }, + { url = "https://files.pythonhosted.org/packages/21/76/9417a6aef9def70e467a5bf560579f816148a4c658b7d525581b356eda9e/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c8cfc8e87ed452b5cecb9419473ee5560a989859fe1d10d1ce11ae87b09a2cb", size = 33703709, upload-time = "2026-02-05T10:24:46.469Z" }, + { url = "https://files.pythonhosted.org/packages/92/ce/bd17ff5772938267fd49716e94ca24f616ff4cb1ff4c6be13085108037be/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0525a3d2c0b46c611e2130b5fdebc94cf404845d8fa64d2f3a3b679572a5bd22", size = 56016764, upload-time = "2026-02-05T10:26:48.904Z" }, + { url = "https://files.pythonhosted.org/packages/8f/b4/b7bcbf7c874665825a8c8e1097e93ea25d1f1d210a3e20d4451d01da30aa/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:eb60e36b237b1ebd40a912da5384b348df8ed534f6f644d8e0b4f103e272ba7d", size = 35010236, upload-time = "2026-02-05T10:28:11.031Z" }, + { url = "https://files.pythonhosted.org/packages/4b/33/b5db29a6c00eb8f50708110d8d453747ca125c8b805bc437b289dbdcc057/opencv_python_headless-4.13.0.92-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0bd48544f77c68b2941392fcdf9bcd2b9cdf00e98cb8c29b2455d194763cf99e", size = 60391106, upload-time = "2026-02-05T10:30:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c3/52cfea47cd33e53e8c0fbd6e7c800b457245c1fda7d61660b4ffe9596a7f/opencv_python_headless-4.13.0.92-cp37-abi3-win32.whl", hash = "sha256:a7cf08e5b191f4ebb530791acc0825a7986e0d0dee2a3c491184bd8599848a4b", size = 30812232, upload-time = "2026-02-05T07:02:29.594Z" }, + { url = "https://files.pythonhosted.org/packages/4a/90/b338326131ccb2aaa3c2c85d00f41822c0050139a4bfe723cfd95455bd2d/opencv_python_headless-4.13.0.92-cp37-abi3-win_amd64.whl", hash = "sha256:77a82fe35ddcec0f62c15f2ba8a12ecc2ed4207c17b0902c7a3151ae29f37fb6", size = 40070414, upload-time = "2026-02-05T07:02:26.448Z" }, ] [[package]] @@ -1135,11 +1137,11 @@ wheels = [ [[package]] name = "pathspec" -version = "1.0.3" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/b2/bb8e495d5262bfec41ab5cb18f522f1012933347fb5d9e62452d446baca2/pathspec-1.0.3.tar.gz", hash = "sha256:bac5cf97ae2c2876e2d25ebb15078eb04d76e4b98921ee31c6f85ade8b59444d", size = 130841, upload-time = "2026-01-09T15:46:46.009Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/32/2b/121e912bd60eebd623f873fd090de0e84f322972ab25a7f9044c056804ed/pathspec-1.0.3-py3-none-any.whl", hash = "sha256:e80767021c1cc524aa3fb14bedda9c34406591343cc42797b386ce7b9354fb6c", size = 55021, upload-time = "2026-01-09T15:46:44.652Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] @@ -1223,33 +1225,33 @@ wheels = [ [[package]] name = "protobuf" -version = "6.33.4" +version = "6.33.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/b8/cda15d9d46d03d4aa3a67cb6bffe05173440ccf86a9541afaf7ac59a1b6b/protobuf-6.33.4.tar.gz", hash = "sha256:dc2e61bca3b10470c1912d166fe0af67bfc20eb55971dcef8dfa48ce14f0ed91", size = 444346, upload-time = "2026-01-12T18:33:40.109Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/be/24ef9f3095bacdf95b458543334d0c4908ccdaee5130420bf064492c325f/protobuf-6.33.4-cp310-abi3-win32.whl", hash = "sha256:918966612c8232fc6c24c78e1cd89784307f5814ad7506c308ee3cf86662850d", size = 425612, upload-time = "2026-01-12T18:33:29.656Z" }, - { url = "https://files.pythonhosted.org/packages/31/ad/e5693e1974a28869e7cd244302911955c1cebc0161eb32dfa2b25b6e96f0/protobuf-6.33.4-cp310-abi3-win_amd64.whl", hash = "sha256:8f11ffae31ec67fc2554c2ef891dcb561dae9a2a3ed941f9e134c2db06657dbc", size = 436962, upload-time = "2026-01-12T18:33:31.345Z" }, - { url = "https://files.pythonhosted.org/packages/66/15/6ee23553b6bfd82670207ead921f4d8ef14c107e5e11443b04caeb5ab5ec/protobuf-6.33.4-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:2fe67f6c014c84f655ee06f6f66213f9254b3a8b6bda6cda0ccd4232c73c06f0", size = 427612, upload-time = "2026-01-12T18:33:32.646Z" }, - { url = "https://files.pythonhosted.org/packages/2b/48/d301907ce6d0db75f959ca74f44b475a9caa8fcba102d098d3c3dd0f2d3f/protobuf-6.33.4-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:757c978f82e74d75cba88eddec479df9b99a42b31193313b75e492c06a51764e", size = 324484, upload-time = "2026-01-12T18:33:33.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/1c/e53078d3f7fe710572ab2dcffd993e1e3b438ae71cfc031b71bae44fcb2d/protobuf-6.33.4-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c7c64f259c618f0bef7bee042075e390debbf9682334be2b67408ec7c1c09ee6", size = 339256, upload-time = "2026-01-12T18:33:35.231Z" }, - { url = "https://files.pythonhosted.org/packages/e8/8e/971c0edd084914f7ee7c23aa70ba89e8903918adca179319ee94403701d5/protobuf-6.33.4-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:3df850c2f8db9934de4cf8f9152f8dc2558f49f298f37f90c517e8e5c84c30e9", size = 323311, upload-time = "2026-01-12T18:33:36.305Z" }, - { url = "https://files.pythonhosted.org/packages/75/b1/1dc83c2c661b4c62d56cc081706ee33a4fc2835bd90f965baa2663ef7676/protobuf-6.33.4-py3-none-any.whl", hash = "sha256:1fe3730068fcf2e595816a6c34fe66eeedd37d51d0400b72fabc848811fdc1bc", size = 170532, upload-time = "2026-01-12T18:33:39.199Z" }, + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, ] [[package]] name = "psutil" -version = "7.2.1" +version = "7.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/cb/09e5184fb5fc0358d110fc3ca7f6b1d033800734d34cac10f4136cfac10e/psutil-7.2.1.tar.gz", hash = "sha256:f7583aec590485b43ca601dd9cea0dcd65bd7bb21d30ef4ddbf4ea6b5ed1bdd3", size = 490253, upload-time = "2025-12-29T08:26:00.169Z" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/cf/5180eb8c8bdf6a503c6919f1da28328bd1e6b3b1b5b9d5b01ae64f019616/psutil-7.2.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b2e953fcfaedcfbc952b44744f22d16575d3aa78eb4f51ae74165b4e96e55f42", size = 128137, upload-time = "2025-12-29T08:26:27.759Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2c/78e4a789306a92ade5000da4f5de3255202c534acdadc3aac7b5458fadef/psutil-7.2.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:05cc68dbb8c174828624062e73078e7e35406f4ca2d0866c272c2410d8ef06d1", size = 128947, upload-time = "2025-12-29T08:26:29.548Z" }, - { url = "https://files.pythonhosted.org/packages/29/f8/40e01c350ad9a2b3cb4e6adbcc8a83b17ee50dd5792102b6142385937db5/psutil-7.2.1-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e38404ca2bb30ed7267a46c02f06ff842e92da3bb8c5bfdadbd35a5722314d8", size = 154694, upload-time = "2025-12-29T08:26:32.147Z" }, - { url = "https://files.pythonhosted.org/packages/06/e4/b751cdf839c011a9714a783f120e6a86b7494eb70044d7d81a25a5cd295f/psutil-7.2.1-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ab2b98c9fc19f13f59628d94df5cc4cc4844bc572467d113a8b517d634e362c6", size = 156136, upload-time = "2025-12-29T08:26:34.079Z" }, - { url = "https://files.pythonhosted.org/packages/44/ad/bbf6595a8134ee1e94a4487af3f132cef7fce43aef4a93b49912a48c3af7/psutil-7.2.1-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:f78baafb38436d5a128f837fab2d92c276dfb48af01a240b861ae02b2413ada8", size = 148108, upload-time = "2025-12-29T08:26:36.225Z" }, - { url = "https://files.pythonhosted.org/packages/1c/15/dd6fd869753ce82ff64dcbc18356093471a5a5adf4f77ed1f805d473d859/psutil-7.2.1-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:99a4cd17a5fdd1f3d014396502daa70b5ec21bf4ffe38393e152f8e449757d67", size = 147402, upload-time = "2025-12-29T08:26:39.21Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/d9317542e3f2b180c4306e3f45d3c922d7e86d8ce39f941bb9e2e9d8599e/psutil-7.2.1-cp37-abi3-win_amd64.whl", hash = "sha256:b1b0671619343aa71c20ff9767eced0483e4fc9e1f489d50923738caf6a03c17", size = 136938, upload-time = "2025-12-29T08:26:41.036Z" }, - { url = "https://files.pythonhosted.org/packages/3e/73/2ce007f4198c80fcf2cb24c169884f833fe93fbc03d55d302627b094ee91/psutil-7.2.1-cp37-abi3-win_arm64.whl", hash = "sha256:0d67c1822c355aa6f7314d92018fb4268a76668a536f133599b91edd48759442", size = 133836, upload-time = "2025-12-29T08:26:43.086Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] @@ -1358,11 +1360,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, ] [[package]] @@ -4067,28 +4069,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/06/f71e3a86b2df0dfa2d2f72195941cd09b44f87711cb7fa5193732cb9a5fc/ruff-0.14.14.tar.gz", hash = "sha256:2d0f819c9a90205f3a867dbbd0be083bee9912e170fd7d9704cc8ae45824896b", size = 4515732, upload-time = "2026-01-22T22:30:17.527Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/89/20a12e97bc6b9f9f68343952da08a8099c57237aef953a56b82711d55edd/ruff-0.14.14-py3-none-linux_armv6l.whl", hash = "sha256:7cfe36b56e8489dee8fbc777c61959f60ec0f1f11817e8f2415f429552846aed", size = 10467650, upload-time = "2026-01-22T22:30:08.578Z" }, - { url = "https://files.pythonhosted.org/packages/a3/b1/c5de3fd2d5a831fcae21beda5e3589c0ba67eec8202e992388e4b17a6040/ruff-0.14.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6006a0082336e7920b9573ef8a7f52eec837add1265cc74e04ea8a4368cd704c", size = 10883245, upload-time = "2026-01-22T22:30:04.155Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7c/3c1db59a10e7490f8f6f8559d1db8636cbb13dccebf18686f4e3c9d7c772/ruff-0.14.14-py3-none-macosx_11_0_arm64.whl", hash = "sha256:026c1d25996818f0bf498636686199d9bd0d9d6341c9c2c3b62e2a0198b758de", size = 10231273, upload-time = "2026-01-22T22:30:34.642Z" }, - { url = "https://files.pythonhosted.org/packages/a1/6e/5e0e0d9674be0f8581d1f5e0f0a04761203affce3232c1a1189d0e3b4dad/ruff-0.14.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f666445819d31210b71e0a6d1c01e24447a20b85458eea25a25fe8142210ae0e", size = 10585753, upload-time = "2026-01-22T22:30:31.781Z" }, - { url = "https://files.pythonhosted.org/packages/23/09/754ab09f46ff1884d422dc26d59ba18b4e5d355be147721bb2518aa2a014/ruff-0.14.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c0f18b922c6d2ff9a5e6c3ee16259adc513ca775bcf82c67ebab7cbd9da5bc8", size = 10286052, upload-time = "2026-01-22T22:30:24.827Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cc/e71f88dd2a12afb5f50733851729d6b571a7c3a35bfdb16c3035132675a0/ruff-0.14.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1629e67489c2dea43e8658c3dba659edbfd87361624b4040d1df04c9740ae906", size = 11043637, upload-time = "2026-01-22T22:30:13.239Z" }, - { url = "https://files.pythonhosted.org/packages/67/b2/397245026352494497dac935d7f00f1468c03a23a0c5db6ad8fc49ca3fb2/ruff-0.14.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:27493a2131ea0f899057d49d303e4292b2cae2bb57253c1ed1f256fbcd1da480", size = 12194761, upload-time = "2026-01-22T22:30:22.542Z" }, - { url = "https://files.pythonhosted.org/packages/5b/06/06ef271459f778323112c51b7587ce85230785cd64e91772034ddb88f200/ruff-0.14.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ff589aab3f5b539e35db38425da31a57521efd1e4ad1ae08fc34dbe30bd7df", size = 12005701, upload-time = "2026-01-22T22:30:20.499Z" }, - { url = "https://files.pythonhosted.org/packages/41/d6/99364514541cf811ccc5ac44362f88df66373e9fec1b9d1c4cc830593fe7/ruff-0.14.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1cc12d74eef0f29f51775f5b755913eb523546b88e2d733e1d701fe65144e89b", size = 11282455, upload-time = "2026-01-22T22:29:59.679Z" }, - { url = "https://files.pythonhosted.org/packages/ca/71/37daa46f89475f8582b7762ecd2722492df26421714a33e72ccc9a84d7a5/ruff-0.14.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb8481604b7a9e75eff53772496201690ce2687067e038b3cc31aaf16aa0b974", size = 11215882, upload-time = "2026-01-22T22:29:57.032Z" }, - { url = "https://files.pythonhosted.org/packages/2c/10/a31f86169ec91c0705e618443ee74ede0bdd94da0a57b28e72db68b2dbac/ruff-0.14.14-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14649acb1cf7b5d2d283ebd2f58d56b75836ed8c6f329664fa91cdea19e76e66", size = 11180549, upload-time = "2026-01-22T22:30:27.175Z" }, - { url = "https://files.pythonhosted.org/packages/fd/1e/c723f20536b5163adf79bdd10c5f093414293cdf567eed9bdb7b83940f3f/ruff-0.14.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8058d2145566510790eab4e2fad186002e288dec5e0d343a92fe7b0bc1b3e13", size = 10543416, upload-time = "2026-01-22T22:30:01.964Z" }, - { url = "https://files.pythonhosted.org/packages/3e/34/8a84cea7e42c2d94ba5bde1d7a4fae164d6318f13f933d92da6d7c2041ff/ruff-0.14.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e651e977a79e4c758eb807f0481d673a67ffe53cfa92209781dfa3a996cf8412", size = 10285491, upload-time = "2026-01-22T22:30:29.51Z" }, - { url = "https://files.pythonhosted.org/packages/55/ef/b7c5ea0be82518906c978e365e56a77f8de7678c8bb6651ccfbdc178c29f/ruff-0.14.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:cc8b22da8d9d6fdd844a68ae937e2a0adf9b16514e9a97cc60355e2d4b219fc3", size = 10733525, upload-time = "2026-01-22T22:30:06.499Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/aaf1dfbcc53a2811f6cc0a1759de24e4b03e02ba8762daabd9b6bd8c59e3/ruff-0.14.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:16bc890fb4cc9781bb05beb5ab4cd51be9e7cb376bf1dd3580512b24eb3fda2b", size = 11315626, upload-time = "2026-01-22T22:30:36.848Z" }, - { url = "https://files.pythonhosted.org/packages/2c/aa/9f89c719c467dfaf8ad799b9bae0df494513fb21d31a6059cb5870e57e74/ruff-0.14.14-py3-none-win32.whl", hash = "sha256:b530c191970b143375b6a68e6f743800b2b786bbcf03a7965b06c4bf04568167", size = 10502442, upload-time = "2026-01-22T22:30:38.93Z" }, - { url = "https://files.pythonhosted.org/packages/87/44/90fa543014c45560cae1fffc63ea059fb3575ee6e1cb654562197e5d16fb/ruff-0.14.14-py3-none-win_amd64.whl", hash = "sha256:3dde1435e6b6fe5b66506c1dff67a421d0b7f6488d466f651c07f4cab3bf20fd", size = 11630486, upload-time = "2026-01-22T22:30:10.852Z" }, - { url = "https://files.pythonhosted.org/packages/9e/6a/40fee331a52339926a92e17ae748827270b288a35ef4a15c9c8f2ec54715/ruff-0.14.14-py3-none-win_arm64.whl", hash = "sha256:56e6981a98b13a32236a72a8da421d7839221fa308b223b9283312312e5ac76c", size = 10920448, upload-time = "2026-01-22T22:30:15.417Z" }, +version = "0.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, + { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, + { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, + { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, + { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, + { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, + { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, + { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, + { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, + { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, + { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, + { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, + { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, + { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, ] [[package]] @@ -4102,15 +4103,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.50.0" +version = "2.52.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/8a/3c4f53d32c21012e9870913544e56bfa9e931aede080779a0f177513f534/sentry_sdk-2.50.0.tar.gz", hash = "sha256:873437a989ee1b8b25579847bae8384515bf18cfed231b06c591b735c1781fe3", size = 401233, upload-time = "2026-01-20T12:53:16.244Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/eb/1b497650eb564701f9a7b8a95c51b2abe9347ed2c0b290ba78f027ebe4ea/sentry_sdk-2.52.0.tar.gz", hash = "sha256:fa0bec872cfec0302970b2996825723d67390cdd5f0229fb9efed93bd5384899", size = 410273, upload-time = "2026-02-04T15:03:54.706Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/5b/cbc2bb9569f03c8e15d928357e7e6179e5cfab45544a3bbac8aec4caf9be/sentry_sdk-2.50.0-py2.py3-none-any.whl", hash = "sha256:0ef0ed7168657ceb5a0be081f4102d92042a125462d1d1a29277992e344e749e", size = 424961, upload-time = "2026-01-20T12:53:14.826Z" }, + { url = "https://files.pythonhosted.org/packages/ca/63/2c6daf59d86b1c30600bff679d039f57fd1932af82c43c0bde1cbc55e8d4/sentry_sdk-2.52.0-py2.py3-none-any.whl", hash = "sha256:931c8f86169fc6f2752cb5c4e6480f0d516112e78750c312e081ababecbaf2ed", size = 435547, upload-time = "2026-02-04T15:03:51.567Z" }, ] [[package]] @@ -4133,11 +4134,11 @@ wheels = [ [[package]] name = "setuptools" -version = "80.10.2" +version = "82.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/95/faf61eb8363f26aa7e1d762267a8d602a1b26d4f3a1e758e92cb3cb8b054/setuptools-80.10.2.tar.gz", hash = "sha256:8b0e9d10c784bf7d262c4e5ec5d4ec94127ce206e8738f29a437945fbc219b70", size = 1200343, upload-time = "2026-01-25T22:38:17.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/b8/f1f62a5e3c0ad2ff1d189590bfa4c46b4f3b6e49cef6f26c6ee4e575394d/setuptools-80.10.2-py3-none-any.whl", hash = "sha256:95b30ddfb717250edb492926c92b5221f7ef3fbcc2b07579bcd4a27da21d0173", size = 1064234, upload-time = "2026-01-25T22:38:15.216Z" }, + { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, ] [[package]] @@ -4213,38 +4214,38 @@ wheels = [ [[package]] name = "tqdm" -version = "4.67.1" +version = "4.67.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] [[package]] name = "ty" -version = "0.0.13" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/dc/b607f00916f5a7c52860b84a66dc17bc6988e8445e96b1d6e175a3837397/ty-0.0.13.tar.gz", hash = "sha256:7a1d135a400ca076407ea30012d1f75419634160ed3b9cad96607bf2956b23b3", size = 4999183, upload-time = "2026-01-21T13:21:16.133Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/df/3632f1918f4c0a33184f107efc5d436ab6da147fd3d3b94b3af6461efbf4/ty-0.0.13-py3-none-linux_armv6l.whl", hash = "sha256:1b2b8e02697c3a94c722957d712a0615bcc317c9b9497be116ef746615d892f2", size = 9993501, upload-time = "2026-01-21T13:21:26.628Z" }, - { url = "https://files.pythonhosted.org/packages/92/87/6a473ced5ac280c6ce5b1627c71a8a695c64481b99aabc798718376a441e/ty-0.0.13-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f15cdb8e233e2b5adfce673bb21f4c5e8eaf3334842f7eea3c70ac6fda8c1de5", size = 9860986, upload-time = "2026-01-21T13:21:24.425Z" }, - { url = "https://files.pythonhosted.org/packages/5d/9b/d89ae375cf0a7cd9360e1164ce017f8c753759be63b6a11ed4c944abe8c6/ty-0.0.13-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0819e89ac9f0d8af7a062837ce197f0461fee2fc14fd07e2c368780d3a397b73", size = 9350748, upload-time = "2026-01-21T13:21:28.502Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a6/9ad58518056fab344b20c0bb2c1911936ebe195318e8acc3bc45ac1c6b6b/ty-0.0.13-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1de79f481084b7cc7a202ba0d7a75e10970d10ffa4f025b23f2e6b7324b74886", size = 9849884, upload-time = "2026-01-21T13:21:21.886Z" }, - { url = "https://files.pythonhosted.org/packages/b1/c3/8add69095fa179f523d9e9afcc15a00818af0a37f2b237a9b59bc0046c34/ty-0.0.13-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4fb2154cff7c6e95d46bfaba283c60642616f20d73e5f96d0c89c269f3e1bcec", size = 9822975, upload-time = "2026-01-21T13:21:14.292Z" }, - { url = "https://files.pythonhosted.org/packages/a4/05/4c0927c68a0a6d43fb02f3f0b6c19c64e3461dc8ed6c404dde0efb8058f7/ty-0.0.13-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00be58d89337c27968a20d58ca553458608c5b634170e2bec82824c2e4cf4d96", size = 10294045, upload-time = "2026-01-21T13:21:30.505Z" }, - { url = "https://files.pythonhosted.org/packages/b4/86/6dc190838aba967557fe0bfd494c595d00b5081315a98aaf60c0e632aaeb/ty-0.0.13-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:72435eade1fa58c6218abb4340f43a6c3ff856ae2dc5722a247d3a6dd32e9737", size = 10916460, upload-time = "2026-01-21T13:21:07.788Z" }, - { url = "https://files.pythonhosted.org/packages/04/40/9ead96b7c122e1109dfcd11671184c3506996bf6a649306ec427e81d9544/ty-0.0.13-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77a548742ee8f621d718159e7027c3b555051d096a49bb580249a6c5fc86c271", size = 10597154, upload-time = "2026-01-21T13:21:18.064Z" }, - { url = "https://files.pythonhosted.org/packages/aa/7d/e832a2c081d2be845dc6972d0c7998914d168ccbc0b9c86794419ab7376e/ty-0.0.13-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da067c57c289b7cf914669704b552b6207c2cc7f50da4118c3e12388642e6b3f", size = 10410710, upload-time = "2026-01-21T13:21:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/31/e3/898be3a96237a32f05c4c29b43594dc3b46e0eedfe8243058e46153b324f/ty-0.0.13-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:d1b50a01fffa140417fca5a24b658fbe0734074a095d5b6f0552484724474343", size = 9826299, upload-time = "2026-01-21T13:21:00.845Z" }, - { url = "https://files.pythonhosted.org/packages/bb/eb/db2d852ce0ed742505ff18ee10d7d252f3acfd6fc60eca7e9c7a0288a6d8/ty-0.0.13-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0f33c46f52e5e9378378eca0d8059f026f3c8073ace02f7f2e8d079ddfe5207e", size = 9831610, upload-time = "2026-01-21T13:21:05.842Z" }, - { url = "https://files.pythonhosted.org/packages/9e/61/149f59c8abaddcbcbb0bd13b89c7741ae1c637823c5cf92ed2c644fcadef/ty-0.0.13-py3-none-musllinux_1_2_i686.whl", hash = "sha256:168eda24d9a0b202cf3758c2962cc295878842042b7eca9ed2965259f59ce9f2", size = 9978885, upload-time = "2026-01-21T13:21:10.306Z" }, - { url = "https://files.pythonhosted.org/packages/a0/cd/026d4e4af60a80918a8d73d2c42b8262dd43ab2fa7b28d9743004cb88d57/ty-0.0.13-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d4917678b95dc8cb399cc459fab568ba8d5f0f33b7a94bf840d9733043c43f29", size = 10506453, upload-time = "2026-01-21T13:20:56.633Z" }, - { url = "https://files.pythonhosted.org/packages/63/06/8932833a4eca2df49c997a29afb26721612de8078ae79074c8fe87e17516/ty-0.0.13-py3-none-win32.whl", hash = "sha256:c1f2ec40daa405508b053e5b8e440fbae5fdb85c69c9ab0ee078f8bc00eeec3d", size = 9433482, upload-time = "2026-01-21T13:20:58.717Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fd/e8d972d1a69df25c2cecb20ea50e49ad5f27a06f55f1f5f399a563e71645/ty-0.0.13-py3-none-win_amd64.whl", hash = "sha256:8b7b1ab9f187affbceff89d51076038363b14113be29bda2ddfa17116de1d476", size = 10319156, upload-time = "2026-01-21T13:21:03.266Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c2/05fdd64ac003a560d4fbd1faa7d9a31d75df8f901675e5bed1ee2ceeff87/ty-0.0.13-py3-none-win_arm64.whl", hash = "sha256:1c9630333497c77bb9bcabba42971b96ee1f36c601dd3dcac66b4134f9fa38f0", size = 9808316, upload-time = "2026-01-21T13:20:54.053Z" }, +version = "0.0.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/25/257602d316b9333089b688a7a11b33ebc660b74e8dacf400dc3dfdea1594/ty-0.0.15.tar.gz", hash = "sha256:4f9a5b8df208c62dba56e91b93bed8b5bb714839691b8cff16d12c983bfa1174", size = 5101936, upload-time = "2026-02-05T01:06:34.922Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/c5/35626e732b79bf0e6213de9f79aff59b5f247c0a1e3ce0d93e675ab9b728/ty-0.0.15-py3-none-linux_armv6l.whl", hash = "sha256:68e092458516c61512dac541cde0a5e4e5842df00b4e81881ead8f745ddec794", size = 10138374, upload-time = "2026-02-05T01:07:03.804Z" }, + { url = "https://files.pythonhosted.org/packages/d5/8a/48fd81664604848f79d03879b3ca3633762d457a069b07e09fb1b87edd6e/ty-0.0.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79f2e75289eae3cece94c51118b730211af4ba5762906f52a878041b67e54959", size = 9947858, upload-time = "2026-02-05T01:06:47.453Z" }, + { url = "https://files.pythonhosted.org/packages/b6/85/c1ac8e97bcd930946f4c94db85b675561d590b4e72703bf3733419fc3973/ty-0.0.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:112a7b26e63e48cc72c8c5b03227d1db280cfa57a45f2df0e264c3a016aa8c3c", size = 9443220, upload-time = "2026-02-05T01:06:44.98Z" }, + { url = "https://files.pythonhosted.org/packages/3c/d9/244bc02599d950f7a4298fbc0c1b25cc808646b9577bdf7a83470b2d1cec/ty-0.0.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71f62a2644972975a657d9dc867bf901235cde51e8d24c20311067e7afd44a56", size = 9949976, upload-time = "2026-02-05T01:07:01.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/ab/3a0daad66798c91a33867a3ececf17d314ac65d4ae2bbbd28cbfde94da63/ty-0.0.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e48b42be2d257317c85b78559233273b655dd636fc61e7e1d69abd90fd3cba4", size = 9965918, upload-time = "2026-02-05T01:06:54.283Z" }, + { url = "https://files.pythonhosted.org/packages/39/4e/e62b01338f653059a7c0cd09d1a326e9a9eedc351a0f0de9db0601658c3d/ty-0.0.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27dd5b52a421e6871c5bfe9841160331b60866ed2040250cb161886478ab3e4f", size = 10424943, upload-time = "2026-02-05T01:07:08.777Z" }, + { url = "https://files.pythonhosted.org/packages/65/b5/7aa06655ce69c0d4f3e845d2d85e79c12994b6d84c71699cfb437e0bc8cf/ty-0.0.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76b85c9ec2219e11c358a7db8e21b7e5c6674a1fb9b6f633836949de98d12286", size = 10964692, upload-time = "2026-02-05T01:06:37.103Z" }, + { url = "https://files.pythonhosted.org/packages/13/04/36fdfe1f3c908b471e246e37ce3d011175584c26d3853e6c5d9a0364564c/ty-0.0.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e8204c61d8ede4f21f2975dce74efdb80fafb2fae1915c666cceb33ea3c90b", size = 10692225, upload-time = "2026-02-05T01:06:49.714Z" }, + { url = "https://files.pythonhosted.org/packages/13/41/5bf882649bd8b64ded5fbce7fb8d77fb3b868de1a3b1a6c4796402b47308/ty-0.0.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af87c3be7c944bb4d6609d6c63e4594944b0028c7bd490a525a82b88fe010d6d", size = 10516776, upload-time = "2026-02-05T01:06:52.047Z" }, + { url = "https://files.pythonhosted.org/packages/56/75/66852d7e004f859839c17ffe1d16513c1e7cc04bcc810edb80ca022a9124/ty-0.0.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50dccf7398505e5966847d366c9e4c650b8c225411c2a68c32040a63b9521eea", size = 9928828, upload-time = "2026-02-05T01:06:56.647Z" }, + { url = "https://files.pythonhosted.org/packages/65/72/96bc16c7b337a3ef358fd227b3c8ef0c77405f3bfbbfb59ee5915f0d9d71/ty-0.0.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:bd797b8f231a4f4715110259ad1ad5340a87b802307f3e06d92bfb37b858a8f3", size = 9978960, upload-time = "2026-02-05T01:06:29.567Z" }, + { url = "https://files.pythonhosted.org/packages/a0/18/d2e316a35b626de2227f832cd36d21205e4f5d96fd036a8af84c72ecec1b/ty-0.0.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9deb7f20e18b25440a9aa4884f934ba5628ef456dbde91819d5af1a73da48af3", size = 10135903, upload-time = "2026-02-05T01:06:59.256Z" }, + { url = "https://files.pythonhosted.org/packages/02/d3/b617a79c9dad10c888d7c15cd78859e0160b8772273637b9c4241a049491/ty-0.0.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7b31b3de031255b90a5f4d9cb3d050feae246067c87130e5a6861a8061c71754", size = 10615879, upload-time = "2026-02-05T01:07:06.661Z" }, + { url = "https://files.pythonhosted.org/packages/fb/b0/2652a73c71c77296a6343217063f05745da60c67b7e8a8e25f2064167fce/ty-0.0.15-py3-none-win32.whl", hash = "sha256:9362c528ceb62c89d65c216336d28d500bc9f4c10418413f63ebc16886e16cc1", size = 9578058, upload-time = "2026-02-05T01:06:42.928Z" }, + { url = "https://files.pythonhosted.org/packages/84/6e/08a4aedebd2a6ce2784b5bc3760e43d1861f1a184734a78215c2d397c1df/ty-0.0.15-py3-none-win_amd64.whl", hash = "sha256:4db040695ae67c5524f59cb8179a8fa277112e69042d7dfdac862caa7e3b0d9c", size = 10457112, upload-time = "2026-02-05T01:06:39.885Z" }, + { url = "https://files.pythonhosted.org/packages/b3/be/1991f2bc12847ae2d4f1e3ac5dcff8bb7bc1261390645c0755bb55616355/ty-0.0.15-py3-none-win_arm64.whl", hash = "sha256:e5a98d4119e77d6136461e16ae505f8f8069002874ab073de03fbcb1a5e8bf25", size = 9937490, upload-time = "2026-02-05T01:06:32.388Z" }, ] [[package]] From f1785c245ade96a1dff1a4d663f526d8de9df280 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Feb 2026 20:53:02 -0800 Subject: [PATCH 159/685] remove pytest-repeat (#37156) --- pyproject.toml | 1 - uv.lock | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b1295ef8137..f959c613cc9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -89,7 +89,6 @@ testing = [ "pytest-timeout", "pytest-asyncio", "pytest-mock", - "pytest-repeat", "ruff", "codespell", "pre-commit-hooks", diff --git a/uv.lock b/uv.lock index 816733d12ef..8909934e9a0 100644 --- a/uv.lock +++ b/uv.lock @@ -1001,7 +1001,6 @@ testing = [ { name = "pytest-asyncio" }, { name = "pytest-cpp" }, { name = "pytest-mock" }, - { name = "pytest-repeat" }, { name = "pytest-subtests" }, { name = "pytest-timeout" }, { name = "pytest-xdist" }, @@ -1053,7 +1052,6 @@ requires-dist = [ { name = "pytest-asyncio", marker = "extra == 'testing'" }, { name = "pytest-cpp", marker = "extra == 'testing'" }, { name = "pytest-mock", marker = "extra == 'testing'" }, - { name = "pytest-repeat", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-timeout", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, @@ -3829,18 +3827,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] -[[package]] -name = "pytest-repeat" -version = "0.9.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/d4/69e9dbb9b8266df0b157c72be32083403c412990af15c7c15f7a3fd1b142/pytest_repeat-0.9.4.tar.gz", hash = "sha256:d92ac14dfaa6ffcfe6917e5d16f0c9bc82380c135b03c2a5f412d2637f224485", size = 6488, upload-time = "2025-04-07T14:59:53.077Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/73/d4/8b706b81b07b43081bd68a2c0359fe895b74bf664b20aca8005d2bb3be71/pytest_repeat-0.9.4-py3-none-any.whl", hash = "sha256:c1738b4e412a6f3b3b9e0b8b29fcd7a423e50f87381ad9307ef6f5a8601139f3", size = 4180, upload-time = "2025-04-07T14:59:51.492Z" }, -] - [[package]] name = "pytest-subtests" version = "0.15.0" From fcd5897650610317051229ed043b5ece1d73cf90 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 21:29:14 -0800 Subject: [PATCH 160/685] BigButton: push up all content when pressed (#37157) clean implementation --- selfdrive/ui/mici/widgets/button.py | 63 ++++++++++++++++------------- 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 56dd3f7a23a..118d3349650 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -52,6 +52,12 @@ def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (6 def set_enable_pressed_state(self, pressed: bool): self._press_state_enabled = pressed + def _draw_content(self, btn_y: float): + # draw icon + icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) + rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0]), + int(btn_y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), icon_color) + def _render(self, _): # draw background txt_bg = self._txt_btn_bg if not self._red else self._txt_btn_red_bg @@ -65,10 +71,7 @@ def _render(self, _): btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) - # draw icon - icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) - rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0]), - int(self._rect.y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), icon_color) + self._draw_content(btn_y) class BigCircleToggle(BigCircleButton): @@ -93,13 +96,13 @@ def _handle_mouse_release(self, mouse_pos: MousePos): if self._toggle_callback: self._toggle_callback(self._checked) - def _render(self, _): - super()._render(_) + def _draw_content(self, btn_y: float): + super()._draw_content(btn_y) # draw status icon rl.draw_texture(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled, int(self._rect.x + (self._rect.width - self._txt_toggle_enabled.width) / 2), - int(self._rect.y + 5), rl.WHITE) + int(btn_y + 5), rl.WHITE) class BigButton(Widget): @@ -176,19 +179,7 @@ def get_value(self) -> str: def get_text(self): return self.text - def _render(self, _): - # draw _txt_default_bg - txt_bg = self._txt_default_bg - if not self.enabled: - txt_bg = self._txt_disabled_bg - elif self.is_pressed: - txt_bg = self._txt_hover_bg - - scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) - btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 - btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 - rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) - + def _draw_content(self, btn_y: float): # LABEL ------------------------------------------------------------------ label_x = self._rect.x + LABEL_HORIZONTAL_PADDING @@ -210,14 +201,29 @@ def _render(self, _): if self._rotate_icon_t is not None: rotation = (rl.get_time() - self._rotate_icon_t) * 180 - # drop top right with 30px padding + # draw top right with 30px padding x = self._rect.x + self._rect.width - 30 - self._txt_icon.width / 2 - y = self._rect.y + 30 + self._txt_icon.height / 2 + y = btn_y + 30 + self._txt_icon.height / 2 source_rec = rl.Rectangle(0, 0, self._txt_icon.width, self._txt_icon.height) dest_rec = rl.Rectangle(int(x), int(y), self._txt_icon.width, self._txt_icon.height) origin = rl.Vector2(self._txt_icon.width / 2, self._txt_icon.height / 2) rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.WHITE) + def _render(self, _): + # draw _txt_default_bg + txt_bg = self._txt_default_bg + if not self.enabled: + txt_bg = self._txt_disabled_bg + elif self.is_pressed: + txt_bg = self._txt_hover_bg + + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) + btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 + btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 + rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + + self._draw_content(btn_y) + class BigToggle(BigButton): def __init__(self, text: str, value: str = "", initial_state: bool = False, toggle_callback: Callable | None = None): @@ -246,11 +252,11 @@ def _draw_pill(self, x: float, y: float, checked: bool): else: rl.draw_texture(self._txt_disabled_toggle, int(x), int(y), rl.WHITE) - def _render(self, _): - super()._render(_) + def _draw_content(self, btn_y: float): + super()._draw_content(btn_y) x = self._rect.x + self._rect.width - self._txt_enabled_toggle.width - y = self._rect.y + y = btn_y self._draw_pill(x, y, self._checked) @@ -275,13 +281,14 @@ def _handle_mouse_release(self, mouse_pos: MousePos): if self._select_callback: self._select_callback(self.value) - def _render(self, _): - BigButton._render(self, _) + def _draw_content(self, btn_y: float): + # don't draw pill from BigToggle + BigButton._draw_content(self, btn_y) checked_idx = self._options.index(self.value) x = self._rect.x + self._rect.width - self._txt_enabled_toggle.width - y = self._rect.y + y = btn_y for i in range(len(self._options)): self._draw_pill(x, y, checked_idx == i) From 1070dda3ee4f38bb16645800ca508023fcfc8df1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 21:56:45 -0800 Subject: [PATCH 161/685] ui.py: fix stride (#37159) fix uii.py --- tools/replay/ui.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 54667ebfe96..8707f2be99c 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -160,9 +160,12 @@ def ui_thread(addr): camera = DEVICE_CAMERAS[("tici", str(sm['roadCameraState'].sensor))] - imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8).reshape((len(yuv_img_raw.data) // vipc_client.stride, vipc_client.stride)) - num_px = vipc_client.width * vipc_client.height - rgb = cv2.cvtColor(imgff[: vipc_client.height * 3 // 2, : vipc_client.width], cv2.COLOR_YUV2RGB_NV12) + # Use received buffer dimensions (full HEVC can have stride != buffer_len/rows due to VENUS padding) + h, w, stride = yuv_img_raw.height, yuv_img_raw.width, yuv_img_raw.stride + nv12_size = h * 3 // 2 * stride + imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8, count=nv12_size).reshape((h * 3 // 2, stride)) + num_px = w * h + rgb = cv2.cvtColor(imgff[: h * 3 // 2, : w], cv2.COLOR_YUV2RGB_NV12) qcam = "QCAM" in os.environ bb_scale = (528 if qcam else camera.fcam.width) / 640.0 From 77f069cbe5afe1d2adb8895c3b2b2d60626403b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 21:57:34 -0800 Subject: [PATCH 162/685] BigButton: don't round drawn content (#37158) * unround icons * unround rest --- selfdrive/ui/mici/widgets/button.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 118d3349650..29844ccda10 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -55,8 +55,8 @@ def set_enable_pressed_state(self, pressed: bool): def _draw_content(self, btn_y: float): # draw icon icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) - rl.draw_texture(self._txt_icon, int(self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0]), - int(btn_y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), icon_color) + rl.draw_texture_ex(self._txt_icon, (self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0], + btn_y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), 0, 1.0, icon_color) def _render(self, _): # draw background @@ -100,9 +100,9 @@ def _draw_content(self, btn_y: float): super()._draw_content(btn_y) # draw status icon - rl.draw_texture(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled, - int(self._rect.x + (self._rect.width - self._txt_toggle_enabled.width) / 2), - int(btn_y + 5), rl.WHITE) + rl.draw_texture_ex(self._txt_toggle_enabled if self._checked else self._txt_toggle_disabled, + (self._rect.x + (self._rect.width - self._txt_toggle_enabled.width) / 2, btn_y + 5), + 0, 1.0, rl.WHITE) class BigButton(Widget): @@ -205,7 +205,7 @@ def _draw_content(self, btn_y: float): x = self._rect.x + self._rect.width - 30 - self._txt_icon.width / 2 y = btn_y + 30 + self._txt_icon.height / 2 source_rec = rl.Rectangle(0, 0, self._txt_icon.width, self._txt_icon.height) - dest_rec = rl.Rectangle(int(x), int(y), self._txt_icon.width, self._txt_icon.height) + dest_rec = rl.Rectangle(x, y, self._txt_icon.width, self._txt_icon.height) origin = rl.Vector2(self._txt_icon.width / 2, self._txt_icon.height / 2) rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.WHITE) @@ -248,9 +248,9 @@ def _handle_mouse_release(self, mouse_pos: MousePos): def _draw_pill(self, x: float, y: float, checked: bool): # draw toggle icon top right if checked: - rl.draw_texture(self._txt_enabled_toggle, int(x), int(y), rl.WHITE) + rl.draw_texture_ex(self._txt_enabled_toggle, (x, y), 0, 1.0, rl.WHITE) else: - rl.draw_texture(self._txt_disabled_toggle, int(x), int(y), rl.WHITE) + rl.draw_texture_ex(self._txt_disabled_toggle, (x, y), 0, 1.0, rl.WHITE) def _draw_content(self, btn_y: float): super()._draw_content(btn_y) From 45099e7fcd384bb2dac36b55a6c10ba9de58a1e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 10 Feb 2026 23:12:41 -0800 Subject: [PATCH 163/685] Revert tgwarp again (#37161) * Reapply "revert tg calib and opencl cleanup (#37113)" (#37115) This reverts commit 667f3bb32f696b8bdcd93bce9143cd4171e436a3. * revert msgq too * msgq on master --- Dockerfile.openpilot_base | 37 + SConstruct | 1 + common/SConscript | 1 + common/clutil.cc | 98 ++ common/clutil.h | 28 + common/mat.h | 85 + msgq_repo | 2 +- selfdrive/modeld/SConscript | 56 +- selfdrive/modeld/compile_warp.py | 206 --- selfdrive/modeld/dmonitoringmodeld.py | 33 +- selfdrive/modeld/modeld.py | 59 +- selfdrive/modeld/models/commonmodel.cc | 64 + selfdrive/modeld/models/commonmodel.h | 97 ++ selfdrive/modeld/models/commonmodel.pxd | 27 + selfdrive/modeld/models/commonmodel_pyx.pxd | 13 + selfdrive/modeld/models/commonmodel_pyx.pyx | 74 + selfdrive/modeld/runners/tinygrad_helpers.py | 8 + selfdrive/modeld/transforms/loadyuv.cc | 76 + selfdrive/modeld/transforms/loadyuv.cl | 47 + selfdrive/modeld/transforms/loadyuv.h | 20 + selfdrive/modeld/transforms/transform.cc | 97 ++ selfdrive/modeld/transforms/transform.cl | 54 + selfdrive/modeld/transforms/transform.h | 25 + selfdrive/test/process_replay/model_replay.py | 4 +- .../test/process_replay/process_replay.py | 15 +- system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.cc | 5 +- system/camerad/cameras/camera_common.h | 2 +- system/camerad/cameras/camera_qcom2.cc | 22 +- system/camerad/cameras/spectra.cc | 4 +- system/camerad/cameras/spectra.h | 2 +- system/hardware/tici/tests/test_power_draw.py | 2 +- system/loggerd/SConscript | 5 +- third_party/opencl/include/CL/cl.h | 1452 +++++++++++++++++ third_party/opencl/include/CL/cl_d3d10.h | 131 ++ third_party/opencl/include/CL/cl_d3d11.h | 131 ++ .../opencl/include/CL/cl_dx9_media_sharing.h | 132 ++ third_party/opencl/include/CL/cl_egl.h | 136 ++ third_party/opencl/include/CL/cl_ext.h | 391 +++++ third_party/opencl/include/CL/cl_ext_qcom.h | 255 +++ third_party/opencl/include/CL/cl_gl.h | 167 ++ third_party/opencl/include/CL/cl_gl_ext.h | 74 + third_party/opencl/include/CL/cl_platform.h | 1333 +++++++++++++++ third_party/opencl/include/CL/opencl.h | 59 + tools/cabana/SConscript | 2 + tools/install_ubuntu_dependencies.sh | 3 + tools/replay/SConscript | 5 + tools/webcam/README.md | 4 + 48 files changed, 5237 insertions(+), 309 deletions(-) create mode 100644 common/clutil.cc create mode 100644 common/clutil.h create mode 100644 common/mat.h delete mode 100755 selfdrive/modeld/compile_warp.py create mode 100644 selfdrive/modeld/models/commonmodel.cc create mode 100644 selfdrive/modeld/models/commonmodel.h create mode 100644 selfdrive/modeld/models/commonmodel.pxd create mode 100644 selfdrive/modeld/models/commonmodel_pyx.pxd create mode 100644 selfdrive/modeld/models/commonmodel_pyx.pyx create mode 100644 selfdrive/modeld/runners/tinygrad_helpers.py create mode 100644 selfdrive/modeld/transforms/loadyuv.cc create mode 100644 selfdrive/modeld/transforms/loadyuv.cl create mode 100644 selfdrive/modeld/transforms/loadyuv.h create mode 100644 selfdrive/modeld/transforms/transform.cc create mode 100644 selfdrive/modeld/transforms/transform.cl create mode 100644 selfdrive/modeld/transforms/transform.h create mode 100644 third_party/opencl/include/CL/cl.h create mode 100644 third_party/opencl/include/CL/cl_d3d10.h create mode 100644 third_party/opencl/include/CL/cl_d3d11.h create mode 100644 third_party/opencl/include/CL/cl_dx9_media_sharing.h create mode 100644 third_party/opencl/include/CL/cl_egl.h create mode 100644 third_party/opencl/include/CL/cl_ext.h create mode 100644 third_party/opencl/include/CL/cl_ext_qcom.h create mode 100644 third_party/opencl/include/CL/cl_gl.h create mode 100644 third_party/opencl/include/CL/cl_gl_ext.h create mode 100644 third_party/opencl/include/CL/cl_platform.h create mode 100644 third_party/opencl/include/CL/opencl.h diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 8a604129938..44d8d95e95d 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -18,6 +18,43 @@ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ cd /usr/lib/gcc/arm-none-eabi/* && \ rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp +# Add OpenCL +RUN apt-get update && apt-get install -y --no-install-recommends \ + apt-utils \ + alien \ + unzip \ + tar \ + curl \ + xz-utils \ + dbus \ + gcc-arm-none-eabi \ + tmux \ + vim \ + libx11-6 \ + wget \ + && rm -rf /var/lib/apt/lists/* + +RUN mkdir -p /tmp/opencl-driver-intel && \ + cd /tmp/opencl-driver-intel && \ + wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ + wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ + mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ + cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ + tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ + mkdir -p /etc/OpenCL/vendors && \ + echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ + cd /opt/intel && \ + tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ + mkdir -p /etc/ld.so.conf.d && \ + echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ + ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ + cd / && \ + rm -rf /tmp/opencl-driver-intel + ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute ENV QTWEBENGINE_DISABLE_SANDBOX=1 diff --git a/SConstruct b/SConstruct index 4f04be624cf..ca5b7b6cb72 100644 --- a/SConstruct +++ b/SConstruct @@ -94,6 +94,7 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": + env.Append(CPPPATH=["#third_party/opencl/include"]) env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", diff --git a/common/SConscript b/common/SConscript index 15a0e5eff15..1c68cf05c7a 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,6 +5,7 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', + 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc new file mode 100644 index 00000000000..f8381a7e092 --- /dev/null +++ b/common/clutil.cc @@ -0,0 +1,98 @@ +#include "common/clutil.h" + +#include +#include +#include + +#include "common/util.h" +#include "common/swaglog.h" + +namespace { // helper functions + +template +std::string get_info(Func get_info_func, Id id, Name param_name) { + size_t size = 0; + CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); + std::string info(size, '\0'); + CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); + return info; +} +inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } +inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } + +void cl_print_info(cl_platform_id platform, cl_device_id device) { + size_t work_group_size = 0; + cl_device_type device_type = 0; + clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); + clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); + const char *type_str = "Other..."; + switch (device_type) { + case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; + case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; + case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; + } + + LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); + LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); + LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); + LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); + LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); + LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); + LOGD("max work group size: %zu", work_group_size); + LOGD("type = %d, %s", (int)device_type, type_str); +} + +void cl_print_build_errors(cl_program program, cl_device_id device) { + cl_build_status status; + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); + size_t log_size; + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); + std::string log(log_size, '\0'); + clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); + + LOGE("build failed; status=%d, log: %s", status, log.c_str()); +} + +} // namespace + +cl_device_id cl_get_device_id(cl_device_type device_type) { + cl_uint num_platforms = 0; + CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); + std::unique_ptr platform_ids = std::make_unique(num_platforms); + CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); + + for (size_t i = 0; i < num_platforms; ++i) { + LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); + + // Get first device + if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { + cl_print_info(platform_ids[i], device_id); + return device_id; + } + } + LOGE("No valid openCL platform found"); + assert(0); + return nullptr; +} + +cl_context cl_create_context(cl_device_id device_id) { + return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); +} + +void cl_release_context(cl_context context) { + clReleaseContext(context); +} + +cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { + return cl_program_from_source(ctx, device_id, util::read_file(path), args); +} + +cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { + const char *csrc = src.c_str(); + cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); + if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { + cl_print_build_errors(prg, device_id); + assert(0); + } + return prg; +} diff --git a/common/clutil.h b/common/clutil.h new file mode 100644 index 00000000000..b364e79d45b --- /dev/null +++ b/common/clutil.h @@ -0,0 +1,28 @@ +#pragma once + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include + +#define CL_CHECK(_expr) \ + do { \ + assert(CL_SUCCESS == (_expr)); \ + } while (0) + +#define CL_CHECK_ERR(_expr) \ + ({ \ + cl_int err = CL_INVALID_VALUE; \ + __typeof__(_expr) _ret = _expr; \ + assert(_ret&& err == CL_SUCCESS); \ + _ret; \ + }) + +cl_device_id cl_get_device_id(cl_device_type device_type); +cl_context cl_create_context(cl_device_id device_id); +void cl_release_context(cl_context context); +cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); +cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/common/mat.h b/common/mat.h new file mode 100644 index 00000000000..8e10d619717 --- /dev/null +++ b/common/mat.h @@ -0,0 +1,85 @@ +#pragma once + +typedef struct vec3 { + float v[3]; +} vec3; + +typedef struct vec4 { + float v[4]; +} vec4; + +typedef struct mat3 { + float v[3*3]; +} mat3; + +typedef struct mat4 { + float v[4*4]; +} mat4; + +static inline mat3 matmul3(const mat3 &a, const mat3 &b) { + mat3 ret = {{0.0}}; + for (int r=0; r<3; r++) { + for (int c=0; c<3; c++) { + float v = 0.0; + for (int k=0; k<3; k++) { + v += a.v[r*3+k] * b.v[k*3+c]; + } + ret.v[r*3+c] = v; + } + } + return ret; +} + +static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { + vec3 ret = {{0.0}}; + for (int r=0; r<3; r++) { + for (int c=0; c<3; c++) { + ret.v[r] += a.v[r*3+c] * b.v[c]; + } + } + return ret; +} + +static inline mat4 matmul(const mat4 &a, const mat4 &b) { + mat4 ret = {{0.0}}; + for (int r=0; r<4; r++) { + for (int c=0; c<4; c++) { + float v = 0.0; + for (int k=0; k<4; k++) { + v += a.v[r*4+k] * b.v[k*4+c]; + } + ret.v[r*4+c] = v; + } + } + return ret; +} + +static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { + vec4 ret = {{0.0}}; + for (int r=0; r<4; r++) { + for (int c=0; c<4; c++) { + ret.v[r] += a.v[r*4+c] * b.v[c]; + } + } + return ret; +} + +// scales the input and output space of a transformation matrix +// that assumes pixel-center origin. +static inline mat3 transform_scale_buffer(const mat3 &in, float s) { + // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s + + mat3 transform_out = {{ + 1.0f/s, 0.0f, 0.5f, + 0.0f, 1.0f/s, 0.5f, + 0.0f, 0.0f, 1.0f, + }}; + + mat3 transform_in = {{ + s, 0.0f, -0.5f*s, + 0.0f, s, -0.5f*s, + 0.0f, 0.0f, 1.0f, + }}; + + return matmul3(transform_in, matmul3(in, transform_out)); +} diff --git a/msgq_repo b/msgq_repo index 2c191c1a72a..4c4e814ed59 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 2c191c1a72ae8119b93b49e1bc12d4a99b751855 +Subproject commit 4c4e814ed592db52431bb53d37f0bb93299e960c diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 84e94df4a5d..91f3597447b 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,12 +1,35 @@ import os import glob -Import('env', 'arch') +Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') lenv = env.Clone() +lenvCython = envCython.Clone() -tinygrad_root = env.Dir("#").abspath -tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) - if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] +libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] +frameworks = [] + +common_src = [ + "models/commonmodel.cc", + "transforms/loadyuv.cc", + "transforms/transform.cc", +] + +# OpenCL is a framework on Mac +if arch == "Darwin": + frameworks += ['OpenCL'] +else: + libs += ['OpenCL'] + +# Set path definitions +for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): + for xenv in (lenv, lenvCython): + xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') + +# Compile cython +cython_libs = envCython["LIBS"] + libs +commonmodel_lib = lenv.Library('commonmodel', common_src) +lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) +tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: @@ -15,35 +38,22 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) -# compile warp -tg_flags = { - 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env -}.get(arch, 'DEV=CPU CPU_LLVM=1') -image_flag = { - 'larch64': 'IMAGE=2', -}.get(arch, 'IMAGE=0') -script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' -from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -warp_targets = [] -for cam in [_ar_ox_fisheye, _os_fisheye]: - w, h = cam.width, cam.height - warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command(warp_targets, tinygrad_files + script_files, cmd) - def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath return lenv.Command( fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' ) # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: - tg_compile(tg_flags, model_name) + flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env + }.get(arch, 'DEV=CPU CPU_LLVM=1') + tg_compile(flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py deleted file mode 100755 index 5adb60e6240..00000000000 --- a/selfdrive/modeld/compile_warp.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python3 -import time -import pickle -import numpy as np -from pathlib import Path -from tinygrad.tensor import Tensor -from tinygrad.helpers import Context -from tinygrad.device import Device -from tinygrad.engine.jit import TinyJit - -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info -from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE -from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye - -MODELS_DIR = Path(__file__).parent / 'models' - -CAMERA_CONFIGS = [ - (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 - (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 -] - -UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) -UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) - -IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) - - -def warp_pkl_path(w, h): - return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - - -def dm_warp_pkl_path(w, h): - return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' - - -def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad, ratio): - w_dst, h_dst = dst_shape - h_src, w_src = src_shape - - x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst) - y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst) - ones = Tensor.ones_like(x) - dst_coords = x.reshape(1, -1).cat(y.reshape(1, -1)).cat(ones.reshape(1, -1)) - - src_coords = M_inv @ dst_coords - src_coords = src_coords / src_coords[2:3, :] - - x_nn_clipped = Tensor.round(src_coords[0]).clip(0, w_src - 1).cast('int') - y_nn_clipped = Tensor.round(src_coords[1]).clip(0, h_src - 1).cast('int') - idx = y_nn_clipped * w_src + (y_nn_clipped * ratio).cast('int') * stride_pad + x_nn_clipped - - sampled = src_flat[idx] - return sampled - - -def frames_to_tensor(frames, model_w, model_h): - H = (frames.shape[0] * 2) // 3 - W = frames.shape[1] - in_img1 = Tensor.cat(frames[0:H:2, 0::2], - frames[1:H:2, 0::2], - frames[0:H:2, 1::2], - frames[1:H:2, 1::2], - frames[H:H+H//4].reshape((H//2, W//2)), - frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) - return in_img1 - - -def make_frame_prepare(cam_w, cam_h, model_w, model_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - uv_offset = stride * y_height - stride_pad = stride - cam_w - - def frame_prepare_tinygrad(input_frame, M_inv): - tg_scale = Tensor(UV_SCALE_MATRIX) - M_inv_uv = tg_scale @ M_inv @ Tensor(UV_SCALE_MATRIX_INV) - with Context(SPLIT_REDUCEOP=0): - y = warp_perspective_tinygrad(input_frame[:cam_h*stride], - M_inv, (model_w, model_h), - (cam_h, cam_w), stride_pad, 1).realize() - u = warp_perspective_tinygrad(input_frame[uv_offset:uv_offset + (cam_h//4)*stride], - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), stride_pad, 0.5).realize() - v = warp_perspective_tinygrad(input_frame[uv_offset + (cam_h//4)*stride:uv_offset + (cam_h//2)*stride], - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), stride_pad, 0.5).realize() - yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) - tensor = frames_to_tensor(yuv, model_w, model_h) - return tensor - return frame_prepare_tinygrad - - -def make_update_img_input(frame_prepare, model_w, model_h): - def update_img_input_tinygrad(tensor, frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - new_img = frame_prepare(frame, M_inv) - full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() - return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) - return update_img_input_tinygrad - - -def make_update_both_imgs(frame_prepare, model_w, model_h): - update_img = make_update_img_input(frame_prepare, model_w, model_h) - - def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, - calib_big_img_buffer, new_big_img, M_inv_big): - calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) - calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) - return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair - return update_both_imgs_tinygrad - - -def make_warp_dm(cam_w, cam_h, dm_w, dm_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - stride_pad = stride - cam_w - - def warp_dm(input_frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - with Context(SPLIT_REDUCEOP=0): - result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad, 1).reshape(-1, dm_h * dm_w) - return result - return warp_dm - - -def compile_modeld_warp(cam_w, cam_h): - model_w, model_h = MEDMODEL_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling modeld warp for {cam_w}x{cam_h}...") - - frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) - update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) - update_img_jit = TinyJit(update_both_imgs, prune=True) - - full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - - for i in range(10): - new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) - img_inputs = [full_buffer, - Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) - big_img_inputs = [big_full_buffer, - Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - inputs = img_inputs + big_img_inputs - Device.default.synchronize() - - inputs_np = [x.numpy() for x in inputs] - inputs_np[0] = full_buffer_np - inputs_np[3] = big_full_buffer_np - - st = time.perf_counter() - out = update_img_jit(*inputs) - full_buffer = out[0].contiguous().realize().clone() - big_full_buffer = out[2].contiguous().realize().clone() - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(update_img_jit, f) - print(f" Saved to {pkl_path}") - - jit = pickle.load(open(pkl_path, "rb")) - jit(*inputs) - - -def compile_dm_warp(cam_w, cam_h): - dm_w, dm_h = DM_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling DM warp for {cam_w}x{cam_h}...") - - warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) - warp_dm_jit = TinyJit(warp_dm, prune=True) - - for i in range(10): - inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - Device.default.synchronize() - st = time.perf_counter() - warp_dm_jit(*inputs) - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = dm_warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(warp_dm_jit, f) - print(f" Saved to {pkl_path}") - - -def run_and_save_pickle(): - for cam_w, cam_h in CAMERA_CONFIGS: - compile_modeld_warp(cam_w, cam_h) - compile_dm_warp(cam_w, cam_h) - - -if __name__ == "__main__": - run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index bc3adffffa8..7177998571c 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,6 +3,7 @@ from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor +from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -15,34 +16,32 @@ from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp +from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' -MODELS_DIR = Path(__file__).parent / 'models' + class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self): + def __init__(self, cl_ctx): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] + self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } - self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} - self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} - self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} - self.image_warp = None with open(MODEL_PKL_PATH, "rb") as f: self.model_run = pickle.load(f) @@ -51,15 +50,14 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple t1 = time.perf_counter() - if self.image_warp is None: - self.frame_buf_params = get_nv12_info(buf.width, buf.height) - warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.image_warp = pickle.load(f) - self.warp_inputs['frame'] = Tensor.from_blob(buf.data.ctypes.data, (self.frame_buf_params[3],), dtype='uint8').realize() + input_img_cl = self.frame.prepare(buf, transform.flatten()) + if TICI: + # The imgs tensors are backed by opencl memory, only need init once + if 'input_img' not in self.tensor_inputs: + self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) + else: + self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() - self.warp_inputs_np['transform'][:] = transform[:] - self.tensor_inputs['input_img'] = self.image_warp(self.warp_inputs['frame'], self.warp_inputs['transform']).realize() output = self.model_run(**self.tensor_inputs).numpy().flatten() @@ -109,11 +107,12 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - model = ModelState() + cl_context = CLContext() + model = ModelState(cl_context) cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 6f3c481ea23..846bb8d2c31 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,6 +7,7 @@ os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor +from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -21,13 +22,14 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.selfdrive.modeld.constants import ModelConstants, Plan +from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext +from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.modeld" @@ -37,15 +39,11 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' -MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 -IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) -assert IMG_QUEUE_SHAPE[0] == 30 - def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -138,11 +136,12 @@ def get(self, *names) -> dict[str, np.ndarray]: return out class ModelState: + frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self): + def __init__(self, context: CLContext): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -156,6 +155,7 @@ def __init__(self): self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] + self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -165,17 +165,12 @@ def __init__(self): self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), - 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(),} - self.full_frames : dict[str, Tensor] = {} - self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} - self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} + # img buffers are managed in openCL transform code + self.vision_inputs: dict[str, Tensor] = {} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() - self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} - self.update_imgs = None with open(VISION_PKL_PATH, "rb") as f: self.vision_run = pickle.load(f) @@ -193,28 +188,23 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] - if self.update_imgs is None: - for key in bufs.keys(): - w, h = bufs[key].width, bufs[key].height - self.frame_buf_params[key] = get_nv12_info(w, h) - warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.update_imgs = pickle.load(f) - - for key in bufs.keys(): - self.full_frames[key] = Tensor.from_blob(bufs[key].data.ctypes.data, (self.frame_buf_params[key][3],), dtype='uint8').realize() - self.transforms_np[key][:,:] = transforms[key][:,:] + imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} - out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], - self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) - self.img_queues['img'], self.img_queues['big_img'], = out[0].realize(), out[2].realize() - vision_inputs = {'img': out[1], 'big_img': out[3]} + if TICI and not USBGPU: + # The imgs tensors are backed by opencl memory, only need init once + for key in imgs_cl: + if key not in self.vision_inputs: + self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) + else: + for key in imgs_cl: + frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) + self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() if prepare_only: return None - self.vision_output = self.vision_run(**vision_inputs).numpy().flatten() + self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -224,6 +214,7 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], self.policy_output = self.policy_run(**self.policy_inputs).numpy().flatten() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) + combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) @@ -240,8 +231,10 @@ def main(demo=False): config_realtime_process(7, 54) st = time.monotonic() - cloudlog.warning("loading model") - model = ModelState() + cloudlog.warning("setting up CL context") + cl_context = CLContext() + cloudlog.warning("CL context ready; loading model") + model = ModelState(cl_context) cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -254,8 +247,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc new file mode 100644 index 00000000000..d3341e76ec3 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.cc @@ -0,0 +1,64 @@ +#include "selfdrive/modeld/models/commonmodel.h" + +#include +#include + +#include "common/clutil.h" + +DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { + input_frames = std::make_unique(buf_size); + temporal_skip = _temporal_skip; + input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); + img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); + region.origin = temporal_skip * frame_size_bytes; + region.size = frame_size_bytes; + last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); + + loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); + init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); +} + +cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); + + for (int i = 0; i < temporal_skip; i++) { + CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); + } + loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); + + copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); + copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); + + // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. + clFinish(q); + return &input_frames_cl; +} + +DrivingModelFrame::~DrivingModelFrame() { + deinit_transform(); + loadyuv_destroy(&loadyuv); + CL_CHECK(clReleaseMemObject(input_frames_cl)); + CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); + CL_CHECK(clReleaseMemObject(last_img_cl)); + CL_CHECK(clReleaseCommandQueue(q)); +} + + +MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { + input_frames = std::make_unique(buf_size); + input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); + + init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); +} + +cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); + clFinish(q); + return &y_cl; +} + +MonitoringModelFrame::~MonitoringModelFrame() { + deinit_transform(); + CL_CHECK(clReleaseMemObject(input_frame_cl)); + CL_CHECK(clReleaseCommandQueue(q)); +} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h new file mode 100644 index 00000000000..176d7eb6dcf --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include + +#include + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "common/mat.h" +#include "selfdrive/modeld/transforms/loadyuv.h" +#include "selfdrive/modeld/transforms/transform.h" + +class ModelFrame { +public: + ModelFrame(cl_device_id device_id, cl_context context) { + q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); + } + virtual ~ModelFrame() {} + virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } + uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { + CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); + clFinish(q); + return &input_frames[0]; + } + + int MODEL_WIDTH; + int MODEL_HEIGHT; + int MODEL_FRAME_SIZE; + int buf_size; + +protected: + cl_mem y_cl, u_cl, v_cl; + Transform transform; + cl_command_queue q; + std::unique_ptr input_frames; + + void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { + y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); + u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); + v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); + transform_init(&transform, context, device_id); + } + + void deinit_transform() { + transform_destroy(&transform); + CL_CHECK(clReleaseMemObject(v_cl)); + CL_CHECK(clReleaseMemObject(u_cl)); + CL_CHECK(clReleaseMemObject(y_cl)); + } + + void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { + transform_queue(&transform, q, + yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, + y_cl, u_cl, v_cl, model_width, model_height, projection); + } +}; + +class DrivingModelFrame : public ModelFrame { +public: + DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); + ~DrivingModelFrame(); + cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); + + const int MODEL_WIDTH = 512; + const int MODEL_HEIGHT = 256; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; + const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart + const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); + +private: + LoadYUVState loadyuv; + cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; + cl_buffer_region region; + int temporal_skip; +}; + +class MonitoringModelFrame : public ModelFrame { +public: + MonitoringModelFrame(cl_device_id device_id, cl_context context); + ~MonitoringModelFrame(); + cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); + + const int MODEL_WIDTH = 1440; + const int MODEL_HEIGHT = 960; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; + const int buf_size = MODEL_FRAME_SIZE; + +private: + cl_mem input_frame_cl; +}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd new file mode 100644 index 00000000000..4ac64d91720 --- /dev/null +++ b/selfdrive/modeld/models/commonmodel.pxd @@ -0,0 +1,27 @@ +# distutils: language = c++ + +from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem + +cdef extern from "common/mat.h": + cdef struct mat3: + float v[9] + +cdef extern from "common/clutil.h": + cdef unsigned long CL_DEVICE_TYPE_DEFAULT + cl_device_id cl_get_device_id(unsigned long) + cl_context cl_create_context(cl_device_id) + void cl_release_context(cl_context) + +cdef extern from "selfdrive/modeld/models/commonmodel.h": + cppclass ModelFrame: + int buf_size + unsigned char * buffer_from_cl(cl_mem*, int); + cl_mem * prepare(cl_mem, int, int, int, int, mat3) + + cppclass DrivingModelFrame: + int buf_size + DrivingModelFrame(cl_device_id, cl_context, int) + + cppclass MonitoringModelFrame: + int buf_size + MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd new file mode 100644 index 00000000000..0bb798625be --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pxd @@ -0,0 +1,13 @@ +# distutils: language = c++ + +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext + +cdef class CLContext(BaseCLContext): + pass + +cdef class CLMem: + cdef cl_mem * mem + + @staticmethod + cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx new file mode 100644 index 00000000000..5b7d11bc71a --- /dev/null +++ b/selfdrive/modeld/models/commonmodel_pyx.pyx @@ -0,0 +1,74 @@ +# distutils: language = c++ +# cython: c_string_encoding=ascii, language_level=3 + +import numpy as np +cimport numpy as cnp +from libc.string cimport memcpy +from libc.stdint cimport uintptr_t + +from msgq.visionipc.visionipc cimport cl_mem +from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext +from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context +from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame + + +cdef class CLContext(BaseCLContext): + def __cinit__(self): + self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) + self.context = cl_create_context(self.device_id) + + def __dealloc__(self): + if self.context: + cl_release_context(self.context) + +cdef class CLMem: + @staticmethod + cdef create(void * cmem): + mem = CLMem() + mem.mem = cmem + return mem + + @property + def mem_address(self): + return (self.mem) + +def cl_from_visionbuf(VisionBuf buf): + return CLMem.create(&buf.buf.buf_cl) + + +cdef class ModelFrame: + cdef cppModelFrame * frame + cdef int buf_size + + def __dealloc__(self): + del self.frame + + def prepare(self, VisionBuf buf, float[:] projection): + cdef mat3 cprojection + memcpy(cprojection.v, &projection[0], 9*sizeof(float)) + cdef cl_mem * data + data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) + return CLMem.create(data) + + def buffer_from_cl(self, CLMem in_frames): + cdef unsigned char * data2 + data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) + return np.asarray( data2) + + +cdef class DrivingModelFrame(ModelFrame): + cdef cppDrivingModelFrame * _frame + + def __cinit__(self, CLContext context, int temporal_skip): + self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) + self.frame = (self._frame) + self.buf_size = self._frame.buf_size + +cdef class MonitoringModelFrame(ModelFrame): + cdef cppMonitoringModelFrame * _frame + + def __cinit__(self, CLContext context): + self._frame = new cppMonitoringModelFrame(context.device_id, context.context) + self.frame = (self._frame) + self.buf_size = self._frame.buf_size + diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py new file mode 100644 index 00000000000..776381341cf --- /dev/null +++ b/selfdrive/modeld/runners/tinygrad_helpers.py @@ -0,0 +1,8 @@ + +from tinygrad.tensor import Tensor +from tinygrad.helpers import to_mv + +def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): + cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] + rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. + return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc new file mode 100644 index 00000000000..c93f5cd0381 --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.cc @@ -0,0 +1,76 @@ +#include "selfdrive/modeld/transforms/loadyuv.h" + +#include +#include +#include + +void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { + memset(s, 0, sizeof(*s)); + + s->width = width; + s->height = height; + + char args[1024]; + snprintf(args, sizeof(args), + "-cl-fast-relaxed-math -cl-denorms-are-zero " + "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", + width, height); + cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); + + s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); + s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); + s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); + + // done with this + CL_CHECK(clReleaseProgram(prg)); +} + +void loadyuv_destroy(LoadYUVState* s) { + CL_CHECK(clReleaseKernel(s->loadys_krnl)); + CL_CHECK(clReleaseKernel(s->loaduv_krnl)); + CL_CHECK(clReleaseKernel(s->copy_krnl)); +} + +void loadyuv_queue(LoadYUVState* s, cl_command_queue q, + cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, + cl_mem out_cl) { + cl_int global_out_off = 0; + + CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); + CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); + + const size_t loadys_work_size = (s->width*s->height)/8; + CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, + &loadys_work_size, NULL, 0, 0, NULL)); + + const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; + global_out_off += (s->width*s->height); + + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); + + CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, + &loaduv_work_size, NULL, 0, 0, NULL)); + + global_out_off += (s->width/2)*(s->height/2); + + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); + CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); + + CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, + &loaduv_work_size, NULL, 0, 0, NULL)); +} + +void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, + size_t src_offset, size_t dst_offset, size_t size) { + CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); + CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); + const size_t copy_work_size = size/8; + CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, + ©_work_size, NULL, 0, 0, NULL)); +} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl new file mode 100644 index 00000000000..970187a6d70 --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.cl @@ -0,0 +1,47 @@ +#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) + +__kernel void loadys(__global uchar8 const * const Y, + __global uchar * out, + int out_offset) +{ + const int gid = get_global_id(0); + const int ois = gid * 8; + const int oy = ois / TRANSFORMED_WIDTH; + const int ox = ois % TRANSFORMED_WIDTH; + + const uchar8 ys = Y[gid]; + + // 02 + // 13 + + __global uchar* outy0; + __global uchar* outy1; + if ((oy & 1) == 0) { + outy0 = out + out_offset; //y0 + outy1 = out + out_offset + UV_SIZE*2; //y2 + } else { + outy0 = out + out_offset + UV_SIZE; //y1 + outy1 = out + out_offset + UV_SIZE*3; //y3 + } + + vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); + vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); +} + +__kernel void loaduv(__global uchar8 const * const in, + __global uchar8 * out, + int out_offset) +{ + const int gid = get_global_id(0); + const uchar8 inv = in[gid]; + out[gid + out_offset / 8] = inv; +} + +__kernel void copy(__global uchar8 * in, + __global uchar8 * out, + int in_offset, + int out_offset) +{ + const int gid = get_global_id(0); + out[gid + out_offset / 8] = in[gid + in_offset / 8]; +} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h new file mode 100644 index 00000000000..659059cd25e --- /dev/null +++ b/selfdrive/modeld/transforms/loadyuv.h @@ -0,0 +1,20 @@ +#pragma once + +#include "common/clutil.h" + +typedef struct { + int width, height; + cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; +} LoadYUVState; + +void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); + +void loadyuv_destroy(LoadYUVState* s); + +void loadyuv_queue(LoadYUVState* s, cl_command_queue q, + cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, + cl_mem out_cl); + + +void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, + size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc new file mode 100644 index 00000000000..305643cf42e --- /dev/null +++ b/selfdrive/modeld/transforms/transform.cc @@ -0,0 +1,97 @@ +#include "selfdrive/modeld/transforms/transform.h" + +#include +#include + +#include "common/clutil.h" + +void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { + memset(s, 0, sizeof(*s)); + + cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); + s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); + // done with this + CL_CHECK(clReleaseProgram(prg)); + + s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); + s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); +} + +void transform_destroy(Transform* s) { + CL_CHECK(clReleaseMemObject(s->m_y_cl)); + CL_CHECK(clReleaseMemObject(s->m_uv_cl)); + CL_CHECK(clReleaseKernel(s->krnl)); +} + +void transform_queue(Transform* s, + cl_command_queue q, + cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, + cl_mem out_y, cl_mem out_u, cl_mem out_v, + int out_width, int out_height, + const mat3& projection) { + const int zero = 0; + + // sampled using pixel center origin + // (because that's how fastcv and opencv does it) + + mat3 projection_y = projection; + + // in and out uv is half the size of y. + mat3 projection_uv = transform_scale_buffer(projection, 0.5); + + CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); + CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); + + const int in_y_width = in_width; + const int in_y_height = in_height; + const int in_y_px_stride = 1; + const int in_uv_width = in_width/2; + const int in_uv_height = in_height/2; + const int in_uv_px_stride = 2; + const int in_u_offset = in_uv_offset; + const int in_v_offset = in_uv_offset + 1; + + const int out_y_width = out_width; + const int out_y_height = out_height; + const int out_uv_width = out_width/2; + const int out_uv_height = out_height/2; + + CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src + CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset + CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows + CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst + CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset + CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows + CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols + CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M + + const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_y, NULL, 0, 0, NULL)); + + const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; + + CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset + CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows + CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst + CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride + CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset + CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows + CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols + CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); + CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset + CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst + + CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, + (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); +} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl new file mode 100644 index 00000000000..2ca25920cd1 --- /dev/null +++ b/selfdrive/modeld/transforms/transform.cl @@ -0,0 +1,54 @@ +#define INTER_BITS 5 +#define INTER_TAB_SIZE (1 << INTER_BITS) +#define INTER_SCALE 1.f / INTER_TAB_SIZE + +#define INTER_REMAP_COEF_BITS 15 +#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) + +__kernel void warpPerspective(__global const uchar * src, + int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, + __global uchar * dst, + int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, + __constant float * M) +{ + int dx = get_global_id(0); + int dy = get_global_id(1); + + if (dx < dst_cols && dy < dst_rows) + { + float X0 = M[0] * dx + M[1] * dy + M[2]; + float Y0 = M[3] * dx + M[4] * dy + M[5]; + float W = M[6] * dx + M[7] * dy + M[8]; + W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; + int X = rint(X0 * W), Y = rint(Y0 * W); + + int sx = convert_short_sat(X >> INTER_BITS); + int sy = convert_short_sat(Y >> INTER_BITS); + + short sx_clamp = clamp(sx, 0, src_cols - 1); + short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); + short sy_clamp = clamp(sy, 0, src_rows - 1); + short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); + int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + + short ay = (short)(Y & (INTER_TAB_SIZE - 1)); + short ax = (short)(X & (INTER_TAB_SIZE - 1)); + float taby = 1.f/INTER_TAB_SIZE*ay; + float tabx = 1.f/INTER_TAB_SIZE*ax; + + int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); + + int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); + int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); + int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); + int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); + + int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; + + uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); + dst[dst_index] = pix; + } +} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h new file mode 100644 index 00000000000..771a7054b35 --- /dev/null +++ b/selfdrive/modeld/transforms/transform.h @@ -0,0 +1,25 @@ +#pragma once + +#define CL_USE_DEPRECATED_OPENCL_1_2_APIS +#ifdef __APPLE__ +#include +#else +#include +#endif + +#include "common/mat.h" + +typedef struct { + cl_kernel krnl; + cl_mem m_y_cl, m_uv_cl; +} Transform; + +void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); + +void transform_destroy(Transform* transform); + +void transform_queue(Transform* s, cl_command_queue q, + cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, + cl_mem out_y, cl_mem out_u, cl_mem out_v, + int out_width, int out_height, + const mat3& projection); diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 3f671f610d2..d35474f3732 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,8 +34,8 @@ EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.05, 0.028), - ("driverStateV2", 0.05, 0.015), + ("modelV2", 0.035, 0.025), + ("driverStateV2", 0.02, 0.015), ] def get_log_fn(test_route, ref="master"): diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 5143334bc61..8af72e5f4e7 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,7 +4,6 @@ import copy import heapq import signal -import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -24,7 +23,6 @@ from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -205,8 +203,7 @@ def _setup_vision_ipc(self, all_msgs: LogIterable, frs: dict[str, Any]): if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) - vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) + vipc_server.create_buffers(meta.stream, 2, *frame_size) vipc_server.start_listener() self.vipc_server = vipc_server @@ -303,15 +300,7 @@ def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - - h, w = frs[m.which()].h, frs[m.which()].w - stride, y_height, _, yuv_size = get_nv12_info(w, h) - uv_offset = stride * y_height - padded_img = np.zeros((yuv_size), dtype=np.uint8).reshape((-1, stride)) - padded_img[:h, :w] = img[:h * w].reshape((-1, w)) - padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) - - self.vipc_server.send(camera_meta.stream, padded_img.flatten().tobytes(), + self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] diff --git a/system/camerad/SConscript b/system/camerad/SConscript index c28330b32c4..e288c6d8b02 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, messaging, visionipc] +libs = [common, 'OpenCL', messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 329192b63ae..88bca7f775b 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,8 +21,9 @@ void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, Vis const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); + camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d buffers", frame_buf_count); + LOGD("allocated %d CL buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 7f35e06a835..c26859cbc40 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ class CameraBuf { CameraBuf() = default; ~CameraBuf(); - void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 6a7f599ab66..d741e13cf3b 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,8 +12,16 @@ #include #include +#ifdef __TICI__ +#include "CL/cl_ext_qcom.h" +#else +#define CL_PRIORITY_HINT_HIGH_QCOM NULL +#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL +#endif + #include "media/cam_sensor_cmn_header.h" +#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -49,7 +57,7 @@ class CameraState { CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v); + void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -60,8 +68,8 @@ class CameraState { } }; -void CameraState::init(VisionIpcServer *v) { - camera.camera_open(v); +void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { + camera.camera_open(v, device_id, ctx); if (!camera.enabled) return; @@ -249,7 +257,11 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - VisionIpcServer v("camerad"); + cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); + const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; + cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); + + VisionIpcServer v("camerad", device_id, ctx); // *** initial ISP init *** SpectraMaster m; @@ -259,7 +271,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v); + cam->init(&v, device_id, ctx); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 73e0a78da30..5c3e7a9d233 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v) { +void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v) { linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(this, v, ife_buf_depth, cc.stream_type); + buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index a02b8a6cac7..13cb13f98f6 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ class SpectraCamera { SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v); + void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index c4401c9583c..a92e4c8de8a 100644 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -32,7 +32,7 @@ def name(self): PROCS = [ Proc(['camerad'], 1.65, atol=0.4, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), - Proc(['modeld'], 1.5, atol=0.2, msgs=['modelV2']), + Proc(['modeld'], 1.24, atol=0.2, msgs=['modelV2']), Proc(['dmonitoringmodeld'], 0.65, atol=0.35, msgs=['driverStateV2']), Proc(['encoderd'], 0.23, msgs=[]), ] diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cc8ef7c88f6..cf169f4dc61 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,13 +2,16 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'avutil', - 'yuv', 'pthread', 'zstd'] + 'yuv', 'OpenCL', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] if arch == "Darwin": + # fix OpenCL + del libs[libs.index('OpenCL')] + env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h new file mode 100644 index 00000000000..0086319f5fa --- /dev/null +++ b/third_party/opencl/include/CL/cl.h @@ -0,0 +1,1452 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +#ifndef __OPENCL_CL_H +#define __OPENCL_CL_H + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ + +typedef struct _cl_platform_id * cl_platform_id; +typedef struct _cl_device_id * cl_device_id; +typedef struct _cl_context * cl_context; +typedef struct _cl_command_queue * cl_command_queue; +typedef struct _cl_mem * cl_mem; +typedef struct _cl_program * cl_program; +typedef struct _cl_kernel * cl_kernel; +typedef struct _cl_event * cl_event; +typedef struct _cl_sampler * cl_sampler; + +typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ +typedef cl_ulong cl_bitfield; +typedef cl_bitfield cl_device_type; +typedef cl_uint cl_platform_info; +typedef cl_uint cl_device_info; +typedef cl_bitfield cl_device_fp_config; +typedef cl_uint cl_device_mem_cache_type; +typedef cl_uint cl_device_local_mem_type; +typedef cl_bitfield cl_device_exec_capabilities; +typedef cl_bitfield cl_device_svm_capabilities; +typedef cl_bitfield cl_command_queue_properties; +typedef intptr_t cl_device_partition_property; +typedef cl_bitfield cl_device_affinity_domain; + +typedef intptr_t cl_context_properties; +typedef cl_uint cl_context_info; +typedef cl_bitfield cl_queue_properties; +typedef cl_uint cl_command_queue_info; +typedef cl_uint cl_channel_order; +typedef cl_uint cl_channel_type; +typedef cl_bitfield cl_mem_flags; +typedef cl_bitfield cl_svm_mem_flags; +typedef cl_uint cl_mem_object_type; +typedef cl_uint cl_mem_info; +typedef cl_bitfield cl_mem_migration_flags; +typedef cl_uint cl_image_info; +typedef cl_uint cl_buffer_create_type; +typedef cl_uint cl_addressing_mode; +typedef cl_uint cl_filter_mode; +typedef cl_uint cl_sampler_info; +typedef cl_bitfield cl_map_flags; +typedef intptr_t cl_pipe_properties; +typedef cl_uint cl_pipe_info; +typedef cl_uint cl_program_info; +typedef cl_uint cl_program_build_info; +typedef cl_uint cl_program_binary_type; +typedef cl_int cl_build_status; +typedef cl_uint cl_kernel_info; +typedef cl_uint cl_kernel_arg_info; +typedef cl_uint cl_kernel_arg_address_qualifier; +typedef cl_uint cl_kernel_arg_access_qualifier; +typedef cl_bitfield cl_kernel_arg_type_qualifier; +typedef cl_uint cl_kernel_work_group_info; +typedef cl_uint cl_kernel_sub_group_info; +typedef cl_uint cl_event_info; +typedef cl_uint cl_command_type; +typedef cl_uint cl_profiling_info; +typedef cl_bitfield cl_sampler_properties; +typedef cl_uint cl_kernel_exec_info; + +typedef struct _cl_image_format { + cl_channel_order image_channel_order; + cl_channel_type image_channel_data_type; +} cl_image_format; + +typedef struct _cl_image_desc { + cl_mem_object_type image_type; + size_t image_width; + size_t image_height; + size_t image_depth; + size_t image_array_size; + size_t image_row_pitch; + size_t image_slice_pitch; + cl_uint num_mip_levels; + cl_uint num_samples; +#ifdef __GNUC__ + __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ +#endif + union { + cl_mem buffer; + cl_mem mem_object; + }; +} cl_image_desc; + +typedef struct _cl_buffer_region { + size_t origin; + size_t size; +} cl_buffer_region; + + +/******************************************************************************/ + +/* Error Codes */ +#define CL_SUCCESS 0 +#define CL_DEVICE_NOT_FOUND -1 +#define CL_DEVICE_NOT_AVAILABLE -2 +#define CL_COMPILER_NOT_AVAILABLE -3 +#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 +#define CL_OUT_OF_RESOURCES -5 +#define CL_OUT_OF_HOST_MEMORY -6 +#define CL_PROFILING_INFO_NOT_AVAILABLE -7 +#define CL_MEM_COPY_OVERLAP -8 +#define CL_IMAGE_FORMAT_MISMATCH -9 +#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 +#define CL_BUILD_PROGRAM_FAILURE -11 +#define CL_MAP_FAILURE -12 +#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 +#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 +#define CL_COMPILE_PROGRAM_FAILURE -15 +#define CL_LINKER_NOT_AVAILABLE -16 +#define CL_LINK_PROGRAM_FAILURE -17 +#define CL_DEVICE_PARTITION_FAILED -18 +#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 + +#define CL_INVALID_VALUE -30 +#define CL_INVALID_DEVICE_TYPE -31 +#define CL_INVALID_PLATFORM -32 +#define CL_INVALID_DEVICE -33 +#define CL_INVALID_CONTEXT -34 +#define CL_INVALID_QUEUE_PROPERTIES -35 +#define CL_INVALID_COMMAND_QUEUE -36 +#define CL_INVALID_HOST_PTR -37 +#define CL_INVALID_MEM_OBJECT -38 +#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 +#define CL_INVALID_IMAGE_SIZE -40 +#define CL_INVALID_SAMPLER -41 +#define CL_INVALID_BINARY -42 +#define CL_INVALID_BUILD_OPTIONS -43 +#define CL_INVALID_PROGRAM -44 +#define CL_INVALID_PROGRAM_EXECUTABLE -45 +#define CL_INVALID_KERNEL_NAME -46 +#define CL_INVALID_KERNEL_DEFINITION -47 +#define CL_INVALID_KERNEL -48 +#define CL_INVALID_ARG_INDEX -49 +#define CL_INVALID_ARG_VALUE -50 +#define CL_INVALID_ARG_SIZE -51 +#define CL_INVALID_KERNEL_ARGS -52 +#define CL_INVALID_WORK_DIMENSION -53 +#define CL_INVALID_WORK_GROUP_SIZE -54 +#define CL_INVALID_WORK_ITEM_SIZE -55 +#define CL_INVALID_GLOBAL_OFFSET -56 +#define CL_INVALID_EVENT_WAIT_LIST -57 +#define CL_INVALID_EVENT -58 +#define CL_INVALID_OPERATION -59 +#define CL_INVALID_GL_OBJECT -60 +#define CL_INVALID_BUFFER_SIZE -61 +#define CL_INVALID_MIP_LEVEL -62 +#define CL_INVALID_GLOBAL_WORK_SIZE -63 +#define CL_INVALID_PROPERTY -64 +#define CL_INVALID_IMAGE_DESCRIPTOR -65 +#define CL_INVALID_COMPILER_OPTIONS -66 +#define CL_INVALID_LINKER_OPTIONS -67 +#define CL_INVALID_DEVICE_PARTITION_COUNT -68 +#define CL_INVALID_PIPE_SIZE -69 +#define CL_INVALID_DEVICE_QUEUE -70 + +/* OpenCL Version */ +#define CL_VERSION_1_0 1 +#define CL_VERSION_1_1 1 +#define CL_VERSION_1_2 1 +#define CL_VERSION_2_0 1 +#define CL_VERSION_2_1 1 + +/* cl_bool */ +#define CL_FALSE 0 +#define CL_TRUE 1 +#define CL_BLOCKING CL_TRUE +#define CL_NON_BLOCKING CL_FALSE + +/* cl_platform_info */ +#define CL_PLATFORM_PROFILE 0x0900 +#define CL_PLATFORM_VERSION 0x0901 +#define CL_PLATFORM_NAME 0x0902 +#define CL_PLATFORM_VENDOR 0x0903 +#define CL_PLATFORM_EXTENSIONS 0x0904 +#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 + +/* cl_device_type - bitfield */ +#define CL_DEVICE_TYPE_DEFAULT (1 << 0) +#define CL_DEVICE_TYPE_CPU (1 << 1) +#define CL_DEVICE_TYPE_GPU (1 << 2) +#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) +#define CL_DEVICE_TYPE_CUSTOM (1 << 4) +#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF + +/* cl_device_info */ +#define CL_DEVICE_TYPE 0x1000 +#define CL_DEVICE_VENDOR_ID 0x1001 +#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 +#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 +#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 +#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B +#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C +#define CL_DEVICE_ADDRESS_BITS 0x100D +#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E +#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F +#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 +#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 +#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 +#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 +#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 +#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 +#define CL_DEVICE_IMAGE_SUPPORT 0x1016 +#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 +#define CL_DEVICE_MAX_SAMPLERS 0x1018 +#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 +#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A +#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B +#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C +#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D +#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E +#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F +#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 +#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 +#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 +#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 +#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 +#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 +#define CL_DEVICE_ENDIAN_LITTLE 0x1026 +#define CL_DEVICE_AVAILABLE 0x1027 +#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 +#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 +#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ +#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A +#define CL_DEVICE_NAME 0x102B +#define CL_DEVICE_VENDOR 0x102C +#define CL_DRIVER_VERSION 0x102D +#define CL_DEVICE_PROFILE 0x102E +#define CL_DEVICE_VERSION 0x102F +#define CL_DEVICE_EXTENSIONS 0x1030 +#define CL_DEVICE_PLATFORM 0x1031 +#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 +/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ +#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 +#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B +#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C +#define CL_DEVICE_OPENCL_C_VERSION 0x103D +#define CL_DEVICE_LINKER_AVAILABLE 0x103E +#define CL_DEVICE_BUILT_IN_KERNELS 0x103F +#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 +#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 +#define CL_DEVICE_PARENT_DEVICE 0x1042 +#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 +#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 +#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 +#define CL_DEVICE_PARTITION_TYPE 0x1046 +#define CL_DEVICE_REFERENCE_COUNT 0x1047 +#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 +#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 +#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A +#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B +#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C +#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D +#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E +#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F +#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 +#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 +#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 +#define CL_DEVICE_SVM_CAPABILITIES 0x1053 +#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 +#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 +#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 +#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 +#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 +#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 +#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A +#define CL_DEVICE_IL_VERSION 0x105B +#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C +#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D + +/* cl_device_fp_config - bitfield */ +#define CL_FP_DENORM (1 << 0) +#define CL_FP_INF_NAN (1 << 1) +#define CL_FP_ROUND_TO_NEAREST (1 << 2) +#define CL_FP_ROUND_TO_ZERO (1 << 3) +#define CL_FP_ROUND_TO_INF (1 << 4) +#define CL_FP_FMA (1 << 5) +#define CL_FP_SOFT_FLOAT (1 << 6) +#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) + +/* cl_device_mem_cache_type */ +#define CL_NONE 0x0 +#define CL_READ_ONLY_CACHE 0x1 +#define CL_READ_WRITE_CACHE 0x2 + +/* cl_device_local_mem_type */ +#define CL_LOCAL 0x1 +#define CL_GLOBAL 0x2 + +/* cl_device_exec_capabilities - bitfield */ +#define CL_EXEC_KERNEL (1 << 0) +#define CL_EXEC_NATIVE_KERNEL (1 << 1) + +/* cl_command_queue_properties - bitfield */ +#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) +#define CL_QUEUE_PROFILING_ENABLE (1 << 1) +#define CL_QUEUE_ON_DEVICE (1 << 2) +#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) + +/* cl_context_info */ +#define CL_CONTEXT_REFERENCE_COUNT 0x1080 +#define CL_CONTEXT_DEVICES 0x1081 +#define CL_CONTEXT_PROPERTIES 0x1082 +#define CL_CONTEXT_NUM_DEVICES 0x1083 + +/* cl_context_properties */ +#define CL_CONTEXT_PLATFORM 0x1084 +#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 + +/* cl_device_partition_property */ +#define CL_DEVICE_PARTITION_EQUALLY 0x1086 +#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 +#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 +#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 + +/* cl_device_affinity_domain */ +#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) +#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) +#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) +#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) +#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) +#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) + +/* cl_device_svm_capabilities */ +#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) +#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) +#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) +#define CL_DEVICE_SVM_ATOMICS (1 << 3) + +/* cl_command_queue_info */ +#define CL_QUEUE_CONTEXT 0x1090 +#define CL_QUEUE_DEVICE 0x1091 +#define CL_QUEUE_REFERENCE_COUNT 0x1092 +#define CL_QUEUE_PROPERTIES 0x1093 +#define CL_QUEUE_SIZE 0x1094 +#define CL_QUEUE_DEVICE_DEFAULT 0x1095 + +/* cl_mem_flags and cl_svm_mem_flags - bitfield */ +#define CL_MEM_READ_WRITE (1 << 0) +#define CL_MEM_WRITE_ONLY (1 << 1) +#define CL_MEM_READ_ONLY (1 << 2) +#define CL_MEM_USE_HOST_PTR (1 << 3) +#define CL_MEM_ALLOC_HOST_PTR (1 << 4) +#define CL_MEM_COPY_HOST_PTR (1 << 5) +/* reserved (1 << 6) */ +#define CL_MEM_HOST_WRITE_ONLY (1 << 7) +#define CL_MEM_HOST_READ_ONLY (1 << 8) +#define CL_MEM_HOST_NO_ACCESS (1 << 9) +#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ +#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ +#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) + +/* cl_mem_migration_flags - bitfield */ +#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) +#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) + +/* cl_channel_order */ +#define CL_R 0x10B0 +#define CL_A 0x10B1 +#define CL_RG 0x10B2 +#define CL_RA 0x10B3 +#define CL_RGB 0x10B4 +#define CL_RGBA 0x10B5 +#define CL_BGRA 0x10B6 +#define CL_ARGB 0x10B7 +#define CL_INTENSITY 0x10B8 +#define CL_LUMINANCE 0x10B9 +#define CL_Rx 0x10BA +#define CL_RGx 0x10BB +#define CL_RGBx 0x10BC +#define CL_DEPTH 0x10BD +#define CL_DEPTH_STENCIL 0x10BE +#define CL_sRGB 0x10BF +#define CL_sRGBx 0x10C0 +#define CL_sRGBA 0x10C1 +#define CL_sBGRA 0x10C2 +#define CL_ABGR 0x10C3 + +/* cl_channel_type */ +#define CL_SNORM_INT8 0x10D0 +#define CL_SNORM_INT16 0x10D1 +#define CL_UNORM_INT8 0x10D2 +#define CL_UNORM_INT16 0x10D3 +#define CL_UNORM_SHORT_565 0x10D4 +#define CL_UNORM_SHORT_555 0x10D5 +#define CL_UNORM_INT_101010 0x10D6 +#define CL_SIGNED_INT8 0x10D7 +#define CL_SIGNED_INT16 0x10D8 +#define CL_SIGNED_INT32 0x10D9 +#define CL_UNSIGNED_INT8 0x10DA +#define CL_UNSIGNED_INT16 0x10DB +#define CL_UNSIGNED_INT32 0x10DC +#define CL_HALF_FLOAT 0x10DD +#define CL_FLOAT 0x10DE +#define CL_UNORM_INT24 0x10DF +#define CL_UNORM_INT_101010_2 0x10E0 + +/* cl_mem_object_type */ +#define CL_MEM_OBJECT_BUFFER 0x10F0 +#define CL_MEM_OBJECT_IMAGE2D 0x10F1 +#define CL_MEM_OBJECT_IMAGE3D 0x10F2 +#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 +#define CL_MEM_OBJECT_IMAGE1D 0x10F4 +#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 +#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 +#define CL_MEM_OBJECT_PIPE 0x10F7 + +/* cl_mem_info */ +#define CL_MEM_TYPE 0x1100 +#define CL_MEM_FLAGS 0x1101 +#define CL_MEM_SIZE 0x1102 +#define CL_MEM_HOST_PTR 0x1103 +#define CL_MEM_MAP_COUNT 0x1104 +#define CL_MEM_REFERENCE_COUNT 0x1105 +#define CL_MEM_CONTEXT 0x1106 +#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 +#define CL_MEM_OFFSET 0x1108 +#define CL_MEM_USES_SVM_POINTER 0x1109 + +/* cl_image_info */ +#define CL_IMAGE_FORMAT 0x1110 +#define CL_IMAGE_ELEMENT_SIZE 0x1111 +#define CL_IMAGE_ROW_PITCH 0x1112 +#define CL_IMAGE_SLICE_PITCH 0x1113 +#define CL_IMAGE_WIDTH 0x1114 +#define CL_IMAGE_HEIGHT 0x1115 +#define CL_IMAGE_DEPTH 0x1116 +#define CL_IMAGE_ARRAY_SIZE 0x1117 +#define CL_IMAGE_BUFFER 0x1118 +#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 +#define CL_IMAGE_NUM_SAMPLES 0x111A + +/* cl_pipe_info */ +#define CL_PIPE_PACKET_SIZE 0x1120 +#define CL_PIPE_MAX_PACKETS 0x1121 + +/* cl_addressing_mode */ +#define CL_ADDRESS_NONE 0x1130 +#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 +#define CL_ADDRESS_CLAMP 0x1132 +#define CL_ADDRESS_REPEAT 0x1133 +#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 + +/* cl_filter_mode */ +#define CL_FILTER_NEAREST 0x1140 +#define CL_FILTER_LINEAR 0x1141 + +/* cl_sampler_info */ +#define CL_SAMPLER_REFERENCE_COUNT 0x1150 +#define CL_SAMPLER_CONTEXT 0x1151 +#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 +#define CL_SAMPLER_ADDRESSING_MODE 0x1153 +#define CL_SAMPLER_FILTER_MODE 0x1154 +#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 +#define CL_SAMPLER_LOD_MIN 0x1156 +#define CL_SAMPLER_LOD_MAX 0x1157 + +/* cl_map_flags - bitfield */ +#define CL_MAP_READ (1 << 0) +#define CL_MAP_WRITE (1 << 1) +#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) + +/* cl_program_info */ +#define CL_PROGRAM_REFERENCE_COUNT 0x1160 +#define CL_PROGRAM_CONTEXT 0x1161 +#define CL_PROGRAM_NUM_DEVICES 0x1162 +#define CL_PROGRAM_DEVICES 0x1163 +#define CL_PROGRAM_SOURCE 0x1164 +#define CL_PROGRAM_BINARY_SIZES 0x1165 +#define CL_PROGRAM_BINARIES 0x1166 +#define CL_PROGRAM_NUM_KERNELS 0x1167 +#define CL_PROGRAM_KERNEL_NAMES 0x1168 +#define CL_PROGRAM_IL 0x1169 + +/* cl_program_build_info */ +#define CL_PROGRAM_BUILD_STATUS 0x1181 +#define CL_PROGRAM_BUILD_OPTIONS 0x1182 +#define CL_PROGRAM_BUILD_LOG 0x1183 +#define CL_PROGRAM_BINARY_TYPE 0x1184 +#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 + +/* cl_program_binary_type */ +#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 +#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 +#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 +#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 + +/* cl_build_status */ +#define CL_BUILD_SUCCESS 0 +#define CL_BUILD_NONE -1 +#define CL_BUILD_ERROR -2 +#define CL_BUILD_IN_PROGRESS -3 + +/* cl_kernel_info */ +#define CL_KERNEL_FUNCTION_NAME 0x1190 +#define CL_KERNEL_NUM_ARGS 0x1191 +#define CL_KERNEL_REFERENCE_COUNT 0x1192 +#define CL_KERNEL_CONTEXT 0x1193 +#define CL_KERNEL_PROGRAM 0x1194 +#define CL_KERNEL_ATTRIBUTES 0x1195 +#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 +#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA + +/* cl_kernel_arg_info */ +#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 +#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 +#define CL_KERNEL_ARG_TYPE_NAME 0x1198 +#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 +#define CL_KERNEL_ARG_NAME 0x119A + +/* cl_kernel_arg_address_qualifier */ +#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B +#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C +#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D +#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E + +/* cl_kernel_arg_access_qualifier */ +#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 +#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 +#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 +#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 + +/* cl_kernel_arg_type_qualifer */ +#define CL_KERNEL_ARG_TYPE_NONE 0 +#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) +#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) +#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) +#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) + +/* cl_kernel_work_group_info */ +#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 +#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 +#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 +#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 +#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 +#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 + +/* cl_kernel_sub_group_info */ +#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 +#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 +#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 + +/* cl_kernel_exec_info */ +#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 +#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 + +/* cl_event_info */ +#define CL_EVENT_COMMAND_QUEUE 0x11D0 +#define CL_EVENT_COMMAND_TYPE 0x11D1 +#define CL_EVENT_REFERENCE_COUNT 0x11D2 +#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 +#define CL_EVENT_CONTEXT 0x11D4 + +/* cl_command_type */ +#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 +#define CL_COMMAND_TASK 0x11F1 +#define CL_COMMAND_NATIVE_KERNEL 0x11F2 +#define CL_COMMAND_READ_BUFFER 0x11F3 +#define CL_COMMAND_WRITE_BUFFER 0x11F4 +#define CL_COMMAND_COPY_BUFFER 0x11F5 +#define CL_COMMAND_READ_IMAGE 0x11F6 +#define CL_COMMAND_WRITE_IMAGE 0x11F7 +#define CL_COMMAND_COPY_IMAGE 0x11F8 +#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 +#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA +#define CL_COMMAND_MAP_BUFFER 0x11FB +#define CL_COMMAND_MAP_IMAGE 0x11FC +#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD +#define CL_COMMAND_MARKER 0x11FE +#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF +#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 +#define CL_COMMAND_READ_BUFFER_RECT 0x1201 +#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 +#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 +#define CL_COMMAND_USER 0x1204 +#define CL_COMMAND_BARRIER 0x1205 +#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 +#define CL_COMMAND_FILL_BUFFER 0x1207 +#define CL_COMMAND_FILL_IMAGE 0x1208 +#define CL_COMMAND_SVM_FREE 0x1209 +#define CL_COMMAND_SVM_MEMCPY 0x120A +#define CL_COMMAND_SVM_MEMFILL 0x120B +#define CL_COMMAND_SVM_MAP 0x120C +#define CL_COMMAND_SVM_UNMAP 0x120D + +/* command execution status */ +#define CL_COMPLETE 0x0 +#define CL_RUNNING 0x1 +#define CL_SUBMITTED 0x2 +#define CL_QUEUED 0x3 + +/* cl_buffer_create_type */ +#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 + +/* cl_profiling_info */ +#define CL_PROFILING_COMMAND_QUEUED 0x1280 +#define CL_PROFILING_COMMAND_SUBMIT 0x1281 +#define CL_PROFILING_COMMAND_START 0x1282 +#define CL_PROFILING_COMMAND_END 0x1283 +#define CL_PROFILING_COMMAND_COMPLETE 0x1284 + +/********************************************************************************************************/ + +/* Platform API */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPlatformIDs(cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPlatformInfo(cl_platform_id /* platform */, + cl_platform_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Device APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceIDs(cl_platform_id /* platform */, + cl_device_type /* device_type */, + cl_uint /* num_entries */, + cl_device_id * /* devices */, + cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceInfo(cl_device_id /* device */, + cl_device_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCreateSubDevices(cl_device_id /* in_device */, + const cl_device_partition_property * /* properties */, + cl_uint /* num_devices */, + cl_device_id * /* out_devices */, + cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetDefaultDeviceCommandQueue(cl_context /* context */, + cl_device_id /* device */, + cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceAndHostTimer(cl_device_id /* device */, + cl_ulong* /* device_timestamp */, + cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetHostTimer(cl_device_id /* device */, + cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; + + +/* Context APIs */ +extern CL_API_ENTRY cl_context CL_API_CALL +clCreateContext(const cl_context_properties * /* properties */, + cl_uint /* num_devices */, + const cl_device_id * /* devices */, + void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), + void * /* user_data */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_context CL_API_CALL +clCreateContextFromType(const cl_context_properties * /* properties */, + cl_device_type /* device_type */, + void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), + void * /* user_data */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetContextInfo(cl_context /* context */, + cl_context_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Command Queue APIs */ +extern CL_API_ENTRY cl_command_queue CL_API_CALL +clCreateCommandQueueWithProperties(cl_context /* context */, + cl_device_id /* device */, + const cl_queue_properties * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetCommandQueueInfo(cl_command_queue /* command_queue */, + cl_command_queue_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Memory Object APIs */ +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateBuffer(cl_context /* context */, + cl_mem_flags /* flags */, + size_t /* size */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateSubBuffer(cl_mem /* buffer */, + cl_mem_flags /* flags */, + cl_buffer_create_type /* buffer_create_type */, + const void * /* buffer_create_info */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateImage(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + const cl_image_desc * /* image_desc */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreatePipe(cl_context /* context */, + cl_mem_flags /* flags */, + cl_uint /* pipe_packet_size */, + cl_uint /* pipe_max_packets */, + const cl_pipe_properties * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetSupportedImageFormats(cl_context /* context */, + cl_mem_flags /* flags */, + cl_mem_object_type /* image_type */, + cl_uint /* num_entries */, + cl_image_format * /* image_formats */, + cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetMemObjectInfo(cl_mem /* memobj */, + cl_mem_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetImageInfo(cl_mem /* image */, + cl_image_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetPipeInfo(cl_mem /* pipe */, + cl_pipe_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetMemObjectDestructorCallback(cl_mem /* memobj */, + void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), + void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; + +/* SVM Allocation APIs */ +extern CL_API_ENTRY void * CL_API_CALL +clSVMAlloc(cl_context /* context */, + cl_svm_mem_flags /* flags */, + size_t /* size */, + cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY void CL_API_CALL +clSVMFree(cl_context /* context */, + void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; + +/* Sampler APIs */ +extern CL_API_ENTRY cl_sampler CL_API_CALL +clCreateSamplerWithProperties(cl_context /* context */, + const cl_sampler_properties * /* normalized_coords */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetSamplerInfo(cl_sampler /* sampler */, + cl_sampler_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Program Object APIs */ +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithSource(cl_context /* context */, + cl_uint /* count */, + const char ** /* strings */, + const size_t * /* lengths */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithBinary(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const size_t * /* lengths */, + const unsigned char ** /* binaries */, + cl_int * /* binary_status */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithBuiltInKernels(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* kernel_names */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_program CL_API_CALL +clCreateProgramWithIL(cl_context /* context */, + const void* /* il */, + size_t /* length */, + cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clBuildProgram(cl_program /* program */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCompileProgram(cl_program /* program */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + cl_uint /* num_input_headers */, + const cl_program * /* input_headers */, + const char ** /* header_include_names */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_program CL_API_CALL +clLinkProgram(cl_context /* context */, + cl_uint /* num_devices */, + const cl_device_id * /* device_list */, + const char * /* options */, + cl_uint /* num_input_programs */, + const cl_program * /* input_programs */, + void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), + void * /* user_data */, + cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; + + +extern CL_API_ENTRY cl_int CL_API_CALL +clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetProgramInfo(cl_program /* program */, + cl_program_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetProgramBuildInfo(cl_program /* program */, + cl_device_id /* device */, + cl_program_build_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Kernel Object APIs */ +extern CL_API_ENTRY cl_kernel CL_API_CALL +clCreateKernel(cl_program /* program */, + const char * /* kernel_name */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clCreateKernelsInProgram(cl_program /* program */, + cl_uint /* num_kernels */, + cl_kernel * /* kernels */, + cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_kernel CL_API_CALL +clCloneKernel(cl_kernel /* source_kernel */, + cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelArg(cl_kernel /* kernel */, + cl_uint /* arg_index */, + size_t /* arg_size */, + const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelArgSVMPointer(cl_kernel /* kernel */, + cl_uint /* arg_index */, + const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetKernelExecInfo(cl_kernel /* kernel */, + cl_kernel_exec_info /* param_name */, + size_t /* param_value_size */, + const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelInfo(cl_kernel /* kernel */, + cl_kernel_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelArgInfo(cl_kernel /* kernel */, + cl_uint /* arg_indx */, + cl_kernel_arg_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelWorkGroupInfo(cl_kernel /* kernel */, + cl_device_id /* device */, + cl_kernel_work_group_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelSubGroupInfo(cl_kernel /* kernel */, + cl_device_id /* device */, + cl_kernel_sub_group_info /* param_name */, + size_t /* input_value_size */, + const void* /*input_value */, + size_t /* param_value_size */, + void* /* param_value */, + size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; + + +/* Event Object APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clWaitForEvents(cl_uint /* num_events */, + const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetEventInfo(cl_event /* event */, + cl_event_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateUserEvent(cl_context /* context */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetUserEventStatus(cl_event /* event */, + cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetEventCallback( cl_event /* event */, + cl_int /* command_exec_callback_type */, + void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), + void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; + +/* Profiling APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clGetEventProfilingInfo(cl_event /* event */, + cl_profiling_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +/* Flush and Finish APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; + +/* Enqueued Commands APIs */ +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_read */, + size_t /* offset */, + size_t /* size */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadBufferRect(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_read */, + const size_t * /* buffer_offset */, + const size_t * /* host_offset */, + const size_t * /* region */, + size_t /* buffer_row_pitch */, + size_t /* buffer_slice_pitch */, + size_t /* host_row_pitch */, + size_t /* host_slice_pitch */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_write */, + size_t /* offset */, + size_t /* size */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_write */, + const size_t * /* buffer_offset */, + const size_t * /* host_offset */, + const size_t * /* region */, + size_t /* buffer_row_pitch */, + size_t /* buffer_slice_pitch */, + size_t /* host_row_pitch */, + size_t /* host_slice_pitch */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueFillBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + const void * /* pattern */, + size_t /* pattern_size */, + size_t /* offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBuffer(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_buffer */, + size_t /* src_offset */, + size_t /* dst_offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_buffer */, + const size_t * /* src_origin */, + const size_t * /* dst_origin */, + const size_t * /* region */, + size_t /* src_row_pitch */, + size_t /* src_slice_pitch */, + size_t /* dst_row_pitch */, + size_t /* dst_slice_pitch */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReadImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_read */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t /* row_pitch */, + size_t /* slice_pitch */, + void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueWriteImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_write */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t /* input_row_pitch */, + size_t /* input_slice_pitch */, + const void * /* ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueFillImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + const void * /* fill_color */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyImage(cl_command_queue /* command_queue */, + cl_mem /* src_image */, + cl_mem /* dst_image */, + const size_t * /* src_origin[3] */, + const size_t * /* dst_origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, + cl_mem /* src_image */, + cl_mem /* dst_buffer */, + const size_t * /* src_origin[3] */, + const size_t * /* region[3] */, + size_t /* dst_offset */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, + cl_mem /* src_buffer */, + cl_mem /* dst_image */, + size_t /* src_offset */, + const size_t * /* dst_origin[3] */, + const size_t * /* region[3] */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY void * CL_API_CALL +clEnqueueMapBuffer(cl_command_queue /* command_queue */, + cl_mem /* buffer */, + cl_bool /* blocking_map */, + cl_map_flags /* map_flags */, + size_t /* offset */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY void * CL_API_CALL +clEnqueueMapImage(cl_command_queue /* command_queue */, + cl_mem /* image */, + cl_bool /* blocking_map */, + cl_map_flags /* map_flags */, + const size_t * /* origin[3] */, + const size_t * /* region[3] */, + size_t * /* image_row_pitch */, + size_t * /* image_slice_pitch */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, + cl_mem /* memobj */, + void * /* mapped_ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, + cl_uint /* num_mem_objects */, + const cl_mem * /* mem_objects */, + cl_mem_migration_flags /* flags */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, + cl_kernel /* kernel */, + cl_uint /* work_dim */, + const size_t * /* global_work_offset */, + const size_t * /* global_work_size */, + const size_t * /* local_work_size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueNativeKernel(cl_command_queue /* command_queue */, + void (CL_CALLBACK * /*user_func*/)(void *), + void * /* args */, + size_t /* cb_args */, + cl_uint /* num_mem_objects */, + const cl_mem * /* mem_list */, + const void ** /* args_mem_loc */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMFree(cl_command_queue /* command_queue */, + cl_uint /* num_svm_pointers */, + void *[] /* svm_pointers[] */, + void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, + cl_uint /* num_svm_pointers */, + void *[] /* svm_pointers[] */, + void * /* user_data */), + void * /* user_data */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, + cl_bool /* blocking_copy */, + void * /* dst_ptr */, + const void * /* src_ptr */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMemFill(cl_command_queue /* command_queue */, + void * /* svm_ptr */, + const void * /* pattern */, + size_t /* pattern_size */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMap(cl_command_queue /* command_queue */, + cl_bool /* blocking_map */, + cl_map_flags /* flags */, + void * /* svm_ptr */, + size_t /* size */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMUnmap(cl_command_queue /* command_queue */, + void * /* svm_ptr */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, + cl_uint /* num_svm_pointers */, + const void ** /* svm_pointers */, + const size_t * /* sizes */, + cl_mem_migration_flags /* flags */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; + + +/* Extension function access + * + * Returns the extension function address for the given function name, + * or NULL if a valid function can not be found. The client must + * check to make sure the address is not NULL, before using or + * calling the returned function address. + */ +extern CL_API_ENTRY void * CL_API_CALL +clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, + const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; + + +/* Deprecated OpenCL 1.1 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateImage2D(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + size_t /* image_width */, + size_t /* image_height */, + size_t /* image_row_pitch */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateImage3D(cl_context /* context */, + cl_mem_flags /* flags */, + const cl_image_format * /* image_format */, + size_t /* image_width */, + size_t /* image_height */, + size_t /* image_depth */, + size_t /* image_row_pitch */, + size_t /* image_slice_pitch */, + void * /* host_ptr */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueMarker(cl_command_queue /* command_queue */, + cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueWaitForEvents(cl_command_queue /* command_queue */, + cl_uint /* num_events */, + const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL +clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL +clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +/* Deprecated OpenCL 2.0 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL +clCreateCommandQueue(cl_context /* context */, + cl_device_id /* device */, + cl_command_queue_properties /* properties */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL +clCreateSampler(cl_context /* context */, + cl_bool /* normalized_coords */, + cl_addressing_mode /* addressing_mode */, + cl_filter_mode /* filter_mode */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL +clEnqueueTask(cl_command_queue /* command_queue */, + cl_kernel /* kernel */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_H */ + diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h new file mode 100644 index 00000000000..d5960a43f72 --- /dev/null +++ b/third_party/opencl/include/CL/cl_d3d10.h @@ -0,0 +1,131 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_D3D10_H +#define __OPENCL_CL_D3D10_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * cl_khr_d3d10_sharing */ +#define cl_khr_d3d10_sharing 1 + +typedef cl_uint cl_d3d10_device_source_khr; +typedef cl_uint cl_d3d10_device_set_khr; + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_D3D10_DEVICE_KHR -1002 +#define CL_INVALID_D3D10_RESOURCE_KHR -1003 +#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 +#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 + +/* cl_d3d10_device_source_nv */ +#define CL_D3D10_DEVICE_KHR 0x4010 +#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 + +/* cl_d3d10_device_set_nv */ +#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 +#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 + +/* cl_context_info */ +#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 +#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C + +/* cl_mem_info */ +#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 + +/* cl_image_info */ +#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 +#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( + cl_platform_id platform, + cl_d3d10_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d10_device_set_khr d3d_device_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Buffer * resource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Texture2D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D10Texture3D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_0; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_D3D10_H */ + diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h new file mode 100644 index 00000000000..39f9072398a --- /dev/null +++ b/third_party/opencl/include/CL/cl_d3d11.h @@ -0,0 +1,131 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_D3D11_H +#define __OPENCL_CL_D3D11_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/****************************************************************************** + * cl_khr_d3d11_sharing */ +#define cl_khr_d3d11_sharing 1 + +typedef cl_uint cl_d3d11_device_source_khr; +typedef cl_uint cl_d3d11_device_set_khr; + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_D3D11_DEVICE_KHR -1006 +#define CL_INVALID_D3D11_RESOURCE_KHR -1007 +#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 +#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 + +/* cl_d3d11_device_source */ +#define CL_D3D11_DEVICE_KHR 0x4019 +#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A + +/* cl_d3d11_device_set */ +#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B +#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C + +/* cl_context_info */ +#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D +#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D + +/* cl_mem_info */ +#define CL_MEM_D3D11_RESOURCE_KHR 0x401E + +/* cl_image_info */ +#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 +#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( + cl_platform_id platform, + cl_d3d11_device_source_khr d3d_device_source, + void * d3d_object, + cl_d3d11_device_set_khr d3d_device_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Buffer * resource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Texture2D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( + cl_context context, + cl_mem_flags flags, + ID3D11Texture3D * resource, + UINT subresource, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_D3D11_H */ + diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h new file mode 100644 index 00000000000..2729e8b9e89 --- /dev/null +++ b/third_party/opencl/include/CL/cl_dx9_media_sharing.h @@ -0,0 +1,132 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H +#define __OPENCL_CL_DX9_MEDIA_SHARING_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/******************************************************************************/ +/* cl_khr_dx9_media_sharing */ +#define cl_khr_dx9_media_sharing 1 + +typedef cl_uint cl_dx9_media_adapter_type_khr; +typedef cl_uint cl_dx9_media_adapter_set_khr; + +#if defined(_WIN32) +#include +typedef struct _cl_dx9_surface_info_khr +{ + IDirect3DSurface9 *resource; + HANDLE shared_handle; +} cl_dx9_surface_info_khr; +#endif + + +/******************************************************************************/ + +/* Error Codes */ +#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 +#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 +#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 +#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 + +/* cl_media_adapter_type_khr */ +#define CL_ADAPTER_D3D9_KHR 0x2020 +#define CL_ADAPTER_D3D9EX_KHR 0x2021 +#define CL_ADAPTER_DXVA_KHR 0x2022 + +/* cl_media_adapter_set_khr */ +#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 +#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 + +/* cl_context_info */ +#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 +#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 +#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 + +/* cl_mem_info */ +#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 +#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 + +/* cl_image_info */ +#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A + +/* cl_command_type */ +#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B +#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C + +/******************************************************************************/ + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( + cl_platform_id platform, + cl_uint num_media_adapters, + cl_dx9_media_adapter_type_khr * media_adapter_type, + void * media_adapters, + cl_dx9_media_adapter_set_khr media_adapter_set, + cl_uint num_entries, + cl_device_id * devices, + cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( + cl_context context, + cl_mem_flags flags, + cl_dx9_media_adapter_type_khr adapter_type, + void * surface_info, + cl_uint plane, + cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event) CL_API_SUFFIX__VERSION_1_2; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ + diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h new file mode 100644 index 00000000000..a765bd5266c --- /dev/null +++ b/third_party/opencl/include/CL/cl_egl.h @@ -0,0 +1,136 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +#ifndef __OPENCL_CL_EGL_H +#define __OPENCL_CL_EGL_H + +#ifdef __APPLE__ + +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ +#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F +#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D +#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E + +/* Error type for clCreateFromEGLImageKHR */ +#define CL_INVALID_EGL_OBJECT_KHR -1093 +#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 + +/* CLeglImageKHR is an opaque handle to an EGLImage */ +typedef void* CLeglImageKHR; + +/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ +typedef void* CLeglDisplayKHR; + +/* CLeglSyncKHR is an opaque handle to an EGLSync object */ +typedef void* CLeglSyncKHR; + +/* properties passed to clCreateFromEGLImageKHR */ +typedef intptr_t cl_egl_image_properties_khr; + + +#define cl_khr_egl_image 1 + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromEGLImageKHR(cl_context /* context */, + CLeglDisplayKHR /* egldisplay */, + CLeglImageKHR /* eglimage */, + cl_mem_flags /* flags */, + const cl_egl_image_properties_khr * /* properties */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( + cl_context context, + CLeglDisplayKHR egldisplay, + CLeglImageKHR eglimage, + cl_mem_flags flags, + const cl_egl_image_properties_khr * properties, + cl_int * errcode_ret); + + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event); + + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( + cl_command_queue command_queue, + cl_uint num_objects, + const cl_mem * mem_objects, + cl_uint num_events_in_wait_list, + const cl_event * event_wait_list, + cl_event * event); + + +#define cl_khr_egl_event 1 + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateEventFromEGLSyncKHR(cl_context /* context */, + CLeglSyncKHR /* sync */, + CLeglDisplayKHR /* display */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( + cl_context context, + CLeglSyncKHR sync, + CLeglDisplayKHR display, + cl_int * errcode_ret); + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h new file mode 100644 index 00000000000..7941583895c --- /dev/null +++ b/third_party/opencl/include/CL/cl_ext.h @@ -0,0 +1,391 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ + +/* cl_ext.h contains OpenCL extensions which don't have external */ +/* (OpenGL, D3D) dependencies. */ + +#ifndef __CL_EXT_H +#define __CL_EXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + #include + #include +#else + #include +#endif + +/* cl_khr_fp16 extension - no extension #define since it has no functions */ +#define CL_DEVICE_HALF_FP_CONFIG 0x1033 + +/* Memory object destruction + * + * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR + * + * Registers a user callback function that will be called when the memory object is deleted and its resources + * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback + * stack associated with memobj. The registered user callback functions are called in the reverse order in + * which they were registered. The user callback functions are called and then the memory object is deleted + * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be + * notified when the memory referenced by host_ptr, specified when the memory object is created and used as + * the storage bits for the memory object, can be reused or freed. + * + * The application may not call CL api's with the cl_mem object passed to the pfn_notify. + * + * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) + * before using. + */ +#define cl_APPLE_SetMemObjectDestructor 1 +cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, + void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), + void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + + +/* Context Logging Functions + * + * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). + * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) + * before using. + * + * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger + */ +#define cl_APPLE_ContextLoggingFunctions 1 +extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + +/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ +extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + +/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ +extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, + const void * /* private_info */, + size_t /* cb */, + void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; + + +/************************ +* cl_khr_icd extension * +************************/ +#define cl_khr_icd 1 + +/* cl_platform_info */ +#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 + +/* Additional Error Codes */ +#define CL_PLATFORM_NOT_FOUND_KHR -1001 + +extern CL_API_ENTRY cl_int CL_API_CALL +clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */); + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( + cl_uint /* num_entries */, + cl_platform_id * /* platforms */, + cl_uint * /* num_platforms */); + + +/* Extension: cl_khr_image2D_buffer + * + * This extension allows a 2D image to be created from a cl_mem buffer without a copy. + * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. + * Both the sampler and sampler-less read_image built-in functions are supported for 2D images + * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported + * for 2D images created from a buffer. + * + * When the 2D image from buffer is created, the client must specify the width, + * height, image format (i.e. channel order and channel data type) and optionally the row pitch + * + * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. + * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. + */ + +/************************************* + * cl_khr_initalize_memory extension * + *************************************/ + +#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 + + +/************************************** + * cl_khr_terminate_context extension * + **************************************/ + +#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 +#define CL_CONTEXT_TERMINATE_KHR 0x2032 + +#define cl_khr_terminate_context 1 +extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; + + +/* + * Extension: cl_khr_spir + * + * This extension adds support to create an OpenCL program object from a + * Standard Portable Intermediate Representation (SPIR) instance + */ + +#define CL_DEVICE_SPIR_VERSIONS 0x40E0 +#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 + + +/****************************************** +* cl_nv_device_attribute_query extension * +******************************************/ +/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ +#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 +#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 +#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 +#define CL_DEVICE_WARP_SIZE_NV 0x4003 +#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 +#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 +#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 + +/********************************* +* cl_amd_device_attribute_query * +*********************************/ +#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 + +/********************************* +* cl_arm_printf extension +*********************************/ +#define CL_PRINTF_CALLBACK_ARM 0x40B0 +#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 + +#ifdef CL_VERSION_1_1 + /*********************************** + * cl_ext_device_fission extension * + ***********************************/ + #define cl_ext_device_fission 1 + + extern CL_API_ENTRY cl_int CL_API_CALL + clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + extern CL_API_ENTRY cl_int CL_API_CALL + clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef cl_ulong cl_device_partition_property_ext; + extern CL_API_ENTRY cl_int CL_API_CALL + clCreateSubDevicesEXT( cl_device_id /*in_device*/, + const cl_device_partition_property_ext * /* properties */, + cl_uint /*num_entries*/, + cl_device_id * /*out_devices*/, + cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + typedef CL_API_ENTRY cl_int + ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, + const cl_device_partition_property_ext * /* properties */, + cl_uint /*num_entries*/, + cl_device_id * /*out_devices*/, + cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; + + /* cl_device_partition_property_ext */ + #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 + #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 + #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 + #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 + + /* clDeviceGetInfo selectors */ + #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 + #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 + #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 + #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 + #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 + + /* error codes */ + #define CL_DEVICE_PARTITION_FAILED_EXT -1057 + #define CL_INVALID_PARTITION_COUNT_EXT -1058 + #define CL_INVALID_PARTITION_NAME_EXT -1059 + + /* CL_AFFINITY_DOMAINs */ + #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 + #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 + #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 + #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 + #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 + #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 + + /* cl_device_partition_property_ext list terminators */ + #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) + #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) + #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) + +/********************************* +* cl_qcom_ext_host_ptr extension +*********************************/ + +#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) + +#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 +#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 +#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 +#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 +#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 +#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 +#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 +#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 + +typedef cl_uint cl_image_pitch_info_qcom; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetDeviceImageInfoQCOM(cl_device_id device, + size_t image_width, + size_t image_height, + const cl_image_format *image_format, + cl_image_pitch_info_qcom param_name, + size_t param_value_size, + void *param_value, + size_t *param_value_size_ret); + +typedef struct _cl_mem_ext_host_ptr +{ + /* Type of external memory allocation. */ + /* Legal values will be defined in layered extensions. */ + cl_uint allocation_type; + + /* Host cache policy for this external memory allocation. */ + cl_uint host_cache_policy; + +} cl_mem_ext_host_ptr; + +/********************************* +* cl_qcom_ion_host_ptr extension +*********************************/ + +#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 + +typedef struct _cl_mem_ion_host_ptr +{ + /* Type of external memory allocation. */ + /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ + cl_mem_ext_host_ptr ext_host_ptr; + + /* ION file descriptor */ + int ion_filedesc; + + /* Host pointer to the ION allocated memory */ + void* ion_hostptr; + +} cl_mem_ion_host_ptr; + +#endif /* CL_VERSION_1_1 */ + + +#ifdef CL_VERSION_2_0 +/********************************* +* cl_khr_sub_groups extension +*********************************/ +#define cl_khr_sub_groups 1 + +typedef cl_uint cl_kernel_sub_group_info_khr; + +/* cl_khr_sub_group_info */ +#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 +#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, + cl_device_id /*in_device*/, + cl_kernel_sub_group_info_khr /* param_name */, + size_t /*input_value_size*/, + const void * /*input_value*/, + size_t /*param_value_size*/, + void* /*param_value*/, + size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; + +typedef CL_API_ENTRY cl_int + ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, + cl_device_id /*in_device*/, + cl_kernel_sub_group_info_khr /* param_name */, + size_t /*input_value_size*/, + const void * /*input_value*/, + size_t /*param_value_size*/, + void* /*param_value*/, + size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; +#endif /* CL_VERSION_2_0 */ + +#ifdef CL_VERSION_2_1 +/********************************* +* cl_khr_priority_hints extension +*********************************/ +#define cl_khr_priority_hints 1 + +typedef cl_uint cl_queue_priority_khr; + +/* cl_command_queue_properties */ +#define CL_QUEUE_PRIORITY_KHR 0x1096 + +/* cl_queue_priority_khr */ +#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) +#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) +#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) + +#endif /* CL_VERSION_2_1 */ + +#ifdef CL_VERSION_2_1 +/********************************* +* cl_khr_throttle_hints extension +*********************************/ +#define cl_khr_throttle_hints 1 + +typedef cl_uint cl_queue_throttle_khr; + +/* cl_command_queue_properties */ +#define CL_QUEUE_THROTTLE_KHR 0x1097 + +/* cl_queue_throttle_khr */ +#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) +#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) +#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) + +#endif /* CL_VERSION_2_1 */ + +#ifdef __cplusplus +} +#endif + + +#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h new file mode 100644 index 00000000000..6328a1cd93a --- /dev/null +++ b/third_party/opencl/include/CL/cl_ext_qcom.h @@ -0,0 +1,255 @@ +/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. + * Qualcomm Technologies Proprietary and Confidential. + */ + +#ifndef __OPENCL_CL_EXT_QCOM_H +#define __OPENCL_CL_EXT_QCOM_H + +// Needed by cl_khr_egl_event extension +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +/************************************ + * cl_qcom_create_buffer_from_image * + ************************************/ + +#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 +#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateBufferFromImageQCOM(cl_mem image, + cl_mem_flags flags, + cl_int *errcode_ret); + + +/************************************ + * cl_qcom_limited_printf extension * + ************************************/ + +/* Builtin printf function buffer size in bytes. */ +#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 + + +/************************************* + * cl_qcom_extended_images extension * + *************************************/ + +#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA +#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB +#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE +#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF + +/************************************* + * cl_qcom_perf_hint extension * + *************************************/ + +typedef cl_uint cl_perf_hint; + +#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 + +/*cl_perf_hint*/ +#define CL_PERF_HINT_HIGH_QCOM 0x40C3 +#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 +#define CL_PERF_HINT_LOW_QCOM 0x40C5 + +extern CL_API_ENTRY cl_int CL_API_CALL +clSetPerfHintQCOM(cl_context context, + cl_perf_hint perf_hint); + +// This extension is published at Khronos, so its definitions are made in cl_ext.h. +// This duplication is for backward compatibility. + +#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM + +/********************************* +* cl_qcom_android_native_buffer_host_ptr extension +*********************************/ + +#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 + + +typedef struct _cl_mem_android_native_buffer_host_ptr +{ + // Type of external memory allocation. + // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. + cl_mem_ext_host_ptr ext_host_ptr; + + // Virtual pointer to the android native buffer + void* anb_ptr; + +} cl_mem_android_native_buffer_host_ptr; + +#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM + +/*********************************** +* cl_img_egl_image extension * +************************************/ +typedef void* CLeglImageIMG; +typedef void* CLeglDisplayIMG; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromEGLImageIMG(cl_context context, + cl_mem_flags flags, + CLeglImageIMG image, + CLeglDisplayIMG display, + cl_int *errcode_ret); + + +/********************************* +* cl_qcom_other_image extension +*********************************/ + +// Extended flag for creating/querying QCOM non-standard images +#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) + +// cl_channel_type +#define CL_QCOM_UNORM_MIPI10 0x4159 +#define CL_QCOM_UNORM_MIPI12 0x415A +#define CL_QCOM_UNSIGNED_MIPI10 0x415B +#define CL_QCOM_UNSIGNED_MIPI12 0x415C +#define CL_QCOM_UNORM_INT10 0x415D +#define CL_QCOM_UNORM_INT12 0x415E +#define CL_QCOM_UNSIGNED_INT16 0x415F + +// cl_channel_order +// Dedicate 0x4130-0x415F range for QCOM extended image formats +// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format +#define CL_QCOM_BAYER 0x414E + +#define CL_QCOM_NV12 0x4133 +#define CL_QCOM_NV12_Y 0x4134 +#define CL_QCOM_NV12_UV 0x4135 + +#define CL_QCOM_TILED_NV12 0x4136 +#define CL_QCOM_TILED_NV12_Y 0x4137 +#define CL_QCOM_TILED_NV12_UV 0x4138 + +#define CL_QCOM_P010 0x413C +#define CL_QCOM_P010_Y 0x413D +#define CL_QCOM_P010_UV 0x413E + +#define CL_QCOM_TILED_P010 0x413F +#define CL_QCOM_TILED_P010_Y 0x4140 +#define CL_QCOM_TILED_P010_UV 0x4141 + + +#define CL_QCOM_TP10 0x4145 +#define CL_QCOM_TP10_Y 0x4146 +#define CL_QCOM_TP10_UV 0x4147 + +#define CL_QCOM_TILED_TP10 0x4148 +#define CL_QCOM_TILED_TP10_Y 0x4149 +#define CL_QCOM_TILED_TP10_UV 0x414A + +/********************************* +* cl_qcom_compressed_image extension +*********************************/ + +// Extended flag for creating/querying QCOM non-planar compressed images +#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) + +// Extended image format +// cl_channel_order +#define CL_QCOM_COMPRESSED_RGBA 0x4130 +#define CL_QCOM_COMPRESSED_RGBx 0x4131 + +#define CL_QCOM_COMPRESSED_NV12_Y 0x413A +#define CL_QCOM_COMPRESSED_NV12_UV 0x413B + +#define CL_QCOM_COMPRESSED_P010 0x4142 +#define CL_QCOM_COMPRESSED_P010_Y 0x4143 +#define CL_QCOM_COMPRESSED_P010_UV 0x4144 + +#define CL_QCOM_COMPRESSED_TP10 0x414B +#define CL_QCOM_COMPRESSED_TP10_Y 0x414C +#define CL_QCOM_COMPRESSED_TP10_UV 0x414D + +#define CL_QCOM_COMPRESSED_NV12_4R 0x414F +#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 +#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 +/********************************* +* cl_qcom_compressed_yuv_image_read extension +*********************************/ + +// Extended flag for creating/querying QCOM compressed images +#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) + +// Extended image format +#define CL_QCOM_COMPRESSED_NV12 0x10C4 + +// Extended flag for setting ION buffer allocation type +#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD +#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE + +/********************************* +* cl_qcom_accelerated_image_ops +*********************************/ +#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 +#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 +#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 +#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 +#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 +#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 + +//Extended flag for specifying weight image type +#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) + +// Box Filter +typedef struct _cl_box_filter_size_qcom +{ + // Width of box filter on X direction. + float box_filter_width; + + // Height of box filter on Y direction. + float box_filter_height; +} cl_box_filter_size_qcom; + +// HOF Weight Image Desc +typedef struct _cl_weight_desc_qcom +{ + /** Coordinate of the "center" point of the weight image, + based on the weight image's top-left corner as the origin. */ + size_t center_coord_x; + size_t center_coord_y; + cl_bitfield flags; +} cl_weight_desc_qcom; + +typedef struct _cl_weight_image_desc_qcom +{ + cl_image_desc image_desc; + cl_weight_desc_qcom weight_desc; +} cl_weight_image_desc_qcom; + +/************************************* + * cl_qcom_protected_context extension * + *************************************/ + +#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 +#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 + +/************************************* + * cl_qcom_priority_hint extension * + *************************************/ +#define CL_PRIORITY_HINT_NONE_QCOM 0 +typedef cl_uint cl_priority_hint; + +#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 + +/*cl_priority_hint*/ +#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA +#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB +#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h new file mode 100644 index 00000000000..945daa83d7f --- /dev/null +++ b/third_party/opencl/include/CL/cl_gl.h @@ -0,0 +1,167 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +#ifndef __OPENCL_CL_GL_H +#define __OPENCL_CL_GL_H + +#ifdef __APPLE__ +#include +#else +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef cl_uint cl_gl_object_type; +typedef cl_uint cl_gl_texture_info; +typedef cl_uint cl_gl_platform_info; +typedef struct __GLsync *cl_GLsync; + +/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ +#define CL_GL_OBJECT_BUFFER 0x2000 +#define CL_GL_OBJECT_TEXTURE2D 0x2001 +#define CL_GL_OBJECT_TEXTURE3D 0x2002 +#define CL_GL_OBJECT_RENDERBUFFER 0x2003 +#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E +#define CL_GL_OBJECT_TEXTURE1D 0x200F +#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 +#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 + +/* cl_gl_texture_info */ +#define CL_GL_TEXTURE_TARGET 0x2004 +#define CL_GL_MIPMAP_LEVEL 0x2005 +#define CL_GL_NUM_SAMPLES 0x2012 + + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLBuffer(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLuint /* bufobj */, + int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLTexture(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; + +extern CL_API_ENTRY cl_mem CL_API_CALL +clCreateFromGLRenderbuffer(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLuint /* renderbuffer */, + cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLObjectInfo(cl_mem /* memobj */, + cl_gl_object_type * /* gl_object_type */, + cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLTextureInfo(cl_mem /* memobj */, + cl_gl_texture_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + +extern CL_API_ENTRY cl_int CL_API_CALL +clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, + cl_uint /* num_objects */, + const cl_mem * /* mem_objects */, + cl_uint /* num_events_in_wait_list */, + const cl_event * /* event_wait_list */, + cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; + + +/* Deprecated OpenCL 1.1 APIs */ +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateFromGLTexture2D(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL +clCreateFromGLTexture3D(cl_context /* context */, + cl_mem_flags /* flags */, + cl_GLenum /* target */, + cl_GLint /* miplevel */, + cl_GLuint /* texture */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; + +/* cl_khr_gl_sharing extension */ + +#define cl_khr_gl_sharing 1 + +typedef cl_uint cl_gl_context_info; + +/* Additional Error Codes */ +#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 + +/* cl_gl_context_info */ +#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 +#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 + +/* Additional cl_context_properties */ +#define CL_GL_CONTEXT_KHR 0x2008 +#define CL_EGL_DISPLAY_KHR 0x2009 +#define CL_GLX_DISPLAY_KHR 0x200A +#define CL_WGL_HDC_KHR 0x200B +#define CL_CGL_SHAREGROUP_KHR 0x200C + +extern CL_API_ENTRY cl_int CL_API_CALL +clGetGLContextInfoKHR(const cl_context_properties * /* properties */, + cl_gl_context_info /* param_name */, + size_t /* param_value_size */, + void * /* param_value */, + size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; + +typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( + const cl_context_properties * properties, + cl_gl_context_info param_name, + size_t param_value_size, + void * param_value, + size_t * param_value_size_ret); + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h new file mode 100644 index 00000000000..e3c14c6408c --- /dev/null +++ b/third_party/opencl/include/CL/cl_gl_ext.h @@ -0,0 +1,74 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ +/* OpenGL dependencies. */ + +#ifndef __OPENCL_CL_GL_EXT_H +#define __OPENCL_CL_GL_EXT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + #include +#else + #include +#endif + +/* + * For each extension, follow this template + * cl_VEN_extname extension */ +/* #define cl_VEN_extname 1 + * ... define new types, if any + * ... define new tokens, if any + * ... define new APIs, if any + * + * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header + * This allows us to avoid having to decide whether to include GL headers or GLES here. + */ + +/* + * cl_khr_gl_event extension + * See section 9.9 in the OpenCL 1.1 spec for more information + */ +#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D + +extern CL_API_ENTRY cl_event CL_API_CALL +clCreateEventFromGLsyncKHR(cl_context /* context */, + cl_GLsync /* cl_GLsync */, + cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h new file mode 100644 index 00000000000..4e334a29183 --- /dev/null +++ b/third_party/opencl/include/CL/cl_platform.h @@ -0,0 +1,1333 @@ +/********************************************************************************** + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + **********************************************************************************/ + +/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ + +#ifndef __CL_PLATFORM_H +#define __CL_PLATFORM_H + +#ifdef __APPLE__ + /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ + #include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) + #define CL_API_ENTRY + #define CL_API_CALL __stdcall + #define CL_CALLBACK __stdcall +#else + #define CL_API_ENTRY + #define CL_API_CALL + #define CL_CALLBACK +#endif + +/* + * Deprecation flags refer to the last version of the header in which the + * feature was not deprecated. + * + * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without + * deprecation but is deprecated in versions later than 1.1. + */ + +#ifdef __APPLE__ + #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) + #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER + #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 + + #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 + #else + #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! + #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER + #endif +#else + #define CL_EXTENSION_WEAK_LINK + #define CL_API_SUFFIX__VERSION_1_0 + #define CL_EXT_SUFFIX__VERSION_1_0 + #define CL_API_SUFFIX__VERSION_1_1 + #define CL_EXT_SUFFIX__VERSION_1_1 + #define CL_API_SUFFIX__VERSION_1_2 + #define CL_EXT_SUFFIX__VERSION_1_2 + #define CL_API_SUFFIX__VERSION_2_0 + #define CL_EXT_SUFFIX__VERSION_2_0 + #define CL_API_SUFFIX__VERSION_2_1 + #define CL_EXT_SUFFIX__VERSION_2_1 + + #ifdef __GNUC__ + #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #endif + #elif _WIN32 + #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) + #endif + + #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #else + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) + #endif + #else + #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED + #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED + + #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED + #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED + #endif +#endif + +#if (defined (_WIN32) && defined(_MSC_VER)) + +/* scalar types */ +typedef signed __int8 cl_char; +typedef unsigned __int8 cl_uchar; +typedef signed __int16 cl_short; +typedef unsigned __int16 cl_ushort; +typedef signed __int32 cl_int; +typedef unsigned __int32 cl_uint; +typedef signed __int64 cl_long; +typedef unsigned __int64 cl_ulong; + +typedef unsigned __int16 cl_half; +typedef float cl_float; +typedef double cl_double; + +/* Macro names and corresponding values defined by OpenCL */ +#define CL_CHAR_BIT 8 +#define CL_SCHAR_MAX 127 +#define CL_SCHAR_MIN (-127-1) +#define CL_CHAR_MAX CL_SCHAR_MAX +#define CL_CHAR_MIN CL_SCHAR_MIN +#define CL_UCHAR_MAX 255 +#define CL_SHRT_MAX 32767 +#define CL_SHRT_MIN (-32767-1) +#define CL_USHRT_MAX 65535 +#define CL_INT_MAX 2147483647 +#define CL_INT_MIN (-2147483647-1) +#define CL_UINT_MAX 0xffffffffU +#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) +#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) +#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) + +#define CL_FLT_DIG 6 +#define CL_FLT_MANT_DIG 24 +#define CL_FLT_MAX_10_EXP +38 +#define CL_FLT_MAX_EXP +128 +#define CL_FLT_MIN_10_EXP -37 +#define CL_FLT_MIN_EXP -125 +#define CL_FLT_RADIX 2 +#define CL_FLT_MAX 340282346638528859811704183484516925440.0f +#define CL_FLT_MIN 1.175494350822287507969e-38f +#define CL_FLT_EPSILON 0x1.0p-23f + +#define CL_DBL_DIG 15 +#define CL_DBL_MANT_DIG 53 +#define CL_DBL_MAX_10_EXP +308 +#define CL_DBL_MAX_EXP +1024 +#define CL_DBL_MIN_10_EXP -307 +#define CL_DBL_MIN_EXP -1021 +#define CL_DBL_RADIX 2 +#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 +#define CL_DBL_MIN 2.225073858507201383090e-308 +#define CL_DBL_EPSILON 2.220446049250313080847e-16 + +#define CL_M_E 2.718281828459045090796 +#define CL_M_LOG2E 1.442695040888963387005 +#define CL_M_LOG10E 0.434294481903251816668 +#define CL_M_LN2 0.693147180559945286227 +#define CL_M_LN10 2.302585092994045901094 +#define CL_M_PI 3.141592653589793115998 +#define CL_M_PI_2 1.570796326794896557999 +#define CL_M_PI_4 0.785398163397448278999 +#define CL_M_1_PI 0.318309886183790691216 +#define CL_M_2_PI 0.636619772367581382433 +#define CL_M_2_SQRTPI 1.128379167095512558561 +#define CL_M_SQRT2 1.414213562373095145475 +#define CL_M_SQRT1_2 0.707106781186547572737 + +#define CL_M_E_F 2.71828174591064f +#define CL_M_LOG2E_F 1.44269502162933f +#define CL_M_LOG10E_F 0.43429449200630f +#define CL_M_LN2_F 0.69314718246460f +#define CL_M_LN10_F 2.30258512496948f +#define CL_M_PI_F 3.14159274101257f +#define CL_M_PI_2_F 1.57079637050629f +#define CL_M_PI_4_F 0.78539818525314f +#define CL_M_1_PI_F 0.31830987334251f +#define CL_M_2_PI_F 0.63661974668503f +#define CL_M_2_SQRTPI_F 1.12837922573090f +#define CL_M_SQRT2_F 1.41421353816986f +#define CL_M_SQRT1_2_F 0.70710676908493f + +#define CL_NAN (CL_INFINITY - CL_INFINITY) +#define CL_HUGE_VALF ((cl_float) 1e50) +#define CL_HUGE_VAL ((cl_double) 1e500) +#define CL_MAXFLOAT CL_FLT_MAX +#define CL_INFINITY CL_HUGE_VALF + +#else + +#include + +/* scalar types */ +typedef int8_t cl_char; +typedef uint8_t cl_uchar; +typedef int16_t cl_short __attribute__((aligned(2))); +typedef uint16_t cl_ushort __attribute__((aligned(2))); +typedef int32_t cl_int __attribute__((aligned(4))); +typedef uint32_t cl_uint __attribute__((aligned(4))); +typedef int64_t cl_long __attribute__((aligned(8))); +typedef uint64_t cl_ulong __attribute__((aligned(8))); + +typedef uint16_t cl_half __attribute__((aligned(2))); +typedef float cl_float __attribute__((aligned(4))); +typedef double cl_double __attribute__((aligned(8))); + +/* Macro names and corresponding values defined by OpenCL */ +#define CL_CHAR_BIT 8 +#define CL_SCHAR_MAX 127 +#define CL_SCHAR_MIN (-127-1) +#define CL_CHAR_MAX CL_SCHAR_MAX +#define CL_CHAR_MIN CL_SCHAR_MIN +#define CL_UCHAR_MAX 255 +#define CL_SHRT_MAX 32767 +#define CL_SHRT_MIN (-32767-1) +#define CL_USHRT_MAX 65535 +#define CL_INT_MAX 2147483647 +#define CL_INT_MIN (-2147483647-1) +#define CL_UINT_MAX 0xffffffffU +#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) +#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) +#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) + +#define CL_FLT_DIG 6 +#define CL_FLT_MANT_DIG 24 +#define CL_FLT_MAX_10_EXP +38 +#define CL_FLT_MAX_EXP +128 +#define CL_FLT_MIN_10_EXP -37 +#define CL_FLT_MIN_EXP -125 +#define CL_FLT_RADIX 2 +#define CL_FLT_MAX 0x1.fffffep127f +#define CL_FLT_MIN 0x1.0p-126f +#define CL_FLT_EPSILON 0x1.0p-23f + +#define CL_DBL_DIG 15 +#define CL_DBL_MANT_DIG 53 +#define CL_DBL_MAX_10_EXP +308 +#define CL_DBL_MAX_EXP +1024 +#define CL_DBL_MIN_10_EXP -307 +#define CL_DBL_MIN_EXP -1021 +#define CL_DBL_RADIX 2 +#define CL_DBL_MAX 0x1.fffffffffffffp1023 +#define CL_DBL_MIN 0x1.0p-1022 +#define CL_DBL_EPSILON 0x1.0p-52 + +#define CL_M_E 2.718281828459045090796 +#define CL_M_LOG2E 1.442695040888963387005 +#define CL_M_LOG10E 0.434294481903251816668 +#define CL_M_LN2 0.693147180559945286227 +#define CL_M_LN10 2.302585092994045901094 +#define CL_M_PI 3.141592653589793115998 +#define CL_M_PI_2 1.570796326794896557999 +#define CL_M_PI_4 0.785398163397448278999 +#define CL_M_1_PI 0.318309886183790691216 +#define CL_M_2_PI 0.636619772367581382433 +#define CL_M_2_SQRTPI 1.128379167095512558561 +#define CL_M_SQRT2 1.414213562373095145475 +#define CL_M_SQRT1_2 0.707106781186547572737 + +#define CL_M_E_F 2.71828174591064f +#define CL_M_LOG2E_F 1.44269502162933f +#define CL_M_LOG10E_F 0.43429449200630f +#define CL_M_LN2_F 0.69314718246460f +#define CL_M_LN10_F 2.30258512496948f +#define CL_M_PI_F 3.14159274101257f +#define CL_M_PI_2_F 1.57079637050629f +#define CL_M_PI_4_F 0.78539818525314f +#define CL_M_1_PI_F 0.31830987334251f +#define CL_M_2_PI_F 0.63661974668503f +#define CL_M_2_SQRTPI_F 1.12837922573090f +#define CL_M_SQRT2_F 1.41421353816986f +#define CL_M_SQRT1_2_F 0.70710676908493f + +#if defined( __GNUC__ ) + #define CL_HUGE_VALF __builtin_huge_valf() + #define CL_HUGE_VAL __builtin_huge_val() + #define CL_NAN __builtin_nanf( "" ) +#else + #define CL_HUGE_VALF ((cl_float) 1e50) + #define CL_HUGE_VAL ((cl_double) 1e500) + float nanf( const char * ); + #define CL_NAN nanf( "" ) +#endif +#define CL_MAXFLOAT CL_FLT_MAX +#define CL_INFINITY CL_HUGE_VALF + +#endif + +#include + +/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ +typedef unsigned int cl_GLuint; +typedef int cl_GLint; +typedef unsigned int cl_GLenum; + +/* + * Vector types + * + * Note: OpenCL requires that all types be naturally aligned. + * This means that vector types must be naturally aligned. + * For example, a vector of four floats must be aligned to + * a 16 byte boundary (calculated as 4 * the natural 4-byte + * alignment of the float). The alignment qualifiers here + * will only function properly if your compiler supports them + * and if you don't actively work to defeat them. For example, + * in order for a cl_float4 to be 16 byte aligned in a struct, + * the start of the struct must itself be 16-byte aligned. + * + * Maintaining proper alignment is the user's responsibility. + */ + +/* Define basic vector types */ +#if defined( __VEC__ ) + #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ + typedef vector unsigned char __cl_uchar16; + typedef vector signed char __cl_char16; + typedef vector unsigned short __cl_ushort8; + typedef vector signed short __cl_short8; + typedef vector unsigned int __cl_uint4; + typedef vector signed int __cl_int4; + typedef vector float __cl_float4; + #define __CL_UCHAR16__ 1 + #define __CL_CHAR16__ 1 + #define __CL_USHORT8__ 1 + #define __CL_SHORT8__ 1 + #define __CL_UINT4__ 1 + #define __CL_INT4__ 1 + #define __CL_FLOAT4__ 1 +#endif + +#if defined( __SSE__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef float __cl_float4 __attribute__((vector_size(16))); + #else + typedef __m128 __cl_float4; + #endif + #define __CL_FLOAT4__ 1 +#endif + +#if defined( __SSE2__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); + typedef cl_char __cl_char16 __attribute__((vector_size(16))); + typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); + typedef cl_short __cl_short8 __attribute__((vector_size(16))); + typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); + typedef cl_int __cl_int4 __attribute__((vector_size(16))); + typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); + typedef cl_long __cl_long2 __attribute__((vector_size(16))); + typedef cl_double __cl_double2 __attribute__((vector_size(16))); + #else + typedef __m128i __cl_uchar16; + typedef __m128i __cl_char16; + typedef __m128i __cl_ushort8; + typedef __m128i __cl_short8; + typedef __m128i __cl_uint4; + typedef __m128i __cl_int4; + typedef __m128i __cl_ulong2; + typedef __m128i __cl_long2; + typedef __m128d __cl_double2; + #endif + #define __CL_UCHAR16__ 1 + #define __CL_CHAR16__ 1 + #define __CL_USHORT8__ 1 + #define __CL_SHORT8__ 1 + #define __CL_INT4__ 1 + #define __CL_UINT4__ 1 + #define __CL_ULONG2__ 1 + #define __CL_LONG2__ 1 + #define __CL_DOUBLE2__ 1 +#endif + +#if defined( __MMX__ ) + #include + #if defined( __GNUC__ ) + typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); + typedef cl_char __cl_char8 __attribute__((vector_size(8))); + typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); + typedef cl_short __cl_short4 __attribute__((vector_size(8))); + typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); + typedef cl_int __cl_int2 __attribute__((vector_size(8))); + typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); + typedef cl_long __cl_long1 __attribute__((vector_size(8))); + typedef cl_float __cl_float2 __attribute__((vector_size(8))); + #else + typedef __m64 __cl_uchar8; + typedef __m64 __cl_char8; + typedef __m64 __cl_ushort4; + typedef __m64 __cl_short4; + typedef __m64 __cl_uint2; + typedef __m64 __cl_int2; + typedef __m64 __cl_ulong1; + typedef __m64 __cl_long1; + typedef __m64 __cl_float2; + #endif + #define __CL_UCHAR8__ 1 + #define __CL_CHAR8__ 1 + #define __CL_USHORT4__ 1 + #define __CL_SHORT4__ 1 + #define __CL_INT2__ 1 + #define __CL_UINT2__ 1 + #define __CL_ULONG1__ 1 + #define __CL_LONG1__ 1 + #define __CL_FLOAT2__ 1 +#endif + +#if defined( __AVX__ ) + #if defined( __MINGW64__ ) + #include + #else + #include + #endif + #if defined( __GNUC__ ) + typedef cl_float __cl_float8 __attribute__((vector_size(32))); + typedef cl_double __cl_double4 __attribute__((vector_size(32))); + #else + typedef __m256 __cl_float8; + typedef __m256d __cl_double4; + #endif + #define __CL_FLOAT8__ 1 + #define __CL_DOUBLE4__ 1 +#endif + +/* Define capabilities for anonymous struct members. */ +#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) +#define __CL_HAS_ANON_STRUCT__ 1 +#define __CL_ANON_STRUCT__ __extension__ +#elif defined( _WIN32) && (_MSC_VER >= 1500) + /* Microsoft Developer Studio 2008 supports anonymous structs, but + * complains by default. */ +#define __CL_HAS_ANON_STRUCT__ 1 +#define __CL_ANON_STRUCT__ + /* Disable warning C4201: nonstandard extension used : nameless + * struct/union */ +#pragma warning( push ) +#pragma warning( disable : 4201 ) +#else +#define __CL_HAS_ANON_STRUCT__ 0 +#define __CL_ANON_STRUCT__ +#endif + +/* Define alignment keys */ +#if defined( __GNUC__ ) + #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) +#elif defined( _WIN32) && (_MSC_VER) + /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ + /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ + /* #include */ + /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ + #define CL_ALIGNED(_x) +#else + #warning Need to implement some method to align data here + #define CL_ALIGNED(_x) +#endif + +/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ +#if __CL_HAS_ANON_STRUCT__ + /* .xyzw and .s0123...{f|F} are supported */ + #define CL_HAS_NAMED_VECTOR_FIELDS 1 + /* .hi and .lo are supported */ + #define CL_HAS_HI_LO_VECTOR_FIELDS 1 +#endif + +/* Define cl_vector types */ + +/* ---- cl_charn ---- */ +typedef union +{ + cl_char CL_ALIGNED(2) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2; +#endif +}cl_char2; + +typedef union +{ + cl_char CL_ALIGNED(4) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[2]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4; +#endif +}cl_char4; + +/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ +typedef cl_char4 cl_char3; + +typedef union +{ + cl_char CL_ALIGNED(8) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[4]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4[2]; +#endif +#if defined( __CL_CHAR8__ ) + __cl_char8 v8; +#endif +}cl_char8; + +typedef union +{ + cl_char CL_ALIGNED(16) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; +#endif +#if defined( __CL_CHAR2__) + __cl_char2 v2[8]; +#endif +#if defined( __CL_CHAR4__) + __cl_char4 v4[4]; +#endif +#if defined( __CL_CHAR8__ ) + __cl_char8 v8[2]; +#endif +#if defined( __CL_CHAR16__ ) + __cl_char16 v16; +#endif +}cl_char16; + + +/* ---- cl_ucharn ---- */ +typedef union +{ + cl_uchar CL_ALIGNED(2) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; +#endif +#if defined( __cl_uchar2__) + __cl_uchar2 v2; +#endif +}cl_uchar2; + +typedef union +{ + cl_uchar CL_ALIGNED(4) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[2]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4; +#endif +}cl_uchar4; + +/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ +typedef cl_uchar4 cl_uchar3; + +typedef union +{ + cl_uchar CL_ALIGNED(8) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[4]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4[2]; +#endif +#if defined( __CL_UCHAR8__ ) + __cl_uchar8 v8; +#endif +}cl_uchar8; + +typedef union +{ + cl_uchar CL_ALIGNED(16) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; +#endif +#if defined( __CL_UCHAR2__) + __cl_uchar2 v2[8]; +#endif +#if defined( __CL_UCHAR4__) + __cl_uchar4 v4[4]; +#endif +#if defined( __CL_UCHAR8__ ) + __cl_uchar8 v8[2]; +#endif +#if defined( __CL_UCHAR16__ ) + __cl_uchar16 v16; +#endif +}cl_uchar16; + + +/* ---- cl_shortn ---- */ +typedef union +{ + cl_short CL_ALIGNED(4) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2; +#endif +}cl_short2; + +typedef union +{ + cl_short CL_ALIGNED(8) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[2]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4; +#endif +}cl_short4; + +/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ +typedef cl_short4 cl_short3; + +typedef union +{ + cl_short CL_ALIGNED(16) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[4]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4[2]; +#endif +#if defined( __CL_SHORT8__ ) + __cl_short8 v8; +#endif +}cl_short8; + +typedef union +{ + cl_short CL_ALIGNED(32) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; +#endif +#if defined( __CL_SHORT2__) + __cl_short2 v2[8]; +#endif +#if defined( __CL_SHORT4__) + __cl_short4 v4[4]; +#endif +#if defined( __CL_SHORT8__ ) + __cl_short8 v8[2]; +#endif +#if defined( __CL_SHORT16__ ) + __cl_short16 v16; +#endif +}cl_short16; + + +/* ---- cl_ushortn ---- */ +typedef union +{ + cl_ushort CL_ALIGNED(4) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2; +#endif +}cl_ushort2; + +typedef union +{ + cl_ushort CL_ALIGNED(8) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[2]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4; +#endif +}cl_ushort4; + +/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ +typedef cl_ushort4 cl_ushort3; + +typedef union +{ + cl_ushort CL_ALIGNED(16) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[4]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4[2]; +#endif +#if defined( __CL_USHORT8__ ) + __cl_ushort8 v8; +#endif +}cl_ushort8; + +typedef union +{ + cl_ushort CL_ALIGNED(32) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; +#endif +#if defined( __CL_USHORT2__) + __cl_ushort2 v2[8]; +#endif +#if defined( __CL_USHORT4__) + __cl_ushort4 v4[4]; +#endif +#if defined( __CL_USHORT8__ ) + __cl_ushort8 v8[2]; +#endif +#if defined( __CL_USHORT16__ ) + __cl_ushort16 v16; +#endif +}cl_ushort16; + +/* ---- cl_intn ---- */ +typedef union +{ + cl_int CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2; +#endif +}cl_int2; + +typedef union +{ + cl_int CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[2]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4; +#endif +}cl_int4; + +/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ +typedef cl_int4 cl_int3; + +typedef union +{ + cl_int CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[4]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4[2]; +#endif +#if defined( __CL_INT8__ ) + __cl_int8 v8; +#endif +}cl_int8; + +typedef union +{ + cl_int CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; +#endif +#if defined( __CL_INT2__) + __cl_int2 v2[8]; +#endif +#if defined( __CL_INT4__) + __cl_int4 v4[4]; +#endif +#if defined( __CL_INT8__ ) + __cl_int8 v8[2]; +#endif +#if defined( __CL_INT16__ ) + __cl_int16 v16; +#endif +}cl_int16; + + +/* ---- cl_uintn ---- */ +typedef union +{ + cl_uint CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2; +#endif +}cl_uint2; + +typedef union +{ + cl_uint CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[2]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4; +#endif +}cl_uint4; + +/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ +typedef cl_uint4 cl_uint3; + +typedef union +{ + cl_uint CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[4]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4[2]; +#endif +#if defined( __CL_UINT8__ ) + __cl_uint8 v8; +#endif +}cl_uint8; + +typedef union +{ + cl_uint CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; +#endif +#if defined( __CL_UINT2__) + __cl_uint2 v2[8]; +#endif +#if defined( __CL_UINT4__) + __cl_uint4 v4[4]; +#endif +#if defined( __CL_UINT8__ ) + __cl_uint8 v8[2]; +#endif +#if defined( __CL_UINT16__ ) + __cl_uint16 v16; +#endif +}cl_uint16; + +/* ---- cl_longn ---- */ +typedef union +{ + cl_long CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2; +#endif +}cl_long2; + +typedef union +{ + cl_long CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[2]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4; +#endif +}cl_long4; + +/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ +typedef cl_long4 cl_long3; + +typedef union +{ + cl_long CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[4]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4[2]; +#endif +#if defined( __CL_LONG8__ ) + __cl_long8 v8; +#endif +}cl_long8; + +typedef union +{ + cl_long CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; +#endif +#if defined( __CL_LONG2__) + __cl_long2 v2[8]; +#endif +#if defined( __CL_LONG4__) + __cl_long4 v4[4]; +#endif +#if defined( __CL_LONG8__ ) + __cl_long8 v8[2]; +#endif +#if defined( __CL_LONG16__ ) + __cl_long16 v16; +#endif +}cl_long16; + + +/* ---- cl_ulongn ---- */ +typedef union +{ + cl_ulong CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2; +#endif +}cl_ulong2; + +typedef union +{ + cl_ulong CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[2]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4; +#endif +}cl_ulong4; + +/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ +typedef cl_ulong4 cl_ulong3; + +typedef union +{ + cl_ulong CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[4]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4[2]; +#endif +#if defined( __CL_ULONG8__ ) + __cl_ulong8 v8; +#endif +}cl_ulong8; + +typedef union +{ + cl_ulong CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; +#endif +#if defined( __CL_ULONG2__) + __cl_ulong2 v2[8]; +#endif +#if defined( __CL_ULONG4__) + __cl_ulong4 v4[4]; +#endif +#if defined( __CL_ULONG8__ ) + __cl_ulong8 v8[2]; +#endif +#if defined( __CL_ULONG16__ ) + __cl_ulong16 v16; +#endif +}cl_ulong16; + + +/* --- cl_floatn ---- */ + +typedef union +{ + cl_float CL_ALIGNED(8) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2; +#endif +}cl_float2; + +typedef union +{ + cl_float CL_ALIGNED(16) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[2]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4; +#endif +}cl_float4; + +/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ +typedef cl_float4 cl_float3; + +typedef union +{ + cl_float CL_ALIGNED(32) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[4]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4[2]; +#endif +#if defined( __CL_FLOAT8__ ) + __cl_float8 v8; +#endif +}cl_float8; + +typedef union +{ + cl_float CL_ALIGNED(64) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; +#endif +#if defined( __CL_FLOAT2__) + __cl_float2 v2[8]; +#endif +#if defined( __CL_FLOAT4__) + __cl_float4 v4[4]; +#endif +#if defined( __CL_FLOAT8__ ) + __cl_float8 v8[2]; +#endif +#if defined( __CL_FLOAT16__ ) + __cl_float16 v16; +#endif +}cl_float16; + +/* --- cl_doublen ---- */ + +typedef union +{ + cl_double CL_ALIGNED(16) s[2]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; + __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2; +#endif +}cl_double2; + +typedef union +{ + cl_double CL_ALIGNED(32) s[4]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; + __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[2]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4; +#endif +}cl_double4; + +/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ +typedef cl_double4 cl_double3; + +typedef union +{ + cl_double CL_ALIGNED(64) s[8]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; + __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[4]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4[2]; +#endif +#if defined( __CL_DOUBLE8__ ) + __cl_double8 v8; +#endif +}cl_double8; + +typedef union +{ + cl_double CL_ALIGNED(128) s[16]; +#if __CL_HAS_ANON_STRUCT__ + __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; + __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; + __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; +#endif +#if defined( __CL_DOUBLE2__) + __cl_double2 v2[8]; +#endif +#if defined( __CL_DOUBLE4__) + __cl_double4 v4[4]; +#endif +#if defined( __CL_DOUBLE8__ ) + __cl_double8 v8[2]; +#endif +#if defined( __CL_DOUBLE16__ ) + __cl_double16 v16; +#endif +}cl_double16; + +/* Macro to facilitate debugging + * Usage: + * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. + * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" + * Each line thereafter of OpenCL C source must end with: \n\ + * The last line ends in "; + * + * Example: + * + * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ + * kernel void foo( int a, float * b ) \n\ + * { \n\ + * // my comment \n\ + * *b[ get_global_id(0)] = a; \n\ + * } \n\ + * "; + * + * This should correctly set up the line, (column) and file information for your source + * string so you can do source level debugging. + */ +#define __CL_STRINGIFY( _x ) # _x +#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) +#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" + +#ifdef __cplusplus +} +#endif + +#undef __CL_HAS_ANON_STRUCT__ +#undef __CL_ANON_STRUCT__ +#if defined( _WIN32) && (_MSC_VER >= 1500) +#pragma warning( pop ) +#endif + +#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h new file mode 100644 index 00000000000..9855cd75e7d --- /dev/null +++ b/third_party/opencl/include/CL/opencl.h @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2008-2015 The Khronos Group Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and/or associated documentation files (the + * "Materials"), to deal in the Materials without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Materials, and to + * permit persons to whom the Materials are furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Materials. + * + * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS + * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS + * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT + * https://www.khronos.org/registry/ + * + * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + ******************************************************************************/ + +/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ + +#ifndef __OPENCL_H +#define __OPENCL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __APPLE__ + +#include +#include +#include +#include + +#else + +#include +#include +#include +#include + +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __OPENCL_H */ + diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index b79d046fcaf..d4cfc672801 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -57,9 +57,11 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": + base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: + base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index f428e9972a8..5c2131d4bf8 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -58,6 +58,9 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ + opencl-headers \ + ocl-icd-libopencl1 \ + ocl-icd-opencl-dev \ portaudio19-dev \ qttools5-dev-tools \ libqt5svg5-dev \ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 698ab9885d9..136c4119f64 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,6 +6,11 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] +if arch == "Darwin": + base_frameworks.append('OpenCL') +else: + base_libs.append('OpenCL') + replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] if arch != "Darwin": diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 6abbc47935e..67ad2cc8cbc 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,6 +10,10 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements +- Install OpenCL Driver (Ubuntu) +``` +sudo apt install pocl-opencl-icd +``` ## Connect the hardware - Connect the camera first From 10edb90ac65b9bcbdd5371b46314abe2a471246b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Feb 2026 23:27:38 -0800 Subject: [PATCH 164/685] newline in updater error --- selfdrive/ui/mici/layouts/settings/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 60eb7e5b011..a47231340aa 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -224,7 +224,7 @@ def _update_state(self): if self._waiting_for_updater_t is not None and rl.get_time() - self._waiting_for_updater_t > UPDATER_TIMEOUT: self.set_rotate_icon(False) - self.set_value("updater failed to respond") + self.set_value("updater failed\nto respond") self._state = UpdaterState.IDLE self._hide_value_t = rl.get_time() From 3f382d6e695809afda39efef17d6a993b1e72a52 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 00:18:02 -0800 Subject: [PATCH 165/685] Remove vertical scroll bar --- .../icons_mici/settings/vertical_scroll_indicator.png | 3 --- system/ui/widgets/scroller.py | 10 ---------- 2 files changed, 13 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png diff --git a/selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png b/selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png deleted file mode 100644 index 77d9a77d6f3..00000000000 --- a/selfdrive/assets/icons_mici/settings/vertical_scroll_indicator.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:88e6c50358f627fc714c1e9883143aeed00baabeab16132e16001aa1051e5eb8 -size 1272 diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index f33ba941bf9..5f932fba7a3 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -15,7 +15,6 @@ MIN_ZOOM_ANIMATION_TIME = 0.075 # seconds DO_ZOOM = False DO_JELLO = False -SCROLL_BAR = False class LineSeparator(Widget): @@ -65,8 +64,6 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo self.scroll_panel = GuiScrollPanel2(self._horizontal, handle_out_of_bounds=not self._snap_items) self._scroll_enabled: bool | Callable[[], bool] = True - self._txt_scroll_indicator = gui_app.texture("icons_mici/settings/vertical_scroll_indicator.png", 40, 80) - for item in items: self.add_widget(item) @@ -241,13 +238,6 @@ def _render(self, _): else: item.render() - # Draw scroll indicator - if SCROLL_BAR and not self._horizontal and len(self._visible_items) > 0: - _real_content_size = self._content_size - self._rect.height + self._txt_scroll_indicator.height - scroll_bar_y = -self._scroll_offset / _real_content_size * self._rect.height - scroll_bar_y = min(max(scroll_bar_y, self._rect.y), self._rect.y + self._rect.height - self._txt_scroll_indicator.height) - rl.draw_texture_ex(self._txt_scroll_indicator, rl.Vector2(self._rect.x, scroll_bar_y), 0, 1.0, rl.WHITE) - rl.end_scissor_mode() def show_event(self): From 8ba36b76a0eb45d863c8cee38f4e85c77b1c948a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 01:15:02 -0800 Subject: [PATCH 166/685] Simple scroll indicator (#37162) * scroll indicator * 65% * calibrate * margin * cleaner? * manual clean up * clean up * full scroll bar * look * looks * unlook * no fade, looks good but "too much" * clean up * cmt --- .../settings/horizontal_scroll_indicator.png | 3 ++ selfdrive/ui/mici/layouts/main.py | 2 +- system/ui/widgets/scroller.py | 53 ++++++++++++++++++- 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 selfdrive/assets/icons_mici/settings/horizontal_scroll_indicator.png diff --git a/selfdrive/assets/icons_mici/settings/horizontal_scroll_indicator.png b/selfdrive/assets/icons_mici/settings/horizontal_scroll_indicator.png new file mode 100644 index 00000000000..39dd7b19477 --- /dev/null +++ b/selfdrive/assets/icons_mici/settings/horizontal_scroll_indicator.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:af8d5ecb6468442361462aa838a2d234b1256b8139418be8ef2962e4350cfbef +size 2176 diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index b52f9ed39a0..f12c95eafbc 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -47,7 +47,7 @@ def __init__(self): self._alerts_layout, self._home_layout, self._onroad_layout, - ], spacing=0, pad_start=0, pad_end=0) + ], spacing=0, pad_start=0, pad_end=0, scroll_indicator=False) self._scroller.set_reset_scroll_at_show(False) # Disable scrolling when onroad is interacting with bookmark diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 5f932fba7a3..03c49fca659 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -32,9 +32,52 @@ def _render(self, _): LINE_COLOR) +class ScrollIndicator(Widget): + HORIZONTAL_MARGIN = 4 + + def __init__(self): + super().__init__() + self._txt_scroll_indicator = gui_app.texture("icons_mici/settings/horizontal_scroll_indicator.png", 96, 48) + self._scroll_offset: float = 0.0 + self._content_size: float = 0.0 + self._viewport: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) + + def update(self, scroll_offset: float, content_size: float, viewport: rl.Rectangle) -> None: + self._scroll_offset = scroll_offset + self._content_size = content_size + self._viewport = viewport + + def _render(self, _): + # scale indicator width based on content size + indicator_w = float(np.interp(self._content_size, [1000, 3000], [300, 100])) + + # position based on scroll ratio + slide_range = self._viewport.width - indicator_w + max_scroll = self._content_size - self._viewport.width + scroll_ratio = -self._scroll_offset / max_scroll + x = self._viewport.x + scroll_ratio * slide_range + # don't bounce up when NavWidget shows + y = max(self._viewport.y, 0) + self._viewport.height - self._txt_scroll_indicator.height / 2 + + # squeeze when overscrolling past edges + dest_left = max(x, self._viewport.x) + dest_right = min(x + indicator_w, self._viewport.x + self._viewport.width) + dest_w = max(indicator_w / 2, dest_right - dest_left) + + # keep within viewport after applying minimum width + dest_left = min(dest_left, self._viewport.x + self._viewport.width - dest_w) + dest_left = max(dest_left, self._viewport.x) + + src_rec = rl.Rectangle(0, 0, self._txt_scroll_indicator.width, self._txt_scroll_indicator.height) + dest_rec = rl.Rectangle(dest_left, y, dest_w, self._txt_scroll_indicator.height) + rl.draw_texture_pro(self._txt_scroll_indicator, src_rec, dest_rec, rl.Vector2(0, 0), 0.0, + rl.Color(255, 255, 255, int(255 * 0.45))) + + class Scroller(Widget): def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = True, spacing: int = ITEM_SPACING, - line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING): + line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING, + scroll_indicator: bool = True): super().__init__() self._items: list[Widget] = [] self._horizontal = horizontal @@ -64,6 +107,9 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo self.scroll_panel = GuiScrollPanel2(self._horizontal, handle_out_of_bounds=not self._snap_items) self._scroll_enabled: bool | Callable[[], bool] = True + self._show_scroll_indicator = scroll_indicator + self._scroll_indicator = ScrollIndicator() + for item in items: self.add_widget(item) @@ -240,6 +286,11 @@ def _render(self, _): rl.end_scissor_mode() + # Draw scroll indicator + if self._show_scroll_indicator and self._horizontal and len(self._visible_items) > 0: + self._scroll_indicator.update(self._scroll_offset, self._content_size, self._rect) + self._scroll_indicator.render() + def show_event(self): super().show_event() if self._reset_scroll_at_show: From 1e0f1a8abc8302e405527de609e150252e128354 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 01:23:00 -0800 Subject: [PATCH 167/685] Scroll panel: adapt to content size shrinking (#37163) fix --- system/ui/lib/scroll_panel2.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index 0859071dac2..e2a548ba261 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -73,8 +73,14 @@ def _get_offset_bounds(self, bounds_size: float, content_size: float) -> tuple[f def _update_state(self, bounds_size: float, content_size: float) -> None: """Runs per render frame, independent of mouse events. Updates auto-scrolling state and velocity.""" - if self._state == ScrollState.AUTO_SCROLL: - max_offset, min_offset = self._get_offset_bounds(bounds_size, content_size) + max_offset, min_offset = self._get_offset_bounds(bounds_size, content_size) + + if self._state == ScrollState.STEADY: + # if we find ourselves out of bounds, scroll back in (from external layout dimension changes, etc.) + if self.get_offset() > max_offset or self.get_offset() < min_offset: + self._state = ScrollState.AUTO_SCROLL + + elif self._state == ScrollState.AUTO_SCROLL: # simple exponential return if out of bounds out_of_bounds = self.get_offset() > max_offset or self.get_offset() < min_offset if out_of_bounds and self._handle_out_of_bounds: From b9344af9bb8ffcef001471f37936bee4eba57b6d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 01:23:14 -0800 Subject: [PATCH 168/685] WifiManager: sort by known networks (#37164) sort by known --- system/ui/lib/wifi_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index bd66b8e03ab..e0a9e7ca7f0 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -631,7 +631,7 @@ def _update_networks(self): known_connections = self._get_connections() networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()] # sort with quantized strength to reduce jumping - networks.sort(key=lambda n: (-n.is_connected, -round(n.strength / 100 * 2), n.ssid.lower())) + networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) self._networks = networks self._update_ipv4_address() From 5b98ea04ad8fea33dbde3d9d1c22b7ee74dcfd3c Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Wed, 11 Feb 2026 10:21:12 -0800 Subject: [PATCH 169/685] mpc tuning report: minor improvements (#37167) --- .../mpc_longitudinal_tuning_report.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 547f45aa2e6..7623f669cdb 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -186,12 +186,13 @@ def generate_mpc_tuning_report(): for stop_time in np.arange(4, 14, 1): man = Maneuver( '', - duration=50, + duration=30, initial_speed=30.0, + cruise_values=[30.0, 30.0, 30.0], lead_relevancy=True, initial_distance_lead=60.0, - speed_lead_values=[30.0, 30.0, 0.0, 0.0], - breakpoints=[0., 20., 20 + stop_time, 30 + stop_time], + speed_lead_values=[30.0, 30.0, 0.0], + breakpoints=[0., 5., 5 + stop_time], ) valid, results[stop_time] = man.evaluate() results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] @@ -208,12 +209,12 @@ def generate_mpc_tuning_report(): for speed in np.arange(0, 40, 5): man = Maneuver( '', - duration=10, + duration=20, initial_speed=float(speed), + cruise_values=[speed, speed, speed], lead_relevancy=True, initial_distance_lead=desired_follow_distance(speed, speed)/2, speed_lead_values=[speed, speed, speed], - cruise_values=[speed, speed, speed], prob_lead_values=[0.0, 0.0, 1.0], breakpoints=[0., 5.0, 5.01], ) @@ -231,7 +232,7 @@ def generate_mpc_tuning_report(): for speed in np.arange(0, 40, 5): man = Maneuver( '', - duration=50, + duration=60, initial_speed=0.0, lead_relevancy=True, initial_distance_lead=desired_follow_distance(0.0, 0.0), From 99c2fcc797c406774690e55e4e9e21973072a5fa Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 15:05:10 -0800 Subject: [PATCH 170/685] ui: reduce wifi dbus calls (#37170) * 2 -> 1 * cmt --- system/ui/lib/wifi_manager.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index e0a9e7ca7f0..1d1dfc1145b 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -527,13 +527,9 @@ def worker(): threading.Thread(target=worker, daemon=True).start() - def _update_current_network_metered(self) -> None: - if self._wifi_device is None: - cloudlog.warning("No WiFi device found") - return - + def _update_current_network_metered(self, active_conns) -> None: self._current_network_metered = MeteredType.UNKNOWN - for active_conn in self._get_active_connections(): + for active_conn in active_conns: conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] @@ -634,19 +630,17 @@ def _update_networks(self): networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) self._networks = networks - self._update_ipv4_address() - self._update_current_network_metered() + # Get active connections once + active_conns = self._get_active_connections() + self._update_ipv4_address(active_conns) + self._update_current_network_metered(active_conns) self._enqueue_callbacks(self._networks_updated, self._networks) - def _update_ipv4_address(self): - if self._wifi_device is None: - cloudlog.warning("No WiFi device found") - return - + def _update_ipv4_address(self, active_conns): self._ipv4_address = "" - for conn_path in self._get_active_connections(): + for conn_path in active_conns: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] if conn_type == '802-11-wireless': From d977a5bd627292c4329e1fd07ac5483aa3750ab2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 15:13:30 -0800 Subject: [PATCH 171/685] ui: reduce wifi dbus calls during scanning pt. 2 (#37171) one GetAll instead of 2 calls per wifi activeconnection can't trust anyone these days --- system/ui/lib/wifi_manager.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 1d1dfc1145b..9d065803349 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -531,10 +531,10 @@ def _update_current_network_metered(self, active_conns) -> None: self._current_network_metered = MeteredType.UNKNOWN for active_conn in active_conns: conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] + props = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()).body[0] - if conn_type == '802-11-wireless': - conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1] + if props.get('Type', ('s', ''))[1] == '802-11-wireless': + conn_path = props.get('Connection', ('o', '/'))[1] if conn_path == "/": continue @@ -642,9 +642,9 @@ def _update_ipv4_address(self, active_conns): for conn_path in active_conns: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] - if conn_type == '802-11-wireless': - ip4config_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Ip4Config')).body[0][1] + props = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()).body[0] + if props.get('Type', ('s', ''))[1] == '802-11-wireless': + ip4config_path = props.get('Ip4Config', ('o', '/'))[1] if ip4config_path != "/": ip4config_addr = DBusAddress(ip4config_path, bus_name=NM, interface=NM_IP4_CONFIG_IFACE) From cddc3b9e8f69f28a23e165c0c138c8880691ac9b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 15:25:53 -0800 Subject: [PATCH 172/685] Reduce wifi dbus calls pt. 3 (#37172) * fewer calls to set metered * print * hell yeah * Revert "hell yeah" This reverts commit 0e0786bc204821329febc62a1b8dfd870e9aeb6e. * Revert "print" This reverts commit e9c7550496e9835888cb71c7dd622cbfb4624fbf. --- system/ui/lib/wifi_manager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 9d065803349..8cc99c620d1 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -555,10 +555,10 @@ def set_current_network_metered(self, metered: MeteredType): def worker(): for active_conn in self._get_active_connections(): conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - conn_type = self._router_main.send_and_get_reply(Properties(conn_addr).get('Type')).body[0][1] + props = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()).body[0] - if conn_type == '802-11-wireless' and not self.is_tethering_active(): - conn_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('Connection')).body[0][1] + if props.get('Type', ('s', ''))[1] == '802-11-wireless' and not self.is_tethering_active(): + conn_path = props.get('Connection', ('o', '/'))[1] if conn_path == "/": continue From f03efab907f4328c05fd05451ac4c067bbd3e8e5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 16:30:40 -0800 Subject: [PATCH 173/685] Reduce wifi dbus calls pt. 4 (#37174) * combine active AP and all APs into getall * combine these two functions reducing more calls * little clean up * down here --- system/ui/lib/wifi_manager.py | 61 +++++++++++++++-------------------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 8cc99c620d1..8912d2fd9a7 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -527,30 +527,6 @@ def worker(): threading.Thread(target=worker, daemon=True).start() - def _update_current_network_metered(self, active_conns) -> None: - self._current_network_metered = MeteredType.UNKNOWN - for active_conn in active_conns: - conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - props = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()).body[0] - - if props.get('Type', ('s', ''))[1] == '802-11-wireless': - conn_path = props.get('Connection', ('o', '/'))[1] - if conn_path == "/": - continue - - settings = self._get_connection_settings(conn_path) - - if len(settings) == 0: - cloudlog.warning(f'Failed to get connection settings for {conn_path}') - continue - - metered_prop = settings['connection'].get('metered', ('i', 0))[1] - if metered_prop == MeteredType.YES: - self._current_network_metered = MeteredType.YES - elif metered_prop == MeteredType.NO: - self._current_network_metered = MeteredType.NO - return - def set_current_network_metered(self, metered: MeteredType): def worker(): for active_conn in self._get_active_connections(): @@ -595,10 +571,11 @@ def _update_networks(self): cloudlog.warning("No WiFi device found") return - # returns '/' if no active AP + # NOTE: AccessPoints property may exclude hidden APs (use GetAllAccessPoints method if needed) wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) - active_ap_path = self._router_main.send_and_get_reply(Properties(wifi_addr).get('ActiveAccessPoint')).body[0][1] - ap_paths = self._router_main.send_and_get_reply(new_method_call(wifi_addr, 'GetAllAccessPoints')).body[0] + wifi_props = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()).body[0] + active_ap_path = wifi_props.get('ActiveAccessPoint', ('o', '/'))[1] + ap_paths = wifi_props.get('AccessPoints', ('ao', []))[1] aps: dict[str, list[AccessPoint]] = {} @@ -630,20 +607,20 @@ def _update_networks(self): networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) self._networks = networks - # Get active connections once - active_conns = self._get_active_connections() - self._update_ipv4_address(active_conns) - self._update_current_network_metered(active_conns) + self._update_active_connection_info() self._enqueue_callbacks(self._networks_updated, self._networks) - def _update_ipv4_address(self, active_conns): + def _update_active_connection_info(self): self._ipv4_address = "" + self._current_network_metered = MeteredType.UNKNOWN - for conn_path in active_conns: - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + for active_conn in self._get_active_connections(): + conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) props = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()).body[0] + if props.get('Type', ('s', ''))[1] == '802-11-wireless': + # IPv4 address ip4config_path = props.get('Ip4Config', ('o', '/'))[1] if ip4config_path != "/": @@ -653,7 +630,21 @@ def _update_ipv4_address(self, active_conns): for entry in address_data: if 'address' in entry: self._ipv4_address = entry['address'][1] - return + break + + # Metered status + conn_path = props.get('Connection', ('o', '/'))[1] + if conn_path != "/": + settings = self._get_connection_settings(conn_path) + + if len(settings) > 0: + metered_prop = settings['connection'].get('metered', ('i', 0))[1] + + if metered_prop == MeteredType.YES: + self._current_network_metered = MeteredType.YES + elif metered_prop == MeteredType.NO: + self._current_network_metered = MeteredType.NO + return def __del__(self): self.stop() From 0072449b015b394040b0e345634086331fc7198c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 17:07:41 -0800 Subject: [PATCH 174/685] WifiManager: cache connections until new/removed connection (#37175) * new/removed conns signal * clean up * only get connections when adding/removing not every refresh * add debug * block * Revert "block" This reverts commit 30bbffca8d2db21c53d7a3601ae46bf05e2a7cd5. * rm debug * block on any new message, faster conn rem/add reaction * better names --- system/ui/lib/wifi_manager.py | 91 ++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 8912d2fd9a7..aa486a8c033 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -146,6 +146,7 @@ def __init__(self): self._wifi_device: str | None = None # State + self._connections: dict[str, str] = {} # ssid -> connection path, updated via NM signals self._connecting_to_ssid: str = "" self._ipv4_address: str = "" self._current_network_metered: MeteredType = MeteredType.UNKNOWN @@ -181,7 +182,8 @@ def worker(): self._scan_thread.start() self._state_thread.start() - if Params is not None and self._tethering_ssid not in self._get_connections(): + self._update_connections() + if Params is not None and self._tethering_ssid not in self._connections: self._add_tethering_connection() self._tethering_password = self._get_tethering_password() @@ -235,45 +237,69 @@ def set_active(self, active: bool): self._last_network_update = 0.0 def _monitor_state(self): - rule = MatchRule( - type="signal", - interface=NM_DEVICE_IFACE, - member="StateChanged", - path=self._wifi_device, + # Filter for signals + rules = ( + MatchRule( + type="signal", + interface=NM_DEVICE_IFACE, + member="StateChanged", + path=self._wifi_device, + ), + MatchRule( + type="signal", + interface=NM_SETTINGS_IFACE, + member="NewConnection", + path=NM_SETTINGS_PATH, + ), + MatchRule( + type="signal", + interface=NM_SETTINGS_IFACE, + member="ConnectionRemoved", + path=NM_SETTINGS_PATH, + ) ) - # Filter for StateChanged signal - self._conn_monitor.send_and_get_reply(message_bus.AddMatch(rule)) + for rule in rules: + self._conn_monitor.send_and_get_reply(message_bus.AddMatch(rule)) - with self._conn_monitor.filter(rule, bufsize=SIGNAL_QUEUE_SIZE) as q: + with (self._conn_monitor.filter(rules[0], bufsize=SIGNAL_QUEUE_SIZE) as state_q, + self._conn_monitor.filter(rules[1], bufsize=SIGNAL_QUEUE_SIZE) as new_conn_q, + self._conn_monitor.filter(rules[2], bufsize=SIGNAL_QUEUE_SIZE) as removed_conn_q): while not self._exit: if not self._active: time.sleep(1) continue - # Block until a matching signal arrives try: - msg = self._conn_monitor.recv_until_filtered(q, timeout=1) + self._conn_monitor.recv_messages(timeout=1) except TimeoutError: continue - new_state, previous_state, change_reason = msg.body + # Connection added/removed + if len(new_conn_q) or len(removed_conn_q): + new_conn_q.clear() + removed_conn_q.clear() + self._update_connections() - # BAD PASSWORD - if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid): - self.forget_connection(self._connecting_to_ssid, block=True) - self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid) - self._connecting_to_ssid = "" + # Device state changes + while len(state_q): + new_state, previous_state, change_reason = state_q.popleft().body - elif new_state == NMDeviceState.ACTIVATED: - if len(self._activated): - self._update_networks() - self._enqueue_callbacks(self._activated) - self._connecting_to_ssid = "" + # BAD PASSWORD + if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid): + self.forget_connection(self._connecting_to_ssid, block=True) + self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid) + self._connecting_to_ssid = "" - elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: - self._connecting_to_ssid = "" - self._enqueue_callbacks(self._forgotten) + elif new_state == NMDeviceState.ACTIVATED: + if len(self._activated): + self._update_networks() + self._enqueue_callbacks(self._activated) + self._connecting_to_ssid = "" + + elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: + self._connecting_to_ssid = "" + self._enqueue_callbacks(self._forgotten) def _network_scanner(self): while not self._exit: @@ -307,7 +333,7 @@ def _get_adapter(self, adapter_type: int) -> str | None: cloudlog.exception(f"Error getting adapter type {adapter_type}: {e}") return None - def _get_connections(self) -> dict[str, str]: + def _update_connections(self) -> None: settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) known_connections = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'ListConnections')).body[0] @@ -323,7 +349,7 @@ def _get_connections(self) -> dict[str, str]: ssid = settings['802-11-wireless']['ssid'][1].decode("utf-8", "replace") if ssid != "": conns[ssid] = conn_path - return conns + self._connections = conns def _get_active_connections(self): return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] @@ -413,7 +439,7 @@ def worker(): def forget_connection(self, ssid: str, block: bool = False): def worker(): - conn_path = self._get_connections().get(ssid, None) + conn_path = self._connections.get(ssid, None) if conn_path is not None: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) @@ -429,7 +455,7 @@ def worker(): def activate_connection(self, ssid: str, block: bool = False): def worker(): - conn_path = self._get_connections().get(ssid, None) + conn_path = self._connections.get(ssid, None) if conn_path is not None: if self._wifi_device is None: cloudlog.warning("No WiFi device found") @@ -465,7 +491,7 @@ def is_tethering_active(self) -> bool: def set_tethering_password(self, password: str): def worker(): - conn_path = self._get_connections().get(self._tethering_ssid, None) + conn_path = self._connections.get(self._tethering_ssid, None) if conn_path is None: cloudlog.warning('No tethering connection found') return @@ -490,7 +516,7 @@ def worker(): threading.Thread(target=worker, daemon=True).start() def _get_tethering_password(self) -> str: - conn_path = self._get_connections().get(self._tethering_ssid, None) + conn_path = self._connections.get(self._tethering_ssid, None) if conn_path is None: cloudlog.warning('No tethering connection found') return '' @@ -601,8 +627,7 @@ def _update_networks(self): # catch all for parsing errors cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - known_connections = self._get_connections() - networks = [Network.from_dbus(ssid, ap_list, ssid in known_connections) for ssid, ap_list in aps.items()] + networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections) for ssid, ap_list in aps.items()] # sort with quantized strength to reduce jumping networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) self._networks = networks From 6cf95918c57671cb2266b72c890217c5170e3840 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 23:02:07 -0800 Subject: [PATCH 175/685] WifiManager: clean up connections (#37179) * fix recent connect regression from connection not being known yet * always update connections in background, keep track via signals only. no getallconnections each time one is added/deleted. matches c++ * works * clean up * clean up * clean up --- system/ui/lib/wifi_manager.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index aa486a8c033..1c80fa28d65 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -182,7 +182,7 @@ def worker(): self._scan_thread.start() self._state_thread.start() - self._update_connections() + self._init_connections() if Params is not None and self._tethering_ssid not in self._connections: self._add_tethering_connection() @@ -266,20 +266,18 @@ def _monitor_state(self): self._conn_monitor.filter(rules[1], bufsize=SIGNAL_QUEUE_SIZE) as new_conn_q, self._conn_monitor.filter(rules[2], bufsize=SIGNAL_QUEUE_SIZE) as removed_conn_q): while not self._exit: - if not self._active: - time.sleep(1) - continue - try: self._conn_monitor.recv_messages(timeout=1) except TimeoutError: continue # Connection added/removed - if len(new_conn_q) or len(removed_conn_q): - new_conn_q.clear() - removed_conn_q.clear() - self._update_connections() + while len(removed_conn_q): + conn_path = removed_conn_q.popleft().body[0] + self._connection_removed(conn_path) + while len(new_conn_q): + conn_path = new_conn_q.popleft().body[0] + self._new_connection(conn_path) # Device state changes while len(state_q): @@ -333,7 +331,7 @@ def _get_adapter(self, adapter_type: int) -> str | None: cloudlog.exception(f"Error getting adapter type {adapter_type}: {e}") return None - def _update_connections(self) -> None: + def _init_connections(self) -> None: settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) known_connections = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'ListConnections')).body[0] @@ -351,6 +349,19 @@ def _update_connections(self) -> None: conns[ssid] = conn_path self._connections = conns + def _new_connection(self, conn_path: str): + settings = self._get_connection_settings(conn_path) + + if "802-11-wireless" in settings: + ssid = settings['802-11-wireless']['ssid'][1].decode("utf-8", "replace") + if ssid != "": + self._connections[ssid] = conn_path + if ssid != self._tethering_ssid: + self.activate_connection(ssid, block=True) + + def _connection_removed(self, conn_path: str): + self._connections = {ssid: path for ssid, path in self._connections.items() if path != conn_path} + def _get_active_connections(self): return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] @@ -433,7 +444,6 @@ def worker(): settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) self._router_main.send_and_get_reply(new_method_call(settings_addr, 'AddConnection', 'a{sa{sv}}', (connection,))) - self.activate_connection(ssid, block=True) threading.Thread(target=worker, daemon=True).start() @@ -592,6 +602,9 @@ def _request_scan(self): cloudlog.warning(f"Failed to request scan: {reply}") def _update_networks(self): + if not self._active: + return + with self._lock: if self._wifi_device is None: cloudlog.warning("No WiFi device found") From b084294dc0384e3cc86832e5591dc3340ed017c6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 23:05:04 -0800 Subject: [PATCH 176/685] incorrect -> wrong --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index db68632c0e7..955dc4bfafe 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -425,7 +425,7 @@ def _connect_to_network(self, ssid: str): self._on_need_auth(network.ssid, False) def _on_need_auth(self, ssid, incorrect_password=True): - hint = "incorrect password..." if incorrect_password else "enter password..." + hint = "wrong password..." if incorrect_password else "enter password..." dlg = BigInputDialog(hint, "", minimum_length=8, confirm_callback=lambda _password: self._connect_with_password(ssid, _password)) # go back to the manage network page From 13b71b4e81825370828af81b02cda28033fe247b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 23:14:13 -0800 Subject: [PATCH 177/685] WifiManager: update networks on scan (#37177) * like c++ wifiman * rename to scan * can do this can do this * Revert "can do this" This reverts commit 295f7f49d448c6aacdde2ef810904df86357840b. * kinda useless now * clean up --- system/ui/lib/wifi_manager.py | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 1c80fa28d65..833a204cbb5 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -25,7 +25,7 @@ NM_SETTINGS_IFACE, NM_CONNECTION_IFACE, NM_DEVICE_IFACE, NM_DEVICE_TYPE_WIFI, NM_DEVICE_TYPE_MODEM, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, NM_DEVICE_STATE_REASON_NEW_ACTIVATION, NM_ACTIVE_CONNECTION_IFACE, - NM_IP4_CONFIG_IFACE, NMDeviceState) + NM_IP4_CONFIG_IFACE, NM_PROPERTIES_IFACE, NMDeviceState) try: from openpilot.common.params import Params @@ -153,7 +153,7 @@ def __init__(self): self._tethering_password: str = "" self._ipv4_forward = False - self._last_network_update: float = 0.0 + self._last_network_scan: float = 0.0 self._callback_queue: list[Callable] = [] self._tethering_ssid = "weedle" @@ -232,10 +232,6 @@ def process_callbacks(self): def set_active(self, active: bool): self._active = active - # Scan immediately if we haven't scanned in a while - if active and time.monotonic() - self._last_network_update > SCAN_PERIOD_SECONDS / 2: - self._last_network_update = 0.0 - def _monitor_state(self): # Filter for signals rules = ( @@ -256,7 +252,13 @@ def _monitor_state(self): interface=NM_SETTINGS_IFACE, member="ConnectionRemoved", path=NM_SETTINGS_PATH, - ) + ), + MatchRule( + type="signal", + interface=NM_PROPERTIES_IFACE, + member="PropertiesChanged", + path=self._wifi_device, + ), ) for rule in rules: @@ -264,7 +266,8 @@ def _monitor_state(self): with (self._conn_monitor.filter(rules[0], bufsize=SIGNAL_QUEUE_SIZE) as state_q, self._conn_monitor.filter(rules[1], bufsize=SIGNAL_QUEUE_SIZE) as new_conn_q, - self._conn_monitor.filter(rules[2], bufsize=SIGNAL_QUEUE_SIZE) as removed_conn_q): + self._conn_monitor.filter(rules[2], bufsize=SIGNAL_QUEUE_SIZE) as removed_conn_q, + self._conn_monitor.filter(rules[3], bufsize=SIGNAL_QUEUE_SIZE) as props_q): while not self._exit: try: self._conn_monitor.recv_messages(timeout=1) @@ -279,6 +282,12 @@ def _monitor_state(self): conn_path = new_conn_q.popleft().body[0] self._new_connection(conn_path) + # PropertiesChanged on wifi device (LastScan = scan complete) + while len(props_q): + iface, changed, _ = props_q.popleft().body + if iface == NM_WIRELESS_IFACE and 'LastScan' in changed: + self._update_networks() + # Device state changes while len(state_q): new_state, previous_state, change_reason = state_q.popleft().body @@ -302,12 +311,9 @@ def _monitor_state(self): def _network_scanner(self): while not self._exit: if self._active: - if time.monotonic() - self._last_network_update > SCAN_PERIOD_SECONDS: - # Scan for networks every 10 seconds - # TODO: should update when scan is complete (PropertiesChanged), but this is more than good enough for now - self._update_networks() + if time.monotonic() - self._last_network_scan > SCAN_PERIOD_SECONDS: self._request_scan() - self._last_network_update = time.monotonic() + self._last_network_scan = time.monotonic() time.sleep(1 / 2.) def _wait_for_wifi_device(self): From a46007d84d024bc699e3db8abd91c7ce0aff10b2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Feb 2026 23:14:38 -0800 Subject: [PATCH 178/685] WifiManager: safeguard an error response (#37182) safeguard --- system/ui/lib/wifi_manager.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 833a204cbb5..3d4da444bed 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -661,7 +661,13 @@ def _update_active_connection_info(self): for active_conn in self._get_active_connections(): conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - props = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()).body[0] + reply = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()) + + if reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to get active connection properties for {active_conn}: {reply}") + continue + + props = reply.body[0] if props.get('Type', ('s', ''))[1] == '802-11-wireless': # IPv4 address From af1583cdfc3f99fffefb1cc15320959d213239a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 12 Feb 2026 08:59:19 -0800 Subject: [PATCH 179/685] Reapply tgwarp w NV12 fix (#37168) * Revert "Revert tgwarp again (#37161)" This reverts commit 45099e7fcd384bb2dac36b55a6c10ba9de58a1e5. * Weird uv sizes * Fix interleaving * Fix on CPU * make CPU safe * Prevent corruption without clone * Claude knows speeed * fix interleaving * less kernels * blob caching * This is still slightly faster * Comment for blob cache --- Dockerfile.openpilot_base | 37 - SConstruct | 1 - common/SConscript | 1 - common/clutil.cc | 98 -- common/clutil.h | 28 - common/mat.h | 85 - msgq_repo | 2 +- selfdrive/modeld/SConscript | 57 +- selfdrive/modeld/compile_warp.py | 209 +++ selfdrive/modeld/dmonitoringmodeld.py | 39 +- selfdrive/modeld/modeld.py | 72 +- selfdrive/modeld/models/commonmodel.cc | 64 - selfdrive/modeld/models/commonmodel.h | 97 -- selfdrive/modeld/models/commonmodel.pxd | 27 - selfdrive/modeld/models/commonmodel_pyx.pxd | 13 - selfdrive/modeld/models/commonmodel_pyx.pyx | 74 - selfdrive/modeld/runners/tinygrad_helpers.py | 8 - selfdrive/modeld/transforms/loadyuv.cc | 76 - selfdrive/modeld/transforms/loadyuv.cl | 47 - selfdrive/modeld/transforms/loadyuv.h | 20 - selfdrive/modeld/transforms/transform.cc | 97 -- selfdrive/modeld/transforms/transform.cl | 54 - selfdrive/modeld/transforms/transform.h | 25 - selfdrive/test/process_replay/model_replay.py | 4 +- .../test/process_replay/process_replay.py | 17 +- system/camerad/SConscript | 2 +- system/camerad/cameras/camera_common.cc | 5 +- system/camerad/cameras/camera_common.h | 2 +- system/camerad/cameras/camera_qcom2.cc | 22 +- system/camerad/cameras/spectra.cc | 4 +- system/camerad/cameras/spectra.h | 2 +- system/hardware/tici/tests/test_power_draw.py | 2 +- system/loggerd/SConscript | 5 +- third_party/opencl/include/CL/cl.h | 1452 ----------------- third_party/opencl/include/CL/cl_d3d10.h | 131 -- third_party/opencl/include/CL/cl_d3d11.h | 131 -- .../opencl/include/CL/cl_dx9_media_sharing.h | 132 -- third_party/opencl/include/CL/cl_egl.h | 136 -- third_party/opencl/include/CL/cl_ext.h | 391 ----- third_party/opencl/include/CL/cl_ext_qcom.h | 255 --- third_party/opencl/include/CL/cl_gl.h | 167 -- third_party/opencl/include/CL/cl_gl_ext.h | 74 - third_party/opencl/include/CL/cl_platform.h | 1333 --------------- third_party/opencl/include/CL/opencl.h | 59 - tools/cabana/SConscript | 2 - tools/install_ubuntu_dependencies.sh | 3 - tools/replay/SConscript | 5 - tools/webcam/README.md | 4 - 48 files changed, 329 insertions(+), 5242 deletions(-) delete mode 100644 common/clutil.cc delete mode 100644 common/clutil.h delete mode 100644 common/mat.h create mode 100755 selfdrive/modeld/compile_warp.py delete mode 100644 selfdrive/modeld/models/commonmodel.cc delete mode 100644 selfdrive/modeld/models/commonmodel.h delete mode 100644 selfdrive/modeld/models/commonmodel.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pxd delete mode 100644 selfdrive/modeld/models/commonmodel_pyx.pyx delete mode 100644 selfdrive/modeld/runners/tinygrad_helpers.py delete mode 100644 selfdrive/modeld/transforms/loadyuv.cc delete mode 100644 selfdrive/modeld/transforms/loadyuv.cl delete mode 100644 selfdrive/modeld/transforms/loadyuv.h delete mode 100644 selfdrive/modeld/transforms/transform.cc delete mode 100644 selfdrive/modeld/transforms/transform.cl delete mode 100644 selfdrive/modeld/transforms/transform.h delete mode 100644 third_party/opencl/include/CL/cl.h delete mode 100644 third_party/opencl/include/CL/cl_d3d10.h delete mode 100644 third_party/opencl/include/CL/cl_d3d11.h delete mode 100644 third_party/opencl/include/CL/cl_dx9_media_sharing.h delete mode 100644 third_party/opencl/include/CL/cl_egl.h delete mode 100644 third_party/opencl/include/CL/cl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_ext_qcom.h delete mode 100644 third_party/opencl/include/CL/cl_gl.h delete mode 100644 third_party/opencl/include/CL/cl_gl_ext.h delete mode 100644 third_party/opencl/include/CL/cl_platform.h delete mode 100644 third_party/opencl/include/CL/opencl.h diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 44d8d95e95d..8a604129938 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -18,43 +18,6 @@ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ cd /usr/lib/gcc/arm-none-eabi/* && \ rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp -# Add OpenCL -RUN apt-get update && apt-get install -y --no-install-recommends \ - apt-utils \ - alien \ - unzip \ - tar \ - curl \ - xz-utils \ - dbus \ - gcc-arm-none-eabi \ - tmux \ - vim \ - libx11-6 \ - wget \ - && rm -rf /var/lib/apt/lists/* - -RUN mkdir -p /tmp/opencl-driver-intel && \ - cd /tmp/opencl-driver-intel && \ - wget https://github.com/intel/llvm/releases/download/2024-WW14/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - wget https://github.com/oneapi-src/oneTBB/releases/download/v2021.12.0/oneapi-tbb-2021.12.0-lin.tgz && \ - mkdir -p /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - cd /opt/intel/oclcpuexp_2024.17.3.0.09_rel && \ - tar -zxvf /tmp/opencl-driver-intel/oclcpuexp-2024.17.3.0.09_rel.tar.gz && \ - mkdir -p /etc/OpenCL/vendors && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64/libintelocl.so > /etc/OpenCL/vendors/intel_expcpu.icd && \ - cd /opt/intel && \ - tar -zxvf /tmp/opencl-driver-intel/oneapi-tbb-2021.12.0-lin.tgz && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbb.so.12 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - ln -s /opt/intel/oneapi-tbb-2021.12.0/lib/intel64/gcc4.8/libtbbmalloc.so.2 /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 && \ - mkdir -p /etc/ld.so.conf.d && \ - echo /opt/intel/oclcpuexp_2024.17.3.0.09_rel/x64 > /etc/ld.so.conf.d/libintelopenclexp.conf && \ - ldconfig -f /etc/ld.so.conf.d/libintelopenclexp.conf && \ - cd / && \ - rm -rf /tmp/opencl-driver-intel - ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute ENV QTWEBENGINE_DISABLE_SANDBOX=1 diff --git a/SConstruct b/SConstruct index ca5b7b6cb72..4f04be624cf 100644 --- a/SConstruct +++ b/SConstruct @@ -94,7 +94,6 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": - env.Append(CPPPATH=["#third_party/opencl/include"]) env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", diff --git a/common/SConscript b/common/SConscript index 1c68cf05c7a..15a0e5eff15 100644 --- a/common/SConscript +++ b/common/SConscript @@ -5,7 +5,6 @@ common_libs = [ 'swaglog.cc', 'util.cc', 'ratekeeper.cc', - 'clutil.cc', ] _common = env.Library('common', common_libs, LIBS="json11") diff --git a/common/clutil.cc b/common/clutil.cc deleted file mode 100644 index f8381a7e092..00000000000 --- a/common/clutil.cc +++ /dev/null @@ -1,98 +0,0 @@ -#include "common/clutil.h" - -#include -#include -#include - -#include "common/util.h" -#include "common/swaglog.h" - -namespace { // helper functions - -template -std::string get_info(Func get_info_func, Id id, Name param_name) { - size_t size = 0; - CL_CHECK(get_info_func(id, param_name, 0, NULL, &size)); - std::string info(size, '\0'); - CL_CHECK(get_info_func(id, param_name, size, info.data(), NULL)); - return info; -} -inline std::string get_platform_info(cl_platform_id id, cl_platform_info name) { return get_info(&clGetPlatformInfo, id, name); } -inline std::string get_device_info(cl_device_id id, cl_device_info name) { return get_info(&clGetDeviceInfo, id, name); } - -void cl_print_info(cl_platform_id platform, cl_device_id device) { - size_t work_group_size = 0; - cl_device_type device_type = 0; - clGetDeviceInfo(device, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(work_group_size), &work_group_size, NULL); - clGetDeviceInfo(device, CL_DEVICE_TYPE, sizeof(device_type), &device_type, NULL); - const char *type_str = "Other..."; - switch (device_type) { - case CL_DEVICE_TYPE_CPU: type_str ="CL_DEVICE_TYPE_CPU"; break; - case CL_DEVICE_TYPE_GPU: type_str = "CL_DEVICE_TYPE_GPU"; break; - case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; - } - - LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); - LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); - LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); - LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); - LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); - LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); - LOGD("max work group size: %zu", work_group_size); - LOGD("type = %d, %s", (int)device_type, type_str); -} - -void cl_print_build_errors(cl_program program, cl_device_id device) { - cl_build_status status; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, NULL); - size_t log_size; - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size); - std::string log(log_size, '\0'); - clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); - - LOGE("build failed; status=%d, log: %s", status, log.c_str()); -} - -} // namespace - -cl_device_id cl_get_device_id(cl_device_type device_type) { - cl_uint num_platforms = 0; - CL_CHECK(clGetPlatformIDs(0, NULL, &num_platforms)); - std::unique_ptr platform_ids = std::make_unique(num_platforms); - CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); - - for (size_t i = 0; i < num_platforms; ++i) { - LOGD("platform[%zu] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); - - // Get first device - if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { - cl_print_info(platform_ids[i], device_id); - return device_id; - } - } - LOGE("No valid openCL platform found"); - assert(0); - return nullptr; -} - -cl_context cl_create_context(cl_device_id device_id) { - return CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); -} - -void cl_release_context(cl_context context) { - clReleaseContext(context); -} - -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args) { - return cl_program_from_source(ctx, device_id, util::read_file(path), args); -} - -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args) { - const char *csrc = src.c_str(); - cl_program prg = CL_CHECK_ERR(clCreateProgramWithSource(ctx, 1, &csrc, NULL, &err)); - if (int err = clBuildProgram(prg, 1, &device_id, args, NULL, NULL); err != 0) { - cl_print_build_errors(prg, device_id); - assert(0); - } - return prg; -} diff --git a/common/clutil.h b/common/clutil.h deleted file mode 100644 index b364e79d45b..00000000000 --- a/common/clutil.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include - -#define CL_CHECK(_expr) \ - do { \ - assert(CL_SUCCESS == (_expr)); \ - } while (0) - -#define CL_CHECK_ERR(_expr) \ - ({ \ - cl_int err = CL_INVALID_VALUE; \ - __typeof__(_expr) _ret = _expr; \ - assert(_ret&& err == CL_SUCCESS); \ - _ret; \ - }) - -cl_device_id cl_get_device_id(cl_device_type device_type); -cl_context cl_create_context(cl_device_id device_id); -void cl_release_context(cl_context context); -cl_program cl_program_from_source(cl_context ctx, cl_device_id device_id, const std::string& src, const char* args = nullptr); -cl_program cl_program_from_file(cl_context ctx, cl_device_id device_id, const char* path, const char* args); diff --git a/common/mat.h b/common/mat.h deleted file mode 100644 index 8e10d619717..00000000000 --- a/common/mat.h +++ /dev/null @@ -1,85 +0,0 @@ -#pragma once - -typedef struct vec3 { - float v[3]; -} vec3; - -typedef struct vec4 { - float v[4]; -} vec4; - -typedef struct mat3 { - float v[3*3]; -} mat3; - -typedef struct mat4 { - float v[4*4]; -} mat4; - -static inline mat3 matmul3(const mat3 &a, const mat3 &b) { - mat3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - float v = 0.0; - for (int k=0; k<3; k++) { - v += a.v[r*3+k] * b.v[k*3+c]; - } - ret.v[r*3+c] = v; - } - } - return ret; -} - -static inline vec3 matvecmul3(const mat3 &a, const vec3 &b) { - vec3 ret = {{0.0}}; - for (int r=0; r<3; r++) { - for (int c=0; c<3; c++) { - ret.v[r] += a.v[r*3+c] * b.v[c]; - } - } - return ret; -} - -static inline mat4 matmul(const mat4 &a, const mat4 &b) { - mat4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - float v = 0.0; - for (int k=0; k<4; k++) { - v += a.v[r*4+k] * b.v[k*4+c]; - } - ret.v[r*4+c] = v; - } - } - return ret; -} - -static inline vec4 matvecmul(const mat4 &a, const vec4 &b) { - vec4 ret = {{0.0}}; - for (int r=0; r<4; r++) { - for (int c=0; c<4; c++) { - ret.v[r] += a.v[r*4+c] * b.v[c]; - } - } - return ret; -} - -// scales the input and output space of a transformation matrix -// that assumes pixel-center origin. -static inline mat3 transform_scale_buffer(const mat3 &in, float s) { - // in_pt = ( transform(out_pt/s + 0.5) - 0.5) * s - - mat3 transform_out = {{ - 1.0f/s, 0.0f, 0.5f, - 0.0f, 1.0f/s, 0.5f, - 0.0f, 0.0f, 1.0f, - }}; - - mat3 transform_in = {{ - s, 0.0f, -0.5f*s, - 0.0f, s, -0.5f*s, - 0.0f, 0.0f, 1.0f, - }}; - - return matmul3(transform_in, matmul3(in, transform_out)); -} diff --git a/msgq_repo b/msgq_repo index 4c4e814ed59..2c191c1a72a 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 4c4e814ed592db52431bb53d37f0bb93299e960c +Subproject commit 2c191c1a72ae8119b93b49e1bc12d4a99b751855 diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 91f3597447b..b13a84f70a4 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,35 +1,12 @@ import os import glob -Import('env', 'envCython', 'arch', 'cereal', 'messaging', 'common', 'visionipc') +Import('env', 'arch') lenv = env.Clone() -lenvCython = envCython.Clone() -libs = [cereal, messaging, visionipc, common, 'capnp', 'kj', 'pthread'] -frameworks = [] - -common_src = [ - "models/commonmodel.cc", - "transforms/loadyuv.cc", - "transforms/transform.cc", -] - -# OpenCL is a framework on Mac -if arch == "Darwin": - frameworks += ['OpenCL'] -else: - libs += ['OpenCL'] - -# Set path definitions -for pathdef, fn in {'TRANSFORM': 'transforms/transform.cl', 'LOADYUV': 'transforms/loadyuv.cl'}.items(): - for xenv in (lenv, lenvCython): - xenv['CXXFLAGS'].append(f'-D{pathdef}_PATH=\\"{File(fn).abspath}\\"') - -# Compile cython -cython_libs = envCython["LIBS"] + libs -commonmodel_lib = lenv.Library('commonmodel', common_src) -lenvCython.Program('models/commonmodel_pyx.so', 'models/commonmodel_pyx.pyx', LIBS=[commonmodel_lib, *cython_libs], FRAMEWORKS=frameworks) -tinygrad_files = sorted(["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=env.Dir("#").abspath) if 'pycache' not in x]) +tinygrad_root = env.Dir("#").abspath +tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) + if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: @@ -38,22 +15,36 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) +# compile warp +# THREADS=0 is need to prevent bug: https://github.com/tinygrad/tinygrad/issues/14689 +tg_flags = { + 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', + 'Darwin': f'DEV=CPU THREADS=0 HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env +}.get(arch, 'DEV=CPU CPU_LLVM=1 THREADS=0') +image_flag = { + 'larch64': 'IMAGE=2', +}.get(arch, 'IMAGE=0') +script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] +cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye +warp_targets = [] +for cam in [_ar_ox_fisheye, _os_fisheye]: + w, h = cam.width, cam.height + warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] +lenv.Command(warp_targets, tinygrad_files + script_files, cmd) + def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath return lenv.Command( fn + "_tinygrad.pkl", [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' ) # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: - flags = { - 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 IMAGE=2 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env - }.get(arch, 'DEV=CPU CPU_LLVM=1') - tg_compile(flags, model_name) + tg_compile(tg_flags, model_name) # Compile BIG model if USB GPU is available if "USBGPU" in os.environ: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py new file mode 100755 index 00000000000..75cc65f84cb --- /dev/null +++ b/selfdrive/modeld/compile_warp.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +import time +import pickle +import numpy as np +from pathlib import Path +from tinygrad.tensor import Tensor +from tinygrad.helpers import Context +from tinygrad.device import Device +from tinygrad.engine.jit import TinyJit + +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye + +MODELS_DIR = Path(__file__).parent / 'models' + +CAMERA_CONFIGS = [ + (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 + (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 +] + +UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) +UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) + +IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) + + +def warp_pkl_path(w, h): + return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + + +def dm_warp_pkl_path(w, h): + return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' + + +def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad): + w_dst, h_dst = dst_shape + h_src, w_src = src_shape + + x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst).reshape(-1) + y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst).reshape(-1) + + # inline 3x3 matmul as elementwise to avoid reduce op (enables fusion with gather) + src_x = M_inv[0, 0] * x + M_inv[0, 1] * y + M_inv[0, 2] + src_y = M_inv[1, 0] * x + M_inv[1, 1] * y + M_inv[1, 2] + src_w = M_inv[2, 0] * x + M_inv[2, 1] * y + M_inv[2, 2] + + src_x = src_x / src_w + src_y = src_y / src_w + + x_nn_clipped = Tensor.round(src_x).clip(0, w_src - 1).cast('int') + y_nn_clipped = Tensor.round(src_y).clip(0, h_src - 1).cast('int') + idx = y_nn_clipped * (w_src + stride_pad) + x_nn_clipped + + return src_flat[idx] + + +def frames_to_tensor(frames, model_w, model_h): + H = (frames.shape[0] * 2) // 3 + W = frames.shape[1] + in_img1 = Tensor.cat(frames[0:H:2, 0::2], + frames[1:H:2, 0::2], + frames[0:H:2, 1::2], + frames[1:H:2, 1::2], + frames[H:H+H//4].reshape((H//2, W//2)), + frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) + return in_img1 + + +def make_frame_prepare(cam_w, cam_h, model_w, model_h): + stride, y_height, uv_height, _ = get_nv12_info(cam_w, cam_h) + uv_offset = stride * y_height + stride_pad = stride - cam_w + + def frame_prepare_tinygrad(input_frame, M_inv): + # UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling + M_inv_uv = M_inv * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]]) + # deinterleave NV12 UV plane (UVUV... -> separate U, V) + uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride) + with Context(SPLIT_REDUCEOP=0): + y = warp_perspective_tinygrad(input_frame[:cam_h*stride], + M_inv, (model_w, model_h), + (cam_h, cam_w), stride_pad).realize() + u = warp_perspective_tinygrad(uv[:cam_h//2, :cam_w:2].flatten(), + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), 0).realize() + v = warp_perspective_tinygrad(uv[:cam_h//2, 1:cam_w:2].flatten(), + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), 0).realize() + yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) + tensor = frames_to_tensor(yuv, model_w, model_h) + return tensor + return frame_prepare_tinygrad + + +def make_update_img_input(frame_prepare, model_w, model_h): + def update_img_input_tinygrad(tensor, frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + new_img = frame_prepare(frame, M_inv) + full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() + return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) + return update_img_input_tinygrad + + +def make_update_both_imgs(frame_prepare, model_w, model_h): + update_img = make_update_img_input(frame_prepare, model_w, model_h) + + def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, + calib_big_img_buffer, new_big_img, M_inv_big): + calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) + calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) + return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair + return update_both_imgs_tinygrad + + +def make_warp_dm(cam_w, cam_h, dm_w, dm_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + stride_pad = stride - cam_w + + def warp_dm(input_frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad).reshape(-1, dm_h * dm_w) + return result + return warp_dm + + +def compile_modeld_warp(cam_w, cam_h): + model_w, model_h = MEDMODEL_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling modeld warp for {cam_w}x{cam_h}...") + + frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) + update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) + update_img_jit = TinyJit(update_both_imgs, prune=True) + + full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() + full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) + + for i in range(10): + new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + img_inputs = [full_buffer, + Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) + big_img_inputs = [big_full_buffer, + Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + inputs = img_inputs + big_img_inputs + Device.default.synchronize() + + inputs_np = [x.numpy() for x in inputs] + inputs_np[0] = full_buffer_np + inputs_np[3] = big_full_buffer_np + + st = time.perf_counter() + out = update_img_jit(*inputs) + full_buffer = out[0].contiguous().realize().clone() + big_full_buffer = out[2].contiguous().realize().clone() + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(update_img_jit, f) + print(f" Saved to {pkl_path}") + + jit = pickle.load(open(pkl_path, "rb")) + jit(*inputs) + + +def compile_dm_warp(cam_w, cam_h): + dm_w, dm_h = DM_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling DM warp for {cam_w}x{cam_h}...") + + warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) + warp_dm_jit = TinyJit(warp_dm, prune=True) + + for i in range(10): + inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + Device.default.synchronize() + st = time.perf_counter() + warp_dm_jit(*inputs) + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + pkl_path = dm_warp_pkl_path(cam_w, cam_h) + with open(pkl_path, "wb") as f: + pickle.dump(warp_dm_jit, f) + print(f" Saved to {pkl_path}") + + +def run_and_save_pickle(): + for cam_w, cam_h in CAMERA_CONFIGS: + compile_modeld_warp(cam_w, cam_h) + compile_dm_warp(cam_w, cam_h) + + +if __name__ == "__main__": + run_and_save_pickle() diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 7177998571c..94871c19906 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -3,7 +3,6 @@ from openpilot.system.hardware import TICI os.environ['DEV'] = 'QCOM' if TICI else 'CPU' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -16,32 +15,35 @@ from openpilot.common.realtime import config_realtime_process from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -from openpilot.selfdrive.modeld.models.commonmodel_pyx import CLContext, MonitoringModelFrame +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' - +MODELS_DIR = Path(__file__).parent / 'models' class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self, cl_ctx): + def __init__(self): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] self.output_slices = model_metadata['output_slices'] - self.frame = MonitoringModelFrame(cl_ctx) self.numpy_inputs = { 'calib': np.zeros(self.input_shapes['calib'], dtype=np.float32), } + self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} + self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} + self.frame_buf_params = None self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} + self._blob_cache : dict[int, Tensor] = {} + self.image_warp = None with open(MODEL_PKL_PATH, "rb") as f: self.model_run = pickle.load(f) @@ -50,16 +52,20 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple t1 = time.perf_counter() - input_img_cl = self.frame.prepare(buf, transform.flatten()) - if TICI: - # The imgs tensors are backed by opencl memory, only need init once - if 'input_img' not in self.tensor_inputs: - self.tensor_inputs['input_img'] = qcom_tensor_from_opencl_address(input_img_cl.mem_address, self.input_shapes['input_img'], dtype=dtypes.uint8) - else: - self.tensor_inputs['input_img'] = Tensor(self.frame.buffer_from_cl(input_img_cl).reshape(self.input_shapes['input_img']), dtype=dtypes.uint8).realize() + if self.image_warp is None: + self.frame_buf_params = get_nv12_info(buf.width, buf.height) + warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.image_warp = pickle.load(f) + ptr = buf.data.ctypes.data + # There is a ringbuffer of imgs, just cache tensors pointing to all of them + if ptr not in self._blob_cache: + self._blob_cache[ptr] = Tensor.from_blob(ptr, (self.frame_buf_params[3],), dtype='uint8') + self.warp_inputs_np['transform'][:] = transform[:] + self.tensor_inputs['input_img'] = self.image_warp(self._blob_cache[ptr], self.warp_inputs['transform']).realize() - output = self.model_run(**self.tensor_inputs).numpy().flatten() + output = self.model_run(**self.tensor_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() t2 = time.perf_counter() return output, t2 - t1 @@ -107,12 +113,11 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - cl_context = CLContext() - model = ModelState(cl_context) + model = ModelState() cloudlog.warning("models loaded, dmonitoringmodeld starting") cloudlog.warning("connecting to driver stream") - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True, cl_context) + vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) while not vipc_client.connect(False): time.sleep(0.1) assert vipc_client.is_connected() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 846bb8d2c31..df000979e85 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -7,7 +7,6 @@ os.environ['DEV'] = 'AMD' os.environ['AMD_IFACE'] = 'USB' from tinygrad.tensor import Tensor -from tinygrad.dtype import dtypes import time import pickle import numpy as np @@ -22,14 +21,13 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.transformations.camera import DEVICE_CAMERAS +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.common.transformations.model import get_warp_matrix from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.selfdrive.modeld.constants import ModelConstants, Plan -from openpilot.selfdrive.modeld.models.commonmodel_pyx import DrivingModelFrame, CLContext -from openpilot.selfdrive.modeld.runners.tinygrad_helpers import qcom_tensor_from_opencl_address PROCESS_NAME = "selfdrive.modeld.modeld" @@ -39,11 +37,15 @@ POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' +MODELS_DIR = Path(__file__).parent / 'models' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 +IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) +assert IMG_QUEUE_SHAPE[0] == 30 + def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, lat_action_t: float, long_action_t: float, v_ego: float) -> log.ModelDataV2.Action: @@ -136,12 +138,11 @@ def get(self, *names) -> dict[str, np.ndarray]: return out class ModelState: - frames: dict[str, DrivingModelFrame] inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self, context: CLContext): + def __init__(self): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] @@ -155,7 +156,6 @@ def __init__(self, context: CLContext): self.policy_output_slices = policy_metadata['output_slices'] policy_output_size = policy_metadata['output_shapes']['outputs'][1] - self.frames = {name: DrivingModelFrame(context, ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ) for name in self.vision_input_names} self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) # policy inputs @@ -165,13 +165,18 @@ def __init__(self, context: CLContext): self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) self.full_input_queues.reset() - # img buffers are managed in openCL transform code - self.vision_inputs: dict[str, Tensor] = {} + self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), + 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize()} + self.full_frames : dict[str, Tensor] = {} + self._blob_cache : dict[int, Tensor] = {} + self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} + self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() - + self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} + self.update_imgs = None with open(VISION_PKL_PATH, "rb") as f: self.vision_run = pickle.load(f) @@ -188,23 +193,33 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], inputs['desire_pulse'][0] = 0 new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) self.prev_desire[:] = inputs['desire_pulse'] - - imgs_cl = {name: self.frames[name].prepare(bufs[name], transforms[name].flatten()) for name in self.vision_input_names} - - if TICI and not USBGPU: - # The imgs tensors are backed by opencl memory, only need init once - for key in imgs_cl: - if key not in self.vision_inputs: - self.vision_inputs[key] = qcom_tensor_from_opencl_address(imgs_cl[key].mem_address, self.vision_input_shapes[key], dtype=dtypes.uint8) - else: - for key in imgs_cl: - frame_input = self.frames[key].buffer_from_cl(imgs_cl[key]).reshape(self.vision_input_shapes[key]) - self.vision_inputs[key] = Tensor(frame_input, dtype=dtypes.uint8).realize() + if self.update_imgs is None: + for key in bufs.keys(): + w, h = bufs[key].width, bufs[key].height + self.frame_buf_params[key] = get_nv12_info(w, h) + warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' + with open(warp_path, "rb") as f: + self.update_imgs = pickle.load(f) + + for key in bufs.keys(): + ptr = bufs[key].data.ctypes.data + yuv_size = self.frame_buf_params[key][3] + # There is a ringbuffer of imgs, just cache tensors pointing to all of them + if ptr not in self._blob_cache: + self._blob_cache[ptr] = Tensor.from_blob(ptr, (yuv_size,), dtype='uint8') + self.full_frames[key] = self._blob_cache[ptr] + for key in bufs.keys(): + self.transforms_np[key][:,:] = transforms[key][:,:] + + out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], + self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) + self.img_queues['img'], self.img_queues['big_img'] = out[0].realize(), out[2].realize() + vision_inputs = {'img': out[1], 'big_img': out[3]} if prepare_only: return None - self.vision_output = self.vision_run(**self.vision_inputs).contiguous().realize().uop.base.buffer.numpy() + self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) @@ -212,9 +227,8 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k] self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention'] - self.policy_output = self.policy_run(**self.policy_inputs).numpy().flatten() + self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) - combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) @@ -231,10 +245,8 @@ def main(demo=False): config_realtime_process(7, 54) st = time.monotonic() - cloudlog.warning("setting up CL context") - cl_context = CLContext() - cloudlog.warning("CL context ready; loading model") - model = ModelState(cl_context) + cloudlog.warning("loading model") + model = ModelState() cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") # visionipc clients @@ -247,8 +259,8 @@ def main(demo=False): time.sleep(.1) vipc_client_main_stream = VisionStreamType.VISION_STREAM_WIDE_ROAD if main_wide_camera else VisionStreamType.VISION_STREAM_ROAD - vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True, cl_context) - vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False, cl_context) + vipc_client_main = VisionIpcClient("camerad", vipc_client_main_stream, True) + vipc_client_extra = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_WIDE_ROAD, False) cloudlog.warning(f"vision stream set up, main_wide_camera: {main_wide_camera}, use_extra_client: {use_extra_client}") while not vipc_client_main.connect(False): diff --git a/selfdrive/modeld/models/commonmodel.cc b/selfdrive/modeld/models/commonmodel.cc deleted file mode 100644 index d3341e76ec3..00000000000 --- a/selfdrive/modeld/models/commonmodel.cc +++ /dev/null @@ -1,64 +0,0 @@ -#include "selfdrive/modeld/models/commonmodel.h" - -#include -#include - -#include "common/clutil.h" - -DrivingModelFrame::DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - temporal_skip = _temporal_skip; - input_frames_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - img_buffer_20hz_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (temporal_skip+1)*frame_size_bytes, NULL, &err)); - region.origin = temporal_skip * frame_size_bytes; - region.size = frame_size_bytes; - last_img_cl = CL_CHECK_ERR(clCreateSubBuffer(img_buffer_20hz_cl, CL_MEM_READ_WRITE, CL_BUFFER_CREATE_TYPE_REGION, ®ion, &err)); - - loadyuv_init(&loadyuv, context, device_id, MODEL_WIDTH, MODEL_HEIGHT); - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* DrivingModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - - for (int i = 0; i < temporal_skip; i++) { - CL_CHECK(clEnqueueCopyBuffer(q, img_buffer_20hz_cl, img_buffer_20hz_cl, (i+1)*frame_size_bytes, i*frame_size_bytes, frame_size_bytes, 0, nullptr, nullptr)); - } - loadyuv_queue(&loadyuv, q, y_cl, u_cl, v_cl, last_img_cl); - - copy_queue(&loadyuv, q, img_buffer_20hz_cl, input_frames_cl, 0, 0, frame_size_bytes); - copy_queue(&loadyuv, q, last_img_cl, input_frames_cl, 0, frame_size_bytes, frame_size_bytes); - - // NOTE: Since thneed is using a different command queue, this clFinish is needed to ensure the image is ready. - clFinish(q); - return &input_frames_cl; -} - -DrivingModelFrame::~DrivingModelFrame() { - deinit_transform(); - loadyuv_destroy(&loadyuv); - CL_CHECK(clReleaseMemObject(input_frames_cl)); - CL_CHECK(clReleaseMemObject(img_buffer_20hz_cl)); - CL_CHECK(clReleaseMemObject(last_img_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} - - -MonitoringModelFrame::MonitoringModelFrame(cl_device_id device_id, cl_context context) : ModelFrame(device_id, context) { - input_frames = std::make_unique(buf_size); - input_frame_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, buf_size, NULL, &err)); - - init_transform(device_id, context, MODEL_WIDTH, MODEL_HEIGHT); -} - -cl_mem* MonitoringModelFrame::prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - run_transform(yuv_cl, MODEL_WIDTH, MODEL_HEIGHT, frame_width, frame_height, frame_stride, frame_uv_offset, projection); - clFinish(q); - return &y_cl; -} - -MonitoringModelFrame::~MonitoringModelFrame() { - deinit_transform(); - CL_CHECK(clReleaseMemObject(input_frame_cl)); - CL_CHECK(clReleaseCommandQueue(q)); -} diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h deleted file mode 100644 index 176d7eb6dcf..00000000000 --- a/selfdrive/modeld/models/commonmodel.h +++ /dev/null @@ -1,97 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" -#include "selfdrive/modeld/transforms/loadyuv.h" -#include "selfdrive/modeld/transforms/transform.h" - -class ModelFrame { -public: - ModelFrame(cl_device_id device_id, cl_context context) { - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); - } - virtual ~ModelFrame() {} - virtual cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { return NULL; } - uint8_t* buffer_from_cl(cl_mem *in_frames, int buffer_size) { - CL_CHECK(clEnqueueReadBuffer(q, *in_frames, CL_TRUE, 0, buffer_size, input_frames.get(), 0, nullptr, nullptr)); - clFinish(q); - return &input_frames[0]; - } - - int MODEL_WIDTH; - int MODEL_HEIGHT; - int MODEL_FRAME_SIZE; - int buf_size; - -protected: - cl_mem y_cl, u_cl, v_cl; - Transform transform; - cl_command_queue q; - std::unique_ptr input_frames; - - void init_transform(cl_device_id device_id, cl_context context, int model_width, int model_height) { - y_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, model_width * model_height, NULL, &err)); - u_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - v_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, (model_width / 2) * (model_height / 2), NULL, &err)); - transform_init(&transform, context, device_id); - } - - void deinit_transform() { - transform_destroy(&transform); - CL_CHECK(clReleaseMemObject(v_cl)); - CL_CHECK(clReleaseMemObject(u_cl)); - CL_CHECK(clReleaseMemObject(y_cl)); - } - - void run_transform(cl_mem yuv_cl, int model_width, int model_height, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection) { - transform_queue(&transform, q, - yuv_cl, frame_width, frame_height, frame_stride, frame_uv_offset, - y_cl, u_cl, v_cl, model_width, model_height, projection); - } -}; - -class DrivingModelFrame : public ModelFrame { -public: - DrivingModelFrame(cl_device_id device_id, cl_context context, int _temporal_skip); - ~DrivingModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 512; - const int MODEL_HEIGHT = 256; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const int buf_size = MODEL_FRAME_SIZE * 2; // 2 frames are temporal_skip frames apart - const size_t frame_size_bytes = MODEL_FRAME_SIZE * sizeof(uint8_t); - -private: - LoadYUVState loadyuv; - cl_mem img_buffer_20hz_cl, last_img_cl, input_frames_cl; - cl_buffer_region region; - int temporal_skip; -}; - -class MonitoringModelFrame : public ModelFrame { -public: - MonitoringModelFrame(cl_device_id device_id, cl_context context); - ~MonitoringModelFrame(); - cl_mem* prepare(cl_mem yuv_cl, int frame_width, int frame_height, int frame_stride, int frame_uv_offset, const mat3& projection); - - const int MODEL_WIDTH = 1440; - const int MODEL_HEIGHT = 960; - const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT; - const int buf_size = MODEL_FRAME_SIZE; - -private: - cl_mem input_frame_cl; -}; diff --git a/selfdrive/modeld/models/commonmodel.pxd b/selfdrive/modeld/models/commonmodel.pxd deleted file mode 100644 index 4ac64d91720..00000000000 --- a/selfdrive/modeld/models/commonmodel.pxd +++ /dev/null @@ -1,27 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_device_id, cl_context, cl_mem - -cdef extern from "common/mat.h": - cdef struct mat3: - float v[9] - -cdef extern from "common/clutil.h": - cdef unsigned long CL_DEVICE_TYPE_DEFAULT - cl_device_id cl_get_device_id(unsigned long) - cl_context cl_create_context(cl_device_id) - void cl_release_context(cl_context) - -cdef extern from "selfdrive/modeld/models/commonmodel.h": - cppclass ModelFrame: - int buf_size - unsigned char * buffer_from_cl(cl_mem*, int); - cl_mem * prepare(cl_mem, int, int, int, int, mat3) - - cppclass DrivingModelFrame: - int buf_size - DrivingModelFrame(cl_device_id, cl_context, int) - - cppclass MonitoringModelFrame: - int buf_size - MonitoringModelFrame(cl_device_id, cl_context) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd deleted file mode 100644 index 0bb798625be..00000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pxd +++ /dev/null @@ -1,13 +0,0 @@ -# distutils: language = c++ - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport CLContext as BaseCLContext - -cdef class CLContext(BaseCLContext): - pass - -cdef class CLMem: - cdef cl_mem * mem - - @staticmethod - cdef create(void*) diff --git a/selfdrive/modeld/models/commonmodel_pyx.pyx b/selfdrive/modeld/models/commonmodel_pyx.pyx deleted file mode 100644 index 5b7d11bc71a..00000000000 --- a/selfdrive/modeld/models/commonmodel_pyx.pyx +++ /dev/null @@ -1,74 +0,0 @@ -# distutils: language = c++ -# cython: c_string_encoding=ascii, language_level=3 - -import numpy as np -cimport numpy as cnp -from libc.string cimport memcpy -from libc.stdint cimport uintptr_t - -from msgq.visionipc.visionipc cimport cl_mem -from msgq.visionipc.visionipc_pyx cimport VisionBuf, CLContext as BaseCLContext -from .commonmodel cimport CL_DEVICE_TYPE_DEFAULT, cl_get_device_id, cl_create_context, cl_release_context -from .commonmodel cimport mat3, ModelFrame as cppModelFrame, DrivingModelFrame as cppDrivingModelFrame, MonitoringModelFrame as cppMonitoringModelFrame - - -cdef class CLContext(BaseCLContext): - def __cinit__(self): - self.device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT) - self.context = cl_create_context(self.device_id) - - def __dealloc__(self): - if self.context: - cl_release_context(self.context) - -cdef class CLMem: - @staticmethod - cdef create(void * cmem): - mem = CLMem() - mem.mem = cmem - return mem - - @property - def mem_address(self): - return (self.mem) - -def cl_from_visionbuf(VisionBuf buf): - return CLMem.create(&buf.buf.buf_cl) - - -cdef class ModelFrame: - cdef cppModelFrame * frame - cdef int buf_size - - def __dealloc__(self): - del self.frame - - def prepare(self, VisionBuf buf, float[:] projection): - cdef mat3 cprojection - memcpy(cprojection.v, &projection[0], 9*sizeof(float)) - cdef cl_mem * data - data = self.frame.prepare(buf.buf.buf_cl, buf.width, buf.height, buf.stride, buf.uv_offset, cprojection) - return CLMem.create(data) - - def buffer_from_cl(self, CLMem in_frames): - cdef unsigned char * data2 - data2 = self.frame.buffer_from_cl(in_frames.mem, self.buf_size) - return np.asarray( data2) - - -cdef class DrivingModelFrame(ModelFrame): - cdef cppDrivingModelFrame * _frame - - def __cinit__(self, CLContext context, int temporal_skip): - self._frame = new cppDrivingModelFrame(context.device_id, context.context, temporal_skip) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - -cdef class MonitoringModelFrame(ModelFrame): - cdef cppMonitoringModelFrame * _frame - - def __cinit__(self, CLContext context): - self._frame = new cppMonitoringModelFrame(context.device_id, context.context) - self.frame = (self._frame) - self.buf_size = self._frame.buf_size - diff --git a/selfdrive/modeld/runners/tinygrad_helpers.py b/selfdrive/modeld/runners/tinygrad_helpers.py deleted file mode 100644 index 776381341cf..00000000000 --- a/selfdrive/modeld/runners/tinygrad_helpers.py +++ /dev/null @@ -1,8 +0,0 @@ - -from tinygrad.tensor import Tensor -from tinygrad.helpers import to_mv - -def qcom_tensor_from_opencl_address(opencl_address, shape, dtype): - cl_buf_desc_ptr = to_mv(opencl_address, 8).cast('Q')[0] - rawbuf_ptr = to_mv(cl_buf_desc_ptr, 0x100).cast('Q')[20] # offset 0xA0 is a raw gpu pointer. - return Tensor.from_blob(rawbuf_ptr, shape, dtype=dtype, device='QCOM') diff --git a/selfdrive/modeld/transforms/loadyuv.cc b/selfdrive/modeld/transforms/loadyuv.cc deleted file mode 100644 index c93f5cd0381..00000000000 --- a/selfdrive/modeld/transforms/loadyuv.cc +++ /dev/null @@ -1,76 +0,0 @@ -#include "selfdrive/modeld/transforms/loadyuv.h" - -#include -#include -#include - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height) { - memset(s, 0, sizeof(*s)); - - s->width = width; - s->height = height; - - char args[1024]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DTRANSFORMED_WIDTH=%d -DTRANSFORMED_HEIGHT=%d", - width, height); - cl_program prg = cl_program_from_file(ctx, device_id, LOADYUV_PATH, args); - - s->loadys_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loadys", &err)); - s->loaduv_krnl = CL_CHECK_ERR(clCreateKernel(prg, "loaduv", &err)); - s->copy_krnl = CL_CHECK_ERR(clCreateKernel(prg, "copy", &err)); - - // done with this - CL_CHECK(clReleaseProgram(prg)); -} - -void loadyuv_destroy(LoadYUVState* s) { - CL_CHECK(clReleaseKernel(s->loadys_krnl)); - CL_CHECK(clReleaseKernel(s->loaduv_krnl)); - CL_CHECK(clReleaseKernel(s->copy_krnl)); -} - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl) { - cl_int global_out_off = 0; - - CL_CHECK(clSetKernelArg(s->loadys_krnl, 0, sizeof(cl_mem), &y_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loadys_krnl, 2, sizeof(cl_int), &global_out_off)); - - const size_t loadys_work_size = (s->width*s->height)/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->loadys_krnl, 1, NULL, - &loadys_work_size, NULL, 0, 0, NULL)); - - const size_t loaduv_work_size = ((s->width/2)*(s->height/2))/8; - global_out_off += (s->width*s->height); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &u_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); - - global_out_off += (s->width/2)*(s->height/2); - - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 0, sizeof(cl_mem), &v_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 1, sizeof(cl_mem), &out_cl)); - CL_CHECK(clSetKernelArg(s->loaduv_krnl, 2, sizeof(cl_int), &global_out_off)); - - CL_CHECK(clEnqueueNDRangeKernel(q, s->loaduv_krnl, 1, NULL, - &loaduv_work_size, NULL, 0, 0, NULL)); -} - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size) { - CL_CHECK(clSetKernelArg(s->copy_krnl, 0, sizeof(cl_mem), &src)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 1, sizeof(cl_mem), &dst)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 2, sizeof(cl_int), &src_offset)); - CL_CHECK(clSetKernelArg(s->copy_krnl, 3, sizeof(cl_int), &dst_offset)); - const size_t copy_work_size = size/8; - CL_CHECK(clEnqueueNDRangeKernel(q, s->copy_krnl, 1, NULL, - ©_work_size, NULL, 0, 0, NULL)); -} \ No newline at end of file diff --git a/selfdrive/modeld/transforms/loadyuv.cl b/selfdrive/modeld/transforms/loadyuv.cl deleted file mode 100644 index 970187a6d70..00000000000 --- a/selfdrive/modeld/transforms/loadyuv.cl +++ /dev/null @@ -1,47 +0,0 @@ -#define UV_SIZE ((TRANSFORMED_WIDTH/2)*(TRANSFORMED_HEIGHT/2)) - -__kernel void loadys(__global uchar8 const * const Y, - __global uchar * out, - int out_offset) -{ - const int gid = get_global_id(0); - const int ois = gid * 8; - const int oy = ois / TRANSFORMED_WIDTH; - const int ox = ois % TRANSFORMED_WIDTH; - - const uchar8 ys = Y[gid]; - - // 02 - // 13 - - __global uchar* outy0; - __global uchar* outy1; - if ((oy & 1) == 0) { - outy0 = out + out_offset; //y0 - outy1 = out + out_offset + UV_SIZE*2; //y2 - } else { - outy0 = out + out_offset + UV_SIZE; //y1 - outy1 = out + out_offset + UV_SIZE*3; //y3 - } - - vstore4(ys.s0246, 0, outy0 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); - vstore4(ys.s1357, 0, outy1 + (oy/2) * (TRANSFORMED_WIDTH/2) + ox/2); -} - -__kernel void loaduv(__global uchar8 const * const in, - __global uchar8 * out, - int out_offset) -{ - const int gid = get_global_id(0); - const uchar8 inv = in[gid]; - out[gid + out_offset / 8] = inv; -} - -__kernel void copy(__global uchar8 * in, - __global uchar8 * out, - int in_offset, - int out_offset) -{ - const int gid = get_global_id(0); - out[gid + out_offset / 8] = in[gid + in_offset / 8]; -} diff --git a/selfdrive/modeld/transforms/loadyuv.h b/selfdrive/modeld/transforms/loadyuv.h deleted file mode 100644 index 659059cd25e..00000000000 --- a/selfdrive/modeld/transforms/loadyuv.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include "common/clutil.h" - -typedef struct { - int width, height; - cl_kernel loadys_krnl, loaduv_krnl, copy_krnl; -} LoadYUVState; - -void loadyuv_init(LoadYUVState* s, cl_context ctx, cl_device_id device_id, int width, int height); - -void loadyuv_destroy(LoadYUVState* s); - -void loadyuv_queue(LoadYUVState* s, cl_command_queue q, - cl_mem y_cl, cl_mem u_cl, cl_mem v_cl, - cl_mem out_cl); - - -void copy_queue(LoadYUVState* s, cl_command_queue q, cl_mem src, cl_mem dst, - size_t src_offset, size_t dst_offset, size_t size); \ No newline at end of file diff --git a/selfdrive/modeld/transforms/transform.cc b/selfdrive/modeld/transforms/transform.cc deleted file mode 100644 index 305643cf42e..00000000000 --- a/selfdrive/modeld/transforms/transform.cc +++ /dev/null @@ -1,97 +0,0 @@ -#include "selfdrive/modeld/transforms/transform.h" - -#include -#include - -#include "common/clutil.h" - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id) { - memset(s, 0, sizeof(*s)); - - cl_program prg = cl_program_from_file(ctx, device_id, TRANSFORM_PATH, ""); - s->krnl = CL_CHECK_ERR(clCreateKernel(prg, "warpPerspective", &err)); - // done with this - CL_CHECK(clReleaseProgram(prg)); - - s->m_y_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); - s->m_uv_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, 3*3*sizeof(float), NULL, &err)); -} - -void transform_destroy(Transform* s) { - CL_CHECK(clReleaseMemObject(s->m_y_cl)); - CL_CHECK(clReleaseMemObject(s->m_uv_cl)); - CL_CHECK(clReleaseKernel(s->krnl)); -} - -void transform_queue(Transform* s, - cl_command_queue q, - cl_mem in_yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection) { - const int zero = 0; - - // sampled using pixel center origin - // (because that's how fastcv and opencv does it) - - mat3 projection_y = projection; - - // in and out uv is half the size of y. - mat3 projection_uv = transform_scale_buffer(projection, 0.5); - - CL_CHECK(clEnqueueWriteBuffer(q, s->m_y_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_y.v, 0, NULL, NULL)); - CL_CHECK(clEnqueueWriteBuffer(q, s->m_uv_cl, CL_TRUE, 0, 3*3*sizeof(float), (void*)projection_uv.v, 0, NULL, NULL)); - - const int in_y_width = in_width; - const int in_y_height = in_height; - const int in_y_px_stride = 1; - const int in_uv_width = in_width/2; - const int in_uv_height = in_height/2; - const int in_uv_px_stride = 2; - const int in_u_offset = in_uv_offset; - const int in_v_offset = in_uv_offset + 1; - - const int out_y_width = out_width; - const int out_y_height = out_height; - const int out_uv_width = out_width/2; - const int out_uv_height = out_height/2; - - CL_CHECK(clSetKernelArg(s->krnl, 0, sizeof(cl_mem), &in_yuv)); // src - CL_CHECK(clSetKernelArg(s->krnl, 1, sizeof(cl_int), &in_stride)); // src_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_y_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &zero)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_y_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_y_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_y)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_y_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_y_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_y_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_y_cl)); // M - - const size_t work_size_y[2] = {(size_t)out_y_width, (size_t)out_y_height}; - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_y, NULL, 0, 0, NULL)); - - const size_t work_size_uv[2] = {(size_t)out_uv_width, (size_t)out_uv_height}; - - CL_CHECK(clSetKernelArg(s->krnl, 2, sizeof(cl_int), &in_uv_px_stride)); // src_px_stride - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_u_offset)); // src_offset - CL_CHECK(clSetKernelArg(s->krnl, 4, sizeof(cl_int), &in_uv_height)); // src_rows - CL_CHECK(clSetKernelArg(s->krnl, 5, sizeof(cl_int), &in_uv_width)); // src_cols - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_u)); // dst - CL_CHECK(clSetKernelArg(s->krnl, 7, sizeof(cl_int), &out_uv_width)); // dst_row_stride - CL_CHECK(clSetKernelArg(s->krnl, 8, sizeof(cl_int), &zero)); // dst_offset - CL_CHECK(clSetKernelArg(s->krnl, 9, sizeof(cl_int), &out_uv_height)); // dst_rows - CL_CHECK(clSetKernelArg(s->krnl, 10, sizeof(cl_int), &out_uv_width)); // dst_cols - CL_CHECK(clSetKernelArg(s->krnl, 11, sizeof(cl_mem), &s->m_uv_cl)); // M - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); - CL_CHECK(clSetKernelArg(s->krnl, 3, sizeof(cl_int), &in_v_offset)); // src_ofset - CL_CHECK(clSetKernelArg(s->krnl, 6, sizeof(cl_mem), &out_v)); // dst - - CL_CHECK(clEnqueueNDRangeKernel(q, s->krnl, 2, NULL, - (const size_t*)&work_size_uv, NULL, 0, 0, NULL)); -} diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl deleted file mode 100644 index 2ca25920cd1..00000000000 --- a/selfdrive/modeld/transforms/transform.cl +++ /dev/null @@ -1,54 +0,0 @@ -#define INTER_BITS 5 -#define INTER_TAB_SIZE (1 << INTER_BITS) -#define INTER_SCALE 1.f / INTER_TAB_SIZE - -#define INTER_REMAP_COEF_BITS 15 -#define INTER_REMAP_COEF_SCALE (1 << INTER_REMAP_COEF_BITS) - -__kernel void warpPerspective(__global const uchar * src, - int src_row_stride, int src_px_stride, int src_offset, int src_rows, int src_cols, - __global uchar * dst, - int dst_row_stride, int dst_offset, int dst_rows, int dst_cols, - __constant float * M) -{ - int dx = get_global_id(0); - int dy = get_global_id(1); - - if (dx < dst_cols && dy < dst_rows) - { - float X0 = M[0] * dx + M[1] * dy + M[2]; - float Y0 = M[3] * dx + M[4] * dy + M[5]; - float W = M[6] * dx + M[7] * dy + M[8]; - W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; - int X = rint(X0 * W), Y = rint(Y0 * W); - - int sx = convert_short_sat(X >> INTER_BITS); - int sy = convert_short_sat(Y >> INTER_BITS); - - short sx_clamp = clamp(sx, 0, src_cols - 1); - short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); - short sy_clamp = clamp(sy, 0, src_rows - 1); - short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); - int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); - int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); - - short ay = (short)(Y & (INTER_TAB_SIZE - 1)); - short ax = (short)(X & (INTER_TAB_SIZE - 1)); - float taby = 1.f/INTER_TAB_SIZE*ay; - float tabx = 1.f/INTER_TAB_SIZE*ax; - - int dst_index = mad24(dy, dst_row_stride, dst_offset + dx); - - int itab0 = convert_short_sat_rte( (1.0f-taby)*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab1 = convert_short_sat_rte( (1.0f-taby)*tabx * INTER_REMAP_COEF_SCALE ); - int itab2 = convert_short_sat_rte( taby*(1.0f-tabx) * INTER_REMAP_COEF_SCALE ); - int itab3 = convert_short_sat_rte( taby*tabx * INTER_REMAP_COEF_SCALE ); - - int val = v0 * itab0 + v1 * itab1 + v2 * itab2 + v3 * itab3; - - uchar pix = convert_uchar_sat((val + (1 << (INTER_REMAP_COEF_BITS-1))) >> INTER_REMAP_COEF_BITS); - dst[dst_index] = pix; - } -} diff --git a/selfdrive/modeld/transforms/transform.h b/selfdrive/modeld/transforms/transform.h deleted file mode 100644 index 771a7054b35..00000000000 --- a/selfdrive/modeld/transforms/transform.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#define CL_USE_DEPRECATED_OPENCL_1_2_APIS -#ifdef __APPLE__ -#include -#else -#include -#endif - -#include "common/mat.h" - -typedef struct { - cl_kernel krnl; - cl_mem m_y_cl, m_uv_cl; -} Transform; - -void transform_init(Transform* s, cl_context ctx, cl_device_id device_id); - -void transform_destroy(Transform* transform); - -void transform_queue(Transform* s, cl_command_queue q, - cl_mem yuv, int in_width, int in_height, int in_stride, int in_uv_offset, - cl_mem out_y, cl_mem out_u, cl_mem out_v, - int out_width, int out_height, - const mat3& projection); diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index d35474f3732..3f671f610d2 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -34,8 +34,8 @@ EXEC_TIMINGS = [ # model, instant max, average max - ("modelV2", 0.035, 0.025), - ("driverStateV2", 0.02, 0.015), + ("modelV2", 0.05, 0.028), + ("driverStateV2", 0.05, 0.015), ] def get_log_fn(test_route, ref="master"): diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 8af72e5f4e7..d168a7e8002 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -4,6 +4,7 @@ import copy import heapq import signal +import numpy as np from collections import Counter from dataclasses import dataclass, field from itertools import islice @@ -23,6 +24,7 @@ from openpilot.common.prefix import OpenpilotPrefix from openpilot.common.timeout import Timeout from openpilot.common.realtime import DT_CTRL +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.system.manager.process_config import managed_processes from openpilot.selfdrive.test.process_replay.vision_meta import meta_from_camera_state, available_streams from openpilot.selfdrive.test.process_replay.migration import migrate_all @@ -203,7 +205,8 @@ def _setup_vision_ipc(self, all_msgs: LogIterable, frs: dict[str, Any]): if meta.camera_state in self.cfg.vision_pubs: assert frs[meta.camera_state].pix_fmt == 'nv12' frame_size = (frs[meta.camera_state].w, frs[meta.camera_state].h) - vipc_server.create_buffers(meta.stream, 2, *frame_size) + stride, y_height, _, yuv_size = get_nv12_info(frame_size[0], frame_size[1]) + vipc_server.create_buffers_with_sizes(meta.stream, 2, frame_size[0], frame_size[1], yuv_size, stride, stride * y_height) vipc_server.start_listener() self.vipc_server = vipc_server @@ -300,7 +303,17 @@ def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] camera_meta = meta_from_camera_state(m.which()) assert frs is not None img = frs[m.which()].get(camera_state.frameId) - self.vipc_server.send(camera_meta.stream, img.flatten().tobytes(), + + h, w = frs[m.which()].h, frs[m.which()].w + stride, y_height, _, yuv_size = get_nv12_info(w, h) + uv_offset = stride * y_height + padded_img = np.zeros(((uv_offset //stride) + (h // 2), stride)) + padded_img[:h, :w] = img[:h * w].reshape((-1, w)) + padded_img[uv_offset // stride:uv_offset // stride + h // 2, :w] = img[h * w:].reshape((-1, w)) + img_bytes = np.zeros((yuv_size,), dtype=np.uint8) + img_bytes[:padded_img.size] = padded_img.flatten() + + self.vipc_server.send(camera_meta.stream, img_bytes.tobytes(), camera_state.frameId, camera_state.timestampSof, camera_state.timestampEof) self.msg_queue = [] diff --git a/system/camerad/SConscript b/system/camerad/SConscript index e288c6d8b02..c28330b32c4 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -1,6 +1,6 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') -libs = [common, 'OpenCL', messaging, visionipc] +libs = [common, messaging, visionipc] if arch != "Darwin": camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/spectra.cc', diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 88bca7f775b..329192b63ae 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -7,7 +7,7 @@ #include "system/camerad/cameras/spectra.h" -void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { +void CameraBuf::init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type) { vipc_server = v; stream_type = type; frame_buf_count = frame_cnt; @@ -21,9 +21,8 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, SpectraCamera * const int raw_frame_size = (sensor->frame_height + sensor->extra_height) * sensor->frame_stride; for (int i = 0; i < frame_buf_count; i++) { camera_bufs_raw[i].allocate(raw_frame_size); - camera_bufs_raw[i].init_cl(device_id, context); } - LOGD("allocated %d CL buffers", frame_buf_count); + LOGD("allocated %d buffers", frame_buf_count); } vipc_server->create_buffers_with_sizes(stream_type, VIPC_BUFFER_COUNT, out_img_width, out_img_height, cam->yuv_size, cam->stride, cam->uv_offset); diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index c26859cbc40..7f35e06a835 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -36,7 +36,7 @@ class CameraBuf { CameraBuf() = default; ~CameraBuf(); - void init(cl_device_id device_id, cl_context context, SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); + void init(SpectraCamera *cam, VisionIpcServer * v, int frame_cnt, VisionStreamType type); void sendFrameToVipc(); }; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index d741e13cf3b..6a7f599ab66 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -12,16 +12,8 @@ #include #include -#ifdef __TICI__ -#include "CL/cl_ext_qcom.h" -#else -#define CL_PRIORITY_HINT_HIGH_QCOM NULL -#define CL_CONTEXT_PRIORITY_HINT_QCOM NULL -#endif - #include "media/cam_sensor_cmn_header.h" -#include "common/clutil.h" #include "common/params.h" #include "common/swaglog.h" @@ -57,7 +49,7 @@ class CameraState { CameraState(SpectraMaster *master, const CameraConfig &config) : camera(master, config) {}; ~CameraState(); - void init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void init(VisionIpcServer *v); void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void set_exposure_rect(); @@ -68,8 +60,8 @@ class CameraState { } }; -void CameraState::init(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { - camera.camera_open(v, device_id, ctx); +void CameraState::init(VisionIpcServer *v) { + camera.camera_open(v); if (!camera.enabled) return; @@ -257,11 +249,7 @@ void CameraState::sendState() { void camerad_thread() { // TODO: centralize enabled handling - cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); - const cl_context_properties props[] = {CL_CONTEXT_PRIORITY_HINT_QCOM, CL_PRIORITY_HINT_HIGH_QCOM, 0}; - cl_context ctx = CL_CHECK_ERR(clCreateContext(props, 1, &device_id, NULL, NULL, &err)); - - VisionIpcServer v("camerad", device_id, ctx); + VisionIpcServer v("camerad"); // *** initial ISP init *** SpectraMaster m; @@ -271,7 +259,7 @@ void camerad_thread() { std::vector> cams; for (const auto &config : ALL_CAMERA_CONFIGS) { auto cam = std::make_unique(&m, config); - cam->init(&v, device_id, ctx); + cam->init(&v); cams.emplace_back(std::move(cam)); } diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 5c3e7a9d233..73e0a78da30 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -274,7 +274,7 @@ int SpectraCamera::clear_req_queue() { return ret; } -void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx) { +void SpectraCamera::camera_open(VisionIpcServer *v) { if (!openSensor()) { return; } @@ -296,7 +296,7 @@ void SpectraCamera::camera_open(VisionIpcServer *v, cl_device_id device_id, cl_c linkDevices(); LOGD("camera init %d", cc.camera_num); - buf.init(device_id, ctx, this, v, ife_buf_depth, cc.stream_type); + buf.init(this, v, ife_buf_depth, cc.stream_type); camera_map_bufs(); clearAndRequeue(1); } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 13cb13f98f6..a02b8a6cac7 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -113,7 +113,7 @@ class SpectraCamera { SpectraCamera(SpectraMaster *master, const CameraConfig &config); ~SpectraCamera(); - void camera_open(VisionIpcServer *v, cl_device_id device_id, cl_context ctx); + void camera_open(VisionIpcServer *v); bool handle_camera_event(const cam_req_mgr_message *event_data); void camera_close(); void camera_map_bufs(); diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index a92e4c8de8a..c4401c9583c 100644 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -32,7 +32,7 @@ def name(self): PROCS = [ Proc(['camerad'], 1.65, atol=0.4, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), - Proc(['modeld'], 1.24, atol=0.2, msgs=['modelV2']), + Proc(['modeld'], 1.5, atol=0.2, msgs=['modelV2']), Proc(['dmonitoringmodeld'], 0.65, atol=0.35, msgs=['driverStateV2']), Proc(['encoderd'], 0.23, msgs=[]), ] diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cf169f4dc61..cc8ef7c88f6 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,16 +2,13 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'avutil', - 'yuv', 'OpenCL', 'pthread', 'zstd'] + 'yuv', 'pthread', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] if arch == "Darwin": - # fix OpenCL - del libs[libs.index('OpenCL')] - env['FRAMEWORKS'] = ['OpenCL'] # exclude v4l del src[src.index('encoder/v4l_encoder.cc')] diff --git a/third_party/opencl/include/CL/cl.h b/third_party/opencl/include/CL/cl.h deleted file mode 100644 index 0086319f5fa..00000000000 --- a/third_party/opencl/include/CL/cl.h +++ /dev/null @@ -1,1452 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_H -#define __OPENCL_CL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ - -typedef struct _cl_platform_id * cl_platform_id; -typedef struct _cl_device_id * cl_device_id; -typedef struct _cl_context * cl_context; -typedef struct _cl_command_queue * cl_command_queue; -typedef struct _cl_mem * cl_mem; -typedef struct _cl_program * cl_program; -typedef struct _cl_kernel * cl_kernel; -typedef struct _cl_event * cl_event; -typedef struct _cl_sampler * cl_sampler; - -typedef cl_uint cl_bool; /* WARNING! Unlike cl_ types in cl_platform.h, cl_bool is not guaranteed to be the same size as the bool in kernels. */ -typedef cl_ulong cl_bitfield; -typedef cl_bitfield cl_device_type; -typedef cl_uint cl_platform_info; -typedef cl_uint cl_device_info; -typedef cl_bitfield cl_device_fp_config; -typedef cl_uint cl_device_mem_cache_type; -typedef cl_uint cl_device_local_mem_type; -typedef cl_bitfield cl_device_exec_capabilities; -typedef cl_bitfield cl_device_svm_capabilities; -typedef cl_bitfield cl_command_queue_properties; -typedef intptr_t cl_device_partition_property; -typedef cl_bitfield cl_device_affinity_domain; - -typedef intptr_t cl_context_properties; -typedef cl_uint cl_context_info; -typedef cl_bitfield cl_queue_properties; -typedef cl_uint cl_command_queue_info; -typedef cl_uint cl_channel_order; -typedef cl_uint cl_channel_type; -typedef cl_bitfield cl_mem_flags; -typedef cl_bitfield cl_svm_mem_flags; -typedef cl_uint cl_mem_object_type; -typedef cl_uint cl_mem_info; -typedef cl_bitfield cl_mem_migration_flags; -typedef cl_uint cl_image_info; -typedef cl_uint cl_buffer_create_type; -typedef cl_uint cl_addressing_mode; -typedef cl_uint cl_filter_mode; -typedef cl_uint cl_sampler_info; -typedef cl_bitfield cl_map_flags; -typedef intptr_t cl_pipe_properties; -typedef cl_uint cl_pipe_info; -typedef cl_uint cl_program_info; -typedef cl_uint cl_program_build_info; -typedef cl_uint cl_program_binary_type; -typedef cl_int cl_build_status; -typedef cl_uint cl_kernel_info; -typedef cl_uint cl_kernel_arg_info; -typedef cl_uint cl_kernel_arg_address_qualifier; -typedef cl_uint cl_kernel_arg_access_qualifier; -typedef cl_bitfield cl_kernel_arg_type_qualifier; -typedef cl_uint cl_kernel_work_group_info; -typedef cl_uint cl_kernel_sub_group_info; -typedef cl_uint cl_event_info; -typedef cl_uint cl_command_type; -typedef cl_uint cl_profiling_info; -typedef cl_bitfield cl_sampler_properties; -typedef cl_uint cl_kernel_exec_info; - -typedef struct _cl_image_format { - cl_channel_order image_channel_order; - cl_channel_type image_channel_data_type; -} cl_image_format; - -typedef struct _cl_image_desc { - cl_mem_object_type image_type; - size_t image_width; - size_t image_height; - size_t image_depth; - size_t image_array_size; - size_t image_row_pitch; - size_t image_slice_pitch; - cl_uint num_mip_levels; - cl_uint num_samples; -#ifdef __GNUC__ - __extension__ /* Prevents warnings about anonymous union in -pedantic builds */ -#endif - union { - cl_mem buffer; - cl_mem mem_object; - }; -} cl_image_desc; - -typedef struct _cl_buffer_region { - size_t origin; - size_t size; -} cl_buffer_region; - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_SUCCESS 0 -#define CL_DEVICE_NOT_FOUND -1 -#define CL_DEVICE_NOT_AVAILABLE -2 -#define CL_COMPILER_NOT_AVAILABLE -3 -#define CL_MEM_OBJECT_ALLOCATION_FAILURE -4 -#define CL_OUT_OF_RESOURCES -5 -#define CL_OUT_OF_HOST_MEMORY -6 -#define CL_PROFILING_INFO_NOT_AVAILABLE -7 -#define CL_MEM_COPY_OVERLAP -8 -#define CL_IMAGE_FORMAT_MISMATCH -9 -#define CL_IMAGE_FORMAT_NOT_SUPPORTED -10 -#define CL_BUILD_PROGRAM_FAILURE -11 -#define CL_MAP_FAILURE -12 -#define CL_MISALIGNED_SUB_BUFFER_OFFSET -13 -#define CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST -14 -#define CL_COMPILE_PROGRAM_FAILURE -15 -#define CL_LINKER_NOT_AVAILABLE -16 -#define CL_LINK_PROGRAM_FAILURE -17 -#define CL_DEVICE_PARTITION_FAILED -18 -#define CL_KERNEL_ARG_INFO_NOT_AVAILABLE -19 - -#define CL_INVALID_VALUE -30 -#define CL_INVALID_DEVICE_TYPE -31 -#define CL_INVALID_PLATFORM -32 -#define CL_INVALID_DEVICE -33 -#define CL_INVALID_CONTEXT -34 -#define CL_INVALID_QUEUE_PROPERTIES -35 -#define CL_INVALID_COMMAND_QUEUE -36 -#define CL_INVALID_HOST_PTR -37 -#define CL_INVALID_MEM_OBJECT -38 -#define CL_INVALID_IMAGE_FORMAT_DESCRIPTOR -39 -#define CL_INVALID_IMAGE_SIZE -40 -#define CL_INVALID_SAMPLER -41 -#define CL_INVALID_BINARY -42 -#define CL_INVALID_BUILD_OPTIONS -43 -#define CL_INVALID_PROGRAM -44 -#define CL_INVALID_PROGRAM_EXECUTABLE -45 -#define CL_INVALID_KERNEL_NAME -46 -#define CL_INVALID_KERNEL_DEFINITION -47 -#define CL_INVALID_KERNEL -48 -#define CL_INVALID_ARG_INDEX -49 -#define CL_INVALID_ARG_VALUE -50 -#define CL_INVALID_ARG_SIZE -51 -#define CL_INVALID_KERNEL_ARGS -52 -#define CL_INVALID_WORK_DIMENSION -53 -#define CL_INVALID_WORK_GROUP_SIZE -54 -#define CL_INVALID_WORK_ITEM_SIZE -55 -#define CL_INVALID_GLOBAL_OFFSET -56 -#define CL_INVALID_EVENT_WAIT_LIST -57 -#define CL_INVALID_EVENT -58 -#define CL_INVALID_OPERATION -59 -#define CL_INVALID_GL_OBJECT -60 -#define CL_INVALID_BUFFER_SIZE -61 -#define CL_INVALID_MIP_LEVEL -62 -#define CL_INVALID_GLOBAL_WORK_SIZE -63 -#define CL_INVALID_PROPERTY -64 -#define CL_INVALID_IMAGE_DESCRIPTOR -65 -#define CL_INVALID_COMPILER_OPTIONS -66 -#define CL_INVALID_LINKER_OPTIONS -67 -#define CL_INVALID_DEVICE_PARTITION_COUNT -68 -#define CL_INVALID_PIPE_SIZE -69 -#define CL_INVALID_DEVICE_QUEUE -70 - -/* OpenCL Version */ -#define CL_VERSION_1_0 1 -#define CL_VERSION_1_1 1 -#define CL_VERSION_1_2 1 -#define CL_VERSION_2_0 1 -#define CL_VERSION_2_1 1 - -/* cl_bool */ -#define CL_FALSE 0 -#define CL_TRUE 1 -#define CL_BLOCKING CL_TRUE -#define CL_NON_BLOCKING CL_FALSE - -/* cl_platform_info */ -#define CL_PLATFORM_PROFILE 0x0900 -#define CL_PLATFORM_VERSION 0x0901 -#define CL_PLATFORM_NAME 0x0902 -#define CL_PLATFORM_VENDOR 0x0903 -#define CL_PLATFORM_EXTENSIONS 0x0904 -#define CL_PLATFORM_HOST_TIMER_RESOLUTION 0x0905 - -/* cl_device_type - bitfield */ -#define CL_DEVICE_TYPE_DEFAULT (1 << 0) -#define CL_DEVICE_TYPE_CPU (1 << 1) -#define CL_DEVICE_TYPE_GPU (1 << 2) -#define CL_DEVICE_TYPE_ACCELERATOR (1 << 3) -#define CL_DEVICE_TYPE_CUSTOM (1 << 4) -#define CL_DEVICE_TYPE_ALL 0xFFFFFFFF - -/* cl_device_info */ -#define CL_DEVICE_TYPE 0x1000 -#define CL_DEVICE_VENDOR_ID 0x1001 -#define CL_DEVICE_MAX_COMPUTE_UNITS 0x1002 -#define CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS 0x1003 -#define CL_DEVICE_MAX_WORK_GROUP_SIZE 0x1004 -#define CL_DEVICE_MAX_WORK_ITEM_SIZES 0x1005 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR 0x1006 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT 0x1007 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT 0x1008 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG 0x1009 -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT 0x100A -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE 0x100B -#define CL_DEVICE_MAX_CLOCK_FREQUENCY 0x100C -#define CL_DEVICE_ADDRESS_BITS 0x100D -#define CL_DEVICE_MAX_READ_IMAGE_ARGS 0x100E -#define CL_DEVICE_MAX_WRITE_IMAGE_ARGS 0x100F -#define CL_DEVICE_MAX_MEM_ALLOC_SIZE 0x1010 -#define CL_DEVICE_IMAGE2D_MAX_WIDTH 0x1011 -#define CL_DEVICE_IMAGE2D_MAX_HEIGHT 0x1012 -#define CL_DEVICE_IMAGE3D_MAX_WIDTH 0x1013 -#define CL_DEVICE_IMAGE3D_MAX_HEIGHT 0x1014 -#define CL_DEVICE_IMAGE3D_MAX_DEPTH 0x1015 -#define CL_DEVICE_IMAGE_SUPPORT 0x1016 -#define CL_DEVICE_MAX_PARAMETER_SIZE 0x1017 -#define CL_DEVICE_MAX_SAMPLERS 0x1018 -#define CL_DEVICE_MEM_BASE_ADDR_ALIGN 0x1019 -#define CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE 0x101A -#define CL_DEVICE_SINGLE_FP_CONFIG 0x101B -#define CL_DEVICE_GLOBAL_MEM_CACHE_TYPE 0x101C -#define CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE 0x101D -#define CL_DEVICE_GLOBAL_MEM_CACHE_SIZE 0x101E -#define CL_DEVICE_GLOBAL_MEM_SIZE 0x101F -#define CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE 0x1020 -#define CL_DEVICE_MAX_CONSTANT_ARGS 0x1021 -#define CL_DEVICE_LOCAL_MEM_TYPE 0x1022 -#define CL_DEVICE_LOCAL_MEM_SIZE 0x1023 -#define CL_DEVICE_ERROR_CORRECTION_SUPPORT 0x1024 -#define CL_DEVICE_PROFILING_TIMER_RESOLUTION 0x1025 -#define CL_DEVICE_ENDIAN_LITTLE 0x1026 -#define CL_DEVICE_AVAILABLE 0x1027 -#define CL_DEVICE_COMPILER_AVAILABLE 0x1028 -#define CL_DEVICE_EXECUTION_CAPABILITIES 0x1029 -#define CL_DEVICE_QUEUE_PROPERTIES 0x102A /* deprecated */ -#define CL_DEVICE_QUEUE_ON_HOST_PROPERTIES 0x102A -#define CL_DEVICE_NAME 0x102B -#define CL_DEVICE_VENDOR 0x102C -#define CL_DRIVER_VERSION 0x102D -#define CL_DEVICE_PROFILE 0x102E -#define CL_DEVICE_VERSION 0x102F -#define CL_DEVICE_EXTENSIONS 0x1030 -#define CL_DEVICE_PLATFORM 0x1031 -#define CL_DEVICE_DOUBLE_FP_CONFIG 0x1032 -/* 0x1033 reserved for CL_DEVICE_HALF_FP_CONFIG */ -#define CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF 0x1034 -#define CL_DEVICE_HOST_UNIFIED_MEMORY 0x1035 /* deprecated */ -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR 0x1036 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT 0x1037 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_INT 0x1038 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG 0x1039 -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT 0x103A -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE 0x103B -#define CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF 0x103C -#define CL_DEVICE_OPENCL_C_VERSION 0x103D -#define CL_DEVICE_LINKER_AVAILABLE 0x103E -#define CL_DEVICE_BUILT_IN_KERNELS 0x103F -#define CL_DEVICE_IMAGE_MAX_BUFFER_SIZE 0x1040 -#define CL_DEVICE_IMAGE_MAX_ARRAY_SIZE 0x1041 -#define CL_DEVICE_PARENT_DEVICE 0x1042 -#define CL_DEVICE_PARTITION_MAX_SUB_DEVICES 0x1043 -#define CL_DEVICE_PARTITION_PROPERTIES 0x1044 -#define CL_DEVICE_PARTITION_AFFINITY_DOMAIN 0x1045 -#define CL_DEVICE_PARTITION_TYPE 0x1046 -#define CL_DEVICE_REFERENCE_COUNT 0x1047 -#define CL_DEVICE_PREFERRED_INTEROP_USER_SYNC 0x1048 -#define CL_DEVICE_PRINTF_BUFFER_SIZE 0x1049 -#define CL_DEVICE_IMAGE_PITCH_ALIGNMENT 0x104A -#define CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT 0x104B -#define CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS 0x104C -#define CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE 0x104D -#define CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES 0x104E -#define CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE 0x104F -#define CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE 0x1050 -#define CL_DEVICE_MAX_ON_DEVICE_QUEUES 0x1051 -#define CL_DEVICE_MAX_ON_DEVICE_EVENTS 0x1052 -#define CL_DEVICE_SVM_CAPABILITIES 0x1053 -#define CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE 0x1054 -#define CL_DEVICE_MAX_PIPE_ARGS 0x1055 -#define CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS 0x1056 -#define CL_DEVICE_PIPE_MAX_PACKET_SIZE 0x1057 -#define CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT 0x1058 -#define CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT 0x1059 -#define CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT 0x105A -#define CL_DEVICE_IL_VERSION 0x105B -#define CL_DEVICE_MAX_NUM_SUB_GROUPS 0x105C -#define CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS 0x105D - -/* cl_device_fp_config - bitfield */ -#define CL_FP_DENORM (1 << 0) -#define CL_FP_INF_NAN (1 << 1) -#define CL_FP_ROUND_TO_NEAREST (1 << 2) -#define CL_FP_ROUND_TO_ZERO (1 << 3) -#define CL_FP_ROUND_TO_INF (1 << 4) -#define CL_FP_FMA (1 << 5) -#define CL_FP_SOFT_FLOAT (1 << 6) -#define CL_FP_CORRECTLY_ROUNDED_DIVIDE_SQRT (1 << 7) - -/* cl_device_mem_cache_type */ -#define CL_NONE 0x0 -#define CL_READ_ONLY_CACHE 0x1 -#define CL_READ_WRITE_CACHE 0x2 - -/* cl_device_local_mem_type */ -#define CL_LOCAL 0x1 -#define CL_GLOBAL 0x2 - -/* cl_device_exec_capabilities - bitfield */ -#define CL_EXEC_KERNEL (1 << 0) -#define CL_EXEC_NATIVE_KERNEL (1 << 1) - -/* cl_command_queue_properties - bitfield */ -#define CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE (1 << 0) -#define CL_QUEUE_PROFILING_ENABLE (1 << 1) -#define CL_QUEUE_ON_DEVICE (1 << 2) -#define CL_QUEUE_ON_DEVICE_DEFAULT (1 << 3) - -/* cl_context_info */ -#define CL_CONTEXT_REFERENCE_COUNT 0x1080 -#define CL_CONTEXT_DEVICES 0x1081 -#define CL_CONTEXT_PROPERTIES 0x1082 -#define CL_CONTEXT_NUM_DEVICES 0x1083 - -/* cl_context_properties */ -#define CL_CONTEXT_PLATFORM 0x1084 -#define CL_CONTEXT_INTEROP_USER_SYNC 0x1085 - -/* cl_device_partition_property */ -#define CL_DEVICE_PARTITION_EQUALLY 0x1086 -#define CL_DEVICE_PARTITION_BY_COUNTS 0x1087 -#define CL_DEVICE_PARTITION_BY_COUNTS_LIST_END 0x0 -#define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN 0x1088 - -/* cl_device_affinity_domain */ -#define CL_DEVICE_AFFINITY_DOMAIN_NUMA (1 << 0) -#define CL_DEVICE_AFFINITY_DOMAIN_L4_CACHE (1 << 1) -#define CL_DEVICE_AFFINITY_DOMAIN_L3_CACHE (1 << 2) -#define CL_DEVICE_AFFINITY_DOMAIN_L2_CACHE (1 << 3) -#define CL_DEVICE_AFFINITY_DOMAIN_L1_CACHE (1 << 4) -#define CL_DEVICE_AFFINITY_DOMAIN_NEXT_PARTITIONABLE (1 << 5) - -/* cl_device_svm_capabilities */ -#define CL_DEVICE_SVM_COARSE_GRAIN_BUFFER (1 << 0) -#define CL_DEVICE_SVM_FINE_GRAIN_BUFFER (1 << 1) -#define CL_DEVICE_SVM_FINE_GRAIN_SYSTEM (1 << 2) -#define CL_DEVICE_SVM_ATOMICS (1 << 3) - -/* cl_command_queue_info */ -#define CL_QUEUE_CONTEXT 0x1090 -#define CL_QUEUE_DEVICE 0x1091 -#define CL_QUEUE_REFERENCE_COUNT 0x1092 -#define CL_QUEUE_PROPERTIES 0x1093 -#define CL_QUEUE_SIZE 0x1094 -#define CL_QUEUE_DEVICE_DEFAULT 0x1095 - -/* cl_mem_flags and cl_svm_mem_flags - bitfield */ -#define CL_MEM_READ_WRITE (1 << 0) -#define CL_MEM_WRITE_ONLY (1 << 1) -#define CL_MEM_READ_ONLY (1 << 2) -#define CL_MEM_USE_HOST_PTR (1 << 3) -#define CL_MEM_ALLOC_HOST_PTR (1 << 4) -#define CL_MEM_COPY_HOST_PTR (1 << 5) -/* reserved (1 << 6) */ -#define CL_MEM_HOST_WRITE_ONLY (1 << 7) -#define CL_MEM_HOST_READ_ONLY (1 << 8) -#define CL_MEM_HOST_NO_ACCESS (1 << 9) -#define CL_MEM_SVM_FINE_GRAIN_BUFFER (1 << 10) /* used by cl_svm_mem_flags only */ -#define CL_MEM_SVM_ATOMICS (1 << 11) /* used by cl_svm_mem_flags only */ -#define CL_MEM_KERNEL_READ_AND_WRITE (1 << 12) - -/* cl_mem_migration_flags - bitfield */ -#define CL_MIGRATE_MEM_OBJECT_HOST (1 << 0) -#define CL_MIGRATE_MEM_OBJECT_CONTENT_UNDEFINED (1 << 1) - -/* cl_channel_order */ -#define CL_R 0x10B0 -#define CL_A 0x10B1 -#define CL_RG 0x10B2 -#define CL_RA 0x10B3 -#define CL_RGB 0x10B4 -#define CL_RGBA 0x10B5 -#define CL_BGRA 0x10B6 -#define CL_ARGB 0x10B7 -#define CL_INTENSITY 0x10B8 -#define CL_LUMINANCE 0x10B9 -#define CL_Rx 0x10BA -#define CL_RGx 0x10BB -#define CL_RGBx 0x10BC -#define CL_DEPTH 0x10BD -#define CL_DEPTH_STENCIL 0x10BE -#define CL_sRGB 0x10BF -#define CL_sRGBx 0x10C0 -#define CL_sRGBA 0x10C1 -#define CL_sBGRA 0x10C2 -#define CL_ABGR 0x10C3 - -/* cl_channel_type */ -#define CL_SNORM_INT8 0x10D0 -#define CL_SNORM_INT16 0x10D1 -#define CL_UNORM_INT8 0x10D2 -#define CL_UNORM_INT16 0x10D3 -#define CL_UNORM_SHORT_565 0x10D4 -#define CL_UNORM_SHORT_555 0x10D5 -#define CL_UNORM_INT_101010 0x10D6 -#define CL_SIGNED_INT8 0x10D7 -#define CL_SIGNED_INT16 0x10D8 -#define CL_SIGNED_INT32 0x10D9 -#define CL_UNSIGNED_INT8 0x10DA -#define CL_UNSIGNED_INT16 0x10DB -#define CL_UNSIGNED_INT32 0x10DC -#define CL_HALF_FLOAT 0x10DD -#define CL_FLOAT 0x10DE -#define CL_UNORM_INT24 0x10DF -#define CL_UNORM_INT_101010_2 0x10E0 - -/* cl_mem_object_type */ -#define CL_MEM_OBJECT_BUFFER 0x10F0 -#define CL_MEM_OBJECT_IMAGE2D 0x10F1 -#define CL_MEM_OBJECT_IMAGE3D 0x10F2 -#define CL_MEM_OBJECT_IMAGE2D_ARRAY 0x10F3 -#define CL_MEM_OBJECT_IMAGE1D 0x10F4 -#define CL_MEM_OBJECT_IMAGE1D_ARRAY 0x10F5 -#define CL_MEM_OBJECT_IMAGE1D_BUFFER 0x10F6 -#define CL_MEM_OBJECT_PIPE 0x10F7 - -/* cl_mem_info */ -#define CL_MEM_TYPE 0x1100 -#define CL_MEM_FLAGS 0x1101 -#define CL_MEM_SIZE 0x1102 -#define CL_MEM_HOST_PTR 0x1103 -#define CL_MEM_MAP_COUNT 0x1104 -#define CL_MEM_REFERENCE_COUNT 0x1105 -#define CL_MEM_CONTEXT 0x1106 -#define CL_MEM_ASSOCIATED_MEMOBJECT 0x1107 -#define CL_MEM_OFFSET 0x1108 -#define CL_MEM_USES_SVM_POINTER 0x1109 - -/* cl_image_info */ -#define CL_IMAGE_FORMAT 0x1110 -#define CL_IMAGE_ELEMENT_SIZE 0x1111 -#define CL_IMAGE_ROW_PITCH 0x1112 -#define CL_IMAGE_SLICE_PITCH 0x1113 -#define CL_IMAGE_WIDTH 0x1114 -#define CL_IMAGE_HEIGHT 0x1115 -#define CL_IMAGE_DEPTH 0x1116 -#define CL_IMAGE_ARRAY_SIZE 0x1117 -#define CL_IMAGE_BUFFER 0x1118 -#define CL_IMAGE_NUM_MIP_LEVELS 0x1119 -#define CL_IMAGE_NUM_SAMPLES 0x111A - -/* cl_pipe_info */ -#define CL_PIPE_PACKET_SIZE 0x1120 -#define CL_PIPE_MAX_PACKETS 0x1121 - -/* cl_addressing_mode */ -#define CL_ADDRESS_NONE 0x1130 -#define CL_ADDRESS_CLAMP_TO_EDGE 0x1131 -#define CL_ADDRESS_CLAMP 0x1132 -#define CL_ADDRESS_REPEAT 0x1133 -#define CL_ADDRESS_MIRRORED_REPEAT 0x1134 - -/* cl_filter_mode */ -#define CL_FILTER_NEAREST 0x1140 -#define CL_FILTER_LINEAR 0x1141 - -/* cl_sampler_info */ -#define CL_SAMPLER_REFERENCE_COUNT 0x1150 -#define CL_SAMPLER_CONTEXT 0x1151 -#define CL_SAMPLER_NORMALIZED_COORDS 0x1152 -#define CL_SAMPLER_ADDRESSING_MODE 0x1153 -#define CL_SAMPLER_FILTER_MODE 0x1154 -#define CL_SAMPLER_MIP_FILTER_MODE 0x1155 -#define CL_SAMPLER_LOD_MIN 0x1156 -#define CL_SAMPLER_LOD_MAX 0x1157 - -/* cl_map_flags - bitfield */ -#define CL_MAP_READ (1 << 0) -#define CL_MAP_WRITE (1 << 1) -#define CL_MAP_WRITE_INVALIDATE_REGION (1 << 2) - -/* cl_program_info */ -#define CL_PROGRAM_REFERENCE_COUNT 0x1160 -#define CL_PROGRAM_CONTEXT 0x1161 -#define CL_PROGRAM_NUM_DEVICES 0x1162 -#define CL_PROGRAM_DEVICES 0x1163 -#define CL_PROGRAM_SOURCE 0x1164 -#define CL_PROGRAM_BINARY_SIZES 0x1165 -#define CL_PROGRAM_BINARIES 0x1166 -#define CL_PROGRAM_NUM_KERNELS 0x1167 -#define CL_PROGRAM_KERNEL_NAMES 0x1168 -#define CL_PROGRAM_IL 0x1169 - -/* cl_program_build_info */ -#define CL_PROGRAM_BUILD_STATUS 0x1181 -#define CL_PROGRAM_BUILD_OPTIONS 0x1182 -#define CL_PROGRAM_BUILD_LOG 0x1183 -#define CL_PROGRAM_BINARY_TYPE 0x1184 -#define CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE 0x1185 - -/* cl_program_binary_type */ -#define CL_PROGRAM_BINARY_TYPE_NONE 0x0 -#define CL_PROGRAM_BINARY_TYPE_COMPILED_OBJECT 0x1 -#define CL_PROGRAM_BINARY_TYPE_LIBRARY 0x2 -#define CL_PROGRAM_BINARY_TYPE_EXECUTABLE 0x4 - -/* cl_build_status */ -#define CL_BUILD_SUCCESS 0 -#define CL_BUILD_NONE -1 -#define CL_BUILD_ERROR -2 -#define CL_BUILD_IN_PROGRESS -3 - -/* cl_kernel_info */ -#define CL_KERNEL_FUNCTION_NAME 0x1190 -#define CL_KERNEL_NUM_ARGS 0x1191 -#define CL_KERNEL_REFERENCE_COUNT 0x1192 -#define CL_KERNEL_CONTEXT 0x1193 -#define CL_KERNEL_PROGRAM 0x1194 -#define CL_KERNEL_ATTRIBUTES 0x1195 -#define CL_KERNEL_MAX_NUM_SUB_GROUPS 0x11B9 -#define CL_KERNEL_COMPILE_NUM_SUB_GROUPS 0x11BA - -/* cl_kernel_arg_info */ -#define CL_KERNEL_ARG_ADDRESS_QUALIFIER 0x1196 -#define CL_KERNEL_ARG_ACCESS_QUALIFIER 0x1197 -#define CL_KERNEL_ARG_TYPE_NAME 0x1198 -#define CL_KERNEL_ARG_TYPE_QUALIFIER 0x1199 -#define CL_KERNEL_ARG_NAME 0x119A - -/* cl_kernel_arg_address_qualifier */ -#define CL_KERNEL_ARG_ADDRESS_GLOBAL 0x119B -#define CL_KERNEL_ARG_ADDRESS_LOCAL 0x119C -#define CL_KERNEL_ARG_ADDRESS_CONSTANT 0x119D -#define CL_KERNEL_ARG_ADDRESS_PRIVATE 0x119E - -/* cl_kernel_arg_access_qualifier */ -#define CL_KERNEL_ARG_ACCESS_READ_ONLY 0x11A0 -#define CL_KERNEL_ARG_ACCESS_WRITE_ONLY 0x11A1 -#define CL_KERNEL_ARG_ACCESS_READ_WRITE 0x11A2 -#define CL_KERNEL_ARG_ACCESS_NONE 0x11A3 - -/* cl_kernel_arg_type_qualifer */ -#define CL_KERNEL_ARG_TYPE_NONE 0 -#define CL_KERNEL_ARG_TYPE_CONST (1 << 0) -#define CL_KERNEL_ARG_TYPE_RESTRICT (1 << 1) -#define CL_KERNEL_ARG_TYPE_VOLATILE (1 << 2) -#define CL_KERNEL_ARG_TYPE_PIPE (1 << 3) - -/* cl_kernel_work_group_info */ -#define CL_KERNEL_WORK_GROUP_SIZE 0x11B0 -#define CL_KERNEL_COMPILE_WORK_GROUP_SIZE 0x11B1 -#define CL_KERNEL_LOCAL_MEM_SIZE 0x11B2 -#define CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE 0x11B3 -#define CL_KERNEL_PRIVATE_MEM_SIZE 0x11B4 -#define CL_KERNEL_GLOBAL_WORK_SIZE 0x11B5 - -/* cl_kernel_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE 0x2034 -#define CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT 0x11B8 - -/* cl_kernel_exec_info */ -#define CL_KERNEL_EXEC_INFO_SVM_PTRS 0x11B6 -#define CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM 0x11B7 - -/* cl_event_info */ -#define CL_EVENT_COMMAND_QUEUE 0x11D0 -#define CL_EVENT_COMMAND_TYPE 0x11D1 -#define CL_EVENT_REFERENCE_COUNT 0x11D2 -#define CL_EVENT_COMMAND_EXECUTION_STATUS 0x11D3 -#define CL_EVENT_CONTEXT 0x11D4 - -/* cl_command_type */ -#define CL_COMMAND_NDRANGE_KERNEL 0x11F0 -#define CL_COMMAND_TASK 0x11F1 -#define CL_COMMAND_NATIVE_KERNEL 0x11F2 -#define CL_COMMAND_READ_BUFFER 0x11F3 -#define CL_COMMAND_WRITE_BUFFER 0x11F4 -#define CL_COMMAND_COPY_BUFFER 0x11F5 -#define CL_COMMAND_READ_IMAGE 0x11F6 -#define CL_COMMAND_WRITE_IMAGE 0x11F7 -#define CL_COMMAND_COPY_IMAGE 0x11F8 -#define CL_COMMAND_COPY_IMAGE_TO_BUFFER 0x11F9 -#define CL_COMMAND_COPY_BUFFER_TO_IMAGE 0x11FA -#define CL_COMMAND_MAP_BUFFER 0x11FB -#define CL_COMMAND_MAP_IMAGE 0x11FC -#define CL_COMMAND_UNMAP_MEM_OBJECT 0x11FD -#define CL_COMMAND_MARKER 0x11FE -#define CL_COMMAND_ACQUIRE_GL_OBJECTS 0x11FF -#define CL_COMMAND_RELEASE_GL_OBJECTS 0x1200 -#define CL_COMMAND_READ_BUFFER_RECT 0x1201 -#define CL_COMMAND_WRITE_BUFFER_RECT 0x1202 -#define CL_COMMAND_COPY_BUFFER_RECT 0x1203 -#define CL_COMMAND_USER 0x1204 -#define CL_COMMAND_BARRIER 0x1205 -#define CL_COMMAND_MIGRATE_MEM_OBJECTS 0x1206 -#define CL_COMMAND_FILL_BUFFER 0x1207 -#define CL_COMMAND_FILL_IMAGE 0x1208 -#define CL_COMMAND_SVM_FREE 0x1209 -#define CL_COMMAND_SVM_MEMCPY 0x120A -#define CL_COMMAND_SVM_MEMFILL 0x120B -#define CL_COMMAND_SVM_MAP 0x120C -#define CL_COMMAND_SVM_UNMAP 0x120D - -/* command execution status */ -#define CL_COMPLETE 0x0 -#define CL_RUNNING 0x1 -#define CL_SUBMITTED 0x2 -#define CL_QUEUED 0x3 - -/* cl_buffer_create_type */ -#define CL_BUFFER_CREATE_TYPE_REGION 0x1220 - -/* cl_profiling_info */ -#define CL_PROFILING_COMMAND_QUEUED 0x1280 -#define CL_PROFILING_COMMAND_SUBMIT 0x1281 -#define CL_PROFILING_COMMAND_START 0x1282 -#define CL_PROFILING_COMMAND_END 0x1283 -#define CL_PROFILING_COMMAND_COMPLETE 0x1284 - -/********************************************************************************************************/ - -/* Platform API */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformIDs(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPlatformInfo(cl_platform_id /* platform */, - cl_platform_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Device APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceIDs(cl_platform_id /* platform */, - cl_device_type /* device_type */, - cl_uint /* num_entries */, - cl_device_id * /* devices */, - cl_uint * /* num_devices */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceInfo(cl_device_id /* device */, - cl_device_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateSubDevices(cl_device_id /* in_device */, - const cl_device_partition_property * /* properties */, - cl_uint /* num_devices */, - cl_device_id * /* out_devices */, - cl_uint * /* num_devices_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseDevice(cl_device_id /* device */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetDefaultDeviceCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceAndHostTimer(cl_device_id /* device */, - cl_ulong* /* device_timestamp */, - cl_ulong* /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetHostTimer(cl_device_id /* device */, - cl_ulong * /* host_timestamp */) CL_API_SUFFIX__VERSION_2_1; - - -/* Context APIs */ -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContext(const cl_context_properties * /* properties */, - cl_uint /* num_devices */, - const cl_device_id * /* devices */, - void (CL_CALLBACK * /* pfn_notify */)(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_context CL_API_CALL -clCreateContextFromType(const cl_context_properties * /* properties */, - cl_device_type /* device_type */, - void (CL_CALLBACK * /* pfn_notify*/ )(const char *, const void *, size_t, void *), - void * /* user_data */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseContext(cl_context /* context */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetContextInfo(cl_context /* context */, - cl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Command Queue APIs */ -extern CL_API_ENTRY cl_command_queue CL_API_CALL -clCreateCommandQueueWithProperties(cl_context /* context */, - cl_device_id /* device */, - const cl_queue_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseCommandQueue(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetCommandQueueInfo(cl_command_queue /* command_queue */, - cl_command_queue_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Memory Object APIs */ -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - size_t /* size */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateSubBuffer(cl_mem /* buffer */, - cl_mem_flags /* flags */, - cl_buffer_create_type /* buffer_create_type */, - const void * /* buffer_create_info */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateImage(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - const cl_image_desc * /* image_desc */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreatePipe(cl_context /* context */, - cl_mem_flags /* flags */, - cl_uint /* pipe_packet_size */, - cl_uint /* pipe_max_packets */, - const cl_pipe_properties * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseMemObject(cl_mem /* memobj */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSupportedImageFormats(cl_context /* context */, - cl_mem_flags /* flags */, - cl_mem_object_type /* image_type */, - cl_uint /* num_entries */, - cl_image_format * /* image_formats */, - cl_uint * /* num_image_formats */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetMemObjectInfo(cl_mem /* memobj */, - cl_mem_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetImageInfo(cl_mem /* image */, - cl_image_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetPipeInfo(cl_mem /* pipe */, - cl_pipe_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_2_0; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetMemObjectDestructorCallback(cl_mem /* memobj */, - void (CL_CALLBACK * /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_API_SUFFIX__VERSION_1_1; - -/* SVM Allocation APIs */ -extern CL_API_ENTRY void * CL_API_CALL -clSVMAlloc(cl_context /* context */, - cl_svm_mem_flags /* flags */, - size_t /* size */, - cl_uint /* alignment */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY void CL_API_CALL -clSVMFree(cl_context /* context */, - void * /* svm_pointer */) CL_API_SUFFIX__VERSION_2_0; - -/* Sampler APIs */ -extern CL_API_ENTRY cl_sampler CL_API_CALL -clCreateSamplerWithProperties(cl_context /* context */, - const cl_sampler_properties * /* normalized_coords */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseSampler(cl_sampler /* sampler */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetSamplerInfo(cl_sampler /* sampler */, - cl_sampler_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Program Object APIs */ -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithSource(cl_context /* context */, - cl_uint /* count */, - const char ** /* strings */, - const size_t * /* lengths */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBinary(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const size_t * /* lengths */, - const unsigned char ** /* binaries */, - cl_int * /* binary_status */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithBuiltInKernels(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* kernel_names */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clCreateProgramWithIL(cl_context /* context */, - const void* /* il */, - size_t /* length */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseProgram(cl_program /* program */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clBuildProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCompileProgram(cl_program /* program */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_headers */, - const cl_program * /* input_headers */, - const char ** /* header_include_names */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_program CL_API_CALL -clLinkProgram(cl_context /* context */, - cl_uint /* num_devices */, - const cl_device_id * /* device_list */, - const char * /* options */, - cl_uint /* num_input_programs */, - const cl_program * /* input_programs */, - void (CL_CALLBACK * /* pfn_notify */)(cl_program /* program */, void * /* user_data */), - void * /* user_data */, - cl_int * /* errcode_ret */ ) CL_API_SUFFIX__VERSION_1_2; - - -extern CL_API_ENTRY cl_int CL_API_CALL -clUnloadPlatformCompiler(cl_platform_id /* platform */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramInfo(cl_program /* program */, - cl_program_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetProgramBuildInfo(cl_program /* program */, - cl_device_id /* device */, - cl_program_build_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Kernel Object APIs */ -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCreateKernel(cl_program /* program */, - const char * /* kernel_name */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clCreateKernelsInProgram(cl_program /* program */, - cl_uint /* num_kernels */, - cl_kernel * /* kernels */, - cl_uint * /* num_kernels_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_kernel CL_API_CALL -clCloneKernel(cl_kernel /* source_kernel */, - cl_int* /* errcode_ret */) CL_API_SUFFIX__VERSION_2_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseKernel(cl_kernel /* kernel */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArg(cl_kernel /* kernel */, - cl_uint /* arg_index */, - size_t /* arg_size */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelArgSVMPointer(cl_kernel /* kernel */, - cl_uint /* arg_index */, - const void * /* arg_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetKernelExecInfo(cl_kernel /* kernel */, - cl_kernel_exec_info /* param_name */, - size_t /* param_value_size */, - const void * /* param_value */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelInfo(cl_kernel /* kernel */, - cl_kernel_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelArgInfo(cl_kernel /* kernel */, - cl_uint /* arg_indx */, - cl_kernel_arg_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelWorkGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_work_group_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfo(cl_kernel /* kernel */, - cl_device_id /* device */, - cl_kernel_sub_group_info /* param_name */, - size_t /* input_value_size */, - const void* /*input_value */, - size_t /* param_value_size */, - void* /* param_value */, - size_t* /* param_value_size_ret */ ) CL_API_SUFFIX__VERSION_2_1; - - -/* Event Object APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clWaitForEvents(cl_uint /* num_events */, - const cl_event * /* event_list */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventInfo(cl_event /* event */, - cl_event_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateUserEvent(cl_context /* context */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clRetainEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clReleaseEvent(cl_event /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetUserEventStatus(cl_event /* event */, - cl_int /* execution_status */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetEventCallback( cl_event /* event */, - cl_int /* command_exec_callback_type */, - void (CL_CALLBACK * /* pfn_notify */)(cl_event, cl_int, void *), - void * /* user_data */) CL_API_SUFFIX__VERSION_1_1; - -/* Profiling APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clGetEventProfilingInfo(cl_event /* event */, - cl_profiling_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -/* Flush and Finish APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clFlush(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clFinish(cl_command_queue /* command_queue */) CL_API_SUFFIX__VERSION_1_0; - -/* Enqueued Commands APIs */ -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - size_t /* offset */, - size_t /* size */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_read */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - size_t /* offset */, - size_t /* size */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteBufferRect(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_write */, - const size_t * /* buffer_offset */, - const size_t * /* host_offset */, - const size_t * /* region */, - size_t /* buffer_row_pitch */, - size_t /* buffer_slice_pitch */, - size_t /* host_row_pitch */, - size_t /* host_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - size_t /* src_offset */, - size_t /* dst_offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferRect(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin */, - const size_t * /* dst_origin */, - const size_t * /* region */, - size_t /* src_row_pitch */, - size_t /* src_slice_pitch */, - size_t /* dst_row_pitch */, - size_t /* dst_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_1; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReadImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_read */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* row_pitch */, - size_t /* slice_pitch */, - void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueWriteImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_write */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t /* input_row_pitch */, - size_t /* input_slice_pitch */, - const void * /* ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueFillImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - const void * /* fill_color */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImage(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_image */, - const size_t * /* src_origin[3] */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyImageToBuffer(cl_command_queue /* command_queue */, - cl_mem /* src_image */, - cl_mem /* dst_buffer */, - const size_t * /* src_origin[3] */, - const size_t * /* region[3] */, - size_t /* dst_offset */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueCopyBufferToImage(cl_command_queue /* command_queue */, - cl_mem /* src_buffer */, - cl_mem /* dst_image */, - size_t /* src_offset */, - const size_t * /* dst_origin[3] */, - const size_t * /* region[3] */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapBuffer(cl_command_queue /* command_queue */, - cl_mem /* buffer */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - size_t /* offset */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY void * CL_API_CALL -clEnqueueMapImage(cl_command_queue /* command_queue */, - cl_mem /* image */, - cl_bool /* blocking_map */, - cl_map_flags /* map_flags */, - const size_t * /* origin[3] */, - const size_t * /* region[3] */, - size_t * /* image_row_pitch */, - size_t * /* image_slice_pitch */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueUnmapMemObject(cl_command_queue /* command_queue */, - cl_mem /* memobj */, - void * /* mapped_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMigrateMemObjects(cl_command_queue /* command_queue */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_objects */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNDRangeKernel(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* work_dim */, - const size_t * /* global_work_offset */, - const size_t * /* global_work_size */, - const size_t * /* local_work_size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueNativeKernel(cl_command_queue /* command_queue */, - void (CL_CALLBACK * /*user_func*/)(void *), - void * /* args */, - size_t /* cb_args */, - cl_uint /* num_mem_objects */, - const cl_mem * /* mem_list */, - const void ** /* args_mem_loc */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueMarkerWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueBarrierWithWaitList(cl_command_queue /* command_queue */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMFree(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void (CL_CALLBACK * /*pfn_free_func*/)(cl_command_queue /* queue */, - cl_uint /* num_svm_pointers */, - void *[] /* svm_pointers[] */, - void * /* user_data */), - void * /* user_data */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemcpy(cl_command_queue /* command_queue */, - cl_bool /* blocking_copy */, - void * /* dst_ptr */, - const void * /* src_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMemFill(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - const void * /* pattern */, - size_t /* pattern_size */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMap(cl_command_queue /* command_queue */, - cl_bool /* blocking_map */, - cl_map_flags /* flags */, - void * /* svm_ptr */, - size_t /* size */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMUnmap(cl_command_queue /* command_queue */, - void * /* svm_ptr */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueSVMMigrateMem(cl_command_queue /* command_queue */, - cl_uint /* num_svm_pointers */, - const void ** /* svm_pointers */, - const size_t * /* sizes */, - cl_mem_migration_flags /* flags */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_2_1; - - -/* Extension function access - * - * Returns the extension function address for the given function name, - * or NULL if a valid function can not be found. The client must - * check to make sure the address is not NULL, before using or - * calling the returned function address. - */ -extern CL_API_ENTRY void * CL_API_CALL -clGetExtensionFunctionAddressForPlatform(cl_platform_id /* platform */, - const char * /* func_name */) CL_API_SUFFIX__VERSION_1_2; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage2D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_row_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateImage3D(cl_context /* context */, - cl_mem_flags /* flags */, - const cl_image_format * /* image_format */, - size_t /* image_width */, - size_t /* image_height */, - size_t /* image_depth */, - size_t /* image_row_pitch */, - size_t /* image_slice_pitch */, - void * /* host_ptr */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueMarker(cl_command_queue /* command_queue */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueWaitForEvents(cl_command_queue /* command_queue */, - cl_uint /* num_events */, - const cl_event * /* event_list */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clEnqueueBarrier(cl_command_queue /* command_queue */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_int CL_API_CALL -clUnloadCompiler(void) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED void * CL_API_CALL -clGetExtensionFunctionAddress(const char * /* func_name */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* Deprecated OpenCL 2.0 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_command_queue CL_API_CALL -clCreateCommandQueue(cl_context /* context */, - cl_device_id /* device */, - cl_command_queue_properties /* properties */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_sampler CL_API_CALL -clCreateSampler(cl_context /* context */, - cl_bool /* normalized_coords */, - cl_addressing_mode /* addressing_mode */, - cl_filter_mode /* filter_mode */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_2_DEPRECATED cl_int CL_API_CALL -clEnqueueTask(cl_command_queue /* command_queue */, - cl_kernel /* kernel */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d10.h b/third_party/opencl/include/CL/cl_d3d10.h deleted file mode 100644 index d5960a43f72..00000000000 --- a/third_party/opencl/include/CL/cl_d3d10.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D10_H -#define __OPENCL_CL_D3D10_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d10_sharing */ -#define cl_khr_d3d10_sharing 1 - -typedef cl_uint cl_d3d10_device_source_khr; -typedef cl_uint cl_d3d10_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D10_DEVICE_KHR -1002 -#define CL_INVALID_D3D10_RESOURCE_KHR -1003 -#define CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR -1004 -#define CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR -1005 - -/* cl_d3d10_device_source_nv */ -#define CL_D3D10_DEVICE_KHR 0x4010 -#define CL_D3D10_DXGI_ADAPTER_KHR 0x4011 - -/* cl_d3d10_device_set_nv */ -#define CL_PREFERRED_DEVICES_FOR_D3D10_KHR 0x4012 -#define CL_ALL_DEVICES_FOR_D3D10_KHR 0x4013 - -/* cl_context_info */ -#define CL_CONTEXT_D3D10_DEVICE_KHR 0x4014 -#define CL_CONTEXT_D3D10_PREFER_SHARED_RESOURCES_KHR 0x402C - -/* cl_mem_info */ -#define CL_MEM_D3D10_RESOURCE_KHR 0x4015 - -/* cl_image_info */ -#define CL_IMAGE_D3D10_SUBRESOURCE_KHR 0x4016 - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D10_OBJECTS_KHR 0x4017 -#define CL_COMMAND_RELEASE_D3D10_OBJECTS_KHR 0x4018 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D10KHR_fn)( - cl_platform_id platform, - cl_d3d10_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d10_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D10Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D10Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D10ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_0; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D10_H */ - diff --git a/third_party/opencl/include/CL/cl_d3d11.h b/third_party/opencl/include/CL/cl_d3d11.h deleted file mode 100644 index 39f9072398a..00000000000 --- a/third_party/opencl/include/CL/cl_d3d11.h +++ /dev/null @@ -1,131 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_D3D11_H -#define __OPENCL_CL_D3D11_H - -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/****************************************************************************** - * cl_khr_d3d11_sharing */ -#define cl_khr_d3d11_sharing 1 - -typedef cl_uint cl_d3d11_device_source_khr; -typedef cl_uint cl_d3d11_device_set_khr; - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_D3D11_DEVICE_KHR -1006 -#define CL_INVALID_D3D11_RESOURCE_KHR -1007 -#define CL_D3D11_RESOURCE_ALREADY_ACQUIRED_KHR -1008 -#define CL_D3D11_RESOURCE_NOT_ACQUIRED_KHR -1009 - -/* cl_d3d11_device_source */ -#define CL_D3D11_DEVICE_KHR 0x4019 -#define CL_D3D11_DXGI_ADAPTER_KHR 0x401A - -/* cl_d3d11_device_set */ -#define CL_PREFERRED_DEVICES_FOR_D3D11_KHR 0x401B -#define CL_ALL_DEVICES_FOR_D3D11_KHR 0x401C - -/* cl_context_info */ -#define CL_CONTEXT_D3D11_DEVICE_KHR 0x401D -#define CL_CONTEXT_D3D11_PREFER_SHARED_RESOURCES_KHR 0x402D - -/* cl_mem_info */ -#define CL_MEM_D3D11_RESOURCE_KHR 0x401E - -/* cl_image_info */ -#define CL_IMAGE_D3D11_SUBRESOURCE_KHR 0x401F - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_D3D11_OBJECTS_KHR 0x4020 -#define CL_COMMAND_RELEASE_D3D11_OBJECTS_KHR 0x4021 - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromD3D11KHR_fn)( - cl_platform_id platform, - cl_d3d11_device_source_khr d3d_device_source, - void * d3d_object, - cl_d3d11_device_set_khr d3d_device_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11BufferKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Buffer * resource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture2DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture2D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromD3D11Texture3DKHR_fn)( - cl_context context, - cl_mem_flags flags, - ID3D11Texture3D * resource, - UINT subresource, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseD3D11ObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_D3D11_H */ - diff --git a/third_party/opencl/include/CL/cl_dx9_media_sharing.h b/third_party/opencl/include/CL/cl_dx9_media_sharing.h deleted file mode 100644 index 2729e8b9e89..00000000000 --- a/third_party/opencl/include/CL/cl_dx9_media_sharing.h +++ /dev/null @@ -1,132 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_CL_DX9_MEDIA_SHARING_H -#define __OPENCL_CL_DX9_MEDIA_SHARING_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/******************************************************************************/ -/* cl_khr_dx9_media_sharing */ -#define cl_khr_dx9_media_sharing 1 - -typedef cl_uint cl_dx9_media_adapter_type_khr; -typedef cl_uint cl_dx9_media_adapter_set_khr; - -#if defined(_WIN32) -#include -typedef struct _cl_dx9_surface_info_khr -{ - IDirect3DSurface9 *resource; - HANDLE shared_handle; -} cl_dx9_surface_info_khr; -#endif - - -/******************************************************************************/ - -/* Error Codes */ -#define CL_INVALID_DX9_MEDIA_ADAPTER_KHR -1010 -#define CL_INVALID_DX9_MEDIA_SURFACE_KHR -1011 -#define CL_DX9_MEDIA_SURFACE_ALREADY_ACQUIRED_KHR -1012 -#define CL_DX9_MEDIA_SURFACE_NOT_ACQUIRED_KHR -1013 - -/* cl_media_adapter_type_khr */ -#define CL_ADAPTER_D3D9_KHR 0x2020 -#define CL_ADAPTER_D3D9EX_KHR 0x2021 -#define CL_ADAPTER_DXVA_KHR 0x2022 - -/* cl_media_adapter_set_khr */ -#define CL_PREFERRED_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2023 -#define CL_ALL_DEVICES_FOR_DX9_MEDIA_ADAPTER_KHR 0x2024 - -/* cl_context_info */ -#define CL_CONTEXT_ADAPTER_D3D9_KHR 0x2025 -#define CL_CONTEXT_ADAPTER_D3D9EX_KHR 0x2026 -#define CL_CONTEXT_ADAPTER_DXVA_KHR 0x2027 - -/* cl_mem_info */ -#define CL_MEM_DX9_MEDIA_ADAPTER_TYPE_KHR 0x2028 -#define CL_MEM_DX9_MEDIA_SURFACE_INFO_KHR 0x2029 - -/* cl_image_info */ -#define CL_IMAGE_DX9_MEDIA_PLANE_KHR 0x202A - -/* cl_command_type */ -#define CL_COMMAND_ACQUIRE_DX9_MEDIA_SURFACES_KHR 0x202B -#define CL_COMMAND_RELEASE_DX9_MEDIA_SURFACES_KHR 0x202C - -/******************************************************************************/ - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetDeviceIDsFromDX9MediaAdapterKHR_fn)( - cl_platform_id platform, - cl_uint num_media_adapters, - cl_dx9_media_adapter_type_khr * media_adapter_type, - void * media_adapters, - cl_dx9_media_adapter_set_khr media_adapter_set, - cl_uint num_entries, - cl_device_id * devices, - cl_uint * num_devices) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromDX9MediaSurfaceKHR_fn)( - cl_context context, - cl_mem_flags flags, - cl_dx9_media_adapter_type_khr adapter_type, - void * surface_info, - cl_uint plane, - cl_int * errcode_ret) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseDX9MediaSurfacesKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event) CL_API_SUFFIX__VERSION_1_2; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_DX9_MEDIA_SHARING_H */ - diff --git a/third_party/opencl/include/CL/cl_egl.h b/third_party/opencl/include/CL/cl_egl.h deleted file mode 100644 index a765bd5266c..00000000000 --- a/third_party/opencl/include/CL/cl_egl.h +++ /dev/null @@ -1,136 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -#ifndef __OPENCL_CL_EGL_H -#define __OPENCL_CL_EGL_H - -#ifdef __APPLE__ - -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Command type for events created with clEnqueueAcquireEGLObjectsKHR */ -#define CL_COMMAND_EGL_FENCE_SYNC_OBJECT_KHR 0x202F -#define CL_COMMAND_ACQUIRE_EGL_OBJECTS_KHR 0x202D -#define CL_COMMAND_RELEASE_EGL_OBJECTS_KHR 0x202E - -/* Error type for clCreateFromEGLImageKHR */ -#define CL_INVALID_EGL_OBJECT_KHR -1093 -#define CL_EGL_RESOURCE_NOT_ACQUIRED_KHR -1092 - -/* CLeglImageKHR is an opaque handle to an EGLImage */ -typedef void* CLeglImageKHR; - -/* CLeglDisplayKHR is an opaque handle to an EGLDisplay */ -typedef void* CLeglDisplayKHR; - -/* CLeglSyncKHR is an opaque handle to an EGLSync object */ -typedef void* CLeglSyncKHR; - -/* properties passed to clCreateFromEGLImageKHR */ -typedef intptr_t cl_egl_image_properties_khr; - - -#define cl_khr_egl_image 1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageKHR(cl_context /* context */, - CLeglDisplayKHR /* egldisplay */, - CLeglImageKHR /* eglimage */, - cl_mem_flags /* flags */, - const cl_egl_image_properties_khr * /* properties */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_mem (CL_API_CALL *clCreateFromEGLImageKHR_fn)( - cl_context context, - CLeglDisplayKHR egldisplay, - CLeglImageKHR eglimage, - cl_mem_flags flags, - const cl_egl_image_properties_khr * properties, - cl_int * errcode_ret); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueAcquireEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseEGLObjectsKHR(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clEnqueueReleaseEGLObjectsKHR_fn)( - cl_command_queue command_queue, - cl_uint num_objects, - const cl_mem * mem_objects, - cl_uint num_events_in_wait_list, - const cl_event * event_wait_list, - cl_event * event); - - -#define cl_khr_egl_event 1 - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromEGLSyncKHR(cl_context /* context */, - CLeglSyncKHR /* sync */, - CLeglDisplayKHR /* display */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_event (CL_API_CALL *clCreateEventFromEGLSyncKHR_fn)( - cl_context context, - CLeglSyncKHR sync, - CLeglDisplayKHR display, - cl_int * errcode_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EGL_H */ diff --git a/third_party/opencl/include/CL/cl_ext.h b/third_party/opencl/include/CL/cl_ext.h deleted file mode 100644 index 7941583895c..00000000000 --- a/third_party/opencl/include/CL/cl_ext.h +++ /dev/null @@ -1,391 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11928 $ on $Date: 2010-07-13 09:04:56 -0700 (Tue, 13 Jul 2010) $ */ - -/* cl_ext.h contains OpenCL extensions which don't have external */ -/* (OpenGL, D3D) dependencies. */ - -#ifndef __CL_EXT_H -#define __CL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include - #include -#else - #include -#endif - -/* cl_khr_fp16 extension - no extension #define since it has no functions */ -#define CL_DEVICE_HALF_FP_CONFIG 0x1033 - -/* Memory object destruction - * - * Apple extension for use to manage externally allocated buffers used with cl_mem objects with CL_MEM_USE_HOST_PTR - * - * Registers a user callback function that will be called when the memory object is deleted and its resources - * freed. Each call to clSetMemObjectCallbackFn registers the specified user callback function on a callback - * stack associated with memobj. The registered user callback functions are called in the reverse order in - * which they were registered. The user callback functions are called and then the memory object is deleted - * and its resources freed. This provides a mechanism for the application (and libraries) using memobj to be - * notified when the memory referenced by host_ptr, specified when the memory object is created and used as - * the storage bits for the memory object, can be reused or freed. - * - * The application may not call CL api's with the cl_mem object passed to the pfn_notify. - * - * Please check for the "cl_APPLE_SetMemObjectDestructor" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - */ -#define cl_APPLE_SetMemObjectDestructor 1 -cl_int CL_API_ENTRY clSetMemObjectDestructorAPPLE( cl_mem /* memobj */, - void (* /*pfn_notify*/)( cl_mem /* memobj */, void* /*user_data*/), - void * /*user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/* Context Logging Functions - * - * The next three convenience functions are intended to be used as the pfn_notify parameter to clCreateContext(). - * Please check for the "cl_APPLE_ContextLoggingFunctions" extension using clGetDeviceInfo(CL_DEVICE_EXTENSIONS) - * before using. - * - * clLogMessagesToSystemLog fowards on all log messages to the Apple System Logger - */ -#define cl_APPLE_ContextLoggingFunctions 1 -extern void CL_API_ENTRY clLogMessagesToSystemLogAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStdout sends all log messages to the file descriptor stdout */ -extern void CL_API_ENTRY clLogMessagesToStdoutAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - -/* clLogMessagesToStderr sends all log messages to the file descriptor stderr */ -extern void CL_API_ENTRY clLogMessagesToStderrAPPLE( const char * /* errstr */, - const void * /* private_info */, - size_t /* cb */, - void * /* user_data */ ) CL_EXT_SUFFIX__VERSION_1_0; - - -/************************ -* cl_khr_icd extension * -************************/ -#define cl_khr_icd 1 - -/* cl_platform_info */ -#define CL_PLATFORM_ICD_SUFFIX_KHR 0x0920 - -/* Additional Error Codes */ -#define CL_PLATFORM_NOT_FOUND_KHR -1001 - -extern CL_API_ENTRY cl_int CL_API_CALL -clIcdGetPlatformIDsKHR(cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clIcdGetPlatformIDsKHR_fn)( - cl_uint /* num_entries */, - cl_platform_id * /* platforms */, - cl_uint * /* num_platforms */); - - -/* Extension: cl_khr_image2D_buffer - * - * This extension allows a 2D image to be created from a cl_mem buffer without a copy. - * The type associated with a 2D image created from a buffer in an OpenCL program is image2d_t. - * Both the sampler and sampler-less read_image built-in functions are supported for 2D images - * and 2D images created from a buffer. Similarly, the write_image built-ins are also supported - * for 2D images created from a buffer. - * - * When the 2D image from buffer is created, the client must specify the width, - * height, image format (i.e. channel order and channel data type) and optionally the row pitch - * - * The pitch specified must be a multiple of CL_DEVICE_IMAGE_PITCH_ALIGNMENT pixels. - * The base address of the buffer must be aligned to CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT pixels. - */ - -/************************************* - * cl_khr_initalize_memory extension * - *************************************/ - -#define CL_CONTEXT_MEMORY_INITIALIZE_KHR 0x2030 - - -/************************************** - * cl_khr_terminate_context extension * - **************************************/ - -#define CL_DEVICE_TERMINATE_CAPABILITY_KHR 0x2031 -#define CL_CONTEXT_TERMINATE_KHR 0x2032 - -#define cl_khr_terminate_context 1 -extern CL_API_ENTRY cl_int CL_API_CALL clTerminateContextKHR(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clTerminateContextKHR_fn)(cl_context /* context */) CL_EXT_SUFFIX__VERSION_1_2; - - -/* - * Extension: cl_khr_spir - * - * This extension adds support to create an OpenCL program object from a - * Standard Portable Intermediate Representation (SPIR) instance - */ - -#define CL_DEVICE_SPIR_VERSIONS 0x40E0 -#define CL_PROGRAM_BINARY_TYPE_INTERMEDIATE 0x40E1 - - -/****************************************** -* cl_nv_device_attribute_query extension * -******************************************/ -/* cl_nv_device_attribute_query extension - no extension #define since it has no functions */ -#define CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV 0x4000 -#define CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV 0x4001 -#define CL_DEVICE_REGISTERS_PER_BLOCK_NV 0x4002 -#define CL_DEVICE_WARP_SIZE_NV 0x4003 -#define CL_DEVICE_GPU_OVERLAP_NV 0x4004 -#define CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV 0x4005 -#define CL_DEVICE_INTEGRATED_MEMORY_NV 0x4006 - -/********************************* -* cl_amd_device_attribute_query * -*********************************/ -#define CL_DEVICE_PROFILING_TIMER_OFFSET_AMD 0x4036 - -/********************************* -* cl_arm_printf extension -*********************************/ -#define CL_PRINTF_CALLBACK_ARM 0x40B0 -#define CL_PRINTF_BUFFERSIZE_ARM 0x40B1 - -#ifdef CL_VERSION_1_1 - /*********************************** - * cl_ext_device_fission extension * - ***********************************/ - #define cl_ext_device_fission 1 - - extern CL_API_ENTRY cl_int CL_API_CALL - clReleaseDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clReleaseDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - extern CL_API_ENTRY cl_int CL_API_CALL - clRetainDeviceEXT( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - (CL_API_CALL *clRetainDeviceEXT_fn)( cl_device_id /*device*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef cl_ulong cl_device_partition_property_ext; - extern CL_API_ENTRY cl_int CL_API_CALL - clCreateSubDevicesEXT( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clCreateSubDevicesEXT_fn)( cl_device_id /*in_device*/, - const cl_device_partition_property_ext * /* properties */, - cl_uint /*num_entries*/, - cl_device_id * /*out_devices*/, - cl_uint * /*num_devices*/ ) CL_EXT_SUFFIX__VERSION_1_1; - - /* cl_device_partition_property_ext */ - #define CL_DEVICE_PARTITION_EQUALLY_EXT 0x4050 - #define CL_DEVICE_PARTITION_BY_COUNTS_EXT 0x4051 - #define CL_DEVICE_PARTITION_BY_NAMES_EXT 0x4052 - #define CL_DEVICE_PARTITION_BY_AFFINITY_DOMAIN_EXT 0x4053 - - /* clDeviceGetInfo selectors */ - #define CL_DEVICE_PARENT_DEVICE_EXT 0x4054 - #define CL_DEVICE_PARTITION_TYPES_EXT 0x4055 - #define CL_DEVICE_AFFINITY_DOMAINS_EXT 0x4056 - #define CL_DEVICE_REFERENCE_COUNT_EXT 0x4057 - #define CL_DEVICE_PARTITION_STYLE_EXT 0x4058 - - /* error codes */ - #define CL_DEVICE_PARTITION_FAILED_EXT -1057 - #define CL_INVALID_PARTITION_COUNT_EXT -1058 - #define CL_INVALID_PARTITION_NAME_EXT -1059 - - /* CL_AFFINITY_DOMAINs */ - #define CL_AFFINITY_DOMAIN_L1_CACHE_EXT 0x1 - #define CL_AFFINITY_DOMAIN_L2_CACHE_EXT 0x2 - #define CL_AFFINITY_DOMAIN_L3_CACHE_EXT 0x3 - #define CL_AFFINITY_DOMAIN_L4_CACHE_EXT 0x4 - #define CL_AFFINITY_DOMAIN_NUMA_EXT 0x10 - #define CL_AFFINITY_DOMAIN_NEXT_FISSIONABLE_EXT 0x100 - - /* cl_device_partition_property_ext list terminators */ - #define CL_PROPERTIES_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_COUNTS_LIST_END_EXT ((cl_device_partition_property_ext) 0) - #define CL_PARTITION_BY_NAMES_LIST_END_EXT ((cl_device_partition_property_ext) 0 - 1) - -/********************************* -* cl_qcom_ext_host_ptr extension -*********************************/ - -#define CL_MEM_EXT_HOST_PTR_QCOM (1 << 29) - -#define CL_DEVICE_EXT_MEM_PADDING_IN_BYTES_QCOM 0x40A0 -#define CL_DEVICE_PAGE_SIZE_QCOM 0x40A1 -#define CL_IMAGE_ROW_ALIGNMENT_QCOM 0x40A2 -#define CL_IMAGE_SLICE_ALIGNMENT_QCOM 0x40A3 -#define CL_MEM_HOST_UNCACHED_QCOM 0x40A4 -#define CL_MEM_HOST_WRITEBACK_QCOM 0x40A5 -#define CL_MEM_HOST_WRITETHROUGH_QCOM 0x40A6 -#define CL_MEM_HOST_WRITE_COMBINING_QCOM 0x40A7 - -typedef cl_uint cl_image_pitch_info_qcom; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetDeviceImageInfoQCOM(cl_device_id device, - size_t image_width, - size_t image_height, - const cl_image_format *image_format, - cl_image_pitch_info_qcom param_name, - size_t param_value_size, - void *param_value, - size_t *param_value_size_ret); - -typedef struct _cl_mem_ext_host_ptr -{ - /* Type of external memory allocation. */ - /* Legal values will be defined in layered extensions. */ - cl_uint allocation_type; - - /* Host cache policy for this external memory allocation. */ - cl_uint host_cache_policy; - -} cl_mem_ext_host_ptr; - -/********************************* -* cl_qcom_ion_host_ptr extension -*********************************/ - -#define CL_MEM_ION_HOST_PTR_QCOM 0x40A8 - -typedef struct _cl_mem_ion_host_ptr -{ - /* Type of external memory allocation. */ - /* Must be CL_MEM_ION_HOST_PTR_QCOM for ION allocations. */ - cl_mem_ext_host_ptr ext_host_ptr; - - /* ION file descriptor */ - int ion_filedesc; - - /* Host pointer to the ION allocated memory */ - void* ion_hostptr; - -} cl_mem_ion_host_ptr; - -#endif /* CL_VERSION_1_1 */ - - -#ifdef CL_VERSION_2_0 -/********************************* -* cl_khr_sub_groups extension -*********************************/ -#define cl_khr_sub_groups 1 - -typedef cl_uint cl_kernel_sub_group_info_khr; - -/* cl_khr_sub_group_info */ -#define CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR 0x2033 -#define CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR 0x2034 - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetKernelSubGroupInfoKHR(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; - -typedef CL_API_ENTRY cl_int - ( CL_API_CALL * clGetKernelSubGroupInfoKHR_fn)(cl_kernel /* in_kernel */, - cl_device_id /*in_device*/, - cl_kernel_sub_group_info_khr /* param_name */, - size_t /*input_value_size*/, - const void * /*input_value*/, - size_t /*param_value_size*/, - void* /*param_value*/, - size_t* /*param_value_size_ret*/ ) CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED; -#endif /* CL_VERSION_2_0 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_priority_hints extension -*********************************/ -#define cl_khr_priority_hints 1 - -typedef cl_uint cl_queue_priority_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_PRIORITY_KHR 0x1096 - -/* cl_queue_priority_khr */ -#define CL_QUEUE_PRIORITY_HIGH_KHR (1<<0) -#define CL_QUEUE_PRIORITY_MED_KHR (1<<1) -#define CL_QUEUE_PRIORITY_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef CL_VERSION_2_1 -/********************************* -* cl_khr_throttle_hints extension -*********************************/ -#define cl_khr_throttle_hints 1 - -typedef cl_uint cl_queue_throttle_khr; - -/* cl_command_queue_properties */ -#define CL_QUEUE_THROTTLE_KHR 0x1097 - -/* cl_queue_throttle_khr */ -#define CL_QUEUE_THROTTLE_HIGH_KHR (1<<0) -#define CL_QUEUE_THROTTLE_MED_KHR (1<<1) -#define CL_QUEUE_THROTTLE_LOW_KHR (1<<2) - -#endif /* CL_VERSION_2_1 */ - -#ifdef __cplusplus -} -#endif - - -#endif /* __CL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_ext_qcom.h b/third_party/opencl/include/CL/cl_ext_qcom.h deleted file mode 100644 index 6328a1cd93a..00000000000 --- a/third_party/opencl/include/CL/cl_ext_qcom.h +++ /dev/null @@ -1,255 +0,0 @@ -/* Copyright (c) 2009-2017 Qualcomm Technologies, Inc. All Rights Reserved. - * Qualcomm Technologies Proprietary and Confidential. - */ - -#ifndef __OPENCL_CL_EXT_QCOM_H -#define __OPENCL_CL_EXT_QCOM_H - -// Needed by cl_khr_egl_event extension -#include -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - - -/************************************ - * cl_qcom_create_buffer_from_image * - ************************************/ - -#define CL_BUFFER_FROM_IMAGE_ROW_PITCH_QCOM 0x40C0 -#define CL_BUFFER_FROM_IMAGE_SLICE_PITCH_QCOM 0x40C1 - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateBufferFromImageQCOM(cl_mem image, - cl_mem_flags flags, - cl_int *errcode_ret); - - -/************************************ - * cl_qcom_limited_printf extension * - ************************************/ - -/* Builtin printf function buffer size in bytes. */ -#define CL_DEVICE_PRINTF_BUFFER_SIZE_QCOM 0x1049 - - -/************************************* - * cl_qcom_extended_images extension * - *************************************/ - -#define CL_CONTEXT_ENABLE_EXTENDED_IMAGES_QCOM 0x40AA -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_WIDTH_QCOM 0x40AB -#define CL_DEVICE_EXTENDED_IMAGE2D_MAX_HEIGHT_QCOM 0x40AC -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_WIDTH_QCOM 0x40AD -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_HEIGHT_QCOM 0x40AE -#define CL_DEVICE_EXTENDED_IMAGE3D_MAX_DEPTH_QCOM 0x40AF - -/************************************* - * cl_qcom_perf_hint extension * - *************************************/ - -typedef cl_uint cl_perf_hint; - -#define CL_CONTEXT_PERF_HINT_QCOM 0x40C2 - -/*cl_perf_hint*/ -#define CL_PERF_HINT_HIGH_QCOM 0x40C3 -#define CL_PERF_HINT_NORMAL_QCOM 0x40C4 -#define CL_PERF_HINT_LOW_QCOM 0x40C5 - -extern CL_API_ENTRY cl_int CL_API_CALL -clSetPerfHintQCOM(cl_context context, - cl_perf_hint perf_hint); - -// This extension is published at Khronos, so its definitions are made in cl_ext.h. -// This duplication is for backward compatibility. - -#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/********************************* -* cl_qcom_android_native_buffer_host_ptr extension -*********************************/ - -#define CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM 0x40C6 - - -typedef struct _cl_mem_android_native_buffer_host_ptr -{ - // Type of external memory allocation. - // Must be CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM for Android native buffers. - cl_mem_ext_host_ptr ext_host_ptr; - - // Virtual pointer to the android native buffer - void* anb_ptr; - -} cl_mem_android_native_buffer_host_ptr; - -#endif //#ifndef CL_MEM_ANDROID_NATIVE_BUFFER_HOST_PTR_QCOM - -/*********************************** -* cl_img_egl_image extension * -************************************/ -typedef void* CLeglImageIMG; -typedef void* CLeglDisplayIMG; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromEGLImageIMG(cl_context context, - cl_mem_flags flags, - CLeglImageIMG image, - CLeglDisplayIMG display, - cl_int *errcode_ret); - - -/********************************* -* cl_qcom_other_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-standard images -#define CL_MEM_OTHER_IMAGE_QCOM (1<<25) - -// cl_channel_type -#define CL_QCOM_UNORM_MIPI10 0x4159 -#define CL_QCOM_UNORM_MIPI12 0x415A -#define CL_QCOM_UNSIGNED_MIPI10 0x415B -#define CL_QCOM_UNSIGNED_MIPI12 0x415C -#define CL_QCOM_UNORM_INT10 0x415D -#define CL_QCOM_UNORM_INT12 0x415E -#define CL_QCOM_UNSIGNED_INT16 0x415F - -// cl_channel_order -// Dedicate 0x4130-0x415F range for QCOM extended image formats -// 0x4130 - 0x4132 range is assigned to pixel-oriented compressed format -#define CL_QCOM_BAYER 0x414E - -#define CL_QCOM_NV12 0x4133 -#define CL_QCOM_NV12_Y 0x4134 -#define CL_QCOM_NV12_UV 0x4135 - -#define CL_QCOM_TILED_NV12 0x4136 -#define CL_QCOM_TILED_NV12_Y 0x4137 -#define CL_QCOM_TILED_NV12_UV 0x4138 - -#define CL_QCOM_P010 0x413C -#define CL_QCOM_P010_Y 0x413D -#define CL_QCOM_P010_UV 0x413E - -#define CL_QCOM_TILED_P010 0x413F -#define CL_QCOM_TILED_P010_Y 0x4140 -#define CL_QCOM_TILED_P010_UV 0x4141 - - -#define CL_QCOM_TP10 0x4145 -#define CL_QCOM_TP10_Y 0x4146 -#define CL_QCOM_TP10_UV 0x4147 - -#define CL_QCOM_TILED_TP10 0x4148 -#define CL_QCOM_TILED_TP10_Y 0x4149 -#define CL_QCOM_TILED_TP10_UV 0x414A - -/********************************* -* cl_qcom_compressed_image extension -*********************************/ - -// Extended flag for creating/querying QCOM non-planar compressed images -#define CL_MEM_COMPRESSED_IMAGE_QCOM (1<<27) - -// Extended image format -// cl_channel_order -#define CL_QCOM_COMPRESSED_RGBA 0x4130 -#define CL_QCOM_COMPRESSED_RGBx 0x4131 - -#define CL_QCOM_COMPRESSED_NV12_Y 0x413A -#define CL_QCOM_COMPRESSED_NV12_UV 0x413B - -#define CL_QCOM_COMPRESSED_P010 0x4142 -#define CL_QCOM_COMPRESSED_P010_Y 0x4143 -#define CL_QCOM_COMPRESSED_P010_UV 0x4144 - -#define CL_QCOM_COMPRESSED_TP10 0x414B -#define CL_QCOM_COMPRESSED_TP10_Y 0x414C -#define CL_QCOM_COMPRESSED_TP10_UV 0x414D - -#define CL_QCOM_COMPRESSED_NV12_4R 0x414F -#define CL_QCOM_COMPRESSED_NV12_4R_Y 0x4150 -#define CL_QCOM_COMPRESSED_NV12_4R_UV 0x4151 -/********************************* -* cl_qcom_compressed_yuv_image_read extension -*********************************/ - -// Extended flag for creating/querying QCOM compressed images -#define CL_MEM_COMPRESSED_YUV_IMAGE_QCOM (1<<28) - -// Extended image format -#define CL_QCOM_COMPRESSED_NV12 0x10C4 - -// Extended flag for setting ION buffer allocation type -#define CL_MEM_ION_HOST_PTR_COMPRESSED_YUV_QCOM 0x40CD -#define CL_MEM_ION_HOST_PTR_PROTECTED_COMPRESSED_YUV_QCOM 0x40CE - -/********************************* -* cl_qcom_accelerated_image_ops -*********************************/ -#define CL_MEM_OBJECT_WEIGHT_IMAGE_QCOM 0x4110 -#define CL_DEVICE_HOF_MAX_NUM_PHASES_QCOM 0x4111 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_X_QCOM 0x4112 -#define CL_DEVICE_HOF_MAX_FILTER_SIZE_Y_QCOM 0x4113 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_X_QCOM 0x4114 -#define CL_DEVICE_BLOCK_MATCHING_MAX_REGION_SIZE_Y_QCOM 0x4115 - -//Extended flag for specifying weight image type -#define CL_WEIGHT_IMAGE_SEPARABLE_QCOM (1<<0) - -// Box Filter -typedef struct _cl_box_filter_size_qcom -{ - // Width of box filter on X direction. - float box_filter_width; - - // Height of box filter on Y direction. - float box_filter_height; -} cl_box_filter_size_qcom; - -// HOF Weight Image Desc -typedef struct _cl_weight_desc_qcom -{ - /** Coordinate of the "center" point of the weight image, - based on the weight image's top-left corner as the origin. */ - size_t center_coord_x; - size_t center_coord_y; - cl_bitfield flags; -} cl_weight_desc_qcom; - -typedef struct _cl_weight_image_desc_qcom -{ - cl_image_desc image_desc; - cl_weight_desc_qcom weight_desc; -} cl_weight_image_desc_qcom; - -/************************************* - * cl_qcom_protected_context extension * - *************************************/ - -#define CL_CONTEXT_PROTECTED_QCOM 0x40C7 -#define CL_MEM_ION_HOST_PTR_PROTECTED_QCOM 0x40C8 - -/************************************* - * cl_qcom_priority_hint extension * - *************************************/ -#define CL_PRIORITY_HINT_NONE_QCOM 0 -typedef cl_uint cl_priority_hint; - -#define CL_CONTEXT_PRIORITY_HINT_QCOM 0x40C9 - -/*cl_priority_hint*/ -#define CL_PRIORITY_HINT_HIGH_QCOM 0x40CA -#define CL_PRIORITY_HINT_NORMAL_QCOM 0x40CB -#define CL_PRIORITY_HINT_LOW_QCOM 0x40CC - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_EXT_QCOM_H */ diff --git a/third_party/opencl/include/CL/cl_gl.h b/third_party/opencl/include/CL/cl_gl.h deleted file mode 100644 index 945daa83d7f..00000000000 --- a/third_party/opencl/include/CL/cl_gl.h +++ /dev/null @@ -1,167 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -#ifndef __OPENCL_CL_GL_H -#define __OPENCL_CL_GL_H - -#ifdef __APPLE__ -#include -#else -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef cl_uint cl_gl_object_type; -typedef cl_uint cl_gl_texture_info; -typedef cl_uint cl_gl_platform_info; -typedef struct __GLsync *cl_GLsync; - -/* cl_gl_object_type = 0x2000 - 0x200F enum values are currently taken */ -#define CL_GL_OBJECT_BUFFER 0x2000 -#define CL_GL_OBJECT_TEXTURE2D 0x2001 -#define CL_GL_OBJECT_TEXTURE3D 0x2002 -#define CL_GL_OBJECT_RENDERBUFFER 0x2003 -#define CL_GL_OBJECT_TEXTURE2D_ARRAY 0x200E -#define CL_GL_OBJECT_TEXTURE1D 0x200F -#define CL_GL_OBJECT_TEXTURE1D_ARRAY 0x2010 -#define CL_GL_OBJECT_TEXTURE_BUFFER 0x2011 - -/* cl_gl_texture_info */ -#define CL_GL_TEXTURE_TARGET 0x2004 -#define CL_GL_MIPMAP_LEVEL 0x2005 -#define CL_GL_NUM_SAMPLES 0x2012 - - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLBuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* bufobj */, - int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLTexture(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_2; - -extern CL_API_ENTRY cl_mem CL_API_CALL -clCreateFromGLRenderbuffer(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLuint /* renderbuffer */, - cl_int * /* errcode_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLObjectInfo(cl_mem /* memobj */, - cl_gl_object_type * /* gl_object_type */, - cl_GLuint * /* gl_object_name */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLTextureInfo(cl_mem /* memobj */, - cl_gl_texture_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueAcquireGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - -extern CL_API_ENTRY cl_int CL_API_CALL -clEnqueueReleaseGLObjects(cl_command_queue /* command_queue */, - cl_uint /* num_objects */, - const cl_mem * /* mem_objects */, - cl_uint /* num_events_in_wait_list */, - const cl_event * /* event_wait_list */, - cl_event * /* event */) CL_API_SUFFIX__VERSION_1_0; - - -/* Deprecated OpenCL 1.1 APIs */ -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture2D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -extern CL_API_ENTRY CL_EXT_PREFIX__VERSION_1_1_DEPRECATED cl_mem CL_API_CALL -clCreateFromGLTexture3D(cl_context /* context */, - cl_mem_flags /* flags */, - cl_GLenum /* target */, - cl_GLint /* miplevel */, - cl_GLuint /* texture */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED; - -/* cl_khr_gl_sharing extension */ - -#define cl_khr_gl_sharing 1 - -typedef cl_uint cl_gl_context_info; - -/* Additional Error Codes */ -#define CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR -1000 - -/* cl_gl_context_info */ -#define CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR 0x2006 -#define CL_DEVICES_FOR_GL_CONTEXT_KHR 0x2007 - -/* Additional cl_context_properties */ -#define CL_GL_CONTEXT_KHR 0x2008 -#define CL_EGL_DISPLAY_KHR 0x2009 -#define CL_GLX_DISPLAY_KHR 0x200A -#define CL_WGL_HDC_KHR 0x200B -#define CL_CGL_SHAREGROUP_KHR 0x200C - -extern CL_API_ENTRY cl_int CL_API_CALL -clGetGLContextInfoKHR(const cl_context_properties * /* properties */, - cl_gl_context_info /* param_name */, - size_t /* param_value_size */, - void * /* param_value */, - size_t * /* param_value_size_ret */) CL_API_SUFFIX__VERSION_1_0; - -typedef CL_API_ENTRY cl_int (CL_API_CALL *clGetGLContextInfoKHR_fn)( - const cl_context_properties * properties, - cl_gl_context_info param_name, - size_t param_value_size, - void * param_value, - size_t * param_value_size_ret); - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_H */ diff --git a/third_party/opencl/include/CL/cl_gl_ext.h b/third_party/opencl/include/CL/cl_gl_ext.h deleted file mode 100644 index e3c14c6408c..00000000000 --- a/third_party/opencl/include/CL/cl_gl_ext.h +++ /dev/null @@ -1,74 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -/* cl_gl_ext.h contains vendor (non-KHR) OpenCL extensions which have */ -/* OpenGL dependencies. */ - -#ifndef __OPENCL_CL_GL_EXT_H -#define __OPENCL_CL_GL_EXT_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - #include -#else - #include -#endif - -/* - * For each extension, follow this template - * cl_VEN_extname extension */ -/* #define cl_VEN_extname 1 - * ... define new types, if any - * ... define new tokens, if any - * ... define new APIs, if any - * - * If you need GLtypes here, mirror them with a cl_GLtype, rather than including a GL header - * This allows us to avoid having to decide whether to include GL headers or GLES here. - */ - -/* - * cl_khr_gl_event extension - * See section 9.9 in the OpenCL 1.1 spec for more information - */ -#define CL_COMMAND_GL_FENCE_SYNC_OBJECT_KHR 0x200D - -extern CL_API_ENTRY cl_event CL_API_CALL -clCreateEventFromGLsyncKHR(cl_context /* context */, - cl_GLsync /* cl_GLsync */, - cl_int * /* errcode_ret */) CL_EXT_SUFFIX__VERSION_1_1; - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_CL_GL_EXT_H */ diff --git a/third_party/opencl/include/CL/cl_platform.h b/third_party/opencl/include/CL/cl_platform.h deleted file mode 100644 index 4e334a29183..00000000000 --- a/third_party/opencl/include/CL/cl_platform.h +++ /dev/null @@ -1,1333 +0,0 @@ -/********************************************************************************** - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - **********************************************************************************/ - -/* $Revision: 11803 $ on $Date: 2010-06-25 10:02:12 -0700 (Fri, 25 Jun 2010) $ */ - -#ifndef __CL_PLATFORM_H -#define __CL_PLATFORM_H - -#ifdef __APPLE__ - /* Contains #defines for AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER below */ - #include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -#if defined(_WIN32) - #define CL_API_ENTRY - #define CL_API_CALL __stdcall - #define CL_CALLBACK __stdcall -#else - #define CL_API_ENTRY - #define CL_API_CALL - #define CL_CALLBACK -#endif - -/* - * Deprecation flags refer to the last version of the header in which the - * feature was not deprecated. - * - * E.g. VERSION_1_1_DEPRECATED means the feature is present in 1.1 without - * deprecation but is deprecated in versions later than 1.1. - */ - -#ifdef __APPLE__ - #define CL_EXTENSION_WEAK_LINK __attribute__((weak_import)) - #define CL_API_SUFFIX__VERSION_1_0 AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER - #define CL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_1 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_7 - - #ifdef AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_8_AND_LATER - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER_BUT_DEPRECATED_IN_MAC_OS_X_VERSION_10_8 - #else - #warning This path should never happen outside of internal operating system development. AvailabilityMacros do not function correctly here! - #define CL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define GCL_API_SUFFIX__VERSION_1_2 AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_2 CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED CL_EXTENSION_WEAK_LINK AVAILABLE_MAC_OS_X_VERSION_10_7_AND_LATER - #endif -#else - #define CL_EXTENSION_WEAK_LINK - #define CL_API_SUFFIX__VERSION_1_0 - #define CL_EXT_SUFFIX__VERSION_1_0 - #define CL_API_SUFFIX__VERSION_1_1 - #define CL_EXT_SUFFIX__VERSION_1_1 - #define CL_API_SUFFIX__VERSION_1_2 - #define CL_EXT_SUFFIX__VERSION_1_2 - #define CL_API_SUFFIX__VERSION_2_0 - #define CL_EXT_SUFFIX__VERSION_2_0 - #define CL_API_SUFFIX__VERSION_2_1 - #define CL_EXT_SUFFIX__VERSION_2_1 - - #ifdef __GNUC__ - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED __attribute__((deprecated)) - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif - #elif _WIN32 - #ifdef CL_USE_DEPRECATED_OPENCL_1_0_APIS - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_1_APIS - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_1_2_APIS - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED __declspec(deprecated) - #endif - - #ifdef CL_USE_DEPRECATED_OPENCL_2_0_APIS - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #else - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED __declspec(deprecated) - #endif - #else - #define CL_EXT_SUFFIX__VERSION_1_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_0_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_1_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_1_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_1_2_DEPRECATED - #define CL_EXT_PREFIX__VERSION_1_2_DEPRECATED - - #define CL_EXT_SUFFIX__VERSION_2_0_DEPRECATED - #define CL_EXT_PREFIX__VERSION_2_0_DEPRECATED - #endif -#endif - -#if (defined (_WIN32) && defined(_MSC_VER)) - -/* scalar types */ -typedef signed __int8 cl_char; -typedef unsigned __int8 cl_uchar; -typedef signed __int16 cl_short; -typedef unsigned __int16 cl_ushort; -typedef signed __int32 cl_int; -typedef unsigned __int32 cl_uint; -typedef signed __int64 cl_long; -typedef unsigned __int64 cl_ulong; - -typedef unsigned __int16 cl_half; -typedef float cl_float; -typedef double cl_double; - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 340282346638528859811704183484516925440.0f -#define CL_FLT_MIN 1.175494350822287507969e-38f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.0 -#define CL_DBL_MIN 2.225073858507201383090e-308 -#define CL_DBL_EPSILON 2.220446049250313080847e-16 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#define CL_NAN (CL_INFINITY - CL_INFINITY) -#define CL_HUGE_VALF ((cl_float) 1e50) -#define CL_HUGE_VAL ((cl_double) 1e500) -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#else - -#include - -/* scalar types */ -typedef int8_t cl_char; -typedef uint8_t cl_uchar; -typedef int16_t cl_short __attribute__((aligned(2))); -typedef uint16_t cl_ushort __attribute__((aligned(2))); -typedef int32_t cl_int __attribute__((aligned(4))); -typedef uint32_t cl_uint __attribute__((aligned(4))); -typedef int64_t cl_long __attribute__((aligned(8))); -typedef uint64_t cl_ulong __attribute__((aligned(8))); - -typedef uint16_t cl_half __attribute__((aligned(2))); -typedef float cl_float __attribute__((aligned(4))); -typedef double cl_double __attribute__((aligned(8))); - -/* Macro names and corresponding values defined by OpenCL */ -#define CL_CHAR_BIT 8 -#define CL_SCHAR_MAX 127 -#define CL_SCHAR_MIN (-127-1) -#define CL_CHAR_MAX CL_SCHAR_MAX -#define CL_CHAR_MIN CL_SCHAR_MIN -#define CL_UCHAR_MAX 255 -#define CL_SHRT_MAX 32767 -#define CL_SHRT_MIN (-32767-1) -#define CL_USHRT_MAX 65535 -#define CL_INT_MAX 2147483647 -#define CL_INT_MIN (-2147483647-1) -#define CL_UINT_MAX 0xffffffffU -#define CL_LONG_MAX ((cl_long) 0x7FFFFFFFFFFFFFFFLL) -#define CL_LONG_MIN ((cl_long) -0x7FFFFFFFFFFFFFFFLL - 1LL) -#define CL_ULONG_MAX ((cl_ulong) 0xFFFFFFFFFFFFFFFFULL) - -#define CL_FLT_DIG 6 -#define CL_FLT_MANT_DIG 24 -#define CL_FLT_MAX_10_EXP +38 -#define CL_FLT_MAX_EXP +128 -#define CL_FLT_MIN_10_EXP -37 -#define CL_FLT_MIN_EXP -125 -#define CL_FLT_RADIX 2 -#define CL_FLT_MAX 0x1.fffffep127f -#define CL_FLT_MIN 0x1.0p-126f -#define CL_FLT_EPSILON 0x1.0p-23f - -#define CL_DBL_DIG 15 -#define CL_DBL_MANT_DIG 53 -#define CL_DBL_MAX_10_EXP +308 -#define CL_DBL_MAX_EXP +1024 -#define CL_DBL_MIN_10_EXP -307 -#define CL_DBL_MIN_EXP -1021 -#define CL_DBL_RADIX 2 -#define CL_DBL_MAX 0x1.fffffffffffffp1023 -#define CL_DBL_MIN 0x1.0p-1022 -#define CL_DBL_EPSILON 0x1.0p-52 - -#define CL_M_E 2.718281828459045090796 -#define CL_M_LOG2E 1.442695040888963387005 -#define CL_M_LOG10E 0.434294481903251816668 -#define CL_M_LN2 0.693147180559945286227 -#define CL_M_LN10 2.302585092994045901094 -#define CL_M_PI 3.141592653589793115998 -#define CL_M_PI_2 1.570796326794896557999 -#define CL_M_PI_4 0.785398163397448278999 -#define CL_M_1_PI 0.318309886183790691216 -#define CL_M_2_PI 0.636619772367581382433 -#define CL_M_2_SQRTPI 1.128379167095512558561 -#define CL_M_SQRT2 1.414213562373095145475 -#define CL_M_SQRT1_2 0.707106781186547572737 - -#define CL_M_E_F 2.71828174591064f -#define CL_M_LOG2E_F 1.44269502162933f -#define CL_M_LOG10E_F 0.43429449200630f -#define CL_M_LN2_F 0.69314718246460f -#define CL_M_LN10_F 2.30258512496948f -#define CL_M_PI_F 3.14159274101257f -#define CL_M_PI_2_F 1.57079637050629f -#define CL_M_PI_4_F 0.78539818525314f -#define CL_M_1_PI_F 0.31830987334251f -#define CL_M_2_PI_F 0.63661974668503f -#define CL_M_2_SQRTPI_F 1.12837922573090f -#define CL_M_SQRT2_F 1.41421353816986f -#define CL_M_SQRT1_2_F 0.70710676908493f - -#if defined( __GNUC__ ) - #define CL_HUGE_VALF __builtin_huge_valf() - #define CL_HUGE_VAL __builtin_huge_val() - #define CL_NAN __builtin_nanf( "" ) -#else - #define CL_HUGE_VALF ((cl_float) 1e50) - #define CL_HUGE_VAL ((cl_double) 1e500) - float nanf( const char * ); - #define CL_NAN nanf( "" ) -#endif -#define CL_MAXFLOAT CL_FLT_MAX -#define CL_INFINITY CL_HUGE_VALF - -#endif - -#include - -/* Mirror types to GL types. Mirror types allow us to avoid deciding which 87s to load based on whether we are using GL or GLES here. */ -typedef unsigned int cl_GLuint; -typedef int cl_GLint; -typedef unsigned int cl_GLenum; - -/* - * Vector types - * - * Note: OpenCL requires that all types be naturally aligned. - * This means that vector types must be naturally aligned. - * For example, a vector of four floats must be aligned to - * a 16 byte boundary (calculated as 4 * the natural 4-byte - * alignment of the float). The alignment qualifiers here - * will only function properly if your compiler supports them - * and if you don't actively work to defeat them. For example, - * in order for a cl_float4 to be 16 byte aligned in a struct, - * the start of the struct must itself be 16-byte aligned. - * - * Maintaining proper alignment is the user's responsibility. - */ - -/* Define basic vector types */ -#if defined( __VEC__ ) - #include /* may be omitted depending on compiler. AltiVec spec provides no way to detect whether the header is required. */ - typedef vector unsigned char __cl_uchar16; - typedef vector signed char __cl_char16; - typedef vector unsigned short __cl_ushort8; - typedef vector signed short __cl_short8; - typedef vector unsigned int __cl_uint4; - typedef vector signed int __cl_int4; - typedef vector float __cl_float4; - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_UINT4__ 1 - #define __CL_INT4__ 1 - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef float __cl_float4 __attribute__((vector_size(16))); - #else - typedef __m128 __cl_float4; - #endif - #define __CL_FLOAT4__ 1 -#endif - -#if defined( __SSE2__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar16 __attribute__((vector_size(16))); - typedef cl_char __cl_char16 __attribute__((vector_size(16))); - typedef cl_ushort __cl_ushort8 __attribute__((vector_size(16))); - typedef cl_short __cl_short8 __attribute__((vector_size(16))); - typedef cl_uint __cl_uint4 __attribute__((vector_size(16))); - typedef cl_int __cl_int4 __attribute__((vector_size(16))); - typedef cl_ulong __cl_ulong2 __attribute__((vector_size(16))); - typedef cl_long __cl_long2 __attribute__((vector_size(16))); - typedef cl_double __cl_double2 __attribute__((vector_size(16))); - #else - typedef __m128i __cl_uchar16; - typedef __m128i __cl_char16; - typedef __m128i __cl_ushort8; - typedef __m128i __cl_short8; - typedef __m128i __cl_uint4; - typedef __m128i __cl_int4; - typedef __m128i __cl_ulong2; - typedef __m128i __cl_long2; - typedef __m128d __cl_double2; - #endif - #define __CL_UCHAR16__ 1 - #define __CL_CHAR16__ 1 - #define __CL_USHORT8__ 1 - #define __CL_SHORT8__ 1 - #define __CL_INT4__ 1 - #define __CL_UINT4__ 1 - #define __CL_ULONG2__ 1 - #define __CL_LONG2__ 1 - #define __CL_DOUBLE2__ 1 -#endif - -#if defined( __MMX__ ) - #include - #if defined( __GNUC__ ) - typedef cl_uchar __cl_uchar8 __attribute__((vector_size(8))); - typedef cl_char __cl_char8 __attribute__((vector_size(8))); - typedef cl_ushort __cl_ushort4 __attribute__((vector_size(8))); - typedef cl_short __cl_short4 __attribute__((vector_size(8))); - typedef cl_uint __cl_uint2 __attribute__((vector_size(8))); - typedef cl_int __cl_int2 __attribute__((vector_size(8))); - typedef cl_ulong __cl_ulong1 __attribute__((vector_size(8))); - typedef cl_long __cl_long1 __attribute__((vector_size(8))); - typedef cl_float __cl_float2 __attribute__((vector_size(8))); - #else - typedef __m64 __cl_uchar8; - typedef __m64 __cl_char8; - typedef __m64 __cl_ushort4; - typedef __m64 __cl_short4; - typedef __m64 __cl_uint2; - typedef __m64 __cl_int2; - typedef __m64 __cl_ulong1; - typedef __m64 __cl_long1; - typedef __m64 __cl_float2; - #endif - #define __CL_UCHAR8__ 1 - #define __CL_CHAR8__ 1 - #define __CL_USHORT4__ 1 - #define __CL_SHORT4__ 1 - #define __CL_INT2__ 1 - #define __CL_UINT2__ 1 - #define __CL_ULONG1__ 1 - #define __CL_LONG1__ 1 - #define __CL_FLOAT2__ 1 -#endif - -#if defined( __AVX__ ) - #if defined( __MINGW64__ ) - #include - #else - #include - #endif - #if defined( __GNUC__ ) - typedef cl_float __cl_float8 __attribute__((vector_size(32))); - typedef cl_double __cl_double4 __attribute__((vector_size(32))); - #else - typedef __m256 __cl_float8; - typedef __m256d __cl_double4; - #endif - #define __CL_FLOAT8__ 1 - #define __CL_DOUBLE4__ 1 -#endif - -/* Define capabilities for anonymous struct members. */ -#if defined( __GNUC__) && ! defined( __STRICT_ANSI__ ) -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ __extension__ -#elif defined( _WIN32) && (_MSC_VER >= 1500) - /* Microsoft Developer Studio 2008 supports anonymous structs, but - * complains by default. */ -#define __CL_HAS_ANON_STRUCT__ 1 -#define __CL_ANON_STRUCT__ - /* Disable warning C4201: nonstandard extension used : nameless - * struct/union */ -#pragma warning( push ) -#pragma warning( disable : 4201 ) -#else -#define __CL_HAS_ANON_STRUCT__ 0 -#define __CL_ANON_STRUCT__ -#endif - -/* Define alignment keys */ -#if defined( __GNUC__ ) - #define CL_ALIGNED(_x) __attribute__ ((aligned(_x))) -#elif defined( _WIN32) && (_MSC_VER) - /* Alignment keys neutered on windows because MSVC can't swallow function arguments with alignment requirements */ - /* http://msdn.microsoft.com/en-us/library/373ak2y1%28VS.71%29.aspx */ - /* #include */ - /* #define CL_ALIGNED(_x) _CRT_ALIGN(_x) */ - #define CL_ALIGNED(_x) -#else - #warning Need to implement some method to align data here - #define CL_ALIGNED(_x) -#endif - -/* Indicate whether .xyzw, .s0123 and .hi.lo are supported */ -#if __CL_HAS_ANON_STRUCT__ - /* .xyzw and .s0123...{f|F} are supported */ - #define CL_HAS_NAMED_VECTOR_FIELDS 1 - /* .hi and .lo are supported */ - #define CL_HAS_HI_LO_VECTOR_FIELDS 1 -#endif - -/* Define cl_vector types */ - -/* ---- cl_charn ---- */ -typedef union -{ - cl_char CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_char lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2; -#endif -}cl_char2; - -typedef union -{ - cl_char CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_char2 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[2]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4; -#endif -}cl_char4; - -/* cl_char3 is identical in size, alignment and behavior to cl_char4. See section 6.1.5. */ -typedef cl_char4 cl_char3; - -typedef union -{ - cl_char CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_char4 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[4]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[2]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8; -#endif -}cl_char8; - -typedef union -{ - cl_char CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_char x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_char s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_char8 lo, hi; }; -#endif -#if defined( __CL_CHAR2__) - __cl_char2 v2[8]; -#endif -#if defined( __CL_CHAR4__) - __cl_char4 v4[4]; -#endif -#if defined( __CL_CHAR8__ ) - __cl_char8 v8[2]; -#endif -#if defined( __CL_CHAR16__ ) - __cl_char16 v16; -#endif -}cl_char16; - - -/* ---- cl_ucharn ---- */ -typedef union -{ - cl_uchar CL_ALIGNED(2) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uchar lo, hi; }; -#endif -#if defined( __cl_uchar2__) - __cl_uchar2 v2; -#endif -}cl_uchar2; - -typedef union -{ - cl_uchar CL_ALIGNED(4) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uchar2 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[2]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4; -#endif -}cl_uchar4; - -/* cl_uchar3 is identical in size, alignment and behavior to cl_uchar4. See section 6.1.5. */ -typedef cl_uchar4 cl_uchar3; - -typedef union -{ - cl_uchar CL_ALIGNED(8) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uchar4 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[4]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[2]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8; -#endif -}cl_uchar8; - -typedef union -{ - cl_uchar CL_ALIGNED(16) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uchar x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uchar s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uchar8 lo, hi; }; -#endif -#if defined( __CL_UCHAR2__) - __cl_uchar2 v2[8]; -#endif -#if defined( __CL_UCHAR4__) - __cl_uchar4 v4[4]; -#endif -#if defined( __CL_UCHAR8__ ) - __cl_uchar8 v8[2]; -#endif -#if defined( __CL_UCHAR16__ ) - __cl_uchar16 v16; -#endif -}cl_uchar16; - - -/* ---- cl_shortn ---- */ -typedef union -{ - cl_short CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_short lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2; -#endif -}cl_short2; - -typedef union -{ - cl_short CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_short2 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[2]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4; -#endif -}cl_short4; - -/* cl_short3 is identical in size, alignment and behavior to cl_short4. See section 6.1.5. */ -typedef cl_short4 cl_short3; - -typedef union -{ - cl_short CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_short4 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[4]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[2]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8; -#endif -}cl_short8; - -typedef union -{ - cl_short CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_short x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_short s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_short8 lo, hi; }; -#endif -#if defined( __CL_SHORT2__) - __cl_short2 v2[8]; -#endif -#if defined( __CL_SHORT4__) - __cl_short4 v4[4]; -#endif -#if defined( __CL_SHORT8__ ) - __cl_short8 v8[2]; -#endif -#if defined( __CL_SHORT16__ ) - __cl_short16 v16; -#endif -}cl_short16; - - -/* ---- cl_ushortn ---- */ -typedef union -{ - cl_ushort CL_ALIGNED(4) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ushort lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2; -#endif -}cl_ushort2; - -typedef union -{ - cl_ushort CL_ALIGNED(8) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ushort2 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[2]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4; -#endif -}cl_ushort4; - -/* cl_ushort3 is identical in size, alignment and behavior to cl_ushort4. See section 6.1.5. */ -typedef cl_ushort4 cl_ushort3; - -typedef union -{ - cl_ushort CL_ALIGNED(16) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ushort4 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[4]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[2]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8; -#endif -}cl_ushort8; - -typedef union -{ - cl_ushort CL_ALIGNED(32) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ushort x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ushort s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ushort8 lo, hi; }; -#endif -#if defined( __CL_USHORT2__) - __cl_ushort2 v2[8]; -#endif -#if defined( __CL_USHORT4__) - __cl_ushort4 v4[4]; -#endif -#if defined( __CL_USHORT8__ ) - __cl_ushort8 v8[2]; -#endif -#if defined( __CL_USHORT16__ ) - __cl_ushort16 v16; -#endif -}cl_ushort16; - -/* ---- cl_intn ---- */ -typedef union -{ - cl_int CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_int lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2; -#endif -}cl_int2; - -typedef union -{ - cl_int CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_int2 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[2]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4; -#endif -}cl_int4; - -/* cl_int3 is identical in size, alignment and behavior to cl_int4. See section 6.1.5. */ -typedef cl_int4 cl_int3; - -typedef union -{ - cl_int CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_int4 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[4]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[2]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8; -#endif -}cl_int8; - -typedef union -{ - cl_int CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_int x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_int s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_int8 lo, hi; }; -#endif -#if defined( __CL_INT2__) - __cl_int2 v2[8]; -#endif -#if defined( __CL_INT4__) - __cl_int4 v4[4]; -#endif -#if defined( __CL_INT8__ ) - __cl_int8 v8[2]; -#endif -#if defined( __CL_INT16__ ) - __cl_int16 v16; -#endif -}cl_int16; - - -/* ---- cl_uintn ---- */ -typedef union -{ - cl_uint CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_uint lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2; -#endif -}cl_uint2; - -typedef union -{ - cl_uint CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_uint2 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[2]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4; -#endif -}cl_uint4; - -/* cl_uint3 is identical in size, alignment and behavior to cl_uint4. See section 6.1.5. */ -typedef cl_uint4 cl_uint3; - -typedef union -{ - cl_uint CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_uint4 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[4]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[2]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8; -#endif -}cl_uint8; - -typedef union -{ - cl_uint CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_uint x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_uint s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_uint8 lo, hi; }; -#endif -#if defined( __CL_UINT2__) - __cl_uint2 v2[8]; -#endif -#if defined( __CL_UINT4__) - __cl_uint4 v4[4]; -#endif -#if defined( __CL_UINT8__ ) - __cl_uint8 v8[2]; -#endif -#if defined( __CL_UINT16__ ) - __cl_uint16 v16; -#endif -}cl_uint16; - -/* ---- cl_longn ---- */ -typedef union -{ - cl_long CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_long lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2; -#endif -}cl_long2; - -typedef union -{ - cl_long CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_long2 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[2]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4; -#endif -}cl_long4; - -/* cl_long3 is identical in size, alignment and behavior to cl_long4. See section 6.1.5. */ -typedef cl_long4 cl_long3; - -typedef union -{ - cl_long CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_long4 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[4]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[2]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8; -#endif -}cl_long8; - -typedef union -{ - cl_long CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_long x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_long s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_long8 lo, hi; }; -#endif -#if defined( __CL_LONG2__) - __cl_long2 v2[8]; -#endif -#if defined( __CL_LONG4__) - __cl_long4 v4[4]; -#endif -#if defined( __CL_LONG8__ ) - __cl_long8 v8[2]; -#endif -#if defined( __CL_LONG16__ ) - __cl_long16 v16; -#endif -}cl_long16; - - -/* ---- cl_ulongn ---- */ -typedef union -{ - cl_ulong CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_ulong lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2; -#endif -}cl_ulong2; - -typedef union -{ - cl_ulong CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_ulong2 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[2]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4; -#endif -}cl_ulong4; - -/* cl_ulong3 is identical in size, alignment and behavior to cl_ulong4. See section 6.1.5. */ -typedef cl_ulong4 cl_ulong3; - -typedef union -{ - cl_ulong CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_ulong4 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[4]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[2]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8; -#endif -}cl_ulong8; - -typedef union -{ - cl_ulong CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_ulong x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_ulong s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_ulong8 lo, hi; }; -#endif -#if defined( __CL_ULONG2__) - __cl_ulong2 v2[8]; -#endif -#if defined( __CL_ULONG4__) - __cl_ulong4 v4[4]; -#endif -#if defined( __CL_ULONG8__ ) - __cl_ulong8 v8[2]; -#endif -#if defined( __CL_ULONG16__ ) - __cl_ulong16 v16; -#endif -}cl_ulong16; - - -/* --- cl_floatn ---- */ - -typedef union -{ - cl_float CL_ALIGNED(8) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_float lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2; -#endif -}cl_float2; - -typedef union -{ - cl_float CL_ALIGNED(16) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_float2 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[2]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4; -#endif -}cl_float4; - -/* cl_float3 is identical in size, alignment and behavior to cl_float4. See section 6.1.5. */ -typedef cl_float4 cl_float3; - -typedef union -{ - cl_float CL_ALIGNED(32) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_float4 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[4]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[2]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8; -#endif -}cl_float8; - -typedef union -{ - cl_float CL_ALIGNED(64) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_float x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_float s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_float8 lo, hi; }; -#endif -#if defined( __CL_FLOAT2__) - __cl_float2 v2[8]; -#endif -#if defined( __CL_FLOAT4__) - __cl_float4 v4[4]; -#endif -#if defined( __CL_FLOAT8__ ) - __cl_float8 v8[2]; -#endif -#if defined( __CL_FLOAT16__ ) - __cl_float16 v16; -#endif -}cl_float16; - -/* --- cl_doublen ---- */ - -typedef union -{ - cl_double CL_ALIGNED(16) s[2]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1; }; - __CL_ANON_STRUCT__ struct{ cl_double lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2; -#endif -}cl_double2; - -typedef union -{ - cl_double CL_ALIGNED(32) s[4]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3; }; - __CL_ANON_STRUCT__ struct{ cl_double2 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[2]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4; -#endif -}cl_double4; - -/* cl_double3 is identical in size, alignment and behavior to cl_double4. See section 6.1.5. */ -typedef cl_double4 cl_double3; - -typedef union -{ - cl_double CL_ALIGNED(64) s[8]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7; }; - __CL_ANON_STRUCT__ struct{ cl_double4 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[4]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[2]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8; -#endif -}cl_double8; - -typedef union -{ - cl_double CL_ALIGNED(128) s[16]; -#if __CL_HAS_ANON_STRUCT__ - __CL_ANON_STRUCT__ struct{ cl_double x, y, z, w, __spacer4, __spacer5, __spacer6, __spacer7, __spacer8, __spacer9, sa, sb, sc, sd, se, sf; }; - __CL_ANON_STRUCT__ struct{ cl_double s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, sA, sB, sC, sD, sE, sF; }; - __CL_ANON_STRUCT__ struct{ cl_double8 lo, hi; }; -#endif -#if defined( __CL_DOUBLE2__) - __cl_double2 v2[8]; -#endif -#if defined( __CL_DOUBLE4__) - __cl_double4 v4[4]; -#endif -#if defined( __CL_DOUBLE8__ ) - __cl_double8 v8[2]; -#endif -#if defined( __CL_DOUBLE16__ ) - __cl_double16 v16; -#endif -}cl_double16; - -/* Macro to facilitate debugging - * Usage: - * Place CL_PROGRAM_STRING_DEBUG_INFO on the line before the first line of your source. - * The first line ends with: CL_PROGRAM_STRING_DEBUG_INFO \" - * Each line thereafter of OpenCL C source must end with: \n\ - * The last line ends in "; - * - * Example: - * - * const char *my_program = CL_PROGRAM_STRING_DEBUG_INFO "\ - * kernel void foo( int a, float * b ) \n\ - * { \n\ - * // my comment \n\ - * *b[ get_global_id(0)] = a; \n\ - * } \n\ - * "; - * - * This should correctly set up the line, (column) and file information for your source - * string so you can do source level debugging. - */ -#define __CL_STRINGIFY( _x ) # _x -#define _CL_STRINGIFY( _x ) __CL_STRINGIFY( _x ) -#define CL_PROGRAM_STRING_DEBUG_INFO "#line " _CL_STRINGIFY(__LINE__) " \"" __FILE__ "\" \n\n" - -#ifdef __cplusplus -} -#endif - -#undef __CL_HAS_ANON_STRUCT__ -#undef __CL_ANON_STRUCT__ -#if defined( _WIN32) && (_MSC_VER >= 1500) -#pragma warning( pop ) -#endif - -#endif /* __CL_PLATFORM_H */ diff --git a/third_party/opencl/include/CL/opencl.h b/third_party/opencl/include/CL/opencl.h deleted file mode 100644 index 9855cd75e7d..00000000000 --- a/third_party/opencl/include/CL/opencl.h +++ /dev/null @@ -1,59 +0,0 @@ -/******************************************************************************* - * Copyright (c) 2008-2015 The Khronos Group Inc. - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and/or associated documentation files (the - * "Materials"), to deal in the Materials without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Materials, and to - * permit persons to whom the Materials are furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Materials. - * - * MODIFICATIONS TO THIS FILE MAY MEAN IT NO LONGER ACCURATELY REFLECTS - * KHRONOS STANDARDS. THE UNMODIFIED, NORMATIVE VERSIONS OF KHRONOS - * SPECIFICATIONS AND HEADER INFORMATION ARE LOCATED AT - * https://www.khronos.org/registry/ - * - * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. - ******************************************************************************/ - -/* $Revision: 11708 $ on $Date: 2010-06-13 23:36:24 -0700 (Sun, 13 Jun 2010) $ */ - -#ifndef __OPENCL_H -#define __OPENCL_H - -#ifdef __cplusplus -extern "C" { -#endif - -#ifdef __APPLE__ - -#include -#include -#include -#include - -#else - -#include -#include -#include -#include - -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* __OPENCL_H */ - diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d4cfc672801..b79d046fcaf 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -57,11 +57,9 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": - base_frameworks.append('OpenCL') base_frameworks.append('QtCharts') base_frameworks.append('QtSerialBus') else: - base_libs.append('OpenCL') base_libs.append('Qt5Charts') base_libs.append('Qt5SerialBus') diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 5c2131d4bf8..f428e9972a8 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -58,9 +58,6 @@ function install_ubuntu_common_requirements() { libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ - opencl-headers \ - ocl-icd-libopencl1 \ - ocl-icd-opencl-dev \ portaudio19-dev \ qttools5-dev-tools \ libqt5svg5-dev \ diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 136c4119f64..698ab9885d9 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -6,11 +6,6 @@ replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] -if arch == "Darwin": - base_frameworks.append('OpenCL') -else: - base_libs.append('OpenCL') - replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] if arch != "Darwin": diff --git a/tools/webcam/README.md b/tools/webcam/README.md index 67ad2cc8cbc..6abbc47935e 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -10,10 +10,6 @@ What's needed: ## Setup openpilot - Follow [this readme](../README.md) to install and build the requirements -- Install OpenCL Driver (Ubuntu) -``` -sudo apt install pocl-opencl-icd -``` ## Connect the hardware - Connect the camera first From 7665045fc6737ed37e7a18528fedf8fda7eb0d03 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Thu, 12 Feb 2026 12:46:34 -0600 Subject: [PATCH 180/685] ui replay: fix coverage reporting to include imports (#37180) Fix coverage reporting by adjusting MiciMainLayout import --- selfdrive/ui/tests/diff/replay.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 9da157660e6..fdba5c3502b 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -16,7 +16,6 @@ from openpilot.system.version import terms_version, training_version from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout FPS = 60 HEADLESS = os.getenv("WINDOWED", "0") == "1" @@ -85,6 +84,9 @@ def run_replay(): if not HEADLESS: rl.set_config_flags(rl.FLAG_WINDOW_HIDDEN) gui_app.init_window("ui diff test", fps=FPS) + + from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout # import here for coverage + main_layout = MiciMainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) @@ -117,7 +119,6 @@ def main(): cov = coverage.coverage(source=['openpilot.selfdrive.ui.mici']) with cov.collect(): run_replay() - cov.stop() cov.save() cov.report() cov.html_report(directory=os.path.join(DIFF_OUT_DIR, 'htmlcov')) From 2b7f91d151ce1cd86b3c46e44adde022d43affa0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Feb 2026 13:40:00 -0800 Subject: [PATCH 181/685] WifiManager: update networks on active (#37186) * immediately * only if active --- system/ui/lib/wifi_manager.py | 78 ++++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 3d4da444bed..86fcdb79fb2 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -232,6 +232,10 @@ def process_callbacks(self): def set_active(self, active: bool): self._active = active + # Update networks immediately when activating for UI + if active: + self._update_networks(block=False) + def _monitor_state(self): # Filter for signals rules = ( @@ -607,53 +611,59 @@ def _request_scan(self): if reply.header.message_type == MessageType.error: cloudlog.warning(f"Failed to request scan: {reply}") - def _update_networks(self): + def _update_networks(self, block: bool = True): if not self._active: return - with self._lock: - if self._wifi_device is None: - cloudlog.warning("No WiFi device found") - return - - # NOTE: AccessPoints property may exclude hidden APs (use GetAllAccessPoints method if needed) - wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) - wifi_props = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()).body[0] - active_ap_path = wifi_props.get('ActiveAccessPoint', ('o', '/'))[1] - ap_paths = wifi_props.get('AccessPoints', ('ao', []))[1] + def worker(): + with self._lock: + if self._wifi_device is None: + cloudlog.warning("No WiFi device found") + return - aps: dict[str, list[AccessPoint]] = {} + # NOTE: AccessPoints property may exclude hidden APs (use GetAllAccessPoints method if needed) + wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) + wifi_props = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()).body[0] + active_ap_path = wifi_props.get('ActiveAccessPoint', ('o', '/'))[1] + ap_paths = wifi_props.get('AccessPoints', ('ao', []))[1] - for ap_path in ap_paths: - ap_addr = DBusAddress(ap_path, NM, interface=NM_ACCESS_POINT_IFACE) - ap_props = self._router_main.send_and_get_reply(Properties(ap_addr).get_all()) + aps: dict[str, list[AccessPoint]] = {} - # some APs have been seen dropping off during iteration - if ap_props.header.message_type == MessageType.error: - cloudlog.warning(f"Failed to get AP properties for {ap_path}") - continue + for ap_path in ap_paths: + ap_addr = DBusAddress(ap_path, NM, interface=NM_ACCESS_POINT_IFACE) + ap_props = self._router_main.send_and_get_reply(Properties(ap_addr).get_all()) - try: - ap = AccessPoint.from_dbus(ap_props.body[0], ap_path, active_ap_path) - if ap.ssid == "": + # some APs have been seen dropping off during iteration + if ap_props.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to get AP properties for {ap_path}") continue - if ap.ssid not in aps: - aps[ap.ssid] = [] + try: + ap = AccessPoint.from_dbus(ap_props.body[0], ap_path, active_ap_path) + if ap.ssid == "": + continue + + if ap.ssid not in aps: + aps[ap.ssid] = [] - aps[ap.ssid].append(ap) - except Exception: - # catch all for parsing errors - cloudlog.exception(f"Failed to parse AP properties for {ap_path}") + aps[ap.ssid].append(ap) + except Exception: + # catch all for parsing errors + cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections) for ssid, ap_list in aps.items()] - # sort with quantized strength to reduce jumping - networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) - self._networks = networks + networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections) for ssid, ap_list in aps.items()] + # sort with quantized strength to reduce jumping + networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) + self._networks = networks - self._update_active_connection_info() + self._update_active_connection_info() - self._enqueue_callbacks(self._networks_updated, self._networks) + self._enqueue_callbacks(self._networks_updated, self._networks) + + if block: + worker() + else: + threading.Thread(target=worker, daemon=True).start() def _update_active_connection_info(self): self._ipv4_address = "" From 2e9b980fc216d434790004a9c832794d928eef59 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Feb 2026 13:48:55 -0800 Subject: [PATCH 182/685] remove lang_button --- selfdrive/ui/mici/layouts/settings/device.py | 1 - 1 file changed, 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index a47231340aa..13462674658 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -322,7 +322,6 @@ def uninstall_openpilot_callback(): PairBigButton(), review_training_guide_btn, driver_cam_btn, - # lang_button, reset_calibration_btn, uninstall_openpilot_btn, regulatory_btn, From 1257d31a5602a6edd6c6cd82e05ceee705272720 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Feb 2026 15:00:50 -0800 Subject: [PATCH 183/685] WifiManager: dbus debug flag (#37188) * add dbus debug wrapper * no --- system/ui/lib/wifi_manager.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 86fcdb79fb2..b2f0774228f 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -37,6 +37,23 @@ SIGNAL_QUEUE_SIZE = 10 SCAN_PERIOD_SECONDS = 5 +DEBUG = False +_dbus_call_idx = 0 + + +def _wrap_router(router): + def _wrap(orig): + def wrapper(msg, **kw): + global _dbus_call_idx + _dbus_call_idx += 1 + if DEBUG: + h = msg.header.fields + print(f"[DBUS #{_dbus_call_idx}] {h.get(6, '?')} {h.get(3, '?')} {msg.body}") + return orig(msg, **kw) + return wrapper + router.send_and_get_reply = _wrap(router.send_and_get_reply) + router.send = _wrap(router.send) + class SecurityType(IntEnum): OPEN = 0 @@ -134,6 +151,7 @@ def __init__(self): # DBus connections try: self._router_main = DBusRouter(open_dbus_connection_threading(bus="SYSTEM")) # used by scanner / general method calls + _wrap_router(self._router_main) self._conn_monitor = open_dbus_connection_blocking(bus="SYSTEM") # used by state monitor thread self._nm = DBusAddress(NM_PATH, bus_name=NM, interface=NM_IFACE) except FileNotFoundError: From eb5cd542d9dbe2031e70142b94025a4bcfb00a91 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Feb 2026 16:23:58 -0800 Subject: [PATCH 184/685] WifiUi: add new networks to end, delete buttons on exit (#37189) * add networks to end, remove bad scroller restore logic that sometimes starts in the middle * works * almost * wifi slash * clean up * clean up * opactiy * more clean up * more clean up * set enabled and network missing on regain network * cmt --- .../mici/layouts/settings/network/wifi_ui.py | 79 ++++++++++--------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 955dc4bfafe..2b5c6be6e1f 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -36,20 +36,29 @@ def __init__(self): super().__init__() self.set_rect(rl.Rectangle(0, 0, 86, 64)) + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 86, 64) self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 86, 64) self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 86, 64) self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 86, 64) self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 22, 32) self._network: Network | None = None + self._network_missing = False # if network disappeared from scan results self._scale = 1.0 + self._opacity = 1.0 def set_current_network(self, network: Network): self._network = network + def set_network_missing(self, missing: bool): + self._network_missing = missing + def set_scale(self, scale: float): self._scale = scale + def set_opacity(self, opacity: float): + self._opacity = opacity + @staticmethod def get_strength_icon_idx(strength: int) -> int: return round(strength / 100 * 2) @@ -60,23 +69,26 @@ def _render(self, _): # Determine which wifi strength icon to use strength = self.get_strength_icon_idx(self._network.strength) - if strength == 2: + if self._network_missing: + strength_icon = self._wifi_slash_txt + elif strength == 2: strength_icon = self._wifi_full_txt elif strength == 1: strength_icon = self._wifi_medium_txt else: strength_icon = self._wifi_low_txt + tint = rl.Color(255, 255, 255, int(255 * self._opacity)) icon_x = int(self._rect.x + (self._rect.width - strength_icon.width * self._scale) // 2) icon_y = int(self._rect.y + (self._rect.height - strength_icon.height * self._scale) // 2) - rl.draw_texture_ex(strength_icon, (icon_x, icon_y), 0.0, self._scale, rl.WHITE) + rl.draw_texture_ex(strength_icon, (icon_x, icon_y), 0.0, self._scale, tint) # Render lock icon at lower right of wifi icon if secured if self._network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED): lock_scale = self._scale * 1.1 lock_x = int(icon_x + 1 + strength_icon.width * self._scale - self._lock_txt.width * lock_scale / 2) lock_y = int(icon_y + 1 + strength_icon.height * self._scale - self._lock_txt.height * lock_scale / 2) - rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, lock_scale, rl.WHITE) + rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, lock_scale, tint) class WifiItem(BigDialogOptionButton): @@ -93,16 +105,26 @@ def __init__(self, network: Network): self._wifi_icon = WifiIcon() self._wifi_icon.set_current_network(network) + def set_network_missing(self, missing: bool): + self._wifi_icon.set_network_missing(missing) + def set_current_network(self, network: Network): self._network = network self._wifi_icon.set_current_network(network) + # reset if we see the network again + self.set_enabled(True) + self.set_network_missing(False) + def _render(self, _): + disabled_alpha = 0.35 if not self.enabled else 1.0 + if self._network.is_connected: selected_x = int(self._rect.x - self._selected_txt.width / 2) selected_y = int(self._rect.y + (self._rect.height - self._selected_txt.height) / 2) rl.draw_texture(self._selected_txt, selected_x, selected_y, rl.WHITE) + self._wifi_icon.set_opacity(disabled_alpha) self._wifi_icon.set_scale((1.0 if self._selected else 0.65) * 0.7) self._wifi_icon.render(rl.Rectangle( self._rect.x + self.LEFT_MARGIN, @@ -113,11 +135,11 @@ def _render(self, _): if self._selected: self._label.set_font_size(self.SELECTED_HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9 * disabled_alpha))) self._label.set_font_weight(FontWeight.DISPLAY) else: self._label.set_font_size(self.HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) + self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58 * disabled_alpha))) self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) label_offset = self.LEFT_MARGIN + self._wifi_icon.rect.width + 20 @@ -314,9 +336,6 @@ def _render(self, _): class WifiUIMici(BigMultiOptionDialog): - # Wait this long after user interacts with widget to update network list - INACTIVITY_TIMEOUT = 1 - def __init__(self, wifi_manager: WifiManager, back_callback: Callable): super().__init__([], None) @@ -332,10 +351,6 @@ def __init__(self, wifi_manager: WifiManager, back_callback: Callable): self._connecting: str | None = None self._networks: dict[str, Network] = {} - # widget state - self._last_interaction_time = -float('inf') - self._restore_selection = False - self._wifi_manager.add_callbacks( need_auth=self._on_need_auth, activated=self._on_activated, @@ -348,11 +363,12 @@ def show_event(self): # Call super to prepare scroller; selection scroll is handled dynamically super().show_event() self._wifi_manager.set_active(True) - self._last_interaction_time = -float('inf') def hide_event(self): super().hide_event() self._wifi_manager.set_active(False) + # clear scroller items to remove old networks on next show + self._scroller._items.clear() def _open_network_manage_page(self, result=None): self._network_info_page.update_networks(self._networks) @@ -372,27 +388,28 @@ def _on_network_updated(self, networks: list[Network]): self._network_info_page.update_networks(self._networks) def _update_buttons(self): - # Don't update buttons while user is actively interacting - if rl.get_time() - self._last_interaction_time < self.INACTIVITY_TIMEOUT: - return + # Only add new buttons to the end. Update existing buttons without re-sorting so user can freely scroll around for network in self._networks.values(): - # pop and re-insert to eliminate stuttering on update (prevents position lost for a frame) network_button_idx = next((i for i, btn in enumerate(self._scroller._items) if btn.option == network.ssid), None) if network_button_idx is not None: - network_button = self._scroller._items.pop(network_button_idx) # Update network on existing button - network_button.set_current_network(network) + self._scroller._items[network_button_idx].set_current_network(network) else: network_button = WifiItem(network) + self._scroller.add_widget(network_button) - self._scroller.add_widget(network_button) - - # remove networks no longer present - self._scroller._items[:] = [btn for btn in self._scroller._items if btn.option in self._networks] + # Move connected network to the start + connected_btn_idx = next((i for i, btn in enumerate(self._scroller._items) if btn._network.is_connected), None) + if connected_btn_idx is not None and connected_btn_idx > 0: + self._scroller._items.insert(0, self._scroller._items.pop(connected_btn_idx)) + self._scroller._layout() # fixes selected style single frame stutter - # try to restore previous selection to prevent jumping from adding/removing/reordering buttons - self._restore_selection = True + # Disable networks no longer present + for btn in self._scroller._items: + if btn.option not in self._networks: + btn.set_enabled(False) + btn.set_network_missing(True) def _connect_with_password(self, ssid: str, password: str): if password: @@ -440,19 +457,7 @@ def _on_forgotten(self): def _on_disconnected(self): self._connecting = None - def _update_state(self): - super()._update_state() - if self.is_pressed: - self._last_interaction_time = rl.get_time() - def _render(self, _): - # Update Scroller layout and restore current selection whenever buttons are updated, before first render - current_selection = self.get_selected_option() - if self._restore_selection and current_selection in self._networks: - self._scroller._layout() - BigMultiOptionDialog._on_option_selected(self, current_selection) - self._restore_selection = None - super()._render(_) if not self._networks: From f142f1cd706c454770cab7db73686a8072cc179c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Feb 2026 16:24:39 -0800 Subject: [PATCH 185/685] scroller: move scissor to render --- system/ui/widgets/scroller.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 03c49fca659..43539d128ba 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -223,9 +223,6 @@ def _layout(self): self._scroll_offset = self._get_scroll(self._visible_items, self._content_size) - rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), - int(self._rect.width), int(self._rect.height)) - self._item_pos_filter.update(self._scroll_offset) cur_pos = 0 @@ -267,6 +264,9 @@ def _layout(self): item.set_parent_rect(self._rect) def _render(self, _): + rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), + int(self._rect.width), int(self._rect.height)) + for item in self._visible_items: # Skip rendering if not in viewport if not rl.check_collision_recs(item.rect, self._rect): From 0fa8e01d1ffbe31a6a90426b3cb2dc3c27e2390b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Feb 2026 18:39:08 -0800 Subject: [PATCH 186/685] Wifi ui: render scroller gradient under (#37193) * gradient under * blend mode works * Revert "blend mode works" This reverts commit 092924fbd6dc40cbb937cac8578257ba5a28a7ef. * everywehre * everywehre --- selfdrive/assets/icons_mici/buttons/button_rectangle.png | 4 ++-- .../assets/icons_mici/buttons/button_rectangle_disabled.png | 4 ++-- .../assets/icons_mici/buttons/button_rectangle_hover.png | 3 --- .../assets/icons_mici/buttons/button_rectangle_pressed.png | 4 ++-- selfdrive/ui/mici/widgets/button.py | 5 ++--- 5 files changed, 8 insertions(+), 12 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle.png b/selfdrive/assets/icons_mici/buttons/button_rectangle.png index 230c537d6dc..4ccf6995b19 100644 --- a/selfdrive/assets/icons_mici/buttons/button_rectangle.png +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffb293236f5f8f7da44b5a3c4c0b72e86c4e1fdb04f89c94507af008ff7de139 -size 8210 +oid sha256:5dedb4139a7ddeafcdaf050144769e490643820db726201a15250e1042eb6d15 +size 7982 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png b/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png index 76e75d5421e..5e891588f5f 100644 --- a/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle_disabled.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bda53863c9a46c50a1e2920a76c2d2f1fe4df8a94b8d2e26f5d83eef3a9c3bd3 -size 3627 +oid sha256:d527dcff61fa66902681706b4916586244b8cf0520086ac980ff782ab2d99ce7 +size 4778 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png b/selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png deleted file mode 100644 index a9fd28cc35e..00000000000 --- a/selfdrive/assets/icons_mici/buttons/button_rectangle_hover.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6b55e43c50e805ac5e8357e5943374ed02d756cefa3aaffb58c568a0b125c30b -size 7750 diff --git a/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png b/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png index 779c219fcbd..2cbf1cedb87 100644 --- a/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png +++ b/selfdrive/assets/icons_mici/buttons/button_rectangle_pressed.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5528e9c041b824f005bf1ef6e49b2dbbc4ba10f994b0726d2a17a4fbf8c80f55 -size 21379 +oid sha256:c6b1b0f1270a596b5ac150dee8ade54794de55b2033a529d4a17176f688aa6f0 +size 56738 diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 29844ccda10..36b1922385b 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -143,7 +143,6 @@ def _load_images(self): self._txt_default_bg = gui_app.texture("icons_mici/buttons/button_rectangle.png", 402, 180) self._txt_pressed_bg = gui_app.texture("icons_mici/buttons/button_rectangle_pressed.png", 402, 180) self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180) - self._txt_hover_bg = gui_app.texture("icons_mici/buttons/button_rectangle_hover.png", 402, 180) def _width_hint(self) -> int: # Single line if scrolling, so hide behind icon if exists @@ -215,14 +214,14 @@ def _render(self, _): if not self.enabled: txt_bg = self._txt_disabled_bg elif self.is_pressed: - txt_bg = self._txt_hover_bg + txt_bg = self._txt_pressed_bg scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 - rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) self._draw_content(btn_y) + rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) class BigToggle(BigButton): From 98bc70344f0b2a286f001dd1a0771620af6577e4 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Thu, 12 Feb 2026 21:19:25 -0600 Subject: [PATCH 187/685] fix: use correct display ID for WSL2 when setting up Xvfb (#36697) use correct display ID for wsl --- selfdrive/test/setup_xvfb.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/test/setup_xvfb.sh b/selfdrive/test/setup_xvfb.sh index 692b84d65f0..b185e2b431d 100755 --- a/selfdrive/test/setup_xvfb.sh +++ b/selfdrive/test/setup_xvfb.sh @@ -2,7 +2,11 @@ # Sets up a virtual display for running map renderer and simulator without an X11 display -DISP_ID=99 +if uname -r | grep -q "WSL2"; then + DISP_ID=0 # WSLg uses display :0 +else + DISP_ID=99 # Standard Xvfb display +fi export DISPLAY=:$DISP_ID sudo Xvfb $DISPLAY -screen 0 2160x1080x24 2>/dev/null & From 132f10365a2a15e08fce7badb010af5cd67fdcce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Thu, 12 Feb 2026 19:52:22 -0800 Subject: [PATCH 188/685] relax dm timing tgwarp (#37191) --- selfdrive/test/process_replay/model_replay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 3f671f610d2..a6ccaa10477 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -35,7 +35,7 @@ EXEC_TIMINGS = [ # model, instant max, average max ("modelV2", 0.05, 0.028), - ("driverStateV2", 0.05, 0.015), + ("driverStateV2", 0.05, 0.016), ] def get_log_fn(test_route, ref="master"): From 2e21deeae83384e35cbacbf55e3624425dc5ddd6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Feb 2026 20:48:34 -0800 Subject: [PATCH 189/685] WifiUi: fix up wrong password dialog (#37195) * debug why so slow * forget after * i'm not sure why this is a thing * better forget connecting reset * ???? * has lag * fix * clean up * should be fine --- .../ui/mici/layouts/settings/network/wifi_ui.py | 15 +++++++++++---- system/ui/lib/wifi_manager.py | 10 +++++----- system/ui/widgets/network.py | 2 +- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 2b5c6be6e1f..5c9616524c1 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -445,14 +445,21 @@ def _on_need_auth(self, ssid, incorrect_password=True): hint = "wrong password..." if incorrect_password else "enter password..." dlg = BigInputDialog(hint, "", minimum_length=8, confirm_callback=lambda _password: self._connect_with_password(ssid, _password)) - # go back to the manage network page - gui_app.set_modal_overlay(dlg, self._open_network_manage_page) + + def on_close(result=None): + gui_app.set_modal_overlay_tick(None) + self._open_network_manage_page(result) + + # Process wifi callbacks while the keyboard is shown so forgotten clears connecting state + gui_app.set_modal_overlay_tick(self._wifi_manager.process_callbacks) + gui_app.set_modal_overlay(dlg, on_close) def _on_activated(self): self._connecting = None - def _on_forgotten(self): - self._connecting = None + def _on_forgotten(self, ssid): + if self._connecting == ssid: + self._connecting = None def _on_disconnected(self): self._connecting = None diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index b2f0774228f..72373ecbea1 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -183,7 +183,7 @@ def __init__(self): # Callbacks self._need_auth: list[Callable[[str], None]] = [] self._activated: list[Callable[[], None]] = [] - self._forgotten: list[Callable[[], None]] = [] + self._forgotten: list[Callable[[str], None]] = [] self._networks_updated: list[Callable[[list[Network]], None]] = [] self._disconnected: list[Callable[[], None]] = [] @@ -211,7 +211,7 @@ def worker(): def add_callbacks(self, need_auth: Callable[[str], None] | None = None, activated: Callable[[], None] | None = None, - forgotten: Callable[[], None] | None = None, + forgotten: Callable[[str], None] | None = None, networks_updated: Callable[[list[Network]], None] | None = None, disconnected: Callable[[], None] | None = None): if need_auth is not None: @@ -316,8 +316,8 @@ def _monitor_state(self): # BAD PASSWORD if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid): - self.forget_connection(self._connecting_to_ssid, block=True) self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid) + self.forget_connection(self._connecting_to_ssid, block=True) self._connecting_to_ssid = "" elif new_state == NMDeviceState.ACTIVATED: @@ -327,8 +327,8 @@ def _monitor_state(self): self._connecting_to_ssid = "" elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: + self._enqueue_callbacks(self._forgotten, self._connecting_to_ssid) self._connecting_to_ssid = "" - self._enqueue_callbacks(self._forgotten) def _network_scanner(self): while not self._exit: @@ -484,7 +484,7 @@ def worker(): if len(self._forgotten): self._update_networks() - self._enqueue_callbacks(self._forgotten) + self._enqueue_callbacks(self._forgotten, ssid) if block: worker() diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 8f5168958f0..9de44c585c6 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -463,7 +463,7 @@ def _on_activated(self): if self.state == UIState.CONNECTING: self.state = UIState.IDLE - def _on_forgotten(self): + def _on_forgotten(self, _): if self.state == UIState.FORGETTING: self.state = UIState.IDLE From a61badb564bba4f7524e6004e344cb33ba80877c Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Thu, 12 Feb 2026 20:59:14 -0800 Subject: [PATCH 190/685] test_following_distance: bump error margin when initial speed is 0 (#37196) --- selfdrive/controls/tests/test_following_distance.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index 8f66d89bf87..ad1ff1a189b 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -42,4 +42,5 @@ def test_following_distance(self): simulation_steady_state = run_following_distance_simulation(v_lead, e2e=self.e2e, personality=self.personality) correct_steady_state = desired_follow_distance(v_lead, v_lead, get_T_FOLLOW(self.personality)) err_ratio = 0.2 if self.e2e else 0.1 - assert simulation_steady_state == pytest.approx(correct_steady_state, abs=err_ratio * correct_steady_state + .5) + abs_err_margin = 0.5 if v_lead > 0.0 else 1.15 + assert simulation_steady_state == pytest.approx(correct_steady_state, abs=err_ratio * correct_steady_state + abs_err_margin) From 9b7bf4a101560b57da23c4000b9e7e89e0f4e222 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Fri, 13 Feb 2026 11:26:14 -0600 Subject: [PATCH 191/685] mici ui replay: fix indeterminism with swiping and animations (#37110) * fix: get_time and get_frame_time determinism * remove some hackiness * don't need that --- selfdrive/ui/tests/diff/replay.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index fdba5c3502b..1efc6dde922 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -90,8 +90,11 @@ def run_replay(): main_layout = MiciMainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - frame = 0 script_index = 0 + frame = 0 + # Override raylib timing functions to return deterministic values based on frame count instead of real time + rl.get_frame_time = lambda: 1.0 / FPS + rl.get_time = lambda: frame / FPS for should_render in gui_app.render(): while script_index < len(SCRIPT) and SCRIPT[script_index][0] == frame: From 2ba6df2506a3b3e664131f67513b5bcd0c08075d Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Fri, 13 Feb 2026 10:14:24 -0800 Subject: [PATCH 192/685] chunk tinygrad pkl below GitHub max size - NoCache and AlwaysBuild (#37194) * nocache * + * fixes * lint * not split * use pathlib * cleanup * better * even better --- .gitignore | 1 + release/build_release.sh | 3 +-- selfdrive/modeld/SConscript | 23 +++++++++++++--- selfdrive/modeld/dmonitoringmodeld.py | 4 +-- selfdrive/modeld/external_pickle.py | 38 +++++++++++++++++++++++++++ selfdrive/modeld/modeld.py | 8 +++--- 6 files changed, 65 insertions(+), 12 deletions(-) create mode 100755 selfdrive/modeld/external_pickle.py diff --git a/.gitignore b/.gitignore index 1fef0a12552..af18f06628a 100644 --- a/.gitignore +++ b/.gitignore @@ -65,6 +65,7 @@ cppcheck_report.txt comma*.sh selfdrive/modeld/models/*.pkl +selfdrive/modeld/models/*.pkl.* # openpilot log files *.bz2 diff --git a/release/build_release.sh b/release/build_release.sh index 220da05c17d..7bc6732c685 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -72,8 +72,7 @@ find . -name '*.pyc' -delete find . -name 'moc_*' -delete find . -name '__pycache__' -delete rm -rf .sconsign.dblite Jenkinsfile release/ -rm selfdrive/modeld/models/driving_vision.onnx -rm selfdrive/modeld/models/driving_policy.onnx +rm -f selfdrive/modeld/models/*.onnx find third_party/ -name '*x86*' -exec rm -r {} + find third_party/ -name '*Darwin*' -exec rm -r {} + diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index b13a84f70a4..1808cfec2f5 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -3,6 +3,7 @@ import glob Import('env', 'arch') lenv = env.Clone() +CHUNK_BYTES = int(os.environ.get("TG_CHUNK_BYTES", str(45 * 1024 * 1024))) tinygrad_root = env.Dir("#").abspath tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) @@ -36,12 +37,28 @@ lenv.Command(warp_targets, tinygrad_files + script_files, cmd) def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath - return lenv.Command( - fn + "_tinygrad.pkl", + + out = fn + "_tinygrad.pkl" + full = out + ".full" + parts = out + ".parts" + + full_node = lenv.Command( + full, [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {fn}_tinygrad.pkl' + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {full}' ) + split_script = File(Dir("#selfdrive/modeld").File("external_pickle.py").abspath) + parts_node = lenv.Command( + parts, + [full_node, split_script, Value(str(CHUNK_BYTES))], + [f'python3 {split_script.abspath} {full} {out} {CHUNK_BYTES}', Delete(full)], + ) + + lenv.NoCache(parts_node) + lenv.AlwaysBuild(parts_node) + return parts_node + # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: tg_compile(tg_flags, model_name) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 94871c19906..956ea8a6a26 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -17,6 +17,7 @@ from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.system.camerad.cameras.nv12_info import get_nv12_info from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp +from openpilot.selfdrive.modeld.external_pickle import load_external_pickle PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') @@ -44,8 +45,7 @@ def __init__(self): self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self._blob_cache : dict[int, Tensor] = {} self.image_warp = None - with open(MODEL_PKL_PATH, "rb") as f: - self.model_run = pickle.load(f) + self.model_run = load_external_pickle(MODEL_PKL_PATH) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib diff --git a/selfdrive/modeld/external_pickle.py b/selfdrive/modeld/external_pickle.py new file mode 100755 index 00000000000..d60a9632a64 --- /dev/null +++ b/selfdrive/modeld/external_pickle.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import hashlib +import pickle +import sys +from pathlib import Path + +def split_pickle(full_path: Path, out_prefix: Path, chunk_bytes: int) -> None: + data = full_path.read_bytes() + out_dir = out_prefix.parent + + for p in out_dir.glob(f"{out_prefix.name}.data-*"): + p.unlink() + + total = (len(data) + chunk_bytes - 1) // chunk_bytes + names = [] + for i in range(0, len(data), chunk_bytes): + name = f"{out_prefix.name}.data-{(i // chunk_bytes) + 1:04d}-of-{total:04d}" + (out_dir / name).write_bytes(data[i:i + chunk_bytes]) + names.append(name) + + manifest = hashlib.sha256(data).hexdigest() + "\n" + "\n".join(names) + "\n" + (out_dir / (out_prefix.name + ".parts")).write_text(manifest) + +def load_external_pickle(prefix: Path): + parts = prefix.parent / (prefix.name + ".parts") + lines = parts.read_text().splitlines() + expected_hash, chunk_names = lines[0], lines[1:] + + data = bytearray() + for name in chunk_names: + data += (prefix.parent / name).read_bytes() + + if hashlib.sha256(data).hexdigest() != expected_hash: + raise RuntimeError(f"hash mismatch loading {prefix}") + return pickle.loads(data) + +if __name__ == "__main__": + split_pickle(Path(sys.argv[1]), Path(sys.argv[2]), int(sys.argv[3])) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index df000979e85..a8b633bf020 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -28,6 +28,7 @@ from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.selfdrive.modeld.constants import ModelConstants, Plan +from openpilot.selfdrive.modeld.external_pickle import load_external_pickle PROCESS_NAME = "selfdrive.modeld.modeld" @@ -177,11 +178,8 @@ def __init__(self): self.parser = Parser() self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None - with open(VISION_PKL_PATH, "rb") as f: - self.vision_run = pickle.load(f) - - with open(POLICY_PKL_PATH, "rb") as f: - self.policy_run = pickle.load(f) + self.vision_run = load_external_pickle(VISION_PKL_PATH) + self.policy_run = load_external_pickle(POLICY_PKL_PATH) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} From 49a611df59958e1bedb5a16146fd10a28f983a64 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 13 Feb 2026 14:08:26 -0800 Subject: [PATCH 193/685] CI: don't block on badges job for release builds --- .github/workflows/prebuilt.yaml | 2 +- .github/workflows/release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index 921c27465b9..c0a2baa8e3e 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -28,7 +28,7 @@ jobs: wait-interval: 30 running-workflow-name: 'build prebuilt' repo-token: ${{ secrets.GITHUB_TOKEN }} - check-regexp: ^((?!.*(build master-ci).*).)*$ + check-regexp: ^((?!.*(build master-ci|create badges).*).)*$ - uses: actions/checkout@v6 with: submodules: true diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 0f34dbe435b..159902d5196 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -29,7 +29,7 @@ jobs: wait-interval: 30 running-workflow-name: 'build master-ci' repo-token: ${{ secrets.GITHUB_TOKEN }} - check-regexp: ^((?!.*(build prebuilt).*).)*$ + check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$ - uses: actions/checkout@v6 with: submodules: true From c91225b52e21a8f75a9b617feb9116bec22ac6ca Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Feb 2026 15:37:07 -0800 Subject: [PATCH 194/685] WifiUi: reset networks on panel hide (#37199) * stash * fix setup * clean up * clean up * clean up * set active as safeguard --- selfdrive/ui/mici/layouts/settings/network/__init__.py | 6 ++++-- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 8 ++------ system/ui/mici_setup.py | 6 ++++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index fb1d56a1f64..7de9b52278f 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -123,12 +123,12 @@ def _update_state(self): def show_event(self): super().show_event() self._current_panel = NetworkPanelType.NONE - self._wifi_ui.show_event() + self._wifi_manager.set_active(True) self._scroller.show_event() def hide_event(self): super().hide_event() - self._wifi_ui.hide_event() + self._wifi_manager.set_active(False) def _toggle_roaming(self, checked: bool): self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) @@ -186,6 +186,8 @@ def _on_network_updated(self, networks: list[Network]): def _switch_to_panel(self, panel_type: NetworkPanelType): if panel_type == NetworkPanelType.WIFI: self._wifi_ui.show_event() + elif self._current_panel == NetworkPanelType.WIFI: + self._wifi_ui.hide_event() self._current_panel = panel_type def _render(self, rect: rl.Rectangle): diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 5c9616524c1..d25b28d63b5 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -360,15 +360,11 @@ def __init__(self, wifi_manager: WifiManager, back_callback: Callable): ) def show_event(self): - # Call super to prepare scroller; selection scroll is handled dynamically + # Clear scroller items and update from latest scan results super().show_event() self._wifi_manager.set_active(True) - - def hide_event(self): - super().hide_event() - self._wifi_manager.set_active(False) - # clear scroller items to remove old networks on next show self._scroller._items.clear() + self._update_buttons() def _open_network_manage_page(self, result=None): self._network_info_page.update_networks(self._networks) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 78b6b5cecb0..1322e95ef05 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -457,6 +457,8 @@ def __init__(self, wifi_manager, continue_callback: Callable, back_callback: Cal self._prev_has_internet = False def set_state(self, state: NetworkSetupState): + if self._state == NetworkSetupState.WIFI_PANEL and state != NetworkSetupState.WIFI_PANEL: + self._wifi_ui.hide_event() self._state = state if state == NetworkSetupState.WIFI_PANEL: self._wifi_ui.show_event() @@ -478,11 +480,11 @@ def set_has_internet(self, has_internet: bool): def show_event(self): super().show_event() self._state = NetworkSetupState.MAIN - self._wifi_ui.show_event() def hide_event(self): super().hide_event() - self._wifi_ui.hide_event() + if self._state == NetworkSetupState.WIFI_PANEL: + self._wifi_ui.hide_event() def _render(self, _): if self._state == NetworkSetupState.MAIN: From 1b426a31608a1d7aeec35a4ac76055a4b952f470 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Feb 2026 16:15:59 -0800 Subject: [PATCH 195/685] wifi button shows connecting (#37202) * connecting wifi button * use real wifi strength * simplify * meh cursor brought up edge case --- .../mici/layouts/settings/network/__init__.py | 28 +++++++++++-------- system/ui/lib/wifi_manager.py | 4 +++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 7de9b52278f..3a0da36bb11 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -160,18 +160,24 @@ def _on_network_updated(self, networks: list[Network]): # Update wi-fi button with ssid and ip address # TODO: make sure we handle hidden ssids + connecting_ssid = self._wifi_manager.connecting_to_ssid connected_network = next((network for network in networks if network.is_connected), None) - self._wifi_button.set_text(normalize_ssid(connected_network.ssid) if connected_network is not None else "wi-fi") - self._wifi_button.set_value(self._wifi_manager.ipv4_address or "not connected") - if connected_network is not None: - strength = WifiIcon.get_strength_icon_idx(connected_network.strength) - if strength == 2: - strength_icon = self._wifi_full_txt - elif strength == 1: - strength_icon = self._wifi_medium_txt - else: - strength_icon = self._wifi_low_txt - self._wifi_button.set_icon(strength_icon) + if connecting_ssid: + display_network = next((n for n in networks if n.ssid == connecting_ssid), None) + self._wifi_button.set_text(normalize_ssid(connecting_ssid)) + self._wifi_button.set_value("connecting...") + elif connected_network is not None: + display_network = connected_network + self._wifi_button.set_text(normalize_ssid(connected_network.ssid)) + self._wifi_button.set_value(self._wifi_manager.ipv4_address or "not connected") + else: + display_network = None + self._wifi_button.set_text("wi-fi") + self._wifi_button.set_value("not connected") + + if display_network is not None: + strength = WifiIcon.get_strength_icon_idx(display_network.strength) + self._wifi_button.set_icon(self._wifi_full_txt if strength == 2 else self._wifi_medium_txt if strength == 1 else self._wifi_low_txt) else: self._wifi_button.set_icon(self._wifi_slash_txt) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 72373ecbea1..2dfbe63cc33 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -233,6 +233,10 @@ def ipv4_address(self) -> str: def current_network_metered(self) -> MeteredType: return self._current_network_metered + @property + def connecting_to_ssid(self) -> str: + return self._connecting_to_ssid + @property def tethering_password(self) -> str: return self._tethering_password From 10065c8c288598ca4cce1c4e01d2dad68f4790bb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Feb 2026 17:02:42 -0800 Subject: [PATCH 196/685] WifiManager: handle failed state change (#37205) * handle connecting to network that drops out w/ wrong password (no longer says connected and now deletes connection) * clean up * combine --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 5 +++-- system/ui/lib/networkmanager.py | 2 ++ system/ui/lib/wifi_manager.py | 8 ++++++-- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index d25b28d63b5..1c11c09731a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -367,8 +367,9 @@ def show_event(self): self._update_buttons() def _open_network_manage_page(self, result=None): - self._network_info_page.update_networks(self._networks) - gui_app.set_modal_overlay(self._network_info_page) + if self._network_info_page._network is not None and self._network_info_page._network.ssid in self._networks: + self._network_info_page.update_networks(self._networks) + gui_app.set_modal_overlay(self._network_info_page) def _forget_network(self, ssid: str): network = self._networks.get(ssid) diff --git a/system/ui/lib/networkmanager.py b/system/ui/lib/networkmanager.py index ffa2ff4db9d..19d54e65160 100644 --- a/system/ui/lib/networkmanager.py +++ b/system/ui/lib/networkmanager.py @@ -11,6 +11,7 @@ class NMDeviceState(IntEnum): IP_CONFIG = 70 ACTIVATED = 100 DEACTIVATING = 110 + FAILED = 120 # NetworkManager constants @@ -29,6 +30,7 @@ class NMDeviceState(IntEnum): NM_DEVICE_TYPE_WIFI = 2 NM_DEVICE_TYPE_MODEM = 8 +NM_DEVICE_STATE_REASON_NO_SECRETS = 7 NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8 NM_DEVICE_STATE_REASON_NEW_ACTIVATION = 60 diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 2dfbe63cc33..187199b5c9d 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -23,7 +23,8 @@ NM_802_11_AP_FLAGS_PRIVACY, NM_802_11_AP_FLAGS_WPS, NM_PATH, NM_IFACE, NM_ACCESS_POINT_IFACE, NM_SETTINGS_PATH, NM_SETTINGS_IFACE, NM_CONNECTION_IFACE, NM_DEVICE_IFACE, - NM_DEVICE_TYPE_WIFI, NM_DEVICE_TYPE_MODEM, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, + NM_DEVICE_TYPE_WIFI, NM_DEVICE_TYPE_MODEM, NM_DEVICE_STATE_REASON_NO_SECRETS, + NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, NM_DEVICE_STATE_REASON_NEW_ACTIVATION, NM_ACTIVE_CONNECTION_IFACE, NM_IP4_CONFIG_IFACE, NM_PROPERTIES_IFACE, NMDeviceState) @@ -319,7 +320,10 @@ def _monitor_state(self): new_state, previous_state, change_reason = state_q.popleft().body # BAD PASSWORD - if new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT and len(self._connecting_to_ssid): + # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT + # - weak/gone network fails with FAILED+NO_SECRETS + if len(self._connecting_to_ssid) and ((new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT) or + (new_state == NMDeviceState.FAILED and change_reason == NM_DEVICE_STATE_REASON_NO_SECRETS)): self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid) self.forget_connection(self._connecting_to_ssid, block=True) self._connecting_to_ssid = "" From cd03aa19a190224ba7d098741729b2c0d4e010db Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Feb 2026 17:14:51 -0800 Subject: [PATCH 197/685] WifiManager: fix forgetting wrong network (#37187) * not sure how works but does? * clean up * clean up --- system/ui/lib/wifi_manager.py | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 187199b5c9d..e199c74f2da 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -167,6 +167,7 @@ def __init__(self): # State self._connections: dict[str, str] = {} # ssid -> connection path, updated via NM signals self._connecting_to_ssid: str = "" + self._prev_connecting_to_ssid: str = "" self._ipv4_address: str = "" self._current_network_metered: MeteredType = MeteredType.UNKNOWN self._tethering_password: str = "" @@ -242,6 +243,10 @@ def connecting_to_ssid(self) -> str: def tethering_password(self) -> str: return self._tethering_password + def _set_connecting(self, ssid: str): + self._prev_connecting_to_ssid = self._connecting_to_ssid + self._connecting_to_ssid = ssid + def _enqueue_callbacks(self, cbs: list[Callable], *args): for cb in cbs: self._callback_queue.append(lambda _cb=cb: _cb(*args)) @@ -319,23 +324,29 @@ def _monitor_state(self): while len(state_q): new_state, previous_state, change_reason = state_q.popleft().body - # BAD PASSWORD + # BAD PASSWORD - use prev if current has already moved on to a new connection # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT # - weak/gone network fails with FAILED+NO_SECRETS - if len(self._connecting_to_ssid) and ((new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT) or - (new_state == NMDeviceState.FAILED and change_reason == NM_DEVICE_STATE_REASON_NO_SECRETS)): - self._enqueue_callbacks(self._need_auth, self._connecting_to_ssid) - self.forget_connection(self._connecting_to_ssid, block=True) - self._connecting_to_ssid = "" + if ((new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT) or + (new_state == NMDeviceState.FAILED and change_reason == NM_DEVICE_STATE_REASON_NO_SECRETS)): + failed_ssid = self._prev_connecting_to_ssid or self._connecting_to_ssid + if failed_ssid: + self._enqueue_callbacks(self._need_auth, failed_ssid) + self.forget_connection(failed_ssid, block=True) + self._prev_connecting_to_ssid = "" + if self._connecting_to_ssid == failed_ssid: + self._connecting_to_ssid = "" elif new_state == NMDeviceState.ACTIVATED: if len(self._activated): self._update_networks() self._enqueue_callbacks(self._activated) + self._prev_connecting_to_ssid = "" self._connecting_to_ssid = "" elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: self._enqueue_callbacks(self._forgotten, self._connecting_to_ssid) + self._prev_connecting_to_ssid = "" self._connecting_to_ssid = "" def _network_scanner(self): @@ -447,9 +458,10 @@ def _add_tethering_connection(self): self._router_main.send_and_get_reply(new_method_call(settings_addr, 'AddConnection', 'a{sa{sv}}', (connection,))) def connect_to_network(self, ssid: str, password: str, hidden: bool = False): + self._set_connecting(ssid) + def worker(): # Clear all connections that may already exist to the network we are connecting to - self._connecting_to_ssid = ssid self.forget_connection(ssid, block=True) connection = { @@ -500,6 +512,8 @@ def worker(): threading.Thread(target=worker, daemon=True).start() def activate_connection(self, ssid: str, block: bool = False): + self._set_connecting(ssid) + def worker(): conn_path = self._connections.get(ssid, None) if conn_path is not None: @@ -507,7 +521,6 @@ def worker(): cloudlog.warning("No WiFi device found") return - self._connecting_to_ssid = ssid self._router_main.send(new_method_call(self._nm, 'ActivateConnection', 'ooo', (conn_path, self._wifi_device, "/"))) From 9bb6e997aad6c073c7991afcef41dc27a9c06f1b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Feb 2026 17:20:54 -0800 Subject: [PATCH 198/685] Make more icons 90% white (#37206) * 90% icons * fix! --- selfdrive/ui/mici/layouts/home.py | 2 +- selfdrive/ui/mici/widgets/button.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index f5dab7249a4..d4bbb749149 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -249,7 +249,7 @@ def _render_bottom_status_bar(self): # Offset by difference in height between slashless and slash icons to make center align match rl.draw_texture(self._wifi_slash_txt, int(last_x), int(self._rect.y + self.rect.height - self._wifi_slash_txt.height / 2 - (self._wifi_slash_txt.height - self._wifi_none_txt.height) / 2 - Y_CENTER), - rl.Color(255, 255, 255, 255)) + rl.Color(255, 255, 255, int(255 * 0.9))) last_x += self._wifi_slash_txt.width + ITEM_SPACING # draw experimental icon diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 36b1922385b..101f3bb56e0 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -54,7 +54,7 @@ def set_enable_pressed_state(self, pressed: bool): def _draw_content(self, btn_y: float): # draw icon - icon_color = rl.WHITE if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) + icon_color = rl.Color(255, 255, 255, int(255 * 0.9)) if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) rl.draw_texture_ex(self._txt_icon, (self._rect.x + (self._rect.width - self._txt_icon.width) / 2 + self._icon_offset[0], btn_y + (self._rect.height - self._txt_icon.height) / 2 + self._icon_offset[1]), 0, 1.0, icon_color) @@ -206,7 +206,7 @@ def _draw_content(self, btn_y: float): source_rec = rl.Rectangle(0, 0, self._txt_icon.width, self._txt_icon.height) dest_rec = rl.Rectangle(x, y, self._txt_icon.width, self._txt_icon.height) origin = rl.Vector2(self._txt_icon.width / 2, self._txt_icon.height / 2) - rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.WHITE) + rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.Color(255, 255, 255, int(255 * 0.9))) def _render(self, _): # draw _txt_default_bg From 5a9fdde156c467953eab79985097149acb057647 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Feb 2026 17:30:59 -0800 Subject: [PATCH 199/685] WifiUi: use WifiManager forget (#37208) * start * clean up forget --- .../ui/mici/layouts/settings/network/wifi_ui.py | 10 +--------- system/ui/lib/wifi_manager.py | 15 +++++++++------ system/ui/widgets/scroller.py | 2 +- 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 1c11c09731a..b27e03ed14a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -342,7 +342,7 @@ def __init__(self, wifi_manager: WifiManager, back_callback: Callable): # Set up back navigation self.set_back_callback(back_callback) - self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, self._forget_network, self._open_network_manage_page) + self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, self._open_network_manage_page) self._network_info_page.set_connecting(lambda: self._connecting) self._loading_animation = LoadingAnimation() @@ -371,14 +371,6 @@ def _open_network_manage_page(self, result=None): self._network_info_page.update_networks(self._networks) gui_app.set_modal_overlay(self._network_info_page) - def _forget_network(self, ssid: str): - network = self._networks.get(ssid) - if network is None: - cloudlog.warning(f"Trying to forget unknown network: {ssid}") - return - - self._wifi_manager.forget_connection(network.ssid) - def _on_network_updated(self, networks: list[Network]): self._networks = {network.ssid: network for network in networks} self._update_buttons() diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index e199c74f2da..2a4a9ab7111 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -498,13 +498,16 @@ def worker(): def forget_connection(self, ssid: str, block: bool = False): def worker(): conn_path = self._connections.get(ssid, None) - if conn_path is not None: - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) - self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) + if conn_path is None: + cloudlog.warning(f"Trying to forget unknown connection: {ssid}") + return + + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) - if len(self._forgotten): - self._update_networks() - self._enqueue_callbacks(self._forgotten, ssid) + if len(self._forgotten): + self._update_networks() + self._enqueue_callbacks(self._forgotten, ssid) if block: worker() diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 43539d128ba..fcba1952ce9 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -267,7 +267,7 @@ def _render(self, _): rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) - for item in self._visible_items: + for item in reversed(self._visible_items): # Skip rendering if not in viewport if not rl.check_collision_recs(item.rect, self._rect): continue From 2dac616bef98ac92fd4d4490545076c69880c1f8 Mon Sep 17 00:00:00 2001 From: Nick Date: Fri, 13 Feb 2026 17:43:53 -0800 Subject: [PATCH 200/685] keyboard: fix hint text truncation and add trailing ellipsis (#37207) Widen the hint label rect so it doesn't reserve right-side space for the hidden backspace button, preventing unnecessary text eliding. Also show the blinking cursor over the hint and add trailing ellipsis to hint strings for consistency. Co-authored-by: Cursor --- selfdrive/ui/mici/layouts/settings/developer.py | 2 +- .../ui/mici/layouts/settings/network/__init__.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 13 +++++++++---- system/ui/mici_setup.py | 2 +- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index b6145e042eb..ad68d6ee940 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -29,7 +29,7 @@ def github_username_callback(username: str): def ssh_keys_callback(): github_username = ui_state.params.get("GithubUsername") or "" - dlg = BigInputDialog("enter GitHub username", github_username, confirm_callback=github_username_callback) + dlg = BigInputDialog("enter GitHub username...", github_username, confirm_callback=github_username_callback) if not system_time_valid(): dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "") gui_app.set_modal_overlay(dlg) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 3a0da36bb11..bda619feef5 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -144,7 +144,7 @@ def update_apn(apn: str): self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), apn, ui_state.params.get_bool("GsmMetered")) current_apn = ui_state.params.get("GsmApn") or "" - dlg = BigInputDialog("enter APN", current_apn, minimum_length=0, confirm_callback=update_apn) + dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn) gui_app.set_modal_overlay(dlg) def _toggle_cellular_metered(self, checked: bool): diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index b88ac204941..6b4cb92c16e 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -195,11 +195,13 @@ def _render(self, _): rl.BLACK, rl.BLANK) # draw cursor + blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 if text: - blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 cursor_x = min(text_x + text_size.x + 3, text_field_rect.x + text_field_rect.width) - rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_field_rect.y), 4, int(text_size.y)), - 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) + else: + cursor_x = text_field_rect.x - 6 + rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_field_rect.y), 4, int(text_size.y)), + 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) # draw backspace icon with nice fade self._backspace_img_alpha.update(255 * bool(text)) @@ -209,7 +211,10 @@ def _render(self, _): if not text and self._hint_label.text and not candidate_char: # draw description if no text entered yet and not drawing candidate char - self._hint_label.render(text_field_rect) + hint_rect = rl.Rectangle(text_field_rect.x, text_field_rect.y, + self._rect.width - text_field_rect.x - PADDING, + text_field_rect.height) + self._hint_label.render(hint_rect) # TODO: move to update state # make rect take up entire area so it's easier to click diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 1322e95ef05..b5c0d052819 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -639,7 +639,7 @@ def handle_keyboard_exit(result): if result == DialogResult.CANCEL: self._set_state(SetupState.SOFTWARE_SELECTION) - keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) + keyboard = BigInputDialog("custom software URL...", confirm_callback=handle_keyboard_result) gui_app.set_modal_overlay(keyboard, callback=handle_keyboard_exit) def use_openpilot(self): From 4af41ffce6e92d3d75d498ad53de56c9969cdbb8 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Sat, 14 Feb 2026 15:20:44 -0600 Subject: [PATCH 201/685] ui diff: ensure video name matches output (#37211) * auto name diff.mp4 * ensure output file has .html extension --- selfdrive/ui/tests/diff/diff.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/tests/diff/diff.py b/selfdrive/ui/tests/diff/diff.py index a581f687477..bde6d44238c 100755 --- a/selfdrive/ui/tests/diff/diff.py +++ b/selfdrive/ui/tests/diff/diff.py @@ -62,7 +62,7 @@ def find_differences(video1, video2): return different_frames, len(frames1) -def generate_html_report(video1, video2, basedir, different_frames, total_frames): +def generate_html_report(video1, video2, basedir, different_frames, total_frames, diff_video_name): chunks = [] if different_frames: current_chunk = [different_frames[0]] @@ -100,7 +100,7 @@ def generate_html_report(video1, video2, basedir, different_frames, total_frames

Pixel Diff

@@ -152,6 +152,9 @@ def main(): args = parser.parse_args() + if not args.output.lower().endswith('.html'): + args.output += '.html' + os.makedirs(DIFF_OUT_DIR, exist_ok=True) print("=" * 60) @@ -162,8 +165,9 @@ def main(): print(f"Output: {args.output}") print() - # Create diff video - diff_video_path = os.path.join(os.path.dirname(args.output), DIFF_OUT_DIR / "diff.mp4") + # Create diff video with name derived from output HTML + diff_video_name = Path(args.output).stem + '.mp4' + diff_video_path = str(DIFF_OUT_DIR / diff_video_name) create_diff_video(args.video1, args.video2, diff_video_path) different_frames, total_frames = find_differences(args.video1, args.video2) @@ -173,7 +177,7 @@ def main(): print() print("Generating HTML report...") - html = generate_html_report(args.video1, args.video2, args.basedir, different_frames, total_frames) + html = generate_html_report(args.video1, args.video2, args.basedir, different_frames, total_frames, diff_video_name) with open(DIFF_OUT_DIR / args.output, 'w') as f: f.write(html) From ecde60419871820269657a8b88c839a824ecfd73 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Sat, 14 Feb 2026 15:21:09 -0600 Subject: [PATCH 202/685] ui replay: use openpilot prefix (#37185) * fix: use openpilot prefix * fix ui_state import * comment --- selfdrive/ui/tests/diff/replay.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 1efc6dde922..a668cc776b3 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -13,9 +13,9 @@ os.environ["RECORD_OUTPUT"] = os.path.join(DIFF_OUT_DIR, os.environ["RECORD_OUTPUT"]) from openpilot.common.params import Params +from openpilot.common.prefix import OpenpilotPrefix from openpilot.system.version import terms_version, training_version from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent -from openpilot.selfdrive.ui.ui_state import ui_state FPS = 60 HEADLESS = os.getenv("WINDOWED", "0") == "1" @@ -78,6 +78,9 @@ def handle_event(event: DummyEvent): def run_replay(): + from openpilot.selfdrive.ui.ui_state import ui_state # import here for correct param setup (e.g. training guide) + from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout # import here for coverage + setup_state() os.makedirs(DIFF_OUT_DIR, exist_ok=True) @@ -85,7 +88,6 @@ def run_replay(): rl.set_config_flags(rl.FLAG_WINDOW_HIDDEN) gui_app.init_window("ui diff test", fps=FPS) - from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout # import here for coverage main_layout = MiciMainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) @@ -119,13 +121,14 @@ def run_replay(): def main(): - cov = coverage.coverage(source=['openpilot.selfdrive.ui.mici']) - with cov.collect(): - run_replay() - cov.save() - cov.report() - cov.html_report(directory=os.path.join(DIFF_OUT_DIR, 'htmlcov')) - print("HTML report: htmlcov/index.html") + with OpenpilotPrefix(): + cov = coverage.coverage(source=['openpilot.selfdrive.ui.mici']) + with cov.collect(): + run_replay() + cov.save() + cov.report() + cov.html_report(directory=os.path.join(DIFF_OUT_DIR, 'htmlcov')) + print("HTML report: htmlcov/index.html") if __name__ == "__main__": From ae6aa0f0088bbe97c959b17d6d0358b92e7b1587 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Feb 2026 13:39:14 -0800 Subject: [PATCH 203/685] Remove gcc@13 installation from mac_setup.sh (#37213) * Remove gcc@13 installation from mac_setup.sh Removed installation of gcc@13 from mac_setup.sh. * no cache * Revert "no cache" This reverts commit fc27f7dc9e6dab4b61703433130531f12dbe334b. --- tools/mac_setup.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 0ae0b35359e..fba7699beba 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -47,7 +47,6 @@ brew "qt@5" brew "zeromq" cask "gcc-arm-embedded" brew "portaudio" -brew "gcc@13" EOS echo "[ ] finished brew install t=$SECONDS" From 56d3014298cf0f33803ed424a365e4dec85c686d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Feb 2026 13:42:21 -0800 Subject: [PATCH 204/685] Remove pycurl handling from mac_setup.sh (#37214) --- tools/mac_setup.sh | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index fba7699beba..26ef4dd9a57 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -42,7 +42,6 @@ brew "glfw" brew "libusb" brew "libtool" brew "llvm" -brew "openssl@3.0" brew "qt@5" brew "zeromq" cask "gcc-arm-embedded" @@ -59,12 +58,6 @@ export LDFLAGS="$LDFLAGS -L${BREW_PREFIX}/opt/bzip2/lib" export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/zlib/include" export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/bzip2/include" -# pycurl curl/openssl backend dependencies -export LDFLAGS="$LDFLAGS -L${BREW_PREFIX}/opt/openssl@3/lib" -export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/openssl@3/include" -export PYCURL_CURL_CONFIG=/usr/bin/curl-config -export PYCURL_SSL_LIBRARY=openssl - # install python dependencies $DIR/install_python_dependencies.sh echo "[ ] installed python dependencies t=$SECONDS" From 96d1b876bbd7441cac08bab842aded6729ae1c6d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Feb 2026 20:54:09 -0800 Subject: [PATCH 205/685] pandad: remove multi-panda + USB support (#37217) * pandad: remove multi-panda support * lil more * mac * skip mac --- Jenkinsfile | 10 +- selfdrive/pandad/.gitignore | 2 +- selfdrive/pandad/SConscript | 13 +- selfdrive/pandad/main.cc | 4 +- selfdrive/pandad/panda.cc | 38 +-- selfdrive/pandad/panda.h | 9 +- selfdrive/pandad/panda_comms.cc | 227 ------------- selfdrive/pandad/panda_comms.h | 61 +--- selfdrive/pandad/panda_safety.cc | 27 +- selfdrive/pandad/pandad.cc | 306 ++++++------------ selfdrive/pandad/pandad.h | 7 +- selfdrive/pandad/pandad.py | 57 ++-- selfdrive/pandad/spi.cc | 4 +- ...protocol.cc => test_pandad_canprotocol.cc} | 14 +- .../pandad/tests/test_pandad_loopback.py | 18 +- selfdrive/pandad/tests/test_pandad_spi.py | 2 +- 16 files changed, 186 insertions(+), 613 deletions(-) delete mode 100644 selfdrive/pandad/panda_comms.cc rename selfdrive/pandad/tests/{test_pandad_usbprotocol.cc => test_pandad_canprotocol.cc} (88%) diff --git a/Jenkinsfile b/Jenkinsfile index c095eda8a91..6f81755d4ea 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -216,12 +216,6 @@ node { step("test manager", "pytest system/manager/test/test_manager.py"), ]) }, - 'loopback': { - deviceStage("loopback", "tizi-loopback", ["UNSAFE=1"], [ - step("build openpilot", "cd system/manager && ./build.py"), - step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"), - ]) - }, 'camerad OX03C10': { deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), @@ -251,11 +245,9 @@ node { 'tizi': { deviceStage("tizi", "tizi", ["UNSAFE=1"], [ step("build openpilot", "cd system/manager && ./build.py"), - step("test pandad loopback", "SINGLE_PANDA=1 pytest selfdrive/pandad/tests/test_pandad_loopback.py"), + step("test pandad loopback", "pytest selfdrive/pandad/tests/test_pandad_loopback.py"), step("test pandad spi", "pytest selfdrive/pandad/tests/test_pandad_spi.py"), step("test amp", "pytest system/hardware/tici/tests/test_amplifier.py"), - // TODO: enable once new AGNOS is available - // step("test esim", "pytest system/hardware/tici/tests/test_esim.py"), step("test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", [diffPaths: ["system/qcomgpsd/"]]), ]) }, diff --git a/selfdrive/pandad/.gitignore b/selfdrive/pandad/.gitignore index f7226cdb876..cb292405c9f 100644 --- a/selfdrive/pandad/.gitignore +++ b/selfdrive/pandad/.gitignore @@ -1,3 +1,3 @@ pandad pandad_api_impl.cpp -tests/test_pandad_usbprotocol +tests/test_pandad_canprotocol diff --git a/selfdrive/pandad/SConscript b/selfdrive/pandad/SConscript index 5e0b782c1e1..fd59db98537 100644 --- a/selfdrive/pandad/SConscript +++ b/selfdrive/pandad/SConscript @@ -1,9 +1,10 @@ -Import('env', 'common', 'messaging') +Import('env', 'arch', 'common', 'messaging') -libs = ['usb-1.0', common, messaging, 'pthread'] -panda = env.Library('panda', ['panda.cc', 'panda_comms.cc', 'spi.cc']) +if arch != "Darwin": + libs = [common, messaging, 'pthread'] + panda = env.Library('panda', ['panda.cc', 'spi.cc']) -env.Program('pandad', ['main.cc', 'pandad.cc', 'panda_safety.cc'], LIBS=[panda] + libs) + env.Program('pandad', ['main.cc', 'pandad.cc', 'panda_safety.cc'], LIBS=[panda] + libs) -if GetOption('extras'): - env.Program('tests/test_pandad_usbprotocol', ['tests/test_pandad_usbprotocol.cc'], LIBS=[panda] + libs) + if GetOption('extras'): + env.Program('tests/test_pandad_canprotocol', ['tests/test_pandad_canprotocol.cc'], LIBS=[panda] + libs) diff --git a/selfdrive/pandad/main.cc b/selfdrive/pandad/main.cc index b63d884a45e..ef30d6037c2 100644 --- a/selfdrive/pandad/main.cc +++ b/selfdrive/pandad/main.cc @@ -16,7 +16,7 @@ int main(int argc, char *argv[]) { assert(err == 0); } - std::vector serials(argv + 1, argv + argc); - pandad_main_thread(serials); + std::string serial = (argc > 1) ? argv[1] : ""; + pandad_main_thread(serial); return 0; } diff --git a/selfdrive/pandad/panda.cc b/selfdrive/pandad/panda.cc index 93e139f0ec1..edc2228c0c7 100644 --- a/selfdrive/pandad/panda.cc +++ b/selfdrive/pandad/panda.cc @@ -12,19 +12,9 @@ const bool PANDAD_MAXOUT = getenv("PANDAD_MAXOUT") != nullptr; -Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { - // try USB first, then SPI - try { - handle = std::make_unique(serial); - LOGW("connected to %s over USB", serial.c_str()); - } catch (std::exception &e) { -#ifndef __APPLE__ - handle = std::make_unique(serial); - LOGW("connected to %s over SPI", serial.c_str()); -#else - throw e; -#endif - } +Panda::Panda(std::string serial) { + handle = std::make_unique(serial); + LOGW("connected to %s over SPI", serial.c_str()); hw_type = get_hw_type(); can_reset_communications(); @@ -42,20 +32,8 @@ std::string Panda::hw_serial() { return handle->hw_serial; } -std::vector Panda::list(bool usb_only) { - std::vector serials = PandaUsbHandle::list(); - -#ifndef __APPLE__ - if (!usb_only) { - for (const auto &s : PandaSpiHandle::list()) { - if (std::find(serials.begin(), serials.end(), s) == serials.end()) { - serials.push_back(s); - } - } - } -#endif - - return serials; +std::vector Panda::list() { + return PandaSpiHandle::list(); } void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) { @@ -195,7 +173,7 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data for (const auto &cmsg : can_data_list) { // check if the message is intended for this panda uint8_t bus = cmsg.getSrc(); - if (bus < bus_offset || bus >= (bus_offset + PANDA_BUS_OFFSET)) { + if (bus >= PANDA_BUS_OFFSET) { continue; } auto can_data = cmsg.getDat(); @@ -207,7 +185,7 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data header.addr = cmsg.getAddress(); header.extended = (cmsg.getAddress() >= 0x800) ? 1 : 0; header.data_len_code = data_len_code; - header.bus = bus - bus_offset; + header.bus = bus; header.checksum = 0; memcpy(&send_buf[pos], (uint8_t *)&header, sizeof(can_header)); @@ -283,7 +261,7 @@ bool Panda::unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector handle; + std::unique_ptr handle; public: - Panda(std::string serial="", uint32_t bus_offset=0); + Panda(std::string serial); cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN; - const uint32_t bus_offset; bool connected(); bool comms_healthy(); std::string hw_serial(); // Static functions - static std::vector list(bool usb_only=false); + static std::vector list(); // Panda functionality cereal::PandaState::PandaType get_hw_type(); @@ -91,7 +90,7 @@ class Panda { uint8_t receive_buffer[RECV_SIZE + sizeof(can_header) + 64]; uint32_t receive_buffer_size = 0; - Panda(uint32_t bus_offset) : bus_offset(bus_offset) {} + Panda() {} void pack_can_buffer(const capnp::List::Reader &can_data_list, std::function write_func); bool unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector &out_vec); diff --git a/selfdrive/pandad/panda_comms.cc b/selfdrive/pandad/panda_comms.cc deleted file mode 100644 index 8a20f397d31..00000000000 --- a/selfdrive/pandad/panda_comms.cc +++ /dev/null @@ -1,227 +0,0 @@ -#include "selfdrive/pandad/panda.h" - -#include -#include -#include - -#include "common/swaglog.h" - -static libusb_context *init_usb_ctx() { - libusb_context *context = nullptr; - int err = libusb_init(&context); - if (err != 0) { - LOGE("libusb initialization error"); - return nullptr; - } - -#if LIBUSB_API_VERSION >= 0x01000106 - libusb_set_option(context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); -#else - libusb_set_debug(context, 3); -#endif - return context; -} - -PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) { - // init libusb - ssize_t num_devices; - libusb_device **dev_list = NULL; - int err = 0; - ctx = init_usb_ctx(); - if (!ctx) { goto fail; } - - // connect by serial - num_devices = libusb_get_device_list(ctx, &dev_list); - if (num_devices < 0) { goto fail; } - for (size_t i = 0; i < num_devices; ++i) { - libusb_device_descriptor desc; - libusb_get_device_descriptor(dev_list[i], &desc); - if (desc.idVendor == 0x3801 && desc.idProduct == 0xddcc) { - int ret = libusb_open(dev_list[i], &dev_handle); - if (dev_handle == NULL || ret < 0) { goto fail; } - - unsigned char desc_serial[26] = { 0 }; - ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); - if (ret < 0) { goto fail; } - - hw_serial = std::string((char *)desc_serial, ret); - if (serial.empty() || serial == hw_serial) { - break; - } - libusb_close(dev_handle); - dev_handle = NULL; - } - } - if (dev_handle == NULL) goto fail; - libusb_free_device_list(dev_list, 1); - dev_list = nullptr; - - if (libusb_kernel_driver_active(dev_handle, 0) == 1) { - libusb_detach_kernel_driver(dev_handle, 0); - } - - err = libusb_set_configuration(dev_handle, 1); - if (err != 0) { goto fail; } - - err = libusb_claim_interface(dev_handle, 0); - if (err != 0) { goto fail; } - - return; - -fail: - if (dev_list != NULL) { - libusb_free_device_list(dev_list, 1); - } - cleanup(); - throw std::runtime_error("Error connecting to panda"); -} - -PandaUsbHandle::~PandaUsbHandle() { - std::lock_guard lk(hw_lock); - cleanup(); - connected = false; -} - -void PandaUsbHandle::cleanup() { - if (dev_handle) { - libusb_release_interface(dev_handle, 0); - libusb_close(dev_handle); - } - - if (ctx) { - libusb_exit(ctx); - } -} - -std::vector PandaUsbHandle::list() { - static std::unique_ptr context(init_usb_ctx(), libusb_exit); - // init libusb - ssize_t num_devices; - libusb_device **dev_list = NULL; - std::vector serials; - if (!context) { return serials; } - - num_devices = libusb_get_device_list(context.get(), &dev_list); - if (num_devices < 0) { - LOGE("libusb can't get device list"); - goto finish; - } - for (size_t i = 0; i < num_devices; ++i) { - libusb_device *device = dev_list[i]; - libusb_device_descriptor desc; - libusb_get_device_descriptor(device, &desc); - if (desc.idVendor == 0x3801 && desc.idProduct == 0xddcc) { - libusb_device_handle *handle = NULL; - int ret = libusb_open(device, &handle); - if (ret < 0) { goto finish; } - - unsigned char desc_serial[26] = { 0 }; - ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); - libusb_close(handle); - if (ret < 0) { goto finish; } - - serials.push_back(std::string((char *)desc_serial, ret)); - } - } - -finish: - if (dev_list != NULL) { - libusb_free_device_list(dev_list, 1); - } - return serials; -} - -void PandaUsbHandle::handle_usb_issue(int err, const char func[]) { - LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func); - if (err == LIBUSB_ERROR_NO_DEVICE) { - LOGE("lost connection"); - connected = false; - } - // TODO: check other errors, is simply retrying okay? -} - -int PandaUsbHandle::control_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) { - int err; - const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; - - if (!connected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - std::lock_guard lk(hw_lock); - do { - err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout); - if (err < 0) handle_usb_issue(err, __func__); - } while (err < 0 && connected); - - return err; -} - -int PandaUsbHandle::control_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) { - int err; - const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; - - if (!connected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - std::lock_guard lk(hw_lock); - do { - err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout); - if (err < 0) handle_usb_issue(err, __func__); - } while (err < 0 && connected); - - return err; -} - -int PandaUsbHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { - int err; - int transferred = 0; - - if (!connected) { - return 0; - } - - std::lock_guard lk(hw_lock); - do { - // Try sending can messages. If the receive buffer on the panda is full it will NAK - // and libusb will try again. After 5ms, it will time out. We will drop the messages. - err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); - - if (err == LIBUSB_ERROR_TIMEOUT) { - LOGW("Transmit buffer full"); - break; - } else if (err != 0 || length != transferred) { - handle_usb_issue(err, __func__); - } - } while (err != 0 && connected); - - return transferred; -} - -int PandaUsbHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { - int err; - int transferred = 0; - - if (!connected) { - return 0; - } - - std::lock_guard lk(hw_lock); - - do { - err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); - - if (err == LIBUSB_ERROR_TIMEOUT) { - break; // timeout is okay to exit, recv still happened - } else if (err == LIBUSB_ERROR_OVERFLOW) { - comms_healthy = false; - LOGE_100("overflow got 0x%x", transferred); - } else if (err != 0) { - handle_usb_issue(err, __func__); - } - - } while (err != 0 && connected); - - return transferred; -} diff --git a/selfdrive/pandad/panda_comms.h b/selfdrive/pandad/panda_comms.h index 9c452faf6da..cdfb5019b62 100644 --- a/selfdrive/pandad/panda_comms.h +++ b/selfdrive/pandad/panda_comms.h @@ -6,67 +6,20 @@ #include #include -#ifndef __APPLE__ -#include -#endif - -#include - #define TIMEOUT 0 #define SPI_BUF_SIZE 2048 -// comms base class -class PandaCommsHandle { +class PandaSpiHandle { public: - PandaCommsHandle(std::string serial) {} - virtual ~PandaCommsHandle() {} - virtual void cleanup() = 0; - std::string hw_serial; std::atomic connected = true; std::atomic comms_healthy = true; - static std::vector list(); - - // HW communication - virtual int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT) = 0; - virtual int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT) = 0; - virtual int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; - virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; -}; -class PandaUsbHandle : public PandaCommsHandle { -public: - PandaUsbHandle(std::string serial); - ~PandaUsbHandle(); - int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); - int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); - int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); - int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); - void cleanup(); - - static std::vector list(); - -private: - libusb_context *ctx = NULL; - libusb_device_handle *dev_handle = NULL; - std::recursive_mutex hw_lock; - void handle_usb_issue(int err, const char func[]); -}; - -#ifndef __APPLE__ -struct __attribute__((packed)) spi_header { - uint8_t sync; - uint8_t endpoint; - uint16_t tx_len; - uint16_t max_rx_len; -}; - -class PandaSpiHandle : public PandaCommsHandle { -public: PandaSpiHandle(std::string serial); ~PandaSpiHandle(); + int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); @@ -81,13 +34,19 @@ class PandaSpiHandle : public PandaCommsHandle { uint8_t rx_buf[SPI_BUF_SIZE]; inline static std::recursive_mutex hw_lock; + struct __attribute__((packed)) spi_header { + uint8_t sync; + uint8_t endpoint; + uint16_t tx_len; + uint16_t max_rx_len; + }; + int wait_for_ack(uint8_t ack, uint8_t tx, unsigned int timeout, unsigned int length); int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len, unsigned int timeout); int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout); int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len, unsigned int timeout); - int lltransfer(spi_ioc_transfer &t); + int lltransfer(struct spi_ioc_transfer &t); spi_header header; uint32_t xfer_count = 0; }; -#endif diff --git a/selfdrive/pandad/panda_safety.cc b/selfdrive/pandad/panda_safety.cc index b089503417d..32d129bc2e7 100644 --- a/selfdrive/pandad/panda_safety.cc +++ b/selfdrive/pandad/panda_safety.cc @@ -23,19 +23,15 @@ void PandaSafety::updateMultiplexingMode() { // Initialize to ELM327 without OBD multiplexing for initial fingerprinting if (!initialized_) { prev_obd_multiplexing_ = false; - for (int i = 0; i < pandas_.size(); ++i) { - pandas_[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); - } + panda_->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); initialized_ = true; } // Switch between multiplexing modes based on the OBD multiplexing request bool obd_multiplexing_requested = params_.getBool("ObdMultiplexingEnabled"); if (obd_multiplexing_requested != prev_obd_multiplexing_) { - for (int i = 0; i < pandas_.size(); ++i) { - const uint16_t safety_param = (i > 0 || !obd_multiplexing_requested) ? 1U : 0U; - pandas_[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); - } + const uint16_t safety_param = obd_multiplexing_requested ? 0U : 1U; + panda_->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); prev_obd_multiplexing_ = obd_multiplexing_requested; params_.putBool("ObdMultiplexingChanged", true); } @@ -65,17 +61,10 @@ void PandaSafety::setSafetyMode(const std::string ¶ms_string) { auto safety_configs = car_params.getSafetyConfigs(); uint16_t alternative_experience = car_params.getAlternativeExperience(); - for (int i = 0; i < pandas_.size(); ++i) { - // Default to SILENT safety model if not specified - cereal::CarParams::SafetyModel safety_model = cereal::CarParams::SafetyModel::SILENT; - uint16_t safety_param = 0U; - if (i < safety_configs.size()) { - safety_model = safety_configs[i].getSafetyModel(); - safety_param = safety_configs[i].getSafetyParam(); - } + cereal::CarParams::SafetyModel safety_model = safety_configs[0].getSafetyModel(); + uint16_t safety_param = safety_configs[0].getSafetyParam(); - LOGW("Panda %d: setting safety model: %d, param: %d, alternative experience: %d", i, (int)safety_model, safety_param, alternative_experience); - pandas_[i]->set_alternative_experience(alternative_experience); - pandas_[i]->set_safety_model(safety_model, safety_param); - } + LOGW("setting safety model: %d, param: %d, alternative experience: %d", (int)safety_model, safety_param, alternative_experience); + panda_->set_alternative_experience(alternative_experience); + panda_->set_safety_model(safety_model, safety_param); } diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index 2fd4a4def24..d048dbd0c3d 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -1,6 +1,5 @@ #include "selfdrive/pandad/pandad.h" -#include #include #include #include @@ -18,45 +17,24 @@ #include "common/util.h" #include "system/hardware/hw.h" -// -- Multi-panda conventions -- -// Ordering: -// - The internal panda will always be the first panda -// - Consecutive pandas will be sorted based on panda type, and then serial number -// Connecting: -// - If a panda connection is dropped, pandad will reconnect to all pandas -// - If a panda is added, we will only reconnect when we are offroad -// CAN buses: -// - Each panda will have its block of 4 buses. E.g.: the second panda will use -// bus numbers 4, 5, 6 and 7 -// - The internal panda will always be used for accessing the OBD2 port, -// and thus firmware queries -// Safety: -// - SafetyConfig is a list, which is mapped to the connected pandas -// - If there are more pandas connected than there are SafetyConfigs, -// the excess pandas will remain in "silent" or "noOutput" mode -// Ignition: -// - If any of the ignition sources in any panda is high, ignition is high - #define MAX_IR_PANDA_VAL 50 #define CUTOFF_IL 400 #define SATURATE_IL 1000 ExitHandler do_exit; -bool check_all_connected(const std::vector &pandas) { - for (const auto& panda : pandas) { - if (!panda->connected()) { - do_exit = true; - return false; - } +bool check_connected(Panda *panda) { + if (!panda->connected()) { + do_exit = true; + return false; } return true; } -Panda *connect(std::string serial="", uint32_t index=0) { +Panda *connect(std::string serial) { std::unique_ptr panda; try { - panda = std::make_unique(serial, (index * PANDA_BUS_OFFSET)); + panda = std::make_unique(serial); } catch (std::exception &e) { return nullptr; } @@ -78,7 +56,7 @@ Panda *connect(std::string serial="", uint32_t index=0) { return panda.release(); } -void can_send_thread(std::vector pandas, bool fake_send) { +void can_send_thread(Panda *panda, bool fake_send) { util::set_thread_name("pandad_can_send"); AlignedBuffer aligned_buf; @@ -88,7 +66,7 @@ void can_send_thread(std::vector pandas, bool fake_send) { subscriber->setTimeout(100); // run as fast as messages come in - while (!do_exit && check_all_connected(pandas)) { + while (!do_exit && check_connected(panda)) { std::unique_ptr msg(subscriber->receive()); if (!msg) { continue; @@ -99,25 +77,20 @@ void can_send_thread(std::vector pandas, bool fake_send) { // Don't send if older than 1 second if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { - for (const auto& panda : pandas) { - LOGT("sending sendcan to panda: %s", (panda->hw_serial()).c_str()); - panda->can_send(event.getSendcan()); - LOGT("sendcan sent to panda: %s", (panda->hw_serial()).c_str()); - } + LOGT("sending sendcan to panda: %s", (panda->hw_serial()).c_str()); + panda->can_send(event.getSendcan()); + LOGT("sendcan sent to panda: %s", (panda->hw_serial()).c_str()); } else { LOGE("sendcan too old to send: %" PRIu64 ", %" PRIu64, nanos_since_boot(), event.getLogMonoTime()); } } } -void can_recv(std::vector &pandas, PubMaster *pm) { +void can_recv(Panda *panda, PubMaster *pm) { static std::vector raw_can_data; { - bool comms_healthy = true; raw_can_data.clear(); - for (const auto& panda : pandas) { - comms_healthy &= panda->can_receive(raw_can_data); - } + bool comms_healthy = panda->can_receive(raw_can_data); MessageBuilder msg; auto evt = msg.initEvent(); @@ -187,102 +160,72 @@ void fill_panda_can_state(cereal::PandaState::PandaCanState::Builder &cs, const cs.setCanCoreResetCnt(can_health.can_core_reset_cnt); } -std::optional send_panda_states(PubMaster *pm, const std::vector &pandas, bool is_onroad, bool spoofing_started) { - bool ignition_local = false; - const uint32_t pandas_cnt = pandas.size(); - +std::optional send_panda_states(PubMaster *pm, Panda *panda, bool is_onroad, bool spoofing_started) { // build msg MessageBuilder msg; auto evt = msg.initEvent(); - auto pss = evt.initPandaStates(pandas_cnt); - - std::vector pandaStates; - pandaStates.reserve(pandas_cnt); + auto pss = evt.initPandaStates(1); - std::vector> pandaCanStates; - pandaCanStates.reserve(pandas_cnt); + auto health_opt = panda->get_state(); + if (!health_opt) { + return std::nullopt; + } - const bool red_panda_comma_three = (pandas.size() == 2) && - (pandas[0]->hw_type == cereal::PandaState::PandaType::DOS) && - (pandas[1]->hw_type == cereal::PandaState::PandaType::RED_PANDA); + health_t health = *health_opt; - for (const auto& panda : pandas){ - auto health_opt = panda->get_state(); - if (!health_opt) { + std::array can_health{}; + for (uint32_t i = 0; i < PANDA_CAN_CNT; i++) { + auto can_health_opt = panda->get_can_state(i); + if (!can_health_opt) { return std::nullopt; } + can_health[i] = *can_health_opt; + } - health_t health = *health_opt; - - std::array can_health{}; - for (uint32_t i = 0; i < PANDA_CAN_CNT; i++) { - auto can_health_opt = panda->get_can_state(i); - if (!can_health_opt) { - return std::nullopt; - } - can_health[i] = *can_health_opt; - } - pandaCanStates.push_back(can_health); - - if (spoofing_started) { - health.ignition_line_pkt = 1; - } - - // on comma three setups with a red panda, the dos can - // get false positive ignitions due to the harness box - // without a harness connector, so ignore it - if (red_panda_comma_three && (panda->hw_type == cereal::PandaState::PandaType::DOS)) { - health.ignition_line_pkt = 0; - } - - ignition_local |= ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)); - - pandaStates.push_back(health); + if (spoofing_started) { + health.ignition_line_pkt = 1; } - for (uint32_t i = 0; i < pandas_cnt; i++) { - auto panda = pandas[i]; - const auto &health = pandaStates[i]; + bool ignition_local = ((health.ignition_line_pkt != 0) || (health.ignition_can_pkt != 0)); - // Make sure CAN buses are live: safety_setter_thread does not work if Panda CAN are silent and there is only one other CAN node - if (health.safety_mode_pkt == (uint8_t)(cereal::CarParams::SafetyModel::SILENT)) { - panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); - } + // Make sure CAN buses are live: safety_setter_thread does not work if Panda CAN are silent and there is only one other CAN node + if (health.safety_mode_pkt == (uint8_t)(cereal::CarParams::SafetyModel::SILENT)) { + panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); + } - bool power_save_desired = !ignition_local; - if (health.power_save_enabled_pkt != power_save_desired) { - panda->set_power_saving(power_save_desired); - } + bool power_save_desired = !ignition_local; + if (health.power_save_enabled_pkt != power_save_desired) { + panda->set_power_saving(power_save_desired); + } - // set safety mode to NO_OUTPUT when car is off or we're not onroad. ELM327 is an alternative if we want to leverage athenad/connect - bool should_close_relay = !ignition_local || !is_onroad; - if (should_close_relay && (health.safety_mode_pkt != (uint8_t)(cereal::CarParams::SafetyModel::NO_OUTPUT))) { - panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); - } + // set safety mode to NO_OUTPUT when car is off or we're not onroad. ELM327 is an alternative if we want to leverage athenad/connect + bool should_close_relay = !ignition_local || !is_onroad; + if (should_close_relay && (health.safety_mode_pkt != (uint8_t)(cereal::CarParams::SafetyModel::NO_OUTPUT))) { + panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); + } - if (!panda->comms_healthy()) { - evt.setValid(false); - } + if (!panda->comms_healthy()) { + evt.setValid(false); + } - auto ps = pss[i]; - fill_panda_state(ps, panda->hw_type, health); + auto ps = pss[0]; + fill_panda_state(ps, panda->hw_type, health); - auto cs = std::array{ps.initCanState0(), ps.initCanState1(), ps.initCanState2()}; - for (uint32_t j = 0; j < PANDA_CAN_CNT; j++) { - fill_panda_can_state(cs[j], pandaCanStates[i][j]); - } + auto cs = std::array{ps.initCanState0(), ps.initCanState1(), ps.initCanState2()}; + for (uint32_t j = 0; j < PANDA_CAN_CNT; j++) { + fill_panda_can_state(cs[j], can_health[j]); + } - // Convert faults bitset to capnp list - std::bitset fault_bits(health.faults_pkt); - auto faults = ps.initFaults(fault_bits.count()); + // Convert faults bitset to capnp list + std::bitset fault_bits(health.faults_pkt); + auto faults = ps.initFaults(fault_bits.count()); - size_t j = 0; - for (size_t f = size_t(cereal::PandaState::FaultType::RELAY_MALFUNCTION); - f <= size_t(cereal::PandaState::FaultType::HEARTBEAT_LOOP_WATCHDOG); f++) { - if (fault_bits.test(f)) { - faults.set(j, cereal::PandaState::FaultType(f)); - j++; - } + size_t j = 0; + for (size_t f = size_t(cereal::PandaState::FaultType::RELAY_MALFUNCTION); + f <= size_t(cereal::PandaState::FaultType::HEARTBEAT_LOOP_WATCHDOG); f++) { + if (fault_bits.test(f)) { + faults.set(j, cereal::PandaState::FaultType(f)); + j++; } } @@ -323,46 +266,22 @@ void send_peripheral_state(Panda *panda, PubMaster *pm) { pm->send("peripheralState", msg); } -void process_panda_state(std::vector &pandas, PubMaster *pm, bool engaged, bool is_onroad, bool spoofing_started) { - std::vector connected_serials; - for (Panda *p : pandas) { - connected_serials.push_back(p->hw_serial()); +void process_panda_state(Panda *panda, PubMaster *pm, bool engaged, bool is_onroad, bool spoofing_started) { + auto ignition_opt = send_panda_states(pm, panda, is_onroad, spoofing_started); + if (!ignition_opt) { + LOGE("Failed to get ignition_opt"); + return; } - { - auto ignition_opt = send_panda_states(pm, pandas, is_onroad, spoofing_started); - if (!ignition_opt) { - LOGE("Failed to get ignition_opt"); - return; - } - - // check if we should have pandad reconnect - if (!ignition_opt.value()) { - bool comms_healthy = true; - for (const auto &panda : pandas) { - comms_healthy &= panda->comms_healthy(); - } - - if (!comms_healthy) { - LOGE("Reconnecting, communication to pandas not healthy"); - do_exit = true; - - } else { - // check for new pandas - for (std::string &s : Panda::list(true)) { - if (!std::count(connected_serials.begin(), connected_serials.end(), s)) { - LOGW("Reconnecting to new panda: %s", s.c_str()); - do_exit = true; - break; - } - } - } - } - - for (const auto &panda : pandas) { - panda->send_heartbeat(engaged); + // check if we should have pandad reconnect + if (!ignition_opt.value()) { + if (!panda->comms_healthy()) { + LOGE("Reconnecting, communication to panda not healthy"); + do_exit = true; } } + + panda->send_heartbeat(engaged); } void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) { @@ -429,30 +348,29 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) } } -void pandad_run(std::vector &pandas) { +void pandad_run(Panda *panda) { const bool no_fan_control = getenv("NO_FAN_CONTROL") != nullptr; const bool spoofing_started = getenv("STARTED") != nullptr; const bool fake_send = getenv("FAKESEND") != nullptr; // Start the CAN send thread - std::thread send_thread(can_send_thread, pandas, fake_send); + std::thread send_thread(can_send_thread, panda, fake_send); Params params; RateKeeper rk("pandad", 100); SubMaster sm({"selfdriveState"}); PubMaster pm({"can", "pandaStates", "peripheralState"}); - PandaSafety panda_safety(pandas); - Panda *peripheral_panda = pandas[0]; + PandaSafety panda_safety(panda); bool engaged = false; bool is_onroad = false; // Main loop: receive CAN data and process states - while (!do_exit && check_all_connected(pandas)) { - can_recv(pandas, &pm); + while (!do_exit && check_connected(panda)) { + can_recv(panda, &pm); // Process peripheral state at 20 Hz if (rk.frame() % 5 == 0) { - process_peripheral_state(peripheral_panda, &pm, no_fan_control); + process_peripheral_state(panda, &pm, no_fan_control); } // Process panda state at 10 Hz @@ -460,25 +378,23 @@ void pandad_run(std::vector &pandas) { sm.update(0); engaged = sm.allAliveAndValid({"selfdriveState"}) && sm["selfdriveState"].getSelfdriveState().getEnabled(); is_onroad = params.getBool("IsOnroad"); - process_panda_state(pandas, &pm, engaged, is_onroad, spoofing_started); + process_panda_state(panda, &pm, engaged, is_onroad, spoofing_started); panda_safety.configureSafetyMode(is_onroad); } // Send out peripheralState at 2Hz if (rk.frame() % 50 == 0) { - send_peripheral_state(peripheral_panda, &pm); + send_peripheral_state(panda, &pm); } - // Forward logs from pandas to cloudlog if available - for (auto *panda : pandas) { - std::string log = panda->serial_read(); - if (!log.empty()) { - if (log.find("Register 0x") != std::string::npos) { - // Log register divergent faults as errors - LOGE("%s", log.c_str()); - } else { - LOGD("%s", log.c_str()); - } + // Forward logs from panda to cloudlog if available + std::string log = panda->serial_read(); + if (!log.empty()) { + if (log.find("Register 0x") != std::string::npos) { + // Log register divergent faults as errors + LOGE("%s", log.c_str()); + } else { + LOGD("%s", log.c_str()); } } @@ -487,52 +403,38 @@ void pandad_run(std::vector &pandas) { // Close relay on exit to prevent a fault if (is_onroad && !engaged) { - for (auto &p : pandas) { - if (p->connected()) { - p->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); - } + if (panda->connected()) { + panda->set_safety_model(cereal::CarParams::SafetyModel::NO_OUTPUT); } } send_thread.join(); } -void pandad_main_thread(std::vector serials) { - if (serials.size() == 0) { - serials = Panda::list(); +void pandad_main_thread(std::string serial) { + if (serial.empty()) { + auto serials = Panda::list(); - if (serials.size() == 0) { + if (serials.empty()) { LOGW("no pandas found, exiting"); return; } + serial = serials[0]; } - std::string serials_str; - for (int i = 0; i < serials.size(); i++) { - serials_str += serials[i]; - if (i < serials.size() - 1) serials_str += ", "; - } - LOGW("connecting to pandas: %s", serials_str.c_str()); - - // connect to all provided serials - std::vector pandas; - for (int i = 0; i < serials.size() && !do_exit; /**/) { - Panda *p = connect(serials[i], i); - if (!p) { - util::sleep_for(100); - continue; - } + LOGW("connecting to panda: %s", serial.c_str()); - pandas.push_back(p); - ++i; + Panda *panda = nullptr; + while (!do_exit) { + panda = connect(serial); + if (panda) break; + util::sleep_for(100); } if (!do_exit) { - LOGW("connected to all pandas"); - pandad_run(pandas); + LOGW("connected to panda"); + pandad_run(panda); } - for (Panda *panda : pandas) { - delete panda; - } + delete panda; } diff --git a/selfdrive/pandad/pandad.h b/selfdrive/pandad/pandad.h index 637807e0749..aa10d1ae4b1 100644 --- a/selfdrive/pandad/pandad.h +++ b/selfdrive/pandad/pandad.h @@ -1,16 +1,15 @@ #pragma once #include -#include #include "common/params.h" #include "selfdrive/pandad/panda.h" -void pandad_main_thread(std::vector serials); +void pandad_main_thread(std::string serial); class PandaSafety { public: - PandaSafety(const std::vector &pandas) : pandas_(pandas) {} + PandaSafety(Panda *panda) : panda_(panda) {} void configureSafetyMode(bool is_onroad); private: @@ -22,6 +21,6 @@ class PandaSafety { bool log_once_ = false; bool safety_configured_ = false; bool prev_obd_multiplexing_ = false; - std::vector pandas_; + Panda *panda_; Params params_; }; diff --git a/selfdrive/pandad/pandad.py b/selfdrive/pandad/pandad.py index d5c58ddd6d1..df2b4f7ee89 100755 --- a/selfdrive/pandad/pandad.py +++ b/selfdrive/pandad/pandad.py @@ -110,46 +110,35 @@ def signal_handler(signum, frame): cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}") - # Flash pandas - pandas: list[Panda] = [] - for serial in panda_serials: - pandas.append(flash_panda(serial)) + # Flash the first panda + panda_serial = panda_serials[0] + panda = flash_panda(panda_serial) # Ensure internal panda is present if expected - internal_pandas = [panda for panda in pandas if panda.is_internal()] - if HARDWARE.has_internal_panda() and len(internal_pandas) == 0: + if HARDWARE.has_internal_panda() and not panda.is_internal(): cloudlog.error("Internal panda is missing, trying again") no_internal_panda_count += 1 continue no_internal_panda_count = 0 - # sort pandas to have deterministic order - # * the internal one is always first - # * then sort by hardware type - # * as a last resort, sort by serial number - pandas.sort(key=lambda x: (not x.is_internal(), x.get_type(), x.get_usb_serial())) - panda_serials = [p.get_usb_serial() for p in pandas] - - # log panda fw versions - params.put("PandaSignatures", b','.join(p.get_signature() for p in pandas)) - - for panda in pandas: - # check health for lost heartbeat - health = panda.health() - if health["heartbeat_lost"]: - params.put_bool("PandaHeartbeatLost", True) - cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial()) - if health["som_reset_triggered"]: - params.put_bool("PandaSomResetTriggered", True) - cloudlog.event("panda.som_reset_triggered", health=health, serial=panda.get_usb_serial()) - - if first_run: - # reset panda to ensure we're in a good state - cloudlog.info(f"Resetting panda {panda.get_usb_serial()}") - panda.reset(reconnect=True) - - for p in pandas: - p.close() + # log panda fw version + params.put("PandaSignatures", panda.get_signature()) + + # check health for lost heartbeat + health = panda.health() + if health["heartbeat_lost"]: + params.put_bool("PandaHeartbeatLost", True) + cloudlog.event("heartbeat lost", deviceState=health, serial=panda.get_usb_serial()) + if health["som_reset_triggered"]: + params.put_bool("PandaSomResetTriggered", True) + cloudlog.event("panda.som_reset_triggered", health=health, serial=panda.get_usb_serial()) + + if first_run: + # reset panda to ensure we're in a good state + cloudlog.info(f"Resetting panda {panda.get_usb_serial()}") + panda.reset(reconnect=True) + + panda.close() # TODO: wrap all panda exceptions in a base panda exception except (usb1.USBErrorNoDevice, usb1.USBErrorPipe): # a panda was disconnected while setting everything up. let's try again @@ -166,7 +155,7 @@ def signal_handler(signum, frame): # run pandad with all connected serials as arguments os.environ['MANAGER_DAEMON'] = 'pandad' - process = subprocess.Popen(["./pandad", *panda_serials], cwd=os.path.join(BASEDIR, "selfdrive/pandad")) + process = subprocess.Popen(["./pandad", panda_serial], cwd=os.path.join(BASEDIR, "selfdrive/pandad")) process.wait() diff --git a/selfdrive/pandad/spi.cc b/selfdrive/pandad/spi.cc index b6ee57801a3..25682e6b17c 100644 --- a/selfdrive/pandad/spi.cc +++ b/selfdrive/pandad/spi.cc @@ -1,4 +1,3 @@ -#ifndef __APPLE__ #include #include #include @@ -55,7 +54,7 @@ class LockEx { util::hexdump(tx_buf, std::min((int)header.tx_len, 8)).c_str()); \ } while (0) -PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) { +PandaSpiHandle::PandaSpiHandle(std::string serial) { int ret; const int uid_len = 12; uint8_t uid[uid_len] = {0}; @@ -407,4 +406,3 @@ int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx if (ret >= 0) ret = -1; return ret; } -#endif diff --git a/selfdrive/pandad/tests/test_pandad_usbprotocol.cc b/selfdrive/pandad/tests/test_pandad_canprotocol.cc similarity index 88% rename from selfdrive/pandad/tests/test_pandad_usbprotocol.cc rename to selfdrive/pandad/tests/test_pandad_canprotocol.cc index 11f7184efdb..9b53392f6ba 100644 --- a/selfdrive/pandad/tests/test_pandad_usbprotocol.cc +++ b/selfdrive/pandad/tests/test_pandad_canprotocol.cc @@ -1,13 +1,15 @@ #define CATCH_CONFIG_MAIN #define CATCH_CONFIG_ENABLE_BENCHMARKING +#include + #include "catch2/catch.hpp" #include "cereal/messaging/messaging.h" #include "common/util.h" #include "selfdrive/pandad/panda.h" struct PandaTest : public Panda { - PandaTest(uint32_t bus_offset, int can_list_size, cereal::PandaState::PandaType hw_type); + PandaTest(int can_list_size, cereal::PandaState::PandaType hw_type); void test_can_send(); void test_can_recv(uint32_t chunk_size = 0); void test_chunked_can_recv(); @@ -19,7 +21,7 @@ struct PandaTest : public Panda { capnp::List::Reader can_data_list; }; -PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState::PandaType hw_type) : can_list_size(can_list_size), Panda(bus_offset_) { +PandaTest::PandaTest(int can_list_size, cereal::PandaState::PandaType hw_type) : can_list_size(can_list_size), Panda() { this->hw_type = hw_type; int data_limit = ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? std::size(dlc_to_len) : 8); // prepare test data @@ -40,7 +42,7 @@ PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState uint32_t id = util::random_int(0, std::size(dlc_to_len) - 1); const std::string &dat = test_data[dlc_to_len[id]]; can.setAddress(i); - can.setSrc(util::random_int(0, 2) + bus_offset); + can.setSrc(util::random_int(0, 2)); can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size())); total_pakets_size += sizeof(can_header) + dat.size(); } @@ -103,9 +105,8 @@ void PandaTest::test_can_recv(uint32_t rx_chunk_size) { } TEST_CASE("send/recv CAN 2.0 packets") { - auto bus_offset = GENERATE(0, 4); auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); - PandaTest test(bus_offset, can_list_size, cereal::PandaState::PandaType::DOS); + PandaTest test(can_list_size, cereal::PandaState::PandaType::DOS); SECTION("can_send") { test.test_can_send(); @@ -119,9 +120,8 @@ TEST_CASE("send/recv CAN 2.0 packets") { } TEST_CASE("send/recv CAN FD packets") { - auto bus_offset = GENERATE(0, 4); auto can_list_size = GENERATE(1, 3, 5, 10, 30, 60, 100, 200); - PandaTest test(bus_offset, can_list_size, cereal::PandaState::PandaType::RED_PANDA); + PandaTest test(can_list_size, cereal::PandaState::PandaType::RED_PANDA); SECTION("can_send") { test.test_can_send(); diff --git a/selfdrive/pandad/tests/test_pandad_loopback.py b/selfdrive/pandad/tests/test_pandad_loopback.py index eff70d2544d..fd4a99be628 100644 --- a/selfdrive/pandad/tests/test_pandad_loopback.py +++ b/selfdrive/pandad/tests/test_pandad_loopback.py @@ -13,12 +13,11 @@ from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.selfdrive.pandad import can_list_to_can_capnp -from openpilot.system.hardware import TICI from openpilot.selfdrive.test.helpers import with_processes @retry(attempts=3) -def setup_pandad(num_pandas): +def setup_pandad(): params = Params() params.clear_all() params.put_bool("IsOnroad", False) @@ -29,16 +28,12 @@ def setup_pandad(num_pandas): any(ps.pandaType == log.PandaState.PandaType.unknown for ps in sm['pandaStates']): sm.update(1000) - found_pandas = len(sm['pandaStates']) - assert num_pandas == found_pandas, "connected pandas ({found_pandas}) doesn't match expected panda count ({num_pandas}). \ - connect another panda for multipanda tests." - # pandad safety setting relies on these params cp = car.CarParams.new_message() safety_config = car.CarParams.SafetyConfig.new_message() safety_config.safetyModel = car.CarParams.SafetyModel.allOutput - cp.safetyConfigs = [safety_config]*num_pandas + cp.safetyConfigs = [safety_config] params.put_bool("IsOnroad", True) params.put_bool("FirmwareQueryDone", True) @@ -49,12 +44,12 @@ def setup_pandad(num_pandas): while any(ps.safetyModel != car.CarParams.SafetyModel.allOutput for ps in sm['pandaStates']): sm.update(1000) -def send_random_can_messages(sendcan, count, num_pandas=1): +def send_random_can_messages(sendcan, count): sent_msgs = defaultdict(set) for _ in range(count): to_send = [] for __ in range(random.randrange(20)): - bus = random.choice([b for b in range(3*num_pandas) if b % 4 != 3]) + bus = random.choice(range(3)) addr = random.randrange(1, 1<<29) dat = bytes(random.getrandbits(8) for _ in range(random.randrange(1, 9))) if (addr, dat) in sent_msgs[bus]: @@ -74,8 +69,7 @@ def setup_class(cls): @with_processes(['pandad']) def test_loopback(self): - num_pandas = 2 if TICI and "SINGLE_PANDA" not in os.environ else 1 - setup_pandad(num_pandas) + setup_pandad() sendcan = messaging.pub_sock('sendcan') can = messaging.sub_sock('can', conflate=False, timeout=100) @@ -86,7 +80,7 @@ def test_loopback(self): for i in range(n): print(f"pandad loopback {i}/{n}") - sent_msgs = send_random_can_messages(sendcan, random.randrange(20, 100), num_pandas) + sent_msgs = send_random_can_messages(sendcan, random.randrange(20, 100)) sent_loopback = copy.deepcopy(sent_msgs) sent_loopback.update({k+128: copy.deepcopy(v) for k, v in sent_msgs.items()}) diff --git a/selfdrive/pandad/tests/test_pandad_spi.py b/selfdrive/pandad/tests/test_pandad_spi.py index da4b181993d..69dfb67e933 100644 --- a/selfdrive/pandad/tests/test_pandad_spi.py +++ b/selfdrive/pandad/tests/test_pandad_spi.py @@ -22,7 +22,7 @@ def setup_class(cls): @with_processes(['pandad']) def test_spi_corruption(self, subtests): - setup_pandad(1) + setup_pandad() sendcan = messaging.pub_sock('sendcan') socks = {s: messaging.sub_sock(s, conflate=False, timeout=100) for s in ('can', 'pandaStates', 'peripheralState')} From eea07462fad217a2e98decf7392818dbb0f22478 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Feb 2026 21:00:29 -0800 Subject: [PATCH 206/685] Drop support for Intel macOS (#37215) * Drop support for Intel macOS * arch.sh * scons * platform.sh * lil more * mv tici --- SConstruct | 17 ++-------- scripts/platform.sh | 51 ++++++++++++++++++++++++++++ third_party/acados/build.sh | 11 +++--- third_party/libyuv/build.sh | 15 ++------ third_party/raylib/build.sh | 8 ++--- tools/install_python_dependencies.sh | 2 +- tools/mac_setup.sh | 11 ++---- tools/op.sh | 28 +-------------- tools/plotjuggler/juggle.py | 2 +- 9 files changed, 69 insertions(+), 76 deletions(-) create mode 100755 scripts/platform.sh diff --git a/SConstruct b/SConstruct index 4f04be624cf..05885c0b5d2 100644 --- a/SConstruct +++ b/SConstruct @@ -2,7 +2,6 @@ import os import subprocess import sys import sysconfig -import platform import shlex import numpy as np @@ -24,19 +23,8 @@ AddOption('--minimal', default=os.path.exists(File('#.gitattributes').abspath), # minimal by default on release branch (where there's no LFS) help='the minimum build to run openpilot. no tests, tools, etc.') -# Detect platform -arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() -if platform.system() == "Darwin": - arch = "Darwin" - brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() -elif arch == "aarch64" and os.path.isfile('/TICI'): - arch = "larch64" -assert arch in [ - "larch64", # linux tici arm64 - "aarch64", # linux pc arm64 - "x86_64", # linux pc x64 - "Darwin", # macOS arm64 (x86 not supported) -] +# Detect platform (see scripts/platform.sh) +arch = subprocess.check_output(["bash", "-c", "source scripts/platform.sh >&2 && echo $OPENPILOT_ARCH"], encoding='utf8', stderr=subprocess.PIPE).rstrip() env = Environment( ENV={ @@ -103,6 +91,7 @@ if arch == "larch64": env.Append(CCFLAGS=arch_flags) env.Append(CXXFLAGS=arch_flags) elif arch == "Darwin": + brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() env.Append(LIBPATH=[ f"{brew_prefix}/lib", f"{brew_prefix}/opt/openssl@3.0/lib", diff --git a/scripts/platform.sh b/scripts/platform.sh new file mode 100755 index 00000000000..1cbd8aa6078 --- /dev/null +++ b/scripts/platform.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# +# Centralized platform and architecture detection for openpilot. +# Source this script to get OPENPILOT_ARCH set to one of: +# larch64 - linux tici arm64 +# aarch64 - linux pc arm64 +# x86_64 - linux pc x64 +# Darwin - macOS arm64 +# + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' + +OPENPILOT_ARCH=$(uname -m) + +# ── check OS and normalize arch ────────────────────────────── +if [ -f /TICI ]; then + # TICI runs AGNOS — no OS validation needed + OPENPILOT_ARCH="larch64" + +elif [[ "$OSTYPE" == "darwin"* ]]; then + if [[ "$OPENPILOT_ARCH" == "x86_64" ]]; then + echo -e " ↳ [${RED}✗${NC}] Intel-based Macs are not supported!" + echo " openpilot requires an Apple Silicon Mac (M1 or newer)." + exit 1 + fi + echo -e " ↳ [${GREEN}✔${NC}] macOS detected." + OPENPILOT_ARCH="Darwin" + +elif [[ "$OSTYPE" == "linux-gnu"* ]]; then + if [ -f "/etc/os-release" ]; then + source /etc/os-release + case "$VERSION_CODENAME" in + "jammy" | "kinetic" | "noble" | "focal") + echo -e " ↳ [${GREEN}✔${NC}] Ubuntu $VERSION_CODENAME detected." + ;; + *) + echo -e " ↳ [${RED}✗${NC}] Incompatible Ubuntu version $VERSION_CODENAME detected!" + exit 1 + ;; + esac + else + echo -e " ↳ [${RED}✗${NC}] No /etc/os-release on your system. Make sure you're running on Ubuntu, or similar!" + exit 1 + fi + +else + echo -e " ↳ [${RED}✗${NC}] OS type $OSTYPE not supported!" + exit 1 +fi diff --git a/third_party/acados/build.sh b/third_party/acados/build.sh index 2c4d839ae72..cca17435069 100755 --- a/third_party/acados/build.sh +++ b/third_party/acados/build.sh @@ -6,18 +6,18 @@ export ZERO_AR_DATE=1 DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -ARCHNAME="x86_64" +source "$DIR/../../scripts/platform.sh" +ARCHNAME="$OPENPILOT_ARCH" + BLAS_TARGET="X64_AUTOMATIC" if [ -f /TICI ]; then - ARCHNAME="larch64" BLAS_TARGET="ARMV8A_ARM_CORTEX_A57" fi ACADOS_FLAGS="-DACADOS_WITH_QPOASES=ON -UBLASFEO_TARGET -DBLASFEO_TARGET=$BLAS_TARGET" if [[ "$OSTYPE" == "darwin"* ]]; then - ACADOS_FLAGS="$ACADOS_FLAGS -DCMAKE_OSX_ARCHITECTURES=arm64;x86_64 -DCMAKE_MACOSX_RPATH=1" - ARCHNAME="Darwin" + ACADOS_FLAGS="$ACADOS_FLAGS -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_MACOSX_RPATH=1" fi if [ ! -d acados_repo/ ]; then @@ -57,8 +57,7 @@ fi cd $DIR/acados_repo/interfaces/acados_template/tera_renderer/ if [[ "$OSTYPE" == "darwin"* ]]; then cargo build --verbose --release --target aarch64-apple-darwin - cargo build --verbose --release --target x86_64-apple-darwin - lipo -create -output target/release/t_renderer target/x86_64-apple-darwin/release/t_renderer target/aarch64-apple-darwin/release/t_renderer + cp target/aarch64-apple-darwin/release/t_renderer target/release/t_renderer else cargo build --verbose --release fi diff --git a/third_party/libyuv/build.sh b/third_party/libyuv/build.sh index 35a7d947a29..0e4e10133b1 100755 --- a/third_party/libyuv/build.sh +++ b/third_party/libyuv/build.sh @@ -6,14 +6,8 @@ export ZERO_AR_DATE=1 DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -ARCHNAME=$(uname -m) -if [ -f /TICI ]; then - ARCHNAME="larch64" -fi - -if [[ "$OSTYPE" == "darwin"* ]]; then - ARCHNAME="Darwin" -fi +source "$DIR/../../scripts/platform.sh" +ARCHNAME="$OPENPILOT_ARCH" cd $DIR if [ ! -d libyuv ]; then @@ -35,8 +29,3 @@ rm -rf $DIR/include mkdir -p $INSTALL_DIR/lib cp $DIR/libyuv/libyuv.a $INSTALL_DIR/lib cp -r $DIR/libyuv/include $DIR - -## To create universal binary on Darwin: -## ``` -## lipo -create -output Darwin/libyuv.a path-to-x64/libyuv.a path-to-arm64/libyuv.a -## ``` diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index d20f9d33af1..b8408cd88c7 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -20,9 +20,9 @@ cd $DIR RAYLIB_PLATFORM="PLATFORM_DESKTOP" -ARCHNAME=$(uname -m) +source "$DIR/../../scripts/platform.sh" +ARCHNAME="$OPENPILOT_ARCH" if [ -f /TICI ]; then - ARCHNAME="larch64" RAYLIB_PLATFORM="PLATFORM_COMMA" elif [[ "$OSTYPE" == "linux"* ]]; then # required dependencies on Linux PC @@ -33,10 +33,6 @@ elif [[ "$OSTYPE" == "linux"* ]]; then libxrandr-dev fi -if [[ "$OSTYPE" == "darwin"* ]]; then - ARCHNAME="Darwin" -fi - INSTALL_DIR="$DIR/$ARCHNAME" rm -rf $INSTALL_DIR mkdir -p $INSTALL_DIR diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh index 4454845fcd0..bf7accbbd53 100755 --- a/tools/install_python_dependencies.sh +++ b/tools/install_python_dependencies.sh @@ -23,7 +23,7 @@ echo "installing python packages..." uv sync --frozen --all-extras source .venv/bin/activate -if [[ "$(uname)" == 'Darwin' ]]; then +if [[ "$OSTYPE" == "darwin"* ]]; then touch "$ROOT"/.env echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> "$ROOT"/.env fi diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 26ef4dd9a57..7e126d4f57b 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -3,7 +3,7 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" ROOT="$(cd $DIR/../ && pwd)" -ARCH=$(uname -m) +source $ROOT/scripts/platform.sh # homebrew update is slow export HOMEBREW_NO_AUTO_UPDATE=1 @@ -21,13 +21,8 @@ if [[ $(command -v brew) == "" ]]; then echo "[ ] installed brew t=$SECONDS" # make brew available now - if [[ $ARCH == "x86_64" ]]; then - echo 'eval "$(/usr/local/bin/brew shellenv)"' >> $RC_FILE - eval "$(/usr/local/bin/brew shellenv)" - else - echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> $RC_FILE - eval "$(/opt/homebrew/bin/brew shellenv)" - fi + echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> $RC_FILE + eval "$(/opt/homebrew/bin/brew shellenv)" else brew up fi diff --git a/tools/op.sh b/tools/op.sh index 8c41926e0ce..afee4cfb7ee 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -122,33 +122,7 @@ function op_check_git() { function op_check_os() { echo "Checking for compatible os version..." - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - - if [ -f "/etc/os-release" ]; then - source /etc/os-release - case "$VERSION_CODENAME" in - "jammy" | "kinetic" | "noble" | "focal") - echo -e " ↳ [${GREEN}✔${NC}] Ubuntu $VERSION_CODENAME detected." - ;; - * ) - echo -e " ↳ [${RED}✗${NC}] Incompatible Ubuntu version $VERSION_CODENAME detected!" - loge "ERROR_INCOMPATIBLE_UBUNTU" "$VERSION_CODENAME" - return 1 - ;; - esac - else - echo -e " ↳ [${RED}✗${NC}] No /etc/os-release on your system. Make sure you're running on Ubuntu, or similar!" - loge "ERROR_UNKNOWN_UBUNTU" - return 1 - fi - - elif [[ "$OSTYPE" == "darwin"* ]]; then - echo -e " ↳ [${GREEN}✔${NC}] macOS detected." - else - echo -e " ↳ [${RED}✗${NC}] OS type $OSTYPE not supported!" - loge "ERROR_UNKNOWN_OS" "$OSTYPE" - return 1 - fi + source "$OPENPILOT_ROOT/scripts/platform.sh" } function op_check_python() { diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 142e640504d..c04efd50b49 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -31,7 +31,7 @@ def install(): m = f"{platform.system()}-{platform.machine()}" - supported = ("Linux-x86_64", "Linux-aarch64", "Darwin-arm64", "Darwin-x86_64") + supported = ("Linux-x86_64", "Linux-aarch64", "Darwin-arm64") if m not in supported: raise Exception(f"Unsupported platform: '{m}'. Supported platforms: {supported}") From f67f84109e633908e922a59b1c9a05e130dd61f4 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Sat, 14 Feb 2026 21:02:11 -0800 Subject: [PATCH 207/685] [bot] Update Python packages (#37166) * Update Python packages * clear cache * try this * Revert "try this" This reverts commit 79f21ea0956509c58e5e5ba65a08ae0b2cbd204b. * Revert "clear cache" This reverts commit aa49ac5bd3b6cecb25cf9cbfe1e07ec4ad608d63. * revert for now --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- msgq_repo | 2 +- opendbc_repo | 2 +- panda | 2 +- uv.lock | 126 +++++++++++++++++++++++++-------------------------- 4 files changed, 66 insertions(+), 66 deletions(-) diff --git a/msgq_repo b/msgq_repo index 2c191c1a72a..fa514e7dd77 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit 2c191c1a72ae8119b93b49e1bc12d4a99b751855 +Subproject commit fa514e7dd77025952d94e893c622a5006e260321 diff --git a/opendbc_repo b/opendbc_repo index 143e87f3e45..245cb1f2056 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 143e87f3e4565abd1d2d70c32adcb3167d9c64ca +Subproject commit 245cb1f2056071a7625e2ad7e4515f57784515bd diff --git a/panda b/panda index b99d7969240..24fe11466de 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit b99d79692409514d296d85f367897fa2f86daaa5 +Subproject commit 24fe11466de37a1a761c16e35353ab39602e03e0 diff --git a/uv.lock b/uv.lock index 8909934e9a0..2f095265b46 100644 --- a/uv.lock +++ b/uv.lock @@ -415,11 +415,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.20.3" +version = "3.24.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/cd/fa3ab025a8f9772e8a9146d8fd8eef6d62649274d231ca84249f54a0de4a/filelock-3.24.0.tar.gz", hash = "sha256:aeeab479339ddf463a1cdd1f15a6e6894db976071e5883efc94d22ed5139044b", size = 37166, upload-time = "2026-02-14T16:05:28.723Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, + { url = "https://files.pythonhosted.org/packages/d9/dd/d7e7f4f49180e8591c9e1281d15ecf8e7f25eb2c829771d9682f1f9fe0c8/filelock-3.24.0-py3-none-any.whl", hash = "sha256:eebebb403d78363ef7be8e236b63cc6760b0004c7464dceaba3fd0afbd637ced", size = 23977, upload-time = "2026-02-14T16:05:27.578Z" }, ] [[package]] @@ -1144,30 +1144,30 @@ wheels = [ [[package]] name = "pillow" -version = "12.1.0" +version = "12.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/02/d52c733a2452ef1ffcc123b68e6606d07276b0e358db70eabad7e40042b7/pillow-12.1.0.tar.gz", hash = "sha256:5c5ae0a06e9ea030ab786b0251b32c7e4ce10e58d983c0d5c56029455180b5b9", size = 46977283, upload-time = "2026-01-02T09:13:29.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/31/dc53fe21a2f2996e1b7d92bf671cdb157079385183ef7c1ae08b485db510/pillow-12.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a332ac4ccb84b6dde65dbace8431f3af08874bf9770719d32a635c4ef411b18b", size = 5262642, upload-time = "2026-01-02T09:11:10.138Z" }, - { url = "https://files.pythonhosted.org/packages/ab/c1/10e45ac9cc79419cedf5121b42dcca5a50ad2b601fa080f58c22fb27626e/pillow-12.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:907bfa8a9cb790748a9aa4513e37c88c59660da3bcfffbd24a7d9e6abf224551", size = 4657464, upload-time = "2026-01-02T09:11:12.319Z" }, - { url = "https://files.pythonhosted.org/packages/ad/26/7b82c0ab7ef40ebede7a97c72d473bda5950f609f8e0c77b04af574a0ddb/pillow-12.1.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efdc140e7b63b8f739d09a99033aa430accce485ff78e6d311973a67b6bf3208", size = 6234878, upload-time = "2026-01-02T09:11:14.096Z" }, - { url = "https://files.pythonhosted.org/packages/76/25/27abc9792615b5e886ca9411ba6637b675f1b77af3104710ac7353fe5605/pillow-12.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bef9768cab184e7ae6e559c032e95ba8d07b3023c289f79a2bd36e8bf85605a5", size = 8044868, upload-time = "2026-01-02T09:11:15.903Z" }, - { url = "https://files.pythonhosted.org/packages/0a/ea/f200a4c36d836100e7bc738fc48cd963d3ba6372ebc8298a889e0cfc3359/pillow-12.1.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:742aea052cf5ab5034a53c3846165bc3ce88d7c38e954120db0ab867ca242661", size = 6349468, upload-time = "2026-01-02T09:11:17.631Z" }, - { url = "https://files.pythonhosted.org/packages/11/8f/48d0b77ab2200374c66d344459b8958c86693be99526450e7aee714e03e4/pillow-12.1.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6dfc2af5b082b635af6e08e0d1f9f1c4e04d17d4e2ca0ef96131e85eda6eb17", size = 7041518, upload-time = "2026-01-02T09:11:19.389Z" }, - { url = "https://files.pythonhosted.org/packages/1d/23/c281182eb986b5d31f0a76d2a2c8cd41722d6fb8ed07521e802f9bba52de/pillow-12.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:609e89d9f90b581c8d16358c9087df76024cf058fa693dd3e1e1620823f39670", size = 6462829, upload-time = "2026-01-02T09:11:21.28Z" }, - { url = "https://files.pythonhosted.org/packages/25/ef/7018273e0faac099d7b00982abdcc39142ae6f3bd9ceb06de09779c4a9d6/pillow-12.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:43b4899cfd091a9693a1278c4982f3e50f7fb7cff5153b05174b4afc9593b616", size = 7166756, upload-time = "2026-01-02T09:11:23.559Z" }, - { url = "https://files.pythonhosted.org/packages/8f/c8/993d4b7ab2e341fe02ceef9576afcf5830cdec640be2ac5bee1820d693d4/pillow-12.1.0-cp312-cp312-win32.whl", hash = "sha256:aa0c9cc0b82b14766a99fbe6084409972266e82f459821cd26997a488a7261a7", size = 6328770, upload-time = "2026-01-02T09:11:25.661Z" }, - { url = "https://files.pythonhosted.org/packages/a7/87/90b358775a3f02765d87655237229ba64a997b87efa8ccaca7dd3e36e7a7/pillow-12.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:d70534cea9e7966169ad29a903b99fc507e932069a881d0965a1a84bb57f6c6d", size = 7033406, upload-time = "2026-01-02T09:11:27.474Z" }, - { url = "https://files.pythonhosted.org/packages/5d/cf/881b457eccacac9e5b2ddd97d5071fb6d668307c57cbf4e3b5278e06e536/pillow-12.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:65b80c1ee7e14a87d6a068dd3b0aea268ffcabfe0498d38661b00c5b4b22e74c", size = 2452612, upload-time = "2026-01-02T09:11:29.309Z" }, + { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, + { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, + { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, + { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, + { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, + { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, + { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, + { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, + { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, + { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, ] [[package]] name = "platformdirs" -version = "4.5.1" +version = "4.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/86/0248f086a84f01b37aaec0fa567b397df1a119f73c16f6c7a9aac73ea309/platformdirs-4.5.1.tar.gz", hash = "sha256:61d5cdcc6065745cdd94f0f878977f8de9437be93de97c1c12f853c9c0cdcbda", size = 21715, upload-time = "2025-12-05T13:52:58.638Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/d5/763666321efaded11112de8b7a7f2273dd8d1e205168e73c334e54b0ab9a/platformdirs-4.9.1.tar.gz", hash = "sha256:f310f16e89c4e29117805d8328f7c10876eeff36c94eac879532812110f7d39f", size = 28392, upload-time = "2026-02-14T21:02:44.973Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, + { url = "https://files.pythonhosted.org/packages/70/77/e8c95e95f1d4cdd88c90a96e31980df7e709e51059fac150046ad67fac63/platformdirs-4.9.1-py3-none-any.whl", hash = "sha256:61d8b967d34791c162d30d60737369cbbd77debad5b981c4bfda1842e71e0d66", size = 21307, upload-time = "2026-02-14T21:02:43.492Z" }, ] [[package]] @@ -1328,14 +1328,14 @@ wheels = [ [[package]] name = "pyee" -version = "13.0.0" +version = "13.0.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/03/1fd98d5841cd7964a27d729ccf2199602fe05eb7a405c1462eb7277945ed/pyee-13.0.0.tar.gz", hash = "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37", size = 31250, upload-time = "2025-03-17T18:53:15.955Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/04/e7c1fe4dc78a6fdbfd6c337b1c3732ff543b8a397683ab38378447baa331/pyee-13.0.1.tar.gz", hash = "sha256:0b931f7c14535667ed4c7e0d531716368715e860b988770fc7eb8578d1f67fc8", size = 31655, upload-time = "2026-02-14T21:12:28.044Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/4d/b9add7c84060d4c1906abe9a7e5359f2a60f7a9a4f67268b2766673427d8/pyee-13.0.0-py3-none-any.whl", hash = "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", size = 15730, upload-time = "2025-03-17T18:53:14.532Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659, upload-time = "2026-02-14T21:12:26.263Z" }, ] [[package]] @@ -4055,27 +4055,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/39/5cee96809fbca590abea6b46c6d1c586b49663d1d2830a751cc8fc42c666/ruff-0.15.0.tar.gz", hash = "sha256:6bdea47cdbea30d40f8f8d7d69c0854ba7c15420ec75a26f463290949d7f7e9a", size = 4524893, upload-time = "2026-02-03T17:53:35.357Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/88/3fd1b0aa4b6330d6aaa63a285bc96c9f71970351579152d231ed90914586/ruff-0.15.0-py3-none-linux_armv6l.whl", hash = "sha256:aac4ebaa612a82b23d45964586f24ae9bc23ca101919f5590bdb368d74ad5455", size = 10354332, upload-time = "2026-02-03T17:52:54.892Z" }, - { url = "https://files.pythonhosted.org/packages/72/f6/62e173fbb7eb75cc29fe2576a1e20f0a46f671a2587b5f604bfb0eaf5f6f/ruff-0.15.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:dcd4be7cc75cfbbca24a98d04d0b9b36a270d0833241f776b788d59f4142b14d", size = 10767189, upload-time = "2026-02-03T17:53:19.778Z" }, - { url = "https://files.pythonhosted.org/packages/99/e4/968ae17b676d1d2ff101d56dc69cf333e3a4c985e1ec23803df84fc7bf9e/ruff-0.15.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d747e3319b2bce179c7c1eaad3d884dc0a199b5f4d5187620530adf9105268ce", size = 10075384, upload-time = "2026-02-03T17:53:29.241Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bf/9843c6044ab9e20af879c751487e61333ca79a2c8c3058b15722386b8cae/ruff-0.15.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:650bd9c56ae03102c51a5e4b554d74d825ff3abe4db22b90fd32d816c2e90621", size = 10481363, upload-time = "2026-02-03T17:52:43.332Z" }, - { url = "https://files.pythonhosted.org/packages/55/d9/4ada5ccf4cd1f532db1c8d44b6f664f2208d3d93acbeec18f82315e15193/ruff-0.15.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6664b7eac559e3048223a2da77769c2f92b43a6dfd4720cef42654299a599c9", size = 10187736, upload-time = "2026-02-03T17:53:00.522Z" }, - { url = "https://files.pythonhosted.org/packages/86/e2/f25eaecd446af7bb132af0a1d5b135a62971a41f5366ff41d06d25e77a91/ruff-0.15.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f811f97b0f092b35320d1556f3353bf238763420ade5d9e62ebd2b73f2ff179", size = 10968415, upload-time = "2026-02-03T17:53:15.705Z" }, - { url = "https://files.pythonhosted.org/packages/e7/dc/f06a8558d06333bf79b497d29a50c3a673d9251214e0d7ec78f90b30aa79/ruff-0.15.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:761ec0a66680fab6454236635a39abaf14198818c8cdf691e036f4bc0f406b2d", size = 11809643, upload-time = "2026-02-03T17:53:23.031Z" }, - { url = "https://files.pythonhosted.org/packages/dd/45/0ece8db2c474ad7df13af3a6d50f76e22a09d078af63078f005057ca59eb/ruff-0.15.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:940f11c2604d317e797b289f4f9f3fa5555ffe4fb574b55ed006c3d9b6f0eb78", size = 11234787, upload-time = "2026-02-03T17:52:46.432Z" }, - { url = "https://files.pythonhosted.org/packages/8a/d9/0e3a81467a120fd265658d127db648e4d3acfe3e4f6f5d4ea79fac47e587/ruff-0.15.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcbca3d40558789126da91d7ef9a7c87772ee107033db7191edefa34e2c7f1b4", size = 11112797, upload-time = "2026-02-03T17:52:49.274Z" }, - { url = "https://files.pythonhosted.org/packages/b2/cb/8c0b3b0c692683f8ff31351dfb6241047fa873a4481a76df4335a8bff716/ruff-0.15.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:9a121a96db1d75fa3eb39c4539e607f628920dd72ff1f7c5ee4f1b768ac62d6e", size = 11033133, upload-time = "2026-02-03T17:53:33.105Z" }, - { url = "https://files.pythonhosted.org/packages/f8/5e/23b87370cf0f9081a8c89a753e69a4e8778805b8802ccfe175cc410e50b9/ruff-0.15.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5298d518e493061f2eabd4abd067c7e4fb89e2f63291c94332e35631c07c3662", size = 10442646, upload-time = "2026-02-03T17:53:06.278Z" }, - { url = "https://files.pythonhosted.org/packages/e1/9a/3c94de5ce642830167e6d00b5c75aacd73e6347b4c7fc6828699b150a5ee/ruff-0.15.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:afb6e603d6375ff0d6b0cee563fa21ab570fd15e65c852cb24922cef25050cf1", size = 10195750, upload-time = "2026-02-03T17:53:26.084Z" }, - { url = "https://files.pythonhosted.org/packages/30/15/e396325080d600b436acc970848d69df9c13977942fb62bb8722d729bee8/ruff-0.15.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:77e515f6b15f828b94dc17d2b4ace334c9ddb7d9468c54b2f9ed2b9c1593ef16", size = 10676120, upload-time = "2026-02-03T17:53:09.363Z" }, - { url = "https://files.pythonhosted.org/packages/8d/c9/229a23d52a2983de1ad0fb0ee37d36e0257e6f28bfd6b498ee2c76361874/ruff-0.15.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6f6e80850a01eb13b3e42ee0ebdf6e4497151b48c35051aab51c101266d187a3", size = 11201636, upload-time = "2026-02-03T17:52:57.281Z" }, - { url = "https://files.pythonhosted.org/packages/6f/b0/69adf22f4e24f3677208adb715c578266842e6e6a3cc77483f48dd999ede/ruff-0.15.0-py3-none-win32.whl", hash = "sha256:238a717ef803e501b6d51e0bdd0d2c6e8513fe9eec14002445134d3907cd46c3", size = 10465945, upload-time = "2026-02-03T17:53:12.591Z" }, - { url = "https://files.pythonhosted.org/packages/51/ad/f813b6e2c97e9b4598be25e94a9147b9af7e60523b0cb5d94d307c15229d/ruff-0.15.0-py3-none-win_amd64.whl", hash = "sha256:dd5e4d3301dc01de614da3cdffc33d4b1b96fb89e45721f1598e5532ccf78b18", size = 11564657, upload-time = "2026-02-03T17:52:51.893Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b0/2d823f6e77ebe560f4e397d078487e8d52c1516b331e3521bc75db4272ca/ruff-0.15.0-py3-none-win_arm64.whl", hash = "sha256:c480d632cc0ca3f0727acac8b7d053542d9e114a462a145d0b00e7cd658c515a", size = 10865753, upload-time = "2026-02-03T17:53:03.014Z" }, +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" }, + { url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" }, + { url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" }, + { url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" }, + { url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" }, + { url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" }, + { url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" }, + { url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" }, ] [[package]] @@ -4212,26 +4212,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/25/257602d316b9333089b688a7a11b33ebc660b74e8dacf400dc3dfdea1594/ty-0.0.15.tar.gz", hash = "sha256:4f9a5b8df208c62dba56e91b93bed8b5bb714839691b8cff16d12c983bfa1174", size = 5101936, upload-time = "2026-02-05T01:06:34.922Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ce/c5/35626e732b79bf0e6213de9f79aff59b5f247c0a1e3ce0d93e675ab9b728/ty-0.0.15-py3-none-linux_armv6l.whl", hash = "sha256:68e092458516c61512dac541cde0a5e4e5842df00b4e81881ead8f745ddec794", size = 10138374, upload-time = "2026-02-05T01:07:03.804Z" }, - { url = "https://files.pythonhosted.org/packages/d5/8a/48fd81664604848f79d03879b3ca3633762d457a069b07e09fb1b87edd6e/ty-0.0.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79f2e75289eae3cece94c51118b730211af4ba5762906f52a878041b67e54959", size = 9947858, upload-time = "2026-02-05T01:06:47.453Z" }, - { url = "https://files.pythonhosted.org/packages/b6/85/c1ac8e97bcd930946f4c94db85b675561d590b4e72703bf3733419fc3973/ty-0.0.15-py3-none-macosx_11_0_arm64.whl", hash = "sha256:112a7b26e63e48cc72c8c5b03227d1db280cfa57a45f2df0e264c3a016aa8c3c", size = 9443220, upload-time = "2026-02-05T01:06:44.98Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d9/244bc02599d950f7a4298fbc0c1b25cc808646b9577bdf7a83470b2d1cec/ty-0.0.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71f62a2644972975a657d9dc867bf901235cde51e8d24c20311067e7afd44a56", size = 9949976, upload-time = "2026-02-05T01:07:01.515Z" }, - { url = "https://files.pythonhosted.org/packages/7e/ab/3a0daad66798c91a33867a3ececf17d314ac65d4ae2bbbd28cbfde94da63/ty-0.0.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9e48b42be2d257317c85b78559233273b655dd636fc61e7e1d69abd90fd3cba4", size = 9965918, upload-time = "2026-02-05T01:06:54.283Z" }, - { url = "https://files.pythonhosted.org/packages/39/4e/e62b01338f653059a7c0cd09d1a326e9a9eedc351a0f0de9db0601658c3d/ty-0.0.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27dd5b52a421e6871c5bfe9841160331b60866ed2040250cb161886478ab3e4f", size = 10424943, upload-time = "2026-02-05T01:07:08.777Z" }, - { url = "https://files.pythonhosted.org/packages/65/b5/7aa06655ce69c0d4f3e845d2d85e79c12994b6d84c71699cfb437e0bc8cf/ty-0.0.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76b85c9ec2219e11c358a7db8e21b7e5c6674a1fb9b6f633836949de98d12286", size = 10964692, upload-time = "2026-02-05T01:06:37.103Z" }, - { url = "https://files.pythonhosted.org/packages/13/04/36fdfe1f3c908b471e246e37ce3d011175584c26d3853e6c5d9a0364564c/ty-0.0.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9e8204c61d8ede4f21f2975dce74efdb80fafb2fae1915c666cceb33ea3c90b", size = 10692225, upload-time = "2026-02-05T01:06:49.714Z" }, - { url = "https://files.pythonhosted.org/packages/13/41/5bf882649bd8b64ded5fbce7fb8d77fb3b868de1a3b1a6c4796402b47308/ty-0.0.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af87c3be7c944bb4d6609d6c63e4594944b0028c7bd490a525a82b88fe010d6d", size = 10516776, upload-time = "2026-02-05T01:06:52.047Z" }, - { url = "https://files.pythonhosted.org/packages/56/75/66852d7e004f859839c17ffe1d16513c1e7cc04bcc810edb80ca022a9124/ty-0.0.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:50dccf7398505e5966847d366c9e4c650b8c225411c2a68c32040a63b9521eea", size = 9928828, upload-time = "2026-02-05T01:06:56.647Z" }, - { url = "https://files.pythonhosted.org/packages/65/72/96bc16c7b337a3ef358fd227b3c8ef0c77405f3bfbbfb59ee5915f0d9d71/ty-0.0.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:bd797b8f231a4f4715110259ad1ad5340a87b802307f3e06d92bfb37b858a8f3", size = 9978960, upload-time = "2026-02-05T01:06:29.567Z" }, - { url = "https://files.pythonhosted.org/packages/a0/18/d2e316a35b626de2227f832cd36d21205e4f5d96fd036a8af84c72ecec1b/ty-0.0.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9deb7f20e18b25440a9aa4884f934ba5628ef456dbde91819d5af1a73da48af3", size = 10135903, upload-time = "2026-02-05T01:06:59.256Z" }, - { url = "https://files.pythonhosted.org/packages/02/d3/b617a79c9dad10c888d7c15cd78859e0160b8772273637b9c4241a049491/ty-0.0.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7b31b3de031255b90a5f4d9cb3d050feae246067c87130e5a6861a8061c71754", size = 10615879, upload-time = "2026-02-05T01:07:06.661Z" }, - { url = "https://files.pythonhosted.org/packages/fb/b0/2652a73c71c77296a6343217063f05745da60c67b7e8a8e25f2064167fce/ty-0.0.15-py3-none-win32.whl", hash = "sha256:9362c528ceb62c89d65c216336d28d500bc9f4c10418413f63ebc16886e16cc1", size = 9578058, upload-time = "2026-02-05T01:06:42.928Z" }, - { url = "https://files.pythonhosted.org/packages/84/6e/08a4aedebd2a6ce2784b5bc3760e43d1861f1a184734a78215c2d397c1df/ty-0.0.15-py3-none-win_amd64.whl", hash = "sha256:4db040695ae67c5524f59cb8179a8fa277112e69042d7dfdac862caa7e3b0d9c", size = 10457112, upload-time = "2026-02-05T01:06:39.885Z" }, - { url = "https://files.pythonhosted.org/packages/b3/be/1991f2bc12847ae2d4f1e3ac5dcff8bb7bc1261390645c0755bb55616355/ty-0.0.15-py3-none-win_arm64.whl", hash = "sha256:e5a98d4119e77d6136461e16ae505f8f8069002874ab073de03fbcb1a5e8bf25", size = 9937490, upload-time = "2026-02-05T01:06:32.388Z" }, +version = "0.0.17" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c3/41ae6346443eedb65b96761abfab890a48ce2aa5a8a27af69c5c5d99064d/ty-0.0.17.tar.gz", hash = "sha256:847ed6c120913e280bf9b54d8eaa7a1049708acb8824ad234e71498e8ad09f97", size = 5167209, upload-time = "2026-02-13T13:26:36.835Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c0/01/0ef15c22a1c54b0f728ceff3f62d478dbf8b0dcf8ff7b80b954f79584f3e/ty-0.0.17-py3-none-linux_armv6l.whl", hash = "sha256:64a9a16555cc8867d35c2647c2f1afbd3cae55f68fd95283a574d1bb04fe93e0", size = 10192793, upload-time = "2026-02-13T13:27:13.943Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/f4c322d9cded56edc016b1092c14b95cf58c8a33b4787316ea752bb9418e/ty-0.0.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eb2dbd8acd5c5a55f4af0d479523e7c7265a88542efe73ed3d696eb1ba7b6454", size = 10051977, upload-time = "2026-02-13T13:26:57.741Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a5/43746c1ff81e784f5fc303afc61fe5bcd85d0fcf3ef65cb2cef78c7486c7/ty-0.0.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f18f5fd927bc628deb9ea2df40f06b5f79c5ccf355db732025a3e8e7152801f6", size = 9564639, upload-time = "2026-02-13T13:26:42.781Z" }, + { url = "https://files.pythonhosted.org/packages/d6/b8/280b04e14a9c0474af574f929fba2398b5e1c123c1e7735893b4cd73d13c/ty-0.0.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5383814d1d7a5cc53b3b07661856bab04bb2aac7a677c8d33c55169acdaa83df", size = 10061204, upload-time = "2026-02-13T13:27:00.152Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d7/493e1607d8dfe48288d8a768a2adc38ee27ef50e57f0af41ff273987cda0/ty-0.0.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c20423b8744b484f93e7bf2ef8a9724bca2657873593f9f41d08bd9f83444c9", size = 10013116, upload-time = "2026-02-13T13:26:34.543Z" }, + { url = "https://files.pythonhosted.org/packages/80/ef/22f3ed401520afac90dbdf1f9b8b7755d85b0d5c35c1cb35cf5bd11b59c2/ty-0.0.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6f5b1aba97db9af86517b911674b02f5bc310750485dc47603a105bd0e83ddd", size = 10533623, upload-time = "2026-02-13T13:26:31.449Z" }, + { url = "https://files.pythonhosted.org/packages/75/ce/744b15279a11ac7138832e3a55595706b4a8a209c9f878e3ab8e571d9032/ty-0.0.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:488bce1a9bea80b851a97cd34c4d2ffcd69593d6c3f54a72ae02e5c6e47f3d0c", size = 11069750, upload-time = "2026-02-13T13:26:48.638Z" }, + { url = "https://files.pythonhosted.org/packages/f2/be/1133c91f15a0e00d466c24f80df486d630d95d1b2af63296941f7473812f/ty-0.0.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8df66b91ec84239420985ec215e7f7549bfda2ac036a3b3c065f119d1c06825a", size = 10870862, upload-time = "2026-02-13T13:26:54.715Z" }, + { url = "https://files.pythonhosted.org/packages/3e/4a/a2ed209ef215b62b2d3246e07e833081e07d913adf7e0448fc204be443d6/ty-0.0.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:002139e807c53002790dfefe6e2f45ab0e04012e76db3d7c8286f96ec121af8f", size = 10628118, upload-time = "2026-02-13T13:26:45.439Z" }, + { url = "https://files.pythonhosted.org/packages/b3/0c/87476004cb5228e9719b98afffad82c3ef1f84334bde8527bcacba7b18cb/ty-0.0.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6c4e01f05ce82e5d489ab3900ca0899a56c4ccb52659453780c83e5b19e2b64c", size = 10038185, upload-time = "2026-02-13T13:27:02.693Z" }, + { url = "https://files.pythonhosted.org/packages/46/4b/98f0b3ba9aef53c1f0305519536967a4aa793a69ed72677b0a625c5313ac/ty-0.0.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2b226dd1e99c0d2152d218c7e440150d1a47ce3c431871f0efa073bbf899e881", size = 10047644, upload-time = "2026-02-13T13:27:05.474Z" }, + { url = "https://files.pythonhosted.org/packages/93/e0/06737bb80aa1a9103b8651d2eb691a7e53f1ed54111152be25f4a02745db/ty-0.0.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8b11f1da7859e0ad69e84b3c5ef9a7b055ceed376a432fad44231bdfc48061c2", size = 10231140, upload-time = "2026-02-13T13:27:10.844Z" }, + { url = "https://files.pythonhosted.org/packages/7c/79/e2a606bd8852383ba9abfdd578f4a227bd18504145381a10a5f886b4e751/ty-0.0.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c04e196809ff570559054d3e011425fd7c04161529eb551b3625654e5f2434cb", size = 10718344, upload-time = "2026-02-13T13:26:51.66Z" }, + { url = "https://files.pythonhosted.org/packages/c5/2d/2663984ac11de6d78f74432b8b14ba64d170b45194312852b7543cf7fd56/ty-0.0.17-py3-none-win32.whl", hash = "sha256:305b6ed150b2740d00a817b193373d21f0767e10f94ac47abfc3b2e5a5aec809", size = 9672932, upload-time = "2026-02-13T13:27:08.522Z" }, + { url = "https://files.pythonhosted.org/packages/de/b5/39be78f30b31ee9f5a585969930c7248354db90494ff5e3d0756560fb731/ty-0.0.17-py3-none-win_amd64.whl", hash = "sha256:531828267527aee7a63e972f54e5eee21d9281b72baf18e5c2850c6b862add83", size = 10542138, upload-time = "2026-02-13T13:27:17.084Z" }, + { url = "https://files.pythonhosted.org/packages/40/b7/f875c729c5d0079640c75bad2c7e5d43edc90f16ba242f28a11966df8f65/ty-0.0.17-py3-none-win_arm64.whl", hash = "sha256:de9810234c0c8d75073457e10a84825b9cd72e6629826b7f01c7a0b266ae25b1", size = 10023068, upload-time = "2026-02-13T13:26:39.637Z" }, ] [[package]] From ced5f417b88c62f26248ab6e604d1c9bd182d31a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Feb 2026 21:16:26 -0800 Subject: [PATCH 208/685] MetaDrive: slim down & enable CI test (#37216) * MetaDrive slimming * enable * lock * modeld fix * minimal --- .github/workflows/tests.yaml | 1 - pyproject.toml | 2 +- selfdrive/modeld/modeld.py | 7 +- uv.lock | 155 +---------------------------------- 4 files changed, 8 insertions(+), 157 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index fe7c8ef3360..aab0f58b0a0 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -238,7 +238,6 @@ jobs: (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} - if: false # FIXME: Started to timeout recently steps: - uses: actions/checkout@v6 with: diff --git a/pyproject.toml b/pyproject.toml index f959c613cc9..2a6d619f7fd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,7 +105,7 @@ dev = [ ] tools = [ - "metadrive-simulator @ https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl ; (platform_machine != 'aarch64')", + "metadrive-simulator @ git+https://github.com/commaai/metadrive.git@minimal ; (platform_machine != 'aarch64')", "dearpygui>=2.1.0; (sys_platform != 'linux' or platform_machine != 'aarch64')", # not vended for linux aarch64 ] diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index a8b633bf020..3fe3e0e6d6a 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -203,9 +203,10 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], ptr = bufs[key].data.ctypes.data yuv_size = self.frame_buf_params[key][3] # There is a ringbuffer of imgs, just cache tensors pointing to all of them - if ptr not in self._blob_cache: - self._blob_cache[ptr] = Tensor.from_blob(ptr, (yuv_size,), dtype='uint8') - self.full_frames[key] = self._blob_cache[ptr] + cache_key = (key, ptr) + if cache_key not in self._blob_cache: + self._blob_cache[cache_key] = Tensor.from_blob(ptr, (yuv_size,), dtype='uint8') + self.full_frames[key] = self._blob_cache[cache_key] for key in bufs.keys(): self.transforms_np[key][:,:] = transforms[key][:,:] diff --git a/uv.lock b/uv.lock index 2f095265b46..cd077d3de7d 100644 --- a/uv.lock +++ b/uv.lock @@ -205,15 +205,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, ] -[[package]] -name = "cloudpickle" -version = "3.1.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, -] - [[package]] name = "codespell" version = "2.4.1" @@ -404,24 +395,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] -[[package]] -name = "farama-notifications" -version = "0.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/2c/8384832b7a6b1fd6ba95bbdcae26e7137bb3eedc955c42fd5cdcc086cfbf/Farama-Notifications-0.0.4.tar.gz", hash = "sha256:13fceff2d14314cf80703c8266462ebf3733c7d165336eee998fc58e545efd18", size = 2131, upload-time = "2023-02-27T18:28:41.047Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/2c/ffc08c54c05cdce6fbed2aeebc46348dbe180c6d2c541c7af7ba0aa5f5f8/Farama_Notifications-0.0.4-py3-none-any.whl", hash = "sha256:14de931035a41961f7c056361dc7f980762a143d05791ef5794a751a2caf05ae", size = 2511, upload-time = "2023-02-27T18:28:39.447Z" }, -] - -[[package]] -name = "filelock" -version = "3.24.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/00/cd/fa3ab025a8f9772e8a9146d8fd8eef6d62649274d231ca84249f54a0de4a/filelock-3.24.0.tar.gz", hash = "sha256:aeeab479339ddf463a1cdd1f15a6e6894db976071e5883efc94d22ed5139044b", size = 37166, upload-time = "2026-02-14T16:05:28.723Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/dd/d7e7f4f49180e8591c9e1281d15ecf8e7f25eb2c829771d9682f1f9fe0c8/filelock-3.24.0-py3-none-any.whl", hash = "sha256:eebebb403d78363ef7be8e236b63cc6760b0004c7464dceaba3fd0afbd637ced", size = 23977, upload-time = "2026-02-14T16:05:27.578Z" }, -] - [[package]] name = "fonttools" version = "4.61.1" @@ -489,21 +462,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/c0/87c2073e0c72515bb8733d4eef7b21548e8d189f094b5dad20b0ecaf64f6/google_crc32c-1.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:3cc0c8912038065eafa603b238abf252e204accab2a704c63b9e14837a854962", size = 34437, upload-time = "2025-12-16T00:35:21.395Z" }, ] -[[package]] -name = "gymnasium" -version = "1.2.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cloudpickle" }, - { name = "farama-notifications" }, - { name = "numpy" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/76/59/653a9417d98ed3e29ef9734ba52c3495f6c6823b8d5c0c75369f25111708/gymnasium-1.2.3.tar.gz", hash = "sha256:2b2cb5b5fbbbdf3afb9f38ca952cc48aa6aa3e26561400d940747fda3ad42509", size = 829230, upload-time = "2025-12-18T16:51:10.234Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/d3/ea5f088e3638dbab12e5c20d6559d5b3bdaeaa1f2af74e526e6815836285/gymnasium-1.2.3-py3-none-any.whl", hash = "sha256:e6314bba8f549c7fdcc8677f7cd786b64908af6e79b57ddaa5ce1825bffb5373", size = 952113, upload-time = "2025-12-18T16:51:08.445Z" }, -] - [[package]] name = "hypothesis" version = "6.47.5" @@ -615,32 +573,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/6d/344a164d32d65d503ffe9201cd74cf13a020099dc446554d1e50b07f167b/libusb1-3.3.1-py3-none-win_amd64.whl", hash = "sha256:6e21b772d80d6487fbb55d3d2141218536db302da82f1983754e96c72781c102", size = 141080, upload-time = "2025-03-24T05:36:46.594Z" }, ] -[[package]] -name = "lxml" -version = "6.0.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/aa/88/262177de60548e5a2bfc46ad28232c9e9cbde697bd94132aeb80364675cb/lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62", size = 4073426, upload-time = "2025-09-22T04:04:59.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/c8/8ff2bc6b920c84355146cd1ab7d181bc543b89241cfb1ebee824a7c81457/lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456", size = 8661887, upload-time = "2025-09-22T04:01:17.265Z" }, - { url = "https://files.pythonhosted.org/packages/37/6f/9aae1008083bb501ef63284220ce81638332f9ccbfa53765b2b7502203cf/lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924", size = 4667818, upload-time = "2025-09-22T04:01:19.688Z" }, - { url = "https://files.pythonhosted.org/packages/f1/ca/31fb37f99f37f1536c133476674c10b577e409c0a624384147653e38baf2/lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f", size = 4950807, upload-time = "2025-09-22T04:01:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/da/87/f6cb9442e4bada8aab5ae7e1046264f62fdbeaa6e3f6211b93f4c0dd97f1/lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534", size = 5109179, upload-time = "2025-09-22T04:01:23.32Z" }, - { url = "https://files.pythonhosted.org/packages/c8/20/a7760713e65888db79bbae4f6146a6ae5c04e4a204a3c48896c408cd6ed2/lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564", size = 5023044, upload-time = "2025-09-22T04:01:25.118Z" }, - { url = "https://files.pythonhosted.org/packages/a2/b0/7e64e0460fcb36471899f75831509098f3fd7cd02a3833ac517433cb4f8f/lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f", size = 5359685, upload-time = "2025-09-22T04:01:27.398Z" }, - { url = "https://files.pythonhosted.org/packages/b9/e1/e5df362e9ca4e2f48ed6411bd4b3a0ae737cc842e96877f5bf9428055ab4/lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0", size = 5654127, upload-time = "2025-09-22T04:01:29.629Z" }, - { url = "https://files.pythonhosted.org/packages/c6/d1/232b3309a02d60f11e71857778bfcd4acbdb86c07db8260caf7d008b08f8/lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192", size = 5253958, upload-time = "2025-09-22T04:01:31.535Z" }, - { url = "https://files.pythonhosted.org/packages/35/35/d955a070994725c4f7d80583a96cab9c107c57a125b20bb5f708fe941011/lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0", size = 4711541, upload-time = "2025-09-22T04:01:33.801Z" }, - { url = "https://files.pythonhosted.org/packages/1e/be/667d17363b38a78c4bd63cfd4b4632029fd68d2c2dc81f25ce9eb5224dd5/lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092", size = 5267426, upload-time = "2025-09-22T04:01:35.639Z" }, - { url = "https://files.pythonhosted.org/packages/ea/47/62c70aa4a1c26569bc958c9ca86af2bb4e1f614e8c04fb2989833874f7ae/lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f", size = 5064917, upload-time = "2025-09-22T04:01:37.448Z" }, - { url = "https://files.pythonhosted.org/packages/bd/55/6ceddaca353ebd0f1908ef712c597f8570cc9c58130dbb89903198e441fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8", size = 4788795, upload-time = "2025-09-22T04:01:39.165Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e8/fd63e15da5e3fd4c2146f8bbb3c14e94ab850589beab88e547b2dbce22e1/lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f", size = 5676759, upload-time = "2025-09-22T04:01:41.506Z" }, - { url = "https://files.pythonhosted.org/packages/76/47/b3ec58dc5c374697f5ba37412cd2728f427d056315d124dd4b61da381877/lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6", size = 5255666, upload-time = "2025-09-22T04:01:43.363Z" }, - { url = "https://files.pythonhosted.org/packages/19/93/03ba725df4c3d72afd9596eef4a37a837ce8e4806010569bedfcd2cb68fd/lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322", size = 5277989, upload-time = "2025-09-22T04:01:45.215Z" }, - { url = "https://files.pythonhosted.org/packages/c6/80/c06de80bfce881d0ad738576f243911fccf992687ae09fd80b734712b39c/lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849", size = 3611456, upload-time = "2025-09-22T04:01:48.243Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d7/0cdfb6c3e30893463fb3d1e52bc5f5f99684a03c29a0b6b605cfae879cd5/lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f", size = 4011793, upload-time = "2025-09-22T04:01:50.042Z" }, - { url = "https://files.pythonhosted.org/packages/ea/7b/93c73c67db235931527301ed3785f849c78991e2e34f3fd9a6663ffda4c5/lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6", size = 3672836, upload-time = "2025-09-22T04:01:52.145Z" }, -] - [[package]] name = "mapbox-earcut" version = "2.0.0" @@ -726,58 +658,14 @@ wheels = [ [[package]] name = "metadrive-simulator" -version = "0.4.2.4" -source = { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" } +version = "0.4.2.3" +source = { git = "https://github.com/commaai/metadrive.git?rev=minimal#2716f55a9c7b928ce957a497a15c2c19840c08bc" } dependencies = [ - { name = "filelock" }, - { name = "gymnasium" }, - { name = "lxml" }, - { name = "matplotlib" }, { name = "numpy" }, - { name = "opencv-python-headless" }, { name = "panda3d" }, { name = "panda3d-gltf" }, - { name = "pillow" }, - { name = "progressbar" }, - { name = "psutil" }, - { name = "pygments" }, - { name = "requests" }, - { name = "shapely" }, - { name = "tqdm" }, - { name = "yapf" }, -] -wheels = [ - { url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl", hash = "sha256:d0afaf3b005e35e14b929d5491d2d5b64562d0c1cd5093ba969fb63908670dd4" }, ] -[package.metadata] -requires-dist = [ - { name = "cuda-python", marker = "extra == 'cuda'", specifier = "==12.0.0" }, - { name = "filelock" }, - { name = "glfw", marker = "extra == 'cuda'" }, - { name = "gym", marker = "extra == 'gym'", specifier = ">=0.19.0,<=0.26.0" }, - { name = "gymnasium", specifier = ">=0.28" }, - { name = "lxml" }, - { name = "matplotlib" }, - { name = "numpy", specifier = ">=1.21.6" }, - { name = "opencv-python-headless" }, - { name = "panda3d", specifier = "==1.10.14" }, - { name = "panda3d-gltf", specifier = "==0.13" }, - { name = "pillow" }, - { name = "progressbar" }, - { name = "psutil" }, - { name = "pygments" }, - { name = "pyopengl", marker = "extra == 'cuda'", specifier = "==3.1.6" }, - { name = "pyopengl-accelerate", marker = "extra == 'cuda'", specifier = "==3.1.6" }, - { name = "pyrr", marker = "extra == 'cuda'", specifier = "==0.10.3" }, - { name = "requests" }, - { name = "shapely" }, - { name = "tqdm" }, - { name = "yapf" }, - { name = "zmq", marker = "extra == 'ros'" }, -] -provides-extras = ["cuda", "gym", "ros"] - [[package]] name = "mkdocs" version = "1.6.1" @@ -1033,7 +921,7 @@ requires-dist = [ { name = "libusb1" }, { name = "mapbox-earcut" }, { name = "matplotlib", marker = "extra == 'dev'" }, - { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", url = "https://github.com/commaai/metadrive/releases/download/MetaDrive-minimal-0.4.2.4/metadrive_simulator-0.4.2.4-py3-none-any.whl" }, + { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, { name = "mkdocs", marker = "extra == 'docs'" }, { name = "numpy", specifier = ">=2.0" }, { name = "onnx", specifier = ">=1.14.0" }, @@ -1191,12 +1079,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/46/eba9be9daa403fa94854ce16a458c29df9a01c6c047931c3d8be6016cd9a/pre_commit_hooks-6.0.0-py2.py3-none-any.whl", hash = "sha256:76161b76d321d2f8ee2a8e0b84c30ee8443e01376121fd1c90851e33e3bd7ee2", size = 41338, upload-time = "2025-08-09T19:25:03.513Z" }, ] -[[package]] -name = "progressbar" -version = "2.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/a6/b8e451f6cff1c99b4747a2f7235aa904d2d49e8e1464e0b798272aa84358/progressbar-2.5.tar.gz", hash = "sha256:5d81cb529da2e223b53962afd6c8ca0f05c6670e40309a7219eacc36af9b6c63", size = 10046, upload-time = "2018-06-29T02:32:00.222Z" } - [[package]] name = "propcache" version = "0.4.1" @@ -4127,25 +4009,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, ] -[[package]] -name = "shapely" -version = "2.1.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4d/bc/0989043118a27cccb4e906a46b7565ce36ca7b57f5a18b78f4f1b0f72d9d/shapely-2.1.2.tar.gz", hash = "sha256:2ed4ecb28320a433db18a5bf029986aa8afcfd740745e78847e330d5d94922a9", size = 315489, upload-time = "2025-09-24T13:51:41.432Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/c0/f3b6453cf2dfa99adc0ba6675f9aaff9e526d2224cbd7ff9c1a879238693/shapely-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fe2533caae6a91a543dec62e8360fe86ffcdc42a7c55f9dfd0128a977a896b94", size = 1833550, upload-time = "2025-09-24T13:50:30.019Z" }, - { url = "https://files.pythonhosted.org/packages/86/07/59dee0bc4b913b7ab59ab1086225baca5b8f19865e6101db9ebb7243e132/shapely-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ba4d1333cc0bc94381d6d4308d2e4e008e0bd128bdcff5573199742ee3634359", size = 1643556, upload-time = "2025-09-24T13:50:32.291Z" }, - { url = "https://files.pythonhosted.org/packages/26/29/a5397e75b435b9895cd53e165083faed5d12fd9626eadec15a83a2411f0f/shapely-2.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0bd308103340030feef6c111d3eb98d50dc13feea33affc8a6f9fa549e9458a3", size = 2988308, upload-time = "2025-09-24T13:50:33.862Z" }, - { url = "https://files.pythonhosted.org/packages/b9/37/e781683abac55dde9771e086b790e554811a71ed0b2b8a1e789b7430dd44/shapely-2.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1e7d4d7ad262a48bb44277ca12c7c78cb1b0f56b32c10734ec9a1d30c0b0c54b", size = 3099844, upload-time = "2025-09-24T13:50:35.459Z" }, - { url = "https://files.pythonhosted.org/packages/d8/f3/9876b64d4a5a321b9dc482c92bb6f061f2fa42131cba643c699f39317cb9/shapely-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e9eddfe513096a71896441a7c37db72da0687b34752c4e193577a145c71736fc", size = 3988842, upload-time = "2025-09-24T13:50:37.478Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a0/704c7292f7014c7e74ec84eddb7b109e1fbae74a16deae9c1504b1d15565/shapely-2.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:980c777c612514c0cf99bc8a9de6d286f5e186dcaf9091252fcd444e5638193d", size = 4152714, upload-time = "2025-09-24T13:50:39.9Z" }, - { url = "https://files.pythonhosted.org/packages/53/46/319c9dc788884ad0785242543cdffac0e6530e4d0deb6c4862bc4143dcf3/shapely-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9111274b88e4d7b54a95218e243282709b330ef52b7b86bc6aaf4f805306f454", size = 1542745, upload-time = "2025-09-24T13:50:41.414Z" }, - { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, -] - [[package]] name = "six" version = "1.17.0" @@ -4300,18 +4163,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/63/188f7cb41ab35d795558325d5cc8ab552171d5498cfb178fd14409651e18/xattr-1.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2aaa5d66af6523332189108f34e966ca120ff816dfa077ca34b31e6263f8a236", size = 37754, upload-time = "2025-10-13T22:16:15.306Z" }, ] -[[package]] -name = "yapf" -version = "0.43.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "platformdirs" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/23/97/b6f296d1e9cc1ec25c7604178b48532fa5901f721bcf1b8d8148b13e5588/yapf-0.43.0.tar.gz", hash = "sha256:00d3aa24bfedff9420b2e0d5d9f5ab6d9d4268e72afbf59bb3fa542781d5218e", size = 254907, upload-time = "2024-11-14T00:11:41.584Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/81/6acd6601f61e31cfb8729d3da6d5df966f80f374b78eff83760714487338/yapf-0.43.0-py3-none-any.whl", hash = "sha256:224faffbc39c428cb095818cf6ef5511fdab6f7430a10783fdfb292ccf2852ca", size = 256158, upload-time = "2024-11-14T00:11:39.37Z" }, -] - [[package]] name = "yarl" version = "1.22.0" From 4166c9fccbf1fbfbaba2da1964f15585d24145a3 Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Sun, 15 Feb 2026 19:44:06 +0200 Subject: [PATCH 209/685] ci: fix first-interaction action missing required input (#37221) actions/first-interaction@v3 requires both issue_message and pr_message inputs, but only pr_message was provided, causing the action to fail. --- .github/workflows/auto_pr_review.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index cf12360e6e1..63cb062ebe3 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -40,6 +40,7 @@ jobs: if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' with: repo_token: ${{ secrets.GITHUB_TOKEN }} + issue_message: "" pr_message: | Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following: From 27f89e66346bb18a302673d30972d9fb27f7467a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 15 Feb 2026 16:39:38 -0800 Subject: [PATCH 210/685] jenkins: merge & speedup camera tests (#37223) --- Jenkinsfile | 6 +- selfdrive/test/helpers.py | 37 ++++++++++ system/camerad/test/test_camerad.py | 103 ++++++++++++++++++++------- system/camerad/test/test_exposure.py | 51 ------------- 4 files changed, 118 insertions(+), 79 deletions(-) delete mode 100644 system/camerad/test/test_exposure.py diff --git a/Jenkinsfile b/Jenkinsfile index 6f81755d4ea..e58ac817ebf 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -219,15 +219,13 @@ node { 'camerad OX03C10': { deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]), - step("test exposure", "pytest system/camerad/test/test_exposure.py"), + step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'camerad OS04C10': { deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 60]), - step("test exposure", "pytest system/camerad/test/test_exposure.py"), + step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'sensord': { diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index 81635aa31f6..5dfc1c3ec8d 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -37,6 +37,43 @@ def wrap(self, *args, **kwargs): return wrap +def collect_logs(services, duration): + socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in services] + logs = [] + start = time.monotonic() + while time.monotonic() - start < duration: + for s in socks: + logs.extend(messaging.drain_sock(s)) + return logs + + +@contextlib.contextmanager +def log_collector(services): + """Background thread that continuously drains messages from services. + Use when the main thread needs to do blocking work (e.g. capturing images).""" + socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in services] + raw_logs = [] + lock = threading.Lock() + stop_event = threading.Event() + + def _drain(): + while not stop_event.is_set(): + for s in socks: + msgs = messaging.drain_sock(s) + if msgs: + with lock: + raw_logs.extend(msgs) + time.sleep(0.01) + + thread = threading.Thread(target=_drain, daemon=True) + thread.start() + try: + yield raw_logs, lock + finally: + stop_event.set() + thread.join(timeout=2) + + @contextlib.contextmanager def processes_context(processes, init_time=0, ignore_stopped=None): ignore_stopped = [] if ignore_stopped is None else ignore_stopped diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index 1f3f97b0820..5f8de868991 100644 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -3,51 +3,103 @@ import pytest import numpy as np -import cereal.messaging as messaging from cereal.services import SERVICE_LIST -from openpilot.system.manager.process_config import managed_processes from openpilot.tools.lib.log_time_series import msgs_to_time_series +from openpilot.system.camerad.snapshot import get_snapshots +from openpilot.selfdrive.test.helpers import collect_logs, log_collector, processes_context TEST_TIMESPAN = 10 CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') +EXPOSURE_STABLE_COUNT = 3 +EXPOSURE_RANGE = (0.15, 0.35) +MAX_TEST_TIME = 25 -def run_and_log(procs, services, duration): - logs = [] +def _numpy_rgb2gray(im): + return np.clip(im[:,:,2] * 0.114 + im[:,:,1] * 0.587 + im[:,:,0] * 0.299, 0, 255).astype(np.uint8) + +def _exposure_stats(im): + h, w = im.shape[:2] + gray = _numpy_rgb2gray(im[h//10:9*h//10, w//10:9*w//10]) + return float(np.median(gray) / 255.), float(np.mean(gray) / 255.) - try: - for p in procs: - managed_processes[p].start() - socks = [messaging.sub_sock(s, conflate=False, timeout=100) for s in services] +def _in_range(median, mean): + lo, hi = EXPOSURE_RANGE + return lo < median < hi and lo < mean < hi - start_time = time.monotonic() - while time.monotonic() - start_time < duration: - for s in socks: - logs.extend(messaging.drain_sock(s)) - for p in procs: - assert managed_processes[p].proc.is_alive() - finally: - for p in procs: - managed_processes[p].stop() +def _exposure_stable(results): + return all( + len(v) >= EXPOSURE_STABLE_COUNT and all(_in_range(*s) for s in v[-EXPOSURE_STABLE_COUNT:]) + for v in results.values() + ) - return logs + +def run_and_log(procs, services, duration): + with processes_context(procs): + return collect_logs(services, duration) @pytest.fixture(scope="module") -def logs(): - logs = run_and_log(["camerad", ], CAMERAS, TEST_TIMESPAN) - ts = msgs_to_time_series(logs) +def _camera_session(): + """Single camerad session that collects logs and exposure data. + Runs until exposure stabilizes (min TEST_TIMESPAN seconds for enough log data).""" + with processes_context(["camerad"]), log_collector(CAMERAS) as (raw_logs, lock): + exposure = {cam: [] for cam in CAMERAS} + start = time.monotonic() + while time.monotonic() - start < MAX_TEST_TIME: + rpic, dpic = get_snapshots(frame="roadCameraState", front_frame="driverCameraState") + wpic, _ = get_snapshots(frame="wideRoadCameraState") + for cam, img in zip(CAMERAS, [rpic, dpic, wpic], strict=True): + exposure[cam].append(_exposure_stats(img)) + + if time.monotonic() - start >= TEST_TIMESPAN and _exposure_stable(exposure): + break + + elapsed = time.monotonic() - start + + with lock: + ts = msgs_to_time_series(raw_logs) for cam in CAMERAS: - expected_frames = SERVICE_LIST[cam].frequency * TEST_TIMESPAN + expected_frames = SERVICE_LIST[cam].frequency * elapsed cnt = len(ts[cam]['t']) assert expected_frames*0.8 < cnt < expected_frames*1.2, f"unexpected frame count {cam}: {expected_frames=}, got {cnt}" dts = np.abs(np.diff([ts[cam]['timestampSof']/1e6]) - 1000/SERVICE_LIST[cam].frequency) assert (dts < 1.0).all(), f"{cam} dts(ms) out of spec: max diff {dts.max()}, 99 percentile {np.percentile(dts, 99)}" - return ts + + return ts, exposure + +@pytest.fixture(scope="module") +def logs(_camera_session): + return _camera_session[0] + +@pytest.fixture(scope="module") +def exposure_data(_camera_session): + return _camera_session[1] @pytest.mark.tici class TestCamerad: + @pytest.mark.parametrize("cam", CAMERAS) + def test_camera_exposure(self, exposure_data, cam): + lo, hi = EXPOSURE_RANGE + checks = exposure_data[cam] + assert len(checks) >= EXPOSURE_STABLE_COUNT, f"{cam}: only got {len(checks)} samples" + + # check that exposure converges into the valid range + passed = sum(_in_range(med, mean) for med, mean in checks) + assert passed >= EXPOSURE_STABLE_COUNT, \ + f"{cam}: only {passed}/{len(checks)} checks in range. " + \ + " | ".join(f"#{i+1}: med={m:.4f} mean={u:.4f}" for i, (m, u) in enumerate(checks)) + + # check that exposure is stable once converged (no regressions) + in_range = False + for i, (median, mean) in enumerate(checks): + ok = _in_range(median, mean) + if in_range and not ok: + pytest.fail(f"{cam}: exposure regressed on sample {i+1} " + + f"(median={median:.4f}, mean={mean:.4f}, expected: ({lo}, {hi}))") + in_range = ok + def test_frame_skips(self, logs): for c in CAMERAS: assert set(np.diff(logs[c]['frameId'])) == {1, }, f"{c} has frame skips" @@ -91,7 +143,10 @@ def _sanity_checks(self, ts): def test_stress_test(self): os.environ['SPECTRA_ERROR_PROB'] = '0.008' - logs = run_and_log(["camerad", ], CAMERAS, 10) + try: + logs = run_and_log(["camerad", ], CAMERAS, 10) + finally: + del os.environ['SPECTRA_ERROR_PROB'] ts = msgs_to_time_series(logs) # we should see some jumps from introduced errors diff --git a/system/camerad/test/test_exposure.py b/system/camerad/test/test_exposure.py deleted file mode 100644 index 6f89e048004..00000000000 --- a/system/camerad/test/test_exposure.py +++ /dev/null @@ -1,51 +0,0 @@ -import time -import numpy as np -import pytest - -from openpilot.selfdrive.test.helpers import with_processes -from openpilot.system.camerad.snapshot import get_snapshots - -TEST_TIME = 45 -REPEAT = 5 - -@pytest.mark.tici -class TestCamerad: - @classmethod - def setup_class(cls): - pass - - def _numpy_rgb2gray(self, im): - ret = np.clip(im[:,:,2] * 0.114 + im[:,:,1] * 0.587 + im[:,:,0] * 0.299, 0, 255).astype(np.uint8) - return ret - - def _is_exposure_okay(self, i, med_mean=None): - if med_mean is None: - med_mean = np.array([[0.18,0.3],[0.18,0.3]]) - h, w = i.shape[:2] - i = i[h//10:9*h//10,w//10:9*w//10] - med_ex, mean_ex = med_mean - i = self._numpy_rgb2gray(i) - i_median = np.median(i) / 255. - i_mean = np.mean(i) / 255. - print([i_median, i_mean]) - return med_ex[0] < i_median < med_ex[1] and mean_ex[0] < i_mean < mean_ex[1] - - @with_processes(['camerad']) - def test_camera_operation(self): - passed = 0 - start = time.monotonic() - while time.monotonic() - start < TEST_TIME and passed < REPEAT: - rpic, dpic = get_snapshots(frame="roadCameraState", front_frame="driverCameraState") - wpic, _ = get_snapshots(frame="wideRoadCameraState") - - res = self._is_exposure_okay(rpic) - res = res and self._is_exposure_okay(dpic) - res = res and self._is_exposure_okay(wpic) - - if passed > 0 and not res: - passed = -passed # fails test if any failure after first sus - break - - passed += int(res) - time.sleep(2) - assert passed >= REPEAT From c393973916de984ae2444383e1de5685505f67ce Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 15 Feb 2026 17:45:03 -0800 Subject: [PATCH 211/685] disable sim test, still not ready for it --- .github/workflows/tests.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index aab0f58b0a0..fe7c8ef3360 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -238,6 +238,7 @@ jobs: (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} + if: false # FIXME: Started to timeout recently steps: - uses: actions/checkout@v6 with: From 03a4f7ef9ac2955fffb18c570efe4e9d89742edc Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:03:30 -0600 Subject: [PATCH 212/685] ui: add big (tizi) replay (#37198) * init: tizi_replay.py from pr 37123 * separate coverage folder * ui replay: adjust HOLD constant, fix coverage, use separate folder for coverage * openpilot prefix * fix directory * fix ui_state * fix settings click pos * remove * attempt merge replay files * remove * todo * fix recording * spacing * simplify * comment * refactor hold * refactor: remove layout definitions from VARIANTS and import conditionally in run_replay * refactor: remove VARIANTS config * add argparser with --big flag and improve coverage sources * refactor * lowercase * refactor: combine scripts * add types * refactor: move imports for gui_app and ui_state to improve coverage and organization * update * update script * comment * fix headless * todo * fix: get_time and get_frame_time determinism * todo * remove file accidently commited * fix: improve inject_click and handle_event for deterministic event timestamps * comment * simplify add * refactor script building * fix mici clicks * pass in pm * fix wifi state * refactor clicks * more refactor * click cancel instead of remove overlay * setup_send_fn * add setup fn * dummy update * change * remove todo * rename fn to frame_fn * refactor * fix workflow * rename raylib ui preview to old * rename mici workflow * fix diff videos * ignore sub html and mp4 files * rename for diff * rename for diff again (mici) * use ScriptEvent instead of DummyEvent, and move mouse events directly to it; rename hold to wait * fix: only import MouseEvent for type hint to fix coverage * adjust settings button click * clarify * move ScriptEvent to replay_script * add handle_event function * remove passing in setup function, and refactor click events * clean * formatting * refactor * no import * comment * refactor * refactor setup functions to replay_setup * refactor * add ReplayContext * refactor * move more setup functions * refactor and simplify * refactor * refactor: add Script class * refactor: enhance Script event handling and add wait functionality * refactor * remove setup_and_click * use script.setup instead * comments * rename wait_frames to wait_after * add comments * revert workflows * revert rename * move arg parsing to main * remove quotes * add type * return types * type * VariantType * rename to LayoutVariant * clarify * switch * todo * Revert "fix diff videos" This reverts commit 7a6e45a409cb7e6d7a330317639fcee74ef8bd31. * add todos * add more coverage * wait 2 frames by default * add comment * comment * switch * fix space Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * remove extra Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove unnecessary blank line in ReplayContext class * simplify --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Adeeb Shihadeh --- selfdrive/ui/tests/.gitignore | 5 +- selfdrive/ui/tests/diff/replay.py | 144 +++++++------ selfdrive/ui/tests/diff/replay_script.py | 249 +++++++++++++++++++++++ 3 files changed, 320 insertions(+), 78 deletions(-) create mode 100644 selfdrive/ui/tests/diff/replay_script.py diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore index 98f2a5e8ce9..90a32ffca25 100644 --- a/selfdrive/ui/tests/.gitignore +++ b/selfdrive/ui/tests/.gitignore @@ -3,7 +3,6 @@ test_translations test_ui/report_1 test_ui/raylib_report -diff/*.mp4 -diff/*.html +diff/**/*.mp4 +diff/**/*.html diff/.coverage -diff/htmlcov/ diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index a668cc776b3..1cb0b42ada0 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -1,41 +1,21 @@ #!/usr/bin/env python3 import os -import time +import argparse import coverage import pyray as rl -from dataclasses import dataclass -from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR - -os.environ["RECORD"] = "1" -if "RECORD_OUTPUT" not in os.environ: - os.environ["RECORD_OUTPUT"] = "mici_ui_replay.mp4" - -os.environ["RECORD_OUTPUT"] = os.path.join(DIFF_OUT_DIR, os.environ["RECORD_OUTPUT"]) +from typing import Literal +from collections.abc import Callable +from cereal.messaging import PubMaster from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix +from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR from openpilot.system.version import terms_version, training_version -from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent -FPS = 60 -HEADLESS = os.getenv("WINDOWED", "0") == "1" +LayoutVariant = Literal["mici", "tizi"] - -@dataclass -class DummyEvent: - click: bool = False - # TODO: add some kind of intensity - swipe_left: bool = False - swipe_right: bool = False - swipe_down: bool = False - - -SCRIPT = [ - (0, DummyEvent()), - (FPS * 1, DummyEvent(click=True)), - (FPS * 2, DummyEvent(click=True)), - (FPS * 3, DummyEvent()), -] +FPS = 60 +HEADLESS = os.getenv("WINDOWED", "0") != "1" def setup_state(): @@ -44,66 +24,60 @@ def setup_state(): params.put("CompletedTrainingVersion", training_version) params.put("DongleId", "test123456789") params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30") - return None - - -def inject_click(coords): - events = [] - x, y = coords[0] - events.append(MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=False, t=time.monotonic())) - for x, y in coords[1:]: - events.append(MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=False, left_down=True, t=time.monotonic())) - x, y = coords[-1] - events.append(MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=time.monotonic())) - - with gui_app._mouse._lock: - gui_app._mouse._events.extend(events) - - -def handle_event(event: DummyEvent): - if event.click: - inject_click([(gui_app.width // 2, gui_app.height // 2)]) - if event.swipe_left: - inject_click([(gui_app.width * 3 // 4, gui_app.height // 2), - (gui_app.width // 4, gui_app.height // 2), - (0, gui_app.height // 2)]) - if event.swipe_right: - inject_click([(gui_app.width // 4, gui_app.height // 2), - (gui_app.width * 3 // 4, gui_app.height // 2), - (gui_app.width, gui_app.height // 2)]) - if event.swipe_down: - inject_click([(gui_app.width // 2, gui_app.height // 4), - (gui_app.width // 2, gui_app.height * 3 // 4), - (gui_app.width // 2, gui_app.height)]) - - -def run_replay(): - from openpilot.selfdrive.ui.ui_state import ui_state # import here for correct param setup (e.g. training guide) - from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout # import here for coverage + + +def run_replay(variant: LayoutVariant) -> None: + from openpilot.selfdrive.ui.ui_state import ui_state # Import within OpenpilotPrefix context so param values are setup correctly + from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage + from openpilot.selfdrive.ui.tests.diff.replay_script import build_script setup_state() os.makedirs(DIFF_OUT_DIR, exist_ok=True) - if not HEADLESS: + if HEADLESS: rl.set_config_flags(rl.FLAG_WINDOW_HIDDEN) gui_app.init_window("ui diff test", fps=FPS) - - main_layout = MiciMainLayout() + # Dynamically import main layout based on variant + if variant == "mici": + from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout as MainLayout + else: + from openpilot.selfdrive.ui.layouts.main import MainLayout + main_layout = MainLayout() main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) + script = build_script(pm, main_layout, variant) script_index = 0 + + send_fn: Callable | None = None frame = 0 # Override raylib timing functions to return deterministic values based on frame count instead of real time rl.get_frame_time = lambda: 1.0 / FPS rl.get_time = lambda: frame / FPS + # Main loop to replay events and render frames for should_render in gui_app.render(): - while script_index < len(SCRIPT) and SCRIPT[script_index][0] == frame: - _, event = SCRIPT[script_index] - handle_event(event) + # Handle all events for the current frame + while script_index < len(script) and script[script_index][0] == frame: + _, event = script[script_index] + # Call setup function, if any + if event.setup: + event.setup() + # Send mouse events to the application + if event.mouse_events: + with gui_app._mouse._lock: + gui_app._mouse._events.extend(event.mouse_events) + # Update persistent send function + if event.send_fn is not None: + send_fn = event.send_fn + # Move to next script event script_index += 1 + # Keep sending cereal messages for persistent states (onroad, alerts) + if send_fn: + send_fn() + ui_state.update() if should_render: @@ -111,7 +85,7 @@ def run_replay(): frame += 1 - if script_index >= len(SCRIPT): + if script_index >= len(script): break gui_app.close() @@ -121,14 +95,34 @@ def run_replay(): def main(): + parser = argparse.ArgumentParser() + parser.add_argument('--big', action='store_true', help='Use big UI layout (tizi/tici) instead of mici layout') + args = parser.parse_args() + + variant: LayoutVariant = 'tizi' if args.big else 'mici' + + if args.big: + os.environ["BIG"] = "1" + os.environ["RECORD"] = "1" + os.environ["RECORD_OUTPUT"] = os.path.join(DIFF_OUT_DIR, os.environ.get("RECORD_OUTPUT", f"{variant}_ui_replay.mp4")) + + print(f"Running {variant} UI replay...") with OpenpilotPrefix(): - cov = coverage.coverage(source=['openpilot.selfdrive.ui.mici']) + sources = ["openpilot.system.ui"] + if variant == "mici": + sources.append("openpilot.selfdrive.ui.mici") + omit = ["**/*tizi*", "**/*tici*"] # exclude files containing "tizi" or "tici" + else: + sources.extend(["openpilot.selfdrive.ui.layouts", "openpilot.selfdrive.ui.onroad", "openpilot.selfdrive.ui.widgets"]) + omit = ["**/*mici*"] # exclude files containing "mici" + cov = coverage.Coverage(source=sources, omit=omit) with cov.collect(): - run_replay() + run_replay(variant) cov.save() cov.report() - cov.html_report(directory=os.path.join(DIFF_OUT_DIR, 'htmlcov')) - print("HTML report: htmlcov/index.html") + directory = os.path.join(DIFF_OUT_DIR, f"htmlcov-{variant}") + cov.html_report(directory=directory) + print(f"HTML report: {directory}/index.html") if __name__ == "__main__": diff --git a/selfdrive/ui/tests/diff/replay_script.py b/selfdrive/ui/tests/diff/replay_script.py new file mode 100644 index 00000000000..9f2104ec497 --- /dev/null +++ b/selfdrive/ui/tests/diff/replay_script.py @@ -0,0 +1,249 @@ +from __future__ import annotations +from typing import TYPE_CHECKING +from collections.abc import Callable +from dataclasses import dataclass + +from cereal import car, log, messaging +from cereal.messaging import PubMaster +from openpilot.common.basedir import BASEDIR +from openpilot.common.params import Params +from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert +from openpilot.selfdrive.ui.tests.diff.replay import FPS, LayoutVariant +from openpilot.system.updated.updated import parse_release_notes + +WAIT = int(FPS * 0.5) # Default frames to wait after events + +AlertSize = log.SelfdriveState.AlertSize +AlertStatus = log.SelfdriveState.AlertStatus + +BRANCH_NAME = "this-is-a-really-super-mega-ultra-max-extreme-ultimate-long-branch-name" + + +@dataclass +class ScriptEvent: + if TYPE_CHECKING: + # Only import for type checking to avoid excluding the application code from coverage + from openpilot.system.ui.lib.application import MouseEvent + + setup: Callable | None = None # Setup function to run prior to adding mouse events + mouse_events: list[MouseEvent] | None = None # Mouse events to send to the application on this event's frame + send_fn: Callable | None = None # When set, the main loop uses this as the new persistent sender + + +ScriptEntry = tuple[int, ScriptEvent] # (frame, event) + + +class Script: + def __init__(self, fps: int) -> None: + self.fps = fps + self.frame = 0 + self.entries: list[ScriptEntry] = [] + + def get_frame_time(self) -> float: + return self.frame / self.fps + + def add(self, event: ScriptEvent, before: int = 0, after: int = 0) -> None: + """Add event to the script, optionally with the given number of frames to wait before or after the event.""" + self.frame += before + self.entries.append((self.frame, event)) + self.frame += after + + def end(self) -> None: + """Add a final empty event to mark the end of the script.""" + self.add(ScriptEvent()) # Without this, it will just end on the last event without waiting for any specified delay after it + + def wait(self, frames: int) -> None: + """Add a delay for the given number of frames followed by an empty event.""" + self.add(ScriptEvent(), before=frames) + + def setup(self, fn: Callable, wait_after: int = WAIT) -> None: + """Add a setup function to be called immediately followed by a delay of the given number of frames.""" + self.add(ScriptEvent(setup=fn), after=wait_after) + + def set_send(self, fn: Callable, wait_after: int = WAIT) -> None: + """Set a new persistent send function to be called every frame.""" + self.add(ScriptEvent(send_fn=fn), after=wait_after) + + # TODO: Also add more complex gestures, like swipe or drag + def click(self, x: int, y: int, wait_after: int = WAIT, wait_between: int = 2) -> None: + """Add a click event to the script for the given position and specify frames to wait between mouse events or after the click.""" + # NOTE: By default we wait a couple frames between mouse events so pressed states will be rendered + from openpilot.system.ui.lib.application import MouseEvent, MousePos + + # TODO: Add support for long press (left_down=True) + mouse_down = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=False, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_down]), after=wait_between) + mouse_up = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_up]), after=wait_after) + + +# --- Setup functions --- + +def put_update_params(params: Params | None = None) -> None: + if params is None: + params = Params() + params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) + params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) + params.put("UpdaterTargetBranch", BRANCH_NAME) + + +def setup_offroad_alerts() -> None: + put_update_params(Params()) + set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') + set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') + set_offroad_alert("Offroad_IsTakingSnapshot", True) + + +def setup_update_available() -> None: + params = Params() + params.put_bool("UpdateAvailable", True) + params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01") + put_update_params(params) + + +def setup_developer_params() -> None: + CP = car.CarParams() + CP.alphaLongitudinalAvailable = True + Params().put("CarParamsPersistent", CP.to_bytes()) + + +# --- Send functions --- + +def send_onroad(pm: PubMaster) -> None: + ds = messaging.new_message('deviceState') + ds.deviceState.started = True + ds.deviceState.networkType = log.DeviceState.NetworkType.wifi + + ps = messaging.new_message('pandaStates', 1) + ps.pandaStates[0].pandaType = log.PandaState.PandaType.dos + ps.pandaStates[0].ignitionLine = True + + pm.send('deviceState', ds) + pm.send('pandaStates', ps) + + +def make_network_state_setup(pm: PubMaster, network_type) -> Callable: + def _send() -> None: + ds = messaging.new_message('deviceState') + ds.deviceState.networkType = network_type + pm.send('deviceState', ds) + return _send + + +def make_alert_setup(pm: PubMaster, size, text1, text2, status) -> Callable: + def _send() -> None: + send_onroad(pm) + alert = messaging.new_message('selfdriveState') + ss = alert.selfdriveState + ss.alertSize = size + ss.alertText1 = text1 + ss.alertText2 = text2 + ss.alertStatus = status + pm.send('selfdriveState', alert) + return _send + + +# --- Script builders --- + +def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None: + """Build the replay script for the mici layout.""" + from openpilot.system.ui.lib.application import gui_app + + center = (gui_app.width // 2, gui_app.height // 2) + + # TODO: Explore more + script.wait(FPS) + script.click(*center, FPS) # Open settings + script.click(*center, FPS) # Open toggles + script.end() + + +def build_tizi_script(pm: PubMaster, main_layout, script: Script) -> None: + """Build the replay script for the tizi layout.""" + + def make_home_refresh_setup(fn: Callable) -> Callable: + """Return setup function that calls the given function to modify state and forces an immediate refresh on the home layout.""" + from openpilot.selfdrive.ui.layouts.main import MainState + + def setup(): + fn() + main_layout._layouts[MainState.HOME].last_refresh = 0 + + return setup + + # TODO: Better way of organizing the events + + # === Homescreen === + script.set_send(make_network_state_setup(pm, log.DeviceState.NetworkType.wifi)) + + # === Offroad Alerts (auto-transitions via HomeLayout refresh) === + script.setup(make_home_refresh_setup(setup_offroad_alerts)) + + # === Update Available (auto-transitions via HomeLayout refresh) === + script.setup(make_home_refresh_setup(setup_update_available)) + + # === Settings - Device (click sidebar settings button) === + script.click(150, 90) + script.click(1985, 790) # reset calibration confirmation + script.click(650, 750) # cancel + + # === Settings - Network === + script.click(278, 450) + script.click(1880, 100) # advanced network settings + script.click(630, 80) # back + + # === Settings - Toggles === + script.click(278, 600) + script.click(1200, 280) # experimental mode description + + # === Settings - Software === + script.setup(put_update_params, wait_after=0) + script.click(278, 720) + + # === Settings - Firehose === + script.click(278, 845) + + # === Settings - Developer (set CarParamsPersistent first) === + script.setup(setup_developer_params, wait_after=0) + script.click(278, 950) + script.click(2000, 960) # toggle alpha long + script.click(1500, 875) # confirm + + # === Keyboard modal (SSH keys button in developer panel) === + script.click(1930, 470) # click SSH keys + script.click(1930, 115) # click cancel on keyboard + + # === Close settings === + script.click(250, 160) + + # === Onroad === + script.set_send(lambda: send_onroad(pm)) + script.click(1000, 500) # click onroad to toggle sidebar + + # === Onroad alerts === + # Small alert (normal) + script.set_send(make_alert_setup(pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)) + # Medium alert (userPrompt) + script.set_send(make_alert_setup(pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)) + # Full alert (critical) + script.set_send(make_alert_setup(pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)) + # Full alert multiline + script.set_send(make_alert_setup(pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)) + # Full alert long text + script.set_send(make_alert_setup(pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)) + + # End + script.end() + + +def build_script(pm: PubMaster, main_layout, variant: LayoutVariant) -> list[ScriptEntry]: + """Build the replay script for the appropriate layout variant and return list of script entries.""" + print(f"Building {variant} replay script...") + + script = Script(FPS) + builder = build_tizi_script if variant == 'tizi' else build_mici_script + builder(pm, main_layout, script) + + print(f"Built replay script with {len(script.entries)} events and {script.frame} frames ({script.get_frame_time():.2f} seconds)") + + return script.entries From 8831b11a2496d3002c2ab73c40b50aec5813f60d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 15 Feb 2026 20:11:17 -0800 Subject: [PATCH 213/685] remove old raylib screenshot tool (#37225) --- .github/workflows/raylib_ui_preview.yaml | 175 -- .github/workflows/tests.yaml | 26 - pyproject.toml | 2 - selfdrive/ui/tests/.gitignore | 1 - .../ui/tests/test_ui/print_mouse_coords.py | 36 - .../ui/tests/test_ui/raylib_screenshots.py | 317 -- selfdrive/ui/tests/test_ui/template.html | 34 - tools/jotpluggler/pluggle.py | 16 +- uv.lock | 2720 +---------------- 9 files changed, 125 insertions(+), 3202 deletions(-) delete mode 100644 .github/workflows/raylib_ui_preview.yaml delete mode 100755 selfdrive/ui/tests/test_ui/print_mouse_coords.py delete mode 100755 selfdrive/ui/tests/test_ui/raylib_screenshots.py delete mode 100644 selfdrive/ui/tests/test_ui/template.html diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml deleted file mode 100644 index 9044a97f536..00000000000 --- a/.github/workflows/raylib_ui_preview.yaml +++ /dev/null @@ -1,175 +0,0 @@ -name: "raylib ui preview" -on: - push: - branches: - - master - pull_request_target: - types: [assigned, opened, synchronize, reopened, edited] - branches: - - 'master' - paths: - - 'selfdrive/assets/**' - - 'selfdrive/ui/**' - - 'system/ui/**' - workflow_dispatch: - -env: - UI_JOB_NAME: "Create raylib UI Report" - REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} - SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} - BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-raylib-ui" - -jobs: - preview: - if: github.repository == 'commaai/openpilot' - name: preview - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - pull-requests: write - actions: read - steps: - - name: Waiting for ui generation to start - run: sleep 30 - - - name: Waiting for ui generation to end - uses: lewagon/wait-on-check-action@v1.3.4 - with: - ref: ${{ env.SHA }} - check-name: ${{ env.UI_JOB_NAME }} - repo-token: ${{ secrets.GITHUB_TOKEN }} - allowed-conclusions: success - wait-interval: 20 - - - name: Getting workflow run ID - id: get_run_id - run: | - echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT - - - name: Getting proposed ui - id: download-artifact - uses: dawidd6/action-download-artifact@v6 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - run_id: ${{ steps.get_run_id.outputs.run_id }} - search_artifacts: true - name: raylib-report-1-${{ env.REPORT_NAME }} - path: ${{ github.workspace }}/pr_ui - - - name: Getting master ui - uses: actions/checkout@v6 - with: - repository: commaai/ci-artifacts - ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} - path: ${{ github.workspace }}/master_ui_raylib - ref: openpilot_master_ui_raylib - - - name: Saving new master ui - if: github.ref == 'refs/heads/master' && github.event_name == 'push' - working-directory: ${{ github.workspace }}/master_ui_raylib - run: | - git checkout --orphan=new_master_ui_raylib - git rm -rf * - git branch -D openpilot_master_ui_raylib - git branch -m openpilot_master_ui_raylib - git config user.name "GitHub Actions Bot" - git config user.email "<>" - mv ${{ github.workspace }}/pr_ui/*.png . - git add . - git commit -m "raylib screenshots for commit ${{ env.SHA }}" - git push origin openpilot_master_ui_raylib --force - - - name: Finding diff - if: github.event_name == 'pull_request_target' - id: find_diff - run: >- - sudo apt-get update && sudo apt-get install -y imagemagick - - scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device') - A=($scenes) - - DIFF="" - TABLE="
All Screenshots" - TABLE="${TABLE}" - - for ((i=0; i<${#A[*]}; i=i+1)); - do - # Check if the master file exists - if [ ! -f "${{ github.workspace }}/master_ui_raylib/${A[$i]}.png" ]; then - # This is a new file in PR UI that doesn't exist in master - DIFF="${DIFF}
" - DIFF="${DIFF}${A[$i]} : \$\${\\color{cyan}\\text{NEW}}\$\$" - DIFF="${DIFF}
" - - DIFF="${DIFF}" - DIFF="${DIFF} " - DIFF="${DIFF}" - - DIFF="${DIFF}
" - DIFF="${DIFF}
" - elif ! compare -fuzz 2% -highlight-color DeepSkyBlue1 -lowlight-color Black -compose Src ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png; then - convert ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png -transparent black mask.png - composite mask.png ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png composite_diff.png - convert -delay 100 ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png composite_diff.png -loop 0 ${{ github.workspace }}/pr_ui/${A[$i]}_diff.gif - - mv ${{ github.workspace }}/master_ui_raylib/${A[$i]}.png ${{ github.workspace }}/pr_ui/${A[$i]}_master_ref.png - - DIFF="${DIFF}
" - DIFF="${DIFF}${A[$i]} : \$\${\\color{red}\\text{DIFFERENT}}\$\$" - DIFF="${DIFF}" - - DIFF="${DIFF}" - DIFF="${DIFF} " - DIFF="${DIFF} " - DIFF="${DIFF}" - - DIFF="${DIFF}" - DIFF="${DIFF} " - DIFF="${DIFF} " - DIFF="${DIFF}" - - DIFF="${DIFF}
master proposed
diff composite diff
" - DIFF="${DIFF}
" - else - rm -f ${{ github.workspace }}/pr_ui/${A[$i]}_diff.png - fi - - INDEX=$(($i % 2)) - if [[ $INDEX -eq 0 ]]; then - TABLE="${TABLE}" - fi - TABLE="${TABLE} " - if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then - TABLE="${TABLE}" - fi - done - - TABLE="${TABLE}" - - echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT" - - - name: Saving proposed ui - if: github.event_name == 'pull_request_target' - working-directory: ${{ github.workspace }}/master_ui_raylib - run: | - git config user.name "GitHub Actions Bot" - git config user.email "<>" - git checkout --orphan=${{ env.BRANCH_NAME }} - git rm -rf * - mv ${{ github.workspace }}/pr_ui/* . - git add . - git commit -m "raylib screenshots for PR #${{ github.event.number }}" - git push origin ${{ env.BRANCH_NAME }} --force - - - name: Comment Screenshots on PR - if: github.event_name == 'pull_request_target' - uses: thollander/actions-comment-pull-request@v2 - with: - message: | - - ## raylib UI Preview - ${{ steps.find_diff.outputs.DIFF }} - comment_tag: run_id_screenshots_raylib - pr_number: ${{ github.event.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index fe7c8ef3360..2cca3444235 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -255,32 +255,6 @@ jobs: source selfdrive/test/setup_vsound.sh && \ CI=1 pytest -s tools/sim/tests/test_metadrive_bridge.py" - create_raylib_ui_report: - name: Create raylib UI Report - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') - || fromJSON('["ubuntu-24.04"]') }} - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - uses: ./.github/workflows/setup-with-retry - - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc)" - - name: Create raylib UI Report - run: > - ${{ env.RUN }} "PYTHONWARNINGS=ignore && - source selfdrive/test/setup_xvfb.sh && - python3 selfdrive/ui/tests/test_ui/raylib_screenshots.py" - - name: Upload Raylib UI Report - uses: actions/upload-artifact@v6 - with: - name: raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} - path: selfdrive/ui/tests/test_ui/raylib_report/screenshots - create_mici_raylib_ui_report: name: Create mici raylib UI Report runs-on: ${{ diff --git a/pyproject.toml b/pyproject.toml index 2a6d619f7fd..8eb86101afb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -100,8 +100,6 @@ dev = [ "matplotlib", "opencv-python-headless", "parameterized >=0.8, <0.9", - "pyautogui", - "pywinctl", ] tools = [ diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore index 90a32ffca25..39e2f8970c3 100644 --- a/selfdrive/ui/tests/.gitignore +++ b/selfdrive/ui/tests/.gitignore @@ -1,7 +1,6 @@ test test_translations test_ui/report_1 -test_ui/raylib_report diff/**/*.mp4 diff/**/*.html diff --git a/selfdrive/ui/tests/test_ui/print_mouse_coords.py b/selfdrive/ui/tests/test_ui/print_mouse_coords.py deleted file mode 100755 index 1e88ce57d3e..00000000000 --- a/selfdrive/ui/tests/test_ui/print_mouse_coords.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple script to print mouse coordinates on Ubuntu. -Run with: python print_mouse_coords.py -Press Ctrl+C to exit. -""" - -from pynput import mouse - -print("Mouse coordinate printer - Press Ctrl+C to exit") -print("Click to set the top left origin") - -origin: tuple[int, int] | None = None -clicks: list[tuple[int, int]] = [] - - -def on_click(x, y, button, pressed): - global origin, clicks - if pressed: # Only on mouse down, not up - if origin is None: - origin = (x, y) - print(f"Origin set to: {x},{y}") - else: - rel_x = x - origin[0] - rel_y = y - origin[1] - clicks.append((rel_x, rel_y)) - print(f"Clicks: {clicks}") - - -if __name__ == "__main__": - try: - # Start mouse listener - with mouse.Listener(on_click=on_click) as listener: - listener.join() - except KeyboardInterrupt: - print("\nExiting...") diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py deleted file mode 100755 index 481ac111beb..00000000000 --- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py +++ /dev/null @@ -1,317 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import shutil -import time -import pathlib -from collections import namedtuple - -import pyautogui -import pywinctl - -from cereal import car, log -from cereal import messaging -from cereal.messaging import PubMaster -from openpilot.common.basedir import BASEDIR -from openpilot.common.params import Params -from openpilot.common.prefix import OpenpilotPrefix -from openpilot.selfdrive.test.helpers import with_processes -from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert -from openpilot.system.updated.updated import parse_release_notes -from openpilot.system.version import terms_version, training_version - -AlertSize = log.SelfdriveState.AlertSize -AlertStatus = log.SelfdriveState.AlertStatus - -TEST_DIR = pathlib.Path(__file__).parent -TEST_OUTPUT_DIR = TEST_DIR / "raylib_report" -SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots" -UI_DELAY = 0.5 - -BRANCH_NAME = "this-is-a-really-super-mega-ultra-max-extreme-ultimate-long-branch-name" -VERSION = f"0.10.1 / {BRANCH_NAME} / 7864838 / Oct 03" - -# Offroad alerts to test -OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot'] - - -def put_update_params(params: Params): - params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterTargetBranch", BRANCH_NAME) - - -def setup_homescreen(click, pm: PubMaster): - pass - - -def setup_homescreen_update_available(click, pm: PubMaster): - params = Params() - params.put_bool("UpdateAvailable", True) - put_update_params(params) - setup_offroad_alert(click, pm) - - -def setup_settings(click, pm: PubMaster): - click(100, 100) - - -def close_settings(click, pm: PubMaster): - click(240, 216) - - -def setup_settings_network(click, pm: PubMaster): - setup_settings(click, pm) - click(278, 450) - - -def setup_settings_network_advanced(click, pm: PubMaster): - setup_settings_network(click, pm) - click(1880, 100) - - -def setup_settings_toggles(click, pm: PubMaster): - setup_settings(click, pm) - click(278, 600) - - -def setup_settings_software(click, pm: PubMaster): - put_update_params(Params()) - setup_settings(click, pm) - click(278, 720) - - -def setup_settings_software_download(click, pm: PubMaster): - params = Params() - # setup_settings_software but with "DOWNLOAD" button to test long text - params.put("UpdaterState", "idle") - params.put_bool("UpdaterFetchAvailable", True) - setup_settings_software(click, pm) - - -def setup_settings_software_release_notes(click, pm: PubMaster): - setup_settings_software(click, pm) - click(588, 110) # expand description for current version - - -def setup_settings_software_branch_switcher(click, pm: PubMaster): - setup_settings_software(click, pm) - params = Params() - params.put("UpdaterAvailableBranches", f"master,nightly,release,{BRANCH_NAME}") - params.put("GitBranch", BRANCH_NAME) # should be on top - params.put("UpdaterTargetBranch", "nightly") # should be selected - click(1984, 449) - - -def setup_settings_firehose(click, pm: PubMaster): - setup_settings(click, pm) - click(278, 845) - - -def setup_settings_developer(click, pm: PubMaster): - CP = car.CarParams() - CP.alphaLongitudinalAvailable = True # show alpha long control toggle - Params().put("CarParamsPersistent", CP.to_bytes()) - - setup_settings(click, pm) - click(278, 950) - - -def setup_keyboard(click, pm: PubMaster): - setup_settings_developer(click, pm) - click(1930, 470) - - -def setup_pair_device(click, pm: PubMaster): - click(1950, 800) - - -def setup_offroad_alert(click, pm: PubMaster): - put_update_params(Params()) - set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') - set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') - for alert in OFFROAD_ALERTS: - set_offroad_alert(alert, True) - - setup_settings(click, pm) - close_settings(click, pm) - - -def setup_confirmation_dialog(click, pm: PubMaster): - setup_settings(click, pm) - click(1985, 791) # reset calibration - - -def setup_experimental_mode_description(click, pm: PubMaster): - setup_settings_toggles(click, pm) - click(1200, 280) # expand description for experimental mode - - -def setup_openpilot_long_confirmation_dialog(click, pm: PubMaster): - setup_settings_developer(click, pm) - click(2000, 960) # toggle openpilot longitudinal control - - -def setup_onroad(click, pm: PubMaster): - ds = messaging.new_message('deviceState') - ds.deviceState.started = True - - ps = messaging.new_message('pandaStates', 1) - ps.pandaStates[0].pandaType = log.PandaState.PandaType.dos - ps.pandaStates[0].ignitionLine = True - - driverState = messaging.new_message('driverStateV2') - driverState.driverStateV2.leftDriverData.faceOrientation = [0, 0, 0] - - for _ in range(5): - pm.send('deviceState', ds) - pm.send('pandaStates', ps) - pm.send('driverStateV2', driverState) - ds.clear_write_flag() - ps.clear_write_flag() - driverState.clear_write_flag() - time.sleep(0.05) - - -def setup_onroad_sidebar(click, pm: PubMaster): - setup_onroad(click, pm) - click(100, 100) # open sidebar - - -def setup_onroad_alert(click, pm: PubMaster, size: log.SelfdriveState.AlertSize, text1: str, text2: str, status: log.SelfdriveState.AlertStatus): - setup_onroad(click, pm) - alert = messaging.new_message('selfdriveState') - ss = alert.selfdriveState - ss.alertSize = size - ss.alertText1 = text1 - ss.alertText2 = text2 - ss.alertStatus = status - for _ in range(5): - pm.send('selfdriveState', alert) - alert.clear_write_flag() - time.sleep(0.05) - - -def setup_onroad_small_alert(click, pm: PubMaster): - setup_onroad_alert(click, pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal) - - -def setup_onroad_medium_alert(click, pm: PubMaster): - setup_onroad_alert(click, pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt) - - -def setup_onroad_full_alert(click, pm: PubMaster): - setup_onroad_alert(click, pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical) - - -def setup_onroad_full_alert_multiline(click, pm: PubMaster): - setup_onroad_alert(click, pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal) - - -def setup_onroad_full_alert_long_text(click, pm: PubMaster): - setup_onroad_alert(click, pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt) - - -CASES = { - "homescreen": setup_homescreen, - "homescreen_paired": setup_homescreen, - "homescreen_prime": setup_homescreen, - "homescreen_update_available": setup_homescreen_update_available, - "homescreen_unifont": setup_homescreen, - "settings_device": setup_settings, - "settings_network": setup_settings_network, - "settings_network_advanced": setup_settings_network_advanced, - "settings_toggles": setup_settings_toggles, - "settings_software": setup_settings_software, - "settings_software_download": setup_settings_software_download, - "settings_software_release_notes": setup_settings_software_release_notes, - "settings_software_branch_switcher": setup_settings_software_branch_switcher, - "settings_firehose": setup_settings_firehose, - "settings_developer": setup_settings_developer, - "keyboard": setup_keyboard, - "pair_device": setup_pair_device, - "offroad_alert": setup_offroad_alert, - "confirmation_dialog": setup_confirmation_dialog, - "experimental_mode_description": setup_experimental_mode_description, - "openpilot_long_confirmation_dialog": setup_openpilot_long_confirmation_dialog, - "onroad": setup_onroad, - "onroad_sidebar": setup_onroad_sidebar, - "onroad_small_alert": setup_onroad_small_alert, - "onroad_medium_alert": setup_onroad_medium_alert, - "onroad_full_alert": setup_onroad_full_alert, - "onroad_full_alert_multiline": setup_onroad_full_alert_multiline, - "onroad_full_alert_long_text": setup_onroad_full_alert_long_text, -} - - -class TestUI: - def __init__(self): - os.environ["SCALE"] = os.getenv("SCALE", "1") - os.environ["BIG"] = "1" - sys.modules["mouseinfo"] = False - - def setup(self): - # Seed minimal offroad state - self.pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) - ds = messaging.new_message('deviceState') - ds.deviceState.networkType = log.DeviceState.NetworkType.wifi - for _ in range(5): - self.pm.send('deviceState', ds) - ds.clear_write_flag() - time.sleep(0.05) - time.sleep(0.5) - try: - self.ui = pywinctl.getWindowsWithTitle("UI")[0] - except Exception as e: - print(f"failed to find ui window, assuming that it's in the top left (for Xvfb) {e}") - self.ui = namedtuple("bb", ["left", "top", "width", "height"])(0, 0, 2160, 1080) - - def screenshot(self, name: str): - full_screenshot = pyautogui.screenshot() - cropped = full_screenshot.crop((self.ui.left, self.ui.top, self.ui.left + self.ui.width, self.ui.top + self.ui.height)) - cropped.save(SCREENSHOTS_DIR / f"{name}.png") - - def click(self, x: int, y: int, *args, **kwargs): - pyautogui.mouseDown(self.ui.left + x, self.ui.top + y, *args, **kwargs) - time.sleep(0.01) - pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs) - - @with_processes(["ui"]) - def test_ui(self, name, setup_case): - self.setup() - time.sleep(UI_DELAY) # wait for UI to start - setup_case(self.click, self.pm) - self.screenshot(name) - - -def create_screenshots(): - if TEST_OUTPUT_DIR.exists(): - shutil.rmtree(TEST_OUTPUT_DIR) - SCREENSHOTS_DIR.mkdir(parents=True) - - t = TestUI() - for name, setup in CASES.items(): - with OpenpilotPrefix(): - params = Params() - params.put("DongleId", "123456789012345") - - # Set branch name - params.put("UpdaterCurrentDescription", VERSION) - params.put("UpdaterNewDescription", VERSION) - - # Set terms and training version (to skip onboarding) - params.put("HasAcceptedTerms", terms_version) - params.put("CompletedTrainingVersion", training_version) - - if name == "homescreen_paired": - params.put("PrimeType", 0) # NONE - elif name == "homescreen_prime": - params.put("PrimeType", 2) # LITE - elif name == "homescreen_unifont": - params.put("LanguageSetting", "zh-CHT") # Traditional Chinese - - t.test_ui(name, setup) - - -if __name__ == "__main__": - create_screenshots() diff --git a/selfdrive/ui/tests/test_ui/template.html b/selfdrive/ui/tests/test_ui/template.html deleted file mode 100644 index 68df5879e66..00000000000 --- a/selfdrive/ui/tests/test_ui/template.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - -{% for name, (image, ref_image) in cases.items() %} - -

{{name}}

-
-
- -
-
- -
- -{% endfor %} - \ No newline at end of file diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 6fb8d5049bd..92664ae5b32 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -1,8 +1,6 @@ #!/usr/bin/env python3 import argparse import os -import pyautogui -import subprocess import dearpygui.dearpygui as dpg import multiprocessing import uuid @@ -316,11 +314,12 @@ def main(route_to_load=None, layout_to_load=None): dpg.create_context() # TODO: find better way of calculating display scaling - try: - w, h = next(tuple(map(int, l.split()[0].split('x'))) for l in subprocess.check_output(['xrandr']).decode().split('\n') if '*' in l) # actual resolution - scale = pyautogui.size()[0] / w # scaled resolution - except Exception: - scale = 1 + #try: + # w, h = next(tuple(map(int, l.split()[0].split('x'))) for l in subprocess.check_output(['xrandr']).decode().split('\n') if '*' in l) # actual resolution + # scale = pyautogui.size()[0] / w # scaled resolution + #except Exception: + # scale = 1 + scale = 1 with dpg.font_registry(): default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale * 2)) # 2x then scale for hidpi @@ -328,9 +327,8 @@ def main(route_to_load=None, layout_to_load=None): dpg.set_global_font_scale(0.5) viewport_width, viewport_height = int(1200 * scale), int(800 * scale) - mouse_x, mouse_y = pyautogui.position() # TODO: find better way of creating the window where the user is (default dpg behavior annoying on multiple displays) dpg.create_viewport( - title='JotPluggler', width=viewport_width, height=viewport_height, x_pos=mouse_x - viewport_width // 2, y_pos=mouse_y - viewport_height // 2 + title='JotPluggler', width=viewport_width, height=viewport_height, ) dpg.setup_dearpygui() diff --git a/uv.lock b/uv.lock index cd077d3de7d..51e71ce6457 100644 --- a/uv.lock +++ b/uv.lock @@ -374,18 +374,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, ] -[[package]] -name = "ewmhlib" -version = "0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/3a/46ca34abf0725a754bc44ef474ad34aedcc3ea23b052d97b18b76715a6a9/EWMHlib-0.2-py3-none-any.whl", hash = "sha256:f5b07d8cfd4c7734462ee744c32d490f2f3233fa7ab354240069344208d2f6f5", size = 46657, upload-time = "2024-04-17T08:15:56.338Z" }, -] - [[package]] name = "execnet" version = "2.1.2" @@ -720,17 +708,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, ] -[[package]] -name = "mouseinfo" -version = "0.1.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyperclip" }, - { name = "python3-xlib", marker = "sys_platform == 'linux'" }, - { name = "rubicon-objc", marker = "sys_platform == 'darwin'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/28/fa/b2ba8229b9381e8f6381c1dcae6f4159a7f72349e414ed19cfbbd1817173/MouseInfo-0.1.3.tar.gz", hash = "sha256:2c62fb8885062b8e520a3cce0a297c657adcc08c60952eb05bc8256ef6f7f6e7", size = 10850, upload-time = "2020-03-27T21:20:10.136Z" } - [[package]] name = "mpmath" version = "1.3.0" @@ -873,8 +850,6 @@ dev = [ { name = "matplotlib" }, { name = "opencv-python-headless" }, { name = "parameterized" }, - { name = "pyautogui" }, - { name = "pywinctl" }, ] docs = [ { name = "jinja2" }, @@ -930,7 +905,6 @@ requires-dist = [ { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, { name = "pyaudio" }, - { name = "pyautogui", marker = "extra == 'dev'" }, { name = "pycapnp", specifier = "==2.1.0" }, { name = "pycryptodome" }, { name = "pyjwt" }, @@ -943,7 +917,6 @@ requires-dist = [ { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-timeout", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, - { name = "pywinctl", marker = "extra == 'dev'" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib", specifier = ">5.5.0.3" }, @@ -1144,22 +1117,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b0/6a/d25812e5f79f06285767ec607b39149d02aa3b31d50c2269768f48768930/PyAudio-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3", size = 164126, upload-time = "2023-11-07T07:11:41.539Z" }, ] -[[package]] -name = "pyautogui" -version = "0.9.54" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mouseinfo" }, - { name = "pygetwindow" }, - { name = "pymsgbox" }, - { name = "pyobjc-core", marker = "sys_platform == 'darwin'" }, - { name = "pyobjc-framework-quartz", marker = "sys_platform == 'darwin'" }, - { name = "pyscreeze" }, - { name = "python3-xlib", marker = "sys_platform == 'linux'" }, - { name = "pytweening" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/ff/cdae0a8c2118a0de74b6cf4cbcdcaf8fd25857e6c3f205ce4b1794b27814/PyAutoGUI-0.9.54.tar.gz", hash = "sha256:dd1d29e8fd118941cb193f74df57e5c6ff8e9253b99c7b04f39cfc69f3ae04b2", size = 61236, upload-time = "2023-05-24T20:11:32.972Z" } - [[package]] name = "pycapnp" version = "2.1.0" @@ -1220,15 +1177,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a0/c4/b4d4827c93ef43c01f599ef31453ccc1c132b353284fc6c87d535c233129/pyee-13.0.1-py3-none-any.whl", hash = "sha256:af2f8fede4171ef667dfded53f96e2ed0d6e6bd7ee3bb46437f77e3b57689228", size = 15659, upload-time = "2026-02-14T21:12:26.263Z" }, ] -[[package]] -name = "pygetwindow" -version = "0.0.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyrect" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e1/70/c7a4f46dbf06048c6d57d9489b8e0f9c4c3d36b7479f03c5ca97eaa2541d/PyGetWindow-0.0.9.tar.gz", hash = "sha256:17894355e7d2b305cd832d717708384017c1698a90ce24f6f7fbf0242dd0a688", size = 9699, upload-time = "2020-10-04T02:12:50.806Z" } - [[package]] name = "pygments" version = "2.19.2" @@ -1270,2671 +1218,239 @@ wheels = [ ] [[package]] -name = "pymonctl" -version = "0.92" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ewmhlib", marker = "sys_platform == 'linux'" }, - { name = "pyobjc", marker = "sys_platform == 'darwin'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/13/076a20da28b82be281f7e43e16d9da0f545090f5d14b2125699232b9feba/PyMonCtl-0.92-py3-none-any.whl", hash = "sha256:2495d8dab78f9a7dbce37e74543e60b8bd404a35c3108935697dda7768611b5a", size = 45945, upload-time = "2024-04-22T10:07:09.566Z" }, -] - -[[package]] -name = "pymsgbox" -version = "2.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/6a/e80da7594ee598a776972d09e2813df2b06b3bc29218f440631dfa7c78a8/pymsgbox-2.0.1.tar.gz", hash = "sha256:98d055c49a511dcc10fa08c3043e7102d468f5e4b3a83c6d3c61df722c7d798d", size = 20768, upload-time = "2025-09-09T00:38:56.863Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/3e/08c8cac81b2b2f7502746e6b9c8e5b0ec6432cd882c605560fc409aaf087/pymsgbox-2.0.1-py3-none-any.whl", hash = "sha256:5de8ec19bca2ca7e6c09d39c817c83f17c75cee80275235f43a9931db699f73b", size = 9994, upload-time = "2025-09-09T00:38:55.672Z" }, -] - -[[package]] -name = "pyobjc" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-accessibility", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-accounts", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-addressbook" }, - { name = "pyobjc-framework-adservices", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-adsupport", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-applescriptkit" }, - { name = "pyobjc-framework-applescriptobjc", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-applicationservices" }, - { name = "pyobjc-framework-apptrackingtransparency", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-arkit", marker = "platform_release >= '25.0'" }, - { name = "pyobjc-framework-audiovideobridging", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-authenticationservices", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-automaticassessmentconfiguration", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-automator" }, - { name = "pyobjc-framework-avfoundation", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-avkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-avrouting", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-backgroundassets", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-browserenginekit", marker = "platform_release >= '23.4'" }, - { name = "pyobjc-framework-businesschat", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-calendarstore", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-callkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-carbon" }, - { name = "pyobjc-framework-cfnetwork" }, - { name = "pyobjc-framework-cinematic", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-classkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-cloudkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-collaboration", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-colorsync", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-compositorservices", marker = "platform_release >= '25.0'" }, - { name = "pyobjc-framework-contacts", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-contactsui", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coreaudiokit" }, - { name = "pyobjc-framework-corebluetooth", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-coredata" }, - { name = "pyobjc-framework-corehaptics", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-corelocation", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-coremedia", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-coremediaio", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-coremidi" }, - { name = "pyobjc-framework-coreml", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-coremotion", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-coreservices" }, - { name = "pyobjc-framework-corespotlight", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-coretext" }, - { name = "pyobjc-framework-corewlan", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-cryptotokenkit", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-datadetection", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-devicecheck", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-devicediscoveryextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-dictionaryservices", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-discrecording" }, - { name = "pyobjc-framework-discrecordingui" }, - { name = "pyobjc-framework-diskarbitration" }, - { name = "pyobjc-framework-dvdplayback" }, - { name = "pyobjc-framework-eventkit", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-exceptionhandling" }, - { name = "pyobjc-framework-executionpolicy", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-extensionkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-externalaccessory", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-fileprovider", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-fileproviderui", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-findersync", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-fsevents", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-fskit", marker = "platform_release >= '24.4'" }, - { name = "pyobjc-framework-gamecenter", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gamecontroller", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-gamekit", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-gameplaykit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-gamesave", marker = "platform_release >= '25.0'" }, - { name = "pyobjc-framework-healthkit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-imagecapturecore", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-inputmethodkit", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-installerplugins" }, - { name = "pyobjc-framework-instantmessage", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-intents", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-intentsui", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-iobluetooth" }, - { name = "pyobjc-framework-iobluetoothui" }, - { name = "pyobjc-framework-iosurface", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-ituneslibrary", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-kernelmanagement", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-latentsemanticmapping" }, - { name = "pyobjc-framework-launchservices" }, - { name = "pyobjc-framework-libdispatch", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-libxpc", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-linkpresentation", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-localauthentication", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-localauthenticationembeddedui", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mailkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mapkit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaaccessibility", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaextension", marker = "platform_release >= '24.0'" }, - { name = "pyobjc-framework-medialibrary", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-mediaplayer", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-mediatoolbox", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-metal", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalfx", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-metalkit", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-metalperformanceshaders", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-metalperformanceshadersgraph", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-metrickit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-mlcompute", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-modelio", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-multipeerconnectivity", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-naturallanguage", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-netfs", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-network", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-networkextension", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-notificationcenter", marker = "platform_release >= '14.0'" }, - { name = "pyobjc-framework-opendirectory", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-osakit" }, - { name = "pyobjc-framework-oslog", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-passkit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-pencilkit", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-phase", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-photos", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-photosui", marker = "platform_release >= '15.0'" }, - { name = "pyobjc-framework-preferencepanes" }, - { name = "pyobjc-framework-pushkit", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-quartz" }, - { name = "pyobjc-framework-quicklookthumbnailing", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-replaykit", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-safariservices", marker = "platform_release >= '16.0'" }, - { name = "pyobjc-framework-safetykit", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-scenekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-screencapturekit", marker = "platform_release >= '21.4'" }, - { name = "pyobjc-framework-screensaver" }, - { name = "pyobjc-framework-screentime", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-scriptingbridge", marker = "platform_release >= '9.0'" }, - { name = "pyobjc-framework-searchkit" }, - { name = "pyobjc-framework-security" }, - { name = "pyobjc-framework-securityfoundation" }, - { name = "pyobjc-framework-securityinterface" }, - { name = "pyobjc-framework-securityui", marker = "platform_release >= '24.4'" }, - { name = "pyobjc-framework-sensitivecontentanalysis", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-servicemanagement", marker = "platform_release >= '10.0'" }, - { name = "pyobjc-framework-sharedwithyou", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-sharedwithyoucore", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-shazamkit", marker = "platform_release >= '21.0'" }, - { name = "pyobjc-framework-social", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-soundanalysis", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-speech", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-spritekit", marker = "platform_release >= '13.0'" }, - { name = "pyobjc-framework-storekit", marker = "platform_release >= '11.0'" }, - { name = "pyobjc-framework-symbols", marker = "platform_release >= '23.0'" }, - { name = "pyobjc-framework-syncservices" }, - { name = "pyobjc-framework-systemconfiguration" }, - { name = "pyobjc-framework-systemextensions", marker = "platform_release >= '19.0'" }, - { name = "pyobjc-framework-threadnetwork", marker = "platform_release >= '22.0'" }, - { name = "pyobjc-framework-uniformtypeidentifiers", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-usernotifications", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-usernotificationsui", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-videosubscriberaccount", marker = "platform_release >= '18.0'" }, - { name = "pyobjc-framework-videotoolbox", marker = "platform_release >= '12.0'" }, - { name = "pyobjc-framework-virtualization", marker = "platform_release >= '20.0'" }, - { name = "pyobjc-framework-vision", marker = "platform_release >= '17.0'" }, - { name = "pyobjc-framework-webkit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/17/06/d77639ba166cc09aed2d32ae204811b47bc5d40e035cdc9bff7fff72ec5f/pyobjc-12.1.tar.gz", hash = "sha256:686d6db3eb3182fac9846b8ce3eedf4c7d2680b21b8b8d6e6df054a17e92a12d", size = 11345, upload-time = "2025-11-14T10:07:28.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/00/1085de7b73abf37ec27ad59f7a1d7a406e6e6da45720bced2e198fdf1ddf/pyobjc-12.1-py3-none-any.whl", hash = "sha256:6f8c36cf87b1159d2ca1aa387ffc3efcd51cc3da13ef47c65f45e6d9fbccc729", size = 4226, upload-time = "2025-11-14T09:30:25.185Z" }, -] - -[[package]] -name = "pyobjc-core" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b8/b6/d5612eb40be4fd5ef88c259339e6313f46ba67577a95d86c3470b951fce0/pyobjc_core-12.1.tar.gz", hash = "sha256:2bb3903f5387f72422145e1466b3ac3f7f0ef2e9960afa9bcd8961c5cbf8bd21", size = 1000532, upload-time = "2025-11-14T10:08:28.292Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/5a/6b15e499de73050f4a2c88fff664ae154307d25dc04da8fb38998a428358/pyobjc_core-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:818bcc6723561f207e5b5453efe9703f34bc8781d11ce9b8be286bb415eb4962", size = 678335, upload-time = "2025-11-14T09:32:20.107Z" }, -] - -[[package]] -name = "pyobjc-framework-accessibility" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/87/8ca40428d05a668fecc638f2f47dba86054dbdc35351d247f039749de955/pyobjc_framework_accessibility-12.1.tar.gz", hash = "sha256:5ff362c3425edc242d49deec11f5f3e26e565cefb6a2872eda59ab7362149772", size = 29800, upload-time = "2025-11-14T10:08:31.949Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/95/9ea0d1c16316b4b5babf4b0515e9a133ac64269d3ec031f15ee9c7c2a8c1/pyobjc_framework_accessibility-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:537691a0b28fedb8385cd093df069a6e5d7e027629671fc47b50210404eca20b", size = 11335, upload-time = "2025-11-14T09:35:30.81Z" }, -] - -[[package]] -name = "pyobjc-framework-accounts" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/10/f6fe336c7624d6753c1f6edac102310ce4434d49b548c479e8e6420d4024/pyobjc_framework_accounts-12.1.tar.gz", hash = "sha256:76d62c5e7b831eb8f4c9ca6abaf79d9ed961dfffe24d89a041fb1de97fe56a3e", size = 15202, upload-time = "2025-11-14T10:08:33.995Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/70/5f9214250f92fbe2e07f35778875d2771d612f313af2a0e4bacba80af28e/pyobjc_framework_accounts-12.1-py2.py3-none-any.whl", hash = "sha256:e1544ad11a2f889a7aaed649188d0e76d58595a27eec07ca663847a7adb21ae5", size = 5104, upload-time = "2025-11-14T09:35:40.246Z" }, -] - -[[package]] -name = "pyobjc-framework-addressbook" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/28/0404af2a1c6fa8fd266df26fb6196a8f3fb500d6fe3dab94701949247bea/pyobjc_framework_addressbook-12.1.tar.gz", hash = "sha256:c48b740cf981103cef1743d0804a226d86481fcb839bd84b80e9a586187e8000", size = 44359, upload-time = "2025-11-14T10:08:37.687Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b6/33/da709c69cbb60df9522cd614d5c23c15b649b72e5d62fed1048e75c70e7b/pyobjc_framework_addressbook-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7893dd784322f4674299fb3ca40cb03385e5eddb78defd38f08c0b730813b56c", size = 12894, upload-time = "2025-11-14T09:35:47.498Z" }, -] - -[[package]] -name = "pyobjc-framework-adservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/04/1c3d3e0a1ac981664f30b33407dcdf8956046ecde6abc88832cf2aa535f4/pyobjc_framework_adservices-12.1.tar.gz", hash = "sha256:7a31fc8d5c6fd58f012db87c89ba581361fc905114bfb912e0a3a87475c02183", size = 11793, upload-time = "2025-11-14T10:08:39.56Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/13/f7796469b25f50750299c4b0e95dc2f75c7c7fc4c93ef2c644f947f10529/pyobjc_framework_adservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ca3c55e35b2abb3149a0bce5de9a1f7e8ee4f8642036910ca8586ab2e161538", size = 3492, upload-time = "2025-11-14T09:35:57.344Z" }, -] - -[[package]] -name = "pyobjc-framework-adsupport" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/77/f26a2e9994d4df32e9b3680c8014e350b0f1c78d7673b3eba9de2e04816f/pyobjc_framework_adsupport-12.1.tar.gz", hash = "sha256:9a68480e76de567c339dca29a8c739d6d7b5cad30e1cd585ff6e49ec2fc283dd", size = 11645, upload-time = "2025-11-14T10:08:41.439Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/1a/3e90d5a09953bde7b60946cd09cca1411aed05dea855cb88cb9e944c7006/pyobjc_framework_adsupport-12.1-py2.py3-none-any.whl", hash = "sha256:97dcd8799dd61f047bb2eb788bbde81f86e95241b5e5173a3a61cfc05b5598b1", size = 3401, upload-time = "2025-11-14T09:35:59.039Z" }, -] - -[[package]] -name = "pyobjc-framework-applescriptkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/f1/e0c07b2a9eb98f1a2050f153d287a52a92f873eeddb41b74c52c144d8767/pyobjc_framework_applescriptkit-12.1.tar.gz", hash = "sha256:cb09f88cf0ad9753dedc02720065818f854b50e33eb4194f0ea34de6d7a3eb33", size = 11451, upload-time = "2025-11-14T10:08:43.328Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/70/6c399c6ebc37a4e48acf63967e0a916878aedfe420531f6d739215184c0c/pyobjc_framework_applescriptkit-12.1-py2.py3-none-any.whl", hash = "sha256:b955fc017b524027f635d92a8a45a5fd9fbae898f3e03de16ecd94aa4c4db987", size = 4352, upload-time = "2025-11-14T09:36:00.705Z" }, -] - -[[package]] -name = "pyobjc-framework-applescriptobjc" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/4b/e4d1592207cbe17355e01828bdd11dd58f31356108f6a49f5e0484a5df50/pyobjc_framework_applescriptobjc-12.1.tar.gz", hash = "sha256:dce080ed07409b0dda2fee75d559bd312ea1ef0243a4338606440f282a6a0f5f", size = 11588, upload-time = "2025-11-14T10:08:45.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3e/5f/9ce6706399706930eb29c5308037109c30cfb36f943a6df66fdf38cc842a/pyobjc_framework_applescriptobjc-12.1-py2.py3-none-any.whl", hash = "sha256:79068f982cc22471712ce808c0a8fd5deea11258fc8d8c61968a84b1962a3d10", size = 4454, upload-time = "2025-11-14T09:36:02.276Z" }, -] - -[[package]] -name = "pyobjc-framework-applicationservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coretext" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/be/6a/d4e613c8e926a5744fc47a9e9fea08384a510dc4f27d844f7ad7a2d793bd/pyobjc_framework_applicationservices-12.1.tar.gz", hash = "sha256:c06abb74f119bc27aeb41bf1aef8102c0ae1288aec1ac8665ea186a067a8945b", size = 103247, upload-time = "2025-11-14T10:08:52.18Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/37/a7/55fa88def5c02732c4b747606ff1cbce6e1f890734bbd00f5596b21eaa02/pyobjc_framework_applicationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c8f6e2fb3b3e9214ab4864ef04eee18f592b46a986c86ea0113448b310520532", size = 32835, upload-time = "2025-11-14T09:36:11.855Z" }, -] - -[[package]] -name = "pyobjc-framework-apptrackingtransparency" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0d/de/f24348982ecab0cb13067c348fc5fbc882c60d704ca290bada9a2b3e594b/pyobjc_framework_apptrackingtransparency-12.1.tar.gz", hash = "sha256:e25bf4e4dfa2d929993ee8e852b28fdf332fa6cde0a33328fdc3b2f502fa50ec", size = 12407, upload-time = "2025-11-14T10:08:54.118Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/b2/90120b93ecfb099b6af21696c26356ad0f2182bdef72b6cba28aa6472ca6/pyobjc_framework_apptrackingtransparency-12.1-py2.py3-none-any.whl", hash = "sha256:23a98ade55495f2f992ecf62c3cbd8f648cbd68ba5539c3f795bf66de82e37ca", size = 3879, upload-time = "2025-11-14T09:36:26.425Z" }, -] - -[[package]] -name = "pyobjc-framework-arkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/8b/843fe08e696bca8e7fc129344965ab6280f8336f64f01ba0a8862d219c3f/pyobjc_framework_arkit-12.1.tar.gz", hash = "sha256:0c5c6b702926179700b68ba29b8247464c3b609fd002a07a3308e72cfa953adf", size = 35814, upload-time = "2025-11-14T10:08:57.55Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/1e/64c55b409243b3eb9abc7a99e7b27ad4e16b9e74bc4b507fb7e7b81fd41a/pyobjc_framework_arkit-12.1-py2.py3-none-any.whl", hash = "sha256:f6d39e28d858ee03f052d6780a552247e682204382dbc090f1d3192fa1b21493", size = 8302, upload-time = "2025-11-14T09:36:28.127Z" }, -] - -[[package]] -name = "pyobjc-framework-audiovideobridging" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9f/51/f81581e7a3c5cb6c9254c6f1e1ee1d614930493761dec491b5b0d49544b9/pyobjc_framework_audiovideobridging-12.1.tar.gz", hash = "sha256:6230ace6bec1f38e8a727c35d054a7be54e039b3053f98e6dd8d08d6baee2625", size = 38457, upload-time = "2025-11-14T10:09:01.122Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/8e/a28badfcc6c731696e3d3a8a83927bd844d992f9152f903c2fee355702ca/pyobjc_framework_audiovideobridging-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:010021502649e2cca4e999a7c09358d48c6b0ed83530bbc0b85bba6834340e4b", size = 11052, upload-time = "2025-11-14T09:36:34.475Z" }, -] - -[[package]] -name = "pyobjc-framework-authenticationservices" -version = "12.1" +name = "pyopenssl" +version = "24.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "cryptography" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6c/18/86218de3bf67fc1d810065f353d9df70c740de567ebee8550d476cb23862/pyobjc_framework_authenticationservices-12.1.tar.gz", hash = "sha256:cef71faeae2559f5c0ff9a81c9ceea1c81108e2f4ec7de52a98c269feff7a4b6", size = 58683, upload-time = "2025-11-14T10:09:06.003Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5d/70/ff56a63248562e77c0c8ee4aefc3224258f1856977e0c1472672b62dadb8/pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95", size = 184323, upload-time = "2024-07-20T17:26:31.252Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/1d/e9f296fe1ee9a074ff6c45ce9eb109fc3b45696de000f373265c8e42fd47/pyobjc_framework_authenticationservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6fd5ce10fe5359cbbfe03eb12cab3e01992b32ab65653c579b00ac93cf674985", size = 20738, upload-time = "2025-11-14T09:36:51.094Z" }, + { url = "https://files.pythonhosted.org/packages/d9/dd/e0aa7ebef5168c75b772eda64978c597a9129b46be17779054652a7999e4/pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d", size = 58390, upload-time = "2024-07-20T17:26:29.057Z" }, ] [[package]] -name = "pyobjc-framework-automaticassessmentconfiguration" -version = "12.1" +name = "pyparsing" +version = "3.3.2" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/24/080afe8189c47c4bb3daa191ccfd962400ca31a67c14b0f7c2d002c2e249/pyobjc_framework_automaticassessmentconfiguration-12.1.tar.gz", hash = "sha256:2b732c02d9097682ca16e48f5d3b10056b740bc091e217ee4d5715194c8970b1", size = 21895, upload-time = "2025-11-14T10:09:08.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/b2/fbec3d649bf275d7a9604e5f56015be02ef8dcf002f4ae4d760436b8e222/pyobjc_framework_automaticassessmentconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c2e22ea67d7e6d6a84d968169f83d92b59857a49ab12132de07345adbfea8a62", size = 9332, upload-time = "2025-11-14T09:37:07.083Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] -name = "pyobjc-framework-automator" -version = "12.1" +name = "pyserial" +version = "3.5" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7e/08/362bf6ac2bba393c46cf56078d4578b692b56857c385e47690637a72f0dd/pyobjc_framework_automator-12.1.tar.gz", hash = "sha256:7491a99347bb30da3a3f744052a03434ee29bee3e2ae520576f7e796740e4ba7", size = 186068, upload-time = "2025-11-14T10:09:20.82Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/36/2e8c36ddf20d501f9d344ed694e39021190faffc44b596f3a430bf437174/pyobjc_framework_automator-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4df9aec77f0fbca66cd3534d1b8398fe6f3e3c2748c0fc12fec2546c7f2e3ffd", size = 10034, upload-time = "2025-11-14T09:37:20.293Z" }, + { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, ] [[package]] -name = "pyobjc-framework-avfoundation" -version = "12.1" +name = "pytest" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "iniconfig" }, + { name = "packaging" }, + { name = "pluggy" }, + { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cd/42/c026ab308edc2ed5582d8b4b93da6b15d1b6557c0086914a4aabedd1f032/pyobjc_framework_avfoundation-12.1.tar.gz", hash = "sha256:eda0bb60be380f9ba2344600c4231dd58a3efafa99fdc65d3673ecfbb83f6fcb", size = 310047, upload-time = "2025-11-14T10:09:40.069Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/00/ca471e5dd33f040f69320832e45415d00440260bf7f8221a9df4c4662659/pyobjc_framework_avfoundation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bf634f89265b4d93126153200d885b6de4859ed6b3bc65e69ff75540bc398406", size = 83375, upload-time = "2025-11-14T09:37:47.262Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] -name = "pyobjc-framework-avkit" -version = "12.1" +name = "pytest-asyncio" +version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, + { name = "pytest" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/a9/e44db1a1f26e2882c140f1d502d508b1f240af9048909dcf1e1a687375b4/pyobjc_framework_avkit-12.1.tar.gz", hash = "sha256:a5c0ddb0cb700f9b09c8afeca2c58952d554139e9bb078236d2355b1fddfb588", size = 28473, upload-time = "2025-11-14T10:09:43.105Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/75/34/e77b18f7ed0bd707afd388702e910bdf2d0acee39d1139e8619c916d3eb4/pyobjc_framework_avkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eef2c0a51465de025a4509db05ef18ca2b678bb00ee0a8fbad7fd470edfd58f9", size = 11613, upload-time = "2025-11-14T09:38:19.78Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, ] [[package]] -name = "pyobjc-framework-avrouting" -version = "12.1" +name = "pytest-cpp" +version = "2.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/83/15bf6c28ec100dae7f92d37c9e117b3b4ee6b4873db062833e16f1cfd6c4/pyobjc_framework_avrouting-12.1.tar.gz", hash = "sha256:6a6c5e583d14f6501df530a9d0559a32269a821fc8140e3646015f097155cd1c", size = 20031, upload-time = "2025-11-14T10:09:45.701Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cf/a1/c2679d7ff2da20a0f89c7820ae2739cde739eac9b43c192531117b31b5f4/pytest_cpp-2.6.0.tar.gz", hash = "sha256:c2f49d3c038539ac84786a94d852e4f4619c34c95979c2bc69c20b3bdf051d85", size = 465490, upload-time = "2024-09-18T00:08:08.251Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/68/54/fa24f666525c1332a11b2de959c9877b0fe08f00f29ecf96964b24246c13/pyobjc_framework_avrouting-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4c0fb0d3d260527320377a70c87688ca5e4a208b09fddcae2b4257d7fe9b1e18", size = 8450, upload-time = "2025-11-14T09:38:34.941Z" }, + { url = "https://files.pythonhosted.org/packages/2a/44/dc2f5d53165264ae5831f361fe7723c45da05718a97015b2eddc452cf503/pytest_cpp-2.6.0-py3-none-any.whl", hash = "sha256:b33de94609450feea2fba9efff3558b8ac8f1fdf40a99e263b395d4798b911bb", size = 15074, upload-time = "2024-09-18T00:08:06.415Z" }, ] [[package]] -name = "pyobjc-framework-backgroundassets" -version = "12.1" +name = "pytest-mock" +version = "3.15.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/d1/e917fba82790495152fd3508c5053827658881cf7e9887ba60def5e3f221/pyobjc_framework_backgroundassets-12.1.tar.gz", hash = "sha256:8da34df9ae4519c360c429415477fdaf3fbba5addbc647b3340b8783454eb419", size = 26210, upload-time = "2025-11-14T10:09:48.792Z" } +sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/de/34/bbba61f0e8ecb0fe0da7aa2c9ea15f7cb0dca2fb2914fcdcd77b782b5c11/pyobjc_framework_backgroundassets-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2c11cb98650c1a4bc68eeb4b040541ba96613434c5957e98e9bb363413b23c91", size = 10786, upload-time = "2025-11-14T09:38:48.341Z" }, + { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] [[package]] -name = "pyobjc-framework-browserenginekit" -version = "12.1" +name = "pytest-subtests" +version = "0.15.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, + { name = "attrs" }, + { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/b9/39f9de1730e6f8e73be0e4f0c6087cd9439cbe11645b8052d22e1fb8e69b/pyobjc_framework_browserenginekit-12.1.tar.gz", hash = "sha256:6a1a34a155778ab55ab5f463e885f2a3b4680231264e1fe078e62ddeccce49ed", size = 29120, upload-time = "2025-11-14T10:09:51.582Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bb/d9/20097971a8d315e011e055d512fa120fd6be3bdb8f4b3aa3e3c6bf77bebc/pytest_subtests-0.15.0.tar.gz", hash = "sha256:cb495bde05551b784b8f0b8adfaa27edb4131469a27c339b80fd8d6ba33f887c", size = 18525, upload-time = "2025-10-20T16:26:18.358Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/e0/8d2cebbfcfd6aacb805ae0ae7ba931f6a39140540b2e1e96719e7be28359/pyobjc_framework_browserenginekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d15766bb841b081447015c9626e2a766febfe651f487893d29c5d72bef976b94", size = 11545, upload-time = "2025-11-14T09:39:00.988Z" }, + { url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" }, ] [[package]] -name = "pyobjc-framework-businesschat" -version = "12.1" +name = "pytest-timeout" +version = "2.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/da/bc09b6ed19e9ea38ecca9387c291ca11fa680a8132d82b27030f82551c23/pyobjc_framework_businesschat-12.1.tar.gz", hash = "sha256:f6fa3a8369a1a51363e1757530128741d9d09ed90692a1d6777a4c0fbad25868", size = 12055, upload-time = "2025-11-14T10:09:53.436Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/88/4c727424b05efa33ed7f6c45e40333e5a8a8dc5bb238e34695addd68463b/pyobjc_framework_businesschat-12.1-py2.py3-none-any.whl", hash = "sha256:f66ce741507b324de3c301d72ba0cfa6aaf7093d7235972332807645c118cc29", size = 3474, upload-time = "2025-11-14T09:39:10.771Z" }, + { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, ] [[package]] -name = "pyobjc-framework-calendarstore" -version = "12.1" -source = { registry = "https://pypi.org/simple" } +name = "pytest-xdist" +version = "3.7.1.dev24+g2b4372bd6" +source = { git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da#2b4372bd62699fb412c4fe2f95bf9f01bd2018da" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/88/41/ae955d1c44dcc18b5b9df45c679e9a08311a0f853b9d981bca760cf1eef2/pyobjc_framework_calendarstore-12.1.tar.gz", hash = "sha256:f9a798d560a3c99ad4c0d2af68767bc5695d8b1aabef04d8377861cd1d6d1670", size = 52272, upload-time = "2025-11-14T10:09:58.48Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/70/f68aebdb7d3fa2dec2e9da9e9cdaa76d370de326a495917dbcde7bb7711e/pyobjc_framework_calendarstore-12.1-py2.py3-none-any.whl", hash = "sha256:18533e0fcbcdd29ee5884dfbd30606710f65df9b688bf47daee1438ee22e50cc", size = 5285, upload-time = "2025-11-14T09:39:12.473Z" }, + { name = "execnet" }, + { name = "pytest" }, ] [[package]] -name = "pyobjc-framework-callkit" -version = "12.1" +name = "python-dateutil" +version = "2.9.0.post0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1a/c0/1859d4532d39254df085309aff55b85323576f00a883626325af40da4653/pyobjc_framework_callkit-12.1.tar.gz", hash = "sha256:fd6dc9688b785aab360139d683be56f0844bf68bf5e45d0eb770cb68221083cc", size = 29171, upload-time = "2025-11-14T10:10:01.336Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/b7/b3a498b14751b4be6af5272c9be9ded718aa850ebf769b052c7d610a142a/pyobjc_framework_callkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:12adc0ace464a057f8908187698e1d417c6c53619797a69d096f4329bffb1089", size = 11334, upload-time = "2025-11-14T09:39:18.622Z" }, + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] [[package]] -name = "pyobjc-framework-carbon" -version = "12.1" +name = "pyyaml" +version = "6.0.3" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0c/0f/9ab8e518a4e5ac4a1e2fdde38a054c32aef82787ff7f30927345c18b7765/pyobjc_framework_carbon-12.1.tar.gz", hash = "sha256:57a72807db252d5746caccc46da4bd20ff8ea9e82109af9f72735579645ff4f0", size = 37293, upload-time = "2025-11-14T10:10:04.464Z" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/9e/91853c8f98b9d5bccf464113908620c94cc12c2a3e4625f3ce172e3ea4bc/pyobjc_framework_carbon-12.1-py2.py3-none-any.whl", hash = "sha256:f8b719b3c7c5cf1d61ac7c45a8a70b5e5e5a83fa02f5194c2a48a7e81a3d1b7f", size = 4625, upload-time = "2025-11-14T09:39:27.937Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] [[package]] -name = "pyobjc-framework-cfnetwork" -version = "12.1" +name = "pyyaml-env-tag" +version = "1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/6a/f5f0f191956e187db85312cbffcc41bf863670d121b9190b4a35f0d36403/pyobjc_framework_cfnetwork-12.1.tar.gz", hash = "sha256:2d16e820f2d43522c793f55833fda89888139d7a84ca5758548ba1f3a325a88d", size = 44383, upload-time = "2025-11-14T10:10:08.428Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/0b/28034e63f3a25b30ede814469c3f57d44268cbced19664c84a8664200f9d/pyobjc_framework_cfnetwork-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:92760da248c757085fc39bce4388a0f6f0b67540e51edf60a92ad60ca907d071", size = 19135, upload-time = "2025-11-14T09:39:36.382Z" }, + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, ] [[package]] -name = "pyobjc-framework-cinematic" -version = "12.1" +name = "pyzmq" +version = "27.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-metal" }, + { name = "cffi", marker = "implementation_name == 'pypy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/67/4e/f4cc7f9f7f66df0290c90fe445f1ff5aa514c6634f5203fe049161053716/pyobjc_framework_cinematic-12.1.tar.gz", hash = "sha256:795068c30447548c0e8614e9c432d4b288b13d5614622ef2f9e3246132329b06", size = 21215, upload-time = "2025-11-14T10:10:10.795Z" } +sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/a0/cd85c827ce5535c08d936e5723c16ee49f7ff633f2e9881f4f58bf83e4ce/pyobjc_framework_cinematic-12.1-py2.py3-none-any.whl", hash = "sha256:c003543bb6908379680a93dfd77a44228686b86c118cf3bc930f60241d0cd141", size = 5031, upload-time = "2025-11-14T09:39:49.003Z" }, + { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, + { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, + { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, + { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, + { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, + { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, ] [[package]] -name = "pyobjc-framework-classkit" -version = "12.1" +name = "qrcode" +version = "8.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/ef/67815278023b344a79c7e95f748f647245d6f5305136fc80615254ad447c/pyobjc_framework_classkit-12.1.tar.gz", hash = "sha256:8d1e9dd75c3d14938ff533d88b72bca2d34918e4461f418ea323bfb2498473b4", size = 26298, upload-time = "2025-11-14T10:10:13.406Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/87/5e/cf43c647af872499fc8e80cc6ac6e9ad77d9c77861dc2e62bdd9b01473ce/pyobjc_framework_classkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c027a3cd9be5fee3f605589118b8b278297c384a271f224c1a98b224e0c087e6", size = 8877, upload-time = "2025-11-14T09:39:54.979Z" }, + { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" }, ] [[package]] -name = "pyobjc-framework-cloudkit" -version = "12.1" +name = "raylib" +version = "5.5.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-accounts" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coredata" }, - { name = "pyobjc-framework-corelocation" }, + { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/09/762ee4f3ae8568b8e0e5392c705bc4aa1929aa454646c124ca470f1bf9fc/pyobjc_framework_cloudkit-12.1.tar.gz", hash = "sha256:1dddd38e60863f88adb3d1d37d3b4ccb9cbff48c4ef02ab50e36fa40c2379d2f", size = 53730, upload-time = "2025-11-14T10:10:17.831Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/71/cbef7179bf1a594558ea27f1e5ad18f5c17ef71a8a24192aae16127bc849/pyobjc_framework_cloudkit-12.1-py2.py3-none-any.whl", hash = "sha256:875e37bf1a2ce3d05c2492692650104f2d908b56b71a0aedf6620bc517c6c9ca", size = 11090, upload-time = "2025-11-14T09:40:04.207Z" }, + { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, + { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, + { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, ] [[package]] -name = "pyobjc-framework-cocoa" -version = "12.1" +name = "requests" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pyobjc-core" }, + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/a3/16ca9a15e77c061a9250afbae2eae26f2e1579eb8ca9462ae2d2c71e1169/pyobjc_framework_cocoa-12.1.tar.gz", hash = "sha256:5556c87db95711b985d5efdaaf01c917ddd41d148b1e52a0c66b1a2e2c5c1640", size = 2772191, upload-time = "2025-11-14T10:13:02.069Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/95/bf/ee4f27ec3920d5c6fc63c63e797c5b2cc4e20fe439217085d01ea5b63856/pyobjc_framework_cocoa-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:547c182837214b7ec4796dac5aee3aa25abc665757b75d7f44f83c994bcb0858", size = 384590, upload-time = "2025-11-14T09:41:17.336Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] -name = "pyobjc-framework-collaboration" -version = "12.1" +name = "ruamel-yaml" +version = "0.19.1" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/64/21/77fe64b39eae98412de1a0d33e9c735aa9949d53fff6b2d81403572b410b/pyobjc_framework_collaboration-12.1.tar.gz", hash = "sha256:2afa264d3233fc0a03a56789c6fefe655ffd81a2da4ba1dc79ea0c45931ad47b", size = 14299, upload-time = "2025-11-14T10:13:04.631Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/66/1507de01f1e2b309f8e11553a52769e4e2e9939ed770b5b560ef5bc27bc1/pyobjc_framework_collaboration-12.1-py2.py3-none-any.whl", hash = "sha256:182d6e6080833b97f9bef61738ae7bacb509714538f0d7281e5f0814c804b315", size = 4907, upload-time = "2025-11-14T09:42:55.781Z" }, -] - -[[package]] -name = "pyobjc-framework-colorsync" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c0/b4/706e4cc9db25b400201fc90f3edfaa1ab2d51b400b19437b043a68532078/pyobjc_framework_colorsync-12.1.tar.gz", hash = "sha256:d69dab7df01245a8c1bd536b9231c97993a5d1a2765d77692ce40ebbe6c1b8e9", size = 25269, upload-time = "2025-11-14T10:13:07.522Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/e1/82e45c712f43905ee1e6d585180764e8fa6b6f1377feb872f9f03c8c1fb8/pyobjc_framework_colorsync-12.1-py2.py3-none-any.whl", hash = "sha256:41e08d5b9a7af4b380c9adab24c7ff59dfd607b3073ae466693a3e791d8ffdc9", size = 6020, upload-time = "2025-11-14T09:42:57.504Z" }, -] - -[[package]] -name = "pyobjc-framework-compositorservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-metal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/54/c5/0ba31d7af7e464b7f7ece8c2bd09112bdb0b7260848402e79ba6aacc622c/pyobjc_framework_compositorservices-12.1.tar.gz", hash = "sha256:028e357bbee7fbd3723339a321bbe14e6da5a772708a661a13eea5f17c89e4ab", size = 23292, upload-time = "2025-11-14T10:13:10.392Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/34/5a2de8d531dbb88023898e0b5d2ce8edee14751af6c70e6103f6aa31a669/pyobjc_framework_compositorservices-12.1-py2.py3-none-any.whl", hash = "sha256:9ef22d4eacd492e13099b9b8936db892cdbbef1e3d23c3484e0ed749f83c4984", size = 5910, upload-time = "2025-11-14T09:42:59.154Z" }, -] - -[[package]] -name = "pyobjc-framework-contacts" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4b/a0/ce0542d211d4ea02f5cbcf72ee0a16b66b0d477a4ba5c32e00117703f2f0/pyobjc_framework_contacts-12.1.tar.gz", hash = "sha256:89bca3c5cf31404b714abaa1673577e1aaad6f2ef49d4141c6dbcc0643a789ad", size = 42378, upload-time = "2025-11-14T10:13:14.203Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/c8/2c4638c0d06447886a34070eebb9ba57407d4dd5f0fcb7ab642568272b88/pyobjc_framework_contacts-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2e5ce33b686eb9c0a39351938a756442ea8dea88f6ae2f16bff5494a8569c687", size = 12165, upload-time = "2025-11-14T09:43:05.119Z" }, -] - -[[package]] -name = "pyobjc-framework-contactsui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-contacts" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/0c/7bb7f898456a81d88d06a1084a42e374519d2e40a668a872b69b11f8c1f9/pyobjc_framework_contactsui-12.1.tar.gz", hash = "sha256:aaeca7c9e0c9c4e224d73636f9a558f9368c2c7422155a41fd4d7a13613a77c1", size = 18769, upload-time = "2025-11-14T10:13:16.301Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/ab/319aa52dfe6f836f4dc542282c2c13996222d4f5c9ea7ff8f391b12dac83/pyobjc_framework_contactsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:057f40d2f6eb1b169a300675ec75cc7a747cddcbcee8ece133e652a7086c5ab5", size = 7888, upload-time = "2025-11-14T09:43:18.502Z" }, -] - -[[package]] -name = "pyobjc-framework-coreaudio" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/84/d1/0b884c5564ab952ff5daa949128c64815300556019c1bba0cf2ca752a1a0/pyobjc_framework_coreaudio-12.1.tar.gz", hash = "sha256:a9e72925fcc1795430496ce0bffd4ddaa92c22460a10308a7283ade830089fe1", size = 75077, upload-time = "2025-11-14T10:13:22.345Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/48/05b5192122e23140cf583eac99ccc5bf615591d6ff76483ba986c38ee750/pyobjc_framework_coreaudio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a5ad6309779663f846ab36fe6c49647e470b7e08473c3e48b4f004017bdb68a4", size = 36908, upload-time = "2025-11-14T09:43:36.108Z" }, -] - -[[package]] -name = "pyobjc-framework-coreaudiokit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreaudio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/41/1c/5c7e39b9361d4eec99b9115b593edd9825388acd594cb3b4519f8f1ac12c/pyobjc_framework_coreaudiokit-12.1.tar.gz", hash = "sha256:b83624f8de3068ab2ca279f786be0804da5cf904ff9979d96007b69ef4869e1e", size = 20137, upload-time = "2025-11-14T10:13:24.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/19/d7/f171c04c6496afeaad2ab658b0c810682c8407127edc94d4b3f3b90c2bb1/pyobjc_framework_coreaudiokit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:97d5dd857e73d5b597cfc980972b021314b760e2f5bdde7bbba0334fbf404722", size = 7273, upload-time = "2025-11-14T09:43:55.411Z" }, -] - -[[package]] -name = "pyobjc-framework-corebluetooth" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4b/25/d21d6cb3fd249c2c2aa96ee54279f40876a0c93e7161b3304bf21cbd0bfe/pyobjc_framework_corebluetooth-12.1.tar.gz", hash = "sha256:8060c1466d90bbb9100741a1091bb79975d9ba43911c9841599879fc45c2bbe0", size = 33157, upload-time = "2025-11-14T10:13:28.064Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/56/01fef62a479cdd6ff9ee40b6e062a205408ff386ce5ba56d7e14a71fcf73/pyobjc_framework_corebluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe72c9732ee6c5c793b9543f08c1f5bdd98cd95dfc9d96efd5708ec9d6eeb213", size = 13209, upload-time = "2025-11-14T09:44:08.203Z" }, -] - -[[package]] -name = "pyobjc-framework-coredata" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/c5/8cd46cd4f1b7cf88bdeed3848f830ea9cdcc4e55cd0287a968a2838033fb/pyobjc_framework_coredata-12.1.tar.gz", hash = "sha256:1e47d3c5e51fdc87a90da62b97cae1bc49931a2bb064db1305827028e1fc0ffa", size = 124348, upload-time = "2025-11-14T10:13:36.435Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/29/fe24dc81e0f154805534923a56fe572c3b296092f086cf5a239fccc2d46a/pyobjc_framework_coredata-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3ee3581ca23ead0b152257e98622fe0bf7e7948f30a62a25a17cafe28fe015e", size = 16409, upload-time = "2025-11-14T09:44:23.582Z" }, -] - -[[package]] -name = "pyobjc-framework-corehaptics" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/2f/74a3da79d9188b05dd4be4428a819ea6992d4dfaedf7d629027cf1f57bfc/pyobjc_framework_corehaptics-12.1.tar.gz", hash = "sha256:521dd2986c8a4266d583dd9ed9ae42053b11ae7d3aa89bf53fbee88307d8db10", size = 22164, upload-time = "2025-11-14T10:13:38.941Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/25/f4/f469d6a9cac7c195f3d08fa65f94c32dd1dcf97a54b481be648fb3a7a5f3/pyobjc_framework_corehaptics-12.1-py2.py3-none-any.whl", hash = "sha256:a3b07d36ddf5c86a9cdaa411ab53d09553d26ea04fc7d4f82d21a84f0fc05fc0", size = 5382, upload-time = "2025-11-14T09:44:34.725Z" }, -] - -[[package]] -name = "pyobjc-framework-corelocation" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/79/b75885e0d75397dc2fe1ed9ca80be2b64c18b817f5fb924277cb1bf7b163/pyobjc_framework_corelocation-12.1.tar.gz", hash = "sha256:3674e9353f949d91dde6230ad68f6d5748a7f0424751e08a2c09d06050d66231", size = 53511, upload-time = "2025-11-14T10:13:43.384Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/71/57/1b670890fbf650f1a00afe5ee897ea3856a4a1417c2304c633ee2e978ed0/pyobjc_framework_corelocation-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8c35ad29a062fea7d417fd8997a9309660ba7963f2847c004e670efbe6bb5b00", size = 12721, upload-time = "2025-11-14T09:44:41.185Z" }, -] - -[[package]] -name = "pyobjc-framework-coremedia" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/da/7d/5ad600ff7aedfef8ba8f51b11d9aaacdf247b870bd14045d6e6f232e3df9/pyobjc_framework_coremedia-12.1.tar.gz", hash = "sha256:166c66a9c01e7a70103f3ca44c571431d124b9070612ef63a1511a4e6d9d84a7", size = 89566, upload-time = "2025-11-14T10:13:49.788Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/ae/f773cdc33c34a3f9ce6db829dbf72661b65c28ea9efaec8940364185b977/pyobjc_framework_coremedia-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:161a627f5c8cd30a5ebb935189f740e21e6cd94871a9afd463efdb5d51e255fa", size = 29396, upload-time = "2025-11-14T09:44:57.563Z" }, -] - -[[package]] -name = "pyobjc-framework-coremediaio" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/08/8e/23baee53ccd6c011c965cff62eb55638b4088c3df27d2bf05004105d6190/pyobjc_framework_coremediaio-12.1.tar.gz", hash = "sha256:880b313b28f00b27775d630174d09e0b53d1cdbadb74216618c9dd5b3eb6806a", size = 51100, upload-time = "2025-11-14T10:13:54.277Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/0c/9425c53c9a8c26e468e065ba12ef076bab20197ff7c82052a6dddd46d42b/pyobjc_framework_coremediaio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1108f8a278928fbca465f95123ea4a56456bd6571c1dc8b91793e6c61d624517", size = 17277, upload-time = "2025-11-14T09:45:17.457Z" }, -] - -[[package]] -name = "pyobjc-framework-coremidi" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/75/96/2d583060a71a73c8a7e6d92f2a02675621b63c1f489f2639e020fae34792/pyobjc_framework_coremidi-12.1.tar.gz", hash = "sha256:3c6f1fd03997c3b0f20ab8545126b1ce5f0cddcc1587dffacad876c161da8c54", size = 55587, upload-time = "2025-11-14T10:13:58.903Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/2d/99520f6f1685e4cad816e55cbf6d85f8ce6ea908107950e2d37dc17219d8/pyobjc_framework_coremidi-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e84ffc1de59691c04201b0872e184fe55b5589f3a14876bd14460f3b5f3cd109", size = 24317, upload-time = "2025-11-14T09:45:34.92Z" }, -] - -[[package]] -name = "pyobjc-framework-coreml" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/2d/baa9ea02cbb1c200683cb7273b69b4bee5070e86f2060b77e6a27c2a9d7e/pyobjc_framework_coreml-12.1.tar.gz", hash = "sha256:0d1a4216891a18775c9e0170d908714c18e4f53f9dc79fb0f5263b2aa81609ba", size = 40465, upload-time = "2025-11-14T10:14:02.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/39/4defef0deb25c5d7e3b7826d301e71ac5b54ef901b7dac4db1adc00f172d/pyobjc_framework_coreml-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:10dc8e8db53d7631ebc712cad146e3a9a9a443f4e1a037e844149a24c3c42669", size = 11356, upload-time = "2025-11-14T09:45:52.271Z" }, -] - -[[package]] -name = "pyobjc-framework-coremotion" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2c/eb/abef7d405670cf9c844befc2330a46ee59f6ff7bac6f199bf249561a2ca6/pyobjc_framework_coremotion-12.1.tar.gz", hash = "sha256:8e1b094d34084cc8cf07bedc0630b4ee7f32b0215011f79c9e3cd09d205a27c7", size = 33851, upload-time = "2025-11-14T10:14:05.619Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/75/89fa4aab818aeca21ac0a60b7ceb89a9e685df0ddd3828d36a6f84a0cff0/pyobjc_framework_coremotion-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a77908ab83c422030f913a2a761d196359ab47f6d1e7c76f21de2c6c05ea2f5f", size = 10406, upload-time = "2025-11-14T09:46:05.076Z" }, -] - -[[package]] -name = "pyobjc-framework-coreservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-fsevents" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4c/b3/52338a3ff41713f7d7bccaf63bef4ba4a8f2ce0c7eaff39a3629d022a79a/pyobjc_framework_coreservices-12.1.tar.gz", hash = "sha256:fc6a9f18fc6da64c166fe95f2defeb7ac8a9836b3b03bb6a891d36035260dbaa", size = 366150, upload-time = "2025-11-14T10:14:28.133Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/6c/33984caaf497fc5a6f86350d7ca4fac8abeb2bc33203edc96955a21e8c05/pyobjc_framework_coreservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8751dc2edcb7cfa248bf8a274c4d6493e8d53ef28a843827a4fc9a0a8b04b8be", size = 30206, upload-time = "2025-11-14T09:46:22.732Z" }, -] - -[[package]] -name = "pyobjc-framework-corespotlight" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/99/d0/88ca73b0cf23847af463334989dd8f98e44f801b811e7e1d8a5627ec20b4/pyobjc_framework_corespotlight-12.1.tar.gz", hash = "sha256:57add47380cd0bbb9793f50a4a4b435a90d4ebd2a33698e058cb353ddfb0d068", size = 38002, upload-time = "2025-11-14T10:14:31.948Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/3b/d3031eddff8029859de6d92b1f741625b1c233748889141a6a5a89b96f0e/pyobjc_framework_corespotlight-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bfcea64ab3250e2886d202b8731be3817b5ac0c8c9f43e77d0d5a0b6602e71a7", size = 9996, upload-time = "2025-11-14T09:46:47.157Z" }, -] - -[[package]] -name = "pyobjc-framework-coretext" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/29/da/682c9c92a39f713bd3c56e7375fa8f1b10ad558ecb075258ab6f1cdd4a6d/pyobjc_framework_coretext-12.1.tar.gz", hash = "sha256:e0adb717738fae395dc645c9e8a10bb5f6a4277e73cba8fa2a57f3b518e71da5", size = 90124, upload-time = "2025-11-14T10:14:38.596Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/0f/ddf45bf0e3ba4fbdc7772de4728fd97ffc34a0b5a15e1ab1115b202fe4ae/pyobjc_framework_coretext-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d246fa654bdbf43bae3969887d58f0b336c29b795ad55a54eb76397d0e62b93c", size = 30108, upload-time = "2025-11-14T09:47:04.228Z" }, -] - -[[package]] -name = "pyobjc-framework-corewlan" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/88/71/739a5d023566b506b3fd3d2412983faa95a8c16226c0dcd0f67a9294a342/pyobjc_framework_corewlan-12.1.tar.gz", hash = "sha256:a9d82ec71ef61f37e1d611caf51a4203f3dbd8caf827e98128a1afaa0fd2feb5", size = 32417, upload-time = "2025-11-14T10:14:41.921Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/31/3e9cf2c0ac3c979062958eae7a275b602515c9c76fd30680e1ee0fea82ae/pyobjc_framework_corewlan-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cba04c0550fc777767cd3a5471e4ed837406ab182d7d5c273bc5ce6ea237bfe", size = 9958, upload-time = "2025-11-14T09:47:22.474Z" }, -] - -[[package]] -name = "pyobjc-framework-cryptotokenkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6b/7c/d03ff4f74054578577296f33bc669fce16c7827eb1a553bb372b5aab30ca/pyobjc_framework_cryptotokenkit-12.1.tar.gz", hash = "sha256:c95116b4b7a41bf5b54aff823a4ef6f4d9da4d0441996d6d2c115026a42d82f5", size = 32716, upload-time = "2025-11-14T10:14:45.024Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/c7/aecba253cf21303b2c9f3ce03fc0e987523609d7839ea8e0a688ae816c96/pyobjc_framework_cryptotokenkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ef51a86c1d0125fabdfad0b3efa51098fb03660d8dad2787d82e8b71c9f189de", size = 12633, upload-time = "2025-11-14T09:47:35.707Z" }, -] - -[[package]] -name = "pyobjc-framework-datadetection" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/db/97/9b03832695ec4d3008e6150ddfdc581b0fda559d9709a98b62815581259a/pyobjc_framework_datadetection-12.1.tar.gz", hash = "sha256:95539e46d3bc970ce890aa4a97515db10b2690597c5dd362996794572e5d5de0", size = 12323, upload-time = "2025-11-14T10:14:46.769Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/1c/5d2f941501e84da8fef8ef3fd378b5c083f063f083f97dd3e8a07f0404b3/pyobjc_framework_datadetection-12.1-py2.py3-none-any.whl", hash = "sha256:4dc8e1d386d655b44b2681a4a2341fb2fc9addbf3dda14cb1553cd22be6a5387", size = 3497, upload-time = "2025-11-14T09:47:45.826Z" }, -] - -[[package]] -name = "pyobjc-framework-devicecheck" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cd/af/c676107c40d51f55d0a42043865d7246db821d01241b518ea1d3b3ef1394/pyobjc_framework_devicecheck-12.1.tar.gz", hash = "sha256:567e85fc1f567b3fe64ac1cdc323d989509331f64ee54fbcbde2001aec5adbdb", size = 12885, upload-time = "2025-11-14T10:14:48.804Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/d8/1f1b13fa4775b6474c9ad0f4b823953eaeb6c11bd6f03fa8479429b36577/pyobjc_framework_devicecheck-12.1-py2.py3-none-any.whl", hash = "sha256:ffd58148bdef4a1ee8548b243861b7d97a686e73808ca0efac5bef3c430e4a15", size = 3684, upload-time = "2025-11-14T09:47:47.25Z" }, -] - -[[package]] -name = "pyobjc-framework-devicediscoveryextension" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/91/b0/e6e2ed6a7f4b689746818000a003ff7ab9c10945df66398ae8d323ae9579/pyobjc_framework_devicediscoveryextension-12.1.tar.gz", hash = "sha256:60e12445fad97ff1f83472255c943685a8f3a9d95b3126d887cfe769b7261044", size = 14718, upload-time = "2025-11-14T10:14:50.723Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/0c/005fe8db1e19135f493a3de8c8d38031e1ad2d626de4ef89f282acf4aff7/pyobjc_framework_devicediscoveryextension-12.1-py2.py3-none-any.whl", hash = "sha256:d6d6b606d27d4d88efc0bed4727c375e749149b360290c3ad2afc52337739a1b", size = 4321, upload-time = "2025-11-14T09:47:48.78Z" }, -] - -[[package]] -name = "pyobjc-framework-dictionaryservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7a/c0/daf03cdaf6d4e04e0cf164db358378c07facd21e4e3f8622505d72573e2c/pyobjc_framework_dictionaryservices-12.1.tar.gz", hash = "sha256:354158f3c55d66681fa903c7b3cb05a435b717fa78d0cef44d258d61156454a7", size = 10573, upload-time = "2025-11-14T10:14:53.961Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/13/ab308e934146cfd54691ddad87e572cd1edb6659d795903c4c75904e2d7d/pyobjc_framework_dictionaryservices-12.1-py2.py3-none-any.whl", hash = "sha256:578854eec17fa473ac17ab30050a7bbb2ab69f17c5c49b673695254c3e88ad4b", size = 3930, upload-time = "2025-11-14T09:47:50.782Z" }, -] - -[[package]] -name = "pyobjc-framework-discrecording" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c4/87/8bd4544793bfcdf507174abddd02b1f077b48fab0004b3db9a63142ce7e9/pyobjc_framework_discrecording-12.1.tar.gz", hash = "sha256:6defc8ea97fb33b4d43870c673710c04c3dc48be30cdf78ba28191a922094990", size = 55607, upload-time = "2025-11-14T10:14:58.276Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/70/14a5aa348a5eba16e8773bb56698575cf114aa55aa303037b7000fc53959/pyobjc_framework_discrecording-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:865f1551e58459da6073360afc8f2cc452472c676ba83dcaa9b0c44e7775e4b5", size = 14566, upload-time = "2025-11-14T09:47:57.503Z" }, -] - -[[package]] -name = "pyobjc-framework-discrecordingui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-discrecording" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/30/63/8667f5bb1ecb556add04e86b278cb358dc1f2f03862705cae6f09097464c/pyobjc_framework_discrecordingui-12.1.tar.gz", hash = "sha256:6793d4a1a7f3219d063f39d87f1d4ebbbb3347e35d09194a193cfe16cba718a8", size = 16450, upload-time = "2025-11-14T10:15:00.254Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/4e/76016130c27b98943c5758a05beab3ba1bc9349ee881e1dfc509ea954233/pyobjc_framework_discrecordingui-12.1-py2.py3-none-any.whl", hash = "sha256:6544ef99cad3dee95716c83cb207088768b6ecd3de178f7e1b17df5997689dfd", size = 4702, upload-time = "2025-11-14T09:48:08.01Z" }, -] - -[[package]] -name = "pyobjc-framework-diskarbitration" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3a/42/f75fcabec1a0033e4c5235cc8225773f610321d565b63bf982c10c6bbee4/pyobjc_framework_diskarbitration-12.1.tar.gz", hash = "sha256:6703bc5a09b38a720c9ffca356b58f7e99fa76fc988c9ec4d87112344e63dfc2", size = 17121, upload-time = "2025-11-14T10:15:02.223Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/65/c1f54c47af17cb6b923eab85e95f22396c52f90ee8f5b387acffad9a99ea/pyobjc_framework_diskarbitration-12.1-py2.py3-none-any.whl", hash = "sha256:54caf3079fe4ae5ac14466a9b68923ee260a1a88a8290686b4a2015ba14c2db6", size = 4877, upload-time = "2025-11-14T09:48:09.945Z" }, -] - -[[package]] -name = "pyobjc-framework-dvdplayback" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/dd/7859a58e8dd336c77f83feb76d502e9623c394ea09322e29a03f5bc04d32/pyobjc_framework_dvdplayback-12.1.tar.gz", hash = "sha256:279345d4b5fb2c47dd8e5c2fd289e644b6648b74f5c25079805eeb61bfc4a9cd", size = 32332, upload-time = "2025-11-14T10:15:05.257Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/7d/22c07c28fab1f15f0d364806e39a6ca63c737c645fe7e98e157878b5998c/pyobjc_framework_dvdplayback-12.1-py2.py3-none-any.whl", hash = "sha256:af911cc222272a55b46a1a02a46a355279aecfd8132231d8d1b279e252b8ad4c", size = 8243, upload-time = "2025-11-14T09:48:11.824Z" }, -] - -[[package]] -name = "pyobjc-framework-eventkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b6/42/4ec97e641fdcf30896fe76476181622954cb017117b1429f634d24816711/pyobjc_framework_eventkit-12.1.tar.gz", hash = "sha256:7c1882be2f444b1d0f71e9a0cd1e9c04ad98e0261292ab741fc9de0b8bbbbae9", size = 28538, upload-time = "2025-11-14T10:15:07.878Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/35/142f43227627d6324993869d354b9e57eb1e88c4e229e2271592254daf25/pyobjc_framework_eventkit-12.1-py2.py3-none-any.whl", hash = "sha256:3d2d36d5bd9e0a13887a6ac7cdd36675985ebe2a9cb3cdf8cec0725670c92c60", size = 6820, upload-time = "2025-11-14T09:48:14.035Z" }, -] - -[[package]] -name = "pyobjc-framework-exceptionhandling" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/17/5c9d4164f7ccf6b9100be0ad597a7857395dd58ea492cba4f0e9c0b77049/pyobjc_framework_exceptionhandling-12.1.tar.gz", hash = "sha256:7f0719eeea6695197fce0e7042342daa462683dc466eb6a442aad897032ab00d", size = 16694, upload-time = "2025-11-14T10:15:10.173Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/ad/8e05acf3635f20ea7d878be30d58a484c8b901a8552c501feb7893472f86/pyobjc_framework_exceptionhandling-12.1-py2.py3-none-any.whl", hash = "sha256:2f1eae14cf0162e53a0888d9ffe63f047501fe583a23cdc9c966e89f48cf4713", size = 7113, upload-time = "2025-11-14T09:48:15.685Z" }, -] - -[[package]] -name = "pyobjc-framework-executionpolicy" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/11/db765e76e7b00e1521d7bb3a61ae49b59e7573ac108da174720e5d96b61b/pyobjc_framework_executionpolicy-12.1.tar.gz", hash = "sha256:682866589365cd01d3a724d8a2781794b5cba1e152411a58825ea52d7b972941", size = 12594, upload-time = "2025-11-14T10:15:12.077Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/2c/f10352398f10f244401ab8f53cabd127dc3f5dbbfc8de83464661d716671/pyobjc_framework_executionpolicy-12.1-py2.py3-none-any.whl", hash = "sha256:c3a9eca3bd143cf202787dd5e3f40d954c198f18a5e0b8b3e2fcdd317bf33a52", size = 3739, upload-time = "2025-11-14T09:48:17.35Z" }, -] - -[[package]] -name = "pyobjc-framework-extensionkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9a/d4/e9b1f74d29ad9dea3d60468d59b80e14ed3a19f9f7a25afcbc10d29c8a1e/pyobjc_framework_extensionkit-12.1.tar.gz", hash = "sha256:773987353e8aba04223dbba3149253db944abfb090c35318b3a770195b75da6d", size = 18694, upload-time = "2025-11-14T10:15:14.104Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/d9/8064dad6114a489e5439cc20d9fb0dd64cfc406d875b4a3c87015b3f6266/pyobjc_framework_extensionkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e01d705c7ac6d080ae34a81db6d9b81875eabefa63fd6eafbfa30f676dd780b", size = 7932, upload-time = "2025-11-14T09:48:23.653Z" }, -] - -[[package]] -name = "pyobjc-framework-externalaccessory" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8e/35/86c097ae2fdf912c61c1276e80f3e090a3fc898c75effdf51d86afec456b/pyobjc_framework_externalaccessory-12.1.tar.gz", hash = "sha256:079f770a115d517a6ab87db1b8a62ca6cdf6c35ae65f45eecc21b491e78776c0", size = 20958, upload-time = "2025-11-14T10:15:16.419Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/52/984034396089766b6e5ff3be0f93470e721c420fa9d1076398557532234f/pyobjc_framework_externalaccessory-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:dedbf7a09375ac19668156c1417bd7829565b164a246b714e225b9cbb6a351ad", size = 8932, upload-time = "2025-11-14T09:48:37.393Z" }, -] - -[[package]] -name = "pyobjc-framework-fileprovider" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/9a/724b1fae5709f8860f06a6a2a46de568f9bb8bdb2e2aae45b4e010368f51/pyobjc_framework_fileprovider-12.1.tar.gz", hash = "sha256:45034e0d00ae153c991aa01cb1fd41874650a30093e77ba73401dcce5534c8ad", size = 43071, upload-time = "2025-11-14T10:15:19.989Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/f5/56f0751a2988b2caca89d6800c8f29246828d1a7498bb676ef1ab28000b7/pyobjc_framework_fileprovider-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:89b140ea8369512ddf4164b007cbe35b4d97d1dcb8affa12a7264c0ab8d56e45", size = 21003, upload-time = "2025-11-14T09:48:53.128Z" }, -] - -[[package]] -name = "pyobjc-framework-fileproviderui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-fileprovider" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/50/00/234f9b93f75255845df81d9d5ea20cb83ecb5c0a4e59147168b622dd0b9d/pyobjc_framework_fileproviderui-12.1.tar.gz", hash = "sha256:15296429d9db0955abc3242b2920b7a810509a85118dbc185f3ac8234e5a6165", size = 12437, upload-time = "2025-11-14T10:15:22.044Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/65/cc4397511bd0af91993d6302a2aed205296a9ad626146eefdfc8a9624219/pyobjc_framework_fileproviderui-12.1-py2.py3-none-any.whl", hash = "sha256:521a914055089e28631018bd78df4c4f7416e98b4150f861d4a5bc97d5b1ffe4", size = 3715, upload-time = "2025-11-14T09:49:04.213Z" }, -] - -[[package]] -name = "pyobjc-framework-findersync" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/63/c8da472e0910238a905bc48620e005a1b8ae7921701408ca13e5fb0bfb4b/pyobjc_framework_findersync-12.1.tar.gz", hash = "sha256:c513104cef0013c233bf8655b527df665ce6f840c8bc0b3781e996933d4dcfa6", size = 13507, upload-time = "2025-11-14T10:15:24.161Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/9f/ec7f393e3e2fd11cbdf930d884a0ba81078bdb61920b3cba4f264de8b446/pyobjc_framework_findersync-12.1-py2.py3-none-any.whl", hash = "sha256:e07abeca52c486cf14927f617afc27afa7a3828b99fab3ad02355105fb29203e", size = 4889, upload-time = "2025-11-14T09:49:05.763Z" }, -] - -[[package]] -name = "pyobjc-framework-fsevents" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/43/17/21f45d2bca2efc72b975f2dfeae7a163dbeabb1236c1f188578403fd4f09/pyobjc_framework_fsevents-12.1.tar.gz", hash = "sha256:a22350e2aa789dec59b62da869c1b494a429f8c618854b1383d6473f4c065a02", size = 26487, upload-time = "2025-11-14T10:15:26.796Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2d/e3/2c5eeea390c0b053e2d73b223af3ec87a3e99a8106e8d3ee79942edb0822/pyobjc_framework_fsevents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2949358513fd7bc622fb362b5c4af4fc24fc6307320070ca410885e5e13d975", size = 13141, upload-time = "2025-11-14T09:49:11.947Z" }, -] - -[[package]] -name = "pyobjc-framework-fskit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a1/55/d00246d6e6d9756e129e1d94bc131c99eece2daa84b2696f6442b8a22177/pyobjc_framework_fskit-12.1.tar.gz", hash = "sha256:ec54e941cdb0b7d800616c06ca76a93685bd7119b8aa6eb4e7a3ee27658fc7ba", size = 42372, upload-time = "2025-11-14T10:15:30.411Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6d/a9/0c47469fe80fa14bc698bb0a5b772b44283cc3aca0f67e7f70ab45e09b24/pyobjc_framework_fskit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:50972897adea86508cfee33ec4c23aa91dede97e9da1640ea2fe74702b065be1", size = 20250, upload-time = "2025-11-14T09:49:28.065Z" }, -] - -[[package]] -name = "pyobjc-framework-gamecenter" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d2/f8/b5fd86f6b722d4259228922e125b50e0a6975120a1c4d957e990fb84e42c/pyobjc_framework_gamecenter-12.1.tar.gz", hash = "sha256:de4118f14c9cf93eb0316d49da410faded3609ce9cd63425e9ef878cebb7ea72", size = 31473, upload-time = "2025-11-14T10:15:33.38Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/ee/b496cc4248c5b901e159d6d9a437da9b86a3105fc3999a66744ba2b2c884/pyobjc_framework_gamecenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e8d6d10b868be7c00c2d5a0944cc79315945735dcf17eaa3fec1a7986d26be9b", size = 18868, upload-time = "2025-11-14T09:49:44.767Z" }, -] - -[[package]] -name = "pyobjc-framework-gamecontroller" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/21/14/353bb1fe448cd833839fd199ab26426c0248088753e63c22fe19dc07530f/pyobjc_framework_gamecontroller-12.1.tar.gz", hash = "sha256:64ed3cc4844b67f1faeb540c7cc8d512c84f70b3a4bafdb33d4663a2b2a2b1d8", size = 54554, upload-time = "2025-11-14T10:15:37.591Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/06/28/9f03d0ef7c78340441f78b19fb2d2c952af04a240da5ed30c7cf2d0d0f4e/pyobjc_framework_gamecontroller-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:878aa6590c1510e91bfc8710d6c880e7a8f3656a7b7b6f4f3af487a6f677ccd5", size = 20949, upload-time = "2025-11-14T09:50:01.608Z" }, -] - -[[package]] -name = "pyobjc-framework-gamekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/52/7b/d625c0937557f7e2e64200fdbeb867d2f6f86b2f148b8d6bfe085e32d872/pyobjc_framework_gamekit-12.1.tar.gz", hash = "sha256:014d032c3484093f1409f8f631ba8a0fd2ff7a3ae23fd9d14235340889854c16", size = 63833, upload-time = "2025-11-14T10:15:42.842Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/05/1c49e1030dc9f2812fa8049442158be76c32f271075f4571f94e4389ea86/pyobjc_framework_gamekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2eee796d5781157f2c5684f7ef4c2a7ace9d9b408a26a9e7e92e8adf5a3f63d7", size = 22493, upload-time = "2025-11-14T09:50:19.129Z" }, -] - -[[package]] -name = "pyobjc-framework-gameplaykit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-spritekit" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e2/11/c310bbc2526f95cce662cc1f1359bb11e2458eab0689737b4850d0f6acb7/pyobjc_framework_gameplaykit-12.1.tar.gz", hash = "sha256:935ebd806d802888969357946245d35a304c530c86f1ffe584e2cf21f0a608a8", size = 41511, upload-time = "2025-11-14T10:15:46.529Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/35/1f/e5fe404f92ec0f9c8c37b00d6cb3ba96ee396c7f91b0a41a39b64bfc2743/pyobjc_framework_gameplaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:309b0d7479f702830c9be92dbe5855ac2557a9d23f05f063caf9d9fdb85ff5f0", size = 13150, upload-time = "2025-11-14T09:50:36.884Z" }, -] - -[[package]] -name = "pyobjc-framework-gamesave" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/1b/1f/8d05585c844535e75dbc242dd6bdfecfc613d074dcb700362d1c908fb403/pyobjc_framework_gamesave-12.1.tar.gz", hash = "sha256:eb731c97aa644e78a87838ed56d0e5bdbaae125bdc8854a7772394877312cc2e", size = 12654, upload-time = "2025-11-14T10:15:48.344Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/ec/93d48cb048a1b35cea559cc9261b07f0d410078b3af029121302faa410d0/pyobjc_framework_gamesave-12.1-py2.py3-none-any.whl", hash = "sha256:432e69f8404be9290d42c89caba241a3156ed52013947978ac54f0f032a14ffd", size = 3689, upload-time = "2025-11-14T09:50:47.263Z" }, -] - -[[package]] -name = "pyobjc-framework-healthkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/af/67/436630d00ba1028ea33cc9df2fc28e081481433e5075600f2ea1ff00f45e/pyobjc_framework_healthkit-12.1.tar.gz", hash = "sha256:29c5e5de54b41080b7a4b0207698ac6f600dcb9149becc9c6b3a69957e200e5c", size = 91802, upload-time = "2025-11-14T10:15:54.661Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/65/87/bb1c438de51c4fa733a99ce4d3301e585f14d7efd94031a97707c0be2b46/pyobjc_framework_healthkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:15b6fc958ff5de42888b18dffdec839cb36d2dd8b82076ed2f21a51db5271109", size = 20799, upload-time = "2025-11-14T09:50:54.531Z" }, -] - -[[package]] -name = "pyobjc-framework-imagecapturecore" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/a1/39347381fc7d3cd5ab942d86af347b25c73f0ddf6f5227d8b4d8f5328016/pyobjc_framework_imagecapturecore-12.1.tar.gz", hash = "sha256:c4776c59f4db57727389d17e1ffd9c567b854b8db52198b3ccc11281711074e5", size = 46397, upload-time = "2025-11-14T10:15:58.541Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/50/13/632957b284dec3743d73fb30dbdf03793b3cf1b4c62e61e6484d870f3879/pyobjc_framework_imagecapturecore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a2777e17ff71fb5a327a897e48c5c7b5a561723a80f990d26e6ed5a1b8748816", size = 16012, upload-time = "2025-11-14T09:51:12.058Z" }, -] - -[[package]] -name = "pyobjc-framework-inputmethodkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5d/b8/d33dd8b7306029bbbd80525bf833fc547e6a223c494bf69a534487283a28/pyobjc_framework_inputmethodkit-12.1.tar.gz", hash = "sha256:f63b6fe2fa7f1412eae63baea1e120e7865e3b68ccfb7d8b0a4aadb309f2b278", size = 23054, upload-time = "2025-11-14T10:16:01.464Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/01/c2/59bea66405784b25f5d4e821467ba534a0b92dfc98e07257c971e2a8ed73/pyobjc_framework_inputmethodkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0b7d813d46a060572fc0c14ef832e4fe538ebf64e5cab80ee955191792ce0110", size = 9506, upload-time = "2025-11-14T09:51:26.924Z" }, -] - -[[package]] -name = "pyobjc-framework-installerplugins" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/60/ca4ab04eafa388a97a521db7d60a812e2f81a3c21c2372587872e6b074f9/pyobjc_framework_installerplugins-12.1.tar.gz", hash = "sha256:1329a193bd2e92a2320a981a9a421a9b99749bade3e5914358923e94fe995795", size = 25277, upload-time = "2025-11-14T10:16:04.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/1f/31dca45db3342882a628aa1b27707a283d4dc7ef558fddd2533175a0661a/pyobjc_framework_installerplugins-12.1-py2.py3-none-any.whl", hash = "sha256:d2201c81b05bdbe0abf0af25db58dc230802573463bea322f8b2863e37b511d5", size = 4813, upload-time = "2025-11-14T09:51:37.836Z" }, -] - -[[package]] -name = "pyobjc-framework-instantmessage" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d4/67/66754e0d26320ba24a33608ca94d3f38e60ee6b2d2e094cb6269b346fdd4/pyobjc_framework_instantmessage-12.1.tar.gz", hash = "sha256:f453118d5693dc3c94554791bd2aaafe32a8b03b0e3d8ec3934b44b7fdd1f7e7", size = 31217, upload-time = "2025-11-14T10:16:07.693Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/38/6ae95b5c87d887c075bd5f4f7cca3d21dafd0a77cfdde870e87ca17579eb/pyobjc_framework_instantmessage-12.1-py2.py3-none-any.whl", hash = "sha256:cd91d38e8f356afd726b6ea8c235699316ea90edfd3472965c251efbf4150bc9", size = 5436, upload-time = "2025-11-14T09:51:39.557Z" }, -] - -[[package]] -name = "pyobjc-framework-intents" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/a1/3bab6139e94b97eca098e1562f5d6840e3ff10ea1f7fd704a17111a97d5b/pyobjc_framework_intents-12.1.tar.gz", hash = "sha256:bd688c3ab34a18412f56e459e9dae29e1f4152d3c2048fcacdef5fc49dfb9765", size = 132262, upload-time = "2025-11-14T10:16:16.428Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/90/e9489492ae90b4c1ffd02c1221c0432b8768d475787e7887f79032c2487a/pyobjc_framework_intents-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ea9f3e79bf4baf6c7b0fd2d2797184ed51a372bf7f32974b4424f9bd067ef50", size = 32156, upload-time = "2025-11-14T09:51:49.438Z" }, -] - -[[package]] -name = "pyobjc-framework-intentsui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-intents" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/19/cf/f0e385b9cfbf153d68efe8d19e5ae672b59acbbfc1f9b58faaefc5ec8c9e/pyobjc_framework_intentsui-12.1.tar.gz", hash = "sha256:16bdf4b7b91c0d1ec9d5513a1182861f1b5b7af95d4f4218ff7cf03032d57f99", size = 19784, upload-time = "2025-11-14T10:16:18.716Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/17/06812542a9028f5b2dcce56f52f25633c08b638faacd43bad862aad1b41d/pyobjc_framework_intentsui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cb894fcc4c9ea613a424dcf6fb48142d51174559b82cfdafac8cb47555c842cf", size = 8983, upload-time = "2025-11-14T09:52:07.667Z" }, -] - -[[package]] -name = "pyobjc-framework-iobluetooth" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e4/aa/ca3944bbdfead4201b4ae6b51510942c5a7d8e5e2dc3139a071c74061fdf/pyobjc_framework_iobluetooth-12.1.tar.gz", hash = "sha256:8a434118812f4c01dfc64339d41fe8229516864a59d2803e9094ee4cbe2b7edd", size = 155241, upload-time = "2025-11-14T10:16:28.896Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/b6/933b56afb5e84c3c35c074c9e30d7b701c6038989d4867867bdaa7ab618b/pyobjc_framework_iobluetooth-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:111a6e54be9e9dcf77fa2bf84fdac09fae339aa33087d8647ea7ffbd34765d4c", size = 40439, upload-time = "2025-11-14T09:52:26.071Z" }, -] - -[[package]] -name = "pyobjc-framework-iobluetoothui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-iobluetooth" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8f/39/31d9a4e8565a4b1ec0a9ad81480dc0879f3df28799eae3bc22d1dd53705d/pyobjc_framework_iobluetoothui-12.1.tar.gz", hash = "sha256:81f8158bdfb2966a574b6988eb346114d6a4c277300c8c0a978c272018184e6f", size = 16495, upload-time = "2025-11-14T10:16:31.212Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/c9/69aeda0cdb5d25d30dc4596a1c5b464fc81b5c0c4e28efc54b7e11bde51c/pyobjc_framework_iobluetoothui-12.1-py2.py3-none-any.whl", hash = "sha256:a6d8ab98efa3029130577a57ee96b183c35c39b0f1c53a7534f8838260fab993", size = 4045, upload-time = "2025-11-14T09:52:42.201Z" }, -] - -[[package]] -name = "pyobjc-framework-iosurface" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/07/61/0f12ad67a72d434e1c84b229ec760b5be71f53671ee9018593961c8bfeb7/pyobjc_framework_iosurface-12.1.tar.gz", hash = "sha256:4b9d0c66431aa296f3ca7c4f84c00dc5fc961194830ad7682fdbbc358fa0db55", size = 17690, upload-time = "2025-11-14T10:16:33.282Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ad/793d98a7ed9b775dc8cce54144cdab0df1808a1960ee017e46189291a8f3/pyobjc_framework_iosurface-12.1-py2.py3-none-any.whl", hash = "sha256:e784e248397cfebef4655d2c0025766d3eaa4a70474e363d084fc5ce2a4f2a3f", size = 4902, upload-time = "2025-11-14T09:52:43.899Z" }, -] - -[[package]] -name = "pyobjc-framework-ituneslibrary" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f5/46/d9bcec88675bf4ee887b9707bd245e2a793e7cb916cf310f286741d54b1f/pyobjc_framework_ituneslibrary-12.1.tar.gz", hash = "sha256:7f3aa76c4d05f6fa6015056b88986cacbda107c3f29520dd35ef0936c7367a6e", size = 23730, upload-time = "2025-11-14T10:16:36.127Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/de/92/b598694a1713ee46f45c4bfb1a0425082253cbd2b1caf9f8fd50f292b017/pyobjc_framework_ituneslibrary-12.1-py2.py3-none-any.whl", hash = "sha256:fb678d7c3ff14c81672e09c015e25880dac278aa819971f4d5f75d46465932ef", size = 5205, upload-time = "2025-11-14T09:52:45.733Z" }, -] - -[[package]] -name = "pyobjc-framework-kernelmanagement" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0a/7e/ecbac119866e8ac2cce700d7a48a4297946412ac7cbc243a7084a6582fb1/pyobjc_framework_kernelmanagement-12.1.tar.gz", hash = "sha256:488062893ac2074e0c8178667bf864a21f7909c11111de2f6a10d9bc579df59d", size = 11773, upload-time = "2025-11-14T10:16:38.216Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/32/04325a20f39d88d6d712437e536961a9e6a4ec19f204f241de6ed54d1d84/pyobjc_framework_kernelmanagement-12.1-py2.py3-none-any.whl", hash = "sha256:926381bfbfbc985c3e6dfcb7004af21bb16ff66ecbc08912b925989a705944ff", size = 3704, upload-time = "2025-11-14T09:52:47.268Z" }, -] - -[[package]] -name = "pyobjc-framework-latentsemanticmapping" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/88/3c/b621dac54ae8e77ac25ee75dd93e310e2d6e0faaf15b8da13513258d6657/pyobjc_framework_latentsemanticmapping-12.1.tar.gz", hash = "sha256:f0b1fa823313eefecbf1539b4ed4b32461534b7a35826c2cd9f6024411dc9284", size = 15526, upload-time = "2025-11-14T10:16:40.149Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/8e/74a7eb29b545f294485cd3cf70557b4a35616555fe63021edbb3e0ea4c20/pyobjc_framework_latentsemanticmapping-12.1-py2.py3-none-any.whl", hash = "sha256:7d760213b42bc8b1bc1472e1873c0f78ee80f987225978837b1fecdceddbdbf4", size = 5471, upload-time = "2025-11-14T09:52:48.939Z" }, -] - -[[package]] -name = "pyobjc-framework-launchservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/37/d0/24673625922b0ad21546be5cf49e5ec1afaa4553ae92f222adacdc915907/pyobjc_framework_launchservices-12.1.tar.gz", hash = "sha256:4d2d34c9bd6fb7f77566155b539a2c70283d1f0326e1695da234a93ef48352dc", size = 20470, upload-time = "2025-11-14T10:16:42.499Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/af/9a0aebaab4c15632dc8fcb3669c68fa541a3278d99541d9c5f966fbc0909/pyobjc_framework_launchservices-12.1-py2.py3-none-any.whl", hash = "sha256:e63e78fceeed4d4dc807f9dabd5cf90407e4f552fab6a0d75a8d0af63094ad3c", size = 3905, upload-time = "2025-11-14T09:52:50.71Z" }, -] - -[[package]] -name = "pyobjc-framework-libdispatch" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/26/e8/75b6b9b3c88b37723c237e5a7600384ea2d84874548671139db02e76652b/pyobjc_framework_libdispatch-12.1.tar.gz", hash = "sha256:4035535b4fae1b5e976f3e0e38b6e3442ffea1b8aa178d0ca89faa9b8ecdea41", size = 38277, upload-time = "2025-11-14T10:16:46.235Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/83/6f/96e15c7b2f7b51fc53252216cd0bed0c3541bc0f0aeb32756fefd31bed7d/pyobjc_framework_libdispatch-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e9570d7a9a3136f54b0b834683bf3f206acd5df0e421c30f8fd4f8b9b556789", size = 15650, upload-time = "2025-11-14T09:52:59.284Z" }, -] - -[[package]] -name = "pyobjc-framework-libxpc" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/16/e4/364db7dc26f235e3d7eaab2f92057f460b39800bffdec3128f113388ac9f/pyobjc_framework_libxpc-12.1.tar.gz", hash = "sha256:e46363a735f3ecc9a2f91637750623f90ee74f9938a4e7c833e01233174af44d", size = 35186, upload-time = "2025-11-14T10:16:49.503Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/82/7f/fdec72430f90921b154517a6f9bbeefa7bacfb16b91320742eb16a5955c5/pyobjc_framework_libxpc-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ba93e91e9ca79603dd265382e9f80e9bd32309cd09c8ac3e6489fc5b233676c8", size = 19730, upload-time = "2025-11-14T09:53:17.113Z" }, -] - -[[package]] -name = "pyobjc-framework-linkpresentation" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e3/58/c0c5919d883485ccdb6dccd8ecfe50271d2f6e6ab7c9b624789235ccec5a/pyobjc_framework_linkpresentation-12.1.tar.gz", hash = "sha256:84df6779591bb93217aa8bd82c10e16643441678547d2d73ba895475a02ade94", size = 13330, upload-time = "2025-11-14T10:16:52.169Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/51/226eb45f196f3bf93374713571aae6c8a4760389e1d9435c4a4cc3f38ea4/pyobjc_framework_linkpresentation-12.1-py2.py3-none-any.whl", hash = "sha256:853a84c7b525b77b114a7a8d798aef83f528ed3a6803bda12184fe5af4e79a47", size = 3865, upload-time = "2025-11-14T09:53:28.386Z" }, -] - -[[package]] -name = "pyobjc-framework-localauthentication" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8d/0e/7e5d9a58bb3d5b79a75d925557ef68084171526191b1c0929a887a553d4f/pyobjc_framework_localauthentication-12.1.tar.gz", hash = "sha256:2284f587d8e1206166e4495b33f420c1de486c36c28c4921d09eec858a699d05", size = 29947, upload-time = "2025-11-14T10:16:54.923Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/93/91761ad4e5fa1c3ec25819865d1ccfbee033987147087bff4fcce67a4dc4/pyobjc_framework_localauthentication-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3af1acd287d830cc7f912f46cde0dab054952bde0adaf66c8e8524311a68d279", size = 10773, upload-time = "2025-11-14T09:53:34.074Z" }, -] - -[[package]] -name = "pyobjc-framework-localauthenticationembeddedui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-localauthentication" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/20/83ab4180e29b9a4a44d735c7f88909296c6adbe6250e8e00a156aff753e1/pyobjc_framework_localauthenticationembeddedui-12.1.tar.gz", hash = "sha256:a15ec44bf2769c872e86c6b550b6dd4f58d4eda40ad9ff00272a67d279d1d4e9", size = 13611, upload-time = "2025-11-14T10:16:57.145Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/7d/0d46639c7a26b6af928ab4c822cd28b733791e02ac28cc84c3014bcf7dc7/pyobjc_framework_localauthenticationembeddedui-12.1-py2.py3-none-any.whl", hash = "sha256:a7ce7b56346597b9f4768be61938cbc8fc5b1292137225b6c7f631b9cde97cd7", size = 3991, upload-time = "2025-11-14T09:53:42.958Z" }, -] - -[[package]] -name = "pyobjc-framework-mailkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/98/3d9028620c1cd32ff4fb031155aba3b5511e980cdd114dd51383be9cb51b/pyobjc_framework_mailkit-12.1.tar.gz", hash = "sha256:d5574b7259baec17096410efcaacf5d45c7bb5f893d4c25cbb7072369799b652", size = 20996, upload-time = "2025-11-14T10:16:59.449Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/8d/3c968b736a3a8bd9d8e870b39b1c772a013eea1b81b89fc4efad9021a6cb/pyobjc_framework_mailkit-12.1-py2.py3-none-any.whl", hash = "sha256:536ac0c4ea3560364cd159a6512c3c18a744a12e4e0883c07df0f8a2ff21e3fe", size = 4871, upload-time = "2025-11-14T09:53:44.697Z" }, -] - -[[package]] -name = "pyobjc-framework-mapkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-corelocation" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/36/bb/2a668203c20e509a648c35e803d79d0c7f7816dacba74eb5ad8acb186790/pyobjc_framework_mapkit-12.1.tar.gz", hash = "sha256:dbc32dc48e821aaa9b4294402c240adbc1c6834e658a07677b7c19b7990533c5", size = 63520, upload-time = "2025-11-14T10:17:04.221Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/11/00/a3de41cdf3e6cd7a144e38999fe1ea9777ad19e19d863f2da862e7affe7b/pyobjc_framework_mapkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:84ad7766271c114bdc423e4e2ff5433e5fc6771a3338b5f8e4b54d0340775800", size = 22518, upload-time = "2025-11-14T09:53:52.727Z" }, -] - -[[package]] -name = "pyobjc-framework-mediaaccessibility" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e0/10/dc1007e56944ed2e981e69e7b2fed2b2202c79b0d5b742b29b1081d1cbdd/pyobjc_framework_mediaaccessibility-12.1.tar.gz", hash = "sha256:cc4e3b1d45e84133d240318d53424eff55968f5c6873c2c53267598853445a3f", size = 16325, upload-time = "2025-11-14T10:17:07.454Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/0c/7fb5462561f59d739192c6d02ba0fd36ad7841efac5a8398a85a030ef7fc/pyobjc_framework_mediaaccessibility-12.1-py2.py3-none-any.whl", hash = "sha256:2ff8845c97dd52b0e5cf53990291e6d77c8a73a7aac0e9235d62d9a4256916d1", size = 4800, upload-time = "2025-11-14T09:54:05.04Z" }, -] - -[[package]] -name = "pyobjc-framework-mediaextension" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d6/aa/1e8015711df1cdb5e4a0aa0ed4721409d39971ae6e1e71915e3ab72423a3/pyobjc_framework_mediaextension-12.1.tar.gz", hash = "sha256:44409d63cc7d74e5724a68e3f9252cb62fd0fd3ccf0ca94c6a33e5c990149953", size = 39425, upload-time = "2025-11-14T10:17:11.486Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/ed/99038bcf72ec68e452709af10a087c1377c2d595ba4e66d7a2b0775145d2/pyobjc_framework_mediaextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:442bc3a759efb5c154cb75d643a5e182297093533fcdd1c24be6f64f68b93371", size = 38973, upload-time = "2025-11-14T09:54:16.701Z" }, -] - -[[package]] -name = "pyobjc-framework-medialibrary" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/34/e9/848ebd02456f8fdb41b42298ec585bfed5899dbd30306ea5b0a7e4c4b341/pyobjc_framework_medialibrary-12.1.tar.gz", hash = "sha256:690dcca09b62511df18f58e8566cb33d9652aae09fe63a83f594bd018b5edfcd", size = 15995, upload-time = "2025-11-14T10:17:15.45Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/cd/eeaf8585a343fda5b8cf3b8f144c872d1057c845202098b9441a39b76cb0/pyobjc_framework_medialibrary-12.1-py2.py3-none-any.whl", hash = "sha256:1f03ad6802a5c6e19ee3208b065689d3ec79defe1052cb80e00f54e1eff5f2a0", size = 4361, upload-time = "2025-11-14T09:54:32.259Z" }, -] - -[[package]] -name = "pyobjc-framework-mediaplayer" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e9/f0/851f6f47e11acbd62d5f5dcb8274afc969135e30018591f75bf3cbf6417f/pyobjc_framework_mediaplayer-12.1.tar.gz", hash = "sha256:5ef3f669bdf837d87cdb5a486ec34831542360d14bcba099c7c2e0383380794c", size = 35402, upload-time = "2025-11-14T10:17:18.97Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/c0/038ee3efd286c0fbc89c1e0cb688f4670ed0e5803aa36e739e79ffc91331/pyobjc_framework_mediaplayer-12.1-py2.py3-none-any.whl", hash = "sha256:85d9baec131807bfdf0f4c24d4b943e83cce806ab31c95c7e19c78e3fb7eefc8", size = 7120, upload-time = "2025-11-14T09:54:33.901Z" }, -] - -[[package]] -name = "pyobjc-framework-mediatoolbox" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a3/71/be5879380a161f98212a336b432256f307d1dcbaaaeb8ec988aea2ada2cd/pyobjc_framework_mediatoolbox-12.1.tar.gz", hash = "sha256:385b48746a5f08756ee87afc14037e552954c427ed5745d7ece31a21a7bad5ab", size = 22305, upload-time = "2025-11-14T10:17:22.501Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/94/d5ee221f2afbc64b2a7074efe25387cd8700e8116518904b28091ea6ad74/pyobjc_framework_mediatoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d7bcfeeff3fbf7e9e556ecafd8eaed2411df15c52baf134efa7480494e6faf6d", size = 12818, upload-time = "2025-11-14T09:54:41.251Z" }, -] - -[[package]] -name = "pyobjc-framework-metal" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/06/a84f7eb8561d5631954b9458cfca04b690b80b5b85ce70642bc89335f52a/pyobjc_framework_metal-12.1.tar.gz", hash = "sha256:bb554877d5ee2bf3f340ad88e8fe1b85baab7b5ec4bd6ae0f4f7604147e3eae7", size = 181847, upload-time = "2025-11-14T10:17:34.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/48/9286d06e1b14c11b65d3fea1555edc0061d9ebe11898dff8a14089e3a4c9/pyobjc_framework_metal-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38ab566b5a2979a43e13593d3eb12000a45e574576fe76996a5e1eb75ad7ac78", size = 75841, upload-time = "2025-11-14T09:55:06.801Z" }, -] - -[[package]] -name = "pyobjc-framework-metalfx" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f1/09/ce5c74565677fde66de3b9d35389066b19e5d1bfef9d9a4ad80f0c858c0c/pyobjc_framework_metalfx-12.1.tar.gz", hash = "sha256:1551b686fb80083a97879ce0331bdb1d4c9b94557570b7ecc35ebf40ff65c90b", size = 29470, upload-time = "2025-11-14T10:17:37.16Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/0b/508e3af499694f4eec74cc3ab0530e38db76e43a27db9ecb98c50c68f5f9/pyobjc_framework_metalfx-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a4418ae5c2eb77ec00695fa720a547638dc252dfd77ecb6feb88f713f5a948fd", size = 15062, upload-time = "2025-11-14T09:55:37.352Z" }, -] - -[[package]] -name = "pyobjc-framework-metalkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-metal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/14/15/5091147aae12d4011a788b93971c3376aaaf9bf32aa935a2c9a06a71e18b/pyobjc_framework_metalkit-12.1.tar.gz", hash = "sha256:14cc5c256f0e3471b412a5b3582cb2a0d36d3d57401a8aa09e433252d1c34824", size = 25473, upload-time = "2025-11-14T10:17:39.721Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/c0/c8b5b060895cd51493afe3f09909b7e34893b1161cf4d93bc8e3cd306129/pyobjc_framework_metalkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c4869076571d94788fe539fabfdd568a5c8e340936c7726d2551196640bd152", size = 8755, upload-time = "2025-11-14T09:55:51.683Z" }, -] - -[[package]] -name = "pyobjc-framework-metalperformanceshaders" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metal" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c5/68/58da38e54aa0d8c19f0d3084d8c84e92d54cc8c9254041f07119d86aa073/pyobjc_framework_metalperformanceshaders-12.1.tar.gz", hash = "sha256:b198e755b95a1de1525e63c3b14327ae93ef1d88359e6be1ce554a3493755b50", size = 137301, upload-time = "2025-11-14T10:17:49.554Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/84/d505496fca9341e0cb11258ace7640cd986fe3e831f8b4749035e9f82109/pyobjc_framework_metalperformanceshaders-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c00e786c352b3ff5d86cf0cf3a830dc9f6fc32a03ae1a7539d20d11324adb2e8", size = 33242, upload-time = "2025-11-14T09:56:09.354Z" }, -] - -[[package]] -name = "pyobjc-framework-metalperformanceshadersgraph" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-metalperformanceshaders" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a7/56/7ad0cd085532f7bdea9a8d4e9a2dfde376d26dd21e5eabdf1a366040eff8/pyobjc_framework_metalperformanceshadersgraph-12.1.tar.gz", hash = "sha256:b8fd017b47698037d7b172d41bed7a4835f4c4f2a288235819d200005f89ee35", size = 42992, upload-time = "2025-11-14T10:17:53.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/c9/5e7fd0d4bc9bdf7b442f36e020677c721ba9b4c1dc1fa3180085f22a4ef9/pyobjc_framework_metalperformanceshadersgraph-12.1-py2.py3-none-any.whl", hash = "sha256:85a1c7a6114ada05c7924b3235a1a98c45359410d148097488f15aee5ebb6ab9", size = 6481, upload-time = "2025-11-14T09:56:23.66Z" }, -] - -[[package]] -name = "pyobjc-framework-metrickit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ba/13/5576ddfbc0b174810a49171e2dbe610bdafd3b701011c6ecd9b3a461de8a/pyobjc_framework_metrickit-12.1.tar.gz", hash = "sha256:77841daf6b36ba0c19df88545fd910c0516acf279e6b7b4fa0a712a046eaa9f1", size = 27627, upload-time = "2025-11-14T10:17:56.353Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/04/8da5126e47306438c99750f1dfed430d7cc388f6b7f420ae748f3060ab96/pyobjc_framework_metrickit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3ec96e9ec7dc37fbce57dae277f0d89c66ffe1c3fa2feaca1b7125f8b2b29d87", size = 8120, upload-time = "2025-11-14T09:56:28.73Z" }, -] - -[[package]] -name = "pyobjc-framework-mlcompute" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/69/15f8ce96c14383aa783c8e4bc1e6d936a489343bb197b8e71abb3ddc1cb8/pyobjc_framework_mlcompute-12.1.tar.gz", hash = "sha256:3281db120273dcc56e97becffd5cedf9c62042788289f7be6ea067a863164f1e", size = 40698, upload-time = "2025-11-14T10:17:59.792Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/f7/4614b9ccd0151795e328b9ed881fbcbb13e577a8ec4ae3507edb1a462731/pyobjc_framework_mlcompute-12.1-py2.py3-none-any.whl", hash = "sha256:4f0fc19551d710a03dfc4c7129299897544ff8ea76db6c7539ecc2f9b2571bde", size = 6744, upload-time = "2025-11-14T09:56:36.973Z" }, -] - -[[package]] -name = "pyobjc-framework-modelio" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b4/11/32c358111b623b4a0af9e90470b198fffc068b45acac74e1ba711aee7199/pyobjc_framework_modelio-12.1.tar.gz", hash = "sha256:d041d7bca7c2a4526344d3e593347225b7a2e51a499b3aa548895ba516d1bdbb", size = 66482, upload-time = "2025-11-14T10:18:04.92Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/0e/b8331100f0d658ecb3e87e75c108e2ae8ac7c78b521fd5ad0205b60a2584/pyobjc_framework_modelio-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:68d971917c289fdddf69094c74915d2ccb746b42b150e0bdc16d8161e6164022", size = 20193, upload-time = "2025-11-14T09:56:44.296Z" }, -] - -[[package]] -name = "pyobjc-framework-multipeerconnectivity" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/87/35/0d0bb6881004cb238cfd7bf74f4b2e42601a1accdf27b2189ec61cf3a2dc/pyobjc_framework_multipeerconnectivity-12.1.tar.gz", hash = "sha256:7123f734b7174cacbe92a51a62b4645cc9033f6b462ff945b504b62e1b9e6c1c", size = 22816, upload-time = "2025-11-14T10:18:07.363Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/33/8d/0646ff7db36942829f0e84be18ba44bc5cd96d6a81651f8e7dc0974821c1/pyobjc_framework_multipeerconnectivity-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1c3bd254a16debed321debf4858f9c9b7d41572ddf1058a4bacf6a5bcfedeeff", size = 12001, upload-time = "2025-11-14T09:57:01.027Z" }, -] - -[[package]] -name = "pyobjc-framework-naturallanguage" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8a/d1/c81c0cdbb198d498edc9bc5fbb17e79b796450c17bb7541adbf502f9ad65/pyobjc_framework_naturallanguage-12.1.tar.gz", hash = "sha256:cb27a1e1e5b2913d308c49fcd2fd04ab5ea87cb60cac4a576a91ebf6a50e52f6", size = 23524, upload-time = "2025-11-14T10:18:09.883Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/d8/715a11111f76c80769cb267a19ecf2a4ac76152a6410debb5a4790422256/pyobjc_framework_naturallanguage-12.1-py2.py3-none-any.whl", hash = "sha256:a02ef383ec88948ca28f03ab8995523726b3bc75c49f593b5c89c218bcbce7ce", size = 5320, upload-time = "2025-11-14T09:57:10.294Z" }, -] - -[[package]] -name = "pyobjc-framework-netfs" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/46/68/4bf0e5b8cc0780cf7acf0aec54def58c8bcf8d733db0bd38f5a264d1af06/pyobjc_framework_netfs-12.1.tar.gz", hash = "sha256:e8d0c25f41d7d9ced1aa2483238d0a80536df21f4b588640a72e1bdb87e75c1e", size = 14799, upload-time = "2025-11-14T10:18:11.85Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/6b/8c2f223879edd3e3f030d0a9c9ba812775519c6d0c257e3e7255785ca6e7/pyobjc_framework_netfs-12.1-py2.py3-none-any.whl", hash = "sha256:0021f8b141e693d3821524c170e9c645090eb320e80c2935ddb978a6e8b8da81", size = 4163, upload-time = "2025-11-14T09:57:11.845Z" }, -] - -[[package]] -name = "pyobjc-framework-network" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/38/13/a71270a1b0a9ec979e68b8ec84b0f960e908b17b51cb3cac246a74d52b6b/pyobjc_framework_network-12.1.tar.gz", hash = "sha256:dbf736ff84d1caa41224e86ff84d34b4e9eb6918ae4e373a44d3cb597648a16a", size = 56990, upload-time = "2025-11-14T10:18:16.714Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/ef/a53f04f43e93932817f2ea71689dcc8afe3b908d631c21d11ec30c7b2e87/pyobjc_framework_network-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5e53aad64eae2933fe12d49185d66aca62fb817abf8a46f86b01e436ce1b79e4", size = 19613, upload-time = "2025-11-14T09:57:19.571Z" }, -] - -[[package]] -name = "pyobjc-framework-networkextension" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bf/3e/ac51dbb2efa16903e6af01f3c1f5a854c558661a7a5375c3e8767ac668e8/pyobjc_framework_networkextension-12.1.tar.gz", hash = "sha256:36abc339a7f214ab6a05cb2384a9df912f247163710741e118662bd049acfa2e", size = 62796, upload-time = "2025-11-14T10:18:21.769Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/14/4934b10ade5ad0518001bfc25260d926816b9c7d08d85ef45e8a61fdef1b/pyobjc_framework_networkextension-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:adc9baacfc532944d67018e381c7645f66a9fa0064939a5a841476d81422cdcc", size = 14376, upload-time = "2025-11-14T09:57:36.132Z" }, -] - -[[package]] -name = "pyobjc-framework-notificationcenter" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c6/12/ae0fe82fb1e02365c9fe9531c9de46322f7af09e3659882212c6bf24d75e/pyobjc_framework_notificationcenter-12.1.tar.gz", hash = "sha256:2d09f5ab9dc39770bae4fa0c7cfe961e6c440c8fc465191d403633dccc941094", size = 21282, upload-time = "2025-11-14T10:18:24.51Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/05/3168637dd425257df5693c2ceafecf92d2e6833c0aaa6594d894a528d797/pyobjc_framework_notificationcenter-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82a735bd63f315f0a56abd206373917b7d09a0ae35fd99f1639a0fac4c525c0a", size = 9895, upload-time = "2025-11-14T09:57:51.151Z" }, -] - -[[package]] -name = "pyobjc-framework-opendirectory" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5b/11/bc2f71d3077b3bd078dccad5c0c5c57ec807fefe3d90c97b97dd0ed3d04b/pyobjc_framework_opendirectory-12.1.tar.gz", hash = "sha256:2c63ce5dd179828ef2d8f9e3961da3bfa971a57db07a6c34eedc296548a928bb", size = 61049, upload-time = "2025-11-14T10:18:29.336Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d6/e7/3c2dece9c5b28af28a44d72a27b35ea5ffac31fed7cbd8d696ea75dc4a81/pyobjc_framework_opendirectory-12.1-py2.py3-none-any.whl", hash = "sha256:b5b5a5cf3cc2fb25147b16b79f046b90e3982bf3ded1b210a993d8cfdba737c4", size = 11845, upload-time = "2025-11-14T09:58:00.175Z" }, -] - -[[package]] -name = "pyobjc-framework-osakit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/b9/bf52c555c75a83aa45782122432fa06066bb76469047f13d06fb31e585c4/pyobjc_framework_osakit-12.1.tar.gz", hash = "sha256:36ea6acf03483dc1e4344a0cce7250a9656f44277d12bc265fa86d4cbde01f23", size = 17102, upload-time = "2025-11-14T10:18:31.354Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/10/30a15d7b23e6fcfa63d41ca4c7356c39ff81300249de89c3ff28216a9790/pyobjc_framework_osakit-12.1-py2.py3-none-any.whl", hash = "sha256:c49165336856fd75113d2e264a98c6deb235f1bd033eae48f661d4d832d85e6b", size = 4162, upload-time = "2025-11-14T09:58:01.953Z" }, -] - -[[package]] -name = "pyobjc-framework-oslog" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/12/42/805c9b4ac6ad25deb4215989d8fc41533d01e07ffd23f31b65620bade546/pyobjc_framework_oslog-12.1.tar.gz", hash = "sha256:d0ec6f4e3d1689d5e4341bc1130c6f24cb4ad619939f6c14d11a7e80c0ac4553", size = 21193, upload-time = "2025-11-14T10:18:33.645Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/60/0b742347d484068e9d6867cd95dedd1810c790b6aca45f6ef1d0f089f1f5/pyobjc_framework_oslog-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:072a41d36fcf780a070f13ac2569f8bafbb5ae4792fab4136b1a4d602dd9f5b4", size = 7813, upload-time = "2025-11-14T09:58:07.768Z" }, -] - -[[package]] -name = "pyobjc-framework-passkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6c/d4/2afb59fb0f99eb2f03888850887e536f1ef64b303fd756283679471a5189/pyobjc_framework_passkit-12.1.tar.gz", hash = "sha256:d8c27c352e86a3549bf696504e6b25af5f2134b173d9dd60d66c6d3da53bb078", size = 53835, upload-time = "2025-11-14T10:18:37.906Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d8/dc/9cb27e8b7b00649af5e802815ffa8928bd8a619f2984a1bea7dabd28f741/pyobjc_framework_passkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7e95a484ec529dbf1d44f5f7f1406502a77bda733511e117856e3dca9fa29c5c", size = 14102, upload-time = "2025-11-14T09:58:20.903Z" }, -] - -[[package]] -name = "pyobjc-framework-pencilkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7e/43/859068016bcbe7d80597d5c579de0b84b0da62c5c55cdf9cc940e9f9c0f8/pyobjc_framework_pencilkit-12.1.tar.gz", hash = "sha256:d404982d1f7a474369f3e7fea3fbd6290326143fa4138d64b6753005a6263dc4", size = 17664, upload-time = "2025-11-14T10:18:40.045Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/26/daf47dcfced8f7326218dced5c68ed2f3b522ec113329218ce1305809535/pyobjc_framework_pencilkit-12.1-py2.py3-none-any.whl", hash = "sha256:33b88e5ed15724a12fd8bf27a68614b654ff739d227e81161298bc0d03acca4f", size = 4206, upload-time = "2025-11-14T09:58:30.814Z" }, -] - -[[package]] -name = "pyobjc-framework-phase" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-avfoundation" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/96/51/3b25eaf7ca85f38ceef892fdf066b7faa0fec716f35ea928c6ffec6ae311/pyobjc_framework_phase-12.1.tar.gz", hash = "sha256:3a69005c572f6fd777276a835115eb8359a33673d4a87e754209f99583534475", size = 32730, upload-time = "2025-11-14T10:18:43.102Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/9f/1ae45db731e8d6dd3e0b408c3accd0cf3236849e671f95c7c8cf95687240/pyobjc_framework_phase-12.1-py2.py3-none-any.whl", hash = "sha256:99a1c1efc6644f5312cce3693117d4e4482538f65ad08fe59e41e2579b67ab17", size = 6902, upload-time = "2025-11-14T09:58:32.436Z" }, -] - -[[package]] -name = "pyobjc-framework-photos" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b8/53/f8a3dc7f711034d2283e289cd966fb7486028ea132a24260290ff32d3525/pyobjc_framework_photos-12.1.tar.gz", hash = "sha256:adb68aaa29e186832d3c36a0b60b0592a834e24c5263e9d78c956b2b77dce563", size = 47034, upload-time = "2025-11-14T10:18:47.27Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/13/38/e6f25aec46a1a9d0a310795606cc43f9823d41c3e152114b814b597835a8/pyobjc_framework_photos-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eda8a584a851506a1ebbb2ee8de2cb1ed9e3431e6a642ef6a9543e32117d17b9", size = 12358, upload-time = "2025-11-14T09:58:38.131Z" }, -] - -[[package]] -name = "pyobjc-framework-photosui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/40/a5/14c538828ed1a420e047388aedc4a2d7d9292030d81bf6b1ced2ec27b6e9/pyobjc_framework_photosui-12.1.tar.gz", hash = "sha256:9141234bb9d17687f1e8b66303158eccdd45132341fbe5e892174910035f029a", size = 29886, upload-time = "2025-11-14T10:18:50.238Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/a2/b5afca8039b1a659a2a979bb1bdbdddfdf9b1d2724a2cc4633dca2573d5f/pyobjc_framework_photosui-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:713e3bb25feb5ea891e67260c2c0769cab44a7f11b252023bfcf9f8c29dd1206", size = 11714, upload-time = "2025-11-14T09:58:53.674Z" }, -] - -[[package]] -name = "pyobjc-framework-preferencepanes" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/bc/e87df041d4f7f6b7721bf7996fa02aa0255939fb0fac0ecb294229765f92/pyobjc_framework_preferencepanes-12.1.tar.gz", hash = "sha256:b2a02f9049f136bdeca7642b3307637b190850e5853b74b5c372bc7d88ef9744", size = 24543, upload-time = "2025-11-14T10:18:53.259Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/7b/8ceec1ab0446224d685e243e2770c5a5c92285bcab0b9324dbe7a893ae5a/pyobjc_framework_preferencepanes-12.1-py2.py3-none-any.whl", hash = "sha256:1b3af9db9e0cfed8db28c260b2cf9a22c15fda5f0ff4c26157b17f99a0e29bbf", size = 4797, upload-time = "2025-11-14T09:59:03.998Z" }, -] - -[[package]] -name = "pyobjc-framework-pushkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a9/45/de756b62709add6d0615f86e48291ee2bee40223e7dde7bbe68a952593f0/pyobjc_framework_pushkit-12.1.tar.gz", hash = "sha256:829a2fc8f4780e75fc2a41217290ee0ff92d4ade43c42def4d7e5af436d8ae82", size = 19465, upload-time = "2025-11-14T10:18:57.727Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b9/01/74cf1dd0764c590de05dc1e87d168031e424f834721940b7bb02c67fe821/pyobjc_framework_pushkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7bdf472a55ac65154e03f54ae0bcad64c4cf45e9b1acba62f15107f2bc994d69", size = 8177, upload-time = "2025-11-14T09:59:11.155Z" }, -] - -[[package]] -name = "pyobjc-framework-quartz" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/18/cc59f3d4355c9456fc945eae7fe8797003c4da99212dd531ad1b0de8a0c6/pyobjc_framework_quartz-12.1.tar.gz", hash = "sha256:27f782f3513ac88ec9b6c82d9767eef95a5cf4175ce88a1e5a65875fee799608", size = 3159099, upload-time = "2025-11-14T10:21:24.31Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e9/9b/780f057e5962f690f23fdff1083a4cfda5a96d5b4d3bb49505cac4f624f2/pyobjc_framework_quartz-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7730cdce46c7e985535b5a42c31381af4aa6556e5642dc55b5e6597595e57a16", size = 218798, upload-time = "2025-11-14T10:00:01.236Z" }, -] - -[[package]] -name = "pyobjc-framework-quicklookthumbnailing" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/97/1a/b90539500e9a27c2049c388d85a824fc0704009b11e33b05009f52a6dc67/pyobjc_framework_quicklookthumbnailing-12.1.tar.gz", hash = "sha256:4f7e09e873e9bda236dce6e2f238cab571baeb75eca2e0bc0961d5fcd85f3c8f", size = 14790, upload-time = "2025-11-14T10:21:26.442Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/22/7bd07b5b44bf8540514a9f24bc46da68812c1fd6c63bb2d3496e5ea44bf0/pyobjc_framework_quicklookthumbnailing-12.1-py2.py3-none-any.whl", hash = "sha256:5efe50b0318188b3a4147681788b47fce64709f6fe0e1b5d020e408ef40ab08e", size = 4234, upload-time = "2025-11-14T10:01:02.209Z" }, -] - -[[package]] -name = "pyobjc-framework-replaykit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/35/f8/b92af879734d91c1726227e7a03b9e68ab8d9d2bb1716d1a5c29254087f2/pyobjc_framework_replaykit-12.1.tar.gz", hash = "sha256:95801fd35c329d7302b2541f2754e6574bf36547ab869fbbf41e408dfa07268a", size = 23312, upload-time = "2025-11-14T10:21:29.18Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/fc/c68d2111b2655148d88574959d3d8b21d3a003573013301d4d2a7254c1af/pyobjc_framework_replaykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b0528c2a6188440fdc2017f0924c0a0f15d0a2f6aa295f1d1c2d6b3894c22f1d", size = 10120, upload-time = "2025-11-14T10:01:08.397Z" }, -] - -[[package]] -name = "pyobjc-framework-safariservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3e/4b/8f896bafbdbfa180a5ba1e21a6f5dc63150c09cba69d85f68708e02866ae/pyobjc_framework_safariservices-12.1.tar.gz", hash = "sha256:6a56f71c1e692bca1f48fe7c40e4c5a41e148b4e3c6cfb185fd80a4d4a951897", size = 25165, upload-time = "2025-11-14T10:21:32.041Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/67/3a/8c525562fd782c88bc44e8c07fc2c073919f98dead08fffd50f280ef1afa/pyobjc_framework_safariservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b475abc82504fc1c0801096a639562d6a6d37370193e8e4a406de9199a7cea13", size = 7281, upload-time = "2025-11-14T10:01:21.238Z" }, -] - -[[package]] -name = "pyobjc-framework-safetykit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f4/bf/ad6bf60ceb61614c9c9f5758190971e9b90c45b1c7a244e45db64138b6c2/pyobjc_framework_safetykit-12.1.tar.gz", hash = "sha256:0cd4850659fb9b5632fd8ad21f2de6863e8303ff0d51c5cc9c0034aac5db08d8", size = 20086, upload-time = "2025-11-14T10:21:34.212Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/0c/08a20fb7516405186c0fe7299530edd4aa22c24f73290198312447f26c8c/pyobjc_framework_safetykit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e4977f7069a23252053d1a42b1a053aefc19b85c960a5214b05daf3c037a6f16", size = 8550, upload-time = "2025-11-14T10:01:32.885Z" }, -] - -[[package]] -name = "pyobjc-framework-scenekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/94/8c/1f4005cf0cb68f84dd98b93bbc0974ee7851bb33d976791c85e042dc2278/pyobjc_framework_scenekit-12.1.tar.gz", hash = "sha256:1bd5b866f31fd829f26feac52e807ed942254fd248115c7c742cfad41d949426", size = 101212, upload-time = "2025-11-14T10:21:41.265Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/f1/4986bd96e0ba0f60bff482a6b135b9d6db65d56578d535751f18f88190f0/pyobjc_framework_scenekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:40aea10098893f0b06191f1e79d7b25e12e36a9265549d324238bdb25c7e6df0", size = 33597, upload-time = "2025-11-14T10:01:51.297Z" }, -] - -[[package]] -name = "pyobjc-framework-screencapturekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2d/7f/73458db1361d2cb408f43821a1e3819318a0f81885f833d78d93bdc698e0/pyobjc_framework_screencapturekit-12.1.tar.gz", hash = "sha256:50992c6128b35ab45d9e336f0993ddd112f58b8c8c8f0892a9cb42d61bd1f4c9", size = 32573, upload-time = "2025-11-14T10:21:44.497Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/a8/533acdbf26e0a908ff640d3a445481f3c948682ca887be6711b5fcf82682/pyobjc_framework_screencapturekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:27df138ce2dfa9d4aae5106d4877e9ed694b5a174643c058f1c48678ffc7001a", size = 11504, upload-time = "2025-11-14T10:02:11.36Z" }, -] - -[[package]] -name = "pyobjc-framework-screensaver" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/99/7cfbce880cea61253a44eed594dce66c2b2fbf29e37eaedcd40cffa949e9/pyobjc_framework_screensaver-12.1.tar.gz", hash = "sha256:c4ca111317c5a3883b7eace0a9e7dd72bc6ffaa2ca954bdec918c3ab7c65c96f", size = 22229, upload-time = "2025-11-14T10:21:47.299Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/a4/2481711f2e9557b90bac74fa8bf821162cf7b65835732ae560fd52e9037e/pyobjc_framework_screensaver-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3c90c2299eac6d01add81427ae2f90d7724f15d676261e838d7a7750f812322", size = 8422, upload-time = "2025-11-14T10:02:24.49Z" }, -] - -[[package]] -name = "pyobjc-framework-screentime" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/11/ba18f905321895715dac3cae2071c2789745ae13605b283b8114b41e0459/pyobjc_framework_screentime-12.1.tar.gz", hash = "sha256:583de46b365543bbbcf27cd70eedd375d397441d64a2cf43c65286fd9c91af55", size = 13413, upload-time = "2025-11-14T10:21:49.17Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/27/06/904174de6170e11b53673cc5844e5f13394eeeed486e0bcdf5288c1b0853/pyobjc_framework_screentime-12.1-py2.py3-none-any.whl", hash = "sha256:d34a068ec8ba2704987fcd05c37c9a9392de61d92933e6e71c8e4eaa4dfce029", size = 3963, upload-time = "2025-11-14T10:02:32.577Z" }, -] - -[[package]] -name = "pyobjc-framework-scriptingbridge" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0c/cb/adc0a09e8c4755c2281bd12803a87f36e0832a8fc853a2d663433dbb72ce/pyobjc_framework_scriptingbridge-12.1.tar.gz", hash = "sha256:0e90f866a7e6a8aeaf723d04c826657dd528c8c1b91e7a605f8bb947c74ad082", size = 20339, upload-time = "2025-11-14T10:21:51.769Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/51/46/e0b07d2b3ff9effb8b1179a6cc681a953d3dfbf0eb8b1d6a0e54cef2e922/pyobjc_framework_scriptingbridge-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8083cd68c559c55a3787b2e74fc983c8665e5078571475aaeabf4f34add36b62", size = 8356, upload-time = "2025-11-14T10:02:38.559Z" }, -] - -[[package]] -name = "pyobjc-framework-searchkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-coreservices" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6e/60/a38523198430e14fdef21ebe62a93c43aedd08f1f3a07ea3d96d9997db5d/pyobjc_framework_searchkit-12.1.tar.gz", hash = "sha256:ddd94131dabbbc2d7c3f17db3da87c1a712c431310eef16f07187771e7e85226", size = 30942, upload-time = "2025-11-14T10:21:55.483Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/72/46/4f9cd3011f47b43b21b2924ab3770303c3f0a4d16f05550d38c5fcb42e78/pyobjc_framework_searchkit-12.1-py2.py3-none-any.whl", hash = "sha256:844ce62b7296b19da8db7dedd539d07f7b3fb3bb8b029c261f7bcf0e01a97758", size = 3733, upload-time = "2025-11-14T10:02:47.026Z" }, -] - -[[package]] -name = "pyobjc-framework-security" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/80/aa/796e09a3e3d5cee32ebeebb7dcf421b48ea86e28c387924608a05e3f668b/pyobjc_framework_security-12.1.tar.gz", hash = "sha256:7fecb982bd2f7c4354513faf90ba4c53c190b7e88167984c2d0da99741de6da9", size = 168044, upload-time = "2025-11-14T10:22:06.334Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/66/5160c0f938fc0515fe8d9af146aac1b093f7ef285ce797fedae161b6c0e8/pyobjc_framework_security-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab42e55f5b782332be5442750fcd9637ee33247d57c7b1d5801bc0e24ee13278", size = 41280, upload-time = "2025-11-14T10:02:58.097Z" }, -] - -[[package]] -name = "pyobjc-framework-securityfoundation" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/57/d5/c2b77e83c1585ba43e5f00c917273ba4bf7ed548c1b691f6766eb0418d52/pyobjc_framework_securityfoundation-12.1.tar.gz", hash = "sha256:1f39f4b3db6e3bd3a420aaf4923228b88e48c90692cf3612b0f6f1573302a75d", size = 12669, upload-time = "2025-11-14T10:22:09.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/1e/349fb71a413b37b1b41e712c7ca180df82144478f8a9a59497d66d0f2ea2/pyobjc_framework_securityfoundation-12.1-py2.py3-none-any.whl", hash = "sha256:579cf23e63434226f78ffe0afb8426e971009588e4ad812c478d47dfd558201c", size = 3792, upload-time = "2025-11-14T10:03:14.459Z" }, -] - -[[package]] -name = "pyobjc-framework-securityinterface" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f9/64/bf5b5d82655112a2314422ee649f1e1e73d4381afa87e1651ce7e8444694/pyobjc_framework_securityinterface-12.1.tar.gz", hash = "sha256:deef11ad03be8d9ff77db6e7ac40f6b641ee2d72eaafcf91040537942472e88b", size = 25552, upload-time = "2025-11-14T10:22:12.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/59/3e/17889a6de03dc813606bb97887dc2c4c2d4e7c8f266bc439548bae756e90/pyobjc_framework_securityinterface-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:5cb5e79a73ea17663ebd29e350401162d93e42343da7d96c77efb38ae64ff01f", size = 10783, upload-time = "2025-11-14T10:03:20.202Z" }, -] - -[[package]] -name = "pyobjc-framework-securityui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-security" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/3f/d870305f5dec58cd02966ca06ac29b69fb045d8b46dfb64e2da31f295345/pyobjc_framework_securityui-12.1.tar.gz", hash = "sha256:f1435fed85edc57533c334a4efc8032170424b759da184cb7a7a950ceea0e0b6", size = 12184, upload-time = "2025-11-14T10:22:14.323Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/36/7f/eff9ffdd34511cc95a60e5bd62f1cfbcbcec1a5012ef1168161506628c87/pyobjc_framework_securityui-12.1-py2.py3-none-any.whl", hash = "sha256:3e988b83c9a2bb0393207eaa030fc023a8708a975ac5b8ea0508cdafc2b60705", size = 3594, upload-time = "2025-11-14T10:03:29.628Z" }, -] - -[[package]] -name = "pyobjc-framework-sensitivecontentanalysis" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e7/ce/17bf31753e14cb4d64fffaaba2377453c4977c2c5d3cf2ff0a3db30026c7/pyobjc_framework_sensitivecontentanalysis-12.1.tar.gz", hash = "sha256:2c615ac10e93eb547b32b214cd45092056bee0e79696426fd09978dc3e670f25", size = 13745, upload-time = "2025-11-14T10:22:16.447Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/23/c99568a0d4e38bd8337d52e4ae25a0b0bd540577f2e06f3430c951d73209/pyobjc_framework_sensitivecontentanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:faf19d32d4599ac2b18fb1ccdc3e33b2b242bdf34c02e69978bd62d3643ad068", size = 4230, upload-time = "2025-11-14T10:03:31.26Z" }, -] - -[[package]] -name = "pyobjc-framework-servicemanagement" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/d0/b26c83ae96ab55013df5fedf89337d4d62311b56ce3f520fc7597d223d82/pyobjc_framework_servicemanagement-12.1.tar.gz", hash = "sha256:08120981749a698033a1d7a6ab99dbbe412c5c0d40f2b4154014b52113511c1d", size = 14585, upload-time = "2025-11-14T10:22:18.735Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/5d/1009c32189f9cb26da0124b4a60640ed26dd8ad453810594f0cbfab0ff70/pyobjc_framework_servicemanagement-12.1-py2.py3-none-any.whl", hash = "sha256:9a2941f16eeb71e55e1cd94f50197f91520778c7f48ad896761f5e78725cc08f", size = 5357, upload-time = "2025-11-14T10:03:32.928Z" }, -] - -[[package]] -name = "pyobjc-framework-sharedwithyou" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-sharedwithyoucore" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0f/8b/8ab209a143c11575a857e2111acc5427fb4986b84708b21324cbcbf5591b/pyobjc_framework_sharedwithyou-12.1.tar.gz", hash = "sha256:167d84794a48f408ee51f885210c616fda1ec4bff3dd8617a4b5547f61b05caf", size = 24791, upload-time = "2025-11-14T10:22:21.248Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/ee/e5113ce985a480d13a0fa3d41a242c8068dc09b3c13210557cf5cc6a544a/pyobjc_framework_sharedwithyou-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a99a6ebc6b6de7bc8663b1f07332fab9560b984a57ce344dc5703f25258f258d", size = 8763, upload-time = "2025-11-14T10:03:38.467Z" }, -] - -[[package]] -name = "pyobjc-framework-sharedwithyoucore" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/55/ef/84059c5774fd5435551ab7ab40b51271cfb9997b0d21f491c6b429fe57a8/pyobjc_framework_sharedwithyoucore-12.1.tar.gz", hash = "sha256:0813149eeb755d718b146ec9365eb4ca3262b6af9ff9ba7db2f7b6f4fd104518", size = 22350, upload-time = "2025-11-14T10:22:23.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/0e/0c2b0591ebc72d437dccca7a1e7164c5f11dde2189d4f4c707a132bab740/pyobjc_framework_sharedwithyoucore-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed928266ae9d577ff73de72a03bebc66a751918eb59ca660a9eca157392f17be", size = 8530, upload-time = "2025-11-14T10:03:50.839Z" }, -] - -[[package]] -name = "pyobjc-framework-shazamkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/2c/8d82c5066cc376de68ad8c1454b7c722c7a62215e5c2f9dac5b33a6c3d42/pyobjc_framework_shazamkit-12.1.tar.gz", hash = "sha256:71db2addd016874639a224ed32b2000b858802b0370c595a283cce27f76883fe", size = 22518, upload-time = "2025-11-14T10:22:25.996Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/5e/7d60d8e7b036b20d0e94cd7c4563e7414653344482e85fbc7facffabc95f/pyobjc_framework_shazamkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e184dd0f61a604b1cfcf44418eb95b943e7b8f536058a29e4b81acadd27a9420", size = 8577, upload-time = "2025-11-14T10:04:04.182Z" }, -] - -[[package]] -name = "pyobjc-framework-social" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/31/21/afc6f37dfdd2cafcba0227e15240b5b0f1f4ad57621aeefda2985ac9560e/pyobjc_framework_social-12.1.tar.gz", hash = "sha256:1963db6939e92ae40dd9d68852e8f88111cbfd37a83a9fdbc9a0c08993ca7e60", size = 13184, upload-time = "2025-11-14T10:22:28.048Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/fb/090867e332d49a1e492e4b8972ac6034d1c7d17cf39f546077f35be58c46/pyobjc_framework_social-12.1-py2.py3-none-any.whl", hash = "sha256:2f3b36ba5769503b1bc945f85fd7b255d42d7f6e417d78567507816502ff2b44", size = 4462, upload-time = "2025-11-14T10:04:14.578Z" }, -] - -[[package]] -name = "pyobjc-framework-soundanalysis" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6b/d6/5039b61edc310083425f87ce2363304d3a87617e941c1d07968c63b5638d/pyobjc_framework_soundanalysis-12.1.tar.gz", hash = "sha256:e2deead8b9a1c4513dbdcf703b21650dcb234b60a32d08afcec4895582b040b1", size = 14804, upload-time = "2025-11-14T10:22:29.998Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/53/d3/8df5183d52d20d459225d3f5d24f55e01b8cd9fe587ed972e3f20dd18709/pyobjc_framework_soundanalysis-12.1-py2.py3-none-any.whl", hash = "sha256:8b2029ab48c1a9772f247f0aea995e8c3ff4706909002a9c1551722769343a52", size = 4188, upload-time = "2025-11-14T10:04:16.12Z" }, -] - -[[package]] -name = "pyobjc-framework-speech" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8d/3d/194cf19fe7a56c2be5dfc28f42b3b597a62ebb1e1f52a7dd9c55b917ac6c/pyobjc_framework_speech-12.1.tar.gz", hash = "sha256:2a2a546ba6c52d5dd35ddcfee3fd9226a428043d1719597e8701851a6566afdd", size = 25218, upload-time = "2025-11-14T10:22:32.505Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/1b/224cb98c9c32a6d5e68072f89d26444095be54c6f461efe4fefe9d1330a5/pyobjc_framework_speech-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:cae4b88ef9563157a6c9e66b37778fc4022ee44dd1a2a53081c2adbb69698945", size = 9254, upload-time = "2025-11-14T10:04:21.361Z" }, -] - -[[package]] -name = "pyobjc-framework-spritekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b6/78/d683ebe0afb49f46d2d21d38c870646e7cb3c2e83251f264e79d357b1b74/pyobjc_framework_spritekit-12.1.tar.gz", hash = "sha256:a851f4ef5aa65cc9e08008644a528e83cb31021a1c0f17ebfce4de343764d403", size = 64470, upload-time = "2025-11-14T10:22:37.569Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/38/97c3b6c3437e3e9267fb4e1cd86e0da4eff07e0abe7cd6923644d2dfc878/pyobjc_framework_spritekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1649e57c25145795d04bb6a1ec44c20ef7cf0af7c60a9f6f5bc7998dd269db1e", size = 17802, upload-time = "2025-11-14T10:04:35.346Z" }, -] - -[[package]] -name = "pyobjc-framework-storekit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/00/87/8a66a145feb026819775d44975c71c1c64df4e5e9ea20338f01456a61208/pyobjc_framework_storekit-12.1.tar.gz", hash = "sha256:818452e67e937a10b5c8451758274faa44ad5d4329df0fa85735115fb0608da9", size = 34574, upload-time = "2025-11-14T10:22:40.73Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8a/9f/938985e506de0cc3a543e44e1f9990e9e2fb8980b8f3bcfc8f7921d09061/pyobjc_framework_storekit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9fe2d65a2b644bb6b4fdd3002292cba153560917de3dd6cf969431fa32d21dd0", size = 12819, upload-time = "2025-11-14T10:04:50.945Z" }, -] - -[[package]] -name = "pyobjc-framework-symbols" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/9a/ce/a48819eb8524fa2dc11fb3dd40bb9c4dcad0596fe538f5004923396c2c6c/pyobjc_framework_symbols-12.1.tar.gz", hash = "sha256:7d8e999b8a59c97d38d1d343b6253b1b7d04bf50b665700957d89c8ac43b9110", size = 12782, upload-time = "2025-11-14T10:22:42.609Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/ea/6e9af9c750d68109ac54fbffb5463e33a7b54ffe8b9901a5b6b603b7884b/pyobjc_framework_symbols-12.1-py2.py3-none-any.whl", hash = "sha256:c72eecbc25f6bfcd39c733067276270057c5aca684be20fdc56def645f2b6446", size = 3331, upload-time = "2025-11-14T10:05:01.333Z" }, -] - -[[package]] -name = "pyobjc-framework-syncservices" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coredata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/21/91/6d03a988831ddb0fb001b13573560e9a5bcccde575b99350f98fe56a2dd4/pyobjc_framework_syncservices-12.1.tar.gz", hash = "sha256:6a213e93d9ce15128810987e4c5de8c73cfab1564ac8d273e6b437a49965e976", size = 31032, upload-time = "2025-11-14T10:22:45.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/ac/a83cdd120e279ee905e9085afda90992159ed30c6a728b2c56fa2d36b6ea/pyobjc_framework_syncservices-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0cd629bea95692aad2d26196657cde2fbadedae252c7846964228661a600b900", size = 13411, upload-time = "2025-11-14T10:05:07.741Z" }, -] - -[[package]] -name = "pyobjc-framework-systemconfiguration" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/7d/50848df8e1c6b5e13967dee9fb91d3391fe1f2399d2d0797d2fc5edb32ba/pyobjc_framework_systemconfiguration-12.1.tar.gz", hash = "sha256:90fe04aa059876a21626931c71eaff742a27c79798a46347fd053d7008ec496e", size = 59158, upload-time = "2025-11-14T10:22:53.056Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/d3/bb935c3d4bae9e6ce4a52638e30eea7039c480dd96bc4f0777c9fabda21b/pyobjc_framework_systemconfiguration-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0e5bb9103d39483964431db7125195c59001b7bff2961869cfe157b4c861e52d", size = 21578, upload-time = "2025-11-14T10:05:25.572Z" }, -] - -[[package]] -name = "pyobjc-framework-systemextensions" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/12/01/8a706cd3f7dfcb9a5017831f2e6f9e5538298e90052db3bb8163230cbc4f/pyobjc_framework_systemextensions-12.1.tar.gz", hash = "sha256:243e043e2daee4b5c46cd90af5fff46b34596aac25011bab8ba8a37099685eeb", size = 20701, upload-time = "2025-11-14T10:22:58.257Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/cc/a42883d6ad0ae257a7fa62660b4dd13be15f8fa657922f9a5b6697f26e28/pyobjc_framework_systemextensions-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:01fac4f8d88c0956d9fc714d24811cd070e67200ba811904317d91e849e38233", size = 9166, upload-time = "2025-11-14T10:05:41.479Z" }, -] - -[[package]] -name = "pyobjc-framework-threadnetwork" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/62/7e/f1816c3461e4121186f2f7750c58af083d1826bbd73f72728da3edcf4915/pyobjc_framework_threadnetwork-12.1.tar.gz", hash = "sha256:e071eedb41bfc1b205111deb54783ec5a035ccd6929e6e0076336107fdd046ee", size = 12788, upload-time = "2025-11-14T10:23:00.329Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/b8/94b37dd353302c051a76f1a698cf55b5ad50ca061db7f0f332aa9e195766/pyobjc_framework_threadnetwork-12.1-py2.py3-none-any.whl", hash = "sha256:07d937748fc54199f5ec04d5a408e8691a870481c11b641785c2adc279dd8e4b", size = 3771, upload-time = "2025-11-14T10:05:49.899Z" }, -] - -[[package]] -name = "pyobjc-framework-uniformtypeidentifiers" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/65/b8/dd9d2a94509a6c16d965a7b0155e78edf520056313a80f0cd352413f0d0b/pyobjc_framework_uniformtypeidentifiers-12.1.tar.gz", hash = "sha256:64510a6df78336579e9c39b873cfcd03371c4b4be2cec8af75a8a3d07dff607d", size = 17030, upload-time = "2025-11-14T10:23:02.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/5f/1f10f5275b06d213c9897850f1fca9c881c741c1f9190cea6db982b71824/pyobjc_framework_uniformtypeidentifiers-12.1-py2.py3-none-any.whl", hash = "sha256:ec5411e39152304d2a7e0e426c3058fa37a00860af64e164794e0bcffee813f2", size = 4901, upload-time = "2025-11-14T10:05:51.532Z" }, -] - -[[package]] -name = "pyobjc-framework-usernotifications" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/cd/e0253072f221fa89a42fe53f1a2650cc9bf415eb94ae455235bd010ee12e/pyobjc_framework_usernotifications-12.1.tar.gz", hash = "sha256:019ccdf2d400f9a428769df7dba4ea97c02453372bc5f8b75ce7ae54dfe130f9", size = 29749, upload-time = "2025-11-14T10:23:05.364Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/c95053a475246464cba686e16269b0973821601910d1947d088b855a8dac/pyobjc_framework_usernotifications-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:412afb2bf5fe0049f9c4e732e81a8a35d5ebf97c30a5a6abd276259d020c82ac", size = 9644, upload-time = "2025-11-14T10:05:56.801Z" }, -] - -[[package]] -name = "pyobjc-framework-usernotificationsui" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-usernotifications" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/03/73e29fd5e5973cb3800c9d56107c1062547ef7524cbcc757c3cbbd5465c6/pyobjc_framework_usernotificationsui-12.1.tar.gz", hash = "sha256:51381c97c7344099377870e49ed0871fea85ba50efe50ab05ccffc06b43ec02e", size = 13125, upload-time = "2025-11-14T10:23:07.259Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/c8/52ac8a879079c1fbf25de8335ff506f7db87ff61e64838b20426f817f5d5/pyobjc_framework_usernotificationsui-12.1-py2.py3-none-any.whl", hash = "sha256:11af59dc5abfcb72c08769ab4d7ca32a628527a8ba341786431a0d2dacf31605", size = 3933, upload-time = "2025-11-14T10:06:05.478Z" }, -] - -[[package]] -name = "pyobjc-framework-videosubscriberaccount" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/10/f8/27927a9c125c622656ee5aada4596ccb8e5679da0260742360f193df6dcf/pyobjc_framework_videosubscriberaccount-12.1.tar.gz", hash = "sha256:750459fa88220ab83416f769f2d5d210a1f77b8938fa4d119aad0002fc32846b", size = 18793, upload-time = "2025-11-14T10:23:09.33Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/ca/e2f982916267508c1594f1e50d27bf223a24f55a5e175ab7d7822a00997c/pyobjc_framework_videosubscriberaccount-12.1-py2.py3-none-any.whl", hash = "sha256:381a5e8a3016676e52b88e38b706559fa09391d33474d8a8a52f20a883104a7b", size = 4825, upload-time = "2025-11-14T10:06:07.027Z" }, -] - -[[package]] -name = "pyobjc-framework-videotoolbox" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coremedia" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b3/5f/6995ee40dc0d1a3460ee183f696e5254c0ad14a25b5bc5fd9bd7266c077b/pyobjc_framework_videotoolbox-12.1.tar.gz", hash = "sha256:7adc8670f3b94b086aed6e86c3199b388892edab4f02933c2e2d9b1657561bef", size = 57825, upload-time = "2025-11-14T10:23:13.825Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/94/a5/91c6c95416f41c412c2079950527cb746c0712ec319c51a6c728c8d6b231/pyobjc_framework_videotoolbox-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eb6ce6837344ee319122066c16ada4beb913e7bfd62188a8d14b1ecbb5a89234", size = 18908, upload-time = "2025-11-14T10:06:14.087Z" }, -] - -[[package]] -name = "pyobjc-framework-virtualization" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/6a/9d110b5521d9b898fad10928818c9f55d66a4af9ac097426c65a9878b095/pyobjc_framework_virtualization-12.1.tar.gz", hash = "sha256:e96afd8e801e92c6863da0921e40a3b68f724804f888bce43791330658abdb0f", size = 40682, upload-time = "2025-11-14T10:23:17.456Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c6/f2/0da47e91f3f8eeda9a8b4bb0d3a0c54a18925009e99b66a8226b9e06ce1e/pyobjc_framework_virtualization-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7d5724b38e64b39ab5ec3b45993afa29fc88b307d99ee2c7a1c0fd770e9b4b21", size = 13131, upload-time = "2025-11-14T10:06:29.337Z" }, -] - -[[package]] -name = "pyobjc-framework-vision" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, - { name = "pyobjc-framework-coreml" }, - { name = "pyobjc-framework-quartz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c2/5a/08bb3e278f870443d226c141af14205ff41c0274da1e053b72b11dfc9fb2/pyobjc_framework_vision-12.1.tar.gz", hash = "sha256:a30959100e85dcede3a786c544e621ad6eb65ff6abf85721f805822b8c5fe9b0", size = 59538, upload-time = "2025-11-14T10:23:21.979Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/5a/23502935b3fc877d7573e743fc3e6c28748f33a45c43851d503bde52cde7/pyobjc_framework_vision-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:6b3211d84f3a12aad0cde752cfd43a80d0218960ac9e6b46b141c730e7d655bd", size = 16625, upload-time = "2025-11-14T10:06:44.422Z" }, -] - -[[package]] -name = "pyobjc-framework-webkit" -version = "12.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyobjc-core" }, - { name = "pyobjc-framework-cocoa" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/14/10/110a50e8e6670765d25190ca7f7bfeecc47ec4a8c018cb928f4f82c56e04/pyobjc_framework_webkit-12.1.tar.gz", hash = "sha256:97a54dd05ab5266bd4f614e41add517ae62cdd5a30328eabb06792474b37d82a", size = 284531, upload-time = "2025-11-14T10:23:40.287Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/67/64920c8d201a7fc27962f467c636c4e763b43845baba2e091a50a97a5d52/pyobjc_framework_webkit-12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:af2c7197447638b92aafbe4847c063b6dd5e1ed83b44d3ce7e71e4c9b042ab5a", size = 50084, upload-time = "2025-11-14T10:07:05.868Z" }, -] - -[[package]] -name = "pyopenssl" -version = "24.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/5d/70/ff56a63248562e77c0c8ee4aefc3224258f1856977e0c1472672b62dadb8/pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95", size = 184323, upload-time = "2024-07-20T17:26:31.252Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/dd/e0aa7ebef5168c75b772eda64978c597a9129b46be17779054652a7999e4/pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d", size = 58390, upload-time = "2024-07-20T17:26:29.057Z" }, -] - -[[package]] -name = "pyparsing" -version = "3.3.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, -] - -[[package]] -name = "pyperclip" -version = "1.11.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e8/52/d87eba7cb129b81563019d1679026e7a112ef76855d6159d24754dbd2a51/pyperclip-1.11.0.tar.gz", hash = "sha256:244035963e4428530d9e3a6101a1ef97209c6825edab1567beac148ccc1db1b6", size = 12185, upload-time = "2025-09-26T14:40:37.245Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/80/fc9d01d5ed37ba4c42ca2b55b4339ae6e200b456be3a1aaddf4a9fa99b8c/pyperclip-1.11.0-py3-none-any.whl", hash = "sha256:299403e9ff44581cb9ba2ffeed69c7aa96a008622ad0c46cb575ca75b5b84273", size = 11063, upload-time = "2025-09-26T14:40:36.069Z" }, -] - -[[package]] -name = "pyrect" -version = "0.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/04/2ba023d5f771b645f7be0c281cdacdcd939fe13d1deb331fc5ed1a6b3a98/PyRect-0.2.0.tar.gz", hash = "sha256:f65155f6df9b929b67caffbd57c0947c5ae5449d3b580d178074bffb47a09b78", size = 17219, upload-time = "2022-03-16T04:45:52.36Z" } - -[[package]] -name = "pyscreeze" -version = "1.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/f0/cb456ac4f1a73723d5b866933b7986f02bacea27516629c00f8e7da94c2d/pyscreeze-1.0.1.tar.gz", hash = "sha256:cf1662710f1b46aa5ff229ee23f367da9e20af4a78e6e365bee973cad0ead4be", size = 27826, upload-time = "2024-08-20T23:03:07.291Z" } - -[[package]] -name = "pyserial" -version = "3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1e/7d/ae3f0a63f41e4d2f6cb66a5b57197850f919f59e558159a4dd3a818f5082/pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", size = 159125, upload-time = "2020-11-23T03:59:15.045Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/07/bc/587a445451b253b285629263eb51c2d8e9bcea4fc97826266d186f96f558/pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0", size = 90585, upload-time = "2020-11-23T03:59:13.41Z" }, -] - -[[package]] -name = "pytest" -version = "9.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, - { name = "pygments" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, -] - -[[package]] -name = "pytest-asyncio" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/90/2c/8af215c0f776415f3590cac4f9086ccefd6fd463befeae41cd4d3f193e5a/pytest_asyncio-1.3.0.tar.gz", hash = "sha256:d7f52f36d231b80ee124cd216ffb19369aa168fc10095013c6b014a34d3ee9e5", size = 50087, upload-time = "2025-11-10T16:07:47.256Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/35/f8b19922b6a25bc0880171a2f1a003eaeb93657475193ab516fd87cac9da/pytest_asyncio-1.3.0-py3-none-any.whl", hash = "sha256:611e26147c7f77640e6d0a92a38ed17c3e9848063698d5c93d5aa7aa11cebff5", size = 15075, upload-time = "2025-11-10T16:07:45.537Z" }, -] - -[[package]] -name = "pytest-cpp" -version = "2.6.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/a1/c2679d7ff2da20a0f89c7820ae2739cde739eac9b43c192531117b31b5f4/pytest_cpp-2.6.0.tar.gz", hash = "sha256:c2f49d3c038539ac84786a94d852e4f4619c34c95979c2bc69c20b3bdf051d85", size = 465490, upload-time = "2024-09-18T00:08:08.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/44/dc2f5d53165264ae5831f361fe7723c45da05718a97015b2eddc452cf503/pytest_cpp-2.6.0-py3-none-any.whl", hash = "sha256:b33de94609450feea2fba9efff3558b8ac8f1fdf40a99e263b395d4798b911bb", size = 15074, upload-time = "2024-09-18T00:08:06.415Z" }, -] - -[[package]] -name = "pytest-mock" -version = "3.15.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/68/14/eb014d26be205d38ad5ad20d9a80f7d201472e08167f0bb4361e251084a9/pytest_mock-3.15.1.tar.gz", hash = "sha256:1849a238f6f396da19762269de72cb1814ab44416fa73a8686deac10b0d87a0f", size = 34036, upload-time = "2025-09-16T16:37:27.081Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, -] - -[[package]] -name = "pytest-subtests" -version = "0.15.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "attrs" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bb/d9/20097971a8d315e011e055d512fa120fd6be3bdb8f4b3aa3e3c6bf77bebc/pytest_subtests-0.15.0.tar.gz", hash = "sha256:cb495bde05551b784b8f0b8adfaa27edb4131469a27c339b80fd8d6ba33f887c", size = 18525, upload-time = "2025-10-20T16:26:18.358Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" }, -] - -[[package]] -name = "pytest-timeout" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, -] - -[[package]] -name = "pytest-xdist" -version = "3.7.1.dev24+g2b4372bd6" -source = { git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da#2b4372bd62699fb412c4fe2f95bf9f01bd2018da" } -dependencies = [ - { name = "execnet" }, - { name = "pytest" }, -] - -[[package]] -name = "python-dateutil" -version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, -] - -[[package]] -name = "python-xlib" -version = "0.33" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/86/f5/8c0653e5bb54e0cbdfe27bf32d41f27bc4e12faa8742778c17f2a71be2c0/python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32", size = 269068, upload-time = "2022-12-25T18:53:00.824Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/b8/ff33610932e0ee81ae7f1269c890f697d56ff74b9f5b2ee5d9b7fa2c5355/python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398", size = 182185, upload-time = "2022-12-25T18:52:58.662Z" }, -] - -[[package]] -name = "python3-xlib" -version = "0.15" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ef/c6/2c5999de3bb1533521f1101e8fe56fd9c266732f4d48011c7c69b29d12ae/python3-xlib-0.15.tar.gz", hash = "sha256:dc4245f3ae4aa5949c1d112ee4723901ade37a96721ba9645f2bfa56e5b383f8", size = 132828, upload-time = "2014-05-31T12:28:59.603Z" } - -[[package]] -name = "pytweening" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/0c/c16bc93ac2755bac0066a8ecbd2a2931a1735a6fffd99a2b9681c7e83e90/pytweening-1.2.0.tar.gz", hash = "sha256:243318b7736698066c5f362ec5c2b6434ecf4297c3c8e7caa8abfe6af4cac71b", size = 171241, upload-time = "2024-02-20T03:37:56.809Z" } - -[[package]] -name = "pywin32" -version = "311" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, -] - -[[package]] -name = "pywinbox" -version = "0.7" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ewmhlib", marker = "sys_platform == 'linux'" }, - { name = "pyobjc", marker = "sys_platform == 'darwin'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/37/d59397221e15d2a7f38afaa4e8e6b8c244d818044f7daa0bdc5988df0a69/PyWinBox-0.7-py3-none-any.whl", hash = "sha256:8b2506a8dd7afa0a910b368762adfac885274132ef9151b0c81b0d2c6ffd6f83", size = 12274, upload-time = "2024-04-17T10:10:31.899Z" }, -] - -[[package]] -name = "pywinctl" -version = "0.4.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ewmhlib", marker = "sys_platform == 'linux'" }, - { name = "pymonctl" }, - { name = "pyobjc", marker = "sys_platform == 'darwin'" }, - { name = "python-xlib", marker = "sys_platform == 'linux'" }, - { name = "pywin32", marker = "sys_platform == 'win32'" }, - { name = "pywinbox" }, - { name = "typing-extensions" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/33/8e4f632210b28fc9e998a9ab990e7ed97ecd2800cc50038e3800e1d85dbe/PyWinCtl-0.4.1-py3-none-any.whl", hash = "sha256:4d875e22969e1c6239d8c73156193630aaab876366167b8d97716f956384b089", size = 63158, upload-time = "2024-09-23T08:33:39.881Z" }, -] - -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, -] - -[[package]] -name = "pyyaml-env-tag" -version = "1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, -] - -[[package]] -name = "pyzmq" -version = "27.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "implementation_name == 'pypy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/04/0b/3c9baedbdf613ecaa7aa07027780b8867f57b6293b6ee50de316c9f3222b/pyzmq-27.1.0.tar.gz", hash = "sha256:ac0765e3d44455adb6ddbf4417dcce460fc40a05978c08efdf2948072f6db540", size = 281750, upload-time = "2025-09-08T23:10:18.157Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/92/e7/038aab64a946d535901103da16b953c8c9cc9c961dadcbf3609ed6428d23/pyzmq-27.1.0-cp312-abi3-macosx_10_15_universal2.whl", hash = "sha256:452631b640340c928fa343801b0d07eb0c3789a5ffa843f6e1a9cee0ba4eb4fc", size = 1306279, upload-time = "2025-09-08T23:08:03.807Z" }, - { url = "https://files.pythonhosted.org/packages/e8/5e/c3c49fdd0f535ef45eefcc16934648e9e59dace4a37ee88fc53f6cd8e641/pyzmq-27.1.0-cp312-abi3-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:1c179799b118e554b66da67d88ed66cd37a169f1f23b5d9f0a231b4e8d44a113", size = 895645, upload-time = "2025-09-08T23:08:05.301Z" }, - { url = "https://files.pythonhosted.org/packages/f8/e5/b0b2504cb4e903a74dcf1ebae157f9e20ebb6ea76095f6cfffea28c42ecd/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3837439b7f99e60312f0c926a6ad437b067356dc2bc2ec96eb395fd0fe804233", size = 652574, upload-time = "2025-09-08T23:08:06.828Z" }, - { url = "https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43ad9a73e3da1fab5b0e7e13402f0b2fb934ae1c876c51d0afff0e7c052eca31", size = 840995, upload-time = "2025-09-08T23:08:08.396Z" }, - { url = "https://files.pythonhosted.org/packages/c2/bb/b79798ca177b9eb0825b4c9998c6af8cd2a7f15a6a1a4272c1d1a21d382f/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:0de3028d69d4cdc475bfe47a6128eb38d8bc0e8f4d69646adfbcd840facbac28", size = 1642070, upload-time = "2025-09-08T23:08:09.989Z" }, - { url = "https://files.pythonhosted.org/packages/9c/80/2df2e7977c4ede24c79ae39dcef3899bfc5f34d1ca7a5b24f182c9b7a9ca/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_i686.whl", hash = "sha256:cf44a7763aea9298c0aa7dbf859f87ed7012de8bda0f3977b6fb1d96745df856", size = 2021121, upload-time = "2025-09-08T23:08:11.907Z" }, - { url = "https://files.pythonhosted.org/packages/46/bd/2d45ad24f5f5ae7e8d01525eb76786fa7557136555cac7d929880519e33a/pyzmq-27.1.0-cp312-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:f30f395a9e6fbca195400ce833c731e7b64c3919aa481af4d88c3759e0cb7496", size = 1878550, upload-time = "2025-09-08T23:08:13.513Z" }, - { url = "https://files.pythonhosted.org/packages/e6/2f/104c0a3c778d7c2ab8190e9db4f62f0b6957b53c9d87db77c284b69f33ea/pyzmq-27.1.0-cp312-abi3-win32.whl", hash = "sha256:250e5436a4ba13885494412b3da5d518cd0d3a278a1ae640e113c073a5f88edd", size = 559184, upload-time = "2025-09-08T23:08:15.163Z" }, - { url = "https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl", hash = "sha256:9ce490cf1d2ca2ad84733aa1d69ce6855372cb5ce9223802450c9b2a7cba0ccf", size = 619480, upload-time = "2025-09-08T23:08:17.192Z" }, - { url = "https://files.pythonhosted.org/packages/78/c2/c012beae5f76b72f007a9e91ee9401cb88c51d0f83c6257a03e785c81cc2/pyzmq-27.1.0-cp312-abi3-win_arm64.whl", hash = "sha256:75a2f36223f0d535a0c919e23615fc85a1e23b71f40c7eb43d7b1dedb4d8f15f", size = 552993, upload-time = "2025-09-08T23:08:18.926Z" }, -] - -[[package]] -name = "qrcode" -version = "8.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8f/b2/7fc2931bfae0af02d5f53b174e9cf701adbb35f39d69c2af63d4a39f81a9/qrcode-8.2.tar.gz", hash = "sha256:35c3f2a4172b33136ab9f6b3ef1c00260dd2f66f858f24d88418a015f446506c", size = 43317, upload-time = "2025-05-01T15:44:24.726Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/b8/d2d6d731733f51684bbf76bf34dab3b70a9148e8f2cef2bb544fccec681a/qrcode-8.2-py3-none-any.whl", hash = "sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f", size = 45986, upload-time = "2025-05-01T15:44:22.781Z" }, -] - -[[package]] -name = "raylib" -version = "5.5.0.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, - { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, - { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, - { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, -] - -[[package]] -name = "requests" -version = "2.32.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "charset-normalizer" }, - { name = "idna" }, - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, -] - -[[package]] -name = "ruamel-yaml" -version = "0.19.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" }, ] -[[package]] -name = "rubicon-objc" -version = "0.5.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/d2/d39ecd205661a5c14c90dbd92a722a203848a3621785c9783716341de427/rubicon_objc-0.5.3.tar.gz", hash = "sha256:74c25920c5951a05db9d3a1aac31d23816ec7dacc841a5b124d911b99ea71b9a", size = 171512, upload-time = "2025-12-03T03:51:10.264Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/93/ab/e834c01138c272fb2e37d2f3c7cba708bc694dbc7b3f03b743f29ceb92d5/rubicon_objc-0.5.3-py3-none-any.whl", hash = "sha256:31dedcda9be38435f5ec067906e1eea5d0ddb790330e98a22e94ff424758b415", size = 64414, upload-time = "2025-12-03T03:51:09.082Z" }, -] - [[package]] name = "ruff" version = "0.15.1" From 8e13d8abd05a895a8308b4e317efbb9321cff66b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 15 Feb 2026 20:30:31 -0800 Subject: [PATCH 214/685] CI: build big UI report --- .github/workflows/tests.yaml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2cca3444235..03e818f4224 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -255,8 +255,8 @@ jobs: source selfdrive/test/setup_vsound.sh && \ CI=1 pytest -s tools/sim/tests/test_metadrive_bridge.py" - create_mici_raylib_ui_report: - name: Create mici raylib UI Report + create_ui_report: + name: Create UI Report runs-on: ${{ (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || @@ -270,13 +270,14 @@ jobs: - uses: ./.github/workflows/setup-with-retry - name: Build openpilot run: ${{ env.RUN }} "scons -j$(nproc)" - - name: Create mici raylib UI Report + - name: Create UI Report run: > ${{ env.RUN }} "PYTHONWARNINGS=ignore && source selfdrive/test/setup_xvfb.sh && - WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py" - - name: Upload Raylib UI Report + WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py && + WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py --big" + - name: Upload UI Report uses: actions/upload-artifact@v6 with: - name: mici-raylib-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} + name: ui-report-${{ inputs.run_number || '1' }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} path: selfdrive/ui/tests/diff/report From 6704f63a3d74c25437167450ef5938f0071d24b3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 15 Feb 2026 20:43:56 -0800 Subject: [PATCH 215/685] update ui job name --- .github/workflows/mici_raylib_ui_preview.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mici_raylib_ui_preview.yaml b/.github/workflows/mici_raylib_ui_preview.yaml index 5025d407cd6..e5cfd34bdd5 100644 --- a/.github/workflows/mici_raylib_ui_preview.yaml +++ b/.github/workflows/mici_raylib_ui_preview.yaml @@ -14,7 +14,7 @@ on: workflow_dispatch: env: - UI_JOB_NAME: "Create mici raylib UI Report" + UI_JOB_NAME: "Create UI Report" REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-mici-raylib-ui" @@ -58,7 +58,7 @@ jobs: github_token: ${{ secrets.GITHUB_TOKEN }} run_id: ${{ steps.get_run_id.outputs.run_id }} search_artifacts: true - name: mici-raylib-report-1-${{ env.REPORT_NAME }} + name: ui-report-1-${{ env.REPORT_NAME }} path: ${{ github.workspace }}/pr_ui - name: Getting master ui # filename: master_ui_raylib/mici_ui_replay.mp4 From a5f9c2fc23b4e9e8fe4aea3884fada99b1dc1b41 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 15 Feb 2026 21:02:41 -0800 Subject: [PATCH 216/685] unified ui preview for mici and tizi (#37226) * unified ui preview for mici and tizi * lil more * variants * run it * trigger --- .github/workflows/mici_raylib_ui_preview.yaml | 151 --------------- .github/workflows/ui_preview.yaml | 176 ++++++++++++++++++ 2 files changed, 176 insertions(+), 151 deletions(-) delete mode 100644 .github/workflows/mici_raylib_ui_preview.yaml create mode 100644 .github/workflows/ui_preview.yaml diff --git a/.github/workflows/mici_raylib_ui_preview.yaml b/.github/workflows/mici_raylib_ui_preview.yaml deleted file mode 100644 index e5cfd34bdd5..00000000000 --- a/.github/workflows/mici_raylib_ui_preview.yaml +++ /dev/null @@ -1,151 +0,0 @@ -name: "mici raylib ui preview" -on: - push: - branches: - - master - pull_request_target: - types: [assigned, opened, synchronize, reopened, edited] - branches: - - 'master' - paths: - - 'selfdrive/assets/**' - - 'selfdrive/ui/**' - - 'system/ui/**' - workflow_dispatch: - -env: - UI_JOB_NAME: "Create UI Report" - REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} - SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} - BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-mici-raylib-ui" - MASTER_BRANCH_NAME: "openpilot_master_ui_mici_raylib" - # All report files are pushed here - REPORT_FILES_BRANCH_NAME: "mici-raylib-ui-reports" - -jobs: - preview: - if: github.repository == 'commaai/openpilot' - name: preview - runs-on: ubuntu-latest - timeout-minutes: 20 - permissions: - contents: read - pull-requests: write - actions: read - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - - name: Waiting for ui generation to end - uses: lewagon/wait-on-check-action@v1.3.4 - with: - ref: ${{ env.SHA }} - check-name: ${{ env.UI_JOB_NAME }} - repo-token: ${{ secrets.GITHUB_TOKEN }} - allowed-conclusions: success - wait-interval: 20 - - - name: Getting workflow run ID - id: get_run_id - run: | - echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT - - - name: Getting proposed ui # filename: pr_ui/mici_ui_replay.mp4 - id: download-artifact - uses: dawidd6/action-download-artifact@v6 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - run_id: ${{ steps.get_run_id.outputs.run_id }} - search_artifacts: true - name: ui-report-1-${{ env.REPORT_NAME }} - path: ${{ github.workspace }}/pr_ui - - - name: Getting master ui # filename: master_ui_raylib/mici_ui_replay.mp4 - uses: actions/checkout@v6 - with: - repository: commaai/ci-artifacts - ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} - path: ${{ github.workspace }}/master_ui_raylib - ref: ${{ env.MASTER_BRANCH_NAME }} - - - name: Saving new master ui - if: github.ref == 'refs/heads/master' && github.event_name == 'push' - working-directory: ${{ github.workspace }}/master_ui_raylib - run: | - git checkout --orphan=new_master_ui_mici_raylib - git rm -rf * - git branch -D ${{ env.MASTER_BRANCH_NAME }} - git branch -m ${{ env.MASTER_BRANCH_NAME }} - git config user.name "GitHub Actions Bot" - git config user.email "<>" - mv ${{ github.workspace }}/pr_ui/* . - git add . - git commit -m "mici raylib video for commit ${{ env.SHA }}" - git push origin ${{ env.MASTER_BRANCH_NAME }} --force - - - name: Setup FFmpeg - uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae - - - name: Finding diff - if: github.event_name == 'pull_request_target' - id: find_diff - run: | - # Find the video file from PR - pr_video="${{ github.workspace }}/pr_ui/mici_ui_replay_proposed.mp4" - mv "${{ github.workspace }}/pr_ui/mici_ui_replay.mp4" "$pr_video" - - master_video="${{ github.workspace }}/pr_ui/mici_ui_replay_master.mp4" - mv "${{ github.workspace }}/master_ui_raylib/mici_ui_replay.mp4" "$master_video" - - # Run report - export PYTHONPATH=${{ github.workspace }} - baseurl="https://github.com/commaai/ci-artifacts/raw/refs/heads/${{ env.BRANCH_NAME }}" - diff_exit_code=0 - python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py "${{ github.workspace }}/pr_ui/mici_ui_replay_master.mp4" "${{ github.workspace }}/pr_ui/mici_ui_replay_proposed.mp4" "diff.html" --basedir "$baseurl" --no-open || diff_exit_code=$? - - # Copy diff report files - cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.html ${{ github.workspace }}/pr_ui/ - cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.mp4 ${{ github.workspace }}/pr_ui/ - - REPORT_URL="https://commaai.github.io/ci-artifacts/diff_pr_${{ github.event.number }}.html" - if [ $diff_exit_code -eq 0 ]; then - DIFF="✅ Videos are identical! [View Diff Report]($REPORT_URL)" - else - DIFF="❌ Videos differ! [View Diff Report]($REPORT_URL)" - fi - echo "DIFF=$DIFF" >> "$GITHUB_OUTPUT" - - - name: Saving proposed ui - if: github.event_name == 'pull_request_target' - working-directory: ${{ github.workspace }}/master_ui_raylib - run: | - # Overwrite PR branch w/ proposed ui, and master ui at this point in time for future reference - git config user.name "GitHub Actions Bot" - git config user.email "<>" - git checkout --orphan=${{ env.BRANCH_NAME }} - git rm -rf * - mv ${{ github.workspace }}/pr_ui/* . - git add . - git commit -m "mici raylib video for PR #${{ github.event.number }}" - git push origin ${{ env.BRANCH_NAME }} --force - - # Append diff report to report files branch - git fetch origin ${{ env.REPORT_FILES_BRANCH_NAME }} - git checkout ${{ env.REPORT_FILES_BRANCH_NAME }} - cp ${{ github.workspace }}/selfdrive/ui/tests/diff/report/diff.html diff_pr_${{ github.event.number }}.html - git add diff_pr_${{ github.event.number }}.html - git commit -m "mici raylib ui diff report for PR #${{ github.event.number }}" || echo "No changes to commit" - git push origin ${{ env.REPORT_FILES_BRANCH_NAME }} - - - name: Comment Video on PR - if: github.event_name == 'pull_request_target' - uses: thollander/actions-comment-pull-request@v2 - with: - message: | - - ## mici raylib UI Preview - ${{ steps.find_diff.outputs.DIFF }} - comment_tag: run_id_video_mici_raylib - pr_number: ${{ github.event.number }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml new file mode 100644 index 00000000000..3663257daf4 --- /dev/null +++ b/.github/workflows/ui_preview.yaml @@ -0,0 +1,176 @@ +name: "ui preview" +on: + push: + branches: + - master + pull_request: # TODO: change back to pull_request_target before merging + types: [assigned, opened, synchronize, reopened, edited] + branches: + - 'master' + paths: # TODO: remove workflow path before merging + - 'selfdrive/assets/**' + - 'selfdrive/ui/**' + - 'system/ui/**' + - '.github/workflows/ui_preview.yaml' + workflow_dispatch: + +env: + UI_JOB_NAME: "Create UI Report" + REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }} + SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }} + BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-ui-preview" + REPORT_FILES_BRANCH_NAME: "mici-raylib-ui-reports" + + # variant:video_prefix:master_branch + VARIANTS: "mici:mici_ui_replay:openpilot_master_ui_mici_raylib big:tizi_ui_replay:openpilot_master_ui_big_raylib" + +jobs: + preview: + if: github.repository == 'commaai/openpilot' + name: preview + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + pull-requests: write + actions: read + steps: + - uses: actions/checkout@v6 + with: + submodules: true + + - name: Waiting for ui generation to end + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ env.SHA }} + check-name: ${{ env.UI_JOB_NAME }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + allowed-conclusions: success + wait-interval: 20 + + - name: Getting workflow run ID + id: get_run_id + run: | + echo "run_id=$(curl https://api.github.com/repos/${{ github.repository }}/commits/${{ env.SHA }}/check-runs | jq -r '.check_runs[] | select(.name == "${{ env.UI_JOB_NAME }}") | .html_url | capture("(?[0-9]+)") | .number')" >> $GITHUB_OUTPUT + + - name: Getting proposed ui + uses: dawidd6/action-download-artifact@v6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + run_id: ${{ steps.get_run_id.outputs.run_id }} + search_artifacts: true + name: ui-report-1-${{ env.REPORT_NAME }} + path: ${{ github.workspace }}/pr_ui + + - name: Getting mici master ui + uses: actions/checkout@v6 + with: + repository: commaai/ci-artifacts + ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} + path: ${{ github.workspace }}/master_mici + ref: openpilot_master_ui_mici_raylib + + - name: Getting big master ui + uses: actions/checkout@v6 + with: + repository: commaai/ci-artifacts + ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }} + path: ${{ github.workspace }}/master_big + ref: openpilot_master_ui_big_raylib + + - name: Saving new master ui + if: github.ref == 'refs/heads/master' && github.event_name == 'push' + run: | + for variant in $VARIANTS; do + IFS=':' read -r name video branch <<< "$variant" + master_dir="${{ github.workspace }}/master_${name}" + cd "$master_dir" + git checkout --orphan=new_branch + git rm -rf * + git branch -D "$branch" + git branch -m "$branch" + git config user.name "GitHub Actions Bot" + git config user.email "<>" + cp "${{ github.workspace }}/pr_ui/${video}.mp4" . + git add . + git commit -m "${name} video for commit ${{ env.SHA }}" + git push origin "$branch" --force + done + + - name: Setup FFmpeg + uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae + + - name: Finding diffs + if: github.event_name == 'pull_request' + id: find_diff + run: | + export PYTHONPATH=${{ github.workspace }} + baseurl="https://github.com/commaai/ci-artifacts/raw/refs/heads/${{ env.BRANCH_NAME }}" + + COMMENT="" + for variant in $VARIANTS; do + IFS=':' read -r name video _ <<< "$variant" + diff_name="${name}_diff" + + mv "${{ github.workspace }}/pr_ui/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_proposed.mp4" + cp "${{ github.workspace }}/master_${name}/${video}.mp4" "${{ github.workspace }}/pr_ui/${video}_master.mp4" + + diff_exit_code=0 + python3 ${{ github.workspace }}/selfdrive/ui/tests/diff/diff.py \ + "${{ github.workspace }}/pr_ui/${video}_master.mp4" \ + "${{ github.workspace }}/pr_ui/${video}_proposed.mp4" \ + "${diff_name}.html" --basedir "$baseurl" --no-open || diff_exit_code=$? + + cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${{ github.workspace }}/pr_ui/" + cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.mp4" "${{ github.workspace }}/pr_ui/" + + REPORT_URL="https://commaai.github.io/ci-artifacts/${diff_name}_pr_${{ github.event.number }}.html" + if [ $diff_exit_code -eq 0 ]; then + COMMENT+="**${name}**: Videos are identical! [View Diff Report]($REPORT_URL)"$'\n' + else + COMMENT+="**${name}**: ⚠️ Videos differ! [View Diff Report]($REPORT_URL)"$'\n' + fi + done + + { + echo "COMMENT<> "$GITHUB_OUTPUT" + + - name: Saving proposed ui + if: github.event_name == 'pull_request' + working-directory: ${{ github.workspace }}/master_mici + run: | + git config user.name "GitHub Actions Bot" + git config user.email "<>" + git checkout --orphan=${{ env.BRANCH_NAME }} + git rm -rf * + mv ${{ github.workspace }}/pr_ui/* . + git add . + git commit -m "ui videos for PR #${{ github.event.number }}" + git push origin ${{ env.BRANCH_NAME }} --force + + # Append diff reports to report files branch + git fetch origin ${{ env.REPORT_FILES_BRANCH_NAME }} + git checkout ${{ env.REPORT_FILES_BRANCH_NAME }} + for variant in $VARIANTS; do + IFS=':' read -r name _ _ <<< "$variant" + diff_name="${name}_diff" + cp "${{ github.workspace }}/selfdrive/ui/tests/diff/report/${diff_name}.html" "${diff_name}_pr_${{ github.event.number }}.html" + git add "${diff_name}_pr_${{ github.event.number }}.html" + done + git commit -m "ui diff reports for PR #${{ github.event.number }}" || echo "No changes to commit" + git push origin ${{ env.REPORT_FILES_BRANCH_NAME }} + + - name: Comment on PR + if: github.event_name == 'pull_request' + uses: thollander/actions-comment-pull-request@v2 + with: + message: | + + ## UI Preview + ${{ steps.find_diff.outputs.COMMENT }} + comment_tag: run_id_ui_preview + pr_number: ${{ github.event.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 136574fbcbb2ddcdd4aab3b97c98fbafceab5e5a Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:47:20 -0600 Subject: [PATCH 217/685] ui replay: run with no window (#37229) run headless --- .github/workflows/tests.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 03e818f4224..d892507319c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -274,8 +274,8 @@ jobs: run: > ${{ env.RUN }} "PYTHONWARNINGS=ignore && source selfdrive/test/setup_xvfb.sh && - WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py && - WINDOWED=1 python3 selfdrive/ui/tests/diff/replay.py --big" + python3 selfdrive/ui/tests/diff/replay.py && + python3 selfdrive/ui/tests/diff/replay.py --big" - name: Upload UI Report uses: actions/upload-artifact@v6 with: From 422885dce67732ccc847baef2917e81a0e01f2bd Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:55:46 -0600 Subject: [PATCH 218/685] ui replay: cleanup and fix workflow todos (#37230) * fix: update pull request trigger and clean up workflow paths * fix other event names --- .github/workflows/ui_preview.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ui_preview.yaml b/.github/workflows/ui_preview.yaml index 3663257daf4..72ced498522 100644 --- a/.github/workflows/ui_preview.yaml +++ b/.github/workflows/ui_preview.yaml @@ -3,15 +3,14 @@ on: push: branches: - master - pull_request: # TODO: change back to pull_request_target before merging + pull_request_target: types: [assigned, opened, synchronize, reopened, edited] branches: - 'master' - paths: # TODO: remove workflow path before merging + paths: - 'selfdrive/assets/**' - 'selfdrive/ui/**' - 'system/ui/**' - - '.github/workflows/ui_preview.yaml' workflow_dispatch: env: @@ -101,7 +100,7 @@ jobs: uses: AnimMouse/setup-ffmpeg@ae28d57dabbb148eff63170b6bf7f2b60062cbae - name: Finding diffs - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request_target' id: find_diff run: | export PYTHONPATH=${{ github.workspace }} @@ -139,7 +138,7 @@ jobs: } >> "$GITHUB_OUTPUT" - name: Saving proposed ui - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request_target' working-directory: ${{ github.workspace }}/master_mici run: | git config user.name "GitHub Actions Bot" @@ -164,7 +163,7 @@ jobs: git push origin ${{ env.REPORT_FILES_BRANCH_NAME }} - name: Comment on PR - if: github.event_name == 'pull_request' + if: github.event_name == 'pull_request_target' uses: thollander/actions-comment-pull-request@v2 with: message: | From 5d8e54ae3ed9444968152edc76355984131ab786 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:17:46 -0800 Subject: [PATCH 219/685] [bot] Update Python packages (#37228) * Update Python packages * revert for now --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- opendbc_repo | 2 +- panda | 2 +- uv.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 245cb1f2056..647441e42db 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 245cb1f2056071a7625e2ad7e4515f57784515bd +Subproject commit 647441e42db5735e249344449ad857eeeeff7224 diff --git a/panda b/panda index 24fe11466de..b1191df619d 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 24fe11466de37a1a761c16e35353ab39602e03e0 +Subproject commit b1191df619dbc201b9444634a29e2c016fee6619 diff --git a/uv.lock b/uv.lock index 51e71ce6457..bd4632942ac 100644 --- a/uv.lock +++ b/uv.lock @@ -1024,11 +1024,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.1" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/d5/763666321efaded11112de8b7a7f2273dd8d1e205168e73c334e54b0ab9a/platformdirs-4.9.1.tar.gz", hash = "sha256:f310f16e89c4e29117805d8328f7c10876eeff36c94eac879532812110f7d39f", size = 28392, upload-time = "2026-02-14T21:02:44.973Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/77/e8c95e95f1d4cdd88c90a96e31980df7e709e51059fac150046ad67fac63/platformdirs-4.9.1-py3-none-any.whl", hash = "sha256:61d8b967d34791c162d30d60737369cbbd77debad5b981c4bfda1842e71e0d66", size = 21307, upload-time = "2026-02-14T21:02:43.492Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] @@ -1487,15 +1487,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.52.0" +version = "2.53.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/59/eb/1b497650eb564701f9a7b8a95c51b2abe9347ed2c0b290ba78f027ebe4ea/sentry_sdk-2.52.0.tar.gz", hash = "sha256:fa0bec872cfec0302970b2996825723d67390cdd5f0229fb9efed93bd5384899", size = 410273, upload-time = "2026-02-04T15:03:54.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/06/66c8b705179bc54087845f28fd1b72f83751b6e9a195628e2e9af9926505/sentry_sdk-2.53.0.tar.gz", hash = "sha256:6520ef2c4acd823f28efc55e43eb6ce2e6d9f954a95a3aa96b6fd14871e92b77", size = 412369, upload-time = "2026-02-16T11:11:14.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ca/63/2c6daf59d86b1c30600bff679d039f57fd1932af82c43c0bde1cbc55e8d4/sentry_sdk-2.52.0-py2.py3-none-any.whl", hash = "sha256:931c8f86169fc6f2752cb5c4e6480f0d516112e78750c312e081ababecbaf2ed", size = 435547, upload-time = "2026-02-04T15:03:51.567Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/2fdf854bc3b9c7f55219678f812600a20a138af2dd847d99004994eada8f/sentry_sdk-2.53.0-py2.py3-none-any.whl", hash = "sha256:46e1ed8d84355ae54406c924f6b290c3d61f4048625989a723fd622aab838899", size = 437908, upload-time = "2026-02-16T11:11:13.227Z" }, ] [[package]] From 7fd131e01c0831a1c1789d3a0ca30e20028827e5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 16 Feb 2026 11:00:12 -0800 Subject: [PATCH 220/685] mem_usage.py: switch to our tabulate --- selfdrive/debug/mem_usage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/debug/mem_usage.py b/selfdrive/debug/mem_usage.py index 9bed566e19b..3451bfc3d61 100755 --- a/selfdrive/debug/mem_usage.py +++ b/selfdrive/debug/mem_usage.py @@ -4,8 +4,8 @@ from collections import defaultdict import numpy as np -from tabulate import tabulate +from openpilot.common.utils import tabulate from openpilot.tools.lib.logreader import LogReader DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" From 3661a014892ca8397c3e179ca520f5ffea749e16 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:05:43 -0600 Subject: [PATCH 221/685] ui diff: compare frame hashes instead of temp files (#37154) * refactor: streamline frame comparison by using frame hashes instead of extracting frames * add vsync Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- selfdrive/ui/tests/diff/diff.py | 72 +++++++++++++++------------------ 1 file changed, 32 insertions(+), 40 deletions(-) diff --git a/selfdrive/ui/tests/diff/diff.py b/selfdrive/ui/tests/diff/diff.py index bde6d44238c..f2b88a3cad2 100755 --- a/selfdrive/ui/tests/diff/diff.py +++ b/selfdrive/ui/tests/diff/diff.py @@ -2,7 +2,6 @@ import os import sys import subprocess -import tempfile import webbrowser import argparse from pathlib import Path @@ -11,17 +10,18 @@ DIFF_OUT_DIR = Path(BASEDIR) / "selfdrive" / "ui" / "tests" / "diff" / "report" -def extract_frames(video_path, output_dir): - output_pattern = str(output_dir / "frame_%04d.png") - cmd = ['ffmpeg', '-i', video_path, '-vsync', '0', output_pattern, '-y'] - subprocess.run(cmd, capture_output=True, check=True) - frames = sorted(output_dir.glob("frame_*.png")) - return frames - - -def compare_frames(frame1_path, frame2_path): - result = subprocess.run(['cmp', '-s', frame1_path, frame2_path]) - return result.returncode == 0 +def extract_framehashes(video_path): + cmd = ['ffmpeg', '-i', video_path, '-map', '0:v:0', '-vsync', '0', '-f', 'framehash', '-hash', 'md5', '-'] + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + hashes = [] + for line in result.stdout.splitlines(): + if not line or line.startswith('#'): + continue + parts = line.split(',') + if len(parts) < 4: + continue + hashes.append(parts[-1].strip()) + return hashes def create_diff_video(video1, video2, output_path): @@ -32,34 +32,26 @@ def create_diff_video(video1, video2, output_path): def find_differences(video1, video2): - with tempfile.TemporaryDirectory() as tmpdir: - tmpdir = Path(tmpdir) - - print(f"Extracting frames from {video1}...") - frames1_dir = tmpdir / "frames1" - frames1_dir.mkdir() - frames1 = extract_frames(video1, frames1_dir) - - print(f"Extracting frames from {video2}...") - frames2_dir = tmpdir / "frames2" - frames2_dir.mkdir() - frames2 = extract_frames(video2, frames2_dir) - - if len(frames1) != len(frames2): - print(f"WARNING: Frame count mismatch: {len(frames1)} vs {len(frames2)}") - min_frames = min(len(frames1), len(frames2)) - frames1 = frames1[:min_frames] - frames2 = frames2[:min_frames] - - print(f"Comparing {len(frames1)} frames...") - different_frames = [] - - for i, (f1, f2) in enumerate(zip(frames1, frames2, strict=False)): - is_different = not compare_frames(f1, f2) - if is_different: - different_frames.append(i) - - return different_frames, len(frames1) + print(f"Hashing frames from {video1}...") + hashes1 = extract_framehashes(video1) + + print(f"Hashing frames from {video2}...") + hashes2 = extract_framehashes(video2) + + if len(hashes1) != len(hashes2): + print(f"WARNING: Frame count mismatch: {len(hashes1)} vs {len(hashes2)}") + min_frames = min(len(hashes1), len(hashes2)) + hashes1 = hashes1[:min_frames] + hashes2 = hashes2[:min_frames] + + print(f"Comparing {len(hashes1)} frames...") + different_frames = [] + + for i, (h1, h2) in enumerate(zip(hashes1, hashes2, strict=False)): + if h1 != h2: + different_frames.append(i) + + return different_frames, len(hashes1) def generate_html_report(video1, video2, basedir, different_frames, total_frames, diff_video_name): From d984fb1bae75dacd503e7c1226cc606424107532 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:32:37 -0600 Subject: [PATCH 222/685] ui diff replay: better display replays of different lengths (#37116) * refactor: improve video synchronization logic in HTML report generation * feat: include description of which video is longer in report; refactor stuff and add types * refactor: simplify HTML report generation and remove extra formatting * reduce diff * fix video name * reduce diff * reduce diff * fix * parentheses * fix I guess --- selfdrive/ui/tests/diff/diff.py | 115 ++++++++++++++++---------------- 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/selfdrive/ui/tests/diff/diff.py b/selfdrive/ui/tests/diff/diff.py index f2b88a3cad2..42efa381b23 100755 --- a/selfdrive/ui/tests/diff/diff.py +++ b/selfdrive/ui/tests/diff/diff.py @@ -31,19 +31,13 @@ def create_diff_video(video1, video2, output_path): subprocess.run(cmd, capture_output=True, check=True) -def find_differences(video1, video2): +def find_differences(video1, video2) -> tuple[list[int], tuple[int, int]]: print(f"Hashing frames from {video1}...") hashes1 = extract_framehashes(video1) print(f"Hashing frames from {video2}...") hashes2 = extract_framehashes(video2) - if len(hashes1) != len(hashes2): - print(f"WARNING: Frame count mismatch: {len(hashes1)} vs {len(hashes2)}") - min_frames = min(len(hashes1), len(hashes2)) - hashes1 = hashes1[:min_frames] - hashes2 = hashes2[:min_frames] - print(f"Comparing {len(hashes1)} frames...") different_frames = [] @@ -51,10 +45,10 @@ def find_differences(video1, video2): if h1 != h2: different_frames.append(i) - return different_frames, len(hashes1) + return different_frames, (len(hashes1), len(hashes2)) -def generate_html_report(video1, video2, basedir, different_frames, total_frames, diff_video_name): +def generate_html_report(videos: tuple[str, str], basedir: str, different_frames: list[int], frame_counts: tuple[int, int], diff_video_name): chunks = [] if different_frames: current_chunk = [different_frames[0]] @@ -66,66 +60,70 @@ def generate_html_report(video1, video2, basedir, different_frames, total_frames current_chunk = [different_frames[i]] chunks.append(current_chunk) + total_frames = max(frame_counts) + frame_delta = frame_counts[1] - frame_counts[0] + different_total = len(different_frames) + abs(frame_delta) + result_text = ( f"✅ Videos are identical! ({total_frames} frames)" - if len(different_frames) == 0 - else f"❌ Found {len(different_frames)} different frames out of {total_frames} total ({(len(different_frames) / total_frames * 100):.1f}%)" + if different_total == 0 + else f"❌ Found {different_total} different frames out of {total_frames} total ({different_total / total_frames * 100:.1f}%)." + + (f" Video {'2' if frame_delta > 0 else '1'} is longer by {abs(frame_delta)} frames." if frame_delta != 0 else "") ) - html = f"""

UI Diff

- - + def render_video_cell(video_id: str, title: str, path: str, is_diff=False): + return f""" - - +""" + + html = f"""

UI Diff

+
-

Video 1

- -
-

Video 2

- -
-

Pixel Diff

-
+ +{render_video_cell("video1", "Video 1", videos[0])} +{render_video_cell("video2", "Video 2", videos[1])} +{render_video_cell("diffVideo", "Pixel Diff", diff_video_name, is_diff=True)}

@@ -162,14 +160,14 @@ def main(): diff_video_path = str(DIFF_OUT_DIR / diff_video_name) create_diff_video(args.video1, args.video2, diff_video_path) - different_frames, total_frames = find_differences(args.video1, args.video2) + different_frames, frame_counts = find_differences(args.video1, args.video2) if different_frames is None: sys.exit(1) print() print("Generating HTML report...") - html = generate_html_report(args.video1, args.video2, args.basedir, different_frames, total_frames, diff_video_name) + html = generate_html_report((args.video1, args.video2), args.basedir, different_frames, frame_counts, diff_video_name) with open(DIFF_OUT_DIR / args.output, 'w') as f: f.write(html) @@ -179,7 +177,8 @@ def main(): print(f"Opening {args.output} in browser...") webbrowser.open(f'file://{os.path.abspath(DIFF_OUT_DIR / args.output)}') - return 0 if len(different_frames) == 0 else 1 + extra_frames = abs(frame_counts[0] - frame_counts[1]) + return 0 if (len(different_frames) + extra_frames) == 0 else 1 if __name__ == "__main__": From 037e6e749aacff472aebc1fbe71c276eafe97f03 Mon Sep 17 00:00:00 2001 From: Ahmed Harmouche Date: Tue, 17 Feb 2026 18:19:41 +0100 Subject: [PATCH 223/685] cabana: fix crash when zmq address is used (#37222) * Fix zmq support in cabana * Refactor to launch bridge, remove socketadapter * bridge_path should be camel_case --- tools/cabana/streams/devicestream.cc | 31 ++++++++++++++++++++++++++-- tools/cabana/streams/devicestream.h | 5 +++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/tools/cabana/streams/devicestream.cc b/tools/cabana/streams/devicestream.cc index 462dd7a3614..20eaa70cd6b 100644 --- a/tools/cabana/streams/devicestream.cc +++ b/tools/cabana/streams/devicestream.cc @@ -6,7 +6,9 @@ #include "cereal/services.h" #include +#include #include +#include #include #include #include @@ -17,12 +19,37 @@ DeviceStream::DeviceStream(QObject *parent, QString address) : zmq_address(address), LiveStream(parent) { } +DeviceStream::~DeviceStream() { + if (!bridge_process) + return; + + bridge_process->terminate(); + if (!bridge_process->waitForFinished(3000)) { + bridge_process->kill(); + bridge_process->waitForFinished(); + } +} + +void DeviceStream::start() { + if (!zmq_address.isEmpty()) { + bridge_process = new QProcess(this); + QString bridge_path = QCoreApplication::applicationDirPath() + "/../../cereal/messaging/bridge"; + bridge_process->start(QFileInfo(bridge_path).absoluteFilePath(), QStringList { zmq_address, "/\"can/\"" }); + + if (!bridge_process->waitForStarted()) { + QMessageBox::warning(nullptr, tr("Error"), tr("Failed to start bridge: %1").arg(bridge_process->errorString())); + return; + } + } + + LiveStream::start(); +} + void DeviceStream::streamThread() { zmq_address.isEmpty() ? unsetenv("ZMQ") : setenv("ZMQ", "1", 1); std::unique_ptr context(Context::create()); - std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString(); - std::unique_ptr sock(SubSocket::create(context.get(), "can", address, false, true, services.at("can").queue_size)); + std::unique_ptr sock(SubSocket::create(context.get(), "can", "127.0.0.1", false, true, services.at("can").queue_size)); assert(sock != NULL); // run as fast as messages come in while (!QThread::currentThread()->isInterruptionRequested()) { diff --git a/tools/cabana/streams/devicestream.h b/tools/cabana/streams/devicestream.h index 3bdf2249986..6beb300d7ad 100644 --- a/tools/cabana/streams/devicestream.h +++ b/tools/cabana/streams/devicestream.h @@ -2,16 +2,21 @@ #include "tools/cabana/streams/livestream.h" +#include + class DeviceStream : public LiveStream { Q_OBJECT public: DeviceStream(QObject *parent, QString address = {}); + ~DeviceStream(); inline QString routeName() const override { return QString("Live Streaming From %1").arg(zmq_address.isEmpty() ? "127.0.0.1" : zmq_address); } protected: + void start() override; void streamThread() override; + QProcess *bridge_process = nullptr; const QString zmq_address; }; From 43d162e8fbb8ab84b233e541e68113e9499120ae Mon Sep 17 00:00:00 2001 From: felsager <76905857+felsager@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:49:26 -0800 Subject: [PATCH 224/685] mpc_longitudinal_tuning_report: use enum for axis (#37231) --- .../maneuver_helpers.py | 18 +++++ .../mpc_longitudinal_tuning_report.py | 75 +++++++------------ 2 files changed, 47 insertions(+), 46 deletions(-) create mode 100644 tools/longitudinal_maneuvers/maneuver_helpers.py diff --git a/tools/longitudinal_maneuvers/maneuver_helpers.py b/tools/longitudinal_maneuvers/maneuver_helpers.py new file mode 100644 index 00000000000..9fc65fb9e67 --- /dev/null +++ b/tools/longitudinal_maneuvers/maneuver_helpers.py @@ -0,0 +1,18 @@ +from enum import IntEnum + +class Axis(IntEnum): + TIME = 0 + EGO_POSITION = 1 + LEAD_DISTANCE= 2 + EGO_V = 3 + LEAD_V = 4 + EGO_A = 5 + D_REL = 6 + +axis_labels = {Axis.TIME: 'Time (s)', + Axis.EGO_POSITION: 'Ego position (m)', + Axis.LEAD_DISTANCE: 'Lead absolute position (m)', + Axis.EGO_V: 'Ego Velocity (m/s)', + Axis.LEAD_V: 'Lead Velocity (m/s)', + Axis.EGO_A: 'Ego acceleration (m/s^2)', + Axis.D_REL: 'Lead distance (m)'} diff --git a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py index 7623f669cdb..ae3fee7355a 100644 --- a/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py +++ b/tools/longitudinal_maneuvers/mpc_longitudinal_tuning_report.py @@ -5,29 +5,16 @@ import matplotlib.pyplot as plt from openpilot.common.realtime import DT_MDL from openpilot.selfdrive.controls.tests.test_following_distance import desired_follow_distance +from openpilot.tools.longitudinal_maneuvers.maneuver_helpers import Axis, axis_labels from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver -TIME = 0 -LEAD_DISTANCE= 2 -EGO_V = 3 -EGO_A = 5 -D_REL = 6 - -axis_labels = ['Time (s)', - 'Ego position (m)', - 'Lead absolute position (m)', - 'Ego Velocity (m/s)', - 'Lead Velocity (m/s)', - 'Ego acceleration (m/s^2)', - 'Lead distance (m)' - ] def get_html_from_results(results, labels, AXIS): fig, ax = plt.subplots(figsize=(16, 8)) - for idx, speed in enumerate(list(results.keys())): - ax.plot(results[speed][:, TIME], results[speed][:, AXIS], label=labels[idx]) + for idx, key in enumerate(results.keys()): + ax.plot(results[key][:, Axis.TIME], results[key][:, AXIS], label=labels[idx]) - ax.set_xlabel('Time (s)') + ax.set_xlabel(axis_labels[Axis.TIME]) ax.set_ylabel(axis_labels[AXIS]) ax.legend(bbox_to_anchor=(1.02, 1), loc='upper left', borderaxespad=0) ax.grid(True, linestyle='--', alpha=0.7) @@ -60,8 +47,8 @@ def generate_mpc_tuning_report(): labels.append(f'{lead_accel} m/s^2 lead acceleration') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -78,12 +65,11 @@ def generate_mpc_tuning_report(): breakpoints=[0., 30.], ) valid, results[speed] = man.evaluate() - results[speed][:,2] = results[speed][:,2] - results[speed][:,1] labels.append(f'{speed} m/s approach speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_A)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -104,9 +90,9 @@ def generate_mpc_tuning_report(): labels.append(f'{oscil} m/s oscillation size') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, D_REL)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -128,12 +114,12 @@ def generate_mpc_tuning_report(): breakpoints=bps, ) valid, results[oscil] = man.evaluate() - labels.append(f'{oscil} m/s oscilliation size') + labels.append(f'{oscil} m/s oscillation size') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, D_REL)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -150,12 +136,11 @@ def generate_mpc_tuning_report(): breakpoints=[0.], ) valid, results[distance] = man.evaluate() - results[distance][:,2] = results[distance][:,2] - results[distance][:,1] labels.append(f'{distance} m initial distance') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -172,12 +157,11 @@ def generate_mpc_tuning_report(): breakpoints=[0.], ) valid, results[distance] = man.evaluate() - results[distance][:,2] = results[distance][:,2] - results[distance][:,1] labels.append(f'{distance} m initial distance') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -195,12 +179,11 @@ def generate_mpc_tuning_report(): breakpoints=[0., 5., 5 + stop_time], ) valid, results[stop_time] = man.evaluate() - results[stop_time][:,2] = results[stop_time][:,2] - results[stop_time][:,1] labels.append(f'{stop_time} seconds stop time') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_A)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -222,8 +205,8 @@ def generate_mpc_tuning_report(): labels.append(f'{speed} m/s speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_A)) - htmls.append(get_html_from_results(results, labels, D_REL)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.D_REL)) results = {} @@ -244,8 +227,8 @@ def generate_mpc_tuning_report(): labels.append(f'{speed} m/s speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -267,8 +250,8 @@ def generate_mpc_tuning_report(): labels.append(f'{speed} m/s speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) results = {} @@ -290,8 +273,8 @@ def generate_mpc_tuning_report(): labels.append(f'{speed} m/s speed') htmls.append(markdown.markdown('# ' + name)) - htmls.append(get_html_from_results(results, labels, EGO_V)) - htmls.append(get_html_from_results(results, labels, EGO_A)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_V)) + htmls.append(get_html_from_results(results, labels, Axis.EGO_A)) return htmls From 0a98ee9e4f2ff45e5129db38b40c9c5ae69b3395 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 14:56:08 -0800 Subject: [PATCH 225/685] WifiUii: rm separate connecting status (#37233) rm connecting --- .../mici/layouts/settings/network/wifi_ui.py | 19 +------------------ system/ui/lib/wifi_manager.py | 18 +++++++++--------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index b27e03ed14a..679372404a6 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -343,20 +343,16 @@ def __init__(self, wifi_manager: WifiManager, back_callback: Callable): self.set_back_callback(back_callback) self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, self._open_network_manage_page) - self._network_info_page.set_connecting(lambda: self._connecting) + self._network_info_page.set_connecting(lambda: wifi_manager.connecting_to_ssid) self._loading_animation = LoadingAnimation() self._wifi_manager = wifi_manager - self._connecting: str | None = None self._networks: dict[str, Network] = {} self._wifi_manager.add_callbacks( need_auth=self._on_need_auth, - activated=self._on_activated, - forgotten=self._on_forgotten, networks_updated=self._on_network_updated, - disconnected=self._on_disconnected, ) def show_event(self): @@ -402,7 +398,6 @@ def _update_buttons(self): def _connect_with_password(self, ssid: str, password: str): if password: - self._connecting = ssid self._wifi_manager.connect_to_network(ssid, password) self._update_buttons() @@ -420,11 +415,9 @@ def _connect_to_network(self, ssid: str): return if network.is_saved: - self._connecting = network.ssid self._wifi_manager.activate_connection(network.ssid) self._update_buttons() elif network.security_type == SecurityType.OPEN: - self._connecting = network.ssid self._wifi_manager.connect_to_network(network.ssid, "") self._update_buttons() else: @@ -443,16 +436,6 @@ def on_close(result=None): gui_app.set_modal_overlay_tick(self._wifi_manager.process_callbacks) gui_app.set_modal_overlay(dlg, on_close) - def _on_activated(self): - self._connecting = None - - def _on_forgotten(self, ssid): - if self._connecting == ssid: - self._connecting = None - - def _on_disconnected(self): - self._connecting = None - def _render(self, _): super()._render(_) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 2a4a9ab7111..e1343137ad4 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -166,8 +166,8 @@ def __init__(self): # State self._connections: dict[str, str] = {} # ssid -> connection path, updated via NM signals - self._connecting_to_ssid: str = "" - self._prev_connecting_to_ssid: str = "" + self._connecting_to_ssid: str | None = None + self._prev_connecting_to_ssid: str | None = None self._ipv4_address: str = "" self._current_network_metered: MeteredType = MeteredType.UNKNOWN self._tethering_password: str = "" @@ -236,7 +236,7 @@ def current_network_metered(self) -> MeteredType: return self._current_network_metered @property - def connecting_to_ssid(self) -> str: + def connecting_to_ssid(self) -> str | None: return self._connecting_to_ssid @property @@ -333,21 +333,21 @@ def _monitor_state(self): if failed_ssid: self._enqueue_callbacks(self._need_auth, failed_ssid) self.forget_connection(failed_ssid, block=True) - self._prev_connecting_to_ssid = "" + self._prev_connecting_to_ssid = None if self._connecting_to_ssid == failed_ssid: - self._connecting_to_ssid = "" + self._connecting_to_ssid = None elif new_state == NMDeviceState.ACTIVATED: if len(self._activated): self._update_networks() self._enqueue_callbacks(self._activated) - self._prev_connecting_to_ssid = "" - self._connecting_to_ssid = "" + self._prev_connecting_to_ssid = None + self._connecting_to_ssid = None elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: self._enqueue_callbacks(self._forgotten, self._connecting_to_ssid) - self._prev_connecting_to_ssid = "" - self._connecting_to_ssid = "" + self._prev_connecting_to_ssid = None + self._connecting_to_ssid = None def _network_scanner(self): while not self._exit: From 7dc56dc064907fc2de4bcd3bd4bae6292c44d084 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 15:01:07 -0800 Subject: [PATCH 226/685] draw black bg behind BigButton --- selfdrive/ui/mici/widgets/button.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 101f3bb56e0..3fb5bce9643 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -220,6 +220,10 @@ def _render(self, _): btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 + # draw black background since images are transparent + scaled_rect = rl.Rectangle(btn_x, btn_y, self._rect.width * scale, self._rect.height * scale) + rl.draw_rectangle_rounded(scaled_rect, 0.4, 7, rl.Color(0, 0, 0, int(255 * 0.5))) + self._draw_content(btn_y) rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) From e527b463a5163ebbbd95e3f2ca8c43f6ebe36412 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 17 Feb 2026 15:02:06 -0800 Subject: [PATCH 227/685] Revert "Drop support for Intel macOS (#37215)" (#37234) This reverts commit eea07462fad217a2e98decf7392818dbb0f22478. --- SConstruct | 17 ++++++++-- scripts/platform.sh | 51 ---------------------------- third_party/acados/build.sh | 6 ++-- third_party/libyuv/build.sh | 10 ++++-- third_party/raylib/build.sh | 8 +++-- tools/install_python_dependencies.sh | 2 +- tools/mac_setup.sh | 1 - tools/op.sh | 28 ++++++++++++++- 8 files changed, 59 insertions(+), 64 deletions(-) delete mode 100755 scripts/platform.sh diff --git a/SConstruct b/SConstruct index 05885c0b5d2..4f04be624cf 100644 --- a/SConstruct +++ b/SConstruct @@ -2,6 +2,7 @@ import os import subprocess import sys import sysconfig +import platform import shlex import numpy as np @@ -23,8 +24,19 @@ AddOption('--minimal', default=os.path.exists(File('#.gitattributes').abspath), # minimal by default on release branch (where there's no LFS) help='the minimum build to run openpilot. no tests, tools, etc.') -# Detect platform (see scripts/platform.sh) -arch = subprocess.check_output(["bash", "-c", "source scripts/platform.sh >&2 && echo $OPENPILOT_ARCH"], encoding='utf8', stderr=subprocess.PIPE).rstrip() +# Detect platform +arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() +if platform.system() == "Darwin": + arch = "Darwin" + brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() +elif arch == "aarch64" and os.path.isfile('/TICI'): + arch = "larch64" +assert arch in [ + "larch64", # linux tici arm64 + "aarch64", # linux pc arm64 + "x86_64", # linux pc x64 + "Darwin", # macOS arm64 (x86 not supported) +] env = Environment( ENV={ @@ -91,7 +103,6 @@ if arch == "larch64": env.Append(CCFLAGS=arch_flags) env.Append(CXXFLAGS=arch_flags) elif arch == "Darwin": - brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() env.Append(LIBPATH=[ f"{brew_prefix}/lib", f"{brew_prefix}/opt/openssl@3.0/lib", diff --git a/scripts/platform.sh b/scripts/platform.sh deleted file mode 100755 index 1cbd8aa6078..00000000000 --- a/scripts/platform.sh +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env bash -# -# Centralized platform and architecture detection for openpilot. -# Source this script to get OPENPILOT_ARCH set to one of: -# larch64 - linux tici arm64 -# aarch64 - linux pc arm64 -# x86_64 - linux pc x64 -# Darwin - macOS arm64 -# - -RED='\033[0;31m' -GREEN='\033[0;32m' -NC='\033[0m' - -OPENPILOT_ARCH=$(uname -m) - -# ── check OS and normalize arch ────────────────────────────── -if [ -f /TICI ]; then - # TICI runs AGNOS — no OS validation needed - OPENPILOT_ARCH="larch64" - -elif [[ "$OSTYPE" == "darwin"* ]]; then - if [[ "$OPENPILOT_ARCH" == "x86_64" ]]; then - echo -e " ↳ [${RED}✗${NC}] Intel-based Macs are not supported!" - echo " openpilot requires an Apple Silicon Mac (M1 or newer)." - exit 1 - fi - echo -e " ↳ [${GREEN}✔${NC}] macOS detected." - OPENPILOT_ARCH="Darwin" - -elif [[ "$OSTYPE" == "linux-gnu"* ]]; then - if [ -f "/etc/os-release" ]; then - source /etc/os-release - case "$VERSION_CODENAME" in - "jammy" | "kinetic" | "noble" | "focal") - echo -e " ↳ [${GREEN}✔${NC}] Ubuntu $VERSION_CODENAME detected." - ;; - *) - echo -e " ↳ [${RED}✗${NC}] Incompatible Ubuntu version $VERSION_CODENAME detected!" - exit 1 - ;; - esac - else - echo -e " ↳ [${RED}✗${NC}] No /etc/os-release on your system. Make sure you're running on Ubuntu, or similar!" - exit 1 - fi - -else - echo -e " ↳ [${RED}✗${NC}] OS type $OSTYPE not supported!" - exit 1 -fi diff --git a/third_party/acados/build.sh b/third_party/acados/build.sh index cca17435069..95b3913c4a8 100755 --- a/third_party/acados/build.sh +++ b/third_party/acados/build.sh @@ -6,11 +6,10 @@ export ZERO_AR_DATE=1 DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -source "$DIR/../../scripts/platform.sh" -ARCHNAME="$OPENPILOT_ARCH" - +ARCHNAME="x86_64" BLAS_TARGET="X64_AUTOMATIC" if [ -f /TICI ]; then + ARCHNAME="larch64" BLAS_TARGET="ARMV8A_ARM_CORTEX_A57" fi @@ -18,6 +17,7 @@ ACADOS_FLAGS="-DACADOS_WITH_QPOASES=ON -UBLASFEO_TARGET -DBLASFEO_TARGET=$BLAS_T if [[ "$OSTYPE" == "darwin"* ]]; then ACADOS_FLAGS="$ACADOS_FLAGS -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_MACOSX_RPATH=1" + ARCHNAME="Darwin" fi if [ ! -d acados_repo/ ]; then diff --git a/third_party/libyuv/build.sh b/third_party/libyuv/build.sh index 0e4e10133b1..11f88ab46cf 100755 --- a/third_party/libyuv/build.sh +++ b/third_party/libyuv/build.sh @@ -6,8 +6,14 @@ export ZERO_AR_DATE=1 DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" -source "$DIR/../../scripts/platform.sh" -ARCHNAME="$OPENPILOT_ARCH" +ARCHNAME=$(uname -m) +if [ -f /TICI ]; then + ARCHNAME="larch64" +fi + +if [[ "$OSTYPE" == "darwin"* ]]; then + ARCHNAME="Darwin" +fi cd $DIR if [ ! -d libyuv ]; then diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh index b8408cd88c7..d20f9d33af1 100755 --- a/third_party/raylib/build.sh +++ b/third_party/raylib/build.sh @@ -20,9 +20,9 @@ cd $DIR RAYLIB_PLATFORM="PLATFORM_DESKTOP" -source "$DIR/../../scripts/platform.sh" -ARCHNAME="$OPENPILOT_ARCH" +ARCHNAME=$(uname -m) if [ -f /TICI ]; then + ARCHNAME="larch64" RAYLIB_PLATFORM="PLATFORM_COMMA" elif [[ "$OSTYPE" == "linux"* ]]; then # required dependencies on Linux PC @@ -33,6 +33,10 @@ elif [[ "$OSTYPE" == "linux"* ]]; then libxrandr-dev fi +if [[ "$OSTYPE" == "darwin"* ]]; then + ARCHNAME="Darwin" +fi + INSTALL_DIR="$DIR/$ARCHNAME" rm -rf $INSTALL_DIR mkdir -p $INSTALL_DIR diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh index bf7accbbd53..4454845fcd0 100755 --- a/tools/install_python_dependencies.sh +++ b/tools/install_python_dependencies.sh @@ -23,7 +23,7 @@ echo "installing python packages..." uv sync --frozen --all-extras source .venv/bin/activate -if [[ "$OSTYPE" == "darwin"* ]]; then +if [[ "$(uname)" == 'Darwin' ]]; then touch "$ROOT"/.env echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> "$ROOT"/.env fi diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 7e126d4f57b..82748e96131 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -3,7 +3,6 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" ROOT="$(cd $DIR/../ && pwd)" -source $ROOT/scripts/platform.sh # homebrew update is slow export HOMEBREW_NO_AUTO_UPDATE=1 diff --git a/tools/op.sh b/tools/op.sh index afee4cfb7ee..8c41926e0ce 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -122,7 +122,33 @@ function op_check_git() { function op_check_os() { echo "Checking for compatible os version..." - source "$OPENPILOT_ROOT/scripts/platform.sh" + if [[ "$OSTYPE" == "linux-gnu"* ]]; then + + if [ -f "/etc/os-release" ]; then + source /etc/os-release + case "$VERSION_CODENAME" in + "jammy" | "kinetic" | "noble" | "focal") + echo -e " ↳ [${GREEN}✔${NC}] Ubuntu $VERSION_CODENAME detected." + ;; + * ) + echo -e " ↳ [${RED}✗${NC}] Incompatible Ubuntu version $VERSION_CODENAME detected!" + loge "ERROR_INCOMPATIBLE_UBUNTU" "$VERSION_CODENAME" + return 1 + ;; + esac + else + echo -e " ↳ [${RED}✗${NC}] No /etc/os-release on your system. Make sure you're running on Ubuntu, or similar!" + loge "ERROR_UNKNOWN_UBUNTU" + return 1 + fi + + elif [[ "$OSTYPE" == "darwin"* ]]; then + echo -e " ↳ [${GREEN}✔${NC}] macOS detected." + else + echo -e " ↳ [${RED}✗${NC}] OS type $OSTYPE not supported!" + loge "ERROR_UNKNOWN_OS" "$OSTYPE" + return 1 + fi } function op_check_python() { From 14f3f6dd1a3989a9d79721cbd37669d59e132383 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 15:02:20 -0800 Subject: [PATCH 228/685] WifiManager: fix forgotten callback signature --- system/ui/lib/wifi_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index e1343137ad4..42432f4bd9a 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -185,7 +185,7 @@ def __init__(self): # Callbacks self._need_auth: list[Callable[[str], None]] = [] self._activated: list[Callable[[], None]] = [] - self._forgotten: list[Callable[[str], None]] = [] + self._forgotten: list[Callable[[str | None], None]] = [] self._networks_updated: list[Callable[[list[Network]], None]] = [] self._disconnected: list[Callable[[], None]] = [] From 1f85860f7e8631892796c374319c61e3bb45a82a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 16:16:05 -0800 Subject: [PATCH 229/685] WifiManager: always update networks after activation --- system/ui/lib/wifi_manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 42432f4bd9a..fc3160e6419 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -338,8 +338,7 @@ def _monitor_state(self): self._connecting_to_ssid = None elif new_state == NMDeviceState.ACTIVATED: - if len(self._activated): - self._update_networks() + self._update_networks() self._enqueue_callbacks(self._activated) self._prev_connecting_to_ssid = None self._connecting_to_ssid = None From fd34659dc37924d404063723b6d2fbd75fa3ca7d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 16:25:44 -0800 Subject: [PATCH 230/685] NetworkManager: add more device states (#37235) * safe * missing states * add enum for nmdevicestatereason * rm for now * fix links --- system/ui/lib/networkmanager.py | 19 +++++++++++++++---- system/ui/lib/wifi_manager.py | 12 +++++------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/system/ui/lib/networkmanager.py b/system/ui/lib/networkmanager.py index 19d54e65160..f16271a5053 100644 --- a/system/ui/lib/networkmanager.py +++ b/system/ui/lib/networkmanager.py @@ -3,17 +3,31 @@ # NetworkManager device states class NMDeviceState(IntEnum): + # https://networkmanager.dev/docs/api/1.46/nm-dbus-types.html#NMDeviceState UNKNOWN = 0 + UNMANAGED = 10 + UNAVAILABLE = 20 DISCONNECTED = 30 PREPARE = 40 - STATE_CONFIG = 50 + CONFIG = 50 NEED_AUTH = 60 IP_CONFIG = 70 + IP_CHECK = 80 + SECONDARIES = 90 ACTIVATED = 100 DEACTIVATING = 110 FAILED = 120 +class NMDeviceStateReason(IntEnum): + # https://networkmanager.dev/docs/api/1.46/nm-dbus-types.html#NMDeviceStateReason + NONE = 0 + UNKNOWN = 1 + NO_SECRETS = 7 + SUPPLICANT_DISCONNECT = 8 + NEW_ACTIVATION = 60 + + # NetworkManager constants NM = "org.freedesktop.NetworkManager" NM_PATH = '/org/freedesktop/NetworkManager' @@ -30,9 +44,6 @@ class NMDeviceState(IntEnum): NM_DEVICE_TYPE_WIFI = 2 NM_DEVICE_TYPE_MODEM = 8 -NM_DEVICE_STATE_REASON_NO_SECRETS = 7 -NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT = 8 -NM_DEVICE_STATE_REASON_NEW_ACTIVATION = 60 # https://developer.gnome.org/NetworkManager/1.26/nm-dbus-types.html#NM80211ApFlags NM_802_11_AP_FLAGS_NONE = 0x0 diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index fc3160e6419..a0d684c789b 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -23,10 +23,8 @@ NM_802_11_AP_FLAGS_PRIVACY, NM_802_11_AP_FLAGS_WPS, NM_PATH, NM_IFACE, NM_ACCESS_POINT_IFACE, NM_SETTINGS_PATH, NM_SETTINGS_IFACE, NM_CONNECTION_IFACE, NM_DEVICE_IFACE, - NM_DEVICE_TYPE_WIFI, NM_DEVICE_TYPE_MODEM, NM_DEVICE_STATE_REASON_NO_SECRETS, - NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, - NM_DEVICE_STATE_REASON_NEW_ACTIVATION, NM_ACTIVE_CONNECTION_IFACE, - NM_IP4_CONFIG_IFACE, NM_PROPERTIES_IFACE, NMDeviceState) + NM_DEVICE_TYPE_WIFI, NM_DEVICE_TYPE_MODEM, NM_ACTIVE_CONNECTION_IFACE, + NM_IP4_CONFIG_IFACE, NM_PROPERTIES_IFACE, NMDeviceState, NMDeviceStateReason) try: from openpilot.common.params import Params @@ -327,8 +325,8 @@ def _monitor_state(self): # BAD PASSWORD - use prev if current has already moved on to a new connection # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT # - weak/gone network fails with FAILED+NO_SECRETS - if ((new_state == NMDeviceState.NEED_AUTH and change_reason == NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT) or - (new_state == NMDeviceState.FAILED and change_reason == NM_DEVICE_STATE_REASON_NO_SECRETS)): + if ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT) or + (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): failed_ssid = self._prev_connecting_to_ssid or self._connecting_to_ssid if failed_ssid: self._enqueue_callbacks(self._need_auth, failed_ssid) @@ -343,7 +341,7 @@ def _monitor_state(self): self._prev_connecting_to_ssid = None self._connecting_to_ssid = None - elif new_state == NMDeviceState.DISCONNECTED and change_reason != NM_DEVICE_STATE_REASON_NEW_ACTIVATION: + elif new_state == NMDeviceState.DISCONNECTED and change_reason != NMDeviceStateReason.NEW_ACTIVATION: self._enqueue_callbacks(self._forgotten, self._connecting_to_ssid) self._prev_connecting_to_ssid = None self._connecting_to_ssid = None From 4f407dabcd59a38618e2b20e732de43f4d2b6112 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Tue, 17 Feb 2026 19:36:01 -0500 Subject: [PATCH 231/685] ci: fix update translations by enable submodule checkout in repo maintenance (#37236) --- .github/workflows/repo-maintenance.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index ec361536d82..55d1c2052c0 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -16,6 +16,8 @@ jobs: if: github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v6 + with: + submodules: true - uses: ./.github/workflows/setup-with-retry - name: Update translations run: | From d6238c285a5c7be6bc7620154a2624c49f1ebb4b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 19:41:31 -0800 Subject: [PATCH 232/685] ui: disable tethering password while updating (#37240) * setting completed * add back * try * try * only pass * just tehteringk --- selfdrive/ui/mici/layouts/settings/network/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index bda619feef5..3854e998e92 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -36,6 +36,7 @@ def __init__(self, back_callback: Callable): # ******** Tethering ******** def tethering_toggle_callback(checked: bool): self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) self._network_metered_btn.set_enabled(False) self._wifi_manager.set_tethering_active(checked) @@ -43,6 +44,8 @@ def tethering_toggle_callback(checked: bool): def tethering_password_callback(password: str): if password: + self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) self._wifi_manager.set_tethering_password(password) def tethering_password_clicked(): @@ -155,6 +158,7 @@ def _on_network_updated(self, networks: list[Network]): tethering_active = self._wifi_manager.is_tethering_active() # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons self._tethering_toggle_btn.set_enabled(True) + self._tethering_password_btn.set_enabled(True) self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) self._tethering_toggle_btn.set_checked(tethering_active) From 028f5ca1f4bd741e32ea1c2cd439ebbb09afad14 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 19:52:37 -0800 Subject: [PATCH 233/685] WifiUi: fix flickering IP and network metered (#37242) fix flickering ip and network metered --- system/ui/lib/wifi_manager.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index a0d684c789b..6737946f4dd 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -705,8 +705,8 @@ def worker(): threading.Thread(target=worker, daemon=True).start() def _update_active_connection_info(self): - self._ipv4_address = "" - self._current_network_metered = MeteredType.UNKNOWN + ipv4_address = "" + metered = MeteredType.UNKNOWN for active_conn in self._get_active_connections(): conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) @@ -728,7 +728,7 @@ def _update_active_connection_info(self): for entry in address_data: if 'address' in entry: - self._ipv4_address = entry['address'][1] + ipv4_address = entry['address'][1] break # Metered status @@ -740,10 +740,13 @@ def _update_active_connection_info(self): metered_prop = settings['connection'].get('metered', ('i', 0))[1] if metered_prop == MeteredType.YES: - self._current_network_metered = MeteredType.YES + metered = MeteredType.YES elif metered_prop == MeteredType.NO: - self._current_network_metered = MeteredType.NO - return + metered = MeteredType.NO + break + + self._ipv4_address = ipv4_address + self._current_network_metered = metered def __del__(self): self.stop() From 735c2fb48e00cd63ce0b3c84e3a1a06ad9e3a801 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 21:24:38 -0800 Subject: [PATCH 234/685] WifiManager: active WiFi connection helper (#37244) * short circuit * rename * move some usages over * clean up * cmt --- system/ui/lib/wifi_manager.py | 106 ++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 6737946f4dd..d403f91f8dc 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -407,8 +407,27 @@ def _connection_removed(self, conn_path: str): self._connections = {ssid: path for ssid, path in self._connections.items() if path != conn_path} def _get_active_connections(self): + # Returns list of ActiveConnection return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] + def _get_active_wifi_connection(self) -> tuple[str | None, dict | None]: + # Returns first Connection settings path and ActiveConnection props from ActiveConnections with Type 802-11-wireless + for active_conn in self._get_active_connections(): + conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + reply = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()) + + if reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to get active connection properties for {active_conn}: {reply}") + continue + + props = reply.body[0] + + conn_path = props.get('Connection', ('o', '/'))[1] + if props.get('Type', ('s', ''))[1] == '802-11-wireless' and conn_path != '/': + return conn_path, props + + return None, None + def _get_connection_settings(self, conn_path: str) -> dict: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'GetSettings')) @@ -530,8 +549,8 @@ def worker(): threading.Thread(target=worker, daemon=True).start() def _deactivate_connection(self, ssid: str): - for conn_path in self._get_active_connections(): - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) + for active_conn in self._get_active_connections(): + conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) specific_obj_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('SpecificObject')).body[0][1] if specific_obj_path != "/": @@ -539,7 +558,7 @@ def _deactivate_connection(self, ssid: str): ap_ssid = bytes(self._router_main.send_and_get_reply(Properties(ap_addr).get('Ssid')).body[0][1]).decode("utf-8", "replace") if ap_ssid == ssid: - self._router_main.send_and_get_reply(new_method_call(self._nm, 'DeactivateConnection', 'o', (conn_path,))) + self._router_main.send_and_get_reply(new_method_call(self._nm, 'DeactivateConnection', 'o', (active_conn,))) return def is_tethering_active(self) -> bool: @@ -614,28 +633,26 @@ def worker(): def set_current_network_metered(self, metered: MeteredType): def worker(): - for active_conn in self._get_active_connections(): - conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - props = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()).body[0] + if self.is_tethering_active(): + return - if props.get('Type', ('s', ''))[1] == '802-11-wireless' and not self.is_tethering_active(): - conn_path = props.get('Connection', ('o', '/'))[1] - if conn_path == "/": - continue + conn_path, _ = self._get_active_wifi_connection() + if conn_path is None: + cloudlog.warning('No active WiFi connection found') + return - settings = self._get_connection_settings(conn_path) + settings = self._get_connection_settings(conn_path) - if len(settings) == 0: - cloudlog.warning(f'Failed to get connection settings for {conn_path}') - return + if len(settings) == 0: + cloudlog.warning(f'Failed to get connection settings for {conn_path}') + return - settings['connection']['metered'] = ('i', int(metered)) + settings['connection']['metered'] = ('i', int(metered)) - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) - reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Update', 'a{sa{sv}}', (settings,))) - if reply.header.message_type == MessageType.error: - cloudlog.warning(f'Failed to update tethering settings: {reply}') - return + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + reply = self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Update', 'a{sa{sv}}', (settings,))) + if reply.header.message_type == MessageType.error: + cloudlog.warning(f'Failed to update metered settings: {reply}') threading.Thread(target=worker, daemon=True).start() @@ -708,42 +725,31 @@ def _update_active_connection_info(self): ipv4_address = "" metered = MeteredType.UNKNOWN - for active_conn in self._get_active_connections(): - conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - reply = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()) - - if reply.header.message_type == MessageType.error: - cloudlog.warning(f"Failed to get active connection properties for {active_conn}: {reply}") - continue - - props = reply.body[0] + conn_path, props = self._get_active_wifi_connection() - if props.get('Type', ('s', ''))[1] == '802-11-wireless': - # IPv4 address - ip4config_path = props.get('Ip4Config', ('o', '/'))[1] + if conn_path is not None and props is not None: + # IPv4 address + ip4config_path = props.get('Ip4Config', ('o', '/'))[1] - if ip4config_path != "/": - ip4config_addr = DBusAddress(ip4config_path, bus_name=NM, interface=NM_IP4_CONFIG_IFACE) - address_data = self._router_main.send_and_get_reply(Properties(ip4config_addr).get('AddressData')).body[0][1] + if ip4config_path != "/": + ip4config_addr = DBusAddress(ip4config_path, bus_name=NM, interface=NM_IP4_CONFIG_IFACE) + address_data = self._router_main.send_and_get_reply(Properties(ip4config_addr).get('AddressData')).body[0][1] - for entry in address_data: - if 'address' in entry: - ipv4_address = entry['address'][1] - break + for entry in address_data: + if 'address' in entry: + ipv4_address = entry['address'][1] + break - # Metered status - conn_path = props.get('Connection', ('o', '/'))[1] - if conn_path != "/": - settings = self._get_connection_settings(conn_path) + # Metered status + settings = self._get_connection_settings(conn_path) - if len(settings) > 0: - metered_prop = settings['connection'].get('metered', ('i', 0))[1] + if len(settings) > 0: + metered_prop = settings['connection'].get('metered', ('i', 0))[1] - if metered_prop == MeteredType.YES: - metered = MeteredType.YES - elif metered_prop == MeteredType.NO: - metered = MeteredType.NO - break + if metered_prop == MeteredType.YES: + metered = MeteredType.YES + elif metered_prop == MeteredType.NO: + metered = MeteredType.NO self._ipv4_address = ipv4_address self._current_network_metered = metered From 887ea25b6d1e66bbdff5fbccccedd519d960775f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 21:49:50 -0800 Subject: [PATCH 235/685] WifiManager: fix is_connected flicker while roaming on low strength networks (#37243) * temp * clean up * debug * clean up * fix * cmt * clean up --- system/ui/lib/wifi_manager.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index d403f91f8dc..57a40895cdd 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -94,10 +94,12 @@ class Network: ip_address: str = "" # TODO: implement @classmethod - def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool) -> "Network": + def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool, active_connection: bool) -> "Network": # we only want to show the strongest AP for each Network/SSID strongest_ap = max(aps, key=lambda ap: ap.strength) - is_connected = any(ap.is_connected for ap in aps) + # fall back to ActiveConnection during momentary AP roaming or low strength networks. matches GNOME shell behavior + # https://github.com/GNOME/gnome-shell/blob/3f8b174274fac7d69477523d4873ef8253e1ed49/js/ui/status/network.js#L810-L819 + is_connected = any(ap.is_connected for ap in aps) or active_connection security_type = get_security_type(strongest_ap.flags, strongest_ap.wpa_flags, strongest_ap.rsn_flags) return cls( @@ -707,7 +709,9 @@ def worker(): # catch all for parsing errors cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections) for ssid, ap_list in aps.items()] + active_wifi_connection, _ = self._get_active_wifi_connection() + networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections, + self._connections.get(ssid) == active_wifi_connection) for ssid, ap_list in aps.items()] # sort with quantized strength to reduce jumping networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) self._networks = networks From 966bb6cc549f1fa55c3f03379428d625e5bc1da9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 22:41:51 -0800 Subject: [PATCH 236/685] WifiUi: update wifi button in loop (#37246) * move to update_state * move back --- .../mici/layouts/settings/network/__init__.py | 46 +++++++++---------- system/ui/lib/wifi_manager.py | 4 ++ 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 3854e998e92..c14d8ad8e53 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -123,6 +123,29 @@ def _update_state(self): self._apn_btn.set_visible(show_cell_settings) self._cellular_metered_btn.set_visible(show_cell_settings) + # Update wi-fi button with ssid and ip address + # TODO: make sure we handle hidden ssids + connecting_ssid = self._wifi_manager.connecting_to_ssid + connected_network = next((network for network in self._wifi_manager.networks if network.is_connected), None) + if connecting_ssid: + display_network = next((n for n in self._wifi_manager.networks if n.ssid == connecting_ssid), None) + self._wifi_button.set_text(normalize_ssid(connecting_ssid)) + self._wifi_button.set_value("connecting...") + elif connected_network is not None: + display_network = connected_network + self._wifi_button.set_text(normalize_ssid(connected_network.ssid)) + self._wifi_button.set_value(self._wifi_manager.ipv4_address or "not connected") + else: + display_network = None + self._wifi_button.set_text("wi-fi") + self._wifi_button.set_value("not connected") + + if display_network is not None: + strength = WifiIcon.get_strength_icon_idx(display_network.strength) + self._wifi_button.set_icon(self._wifi_full_txt if strength == 2 else self._wifi_medium_txt if strength == 1 else self._wifi_low_txt) + else: + self._wifi_button.set_icon(self._wifi_slash_txt) + def show_event(self): super().show_event() self._current_panel = NetworkPanelType.NONE @@ -162,29 +185,6 @@ def _on_network_updated(self, networks: list[Network]): self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) self._tethering_toggle_btn.set_checked(tethering_active) - # Update wi-fi button with ssid and ip address - # TODO: make sure we handle hidden ssids - connecting_ssid = self._wifi_manager.connecting_to_ssid - connected_network = next((network for network in networks if network.is_connected), None) - if connecting_ssid: - display_network = next((n for n in networks if n.ssid == connecting_ssid), None) - self._wifi_button.set_text(normalize_ssid(connecting_ssid)) - self._wifi_button.set_value("connecting...") - elif connected_network is not None: - display_network = connected_network - self._wifi_button.set_text(normalize_ssid(connected_network.ssid)) - self._wifi_button.set_value(self._wifi_manager.ipv4_address or "not connected") - else: - display_network = None - self._wifi_button.set_text("wi-fi") - self._wifi_button.set_value("not connected") - - if display_network is not None: - strength = WifiIcon.get_strength_icon_idx(display_network.strength) - self._wifi_button.set_icon(self._wifi_full_txt if strength == 2 else self._wifi_medium_txt if strength == 1 else self._wifi_low_txt) - else: - self._wifi_button.set_icon(self._wifi_slash_txt) - # Update network metered self._network_metered_btn.set_value( { diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 57a40895cdd..f22c6c0d92c 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -227,6 +227,10 @@ def add_callbacks(self, need_auth: Callable[[str], None] | None = None, if disconnected is not None: self._disconnected.append(disconnected) + @property + def networks(self) -> list[Network]: + return self._networks + @property def ipv4_address(self) -> str: return self._ipv4_address From c8e10139c2afb9137f90702ca03f718dffcfde8c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 17 Feb 2026 22:53:49 -0800 Subject: [PATCH 237/685] WifiUi: if connected, don't show not connected (#37245) * obt * obt * debug * clean up --- selfdrive/ui/mici/layouts/settings/network/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index c14d8ad8e53..659cd5ff3ad 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -134,7 +134,7 @@ def _update_state(self): elif connected_network is not None: display_network = connected_network self._wifi_button.set_text(normalize_ssid(connected_network.ssid)) - self._wifi_button.set_value(self._wifi_manager.ipv4_address or "not connected") + self._wifi_button.set_value(self._wifi_manager.ipv4_address or "obtaining IP...") else: display_network = None self._wifi_button.set_text("wi-fi") From 80f4becabfad873a273dcce0fbcfb3c9cabc5971 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 18 Feb 2026 01:03:39 -0800 Subject: [PATCH 238/685] no need to guard connect with password --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 679372404a6..5bf811e26a6 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -397,9 +397,8 @@ def _update_buttons(self): btn.set_network_missing(True) def _connect_with_password(self, ssid: str, password: str): - if password: - self._wifi_manager.connect_to_network(ssid, password) - self._update_buttons() + self._wifi_manager.connect_to_network(ssid, password) + self._update_buttons() def _on_option_selected(self, option: str): super()._on_option_selected(option) From edafe139a41898f4dae4c445b97e582d93fb94ab Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 18 Feb 2026 01:14:40 -0800 Subject: [PATCH 239/685] WifiManager: set connecting status if NM auto connects (#37247) * set connecting if nm auto connects * good catch --- system/ui/lib/wifi_manager.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index f22c6c0d92c..14a32e0a8ad 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -10,7 +10,7 @@ from jeepney import DBusAddress, new_method_call from jeepney.bus_messages import MatchRule, message_bus -from jeepney.io.blocking import open_dbus_connection as open_dbus_connection_blocking +from jeepney.io.blocking import DBusConnection, open_dbus_connection as open_dbus_connection_blocking from jeepney.io.threading import DBusRouter, open_dbus_connection as open_dbus_connection_threading from jeepney.low_level import MessageType from jeepney.wrappers import Properties @@ -341,6 +341,17 @@ def _monitor_state(self): if self._connecting_to_ssid == failed_ssid: self._connecting_to_ssid = None + elif new_state == NMDeviceState.PREPARE and self._connecting_to_ssid is None: + # Set connecting status when NetworkManager connects to known networks on its own + conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + if conn_path is None: + cloudlog.warning("Failed to get active wifi connection during PREPARE state") + continue + + ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + if ssid: + self._set_connecting(ssid) + elif new_state == NMDeviceState.ACTIVATED: self._update_networks() self._enqueue_callbacks(self._activated) @@ -412,15 +423,21 @@ def _new_connection(self, conn_path: str): def _connection_removed(self, conn_path: str): self._connections = {ssid: path for ssid, path in self._connections.items() if path != conn_path} - def _get_active_connections(self): + def _get_active_connections(self, router: DBusConnection | DBusRouter | None = None): # Returns list of ActiveConnection - return self._router_main.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] + if router is None: + router = self._router_main - def _get_active_wifi_connection(self) -> tuple[str | None, dict | None]: + return router.send_and_get_reply(Properties(self._nm).get('ActiveConnections')).body[0][1] + + def _get_active_wifi_connection(self, router: DBusConnection | DBusRouter | None = None) -> tuple[str | None, dict | None]: # Returns first Connection settings path and ActiveConnection props from ActiveConnections with Type 802-11-wireless - for active_conn in self._get_active_connections(): + if router is None: + router = self._router_main + + for active_conn in self._get_active_connections(router): conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - reply = self._router_main.send_and_get_reply(Properties(conn_addr).get_all()) + reply = router.send_and_get_reply(Properties(conn_addr).get_all()) if reply.header.message_type == MessageType.error: cloudlog.warning(f"Failed to get active connection properties for {active_conn}: {reply}") From 7aeb7085a3fd222d302eaeabe53fa96c53cd3184 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 18 Feb 2026 01:17:42 -0800 Subject: [PATCH 240/685] WifiUi: add hide Scroller event (#37248) * add show/hide scroller events * another good catch --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 5bf811e26a6..dd8b26e5453 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -362,6 +362,10 @@ def show_event(self): self._scroller._items.clear() self._update_buttons() + def hide_event(self): + super().hide_event() + self._scroller.hide_event() + def _open_network_manage_page(self, result=None): if self._network_info_page._network is not None and self._network_info_page._network.ssid in self._networks: self._network_info_page.update_networks(self._networks) From 62b5fd54e64da9bee138b345ae7649e3e7343a25 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 18 Feb 2026 01:18:06 -0800 Subject: [PATCH 241/685] WifiUi: sort by real strength (#37249) sort by real strength --- system/ui/lib/wifi_manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 14a32e0a8ad..8f4d8bdaf00 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -733,8 +733,7 @@ def worker(): active_wifi_connection, _ = self._get_active_wifi_connection() networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections, self._connections.get(ssid) == active_wifi_connection) for ssid, ap_list in aps.items()] - # sort with quantized strength to reduce jumping - networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -round(n.strength / 100 * 2), n.ssid.lower())) + networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -n.strength, n.ssid.lower())) self._networks = networks self._update_active_connection_info() From b5f86446d450c25b3e27d0a057a9b9c92885b04c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 18 Feb 2026 01:19:31 -0800 Subject: [PATCH 242/685] WifiManager: check AddConnection was successful (#37250) check addconnect --- system/ui/lib/wifi_manager.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 8f4d8bdaf00..61522ffdeec 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -530,7 +530,12 @@ def worker(): } settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) - self._router_main.send_and_get_reply(new_method_call(settings_addr, 'AddConnection', 'a{sa{sv}}', (connection,))) + reply = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'AddConnection', 'a{sa{sv}}', (connection,))) + + if reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to add connection for {ssid}: {reply}") + self._connecting_to_ssid = None + self._prev_connecting_to_ssid = None threading.Thread(target=worker, daemon=True).start() From 489afc38421831b895042bbdb91ac7538ea04d8b Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 18 Feb 2026 02:34:57 -0700 Subject: [PATCH 243/685] four ui: edge shadows (#37239) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ui: add edge shadow effect to horizontal scrollers in settings Adds a black gradient falloff shadow (20x240, 100%→0% opacity) on the left and right edges of horizontal Scroller panels. Enabled via an opt-in `edge_shadows` parameter on Scroller for easy per-screen control. Enabled on: settings menu, toggles, network, device, developer. Not enabled on: home screen carousel, vertical scrollers, setup screens. Co-authored-by: Cursor * ui: reduce edge shadow opacity to 80% Co-authored-by: Cursor * what on earth is this * some lines are ok --------- Co-authored-by: Cursor Co-authored-by: Shane Smiskol --- selfdrive/ui/mici/layouts/main.py | 2 +- system/ui/widgets/scroller.py | 22 ++++++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index f12c95eafbc..b78a1d8eaf5 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -47,7 +47,7 @@ def __init__(self): self._alerts_layout, self._home_layout, self._onroad_layout, - ], spacing=0, pad_start=0, pad_end=0, scroll_indicator=False) + ], spacing=0, pad_start=0, pad_end=0, scroll_indicator=False, edge_shadows=False) self._scroller.set_reset_scroll_at_show(False) # Disable scrolling when onroad is interacting with bookmark diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index fcba1952ce9..5930a2a6ebf 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -12,6 +12,8 @@ LINE_PADDING = 40 ANIMATION_SCALE = 0.6 +EDGE_SHADOW_WIDTH = 20 + MIN_ZOOM_ANIMATION_TIME = 0.075 # seconds DO_ZOOM = False DO_JELLO = False @@ -77,7 +79,7 @@ def _render(self, _): class Scroller(Widget): def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = True, spacing: int = ITEM_SPACING, line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING, - scroll_indicator: bool = True): + scroll_indicator: bool = True, edge_shadows: bool = True): super().__init__() self._items: list[Widget] = [] self._horizontal = horizontal @@ -107,8 +109,9 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo self.scroll_panel = GuiScrollPanel2(self._horizontal, handle_out_of_bounds=not self._snap_items) self._scroll_enabled: bool | Callable[[], bool] = True - self._show_scroll_indicator = scroll_indicator + self._show_scroll_indicator = scroll_indicator and self._horizontal self._scroll_indicator = ScrollIndicator() + self._edge_shadows = edge_shadows and self._horizontal for item in items: self.add_widget(item) @@ -286,8 +289,19 @@ def _render(self, _): rl.end_scissor_mode() - # Draw scroll indicator - if self._show_scroll_indicator and self._horizontal and len(self._visible_items) > 0: + # Draw edge shadows on top of scroller content + if self._edge_shadows: + rl.draw_rectangle_gradient_h(int(self._rect.x), int(self._rect.y), + EDGE_SHADOW_WIDTH, int(self._rect.y), + rl.Color(0, 0, 0, 166), rl.BLANK) + + right_x = int(self._rect.x + self._rect.width - EDGE_SHADOW_WIDTH) + rl.draw_rectangle_gradient_h(right_x, int(self._rect.y), + EDGE_SHADOW_WIDTH, int(self._rect.y), + rl.BLANK, rl.Color(0, 0, 0, 166)) + + # Draw scroll indicator on top of edge shadows + if self._show_scroll_indicator and len(self._visible_items) > 0: self._scroll_indicator.update(self._scroll_offset, self._content_size, self._rect) self._scroll_indicator.render() From c6db0cd4b6f61a4f4ce52b6ca941a83af386508f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 18 Feb 2026 05:52:59 -0800 Subject: [PATCH 244/685] WifiManager: fix all networks showing as connected when no active connection (#37252) * WifiManager: fix all networks showing as connected when no active connection When there's no active WiFi connection, _get_active_wifi_connection() returns None. This caused `self._connections.get(ssid) == None` to be True for all unsaved networks, marking them all as connected. Co-authored-by: Cursor * ltl --------- Co-authored-by: Cursor --- system/ui/lib/wifi_manager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 61522ffdeec..52246beb7e5 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -737,6 +737,7 @@ def worker(): active_wifi_connection, _ = self._get_active_wifi_connection() networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections, + active_wifi_connection is not None and self._connections.get(ssid) == active_wifi_connection) for ssid, ap_list in aps.items()] networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -n.strength, n.ssid.lower())) self._networks = networks From d80cde6e41206813126304658c11351c88f77714 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Wed, 18 Feb 2026 18:17:06 -0500 Subject: [PATCH 245/685] tools: block `manage_athenad` in sim startup script (#37256) tools: block `manage_athenad` in Metadrive startup script --- tools/sim/launch_openpilot.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/sim/launch_openpilot.sh b/tools/sim/launch_openpilot.sh index fa5ac731bd3..392f365d037 100755 --- a/tools/sim/launch_openpilot.sh +++ b/tools/sim/launch_openpilot.sh @@ -6,7 +6,7 @@ export SIMULATION="1" export SKIP_FW_QUERY="1" export FINGERPRINT="HONDA_CIVIC_2022" -export BLOCK="${BLOCK},camerad,loggerd,encoderd,micd,logmessaged" +export BLOCK="${BLOCK},camerad,loggerd,encoderd,micd,logmessaged,manage_athenad" if [[ "$CI" ]]; then # TODO: offscreen UI should work export BLOCK="${BLOCK},ui" From b80d3e113b071a5a1e4a7057184e12dfc93c06dc Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:20:25 -0600 Subject: [PATCH 246/685] ui diff: better diff report on mobile (#37255) * Add HTML template for UI diff report * update .gitignore * empty line * use proper html tags * remove paragraph tags * simplify paths * use h3 * use simpler replace instead; dynamically generate videos * update diff html styling * remove unnecessary * fix * use h4 instead * padding on h4 * adjust heading margin * revert * use h4 again * remove viewport * Revert "remove viewport" This reverts commit 7636920e556fc06bbd65cff7ecb4c3d31b11024d. --- selfdrive/ui/tests/.gitignore | 5 -- selfdrive/ui/tests/diff/.gitignore | 2 + selfdrive/ui/tests/diff/diff.py | 70 ++++--------------- selfdrive/ui/tests/diff/diff_template.html | 80 ++++++++++++++++++++++ 4 files changed, 94 insertions(+), 63 deletions(-) create mode 100644 selfdrive/ui/tests/diff/.gitignore create mode 100644 selfdrive/ui/tests/diff/diff_template.html diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore index 39e2f8970c3..74ab2675dbf 100644 --- a/selfdrive/ui/tests/.gitignore +++ b/selfdrive/ui/tests/.gitignore @@ -1,7 +1,2 @@ test test_translations -test_ui/report_1 - -diff/**/*.mp4 -diff/**/*.html -diff/.coverage diff --git a/selfdrive/ui/tests/diff/.gitignore b/selfdrive/ui/tests/diff/.gitignore new file mode 100644 index 00000000000..e21a8d896e9 --- /dev/null +++ b/selfdrive/ui/tests/diff/.gitignore @@ -0,0 +1,2 @@ +report +.coverage diff --git a/selfdrive/ui/tests/diff/diff.py b/selfdrive/ui/tests/diff/diff.py index 42efa381b23..974edb42a36 100755 --- a/selfdrive/ui/tests/diff/diff.py +++ b/selfdrive/ui/tests/diff/diff.py @@ -8,6 +8,7 @@ from openpilot.common.basedir import BASEDIR DIFF_OUT_DIR = Path(BASEDIR) / "selfdrive" / "ui" / "tests" / "diff" / "report" +HTML_TEMPLATE_PATH = Path(__file__).with_name("diff_template.html") def extract_framehashes(video_path): @@ -71,64 +72,17 @@ def generate_html_report(videos: tuple[str, str], basedir: str, different_frames + (f" Video {'2' if frame_delta > 0 else '1'} is longer by {abs(frame_delta)} frames." if frame_delta != 0 else "") ) - def render_video_cell(video_id: str, title: str, path: str, is_diff=False): - return f""" - -

{title}

- - -""" - - html = f"""

UI Diff

- - -{render_video_cell("video1", "Video 1", videos[0])} -{render_video_cell("video2", "Video 2", videos[1])} -{render_video_cell("diffVideo", "Pixel Diff", diff_video_name, is_diff=True)} - -
- -
-

Results: {result_text}

-""" + # Load HTML template and replace placeholders + html = HTML_TEMPLATE_PATH.read_text() + placeholders = { + "VIDEO1_SRC": os.path.join(basedir, os.path.basename(videos[0])), + "VIDEO2_SRC": os.path.join(basedir, os.path.basename(videos[1])), + "DIFF_SRC": os.path.join(basedir, diff_video_name), + "RESULT_TEXT": result_text, + } + for key, value in placeholders.items(): + html = html.replace(f"${key}", value) + return html diff --git a/selfdrive/ui/tests/diff/diff_template.html b/selfdrive/ui/tests/diff/diff_template.html new file mode 100644 index 00000000000..3f1de105120 --- /dev/null +++ b/selfdrive/ui/tests/diff/diff_template.html @@ -0,0 +1,80 @@ + + + + + + UI Diff Report + + + +

UI Diff

+
+
+

Results: $RESULT_TEXT

+ + + From a6f4cdb31943c3bbf3aceea616813a7df660a9d8 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:20:55 -0600 Subject: [PATCH 247/685] ui replay: remove fps limiting during headless replay (#37241) use OFFSCREEN during headless replay for no fps limiting --- selfdrive/ui/tests/diff/replay.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 1cb0b42ada0..bb047b20151 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -27,15 +27,17 @@ def setup_state(): def run_replay(variant: LayoutVariant) -> None: - from openpilot.selfdrive.ui.ui_state import ui_state # Import within OpenpilotPrefix context so param values are setup correctly - from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage - from openpilot.selfdrive.ui.tests.diff.replay_script import build_script + if HEADLESS: + rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) + os.environ["OFFSCREEN"] = "1" # Run UI without FPS limit (set before importing gui_app) setup_state() os.makedirs(DIFF_OUT_DIR, exist_ok=True) - if HEADLESS: - rl.set_config_flags(rl.FLAG_WINDOW_HIDDEN) + from openpilot.selfdrive.ui.ui_state import ui_state # Import within OpenpilotPrefix context so param values are setup correctly + from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage + from openpilot.selfdrive.ui.tests.diff.replay_script import build_script + gui_app.init_window("ui diff test", fps=FPS) # Dynamically import main layout based on variant From b6a0c89dc57da60cffd387ad49005ed07a7f6950 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Wed, 18 Feb 2026 17:21:17 -0600 Subject: [PATCH 248/685] ui replay: record lossless to fix big replay diff (#37237) * add RECORD_LOSSLESS and enable for replays * use RECORD_QUALITY instead * comment * clarify comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * clarify comment --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- selfdrive/ui/tests/diff/replay.py | 1 + system/ui/lib/application.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index bb047b20151..e424d11f623 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -106,6 +106,7 @@ def main(): if args.big: os.environ["BIG"] = "1" os.environ["RECORD"] = "1" + os.environ["RECORD_QUALITY"] = "0" # Use CRF 0 ("lossless" encode) for deterministic output across different machines os.environ["RECORD_OUTPUT"] = os.path.join(DIFF_OUT_DIR, os.environ.get("RECORD_OUTPUT", f"{variant}_ui_replay.mp4")) print(f"Running {variant} UI replay...") diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index da314a394f5..755de674dea 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -41,7 +41,8 @@ PROFILE_STATS = int(os.getenv("PROFILE_STATS", "100")) # Number of functions to show in profile output RECORD = os.getenv("RECORD") == "1" RECORD_OUTPUT = str(Path(os.getenv("RECORD_OUTPUT", "output")).with_suffix(".mp4")) -RECORD_BITRATE = os.getenv("RECORD_BITRATE", "") # Target bitrate e.g. "2000k" +RECORD_QUALITY = int(os.getenv("RECORD_QUALITY", "23")) # Dynamic bitrate quality level (CRF); 0 is lossless (bigger size), max is 51, default is 23 for x264 +RECORD_BITRATE = os.getenv("RECORD_BITRATE", "") # Target bitrate e.g. "2000k" (overrides RECORD_QUALITY when set) RECORD_SPEED = int(os.getenv("RECORD_SPEED", "1")) # Speed multiplier OFFSCREEN = os.getenv("OFFSCREEN") == "1" # Disable FPS limiting for fast offline rendering @@ -298,8 +299,10 @@ def _close(sig, frame): '-r', str(output_fps), # Output frame rate (for speed multiplier) '-c:v', 'libx264', '-preset', 'ultrafast', + '-crf', str(RECORD_QUALITY) ] if RECORD_BITRATE: + # NOTE: custom bitrate overrides crf setting ffmpeg_args += ['-b:v', RECORD_BITRATE, '-maxrate', RECORD_BITRATE, '-bufsize', RECORD_BITRATE] ffmpeg_args += [ '-y', # Overwrite existing file From 488d84c6643b848bb3494bc7b4c5878956aaeda3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 18 Feb 2026 16:40:09 -0800 Subject: [PATCH 249/685] mici updater: clean up unused signal strength (#37259) clean up --- system/ui/mici_updater.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 7ebb4262ff9..5c8748783a1 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -8,7 +8,7 @@ from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network +from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import gui_text_box, gui_label, UnifiedLabel from openpilot.system.ui.widgets.button import FullRoundedButton @@ -28,7 +28,6 @@ def __init__(self, updater_path, manifest_path): self.updater = updater_path self.manifest = manifest_path self.current_screen = Screen.PROMPT - self._current_network_strength = -1 self.progress_value = 0 self.progress_text = "loading" @@ -40,7 +39,6 @@ def __init__(self, updater_path, manifest_path): self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_callback, self._network_setup_back_callback) - self._wifi_manager.add_callbacks(networks_updated=self._on_network_updated) self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() @@ -66,9 +64,6 @@ def _network_setup_continue_callback(self): def _update_failed_retry_callback(self): self.set_current_screen(Screen.PROMPT) - def _on_network_updated(self, networks: list[Network]): - self._current_network_strength = next((net.strength for net in networks if net.is_connected), -1) - def set_current_screen(self, screen: Screen): if self.current_screen != screen: if screen == Screen.PROGRESS: From 3c4ddba992b3ee52a9c881ee0eb9727d3a37af11 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Wed, 18 Feb 2026 20:09:46 -0800 Subject: [PATCH 250/685] DM: Ford GT Le Mans Model (#37257) * b483cec4-7816-4570-a774-be3a2c100098/50 * shipfest * da4b8724-8998-45da-aa36-d8fb390492b9 * revert * typo * deprecates * 53a2718f-206b-4400-a70b-16915de18472/200 * bump * update --- cereal/log.capnp | 4 +-- .../modeld/models/dmonitoring_model.onnx | 4 +-- selfdrive/monitoring/helpers.py | 25 +++---------------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index afb710e808f..119cf299999 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2232,9 +2232,9 @@ struct DriverMonitoringState @0xb83cda094a1da284 { isActiveMode @16 :Bool; isRHD @4 :Bool; uncertainCount @19 :UInt32; - phoneProbOffset @20 :Float32; - phoneProbValidCount @21 :UInt32; + phoneProbOffsetDEPRECATED @20 :Float32; + phoneProbValidCountDEPRECATED @21 :UInt32; isPreviewDEPRECATED @15 :Bool; rhdCheckedDEPRECATED @5 :Bool; eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED); diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 9b1c4a18347..24234d4c50d 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3446bf8b22e50e47669a25bf32460ae8baf8547037f346753e19ecbfcf6d4e59 -size 6954368 +oid sha256:5e8de9dc7df306700cce9c22b992e25b95a38f894c47adaea742a9cf8ba78e1a +size 7307246 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 3377ce6c683..91ddaaa9c13 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -35,14 +35,7 @@ def __init__(self, device_type): self._EYE_THRESHOLD = 0.65 self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - - self._PHONE_THRESH = 0.75 if device_type == 'mici' else 0.4 - self._PHONE_THRESH2 = 15.0 - self._PHONE_MAX_OFFSET = 0.06 - self._PHONE_MIN_OFFSET = 0.025 - self._PHONE_DATA_AVG = 0.05 - self._PHONE_DATA_VAR = 3*0.005 - self._PHONE_MAX_COUNT = int(360 / self._DT_DMON) + self._PHONE_THRESH = 0.68 self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 @@ -152,11 +145,10 @@ def __init__(self, rhd_saved=False, settings=None, always_on=False): # init driver status wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) - phone_filter_raw_priors = (self.settings._PHONE_DATA_AVG, self.settings._PHONE_DATA_VAR, 2) self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) - self.phone = DriverProb(raw_priors=phone_filter_raw_priors, max_trackable=self.settings._PHONE_MAX_COUNT) self.pose = DriverPose(settings=self.settings) self.blink = DriverBlink() + self.phone_prob = 0. self.always_on = always_on self.distracted_types = [] @@ -257,12 +249,7 @@ def _get_distracted_types(self): if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) - if self.phone.prob_calibrated: - using_phone = self.phone.prob > max(min(self.phone.prob_offseter.filtered_stat.M, self.settings._PHONE_MAX_OFFSET), self.settings._PHONE_MIN_OFFSET) \ - * self.settings._PHONE_THRESH2 - else: - using_phone = self.phone.prob > self.settings._PHONE_THRESH - if using_phone: + if self.phone_prob > self.settings._PHONE_THRESH: distracted_types.append(DistractedType.DISTRACTED_PHONE) return distracted_types @@ -301,7 +288,7 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.phone.prob = driver_data.phoneProb + self.phone_prob = driver_data.phoneProb self.distracted_types = self._get_distracted_types() self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types @@ -315,11 +302,9 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) - self.phone.prob_offseter.push_and_update(self.phone.prob) self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT - self.phone.prob_calibrated = self.phone.prob_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: @@ -425,8 +410,6 @@ def get_state_packet(self, valid=True): "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, - "phoneProbOffset": self.phone.prob_offseter.filtered_stat.mean(), - "phoneProbValidCount": self.phone.prob_offseter.filtered_stat.n, "stepChange": self.step_change, "awarenessActive": self.awareness_active, "awarenessPassive": self.awareness_passive, From 612c518dd68b994186bf3d29970c44e673523afb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 18 Feb 2026 22:42:05 -0800 Subject: [PATCH 251/685] WifiManager: signal-driven connection status (#37258) * signal driven wifi state * copy exactly * copy signal handler * remove is_connected * Revert "remove is_connected" This reverts commit f2246a70f4a29e9f3405947ca43d9404578c9d2d. * do 3 network * missing reason * do wifiui * clean up mici updater * rest * or not connecting * clean up is_connected * clean up wifiui * match wifiui state more exactly in network panel for wifi button * update active connection info after activation (used to do in _update_networks) * clean up prints * more * rm * not needed * clean up state machine a bit * more * more * indent * final clean up * debug * debug * wait for ip? * more * revert * just to see * ensure we emit activated even if we fail to get conn path from dbus * hmm * fine * back * back * Revert "back" This reverts commit 6464abe243c2a3bbf62b8f9a109b72ec3ddb3817. * debug flickering on forget then connect to another. commit before this is good * fix rare flicker when forgetting network and immediately connecting to another * clean up * clean up router stuff now * ugh wtf * stash -- wtf * Revert "stash -- wtf" This reverts commit 756a92a9c0530a16917303424e26447f258f17e4. * Revert "fix rare flicker when forgetting network and immediately connecting to" This reverts commit 90c5fc14551726765ab2524e7866ee8b3c5dee7c. * remove debug * fix * add issues * add flow * match previous behavior * it doesn't fix the flikcer * more atomic * Revert "more atomic" This reverts commit ead87c5a7a4030719b64138c12b9154ec82e73d9. * last test! last test! * really the race is here? * atomic wifi_state replace * not slow * clean up --- .../mici/layouts/settings/network/__init__.py | 16 +- .../mici/layouts/settings/network/wifi_ui.py | 40 ++-- system/ui/lib/networkmanager.py | 1 + system/ui/lib/wifi_manager.py | 198 ++++++++++++------ system/ui/widgets/network.py | 4 +- 5 files changed, 166 insertions(+), 93 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 659cd5ff3ad..1940b680eb9 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus class NetworkPanelType(IntEnum): @@ -125,15 +125,13 @@ def _update_state(self): # Update wi-fi button with ssid and ip address # TODO: make sure we handle hidden ssids - connecting_ssid = self._wifi_manager.connecting_to_ssid - connected_network = next((network for network in self._wifi_manager.networks if network.is_connected), None) - if connecting_ssid: - display_network = next((n for n in self._wifi_manager.networks if n.ssid == connecting_ssid), None) - self._wifi_button.set_text(normalize_ssid(connecting_ssid)) + wifi_state = self._wifi_manager.wifi_state + display_network = next((n for n in self._wifi_manager.networks if n.ssid == wifi_state.ssid), None) + if wifi_state.status == ConnectStatus.CONNECTING: + self._wifi_button.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) self._wifi_button.set_value("connecting...") - elif connected_network is not None: - display_network = connected_network - self._wifi_button.set_text(normalize_ssid(connected_network.ssid)) + elif wifi_state.status == ConnectStatus.CONNECTED: + self._wifi_button.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) self._wifi_button.set_value(self._wifi_manager.ipv4_address or "obtaining IP...") else: display_network = None diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index dd8b26e5453..66b8e352f58 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -8,7 +8,7 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigInputDialog, BigDialogOptionButton, BigConfirmationDialogV2 from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget, NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, WifiState def normalize_ssid(ssid: str) -> str: @@ -94,7 +94,7 @@ def _render(self, _): class WifiItem(BigDialogOptionButton): LEFT_MARGIN = 20 - def __init__(self, network: Network): + def __init__(self, network: Network, wifi_state_callback: Callable[[], WifiState]): super().__init__(network.ssid) self.set_rect(rl.Rectangle(0, 0, gui_app.width, self.HEIGHT)) @@ -102,6 +102,7 @@ def __init__(self, network: Network): self._selected_txt = gui_app.texture("icons_mici/settings/network/new/wifi_selected.png", 48, 96) self._network = network + self._wifi_state_callback = wifi_state_callback self._wifi_icon = WifiIcon() self._wifi_icon.set_current_network(network) @@ -119,7 +120,8 @@ def set_current_network(self, network: Network): def _render(self, _): disabled_alpha = 0.35 if not self.enabled else 1.0 - if self._network.is_connected: + # connecting or connected + if self._wifi_state_callback().ssid == self._network.ssid: selected_x = int(self._rect.x - self._selected_txt.width / 2) selected_y = int(self._rect.y + (self._rect.height - self._selected_txt.height) / 2) rl.draw_texture(self._selected_txt, selected_x, selected_y, rl.WHITE) @@ -214,7 +216,8 @@ def _render(self, _): class NetworkInfoPage(NavWidget): - def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, open_network_manage_page: Callable): + def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, open_network_manage_page: Callable, + connecting_callback: Callable[[], str | None], connected_callback: Callable[[], str | None]): super().__init__() self._wifi_manager = wifi_manager @@ -235,7 +238,8 @@ def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Ca # State self._network: Network | None = None - self._connecting: Callable[[], str | None] | None = None + self._connecting_callback = connecting_callback + self._connected_callback = connected_callback def show_event(self): super().show_event() @@ -263,7 +267,7 @@ def _update_state(self): if self._is_connecting: self._connect_btn.set_label("connecting...") self._connect_btn.set_enabled(False) - elif self._network.is_connected: + elif self._is_connected: self._connect_btn.set_label("connected") self._connect_btn.set_enabled(False) elif self._network.security_type == SecurityType.UNSUPPORTED: @@ -285,16 +289,20 @@ def set_current_network(self, network: Network): self._network = network self._wifi_icon.set_current_network(network) - def set_connecting(self, is_connecting: Callable[[], str | None]): - self._connecting = is_connecting - @property def _is_connecting(self): - if self._connecting is None or self._network is None: + if self._network is None: return False - is_connecting = self._connecting() == self._network.ssid + is_connecting = self._connecting_callback() == self._network.ssid return is_connecting + @property + def _is_connected(self): + if self._network is None: + return False + is_connected = self._connected_callback() == self._network.ssid + return is_connected + def _render(self, _): self._wifi_icon.render(rl.Rectangle( self._rect.x + 32, @@ -342,8 +350,8 @@ def __init__(self, wifi_manager: WifiManager, back_callback: Callable): # Set up back navigation self.set_back_callback(back_callback) - self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, self._open_network_manage_page) - self._network_info_page.set_connecting(lambda: wifi_manager.connecting_to_ssid) + self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, self._open_network_manage_page, + lambda: wifi_manager.connecting_to_ssid, lambda: wifi_manager.connected_ssid) self._loading_animation = LoadingAnimation() @@ -385,11 +393,11 @@ def _update_buttons(self): # Update network on existing button self._scroller._items[network_button_idx].set_current_network(network) else: - network_button = WifiItem(network) + network_button = WifiItem(network, lambda: self._wifi_manager.wifi_state) self._scroller.add_widget(network_button) - # Move connected network to the start - connected_btn_idx = next((i for i, btn in enumerate(self._scroller._items) if btn._network.is_connected), None) + # Move connecting/connected network to the start + connected_btn_idx = next((i for i, btn in enumerate(self._scroller._items) if self._wifi_manager.wifi_state.ssid == btn._network.ssid), None) if connected_btn_idx is not None and connected_btn_idx > 0: self._scroller._items.insert(0, self._scroller._items.pop(connected_btn_idx)) self._scroller._layout() # fixes selected style single frame stutter diff --git a/system/ui/lib/networkmanager.py b/system/ui/lib/networkmanager.py index f16271a5053..e04e3eeadca 100644 --- a/system/ui/lib/networkmanager.py +++ b/system/ui/lib/networkmanager.py @@ -25,6 +25,7 @@ class NMDeviceStateReason(IntEnum): UNKNOWN = 1 NO_SECRETS = 7 SUPPLICANT_DISCONNECT = 8 + CONNECTION_REMOVED = 38 NEW_ACTIVATION = 60 diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 52246beb7e5..479bad7ece7 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -4,7 +4,7 @@ import uuid import subprocess from collections.abc import Callable -from dataclasses import dataclass +from dataclasses import dataclass, replace from enum import IntEnum from typing import Any @@ -88,24 +88,19 @@ def get_security_type(flags: int, wpa_flags: int, rsn_flags: int) -> SecurityTyp class Network: ssid: str strength: int - is_connected: bool security_type: SecurityType is_saved: bool ip_address: str = "" # TODO: implement @classmethod - def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool, active_connection: bool) -> "Network": + def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool) -> "Network": # we only want to show the strongest AP for each Network/SSID strongest_ap = max(aps, key=lambda ap: ap.strength) - # fall back to ActiveConnection during momentary AP roaming or low strength networks. matches GNOME shell behavior - # https://github.com/GNOME/gnome-shell/blob/3f8b174274fac7d69477523d4873ef8253e1ed49/js/ui/status/network.js#L810-L819 - is_connected = any(ap.is_connected for ap in aps) or active_connection security_type = get_security_type(strongest_ap.flags, strongest_ap.wpa_flags, strongest_ap.rsn_flags) return cls( ssid=ssid, strength=strongest_ap.strength, - is_connected=is_connected and is_saved, security_type=security_type, is_saved=is_saved, ) @@ -116,14 +111,13 @@ class AccessPoint: ssid: str bssid: str strength: int - is_connected: bool flags: int wpa_flags: int rsn_flags: int ap_path: str @classmethod - def from_dbus(cls, ap_props: dict[str, tuple[str, Any]], ap_path: str, active_ap_path: str) -> "AccessPoint": + def from_dbus(cls, ap_props: dict[str, tuple[str, Any]], ap_path: str) -> "AccessPoint": ssid = bytes(ap_props['Ssid'][1]).decode("utf-8", "replace") bssid = str(ap_props['HwAddress'][1]) strength = int(ap_props['Strength'][1]) @@ -135,7 +129,6 @@ def from_dbus(cls, ap_props: dict[str, tuple[str, Any]], ap_path: str, active_ap ssid=ssid, bssid=bssid, strength=strength, - is_connected=ap_path == active_ap_path, flags=flags, wpa_flags=wpa_flags, rsn_flags=rsn_flags, @@ -143,6 +136,18 @@ def from_dbus(cls, ap_props: dict[str, tuple[str, Any]], ap_path: str, active_ap ) +class ConnectStatus(IntEnum): + DISCONNECTED = 0 + CONNECTING = 1 + CONNECTED = 2 + + +@dataclass +class WifiState: + ssid: str | None = None + status: ConnectStatus = ConnectStatus.DISCONNECTED + + class WifiManager: def __init__(self): self._networks: list[Network] = [] # a network can be comprised of multiple APs @@ -166,8 +171,7 @@ def __init__(self): # State self._connections: dict[str, str] = {} # ssid -> connection path, updated via NM signals - self._connecting_to_ssid: str | None = None - self._prev_connecting_to_ssid: str | None = None + self._wifi_state: WifiState = WifiState() self._ipv4_address: str = "" self._current_network_metered: MeteredType = MeteredType.UNKNOWN self._tethering_password: str = "" @@ -199,18 +203,41 @@ def _initialize(self): def worker(): self._wait_for_wifi_device() - self._scan_thread.start() - self._state_thread.start() - self._init_connections() if Params is not None and self._tethering_ssid not in self._connections: self._add_tethering_connection() + self._init_wifi_state() + + self._scan_thread.start() + self._state_thread.start() + self._tethering_password = self._get_tethering_password() cloudlog.debug("WifiManager initialized") threading.Thread(target=worker, daemon=True).start() + def _init_wifi_state(self, block: bool = True): + def worker(): + dev_addr = DBusAddress(self._wifi_device, bus_name=NM, interface=NM_DEVICE_IFACE) + dev_state = self._router_main.send_and_get_reply(Properties(dev_addr).get('State')).body[0][1] + + wifi_state = WifiState() + if NMDeviceState.PREPARE <= dev_state <= NMDeviceState.SECONDARIES and dev_state != NMDeviceState.NEED_AUTH: + wifi_state.status = ConnectStatus.CONNECTING + elif dev_state == NMDeviceState.ACTIVATED: + wifi_state.status = ConnectStatus.CONNECTED + + conn_path, _ = self._get_active_wifi_connection() + if conn_path: + wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + self._wifi_state = wifi_state + + if block: + worker() + else: + threading.Thread(target=worker, daemon=True).start() + def add_callbacks(self, need_auth: Callable[[str], None] | None = None, activated: Callable[[], None] | None = None, forgotten: Callable[[str], None] | None = None, @@ -231,6 +258,10 @@ def add_callbacks(self, need_auth: Callable[[str], None] | None = None, def networks(self) -> list[Network]: return self._networks + @property + def wifi_state(self) -> WifiState: + return self._wifi_state + @property def ipv4_address(self) -> str: return self._ipv4_address @@ -241,15 +272,18 @@ def current_network_metered(self) -> MeteredType: @property def connecting_to_ssid(self) -> str | None: - return self._connecting_to_ssid + return self._wifi_state.ssid if self._wifi_state.status == ConnectStatus.CONNECTING else None + + @property + def connected_ssid(self) -> str | None: + return self._wifi_state.ssid if self._wifi_state.status == ConnectStatus.CONNECTED else None @property def tethering_password(self) -> str: return self._tethering_password - def _set_connecting(self, ssid: str): - self._prev_connecting_to_ssid = self._connecting_to_ssid - self._connecting_to_ssid = ssid + def _set_connecting(self, ssid: str | None): + self._wifi_state = WifiState(ssid=ssid, status=ConnectStatus.DISCONNECTED if ssid is None else ConnectStatus.CONNECTING) def _enqueue_callbacks(self, cbs: list[Callable], *args): for cb in cbs: @@ -264,8 +298,9 @@ def process_callbacks(self): def set_active(self, active: bool): self._active = active - # Update networks immediately when activating for UI + # Update networks and WiFi state (to self-heal) immediately when activating for UI if active: + self._init_wifi_state(block=False) self._update_networks(block=False) def _monitor_state(self): @@ -325,43 +360,77 @@ def _monitor_state(self): self._update_networks() # Device state changes + # TODO: known race conditions when switching networks (e.g. forget A, connect to B): + # 1. DEACTIVATING/DISCONNECTED + CONNECTION_REMOVED: fires before NewConnection for B + # arrives, so _set_connecting(None) clears B's CONNECTING state causing UI flicker. + # DEACTIVATING(CONNECTION_REMOVED): wifi_state (B, CONNECTING) -> (None, DISCONNECTED) + # Fix: make DEACTIVATING a no-op, and guard DISCONNECTED with + # `if wifi_state.ssid not in _connections` (NewConnection arrives between the two). + # 2. PREPARE/CONFIG ssid lookup: DBus may return stale A's conn_path, overwriting B. + # PREPARE(0): wifi_state (B, CONNECTING) -> (A, CONNECTING) + # Fix: only do DBus lookup when wifi_state.ssid is None (auto-connections); + # user-initiated connections already have ssid set via _set_connecting. while len(state_q): new_state, previous_state, change_reason = state_q.popleft().body - # BAD PASSWORD - use prev if current has already moved on to a new connection - # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT - # - weak/gone network fails with FAILED+NO_SECRETS - if ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT) or - (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): - failed_ssid = self._prev_connecting_to_ssid or self._connecting_to_ssid - if failed_ssid: - self._enqueue_callbacks(self._need_auth, failed_ssid) - self.forget_connection(failed_ssid, block=True) - self._prev_connecting_to_ssid = None - if self._connecting_to_ssid == failed_ssid: - self._connecting_to_ssid = None - - elif new_state == NMDeviceState.PREPARE and self._connecting_to_ssid is None: + if new_state == NMDeviceState.DISCONNECTED: + if change_reason != NMDeviceStateReason.NEW_ACTIVATION: + # catches CONNECTION_REMOVED reason when connection is forgotten + self._set_connecting(None) + + elif new_state in (NMDeviceState.PREPARE, NMDeviceState.CONFIG): # Set connecting status when NetworkManager connects to known networks on its own + wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) + conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) if conn_path is None: - cloudlog.warning("Failed to get active wifi connection during PREPARE state") - continue + cloudlog.warning("Failed to get active wifi connection during PREPARE/CONFIG state") + else: + wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) - ssid = next((s for s, p in self._connections.items() if p == conn_path), None) - if ssid: - self._set_connecting(ssid) + self._wifi_state = wifi_state + + # BAD PASSWORD + # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT + # - weak/gone network fails with FAILED+NO_SECRETS + elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT) or + (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): + + if self._wifi_state.ssid: + self._enqueue_callbacks(self._need_auth, self._wifi_state.ssid) + + self._set_connecting(None) + + elif new_state in (NMDeviceState.NEED_AUTH, NMDeviceState.IP_CONFIG, NMDeviceState.IP_CHECK, + NMDeviceState.SECONDARIES, NMDeviceState.FAILED): + pass elif new_state == NMDeviceState.ACTIVATED: + # Note that IP address from Ip4Config may not be propagated immediately and could take until the next scan results self._update_networks() - self._enqueue_callbacks(self._activated) - self._prev_connecting_to_ssid = None - self._connecting_to_ssid = None - elif new_state == NMDeviceState.DISCONNECTED and change_reason != NMDeviceStateReason.NEW_ACTIVATION: - self._enqueue_callbacks(self._forgotten, self._connecting_to_ssid) - self._prev_connecting_to_ssid = None - self._connecting_to_ssid = None + wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTED) + + conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + if conn_path is None: + cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") + self._wifi_state = wifi_state + self._enqueue_callbacks(self._activated) + else: + wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + self._wifi_state = wifi_state + self._enqueue_callbacks(self._activated) + + # Persist volatile connections (created by AddAndActivateConnection2) to disk + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + save_reply = self._conn_monitor.send_and_get_reply(new_method_call(conn_addr, 'Save')) + if save_reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to persist connection to disk: {save_reply}") + + elif new_state == NMDeviceState.DEACTIVATING: + if change_reason == NMDeviceStateReason.CONNECTION_REMOVED: + # When connection is forgotten + self._set_connecting(None) def _network_scanner(self): while not self._exit: @@ -417,8 +486,6 @@ def _new_connection(self, conn_path: str): ssid = settings['802-11-wireless']['ssid'][1].decode("utf-8", "replace") if ssid != "": self._connections[ssid] = conn_path - if ssid != self._tethering_ssid: - self.activate_connection(ssid, block=True) def _connection_removed(self, conn_path: str): self._connections = {ssid: path for ssid, path in self._connections.items() if path != conn_path} @@ -529,13 +596,19 @@ def worker(): 'psk': ('s', password), } - settings_addr = DBusAddress(NM_SETTINGS_PATH, bus_name=NM, interface=NM_SETTINGS_IFACE) - reply = self._router_main.send_and_get_reply(new_method_call(settings_addr, 'AddConnection', 'a{sa{sv}}', (connection,))) + # Volatile connection auto-deletes on disconnect (wrong password, user switches networks) + # Persisted to disk on ACTIVATED via Save() + if self._wifi_device is None: + cloudlog.warning("No WiFi device found") + self._set_connecting(None) + return + + reply = self._router_main.send_and_get_reply(new_method_call(self._nm, 'AddAndActivateConnection2', 'a{sa{sv}}ooa{sv}', + (connection, self._wifi_device, "/", {'persist': ('s', 'volatile')}))) if reply.header.message_type == MessageType.error: - cloudlog.warning(f"Failed to add connection for {ssid}: {reply}") - self._connecting_to_ssid = None - self._prev_connecting_to_ssid = None + cloudlog.warning(f"Failed to add and activate connection for {ssid}: {reply}") + self._set_connecting(None) threading.Thread(target=worker, daemon=True).start() @@ -549,8 +622,7 @@ def worker(): conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) - if len(self._forgotten): - self._update_networks() + self._update_networks() self._enqueue_callbacks(self._forgotten, ssid) if block: @@ -590,10 +662,8 @@ def _deactivate_connection(self, ssid: str): return def is_tethering_active(self) -> bool: - for network in self._networks: - if network.is_connected: - return bool(network.ssid == self._tethering_ssid) - return False + # Check ssid, not connected_ssid, to also catch connecting state + return self._wifi_state.ssid == self._tethering_ssid def set_tethering_password(self, password: str): def worker(): @@ -708,7 +778,6 @@ def worker(): # NOTE: AccessPoints property may exclude hidden APs (use GetAllAccessPoints method if needed) wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) wifi_props = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()).body[0] - active_ap_path = wifi_props.get('ActiveAccessPoint', ('o', '/'))[1] ap_paths = wifi_props.get('AccessPoints', ('ao', []))[1] aps: dict[str, list[AccessPoint]] = {} @@ -723,7 +792,7 @@ def worker(): continue try: - ap = AccessPoint.from_dbus(ap_props.body[0], ap_path, active_ap_path) + ap = AccessPoint.from_dbus(ap_props.body[0], ap_path) if ap.ssid == "": continue @@ -735,11 +804,8 @@ def worker(): # catch all for parsing errors cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - active_wifi_connection, _ = self._get_active_wifi_connection() - networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections, - active_wifi_connection is not None and - self._connections.get(ssid) == active_wifi_connection) for ssid, ap_list in aps.items()] - networks.sort(key=lambda n: (-n.is_connected, -n.is_saved, -n.strength, n.ssid.lower())) + networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections) for ssid, ap_list in aps.items()] + networks.sort(key=lambda n: (n.ssid != self._wifi_state.ssid, -n.is_saved, -n.strength, n.ssid.lower())) self._networks = networks self._update_active_connection_info() diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 9de44c585c6..186164916bf 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -400,7 +400,7 @@ def _networks_buttons_callback(self, network): self.state = UIState.NEEDS_AUTH self._state_network = network self._password_retry = False - elif not network.is_connected: + elif self._wifi_manager.wifi_state.ssid != network.ssid: self.connect_to_network(network) def _forget_networks_buttons_callback(self, network): @@ -410,7 +410,7 @@ def _forget_networks_buttons_callback(self, network): def _draw_status_icon(self, rect, network: Network): """Draw the status icon based on network's connection state""" icon_file = None - if network.is_connected and self.state != UIState.CONNECTING: + if self._wifi_manager.connected_ssid == network.ssid and self.state != UIState.CONNECTING: icon_file = "icons/checkmark.png" elif network.security_type == SecurityType.UNSUPPORTED: icon_file = "icons/circled_slash.png" From a28cc71b8b940383c0352960a16365a84b5fa518 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 19 Feb 2026 00:12:49 -0800 Subject: [PATCH 252/685] WifiManager: always emit forgot callback (#37261) * fixme * fixme * fixme --- system/ui/lib/wifi_manager.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 479bad7ece7..8f1382a52c4 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -617,11 +617,12 @@ def worker(): conn_path = self._connections.get(ssid, None) if conn_path is None: cloudlog.warning(f"Trying to forget unknown connection: {ssid}") - return - - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) - self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) + else: + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) + # FIXME: race here where ConnectionRemoved signal may arrive after we update all Network is_saved + # and keep the old ssid's is_saved=True self._update_networks() self._enqueue_callbacks(self._forgotten, ssid) From c736d43cce2bc3c7fa62212a6b60bbbdf21e5a01 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 19 Feb 2026 00:40:20 -0800 Subject: [PATCH 253/685] Remove old TODO in WifiManager hell no --- system/ui/lib/wifi_manager.py | 1 - 1 file changed, 1 deletion(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 8f1382a52c4..05eac7959a1 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -90,7 +90,6 @@ class Network: strength: int security_type: SecurityType is_saved: bool - ip_address: str = "" # TODO: implement @classmethod def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool) -> "Network": From a3f2452fa7e5cd5034a46175bf46232d5381579a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 19 Feb 2026 00:49:35 -0800 Subject: [PATCH 254/685] WifiManager: single source for known connections (#37262) * temp * rev * reproduce race condition where connection removed signal takes a while to remove, then update networks keep is_saved true * fix * Revert "reproduce race condition where connection removed signal takes a while to remove, then update networks keep is_saved true" This reverts commit cf7044ee955777db16434ab81c520bbe798c9164. * not anymore * more clear * safe guards nl --- .../mici/layouts/settings/network/wifi_ui.py | 4 +-- system/ui/lib/wifi_manager.py | 29 ++++++++++--------- system/ui/widgets/network.py | 6 ++-- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 66b8e352f58..e8fefb82dd5 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -263,7 +263,7 @@ def _update_state(self): if self._network is None: return - self._connect_btn.set_full(not self._network.is_saved and not self._is_connecting) + self._connect_btn.set_full(not self._wifi_manager.is_connection_saved(self._network.ssid) and not self._is_connecting) if self._is_connecting: self._connect_btn.set_label("connecting...") self._connect_btn.set_enabled(False) @@ -425,7 +425,7 @@ def _connect_to_network(self, ssid: str): cloudlog.warning(f"Trying to connect to unknown network: {ssid}") return - if network.is_saved: + if self._wifi_manager.is_connection_saved(network.ssid): self._wifi_manager.activate_connection(network.ssid) self._update_buttons() elif network.security_type == SecurityType.OPEN: diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 05eac7959a1..a5b5833d7f2 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -89,10 +89,9 @@ class Network: ssid: str strength: int security_type: SecurityType - is_saved: bool @classmethod - def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool) -> "Network": + def from_dbus(cls, ssid: str, aps: list["AccessPoint"]) -> "Network": # we only want to show the strongest AP for each Network/SSID strongest_ap = max(aps, key=lambda ap: ap.strength) security_type = get_security_type(strongest_ap.flags, strongest_ap.wpa_flags, strongest_ap.rsn_flags) @@ -101,7 +100,6 @@ def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_saved: bool) -> "Netw ssid=ssid, strength=strongest_ap.strength, security_type=security_type, - is_saved=is_saved, ) @@ -620,8 +618,6 @@ def worker(): conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) - # FIXME: race here where ConnectionRemoved signal may arrive after we update all Network is_saved - # and keep the old ssid's is_saved=True self._update_networks() self._enqueue_callbacks(self._forgotten, ssid) @@ -635,13 +631,17 @@ def activate_connection(self, ssid: str, block: bool = False): def worker(): conn_path = self._connections.get(ssid, None) - if conn_path is not None: - if self._wifi_device is None: - cloudlog.warning("No WiFi device found") - return + if conn_path is None or self._wifi_device is None: + cloudlog.warning(f"Failed to activate connection for {ssid}: conn_path={conn_path}, wifi_device={self._wifi_device}") + self._set_connecting(None) + return - self._router_main.send(new_method_call(self._nm, 'ActivateConnection', 'ooo', - (conn_path, self._wifi_device, "/"))) + reply = self._router_main.send_and_get_reply(new_method_call(self._nm, 'ActivateConnection', 'ooo', + (conn_path, self._wifi_device, "/"))) + + if reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to activate connection for {ssid}: {reply}") + self._set_connecting(None) if block: worker() @@ -665,6 +665,9 @@ def is_tethering_active(self) -> bool: # Check ssid, not connected_ssid, to also catch connecting state return self._wifi_state.ssid == self._tethering_ssid + def is_connection_saved(self, ssid: str) -> bool: + return ssid in self._connections + def set_tethering_password(self, password: str): def worker(): conn_path = self._connections.get(self._tethering_ssid, None) @@ -804,8 +807,8 @@ def worker(): # catch all for parsing errors cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - networks = [Network.from_dbus(ssid, ap_list, ssid in self._connections) for ssid, ap_list in aps.items()] - networks.sort(key=lambda n: (n.ssid != self._wifi_state.ssid, -n.is_saved, -n.strength, n.ssid.lower())) + networks = [Network.from_dbus(ssid, ap_list) for ssid, ap_list in aps.items()] + networks.sort(key=lambda n: (n.ssid != self._wifi_state.ssid, not self.is_connection_saved(n.ssid), -n.strength, n.ssid.lower())) self._networks = networks self._update_active_connection_info() diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 186164916bf..3e5e4a1d55d 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -383,7 +383,7 @@ def _draw_network_item(self, rect, network: Network): gui_label(status_text_rect, status_text, font_size=48, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) else: # If the network is saved, show the "Forget" button - if network.is_saved: + if self._wifi_manager.is_connection_saved(network.ssid): forget_btn_rect = rl.Rectangle( security_icon_rect.x - self.btn_width - spacing, rect.y + (ITEM_HEIGHT - 80) / 2, @@ -396,7 +396,7 @@ def _draw_network_item(self, rect, network: Network): self._draw_signal_strength_icon(signal_icon_rect, network) def _networks_buttons_callback(self, network): - if not network.is_saved and network.security_type != SecurityType.OPEN: + if not self._wifi_manager.is_connection_saved(network.ssid) and network.security_type != SecurityType.OPEN: self.state = UIState.NEEDS_AUTH self._state_network = network self._password_retry = False @@ -432,7 +432,7 @@ def _draw_signal_strength_icon(self, rect: rl.Rectangle, network: Network): def connect_to_network(self, network: Network, password=''): self.state = UIState.CONNECTING self._state_network = network - if network.is_saved and not password: + if self._wifi_manager.is_connection_saved(network.ssid) and not password: self._wifi_manager.activate_connection(network.ssid) else: self._wifi_manager.connect_to_network(network.ssid, password) From 69544c57fd09fbaad4951c7bbe2bde38d8041096 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Thu, 19 Feb 2026 10:28:04 -0700 Subject: [PATCH 255/685] refactor(esim): cleanup lpa (#37260) cleanup lpa --- system/hardware/esim.py | 1 - system/hardware/tici/esim.py | 106 ------------------------ system/hardware/tici/hardware.py | 2 +- system/hardware/tici/lpa.py | 24 ++++++ system/hardware/tici/tests/test_esim.py | 51 ------------ 5 files changed, 25 insertions(+), 159 deletions(-) delete mode 100644 system/hardware/tici/esim.py create mode 100644 system/hardware/tici/lpa.py delete mode 100644 system/hardware/tici/tests/test_esim.py diff --git a/system/hardware/esim.py b/system/hardware/esim.py index 58ead6593f6..9b7d4f9ec0b 100755 --- a/system/hardware/esim.py +++ b/system/hardware/esim.py @@ -7,7 +7,6 @@ if __name__ == '__main__': parser = argparse.ArgumentParser(prog='esim.py', description='manage eSIM profiles on your comma device', epilog='comma.ai') - parser.add_argument('--backend', choices=['qmi', 'at'], default='qmi', help='use the specified backend, defaults to qmi') parser.add_argument('--switch', metavar='iccid', help='switch to profile') parser.add_argument('--delete', metavar='iccid', help='delete profile (warning: this cannot be undone)') parser.add_argument('--download', nargs=2, metavar=('qr', 'name'), help='download a profile using QR code (format: LPA:1$rsp.truphone.com$QRF-SPEEDTEST)') diff --git a/system/hardware/tici/esim.py b/system/hardware/tici/esim.py deleted file mode 100644 index b489286f50b..00000000000 --- a/system/hardware/tici/esim.py +++ /dev/null @@ -1,106 +0,0 @@ -import json -import os -import shutil -import subprocess -from typing import Literal - -from openpilot.system.hardware.base import LPABase, LPAError, LPAProfileNotFoundError, Profile - -class TiciLPA(LPABase): - def __init__(self, interface: Literal['qmi', 'at'] = 'qmi'): - self.env = os.environ.copy() - self.env['LPAC_APDU'] = interface - self.env['QMI_DEVICE'] = '/dev/cdc-wdm0' - self.env['AT_DEVICE'] = '/dev/ttyUSB2' - - self.timeout_sec = 45 - - if shutil.which('lpac') is None: - raise LPAError('lpac not found, must be installed!') - - def list_profiles(self) -> list[Profile]: - msgs = self._invoke('profile', 'list') - self._validate_successful(msgs) - return [Profile( - iccid=p['iccid'], - nickname=p['profileNickname'], - enabled=p['profileState'] == 'enabled', - provider=p['serviceProviderName'] - ) for p in msgs[-1]['payload']['data']] - - def get_active_profile(self) -> Profile | None: - return next((p for p in self.list_profiles() if p.enabled), None) - - def delete_profile(self, iccid: str) -> None: - self._validate_profile_exists(iccid) - latest = self.get_active_profile() - if latest is not None and latest.iccid == iccid: - raise LPAError('cannot delete active profile, switch to another profile first') - self._validate_successful(self._invoke('profile', 'delete', iccid)) - self._process_notifications() - - def download_profile(self, qr: str, nickname: str | None = None) -> None: - msgs = self._invoke('profile', 'download', '-a', qr) - self._validate_successful(msgs) - new_profile = next((m for m in msgs if m['payload']['message'] == 'es8p_meatadata_parse'), None) - if new_profile is None: - raise LPAError('no new profile found') - if nickname: - self.nickname_profile(new_profile['payload']['data']['iccid'], nickname) - self._process_notifications() - - def nickname_profile(self, iccid: str, nickname: str) -> None: - self._validate_profile_exists(iccid) - self._validate_successful(self._invoke('profile', 'nickname', iccid, nickname)) - - def switch_profile(self, iccid: str) -> None: - self._validate_profile_exists(iccid) - latest = self.get_active_profile() - if latest and latest.iccid == iccid: - return - self._validate_successful(self._invoke('profile', 'enable', iccid)) - self._process_notifications() - - def _invoke(self, *cmd: str): - proc = subprocess.Popen(['sudo', '-E', 'lpac'] + list(cmd), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=self.env) - try: - out, err = proc.communicate(timeout=self.timeout_sec) - except subprocess.TimeoutExpired as e: - proc.kill() - raise LPAError(f"lpac {cmd} timed out after {self.timeout_sec} seconds") from e - - messages = [] - for line in out.decode().strip().splitlines(): - if line.startswith('{'): - message = json.loads(line) - - # lpac response format validations - assert 'type' in message, 'expected type in message' - assert message['type'] == 'lpa' or message['type'] == 'progress', 'expected lpa or progress message type' - assert 'payload' in message, 'expected payload in message' - assert 'code' in message['payload'], 'expected code in message payload' - assert 'data' in message['payload'], 'expected data in message payload' - - msg_ret_code = message['payload']['code'] - if msg_ret_code != 0: - raise LPAError(f"lpac {' '.join(cmd)} failed with code {msg_ret_code}: <{message['payload']['message']}> {message['payload']['data']}") - - messages.append(message) - - if len(messages) == 0: - raise LPAError(f"lpac {cmd} returned no messages") - - return messages - - def _process_notifications(self) -> None: - """ - Process notifications stored on the eUICC, typically to activate/deactivate the profile with the carrier. - """ - self._validate_successful(self._invoke('notification', 'process', '-a', '-r')) - - def _validate_profile_exists(self, iccid: str) -> None: - if not any(p.iccid == iccid for p in self.list_profiles()): - raise LPAProfileNotFoundError(f'profile {iccid} does not exist') - - def _validate_successful(self, msgs: list[dict]) -> None: - assert msgs[-1]['payload']['message'] == 'success', 'expected success notification' diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 5a84afce03b..2295ca3cba5 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -12,7 +12,7 @@ from openpilot.common.gpio import gpio_set, gpio_init, get_irqs_for_action from openpilot.system.hardware.base import HardwareBase, LPABase, ThermalConfig, ThermalZone from openpilot.system.hardware.tici import iwlist -from openpilot.system.hardware.tici.esim import TiciLPA +from openpilot.system.hardware.tici.lpa import TiciLPA from openpilot.system.hardware.tici.pins import GPIO from openpilot.system.hardware.tici.amplifier import Amplifier diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py new file mode 100644 index 00000000000..9bd9d8c7b09 --- /dev/null +++ b/system/hardware/tici/lpa.py @@ -0,0 +1,24 @@ +from openpilot.system.hardware.base import LPABase, Profile + + +class TiciLPA(LPABase): + def __init__(self): + pass + + def list_profiles(self) -> list[Profile]: + return [] + + def get_active_profile(self) -> Profile | None: + return None + + def delete_profile(self, iccid: str) -> None: + return None + + def download_profile(self, qr: str, nickname: str | None = None) -> None: + return None + + def nickname_profile(self, iccid: str, nickname: str) -> None: + return None + + def switch_profile(self, iccid: str) -> None: + return None diff --git a/system/hardware/tici/tests/test_esim.py b/system/hardware/tici/tests/test_esim.py deleted file mode 100644 index 6fab931cced..00000000000 --- a/system/hardware/tici/tests/test_esim.py +++ /dev/null @@ -1,51 +0,0 @@ -import pytest - -from openpilot.system.hardware import HARDWARE, TICI -from openpilot.system.hardware.base import LPAProfileNotFoundError - -# https://euicc-manual.osmocom.org/docs/rsp/known-test-profile -# iccid is always the same for the given activation code -TEST_ACTIVATION_CODE = 'LPA:1$rsp.truphone.com$QRF-BETTERROAMING-PMRDGIR2EARDEIT5' -TEST_ICCID = '8944476500001944011' - -TEST_NICKNAME = 'test_profile' - -def cleanup(): - lpa = HARDWARE.get_sim_lpa() - try: - lpa.delete_profile(TEST_ICCID) - except LPAProfileNotFoundError: - pass - lpa.process_notifications() - -class TestEsim: - - @classmethod - def setup_class(cls): - if not TICI: - pytest.skip() - cleanup() - - @classmethod - def teardown_class(cls): - cleanup() - - def test_provision_enable_disable(self): - lpa = HARDWARE.get_sim_lpa() - current_active = lpa.get_active_profile() - - lpa.download_profile(TEST_ACTIVATION_CODE, TEST_NICKNAME) - assert any(p.iccid == TEST_ICCID and p.nickname == TEST_NICKNAME for p in lpa.list_profiles()) - - lpa.enable_profile(TEST_ICCID) - new_active = lpa.get_active_profile() - assert new_active is not None - assert new_active.iccid == TEST_ICCID - assert new_active.nickname == TEST_NICKNAME - - lpa.disable_profile(TEST_ICCID) - new_active = lpa.get_active_profile() - assert new_active is None - - if current_active: - lpa.enable_profile(current_active.iccid) From 140aa95523060e39dcd5300a269e05dcfe2155ca Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 19 Feb 2026 09:33:03 -0800 Subject: [PATCH 256/685] add kia k7 to release notes --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index 6191c6ba3d4..6ea887efce5 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,6 @@ Version 0.10.4 (2026-02-17) ======================== +* Kia K7 2017 support thanks to royjr! * Lexus LS 2018 support thanks to Hacheoy! Version 0.10.3 (2025-12-17) From 6853f1db29a0cef6f97a4d0a7b8bc65afd37fe75 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Thu, 19 Feb 2026 14:42:28 -0800 Subject: [PATCH 257/685] bump panda (#37265) --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index b1191df619d..e1da7dc918c 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit b1191df619dbc201b9444634a29e2c016fee6619 +Subproject commit e1da7dc918c0bcda6fbbdd9ee6f89c5428ec5039 From 8650ca837ffc37f736473d1052f982455ef44594 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Thu, 19 Feb 2026 14:50:48 -0800 Subject: [PATCH 258/685] add power reduction to release notes (#37266) --- RELEASES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASES.md b/RELEASES.md index 6ea887efce5..895dcbba7a2 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -2,6 +2,7 @@ Version 0.10.4 (2026-02-17) ======================== * Kia K7 2017 support thanks to royjr! * Lexus LS 2018 support thanks to Hacheoy! +* Reduce comma four standby power usage by 77% to 52 mW Version 0.10.3 (2025-12-17) ======================== From 93977e2ee23421ad4dbda7a61d7a51ae2eaa8452 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 19 Feb 2026 16:35:51 -0800 Subject: [PATCH 259/685] ui: fix side gradients (#37268) fix --- system/ui/widgets/scroller.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 5930a2a6ebf..69a50ed84b3 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -291,14 +291,14 @@ def _render(self, _): # Draw edge shadows on top of scroller content if self._edge_shadows: - rl.draw_rectangle_gradient_h(int(self._rect.x), int(self._rect.y), - EDGE_SHADOW_WIDTH, int(self._rect.y), - rl.Color(0, 0, 0, 166), rl.BLANK) + rl.draw_rectangle_gradient_h(int(self._rect.x), 0, + EDGE_SHADOW_WIDTH, int(self._rect.height), + rl.Color(0, 0, 0, 204), rl.BLANK) right_x = int(self._rect.x + self._rect.width - EDGE_SHADOW_WIDTH) - rl.draw_rectangle_gradient_h(right_x, int(self._rect.y), - EDGE_SHADOW_WIDTH, int(self._rect.y), - rl.BLANK, rl.Color(0, 0, 0, 166)) + rl.draw_rectangle_gradient_h(right_x, 0, + EDGE_SHADOW_WIDTH, int(self._rect.height), + rl.BLANK, rl.Color(0, 0, 0, 204)) # Draw scroll indicator on top of edge shadows if self._show_scroll_indicator and len(self._visible_items) > 0: From 6ecb1060be9e69696461fe3190cf93915cda00e3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 19 Feb 2026 16:36:35 -0800 Subject: [PATCH 260/685] ui: normalize ssids for 3X (#37269) * fix for tici * clean up --- selfdrive/ui/mici/layouts/settings/network/__init__.py | 4 ++-- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 6 +----- system/ui/lib/wifi_manager.py | 4 ++++ system/ui/widgets/network.py | 9 +++++---- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 1940b680eb9..cd0f4ee80f2 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -3,14 +3,14 @@ from collections.abc import Callable from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon, normalize_ssid +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, normalize_ssid class NetworkPanelType(IntEnum): diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index e8fefb82dd5..34e73e823eb 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -8,11 +8,7 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigInputDialog, BigDialogOptionButton, BigConfirmationDialogV2 from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget, NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, WifiState - - -def normalize_ssid(ssid: str) -> str: - return ssid.replace("’", "'") # for iPhone hotspots +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, WifiState, normalize_ssid class LoadingAnimation(Widget): diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index a5b5833d7f2..25c4548e945 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -40,6 +40,10 @@ _dbus_call_idx = 0 +def normalize_ssid(ssid: str) -> str: + return ssid.replace("’", "'") # for iPhone hotspots + + def _wrap_router(router): def _wrap(orig): def wrapper(msg, **kw): diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 3e5e4a1d55d..1f146fbdd19 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -6,7 +6,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel -from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType +from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType, normalize_ssid from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog @@ -311,12 +311,13 @@ def _render(self, rect: rl.Rectangle): return if self.state == UIState.NEEDS_AUTH and self._state_network: - self.keyboard.set_title(tr("Wrong password") if self._password_retry else tr("Enter password"), tr("for \"{}\"").format(self._state_network.ssid)) + self.keyboard.set_title(tr("Wrong password") if self._password_retry else tr("Enter password"), + tr("for \"{}\"").format(normalize_ssid(self._state_network.ssid))) self.keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(Network, self._state_network), result)) elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network: confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel")) - confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(self._state_network.ssid)) + confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(normalize_ssid(self._state_network.ssid))) confirm_dialog.reset() gui_app.set_modal_overlay(confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result)) else: @@ -445,7 +446,7 @@ def forget_network(self, network: Network): def _on_network_updated(self, networks: list[Network]): self._networks = networks for n in self._networks: - self._networks_buttons[n.ssid] = Button(n.ssid, partial(self._networks_buttons_callback, n), font_size=55, + self._networks_buttons[n.ssid] = Button(normalize_ssid(n.ssid), partial(self._networks_buttons_callback, n), font_size=55, text_alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT, button_style=ButtonStyle.TRANSPARENT_WHITE_TEXT) self._networks_buttons[n.ssid].set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid()) self._forget_networks_buttons[n.ssid] = Button(tr("Forget"), partial(self._forget_networks_buttons_callback, n), button_style=ButtonStyle.FORGET_WIFI, From 48568cba0bd24197c6f3a85f960d5250cc6ab20c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 19 Feb 2026 22:32:26 -0800 Subject: [PATCH 261/685] mici training guide: fix memory leak each time you open dialog (#37270) * fix * meh * unclaud test is best * yess * try * works! * remove dict handling * clean up * more clean up * remove trash * fixup * fix up onboarding again * fix drivercameradialog * don't show test window * more widgets * fix all * Revert "fix all" This reverts commit 42d3537c9314af382961a16443a63faed202b157. * move and whitelist * clean up * more test + ignore * to fix * temp * Revert "temp" This reverts commit 215ecbb8a8fc0e6826d45b2c0d57999c7a19a400. --- selfdrive/ui/mici/layouts/onboarding.py | 7 +- selfdrive/ui/mici/tests/test_widget_leaks.py | 118 +++++++++++++++++++ 2 files changed, 123 insertions(+), 2 deletions(-) create mode 100755 selfdrive/ui/mici/tests/test_widget_leaks.py diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 4248fef2ecc..a399d7679cf 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -124,8 +124,11 @@ class TrainingGuideDMTutorial(Widget): def __init__(self, continue_callback): super().__init__() + + self_ref = weakref.ref(self) + self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 28, 48)) - self._back_button.set_click_callback(self._show_bad_face_page) + self._back_button.set_click_callback(lambda: self_ref() and self_ref()._show_bad_face_page()) self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 42, 42)) # Wrap the continue callback to restore settings @@ -138,7 +141,7 @@ def wrapped_continue_callback(): self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps) self._dialog = DriverCameraSetupDialog() - self._bad_face_page = DMBadFaceDetected(HARDWARE.shutdown, self._hide_bad_face_page) + self._bad_face_page = DMBadFaceDetected(HARDWARE.shutdown, lambda: self_ref() and self_ref()._hide_bad_face_page()) self._should_show_bad_face_page = False # Disable driver monitoring model when device times out for inactivity diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py new file mode 100755 index 00000000000..ffa256b716a --- /dev/null +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -0,0 +1,118 @@ +import pyray as rl +rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) +import gc +import weakref +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import Widget + +# mici dialogs +from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow +from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog +from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2, BigInputDialog, BigMultiOptionDialog +from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal + +# tici dialogs +from openpilot.selfdrive.ui.onroad.driver_camera_dialog import DriverCameraDialog as TiciDriverCameraDialog +from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow as TiciOnboardingWindow +from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog as TiciPairingDialog +from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog +from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog +from openpilot.system.ui.widgets.html_render import HtmlModal +from openpilot.system.ui.widgets.keyboard import Keyboard + +# FIXME: known small leaks not worth worrying about at the moment +KNOWN_LEAKS = { + "openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog.DriverCameraView", + "openpilot.selfdrive.ui.mici.layouts.onboarding.TermsPage", + "openpilot.selfdrive.ui.mici.layouts.onboarding.TrainingGuide", + "openpilot.selfdrive.ui.mici.layouts.onboarding.DeclinePage", + "openpilot.selfdrive.ui.mici.layouts.onboarding.OnboardingWindow", + "openpilot.selfdrive.ui.onroad.driver_state.DriverStateRenderer", + "openpilot.selfdrive.ui.onroad.driver_camera_dialog.DriverCameraDialog", + "openpilot.selfdrive.ui.layouts.onboarding.TermsPage", + "openpilot.selfdrive.ui.layouts.onboarding.DeclinePage", + "openpilot.selfdrive.ui.layouts.onboarding.OnboardingWindow", + "openpilot.system.ui.widgets.confirm_dialog.ConfirmDialog", + "openpilot.system.ui.widgets.label.Label", + "openpilot.system.ui.widgets.button.Button", + "openpilot.selfdrive.ui.mici.widgets.dialog.BigDialog", + "openpilot.system.ui.widgets.html_render.HtmlRenderer", + "openpilot.system.ui.widgets.NavBar", + "openpilot.system.ui.widgets.inputbox.InputBox", + "openpilot.system.ui.widgets.scroller_tici.Scroller", + "openpilot.system.ui.widgets.scroller.Scroller", + "openpilot.system.ui.widgets.label.UnifiedLabel", + "openpilot.selfdrive.ui.mici.widgets.dialog.BigMultiOptionDialog", + "openpilot.system.ui.widgets.mici_keyboard.MiciKeyboard", + "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialogV2", + "openpilot.system.ui.widgets.keyboard.Keyboard", + "openpilot.system.ui.widgets.slider.BigSlider", + "openpilot.selfdrive.ui.mici.widgets.dialog.BigInputDialog", + "openpilot.system.ui.widgets.option_dialog.MultiOptionDialog", +} + + +def get_child_widgets(widget: Widget) -> list[Widget]: + children = [] + for val in widget.__dict__.values(): + items = val if isinstance(val, (list, tuple)) else (val,) + children.extend(w for w in items if isinstance(w, Widget)) + return children + + +def test_dialogs_do_not_leak(): + gui_app.init_window("ref-test") + + leaked_widgets = set() + + for ctor in ( + # mici + MiciDriverCameraDialog, MiciTrainingGuide, MiciOnboardingWindow, MiciPairingDialog, + lambda: BigDialog("test", "test"), + lambda: BigConfirmationDialogV2("test", "icons_mici/settings/network/new/trash.png"), + lambda: BigInputDialog("test"), + lambda: BigMultiOptionDialog(["a", "b"], "a"), + lambda: MiciFccModal(text="test"), + # tici + TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard, + lambda: ConfirmDialog("test", "ok"), + lambda: MultiOptionDialog("test", ["a", "b"]), + lambda: HtmlModal(text="test"), + ): + widget = ctor() + all_refs = [weakref.ref(w) for w in get_child_widgets(widget) + [widget]] + + del widget + + for ref in all_refs: + if ref() is not None: + obj = ref() + name = f"{type(obj).__module__}.{type(obj).__qualname__}" + leaked_widgets.add(name) + + print(f"\n=== Widget {name} alive after del") + print(" Referrers:") + for r in gc.get_referrers(obj): + if r is obj: + continue + + if hasattr(r, '__self__') and r.__self__ is not obj: + print(f" bound method: {type(r.__self__).__qualname__}.{r.__name__}") + elif hasattr(r, '__func__'): + print(f" method: {r.__name__}") + else: + print(f" {type(r).__module__}.{type(r).__qualname__}") + del obj + + gui_app.close() + + unexpected = leaked_widgets - KNOWN_LEAKS + assert not unexpected, f"New leaked widgets: {unexpected}" + + fixed = KNOWN_LEAKS - leaked_widgets + assert not fixed, f"These leaks are fixed, remove from KNOWN_LEAKS: {fixed}" + + +if __name__ == "__main__": + test_dialogs_do_not_leak() From e54c0091bc4f8aaef8296f1b24ce10d00574c3b2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 19 Feb 2026 22:49:23 -0800 Subject: [PATCH 262/685] tici ui: always show regulatory button (#37273) * i knew it * clean up --- selfdrive/ui/layouts/settings/device.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 00ae6a188ea..0a2f2dacae6 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -9,7 +9,6 @@ from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.layouts.onboarding import TrainingGuide from openpilot.selfdrive.ui.widgets.pairing_dialog import PairingDialog -from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.lib.multilang import multilang, tr, tr_noop from openpilot.system.ui.widgets import Widget, DialogResult @@ -64,11 +63,10 @@ def _initialize_items(self): self._reset_calib_btn, button_item(lambda: tr("Review Training Guide"), lambda: tr("REVIEW"), lambda: tr(DESCRIPTIONS['review_guide']), self._on_review_training_guide, enabled=ui_state.is_offroad), - regulatory_btn := button_item(lambda: tr("Regulatory"), lambda: tr("VIEW"), callback=self._on_regulatory, enabled=ui_state.is_offroad), + button_item(lambda: tr("Regulatory"), lambda: tr("VIEW"), callback=self._on_regulatory, enabled=ui_state.is_offroad), button_item(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog), self._power_off_btn, ] - regulatory_btn.set_visible(TICI) return items def _offroad_transition(self): From 6bd3cab8a8855b2714b169f5957eaab88f6fbbc9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 19 Feb 2026 22:52:21 -0800 Subject: [PATCH 263/685] edge shadows should use widget y --- system/ui/widgets/scroller.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 69a50ed84b3..9bb7883215d 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -291,12 +291,12 @@ def _render(self, _): # Draw edge shadows on top of scroller content if self._edge_shadows: - rl.draw_rectangle_gradient_h(int(self._rect.x), 0, + rl.draw_rectangle_gradient_h(int(self._rect.x), int(self._rect.y), EDGE_SHADOW_WIDTH, int(self._rect.height), rl.Color(0, 0, 0, 204), rl.BLANK) right_x = int(self._rect.x + self._rect.width - EDGE_SHADOW_WIDTH) - rl.draw_rectangle_gradient_h(right_x, 0, + rl.draw_rectangle_gradient_h(right_x, int(self._rect.y), EDGE_SHADOW_WIDTH, int(self._rect.height), rl.BLANK, rl.Color(0, 0, 0, 204)) From f829c90de6d62840dc2a6bb2180c639fd37c2f56 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 20 Feb 2026 02:40:41 -0800 Subject: [PATCH 264/685] skip widget leak test --- selfdrive/ui/mici/tests/test_widget_leaks.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index ffa256b716a..12fa608b360 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -2,6 +2,7 @@ rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) import gc import weakref +import pytest from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget @@ -61,6 +62,7 @@ def get_child_widgets(widget: Widget) -> list[Widget]: return children +@pytest.mark.skip(reason="segfaults") def test_dialogs_do_not_leak(): gui_app.init_window("ref-test") From cefddf4b9b6ea5eb40c9b3fd44dd331793a12e9a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 20 Feb 2026 02:43:11 -0800 Subject: [PATCH 265/685] ui: add navigation stack for tici (#37275) * initial * start to support nav stack in settings panels + fix some navwidget bugs * add deprecation warning and move more to new nav stack * fix overriding NavWidget enabled and do developer panel * fix interactive timeout and do main * more device, not done yet * minor network fixes * dcam dialog * start onboarding * fix onboarding * do mici setup * remove now useless CUSTOM_SOFTWARE * support big ui with old modal overlay * reset can be old modal overlay, but updater needs new since it uses wifiui * flip name truthiness to inspire excitement * all *should* work, but will do pass later * clean up main * clean up settiings * clean up dialog and developer * cleanup mici setup some * rm one more * fix keyboard * revert * might as well but clarify * fix networkinfopage buttons * lint * nice clean up from cursor * animate background fade with position * fix device overlays * cursor fix pt1 cursor fix pt2 * rm print * capital * temp fix from cursor for onboarding not freeing space after reviewing training guide * fix home screen scroller snap not resetting * stash * nice gradient on top * 40 * 20 * no gradient * return unused returns and always show regulatory btn * nice! * revert selfdrive/ui * let's do tici first * bring back ui * not sure why __del__, SetupWidget was never deleted? * device "done" * network "done!!" * toggles "done" * software "done" * developer "done" * fix onboarding * use new modal for debug windows * and aug * setup "done" * clean up * updater "done" * reset "done" * pop first before callbacks in case callbacks push * fix cmt * not needed * remove two commented functions for mici * clean up application * typing * static * not sure what this means * fix big * more static * actually great catch * fix cmt --- selfdrive/ui/layouts/main.py | 6 +- selfdrive/ui/layouts/onboarding.py | 6 +- selfdrive/ui/layouts/settings/developer.py | 6 +- selfdrive/ui/layouts/settings/device.py | 67 ++++++++------------- selfdrive/ui/layouts/settings/software.py | 12 ++-- selfdrive/ui/layouts/settings/toggles.py | 6 +- selfdrive/ui/onroad/augmented_road_view.py | 4 +- selfdrive/ui/onroad/driver_camera_dialog.py | 8 +-- selfdrive/ui/tests/diff/replay.py | 2 +- selfdrive/ui/ui.py | 13 ++-- selfdrive/ui/widgets/pairing_dialog.py | 11 ++-- selfdrive/ui/widgets/setup.py | 14 ++--- selfdrive/ui/widgets/ssh_key.py | 5 +- system/ui/lib/application.py | 58 +++++++++++++++--- system/ui/tici_reset.py | 38 ++++++------ system/ui/tici_setup.py | 17 +++--- system/ui/tici_updater.py | 9 ++- system/ui/widgets/confirm_dialog.py | 22 +++---- system/ui/widgets/html_render.py | 2 +- system/ui/widgets/keyboard.py | 43 +++++++------ system/ui/widgets/network.py | 57 ++++++++++-------- system/ui/widgets/option_dialog.py | 13 ++-- 22 files changed, 231 insertions(+), 188 deletions(-) diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 702854f98a7..15d44e24da5 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -36,10 +36,12 @@ def __init__(self): # Set callbacks self._setup_callbacks() - # Start onboarding if terms or training not completed + gui_app.push_widget(self) + + # Start onboarding if terms or training not completed, make sure to push after self self._onboarding_window = OnboardingWindow() if not self._onboarding_window.completed: - gui_app.set_modal_overlay(self._onboarding_window) + gui_app.push_widget(self._onboarding_window) def _render(self, _): self._handle_onroad_transition() diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index 5d61c1c95a3..25294511d4d 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -81,6 +81,9 @@ def _handle_mouse_release(self, mouse_pos): if self._completed_callback: self._completed_callback() + # NOTE: this pops OnboardingWindow during real onboarding + gui_app.pop_widget() + def _update_state(self): if len(self._image_objs): self._textures.append(gui_app._load_texture_from_image(self._image_objs.pop(0))) @@ -194,11 +197,10 @@ def _on_terms_accepted(self): ui_state.params.put("HasAcceptedTerms", terms_version) self._state = OnboardingState.ONBOARDING if self._training_done: - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def _on_completed_training(self): ui_state.params.put("CompletedTrainingVersion", training_version) - gui_app.set_modal_overlay(None) def _render(self, _): if self._training_guide is None: diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 646c817508d..17ab60172a5 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -164,7 +164,7 @@ def _on_long_maneuver_mode(self, state: bool): def _on_alpha_long_enabled(self, state: bool): if state: - def confirm_callback(result: int): + def confirm_callback(result: DialogResult): if result == DialogResult.CONFIRM: self._params.put_bool("AlphaLongitudinalEnabled", True) self._params.put_bool("OnroadCycleRequested", True) @@ -176,8 +176,8 @@ def confirm_callback(result: int): content = (f"

{self._alpha_long_toggle.title}


" + f"

{self._alpha_long_toggle.description}

") - dlg = ConfirmDialog(content, tr("Enable"), rich=True) - gui_app.set_modal_overlay(dlg, callback=confirm_callback) + dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback) + gui_app.push_widget(dlg) else: self._params.put_bool("AlphaLongitudinalEnabled", False) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 0a2f2dacae6..751373dba63 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -33,8 +33,6 @@ def __init__(self): self._params = Params() self._select_language_dialog: MultiOptionDialog | None = None - self._driver_camera: DriverCameraDialog | None = None - self._pair_device_dialog: PairingDialog | None = None self._fcc_dialog: HtmlModal | None = None self._training_guide: TrainingGuide | None = None @@ -44,7 +42,8 @@ def __init__(self): ui_state.add_offroad_transition_callback(self._offroad_transition) def _initialize_items(self): - self._pair_device_btn = button_item(lambda: tr("Pair Device"), lambda: tr("PAIR"), lambda: tr(DESCRIPTIONS['pair_device']), callback=self._pair_device) + self._pair_device_btn = button_item(lambda: tr("Pair Device"), lambda: tr("PAIR"), lambda: tr(DESCRIPTIONS['pair_device']), + callback=lambda: gui_app.push_widget(PairingDialog())) self._pair_device_btn.set_visible(lambda: not ui_state.prime_state.is_paired()) self._reset_calib_btn = button_item(lambda: tr("Reset Calibration"), lambda: tr("RESET"), lambda: tr(DESCRIPTIONS['reset_calibration']), @@ -59,7 +58,7 @@ def _initialize_items(self): text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))), self._pair_device_btn, button_item(lambda: tr("Driver Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['driver_camera']), - callback=self._show_driver_camera, enabled=ui_state.is_offroad), + callback=lambda: gui_app.push_widget(DriverCameraDialog()), enabled=ui_state.is_offroad), self._reset_calib_btn, button_item(lambda: tr("Review Training Guide"), lambda: tr("REVIEW"), lambda: tr(DESCRIPTIONS['review_guide']), self._on_review_training_guide, enabled=ui_state.is_offroad), @@ -79,29 +78,23 @@ def _render(self, rect): self._scroller.render(rect) def _show_language_dialog(self): - def handle_language_selection(result: int): - if result == 1 and self._select_language_dialog: + def handle_language_selection(result: DialogResult): + if result == DialogResult.CONFIRM and self._select_language_dialog: selected_language = multilang.languages[self._select_language_dialog.selection] multilang.change_language(selected_language) self._update_calib_description() self._select_language_dialog = None self._select_language_dialog = MultiOptionDialog(tr("Select a language"), multilang.languages, multilang.codes[multilang.language], - option_font_weight=FontWeight.UNIFONT) - gui_app.set_modal_overlay(self._select_language_dialog, callback=handle_language_selection) - - def _show_driver_camera(self): - if not self._driver_camera: - self._driver_camera = DriverCameraDialog() - - gui_app.set_modal_overlay(self._driver_camera, callback=lambda result: setattr(self, '_driver_camera', None)) + option_font_weight=FontWeight.UNIFONT, callback=handle_language_selection) + gui_app.push_widget(self._select_language_dialog) def _reset_calibration_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reset Calibration"))) + gui_app.push_widget(alert_dialog(tr("Disengage to Reset Calibration"))) return - def reset_calibration(result: int): + def reset_calibration(result: DialogResult): # Check engaged again in case it changed while the dialog was open if ui_state.engaged or result != DialogResult.CONFIRM: return @@ -114,8 +107,8 @@ def reset_calibration(result: int): self._params.put_bool("OnroadCycleRequested", True) self._update_calib_description() - dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset")) - gui_app.set_modal_overlay(dialog, callback=reset_calibration) + dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset"), callback=reset_calibration) + gui_app.push_widget(dialog) def _update_calib_description(self): desc = tr(DESCRIPTIONS['reset_calibration']) @@ -167,42 +160,34 @@ def _update_calib_description(self): def _reboot_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reboot"))) + gui_app.push_widget(alert_dialog(tr("Disengage to Reboot"))) return - dialog = ConfirmDialog(tr("Are you sure you want to reboot?"), tr("Reboot")) - gui_app.set_modal_overlay(dialog, callback=self._perform_reboot) + def perform_reboot(result: DialogResult): + if not ui_state.engaged and result == DialogResult.CONFIRM: + self._params.put_bool_nonblocking("DoReboot", True) - def _perform_reboot(self, result: int): - if not ui_state.engaged and result == DialogResult.CONFIRM: - self._params.put_bool_nonblocking("DoReboot", True) + dialog = ConfirmDialog(tr("Are you sure you want to reboot?"), tr("Reboot"), callback=perform_reboot) + gui_app.push_widget(dialog) def _power_off_prompt(self): if ui_state.engaged: - gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Power Off"))) + gui_app.push_widget(alert_dialog(tr("Disengage to Power Off"))) return - dialog = ConfirmDialog(tr("Are you sure you want to power off?"), tr("Power Off")) - gui_app.set_modal_overlay(dialog, callback=self._perform_power_off) + def perform_power_off(result: DialogResult): + if not ui_state.engaged and result == DialogResult.CONFIRM: + self._params.put_bool_nonblocking("DoShutdown", True) - def _perform_power_off(self, result: int): - if not ui_state.engaged and result == DialogResult.CONFIRM: - self._params.put_bool_nonblocking("DoShutdown", True) - - def _pair_device(self): - if not self._pair_device_dialog: - self._pair_device_dialog = PairingDialog() - gui_app.set_modal_overlay(self._pair_device_dialog, callback=lambda result: setattr(self, '_pair_device_dialog', None)) + dialog = ConfirmDialog(tr("Are you sure you want to power off?"), tr("Power Off"), callback=perform_power_off) + gui_app.push_widget(dialog) def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = HtmlModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/fcc.html")) - gui_app.set_modal_overlay(self._fcc_dialog) + gui_app.push_widget(self._fcc_dialog) def _on_review_training_guide(self): if not self._training_guide: - def completed_callback(): - gui_app.set_modal_overlay(None) - - self._training_guide = TrainingGuide(completed_callback=completed_callback) - gui_app.set_modal_overlay(self._training_guide) + self._training_guide = TrainingGuide() + gui_app.push_widget(self._training_guide) diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index e0df8f27056..c197b45453e 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -165,12 +165,12 @@ def _on_download_update(self): os.system("pkill -SIGHUP -f system.updated.updated") def _on_uninstall(self): - def handle_uninstall_confirmation(result): + def handle_uninstall_confirmation(result: DialogResult): if result == DialogResult.CONFIRM: ui_state.params.put_bool("DoUninstall", True) - dialog = ConfirmDialog(tr("Are you sure you want to uninstall?"), tr("Uninstall")) - gui_app.set_modal_overlay(dialog, callback=handle_uninstall_confirmation) + dialog = ConfirmDialog(tr("Are you sure you want to uninstall?"), tr("Uninstall"), callback=handle_uninstall_confirmation) + gui_app.push_widget(dialog) def _on_install_update(self): # Trigger reboot to install update @@ -189,9 +189,8 @@ def _on_select_branch(self): branches.insert(0, b) current_target = ui_state.params.get("UpdaterTargetBranch") or "" - self._branch_dialog = MultiOptionDialog(tr("Select a branch"), branches, current_target) - def handle_selection(result): + def handle_selection(result: DialogResult): # Confirmed selection if result == DialogResult.CONFIRM and self._branch_dialog is not None and self._branch_dialog.selection: selection = self._branch_dialog.selection @@ -200,4 +199,5 @@ def handle_selection(result): os.system("pkill -SIGUSR1 -f system.updated.updated") self._branch_dialog = None - gui_app.set_modal_overlay(self._branch_dialog, callback=handle_selection) + self._branch_dialog = MultiOptionDialog(tr("Select a branch"), branches, current_target, callback=handle_selection) + gui_app.push_widget(self._branch_dialog) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 7fae2dfd244..dbe5e241aac 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -214,7 +214,7 @@ def _update_experimental_mode_icon(self): def _handle_experimental_mode_toggle(self, state: bool): confirmed = self._params.get_bool("ExperimentalModeConfirmed") if state and not confirmed: - def confirm_callback(result: int): + def confirm_callback(result: DialogResult): if result == DialogResult.CONFIRM: self._params.put_bool("ExperimentalMode", True) self._params.put_bool("ExperimentalModeConfirmed", True) @@ -225,8 +225,8 @@ def confirm_callback(result: int): # show confirmation dialog content = (f"

{self._toggles['ExperimentalMode'].title}


" + f"

{self._toggles['ExperimentalMode'].description}

") - dlg = ConfirmDialog(content, tr("Enable"), rich=True) - gui_app.set_modal_overlay(dlg, callback=confirm_callback) + dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback) + gui_app.push_widget(dlg) else: self._update_experimental_mode_icon() self._params.put_bool("ExperimentalMode", state) diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index 1f202141c38..f8fb589b617 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -219,8 +219,9 @@ def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: if __name__ == "__main__": - gui_app.init_window("OnRoad Camera View") + gui_app.init_window("OnRoad Camera View", new_modal=True) road_camera_view = AugmentedRoadView(ROAD_CAM) + gui_app.push_widget(road_camera_view) print("***press space to switch camera view***") try: for _ in gui_app.render(): @@ -229,6 +230,5 @@ def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: if WIDE_CAM in road_camera_view.available_streams: stream = ROAD_CAM if road_camera_view.stream_type == WIDE_CAM else WIDE_CAM road_camera_view.switch_stream(stream) - road_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) finally: road_camera_view.close() diff --git a/selfdrive/ui/onroad/driver_camera_dialog.py b/selfdrive/ui/onroad/driver_camera_dialog.py index f69ad8c49cf..a4518a25200 100644 --- a/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/onroad/driver_camera_dialog.py @@ -14,7 +14,7 @@ def __init__(self): super().__init__("camerad", VisionStreamType.VISION_STREAM_DRIVER) self.driver_state_renderer = DriverStateRenderer() # TODO: this can grow unbounded, should be given some thought - device.add_interactive_timeout_callback(lambda: gui_app.set_modal_overlay(None)) + device.add_interactive_timeout_callback(gui_app.pop_widget) ui_state.params.put_bool("IsDriverViewEnabled", True) def hide_event(self): @@ -24,7 +24,7 @@ def hide_event(self): def _handle_mouse_release(self, _): super()._handle_mouse_release(_) - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def __del__(self): self.close() @@ -100,12 +100,12 @@ def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: if __name__ == "__main__": - gui_app.init_window("Driver Camera View") + gui_app.init_window("Driver Camera View", new_modal=True) driver_camera_view = DriverCameraDialog() + gui_app.push_widget(driver_camera_view) try: for _ in gui_app.render(): ui_state.update() - driver_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) finally: driver_camera_view.close() diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index e424d11f623..62a808209ac 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -38,7 +38,7 @@ def run_replay(variant: LayoutVariant) -> None: from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage from openpilot.selfdrive.ui.tests.diff.replay_script import build_script - gui_app.init_window("ui diff test", fps=FPS) + gui_app.init_window("ui diff test", fps=FPS, new_modal=variant == "tizi") # Dynamically import main layout based on variant if variant == "mici": diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index 7fe0dfbbc9a..0e271c72db0 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -9,21 +9,26 @@ from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout from openpilot.selfdrive.ui.ui_state import ui_state +BIG_UI = gui_app.big_ui() + def main(): cores = {5, } config_realtime_process(0, 51) - gui_app.init_window("UI") - if gui_app.big_ui(): + if BIG_UI: + gui_app.init_window("UI", new_modal=True) main_layout = MainLayout() else: + gui_app.init_window("UI") main_layout = MiciMainLayout() - main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + for should_render in gui_app.render(): ui_state.update() if should_render: - main_layout.render() + if not BIG_UI: + main_layout.render() # reaffine after power save offlines our core if TICI and os.sched_getaffinity(0) != cores: diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index f960cf723ee..c07b2463f34 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -26,7 +26,7 @@ def __init__(self): self.qr_texture: rl.Texture | None = None self.last_qr_generation = float('-inf') self._close_btn = IconButton(gui_app.texture("icons/close.png", 80, 80)) - self._close_btn.set_click_callback(lambda: gui_app.set_modal_overlay(None)) + self._close_btn.set_click_callback(gui_app.pop_widget) def _get_pairing_url(self) -> str: try: @@ -69,7 +69,7 @@ def _check_qr_refresh(self) -> None: def _update_state(self): if ui_state.prime_state.is_paired(): - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def _render(self, rect: rl.Rectangle) -> int: rl.clear_background(rl.Color(224, 224, 224, 255)) @@ -160,12 +160,11 @@ def __del__(self): if __name__ == "__main__": - gui_app.init_window("pairing device") + gui_app.init_window("pairing device", new_modal=True) pairing = PairingDialog() + gui_app.push_widget(pairing) try: for _ in gui_app.render(): - result = pairing.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - if result != -1: - break + pass finally: del pairing diff --git a/selfdrive/ui/widgets/setup.py b/selfdrive/ui/widgets/setup.py index 3c9406688f2..c9452fc5350 100644 --- a/selfdrive/ui/widgets/setup.py +++ b/selfdrive/ui/widgets/setup.py @@ -15,7 +15,6 @@ class SetupWidget(Widget): def __init__(self): super().__init__() self._open_settings_callback = None - self._pairing_dialog: PairingDialog | None = None self._pair_device_btn = Button(lambda: tr("Pair device"), self._show_pairing, button_style=ButtonStyle.PRIMARY) self._open_settings_btn = Button(lambda: tr("Open"), lambda: self._open_settings_callback() if self._open_settings_callback else None, button_style=ButtonStyle.PRIMARY) @@ -86,16 +85,11 @@ def _render_firehose_prompt(self, rect: rl.Rectangle): button_rect = rl.Rectangle(x, y, w, button_height) self._open_settings_btn.render(button_rect) - def _show_pairing(self): + @staticmethod + def _show_pairing(): if not system_time_valid(): dlg = alert_dialog(tr("Please connect to Wi-Fi to complete initial pairing")) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) return - if not self._pairing_dialog: - self._pairing_dialog = PairingDialog() - gui_app.set_modal_overlay(self._pairing_dialog, lambda result: setattr(self, '_pairing_dialog', None)) - - def __del__(self): - if self._pairing_dialog: - del self._pairing_dialog + gui_app.push_widget(PairingDialog()) diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index 88389cb0532..b31a9eb3bd1 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -59,7 +59,7 @@ def _render(self, rect: rl.Rectangle) -> bool: # Show error dialog if there's an error if self._error_message: message = copy.copy(self._error_message) - gui_app.set_modal_overlay(alert_dialog(message)) + gui_app.push_widget(alert_dialog(message)) self._username = "" self._error_message = "" @@ -87,7 +87,8 @@ def _handle_button_click(self): if self._state == SshKeyActionState.ADD: self._keyboard.reset() self._keyboard.set_title(tr("Enter your GitHub username")) - gui_app.set_modal_overlay(self._keyboard, callback=self._on_username_submit) + self._keyboard.set_callback(self._on_username_submit) + gui_app.push_widget(self._keyboard) elif self._state == SshKeyActionState.REMOVE: self._params.remove("GithubUsername") self._params.remove("GithubSshKeys") diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 755de674dea..f056fddbf0e 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -230,6 +230,10 @@ def __init__(self, width: int | None = None, height: int | None = None): self._modal_overlay_shown = False self._modal_overlay_tick: Callable[[], None] | None = None + # TODO: move over the entire ui and deprecate + self._new_modal = False + self._nav_stack: list[object] = [] + self._mouse = MouseState(self._scale) self._mouse_events: list[MouseEvent] = [] self._last_mouse_event: MouseEvent = MouseEvent(MousePos(0, 0), 0, False, False, False, 0.0) @@ -262,7 +266,7 @@ def target_fps(self): def request_close(self): self._window_close_requested = True - def init_window(self, title: str, fps: int = _DEFAULT_FPS): + def init_window(self, title: str, fps: int = _DEFAULT_FPS, new_modal: bool = False): with self._startup_profile_context(): def _close(sig, frame): self.close() @@ -270,6 +274,8 @@ def _close(sig, frame): signal.signal(signal.SIGINT, _close) atexit.register(self.close) + self._new_modal = new_modal + flags = rl.ConfigFlags.FLAG_MSAA_4X_HINT if ENABLE_VSYNC: flags |= rl.ConfigFlags.FLAG_VSYNC_HINT @@ -373,7 +379,34 @@ def _ffmpeg_writer_thread(self): except Exception: break + def push_widget(self, widget: object): + assert self._new_modal + + # disable previous widget to prevent input processing + if len(self._nav_stack) > 0: + prev_widget = self._nav_stack[-1] + prev_widget.set_enabled(False) + + self._nav_stack.append(widget) + widget.show_event() + + def pop_widget(self): + assert self._new_modal + + if len(self._nav_stack) < 2: + cloudlog.warning("At least one widget should remain on the stack, ignoring pop") + return + + # re-enable previous widget and pop current + prev_widget = self._nav_stack[-2] + prev_widget.set_enabled(True) + + widget = self._nav_stack.pop() + widget.hide_event() + def set_modal_overlay(self, overlay, callback: Callable | None = None): + assert not self._new_modal, "set_modal_overlay is deprecated, use push_widget instead" + if self._modal_overlay.overlay is not None: if hasattr(self._modal_overlay.overlay, 'hide_event'): self._modal_overlay.overlay.hide_event() @@ -528,15 +561,24 @@ def render(self): rl.begin_drawing() rl.clear_background(rl.BLACK) - # Handle modal overlay rendering and input processing - if self._handle_modal_overlay(): - # Allow a Widget to still run a function while overlay is shown - if self._modal_overlay_tick is not None: - self._modal_overlay_tick() - yield False - else: + if self._new_modal: + # Only render last widget + for widget in self._nav_stack[-1:]: + widget.render(rl.Rectangle(0, 0, self.width, self.height)) + + # Yield to allow caller to run non-rendering related code yield True + else: + # Handle modal overlay rendering and input processing + if self._handle_modal_overlay(): + # Allow a Widget to still run a function while overlay is shown + if self._modal_overlay_tick is not None: + self._modal_overlay_tick() + yield False + else: + yield True + if self._render_texture: rl.end_texture_mode() rl.begin_drawing() diff --git a/system/ui/tici_reset.py b/system/ui/tici_reset.py index 3922c27aac6..b22b2408508 100755 --- a/system/ui/tici_reset.py +++ b/system/ui/tici_reset.py @@ -36,13 +36,9 @@ def __init__(self, mode): self._mode = mode self._previous_reset_state = None self._reset_state = ResetState.NONE - self._cancel_button = Button("Cancel", self._cancel_callback) + self._cancel_button = Button("Cancel", gui_app.request_close) self._confirm_button = Button("Confirm", self._confirm, button_style=ButtonStyle.PRIMARY) self._reboot_button = Button("Reboot", lambda: os.system("sudo reboot")) - self._render_status = True - - def _cancel_callback(self): - self._render_status = False def _do_erase(self): if PC: @@ -69,30 +65,30 @@ def _update_state(self): elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: exit(0) - def _render(self, rect: rl.Rectangle): - label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100 * FONT_SCALE) + def _render(self, _): + content_rect = rl.Rectangle(45, 200, self._rect.width - 90, self._rect.height - 245) + + label_rect = rl.Rectangle(content_rect.x + 140, content_rect.y, content_rect.width - 280, 100 * FONT_SCALE) gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD) - text_rect = rl.Rectangle(rect.x + 140, rect.y + 140, rect.width - 280, rect.height - 90 - 100 * FONT_SCALE) + text_rect = rl.Rectangle(content_rect.x + 140, content_rect.y + 140, content_rect.width - 280, content_rect.height - 90 - 100 * FONT_SCALE) gui_text_box(text_rect, self._get_body_text(), 90) button_height = 160 button_spacing = 50 - button_top = rect.y + rect.height - button_height - button_width = (rect.width - button_spacing) / 2.0 + button_top = content_rect.y + content_rect.height - button_height + button_width = (content_rect.width - button_spacing) / 2.0 if self._reset_state != ResetState.RESETTING: if self._mode == ResetMode.RECOVER: - self._reboot_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height)) + self._reboot_button.render(rl.Rectangle(content_rect.x, button_top, button_width, button_height)) elif self._mode == ResetMode.USER_RESET: - self._cancel_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height)) + self._cancel_button.render(rl.Rectangle(content_rect.x, button_top, button_width, button_height)) if self._reset_state != ResetState.FAILED: - self._confirm_button.render(rl.Rectangle(rect.x + button_width + 50, button_top, button_width, button_height)) + self._confirm_button.render(rl.Rectangle(content_rect.x + button_width + 50, button_top, button_width, button_height)) else: - self._reboot_button.render(rl.Rectangle(rect.x, button_top, rect.width, button_height)) - - return self._render_status + self._reboot_button.render(rl.Rectangle(content_rect.x, button_top, content_rect.width, button_height)) def _confirm(self): if self._reset_state == ResetState.CONFIRM: @@ -120,16 +116,16 @@ def main(): elif sys.argv[1] == "--format": mode = ResetMode.FORMAT - gui_app.init_window("System Reset", 20) + gui_app.init_window("System Reset", 20, new_modal=True) reset = Reset(mode) if mode == ResetMode.FORMAT: reset.start_reset() - for should_render in gui_app.render(): - if should_render: - if not reset.render(rl.Rectangle(45, 200, gui_app.width - 90, gui_app.height - 245)): - break + gui_app.push_widget(reset) + + for _ in gui_app.render(): + pass if __name__ == "__main__": diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py index bf64361bed4..bb70b75b20d 100755 --- a/system/ui/tici_setup.py +++ b/system/ui/tici_setup.py @@ -16,7 +16,7 @@ from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets import DialogResult, Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle, ButtonRadio from openpilot.system.ui.widgets.keyboard import Keyboard from openpilot.system.ui.widgets.label import Label @@ -327,19 +327,20 @@ def render_custom_software_warning(self, rect: rl.Rectangle): def render_custom_software(self): def handle_keyboard_result(result): # Enter pressed - if result == 1: + if result == DialogResult.CONFIRM: url = self.keyboard.text self.keyboard.clear() if url: self.download(url) # Cancel pressed - elif result == 0: + elif result == DialogResult.CANCEL: self.state = SetupState.SOFTWARE_SELECTION self.keyboard.reset(min_text_size=1) self.keyboard.set_title("Enter URL", "for Custom Software") - gui_app.set_modal_overlay(self.keyboard, callback=handle_keyboard_result) + self.keyboard.set_callback(handle_keyboard_result) + gui_app.push_widget(self.keyboard) def use_openpilot(self): if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): @@ -435,11 +436,11 @@ def download_failed(self, url: str, reason: str): def main(): try: - gui_app.init_window("Setup", 20) + gui_app.init_window("Setup", 20, new_modal=True) setup = Setup() - for should_render in gui_app.render(): - if should_render: - setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + gui_app.push_widget(setup) + for _ in gui_app.render(): + pass setup.close() except Exception as e: print(f"Setup error: {e}") diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index ebf4b3bec39..c040e1a4041 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -160,11 +160,10 @@ def main(): manifest_path = sys.argv[2] try: - gui_app.init_window("System Update") - updater = Updater(updater_path, manifest_path) - for should_render in gui_app.render(): - if should_render: - updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + gui_app.init_window("System Update", new_modal=True) + gui_app.push_widget(Updater(updater_path, manifest_path)) + for _ in gui_app.render(): + pass finally: # Make sure we clean up even if there's an error gui_app.close() diff --git a/system/ui/widgets/confirm_dialog.py b/system/ui/widgets/confirm_dialog.py index 97618660bd1..3544836761c 100644 --- a/system/ui/widgets/confirm_dialog.py +++ b/system/ui/widgets/confirm_dialog.py @@ -1,4 +1,5 @@ import pyray as rl +from collections.abc import Callable from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import DialogResult @@ -17,7 +18,7 @@ class ConfirmDialog(Widget): - def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, rich: bool = False): + def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, rich: bool = False, callback: Callable[[DialogResult], None] | None = None): super().__init__() if cancel_text is None: cancel_text = tr("Cancel") @@ -26,7 +27,7 @@ def __init__(self, text: str, confirm_text: str, cancel_text: str | None = None, self._cancel_button = Button(cancel_text, self._cancel_button_callback) self._confirm_button = Button(confirm_text, self._confirm_button_callback, button_style=ButtonStyle.PRIMARY) self._rich = rich - self._dialog_result = DialogResult.NO_ACTION + self._callback = callback self._cancel_text = cancel_text self._scroller = Scroller([self._html_renderer], line_separator=False, spacing=0) @@ -36,14 +37,15 @@ def set_text(self, text): else: self._html_renderer.parse_html_content(text) - def reset(self): - self._dialog_result = DialogResult.NO_ACTION - def _cancel_button_callback(self): - self._dialog_result = DialogResult.CANCEL + gui_app.pop_widget() + if self._callback: + self._callback(DialogResult.CANCEL) def _confirm_button_callback(self): - self._dialog_result = DialogResult.CONFIRM + gui_app.pop_widget() + if self._callback: + self._callback(DialogResult.CONFIRM) def _render(self, rect: rl.Rectangle): dialog_x = OUTER_MARGIN if not self._rich else RICH_OUTER_MARGIN @@ -73,9 +75,9 @@ def _render(self, rect: rl.Rectangle): self._scroller.render(text_rect) if rl.is_key_pressed(rl.KeyboardKey.KEY_ENTER): - self._dialog_result = DialogResult.CONFIRM + self._confirm_button_callback() elif rl.is_key_pressed(rl.KeyboardKey.KEY_ESCAPE): - self._dialog_result = DialogResult.CANCEL + self._cancel_button_callback() if self._cancel_text: self._confirm_button.render(confirm_button) @@ -85,8 +87,6 @@ def _render(self, rect: rl.Rectangle): full_confirm_button = rl.Rectangle(dialog_rect.x + MARGIN, button_y, full_button_width, BUTTON_HEIGHT) self._confirm_button.render(full_confirm_button) - return self._dialog_result - def alert_dialog(message: str, button_text: str | None = None): if button_text is None: diff --git a/system/ui/widgets/html_render.py b/system/ui/widgets/html_render.py index 7d90d569253..77fca9fe348 100644 --- a/system/ui/widgets/html_render.py +++ b/system/ui/widgets/html_render.py @@ -260,7 +260,7 @@ def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() self._content = HtmlRenderer(file_path=file_path, text=text) self._scroll_panel = GuiScrollPanel() - self._ok_button = Button(tr("OK"), click_callback=lambda: gui_app.set_modal_overlay(None), button_style=ButtonStyle.PRIMARY) + self._ok_button = Button(tr("OK"), click_callback=gui_app.pop_widget, button_style=ButtonStyle.PRIMARY) def _render(self, rect: rl.Rectangle): margin = 50 diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 4ec92f507a7..531725688a2 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -1,12 +1,13 @@ from functools import partial import time from typing import Literal +from collections.abc import Callable import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets import DialogResult, Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.inputbox import InputBox from openpilot.system.ui.widgets.label import Label @@ -58,7 +59,8 @@ class Keyboard(Widget): - def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False): + def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mode: bool = False, show_password_toggle: bool = False, + callback: Callable[[DialogResult], None] | None = None): super().__init__() self._layout_name: Literal["lowercase", "uppercase", "numbers", "specials"] = "lowercase" self._caps_lock = False @@ -71,13 +73,13 @@ def __init__(self, max_text_size: int = 255, min_text_size: int = 0, password_mo self._input_box = InputBox(max_text_size) self._password_mode = password_mode self._show_password_toggle = show_password_toggle + self._callback = callback # Backspace key repeat tracking self._backspace_pressed: bool = False self._backspace_press_time: float = 0.0 self._backspace_last_repeat: float = 0.0 - self._render_return_status = -1 self._cancel_button = Button(lambda: tr("Cancel"), self._cancel_button_callback) self._eye_button = Button("", self._eye_button_callback, button_style=ButtonStyle.TRANSPARENT) @@ -122,16 +124,23 @@ def set_title(self, title: str, sub_title: str = ""): self._title.set_text(title) self._sub_title.set_text(sub_title) + def set_callback(self, callback: Callable[[DialogResult], None] | None): + self._callback = callback + def _eye_button_callback(self): self._password_mode = not self._password_mode def _cancel_button_callback(self): self.clear() - self._render_return_status = 0 + gui_app.pop_widget() + if self._callback: + self._callback(DialogResult.CANCEL) def _key_callback(self, k): if k == ENTER_KEY: - self._render_return_status = 1 + gui_app.pop_widget() + if self._callback: + self._callback(DialogResult.CONFIRM) else: self.handle_key_press(k) @@ -197,8 +206,6 @@ def _render(self, rect: rl.Rectangle): self._all_keys[key].set_enabled(is_enabled) self._all_keys[key].render(key_rect) - return self._render_return_status - def _render_input_area(self, input_rect: rl.Rectangle): if self._show_password_toggle: self._input_box.set_password_mode(self._password_mode) @@ -250,7 +257,6 @@ def handle_key_press(self, key): def reset(self, min_text_size: int | None = None): if min_text_size is not None: self._min_text_size = min_text_size - self._render_return_status = -1 self._last_shift_press_time = 0 self._backspace_pressed = False self._backspace_press_time = 0.0 @@ -259,15 +265,18 @@ def reset(self, min_text_size: int | None = None): if __name__ == "__main__": - gui_app.init_window("Keyboard") - keyboard = Keyboard(min_text_size=8, show_password_toggle=True) - for _ in gui_app.render(): - keyboard.set_title("Keyboard Input", "Type your text below") - result = keyboard.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - if result == 1: + def callback(result: DialogResult): + if result == DialogResult.CONFIRM: print(f"You typed: {keyboard.text}") - gui_app.request_close() - elif result == 0: + elif result == DialogResult.CANCEL: print("Canceled") - gui_app.request_close() + gui_app.request_close() + + gui_app.init_window("Keyboard", new_modal=True) + keyboard = Keyboard(min_text_size=8, show_password_toggle=True, callback=callback) + keyboard.set_title("Keyboard Input", "Type your text below") + + gui_app.push_widget(keyboard) + for _ in gui_app.render(): + pass gui_app.close() diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 1f146fbdd19..fcd56607c0b 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -7,7 +7,7 @@ from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wifi_manager import WifiManager, SecurityType, Network, MeteredType, normalize_ssid -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets import DialogResult, Widget from openpilot.system.ui.widgets.button import ButtonStyle, Button from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.keyboard import Keyboard @@ -187,8 +187,8 @@ def _toggle_roaming(self): self._wifi_manager.update_gsm_settings(roaming_state, self._params.get("GsmApn") or "", self._params.get_bool("GsmMetered")) def _edit_apn(self): - def update_apn(result): - if result != 1: + def update_apn(result: DialogResult): + if result != DialogResult.CONFIRM: return apn = self._keyboard.text.strip() @@ -203,7 +203,8 @@ def update_apn(result): self._keyboard.reset(min_text_size=0) self._keyboard.set_title(tr("Enter APN"), tr("leave blank for automatic configuration")) self._keyboard.set_text(current_apn) - gui_app.set_modal_overlay(self._keyboard, update_apn) + self._keyboard.set_callback(update_apn) + gui_app.push_widget(self._keyboard) def _toggle_cellular_metered(self): metered = self._cellular_metered_action.get_state() @@ -216,15 +217,18 @@ def _toggle_wifi_metered(self, metered): self._wifi_manager.set_current_network_metered(metered_type) def _connect_to_hidden_network(self): - def connect_hidden(result): - if result != 1: + def connect_hidden(result: DialogResult): + if result != DialogResult.CONFIRM: return ssid = self._keyboard.text if not ssid: return - def enter_password(result): + def enter_password(result: DialogResult): + if result != DialogResult.CONFIRM: + return + password = self._keyboard.text if password == "": # connect without password @@ -235,15 +239,17 @@ def enter_password(result): self._keyboard.reset(min_text_size=0) self._keyboard.set_title(tr("Enter password"), tr("for \"{}\"").format(ssid)) - gui_app.set_modal_overlay(self._keyboard, enter_password) + self._keyboard.set_callback(enter_password) + gui_app.push_widget(self._keyboard) self._keyboard.reset(min_text_size=1) self._keyboard.set_title(tr("Enter SSID"), "") - gui_app.set_modal_overlay(self._keyboard, connect_hidden) + self._keyboard.set_callback(connect_hidden) + gui_app.push_widget(self._keyboard) def _edit_tethering_password(self): - def update_password(result): - if result != 1: + def update_password(result: DialogResult): + if result != DialogResult.CONFIRM: return password = self._keyboard.text @@ -253,7 +259,8 @@ def update_password(result): self._keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) self._keyboard.set_title(tr("Enter new tethering password"), "") self._keyboard.set_text(self._wifi_manager.tethering_password) - gui_app.set_modal_overlay(self._keyboard, update_password) + self._keyboard.set_callback(update_password) + gui_app.push_widget(self._keyboard) def _update_state(self): self._wifi_manager.process_callbacks() @@ -314,29 +321,29 @@ def _render(self, rect: rl.Rectangle): self.keyboard.set_title(tr("Wrong password") if self._password_retry else tr("Enter password"), tr("for \"{}\"").format(normalize_ssid(self._state_network.ssid))) self.keyboard.reset(min_text_size=MIN_PASSWORD_LENGTH) - gui_app.set_modal_overlay(self.keyboard, lambda result: self._on_password_entered(cast(Network, self._state_network), result)) + self.keyboard.set_callback(lambda result: self._on_password_entered(cast(Network, self._state_network), result)) + gui_app.push_widget(self.keyboard) elif self.state == UIState.SHOW_FORGET_CONFIRM and self._state_network: - confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel")) + confirm_dialog = ConfirmDialog("", tr("Forget"), tr("Cancel"), callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result)) confirm_dialog.set_text(tr("Forget Wi-Fi Network \"{}\"?").format(normalize_ssid(self._state_network.ssid))) - confirm_dialog.reset() - gui_app.set_modal_overlay(confirm_dialog, callback=lambda result: self.on_forgot_confirm_finished(self._state_network, result)) + gui_app.push_widget(confirm_dialog) else: self._draw_network_list(rect) - def _on_password_entered(self, network: Network, result: int): - if result == 1: + def _on_password_entered(self, network: Network, result: DialogResult): + if result == DialogResult.CONFIRM: password = self.keyboard.text self.keyboard.clear() if len(password) >= MIN_PASSWORD_LENGTH: self.connect_to_network(network, password) - elif result == 0: + elif result == DialogResult.CANCEL: self.state = UIState.IDLE - def on_forgot_confirm_finished(self, network, result: int): - if result == 1: + def on_forgot_confirm_finished(self, network, result: DialogResult): + if result == DialogResult.CONFIRM: self.forget_network(network) - elif result == 0: + elif result == DialogResult.CANCEL: self.state = UIState.IDLE def _draw_network_list(self, rect: rl.Rectangle): @@ -474,11 +481,11 @@ def _on_disconnected(self): def main(): - gui_app.init_window("Wi-Fi Manager") - wifi_ui = WifiManagerUI(WifiManager()) + gui_app.init_window("Wi-Fi Manager", new_modal=True) + gui_app.push_widget(WifiManagerUI(WifiManager())) for _ in gui_app.render(): - wifi_ui.render(rl.Rectangle(50, 50, gui_app.width - 100, gui_app.height - 100)) + pass gui_app.close() diff --git a/system/ui/widgets/option_dialog.py b/system/ui/widgets/option_dialog.py index 62578d1cfba..206400a74f1 100644 --- a/system/ui/widgets/option_dialog.py +++ b/system/ui/widgets/option_dialog.py @@ -1,5 +1,6 @@ import pyray as rl -from openpilot.system.ui.lib.application import FontWeight +from collections.abc import Callable +from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget, DialogResult from openpilot.system.ui.widgets.button import Button, ButtonStyle @@ -17,13 +18,13 @@ class MultiOptionDialog(Widget): - def __init__(self, title, options, current="", option_font_weight=FontWeight.MEDIUM): + def __init__(self, title, options, current="", option_font_weight=FontWeight.MEDIUM, callback: Callable[[DialogResult], None] | None = None): super().__init__() self.title = title self.options = options self.current = current self.selection = current - self._result: DialogResult = DialogResult.NO_ACTION + self._callback = callback # Create scroller with option buttons self.option_buttons = [Button(option, click_callback=lambda opt=option: self._on_option_clicked(opt), @@ -36,7 +37,9 @@ def __init__(self, title, options, current="", option_font_weight=FontWeight.MED self.select_button = Button(lambda: tr("Select"), click_callback=lambda: self._set_result(DialogResult.CONFIRM), button_style=ButtonStyle.PRIMARY) def _set_result(self, result: DialogResult): - self._result = result + gui_app.pop_widget() + if self._callback: + self._callback(result) def _on_option_clicked(self, option): self.selection = option @@ -74,5 +77,3 @@ def _render(self, rect): select_rect = rl.Rectangle(content_rect.x + button_w + BUTTON_SPACING, button_y, button_w, BUTTON_HEIGHT) self.select_button.set_enabled(self.selection != self.current) self.select_button.render(select_rect) - - return self._result From 8bca2ca7588bae62a860b1e90a835e1678367596 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:26:50 -0700 Subject: [PATCH 266/685] feat(lpa): `at` client + list profiles (#37271) * feat(lpa): implement list_profiles in TiciLPA Add AT command serial interface, TLV parsing, and ES10x transport to support listing eSIM profiles (SGP.22 v2.3). TiciLPA is a singleton that maintains a persistent connection to the modem. * feat(lpa): close TiciLPA serial connection on exit Register atexit cleanup so the logical channel and serial port are released when the process exits, even on crashes or early exits. * feat(lpa): close stale logical channels on init to prevent timeouts * trying to brick it * Revert "trying to brick it" This reverts commit 46a0467314479c92d2cf331280521a1263f6cc43. * feat(lpa): remove ensure_capabilities check on init Target devices are known to support the required AT commands, so skip the capability probes and stale channel cleanup to speed up initialization. * feat(lpa): enable debug logging via DEBUG=1 env variable * muuuch better * revert * cleanup * constant --- system/hardware/tici/lpa.py | 229 +++++++++++++++++++++++++++++++++++- 1 file changed, 227 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index 9bd9d8c7b09..4d649fda8b0 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -1,12 +1,237 @@ +# SGP.22 v2.3: https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2021/07/SGP.22-v2.3.pdf + +import atexit +import base64 +import os +import serial +import sys + +from collections.abc import Generator + from openpilot.system.hardware.base import LPABase, Profile +DEFAULT_DEVICE = "/dev/ttyUSB2" +DEFAULT_BAUD = 9600 +DEFAULT_TIMEOUT = 5.0 +# https://euicc-manual.osmocom.org/docs/lpa/applet-id/ +ISDR_AID = "A0000005591010FFFFFFFF8900000100" +ES10X_MSS = 120 +DEBUG = os.environ.get("DEBUG") == "1" + +# TLV Tags +TAG_ICCID = 0x5A +TAG_PROFILE_INFO_LIST = 0xBF2D + +STATE_LABELS = {0: "disabled", 1: "enabled", 255: "unknown"} +ICON_LABELS = {0: "jpeg", 1: "png", 255: "unknown"} +CLASS_LABELS = {0: "test", 1: "provisioning", 2: "operational", 255: "unknown"} + + +def b64e(data: bytes) -> str: + return base64.b64encode(data).decode("ascii") + + +class AtClient: + def __init__(self, device: str, baud: int, timeout: float, debug: bool) -> None: + self.serial = serial.Serial(device, baudrate=baud, timeout=timeout) + self.debug = debug + self.channel: str | None = None + self.serial.reset_input_buffer() + + def close(self) -> None: + try: + if self.channel: + self.query(f"AT+CCHC={self.channel}") + self.channel = None + finally: + self.serial.close() + + def send(self, cmd: str) -> None: + if self.debug: + print(f">> {cmd}", file=sys.stderr) + self.serial.write((cmd + "\r").encode("ascii")) + + def expect(self) -> list[str]: + lines: list[str] = [] + while True: + raw = self.serial.readline() + if not raw: + raise TimeoutError("AT command timed out") + line = raw.decode(errors="ignore").strip() + if not line: + continue + if self.debug: + print(f"<< {line}", file=sys.stderr) + if line == "OK": + return lines + if line == "ERROR" or line.startswith("+CME ERROR"): + raise RuntimeError(f"AT command failed: {line}") + lines.append(line) + + def query(self, cmd: str) -> list[str]: + self.send(cmd) + return self.expect() + + def open_isdr(self) -> None: + # close any stale logical channel from a previous crashed session + try: + self.query("AT+CCHC=1") + except RuntimeError: + pass + for line in self.query(f'AT+CCHO="{ISDR_AID}"'): + if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()): + self.channel = ch + return + raise RuntimeError("Failed to open ISD-R application") + + def send_apdu(self, apdu: bytes) -> tuple[bytes, int, int]: + if not self.channel: + raise RuntimeError("Logical channel is not open") + hex_payload = apdu.hex().upper() + for line in self.query(f'AT+CGLA={self.channel},{len(hex_payload)},"{hex_payload}"'): + if line.startswith("+CGLA:"): + parts = line.split(":", 1)[1].split(",", 1) + if len(parts) == 2: + data = bytes.fromhex(parts[1].strip().strip('"')) + if len(data) >= 2: + return data[:-2], data[-2], data[-1] + raise RuntimeError("Missing +CGLA response") + + +# --- TLV utilities --- + +def iter_tlv(data: bytes, with_positions: bool = False) -> Generator: + idx, length = 0, len(data) + while idx < length: + start_pos = idx + tag = data[idx] + idx += 1 + if tag & 0x1F == 0x1F: # Multi-byte tag + tag_value = tag + while idx < length: + next_byte = data[idx] + idx += 1 + tag_value = (tag_value << 8) | next_byte + if not (next_byte & 0x80): + break + else: + tag_value = tag + if idx >= length: + break + size = data[idx] + idx += 1 + if size & 0x80: # Multi-byte length + num_bytes = size & 0x7F + if idx + num_bytes > length: + break + size = int.from_bytes(data[idx : idx + num_bytes], "big") + idx += num_bytes + if idx + size > length: + break + value = data[idx : idx + size] + idx += size + yield (tag_value, value, start_pos, idx) if with_positions else (tag_value, value) + + +def find_tag(data: bytes, target: int) -> bytes | None: + return next((v for t, v in iter_tlv(data) if t == target), None) + + +def tbcd_to_string(raw: bytes) -> str: + return "".join(str(n) for b in raw for n in (b & 0x0F, b >> 4) if n <= 9) + + +# Profile field decoders: TLV tag -> (field_name, decoder) +_PROFILE_FIELDS = { + TAG_ICCID: ("iccid", tbcd_to_string), + 0x4F: ("isdpAid", lambda v: v.hex().upper()), + 0x9F70: ("profileState", lambda v: STATE_LABELS.get(v[0], "unknown")), + 0x90: ("profileNickname", lambda v: v.decode("utf-8", errors="ignore") or None), + 0x91: ("serviceProviderName", lambda v: v.decode("utf-8", errors="ignore") or None), + 0x92: ("profileName", lambda v: v.decode("utf-8", errors="ignore") or None), + 0x93: ("iconType", lambda v: ICON_LABELS.get(v[0], "unknown")), + 0x94: ("icon", b64e), + 0x95: ("profileClass", lambda v: CLASS_LABELS.get(v[0], "unknown")), +} + + +def _decode_profile_fields(data: bytes) -> dict: + """Parse known profile metadata TLV fields into a dict.""" + result = {} + for tag, value in iter_tlv(data): + if (field := _PROFILE_FIELDS.get(tag)): + result[field[0]] = field[1](value) + return result + + +# --- ES10x command transport --- + +def es10x_command(client: AtClient, data: bytes) -> bytes: + response = bytearray() + sequence = 0 + offset = 0 + while offset < len(data): + chunk = data[offset : offset + ES10X_MSS] + offset += len(chunk) + is_last = offset == len(data) + apdu = bytes([0x80, 0xE2, 0x91 if is_last else 0x11, sequence & 0xFF, len(chunk)]) + chunk + segment, sw1, sw2 = client.send_apdu(apdu) + response.extend(segment) + while True: + if sw1 == 0x61: # More data available + segment, sw1, sw2 = client.send_apdu(bytes([0x80, 0xC0, 0x00, 0x00, sw2 or 0])) + response.extend(segment) + continue + if (sw1 & 0xF0) == 0x90: + break + raise RuntimeError(f"APDU failed with SW={sw1:02X}{sw2:02X}") + sequence += 1 + return bytes(response) + + +# --- Profile operations --- + +def decode_profiles(blob: bytes) -> list[dict]: + root = find_tag(blob, TAG_PROFILE_INFO_LIST) + if root is None: + raise RuntimeError("Missing ProfileInfoList") + list_ok = find_tag(root, 0xA0) + if list_ok is None: + return [] + defaults = {name: None for name, _ in _PROFILE_FIELDS.values()} + return [{**defaults, **_decode_profile_fields(value)} for tag, value in iter_tlv(list_ok) if tag == 0xE3] + + +def list_profiles(client: AtClient) -> list[dict]: + return decode_profiles(es10x_command(client, TAG_PROFILE_INFO_LIST.to_bytes(2, "big") + b"\x00")) + + class TiciLPA(LPABase): + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + def __init__(self): - pass + if hasattr(self, '_client'): + return + self._client = AtClient(DEFAULT_DEVICE, DEFAULT_BAUD, DEFAULT_TIMEOUT, debug=DEBUG) + self._client.open_isdr() + atexit.register(self._client.close) def list_profiles(self) -> list[Profile]: - return [] + return [ + Profile( + iccid=p.get("iccid", ""), + nickname=p.get("profileNickname") or "", + enabled=p.get("profileState") == "enabled", + provider=p.get("serviceProviderName") or "", + ) + for p in list_profiles(self._client) + ] def get_active_profile(self) -> Profile | None: return None From b27fa58444766c47f1ef980af79d324a669012c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 20 Feb 2026 10:37:14 -0800 Subject: [PATCH 267/685] Simpler file chunker (#37276) * Chunk tinygrad pkl below GitHub max size * pull that out * rm glob * make work * Single name def * unused comment * more cleanups * revert that * 10MB overhead --------- Co-authored-by: Adeeb Shihadeh --- .gitignore | 3 +- common/file_chunker.py | 31 ++++++++++++++++++ selfdrive/modeld/SConscript | 45 +++++++++++++-------------- selfdrive/modeld/dmonitoringmodeld.py | 7 ++--- selfdrive/modeld/external_pickle.py | 38 ---------------------- selfdrive/modeld/modeld.py | 9 +++--- 6 files changed, 61 insertions(+), 72 deletions(-) create mode 100644 common/file_chunker.py delete mode 100755 selfdrive/modeld/external_pickle.py diff --git a/.gitignore b/.gitignore index af18f06628a..062801d7874 100644 --- a/.gitignore +++ b/.gitignore @@ -64,8 +64,7 @@ flycheck_* cppcheck_report.txt comma*.sh -selfdrive/modeld/models/*.pkl -selfdrive/modeld/models/*.pkl.* +selfdrive/modeld/models/*.pkl* # openpilot log files *.bz2 diff --git a/common/file_chunker.py b/common/file_chunker.py new file mode 100644 index 00000000000..f03d04a3827 --- /dev/null +++ b/common/file_chunker.py @@ -0,0 +1,31 @@ +import math +import os +from pathlib import Path + +CHUNK_SIZE = 49 * 1024 * 1024 # 49MB, under GitHub's 50MB limit + +def get_chunk_name(name, idx, num_chunks): + return f"{name}.chunk{idx+1:02d}of{num_chunks:02d}" + +def get_chunk_paths(path, file_size): + num_chunks = math.ceil(file_size / CHUNK_SIZE) + return [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] + +def chunk_file(path, num_chunks): + with open(path, 'rb') as f: + data = f.read() + actual_num_chunks = max(1, math.ceil(len(data) / CHUNK_SIZE)) + assert num_chunks >= actual_num_chunks, f"Allowed {num_chunks} chunks but needs at least {actual_num_chunks}, for path {path}" + for i in range(num_chunks): + with open(get_chunk_name(path, i, num_chunks), 'wb') as f: + f.write(data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE]) + + +def read_file_chunked(path): + for num_chunks in range(1, 100): + if os.path.isfile(get_chunk_name(path, 0, num_chunks)): + files = [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] + return b''.join(Path(f).read_bytes() for f in files) + if os.path.isfile(path): + return Path(path).read_bytes() + raise FileNotFoundError(path) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 1808cfec2f5..35be2acc04d 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,14 +1,18 @@ import os import glob +from openpilot.common.file_chunker import chunk_file, get_chunk_paths Import('env', 'arch') +chunker_file = File("#common/file_chunker.py") lenv = env.Clone() -CHUNK_BYTES = int(os.environ.get("TG_CHUNK_BYTES", str(45 * 1024 * 1024))) tinygrad_root = env.Dir("#").abspath tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] +def estimate_pickle_max_size(onnx_size): + return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty + # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath @@ -26,39 +30,34 @@ image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +compile_warp_cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye warp_targets = [] for cam in [_ar_ox_fisheye, _os_fisheye]: w, h = cam.width, cam.height warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command(warp_targets, tinygrad_files + script_files, cmd) +def chunk_warps(target, source, env): + for t in warp_targets: + chunk_file(t, 1) +chunk_targets = sum([get_chunk_paths(t, estimate_pickle_max_size(0)) for t in warp_targets], []) +lenv.Command(chunk_targets, tinygrad_files + script_files + [chunker_file], + [compile_warp_cmd, chunk_warps]) def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath - - out = fn + "_tinygrad.pkl" - full = out + ".full" - parts = out + ".parts" - - full_node = lenv.Command( - full, - [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {full}' + pkl = fn + "_tinygrad.pkl" + onnx_path = fn + ".onnx" + chunk_targets = get_chunk_paths(pkl, estimate_pickle_max_size(os.path.getsize(onnx_path))) + def do_chunk(target, source, env): + chunk_file(pkl, len(chunk_targets)) + return lenv.Command( + chunk_targets, + [onnx_path] + tinygrad_files + [chunker_file], + [f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', + do_chunk] ) - split_script = File(Dir("#selfdrive/modeld").File("external_pickle.py").abspath) - parts_node = lenv.Command( - parts, - [full_node, split_script, Value(str(CHUNK_BYTES))], - [f'python3 {split_script.abspath} {full} {out} {CHUNK_BYTES}', Delete(full)], - ) - - lenv.NoCache(parts_node) - lenv.AlwaysBuild(parts_node) - return parts_node - # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: tg_compile(tg_flags, model_name) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 956ea8a6a26..6befe210a44 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -16,8 +16,8 @@ from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp -from openpilot.selfdrive.modeld.external_pickle import load_external_pickle PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') @@ -45,7 +45,7 @@ def __init__(self): self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self._blob_cache : dict[int, Tensor] = {} self.image_warp = None - self.model_run = load_external_pickle(MODEL_PKL_PATH) + self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH))) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib @@ -55,8 +55,7 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple if self.image_warp is None: self.frame_buf_params = get_nv12_info(buf.width, buf.height) warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.image_warp = pickle.load(f) + self.image_warp = pickle.loads(read_file_chunked(str(warp_path))) ptr = buf.data.ctypes.data # There is a ringbuffer of imgs, just cache tensors pointing to all of them if ptr not in self._blob_cache: diff --git a/selfdrive/modeld/external_pickle.py b/selfdrive/modeld/external_pickle.py deleted file mode 100755 index d60a9632a64..00000000000 --- a/selfdrive/modeld/external_pickle.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import hashlib -import pickle -import sys -from pathlib import Path - -def split_pickle(full_path: Path, out_prefix: Path, chunk_bytes: int) -> None: - data = full_path.read_bytes() - out_dir = out_prefix.parent - - for p in out_dir.glob(f"{out_prefix.name}.data-*"): - p.unlink() - - total = (len(data) + chunk_bytes - 1) // chunk_bytes - names = [] - for i in range(0, len(data), chunk_bytes): - name = f"{out_prefix.name}.data-{(i // chunk_bytes) + 1:04d}-of-{total:04d}" - (out_dir / name).write_bytes(data[i:i + chunk_bytes]) - names.append(name) - - manifest = hashlib.sha256(data).hexdigest() + "\n" + "\n".join(names) + "\n" - (out_dir / (out_prefix.name + ".parts")).write_text(manifest) - -def load_external_pickle(prefix: Path): - parts = prefix.parent / (prefix.name + ".parts") - lines = parts.read_text().splitlines() - expected_hash, chunk_names = lines[0], lines[1:] - - data = bytearray() - for name in chunk_names: - data += (prefix.parent / name).read_bytes() - - if hashlib.sha256(data).hexdigest() != expected_hash: - raise RuntimeError(f"hash mismatch loading {prefix}") - return pickle.loads(data) - -if __name__ == "__main__": - split_pickle(Path(sys.argv[1]), Path(sys.argv[2]), int(sys.argv[3])) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 3fe3e0e6d6a..8cfbea02c8d 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -27,8 +27,8 @@ from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan -from openpilot.selfdrive.modeld.external_pickle import load_external_pickle PROCESS_NAME = "selfdrive.modeld.modeld" @@ -178,8 +178,8 @@ def __init__(self): self.parser = Parser() self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None - self.vision_run = load_external_pickle(VISION_PKL_PATH) - self.policy_run = load_external_pickle(POLICY_PKL_PATH) + self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) + self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -196,8 +196,7 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], w, h = bufs[key].width, bufs[key].height self.frame_buf_params[key] = get_nv12_info(w, h) warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.update_imgs = pickle.load(f) + self.update_imgs = pickle.loads(read_file_chunked(str(warp_path))) for key in bufs.keys(): ptr = bufs[key].data.ctypes.data From 66687746f990c7021db673df9eceb06e6850789e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 14:20:02 -0800 Subject: [PATCH 268/685] replace dictdiffer with native capnp differ (#37279) * replace dictdiffer with native capnp differ * capnp diff --- pyproject.toml | 1 - selfdrive/test/process_replay/compare_logs.py | 81 ++++++++++++++----- uv.lock | 11 --- 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 8eb86101afb..ac10630a9c2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,7 +96,6 @@ testing = [ dev = [ "av", - "dictdiffer", "matplotlib", "opencv-python-headless", "parameterized >=0.8, <0.9", diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index 13d51a636f1..e2d912a8339 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -3,13 +3,16 @@ import math import capnp import numbers -import dictdiffer from collections import Counter from openpilot.tools.lib.logreader import LogReader EPSILON = sys.float_info.epsilon +_DynamicStructReader = capnp.lib.capnp._DynamicStructReader +_DynamicListReader = capnp.lib.capnp._DynamicListReader +_DynamicEnum = capnp.lib.capnp._DynamicEnum + def remove_ignored_fields(msg, ignore): msg = msg.as_builder() @@ -39,6 +42,61 @@ def remove_ignored_fields(msg, ignore): return msg +def _diff_capnp(r1, r2, path, tolerance): + """Walk two capnp struct readers and yield (action, dotted_path, value) diffs. + + Floats are compared with the given tolerance (combined absolute+relative). + """ + schema = r1.schema + + for fname in schema.non_union_fields: + child_path = path + (fname,) + v1 = getattr(r1, fname) + v2 = getattr(r2, fname) + yield from _diff_capnp_values(v1, v2, child_path, tolerance) + + if schema.union_fields: + w1, w2 = r1.which(), r2.which() + if w1 != w2: + yield 'change', '.'.join(path), (w1, w2) + else: + child_path = path + (w1,) + v1, v2 = getattr(r1, w1), getattr(r2, w2) + yield from _diff_capnp_values(v1, v2, child_path, tolerance) + + +def _diff_capnp_values(v1, v2, path, tolerance): + if isinstance(v1, _DynamicStructReader): + yield from _diff_capnp(v1, v2, path, tolerance) + + elif isinstance(v1, _DynamicListReader): + dot = '.'.join(path) + n1, n2 = len(v1), len(v2) + n = min(n1, n2) + for i in range(n): + yield from _diff_capnp_values(v1[i], v2[i], path + (str(i),), tolerance) + if n2 > n: + yield 'add', dot, list(enumerate(v2[n:], n)) + if n1 > n: + yield 'remove', dot, list(reversed([(i, v1[i]) for i in range(n, n1)])) + + elif isinstance(v1, _DynamicEnum): + s1, s2 = str(v1), str(v2) + if s1 != s2: + yield 'change', '.'.join(path), (s1, s2) + + elif isinstance(v1, float): + if not (v1 == v2 or ( + math.isfinite(v1) and math.isfinite(v2) and + abs(v1 - v2) <= max(tolerance, tolerance * max(abs(v1), abs(v2))) + )): + yield 'change', '.'.join(path), (v1, v2) + + else: + if v1 != v2: + yield 'change', '.'.join(path), (v1, v2) + + def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=None,): if ignore_fields is None: ignore_fields = [] @@ -65,26 +123,7 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non msg2 = remove_ignored_fields(msg2, ignore_fields) if msg1.to_bytes() != msg2.to_bytes(): - msg1_dict = msg1.as_reader().to_dict(verbose=True) - msg2_dict = msg2.as_reader().to_dict(verbose=True) - - dd = dictdiffer.diff(msg1_dict, msg2_dict, ignore=ignore_fields) - - # Dictdiffer only supports relative tolerance, we also want to check for absolute - # TODO: add this to dictdiffer - def outside_tolerance(diff): - try: - if diff[0] == "change": - a, b = diff[2] - finite = math.isfinite(a) and math.isfinite(b) - if finite and isinstance(a, numbers.Number) and isinstance(b, numbers.Number): - return abs(a - b) > max(tolerance, tolerance * max(abs(a), abs(b))) - except TypeError: - pass - return True - - dd = list(filter(outside_tolerance, dd)) - + dd = list(_diff_capnp(msg1.as_reader(), msg2.as_reader(), (), tolerance)) diff.extend(dd) return diff diff --git a/uv.lock b/uv.lock index bd4632942ac..4f1c957f3cd 100644 --- a/uv.lock +++ b/uv.lock @@ -356,15 +356,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5e/58/d01538556103d544a5a5b4cbcb00646ff92d8a97f0a6283a56bede4307c8/dearpygui-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f2291313d2035f8a4108e13f60d8c1a0e7c19af7554a7739a3fd15b3d5af8f7", size = 1808971, upload-time = "2025-11-14T14:47:28.15Z" }, ] -[[package]] -name = "dictdiffer" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/7b/35cbccb7effc5d7e40f4c55e2b79399e1853041997fcda15c9ff160abba0/dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578", size = 31513, upload-time = "2021-07-22T13:24:29.276Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/47/ef/4cb333825d10317a36a1154341ba37e6e9c087bac99c1990ef07ffdb376f/dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595", size = 16754, upload-time = "2021-07-22T13:24:26.783Z" }, -] - [[package]] name = "dnspython" version = "2.8.0" @@ -846,7 +837,6 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "av" }, - { name = "dictdiffer" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, { name = "parameterized" }, @@ -887,7 +877,6 @@ requires-dist = [ { name = "crcmod-plus" }, { name = "cython" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, - { name = "dictdiffer", marker = "extra == 'dev'" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, From 07163f793b41b039f1b0cdb51a2f7d680b65f42e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 14:48:27 -0800 Subject: [PATCH 269/685] pytest timeout doesn't even work (#37281) --- pyproject.toml | 2 -- selfdrive/locationd/test/test_lagd.py | 1 - uv.lock | 14 -------------- 3 files changed, 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ac10630a9c2..92d82e8330b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,7 +86,6 @@ testing = [ "pytest-subtests", # https://github.com/pytest-dev/pytest-xdist/pull/1229 "pytest-xdist @ git+https://github.com/sshane/pytest-xdist@2b4372bd62699fb412c4fe2f95bf9f01bd2018da", - "pytest-timeout", "pytest-asyncio", "pytest-mock", "ruff", @@ -126,7 +125,6 @@ cpp_files = "test_*" cpp_harness = "selfdrive/test/cpp_harness.py" python_files = "test_*.py" asyncio_default_fixture_loop_scope = "function" -#timeout = "30" # you get this long by default markers = [ "slow: tests that take awhile to run and can be skipped with -m 'not slow'", "tici: tests that are only meant to run on the C3/C3X", diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py index a3dfce9c296..e9b5aff6d4e 100644 --- a/selfdrive/locationd/test/test_lagd.py +++ b/selfdrive/locationd/test/test_lagd.py @@ -120,7 +120,6 @@ def test_estimator_masking(self): assert msg.liveDelay.calPerc == 100 @pytest.mark.skipif(PC, reason="only on device") - @pytest.mark.timeout(60) def test_estimator_performance(self): mocked_CP = car.CarParams(steerActuatorDelay=0.8) estimator = LateralLagEstimator(mocked_CP, DT) diff --git a/uv.lock b/uv.lock index 4f1c957f3cd..86d324ec4c5 100644 --- a/uv.lock +++ b/uv.lock @@ -855,7 +855,6 @@ testing = [ { name = "pytest-cpp" }, { name = "pytest-mock" }, { name = "pytest-subtests" }, - { name = "pytest-timeout" }, { name = "pytest-xdist" }, { name = "ruff" }, { name = "ty" }, @@ -904,7 +903,6 @@ requires-dist = [ { name = "pytest-cpp", marker = "extra == 'testing'" }, { name = "pytest-mock", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, - { name = "pytest-timeout", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, { name = "pyzmq" }, { name = "qrcode" }, @@ -1302,18 +1300,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/23/64/bba465299b37448b4c1b84c7a04178399ac22d47b3dc5db1874fe55a2bd3/pytest_subtests-0.15.0-py3-none-any.whl", hash = "sha256:da2d0ce348e1f8d831d5a40d81e3aeac439fec50bd5251cbb7791402696a9493", size = 9185, upload-time = "2025-10-20T16:26:17.239Z" }, ] -[[package]] -name = "pytest-timeout" -version = "2.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ac/82/4c9ecabab13363e72d880f2fb504c5f750433b2b6f16e99f4ec21ada284c/pytest_timeout-2.4.0.tar.gz", hash = "sha256:7e68e90b01f9eff71332b25001f85c75495fc4e3a836701876183c4bcfd0540a", size = 17973, upload-time = "2025-05-05T19:44:34.99Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/b6/3127540ecdf1464a00e5a01ee60a1b09175f6913f0644ac748494d9c4b21/pytest_timeout-2.4.0-py3-none-any.whl", hash = "sha256:c42667e5cdadb151aeb5b26d114aff6bdf5a907f176a007a30b940d3d865b5c2", size = 14382, upload-time = "2025-05-05T19:44:33.502Z" }, -] - [[package]] name = "pytest-xdist" version = "3.7.1.dev24+g2b4372bd6" From b28ff40d4ded866b59a14107ff972db41d4bed2a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 14:59:36 -0800 Subject: [PATCH 270/685] insource parameterized (#37280) * insource parameterized * lil more * fix --- cereal/messaging/tests/test_messaging.py | 2 +- cereal/messaging/tests/test_services.py | 2 +- common/parameterized.py | 47 +++++++++++++++++++ pyproject.toml | 1 - selfdrive/car/tests/test_car_interfaces.py | 2 +- selfdrive/car/tests/test_cruise_speed.py | 2 +- selfdrive/car/tests/test_models.py | 2 +- .../controls/tests/test_following_distance.py | 2 +- selfdrive/controls/tests/test_latcontrol.py | 2 +- .../tests/test_latcontrol_torque_buffer.py | 2 +- .../test_longitudinal.py | 2 +- selfdrive/test/process_replay/test_fuzzy.py | 2 +- selfdrive/test/process_replay/test_regen.py | 2 +- selfdrive/ui/tests/test_translations.py | 2 +- system/loggerd/tests/test_encoder.py | 2 +- system/webrtc/tests/test_webrtcd.py | 2 +- tools/lib/tests/test_logreader.py | 2 +- uv.lock | 11 ----- 18 files changed, 62 insertions(+), 27 deletions(-) create mode 100644 common/parameterized.py diff --git a/cereal/messaging/tests/test_messaging.py b/cereal/messaging/tests/test_messaging.py index 583eb8b0d86..afdab8a51f4 100644 --- a/cereal/messaging/tests/test_messaging.py +++ b/cereal/messaging/tests/test_messaging.py @@ -5,7 +5,7 @@ import random import threading import time -from parameterized import parameterized +from openpilot.common.parameterized import parameterized import pytest from cereal import log, car diff --git a/cereal/messaging/tests/test_services.py b/cereal/messaging/tests/test_services.py index 8bfd2ea978a..3320723fec8 100644 --- a/cereal/messaging/tests/test_services.py +++ b/cereal/messaging/tests/test_services.py @@ -1,7 +1,7 @@ import os import tempfile from typing import Dict -from parameterized import parameterized +from openpilot.common.parameterized import parameterized import cereal.services as services from cereal.services import SERVICE_LIST diff --git a/common/parameterized.py b/common/parameterized.py new file mode 100644 index 00000000000..7cd21bb9c5e --- /dev/null +++ b/common/parameterized.py @@ -0,0 +1,47 @@ +import sys +import pytest +import inspect + + +class parameterized: + @staticmethod + def expand(cases): + cases = list(cases) + + if not cases: + return lambda func: pytest.mark.skip("no parameterized cases")(func) + + def decorator(func): + params = [p for p in inspect.signature(func).parameters if p != 'self'] + normalized = [c if isinstance(c, tuple) else (c,) for c in cases] + # Infer arg count from first case so extra params (e.g. from @given) are left untouched + expand_params = params[: len(normalized[0])] + if len(expand_params) == 1: + return pytest.mark.parametrize(expand_params[0], [c[0] for c in normalized])(func) + return pytest.mark.parametrize(', '.join(expand_params), normalized)(func) + + return decorator + + +def parameterized_class(attrs, input_list=None): + if isinstance(attrs, list) and (not attrs or isinstance(attrs[0], dict)): + params_list = attrs + else: + assert input_list is not None + attr_names = (attrs,) if isinstance(attrs, str) else tuple(attrs) + params_list = [dict(zip(attr_names, v if isinstance(v, (tuple, list)) else (v,), strict=False)) for v in input_list] + + def decorator(cls): + globs = sys._getframe(1).f_globals + for i, params in enumerate(params_list): + name = f"{cls.__name__}_{i}" + new_cls = type(name, (cls,), dict(params)) + new_cls.__module__ = cls.__module__ + new_cls.__test__ = True # override inherited False so pytest collects this subclass + globs[name] = new_cls + # Don't collect the un-parametrised base, but return it so outer decorators + # (e.g. @pytest.mark.skip) land on it and propagate to subclasses via MRO. + cls.__test__ = False + return cls + + return decorator diff --git a/pyproject.toml b/pyproject.toml index 92d82e8330b..12134ef1d98 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,7 +97,6 @@ dev = [ "av", "matplotlib", "opencv-python-headless", - "parameterized >=0.8, <0.9", ] tools = [ diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 24d2faa0db1..1bc59326a20 100644 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -1,7 +1,7 @@ import os import hypothesis.strategies as st from hypothesis import Phase, given, settings -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import car from opendbc.car import DT_CTRL diff --git a/selfdrive/car/tests/test_cruise_speed.py b/selfdrive/car/tests/test_cruise_speed.py index aa70e49f5d6..05fef93b4ef 100644 --- a/selfdrive/car/tests/test_cruise_speed.py +++ b/selfdrive/car/tests/test_cruise_speed.py @@ -2,7 +2,7 @@ import itertools import numpy as np -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from cereal import log from openpilot.selfdrive.car.cruise import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT from cereal import car diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 94f5b332319..a7f3d68c149 100644 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -6,7 +6,7 @@ from collections import defaultdict, Counter import hypothesis.strategies as st from hypothesis import Phase, given, settings -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from opendbc.car import DT_CTRL, gen_empty_fingerprint, structs from opendbc.car.can_definitions import CanData diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index ad1ff1a189b..1eb88d72067 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -1,6 +1,6 @@ import pytest import itertools -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from cereal import log diff --git a/selfdrive/controls/tests/test_latcontrol.py b/selfdrive/controls/tests/test_latcontrol.py index 354c7f00add..5c3381edce2 100644 --- a/selfdrive/controls/tests/test_latcontrol.py +++ b/selfdrive/controls/tests/test_latcontrol.py @@ -1,4 +1,4 @@ -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import car, log from opendbc.car.car_helpers import interfaces diff --git a/selfdrive/controls/tests/test_latcontrol_torque_buffer.py b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py index 76d0c28423c..ab1d2c7b36c 100644 --- a/selfdrive/controls/tests/test_latcontrol_torque_buffer.py +++ b/selfdrive/controls/tests/test_latcontrol_torque_buffer.py @@ -1,4 +1,4 @@ -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import car, log from opendbc.car.car_helpers import interfaces diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index ab1800b4fbb..90bc46b187d 100644 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -1,5 +1,5 @@ import itertools -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver diff --git a/selfdrive/test/process_replay/test_fuzzy.py b/selfdrive/test/process_replay/test_fuzzy.py index 723112163eb..6989f8957fe 100644 --- a/selfdrive/test/process_replay/test_fuzzy.py +++ b/selfdrive/test/process_replay/test_fuzzy.py @@ -2,7 +2,7 @@ import os from hypothesis import given, HealthCheck, Phase, settings import hypothesis.strategies as st -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import log from opendbc.car.toyota.values import CAR as TOYOTA diff --git a/selfdrive/test/process_replay/test_regen.py b/selfdrive/test/process_replay/test_regen.py index 5f26daf786c..f4942e486ca 100644 --- a/selfdrive/test/process_replay/test_regen.py +++ b/selfdrive/test/process_replay/test_regen.py @@ -1,4 +1,4 @@ -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from openpilot.selfdrive.test.process_replay.regen import regen_segment from openpilot.selfdrive.test.process_replay.process_replay import check_openpilot_enabled diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 3177814f9f0..599c99013c6 100644 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -5,7 +5,7 @@ import xml.etree.ElementTree as ET import string import requests -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, LANGUAGES_FILE with open(str(LANGUAGES_FILE)) as f: diff --git a/system/loggerd/tests/test_encoder.py b/system/loggerd/tests/test_encoder.py index e4dabd3df93..a9de0690aaa 100644 --- a/system/loggerd/tests/test_encoder.py +++ b/system/loggerd/tests/test_encoder.py @@ -7,7 +7,7 @@ import time from pathlib import Path -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from tqdm import trange from openpilot.common.params import Params diff --git a/system/webrtc/tests/test_webrtcd.py b/system/webrtc/tests/test_webrtcd.py index 4fa6d8953f7..e5f6ceaa376 100644 --- a/system/webrtc/tests/test_webrtcd.py +++ b/system/webrtc/tests/test_webrtcd.py @@ -10,7 +10,7 @@ import aiortc from teleoprtc import WebRTCOfferBuilder -from parameterized import parameterized_class +from openpilot.common.parameterized import parameterized_class @parameterized_class(("in_services", "out_services"), [ diff --git a/tools/lib/tests/test_logreader.py b/tools/lib/tests/test_logreader.py index 0151940c44d..123f142383a 100644 --- a/tools/lib/tests/test_logreader.py +++ b/tools/lib/tests/test_logreader.py @@ -7,7 +7,7 @@ import pytest import requests -from parameterized import parameterized +from openpilot.common.parameterized import parameterized from cereal import log as capnp_log from openpilot.tools.lib.logreader import LogsUnavailable, LogIterable, LogReader, parse_indirect, ReadMode diff --git a/uv.lock b/uv.lock index 86d324ec4c5..e14591deee5 100644 --- a/uv.lock +++ b/uv.lock @@ -839,7 +839,6 @@ dev = [ { name = "av" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, - { name = "parameterized" }, ] docs = [ { name = "jinja2" }, @@ -889,7 +888,6 @@ requires-dist = [ { name = "numpy", specifier = ">=2.0" }, { name = "onnx", specifier = ">=1.14.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, - { name = "parameterized", marker = "extra == 'dev'", specifier = ">=0.8,<0.9" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, { name = "pyaudio" }, @@ -972,15 +970,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/5d/3744c6550dddf933785a37cdd4a9921fe13284e6d115b5a2637fe390f158/panda3d_simplepbr-0.13.1-py3-none-any.whl", hash = "sha256:cda41cb57cff035b851646956cfbdcc408bee42511dabd4f2d7bd4fbf48c57a9", size = 2457097, upload-time = "2025-03-30T16:57:39.729Z" }, ] -[[package]] -name = "parameterized" -version = "0.8.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c6/23/2288f308d238b4f261c039cafcd650435d624de97c6ffc903f06ea8af50f/parameterized-0.8.1.tar.gz", hash = "sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c", size = 23936, upload-time = "2021-01-09T20:35:18.235Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/31/13/fe468c8c7400a8eca204e6e160a29bf7dcd45a76e20f1c030f3eaa690d93/parameterized-0.8.1-py2.py3-none-any.whl", hash = "sha256:9cbb0b69a03e8695d68b3399a8a5825200976536fe1cb79db60ed6a4c8c9efe9", size = 26354, upload-time = "2021-01-09T20:35:16.307Z" }, -] - [[package]] name = "pathspec" version = "1.0.4" From 5d54743d8ba5f377f4260d9573d532ecfcfaae93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 20 Feb 2026 15:19:39 -0800 Subject: [PATCH 271/685] safer model pkl chunking (#37283) * safer chunking * rm unchunked --- common/file_chunker.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/common/file_chunker.py b/common/file_chunker.py index f03d04a3827..139a7dcacc1 100644 --- a/common/file_chunker.py +++ b/common/file_chunker.py @@ -1,8 +1,9 @@ +import glob import math import os from pathlib import Path -CHUNK_SIZE = 49 * 1024 * 1024 # 49MB, under GitHub's 50MB limit +CHUNK_SIZE = 19 * 1024 * 1024 # 49MB, under GitHub's 50MB limit def get_chunk_name(name, idx, num_chunks): return f"{name}.chunk{idx+1:02d}of{num_chunks:02d}" @@ -12,6 +13,8 @@ def get_chunk_paths(path, file_size): return [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] def chunk_file(path, num_chunks): + for old in glob.glob(f"{path}.chunk*"): + os.remove(old) with open(path, 'rb') as f: data = f.read() actual_num_chunks = max(1, math.ceil(len(data) / CHUNK_SIZE)) @@ -19,13 +22,15 @@ def chunk_file(path, num_chunks): for i in range(num_chunks): with open(get_chunk_name(path, i, num_chunks), 'wb') as f: f.write(data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE]) + os.remove(path) def read_file_chunked(path): - for num_chunks in range(1, 100): - if os.path.isfile(get_chunk_name(path, 0, num_chunks)): - files = [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] - return b''.join(Path(f).read_bytes() for f in files) + chunks = sorted(glob.glob(f"{path}.chunk*")) + if chunks: + expected = [get_chunk_name(path, i, len(chunks)) for i in range(len(chunks))] + assert chunks == expected, f"Chunk mismatch: {chunks} != {expected}" + return b''.join(Path(f).read_bytes() for f in chunks) if os.path.isfile(path): return Path(path).read_bytes() raise FileNotFoundError(path) From 806655b052372afe830f6b0d2ba6dc73a222fcaf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 15:48:09 -0800 Subject: [PATCH 272/685] CI: replace docker with `op setup` (#37282) --- .github/workflows/auto-cache/action.yaml | 5 +- .github/workflows/badges.yaml | 6 +- .../workflows/compile-openpilot/action.yaml | 10 +-- .github/workflows/repo-maintenance.yaml | 17 +--- .../workflows/setup-with-retry/action.yaml | 4 - .github/workflows/setup/action.yaml | 20 ++--- .github/workflows/tests.yaml | 78 +++++++------------ system/webrtc/tests/test_webrtcd.py | 65 ---------------- 8 files changed, 49 insertions(+), 156 deletions(-) delete mode 100644 system/webrtc/tests/test_webrtcd.py diff --git a/.github/workflows/auto-cache/action.yaml b/.github/workflows/auto-cache/action.yaml index 377b1eedcde..42c8f8fd2db 100644 --- a/.github/workflows/auto-cache/action.yaml +++ b/.github/workflows/auto-cache/action.yaml @@ -52,7 +52,4 @@ runs: # make the directory manually in case we didn't get a hit, so it doesn't fail on future steps - id: scons-cache-setup shell: bash - run: | - mkdir -p ${{ inputs.path }} - sudo chmod -R 777 ${{ inputs.path }} - sudo chown -R $USER ${{ inputs.path }} + run: mkdir -p ${{ inputs.path }} diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index 3f9c9c1c590..23f2c135d5d 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -5,9 +5,7 @@ on: workflow_dispatch: env: - BASE_IMAGE: openpilot-base - DOCKER_REGISTRY: ghcr.io/commaai - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $DOCKER_REGISTRY/$BASE_IMAGE:latest /bin/bash -c + PYTHONPATH: ${{ github.workspace }} jobs: badges: @@ -23,7 +21,7 @@ jobs: - uses: ./.github/workflows/setup-with-retry - name: Push badges run: | - ${{ env.RUN }} "python3 selfdrive/ui/translations/create_badges.py" + python3 selfdrive/ui/translations/create_badges.py rm .gitattributes diff --git a/.github/workflows/compile-openpilot/action.yaml b/.github/workflows/compile-openpilot/action.yaml index 4015746c0e3..627b4845aa6 100644 --- a/.github/workflows/compile-openpilot/action.yaml +++ b/.github/workflows/compile-openpilot/action.yaml @@ -6,16 +6,16 @@ runs: - shell: bash name: Build openpilot with all flags run: | - ${{ env.RUN }} "scons -j$(nproc)" - ${{ env.RUN }} "release/check-dirty.sh" + scons -j$(nproc) + release/check-dirty.sh - shell: bash name: Cleanup scons cache and rebuild run: | - ${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \ - scons -j$(nproc) --cache-populate" + rm -rf /tmp/scons_cache/* + scons -j$(nproc) --cache-populate - name: Save scons cache uses: actions/cache/save@v4 if: github.ref == 'refs/heads/master' with: - path: .ci_cache/scons_cache + path: /tmp/scons_cache key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 55d1c2052c0..10b2e8ab2fc 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -6,9 +6,7 @@ on: workflow_dispatch: env: - BASE_IMAGE: openpilot-base - BUILD: selfdrive/test/docker_build.sh base - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c + PYTHONPATH: ${{ github.workspace }} jobs: update_translations: @@ -20,8 +18,7 @@ jobs: submodules: true - uses: ./.github/workflows/setup-with-retry - name: Update translations - run: | - ${{ env.RUN }} "python3 selfdrive/ui/update_translations.py --vanish" + run: python3 selfdrive/ui/update_translations.py --vanish - name: Create Pull Request uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 with: @@ -37,18 +34,14 @@ jobs: package_updates: name: package_updates runs-on: ubuntu-latest - container: - image: ghcr.io/commaai/openpilot-base:latest if: github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v6 with: submodules: true + - uses: ./.github/workflows/setup-with-retry - name: uv lock - run: | - python3 -m ensurepip --upgrade - pip3 install uv - uv lock --upgrade + run: uv lock --upgrade - name: uv pip tree id: pip_tree run: | @@ -57,12 +50,10 @@ jobs: echo 'EOF' >> $GITHUB_OUTPUT - name: bump submodules run: | - git config --global --add safe.directory '*' git submodule update --remote git add . - name: update car docs run: | - export PYTHONPATH="$PWD" scons -j$(nproc) --minimal opendbc_repo python selfdrive/car/docs.py git add docs/CARS.md diff --git a/.github/workflows/setup-with-retry/action.yaml b/.github/workflows/setup-with-retry/action.yaml index 98a3913600b..923cc3aadbd 100644 --- a/.github/workflows/setup-with-retry/action.yaml +++ b/.github/workflows/setup-with-retry/action.yaml @@ -1,10 +1,6 @@ name: 'openpilot env setup, with retry on failure' inputs: - docker_hub_pat: - description: 'Auth token for Docker Hub, required for BuildJet jobs' - required: false - default: '' sleep_time: description: 'Time to sleep between retries' required: false diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index 818060c3b01..56f0096450e 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -34,23 +34,19 @@ runs: - id: date shell: bash run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - - shell: bash - run: echo "$CACHE_COMMIT_DATE" - id: scons-cache uses: ./.github/workflows/auto-cache with: - path: .ci_cache/scons_cache + path: /tmp/scons_cache key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} restore-keys: | scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }} scons-${{ runner.arch }} - # as suggested here: https://github.com/moby/moby/issues/32816#issuecomment-910030001 - - id: normalize-file-permissions - shell: bash - name: Normalize file permissions to ensure a consistent docker build cache - run: | - find . -type f -executable -not -perm 755 -exec chmod 755 {} \; - find . -type f -not -executable -not -perm 644 -exec chmod 644 {} \; - # build our docker image - shell: bash - run: eval ${{ env.BUILD }} + name: Run setup + run: ./tools/op.sh setup + - shell: bash + name: Setup cache dirs + run: | + mkdir -p /tmp/comma_download_cache + echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index d892507319c..e1765eb5626 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -18,13 +18,8 @@ concurrency: cancel-in-progress: true env: - PYTHONWARNINGS: error - BASE_IMAGE: openpilot-base - DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} - BUILD: selfdrive/test/docker_build.sh base - - RUN: docker run --shm-size 2G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c - + CI: 1 + PYTHONPATH: ${{ github.workspace }} PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical jobs: @@ -38,6 +33,7 @@ jobs: || fromJSON('["ubuntu-24.04"]') }} env: STRIPPED_DIR: /tmp/releasepilot + PYTHONPATH: /tmp/releasepilot steps: - uses: actions/checkout@v6 with: @@ -53,15 +49,13 @@ jobs: run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh - uses: ./.github/workflows/setup-with-retry - name: Build openpilot and run checks - timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache - run: | - cd $STRIPPED_DIR - ${{ env.RUN }} "python3 system/manager/build.py" + timeout-minutes: 30 + working-directory: ${{ env.STRIPPED_DIR }} + run: python3 system/manager/build.py - name: Run tests timeout-minutes: 1 - run: | - cd $STRIPPED_DIR - ${{ env.RUN }} "release/check-dirty.sh" + working-directory: ${{ env.STRIPPED_DIR }} + run: release/check-dirty.sh - name: Check submodules if: github.repository == 'commaai/openpilot' timeout-minutes: 3 @@ -78,11 +72,6 @@ jobs: - uses: actions/checkout@v6 with: submodules: true - - name: Setup docker push - if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' - run: | - echo "PUSH_IMAGE=true" >> "$GITHUB_ENV" - $DOCKER_LOGIN - uses: ./.github/workflows/setup-with-retry - uses: ./.github/workflows/compile-openpilot timeout-minutes: 30 @@ -106,7 +95,6 @@ jobs: - name: Install dependencies run: ./tools/mac_setup.sh env: - PYTHONWARNINGS: default # package install has DeprecationWarnings HOMEBREW_DISPLAY_INSTALL_TIMES: 1 - run: git lfs pull - name: Getting scons cache @@ -128,8 +116,6 @@ jobs: (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') || fromJSON('["ubuntu-24.04"]') }} - env: - PYTHONWARNINGS: default steps: - uses: actions/checkout@v6 with: @@ -137,7 +123,7 @@ jobs: - uses: ./.github/workflows/setup-with-retry - name: Static analysis timeout-minutes: 1 - run: ${{ env.RUN }} "scripts/lint/lint.sh" + run: scripts/lint/lint.sh unit_tests: name: unit tests @@ -154,15 +140,14 @@ jobs: - uses: ./.github/workflows/setup-with-retry id: setup-step - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc)" + run: scons -j$(nproc) - name: Run unit tests timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }} run: | - ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ - # Pre-compile Python bytecode so each pytest worker doesn't need to - $PYTEST --collect-only -m 'not slow' -qq && \ - MAX_EXAMPLES=1 $PYTEST -m 'not slow' && \ - chmod -R 777 /tmp/comma_download_cache" + source selfdrive/test/setup_xvfb.sh + # Pre-compile Python bytecode so each pytest worker doesn't need to + $PYTEST --collect-only -m 'not slow' -qq + MAX_EXAMPLES=1 $PYTEST -m 'not slow' process_replay: name: process replay @@ -182,17 +167,14 @@ jobs: id: dependency-cache uses: actions/cache@v5 with: - path: .ci_cache/comma_download_cache + path: /tmp/comma_download_cache key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/test_processes.py') }} - name: Build openpilot - run: | - ${{ env.RUN }} "scons -j$(nproc)" + run: scons -j$(nproc) - name: Run replay timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }} continue-on-error: ${{ github.ref == 'refs/heads/master' }} - run: | - ${{ env.RUN }} "selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ - chmod -R 777 /tmp/comma_download_cache" + run: selfdrive/test/process_replay/test_processes.py -j$(nproc) - name: Print diff id: print-diff if: always() @@ -226,9 +208,9 @@ jobs: - name: Run regen if: false timeout-minutes: 4 - run: | - ${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \ - chmod -R 777 /tmp/comma_download_cache" + env: + ONNXCPU: 1 + run: $PYTEST selfdrive/test/process_replay/test_regen.py simulator_driving: name: simulator driving @@ -246,14 +228,13 @@ jobs: - uses: ./.github/workflows/setup-with-retry id: setup-step - name: Build openpilot - run: | - ${{ env.RUN }} "scons -j$(nproc)" + run: scons -j$(nproc) - name: Driving test timeout-minutes: ${{ (steps.setup-step.outputs.duration < 18) && 1 || 2 }} run: | - ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ - source selfdrive/test/setup_vsound.sh && \ - CI=1 pytest -s tools/sim/tests/test_metadrive_bridge.py" + source selfdrive/test/setup_xvfb.sh + source selfdrive/test/setup_vsound.sh + pytest -s tools/sim/tests/test_metadrive_bridge.py create_ui_report: name: Create UI Report @@ -269,13 +250,12 @@ jobs: submodules: true - uses: ./.github/workflows/setup-with-retry - name: Build openpilot - run: ${{ env.RUN }} "scons -j$(nproc)" + run: scons -j$(nproc) - name: Create UI Report - run: > - ${{ env.RUN }} "PYTHONWARNINGS=ignore && - source selfdrive/test/setup_xvfb.sh && - python3 selfdrive/ui/tests/diff/replay.py && - python3 selfdrive/ui/tests/diff/replay.py --big" + run: | + source selfdrive/test/setup_xvfb.sh + python3 selfdrive/ui/tests/diff/replay.py + python3 selfdrive/ui/tests/diff/replay.py --big - name: Upload UI Report uses: actions/upload-artifact@v6 with: diff --git a/system/webrtc/tests/test_webrtcd.py b/system/webrtc/tests/test_webrtcd.py deleted file mode 100644 index e5f6ceaa376..00000000000 --- a/system/webrtc/tests/test_webrtcd.py +++ /dev/null @@ -1,65 +0,0 @@ -import pytest -import asyncio -import json -# for aiortc and its dependencies -import warnings -warnings.filterwarnings("ignore", category=DeprecationWarning) -warnings.filterwarnings("ignore", category=RuntimeWarning) # TODO: remove this when google-crc32c publish a python3.12 wheel - -from openpilot.system.webrtc.webrtcd import get_stream - -import aiortc -from teleoprtc import WebRTCOfferBuilder -from openpilot.common.parameterized import parameterized_class - - -@parameterized_class(("in_services", "out_services"), [ - (["testJoystick"], ["carState"]), - ([], ["carState"]), - (["testJoystick"], []), - ([], []), -]) -@pytest.mark.asyncio -class TestWebrtcdProc: - async def assertCompletesWithTimeout(self, awaitable, timeout=1): - try: - async with asyncio.timeout(timeout): - await awaitable - except TimeoutError: - pytest.fail("Timeout while waiting for awaitable to complete") - - async def test_webrtcd(self, mocker): - mock_request = mocker.MagicMock() - async def connect(offer): - body = {'sdp': offer.sdp, 'cameras': offer.video, 'bridge_services_in': self.in_services, 'bridge_services_out': self.out_services} - mock_request.json.side_effect = mocker.AsyncMock(return_value=body) - response = await get_stream(mock_request) - response_json = json.loads(response.text) - return aiortc.RTCSessionDescription(**response_json) - - builder = WebRTCOfferBuilder(connect) - builder.offer_to_receive_video_stream("road") - builder.offer_to_receive_audio_stream() - if len(self.in_services) > 0 or len(self.out_services) > 0: - builder.add_messaging() - - stream = builder.stream() - - await self.assertCompletesWithTimeout(stream.start()) - await self.assertCompletesWithTimeout(stream.wait_for_connection()) - - assert stream.has_incoming_video_track("road") - assert stream.has_incoming_audio_track() - assert stream.has_messaging_channel() == (len(self.in_services) > 0 or len(self.out_services) > 0) - - video_track, audio_track = stream.get_incoming_video_track("road"), stream.get_incoming_audio_track() - await self.assertCompletesWithTimeout(video_track.recv()) - await self.assertCompletesWithTimeout(audio_track.recv()) - - await self.assertCompletesWithTimeout(stream.stop()) - - # cleanup, very implementation specific, test may break if it changes - assert mock_request.app["streams"].__setitem__.called, "Implementation changed, please update this test" - _, session = mock_request.app["streams"].__setitem__.call_args.args - await self.assertCompletesWithTimeout(session.post_run_cleanup()) - From 5fc6fe68f637189c84b53383f5c5d23ae12c63d1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 16:14:46 -0800 Subject: [PATCH 273/685] rm mapbox-earcut (#37284) --- pyproject.toml | 1 - uv.lock | 22 ---------------------- 2 files changed, 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 12134ef1d98..b17af33def9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -67,7 +67,6 @@ dependencies = [ # ui "raylib > 5.5.0.3", "qrcode", - "mapbox-earcut", "jeepney", ] diff --git a/uv.lock b/uv.lock index e14591deee5..b3aa498e9a9 100644 --- a/uv.lock +++ b/uv.lock @@ -552,26 +552,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/6d/344a164d32d65d503ffe9201cd74cf13a020099dc446554d1e50b07f167b/libusb1-3.3.1-py3-none-win_amd64.whl", hash = "sha256:6e21b772d80d6487fbb55d3d2141218536db302da82f1983754e96c72781c102", size = 141080, upload-time = "2025-03-24T05:36:46.594Z" }, ] -[[package]] -name = "mapbox-earcut" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/7b/bbf6b00488662be5d2eb7a188222c264b6f713bac10dc4a77bf37a4cb4b6/mapbox_earcut-2.0.0.tar.gz", hash = "sha256:81eab6b86cf99551deb698b98e3f7502c57900e5c479df15e1bdaf1a57f0f9d6", size = 39934, upload-time = "2025-11-16T18:41:27.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/93/846804029d955c3c841d8efff77c2b0e8d9aab057d3a077dc8e3f88b5ea4/mapbox_earcut-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db55ce18e698bc9d90914ee7d4f8c3e4d23827456ece7c5d7a1ec91e90c7122b", size = 55623, upload-time = "2025-11-16T18:40:32.113Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f6/cc9ece104bc3876b350dba6fef7f34fb7b20ecc028d2cdbdbecb436b1ed1/mapbox_earcut-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01dd6099d16123baf582a11b2bd1d59ce848498cf0cdca3812fd1f8b20ff33b7", size = 52028, upload-time = "2025-11-16T18:40:33.516Z" }, - { url = "https://files.pythonhosted.org/packages/88/6e/230da4aabcc56c99e9bddb4c43ce7d4ba3609c0caf2d316fb26535d7c60c/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5a098aae26a52282bc981a38e7bf6b889d2ea7442f2cd1903d2ba842f4ff07", size = 56351, upload-time = "2025-11-16T18:40:35.217Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f7/5cdd3752526e91d91336c7263af7767b291d21e63c89d7190a60051f0f87/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de35f241d0b9110ad9260f295acedd9d7cc0d7acfe30d36b1b3ee8419c2caba1", size = 59209, upload-time = "2025-11-16T18:40:36.634Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a2/b7781416cb93b37b95d0444e03f87184de8815e57ff202ce4105fa921325/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cb63ab85e2e430c350f93e75c13f8b91cb8c8a045f3cd714c390b69a720368a", size = 152316, upload-time = "2025-11-16T18:40:38.147Z" }, - { url = "https://files.pythonhosted.org/packages/c1/74/396338e3d345e4e36fb23a0380921098b6a95ce7fb19c4777f4185a5974e/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb3c9f069fc3795306db87f8139f70c4f047532f897a3de05f54dc1faebc97f6", size = 157268, upload-time = "2025-11-16T18:40:39.753Z" }, - { url = "https://files.pythonhosted.org/packages/56/2c/66fd137ea86c508f6cd7247f7f6e2d1dabffc9f0e9ccf14c71406b197af1/mapbox_earcut-2.0.0-cp312-cp312-win32.whl", hash = "sha256:eb290e6676217707ed238dd55e07b0a8ca3ab928f6a27c4afefb2ff3af08d7cb", size = 51226, upload-time = "2025-11-16T18:40:41.018Z" }, - { url = "https://files.pythonhosted.org/packages/b8/84/7b78e37b0c2109243c0dad7d9ba9774b02fcee228bf61cf727a5aa1702e2/mapbox_earcut-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ef5b3319a43375272ad2cad9333ed16e569b5102e32a4241451358897e6f6ee", size = 56417, upload-time = "2025-11-16T18:40:42.173Z" }, - { url = "https://files.pythonhosted.org/packages/75/7f/cd7195aa27c1c8f2b9d38025a5a8663f32cd01c07b648a54b1308ab26c15/mapbox_earcut-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4a3706feb5cc8c782d8f68bb0110c8d551304043f680a87a54b0651a2c208c3", size = 50111, upload-time = "2025-11-16T18:40:43.334Z" }, -] - [[package]] name = "markdown" version = "3.10.2" @@ -807,7 +787,6 @@ dependencies = [ { name = "jeepney" }, { name = "json-rpc" }, { name = "libusb1" }, - { name = "mapbox-earcut" }, { name = "numpy" }, { name = "onnx" }, { name = "psutil" }, @@ -881,7 +860,6 @@ requires-dist = [ { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, { name = "libusb1" }, - { name = "mapbox-earcut" }, { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, { name = "mkdocs", marker = "extra == 'docs'" }, From f9f33c4dc4ab3fe366973cd833f415d7384a645b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 16:39:11 -0800 Subject: [PATCH 274/685] show venv size in package update job (#37286) * show venv size in package update job * lil more --- .github/workflows/repo-maintenance.yaml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 10b2e8ab2fc..d2c2447d7ac 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -48,6 +48,20 @@ jobs: echo 'PIP_TREE<> $GITHUB_OUTPUT uv pip tree >> $GITHUB_OUTPUT echo 'EOF' >> $GITHUB_OUTPUT + - name: venv size + id: venv_size + run: | + echo 'VENV_SIZE<> $GITHUB_OUTPUT + echo "Total: $(du -sh .venv | cut -f1)" >> $GITHUB_OUTPUT + echo "" >> $GITHUB_OUTPUT + echo "Top 10 by size:" >> $GITHUB_OUTPUT + du -sh .venv/lib/python*/site-packages/* 2>/dev/null \ + | grep -v '\.dist-info' \ + | grep -v '__pycache__' \ + | sort -rh \ + | head -10 \ + | while IFS=$'\t' read size path; do echo "$size ${path##*/}"; done >> $GITHUB_OUTPUT + echo 'EOF' >> $GITHUB_OUTPUT - name: bump submodules run: | git submodule update --remote @@ -71,6 +85,12 @@ jobs: Automatic PR from repo-maintenance -> package_updates ``` + $ du -sh .venv && du -sh .venv/lib/python*/site-packages/* | sort -rh | head -10 + ${{ steps.venv_size.outputs.VENV_SIZE }} + ``` + + ``` + $ uv pip tree ${{ steps.pip_tree.outputs.PIP_TREE }} ``` labels: bot From 09926bf5b5eaf9821a7c0fca3ac00800c2372119 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Fri, 20 Feb 2026 16:43:33 -0800 Subject: [PATCH 275/685] Revert "safer model pkl chunking (#37283)" This reverts commit 5d54743d8ba5f377f4260d9573d532ecfcfaae93. --- common/file_chunker.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/common/file_chunker.py b/common/file_chunker.py index 139a7dcacc1..f03d04a3827 100644 --- a/common/file_chunker.py +++ b/common/file_chunker.py @@ -1,9 +1,8 @@ -import glob import math import os from pathlib import Path -CHUNK_SIZE = 19 * 1024 * 1024 # 49MB, under GitHub's 50MB limit +CHUNK_SIZE = 49 * 1024 * 1024 # 49MB, under GitHub's 50MB limit def get_chunk_name(name, idx, num_chunks): return f"{name}.chunk{idx+1:02d}of{num_chunks:02d}" @@ -13,8 +12,6 @@ def get_chunk_paths(path, file_size): return [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] def chunk_file(path, num_chunks): - for old in glob.glob(f"{path}.chunk*"): - os.remove(old) with open(path, 'rb') as f: data = f.read() actual_num_chunks = max(1, math.ceil(len(data) / CHUNK_SIZE)) @@ -22,15 +19,13 @@ def chunk_file(path, num_chunks): for i in range(num_chunks): with open(get_chunk_name(path, i, num_chunks), 'wb') as f: f.write(data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE]) - os.remove(path) def read_file_chunked(path): - chunks = sorted(glob.glob(f"{path}.chunk*")) - if chunks: - expected = [get_chunk_name(path, i, len(chunks)) for i in range(len(chunks))] - assert chunks == expected, f"Chunk mismatch: {chunks} != {expected}" - return b''.join(Path(f).read_bytes() for f in chunks) + for num_chunks in range(1, 100): + if os.path.isfile(get_chunk_name(path, 0, num_chunks)): + files = [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] + return b''.join(Path(f).read_bytes() for f in files) if os.path.isfile(path): return Path(path).read_bytes() raise FileNotFoundError(path) From d6af0e6eb50960c9bb7c4fffd44dfe87cb6888c1 Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Fri, 20 Feb 2026 16:43:43 -0800 Subject: [PATCH 276/685] Revert "Simpler file chunker (#37276)" This reverts commit b27fa58444766c47f1ef980af79d324a669012c4. --- .gitignore | 3 +- common/file_chunker.py | 31 ------------------ selfdrive/modeld/SConscript | 45 ++++++++++++++------------- selfdrive/modeld/dmonitoringmodeld.py | 7 +++-- selfdrive/modeld/external_pickle.py | 38 ++++++++++++++++++++++ selfdrive/modeld/modeld.py | 9 +++--- 6 files changed, 72 insertions(+), 61 deletions(-) delete mode 100644 common/file_chunker.py create mode 100755 selfdrive/modeld/external_pickle.py diff --git a/.gitignore b/.gitignore index 062801d7874..af18f06628a 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,8 @@ flycheck_* cppcheck_report.txt comma*.sh -selfdrive/modeld/models/*.pkl* +selfdrive/modeld/models/*.pkl +selfdrive/modeld/models/*.pkl.* # openpilot log files *.bz2 diff --git a/common/file_chunker.py b/common/file_chunker.py deleted file mode 100644 index f03d04a3827..00000000000 --- a/common/file_chunker.py +++ /dev/null @@ -1,31 +0,0 @@ -import math -import os -from pathlib import Path - -CHUNK_SIZE = 49 * 1024 * 1024 # 49MB, under GitHub's 50MB limit - -def get_chunk_name(name, idx, num_chunks): - return f"{name}.chunk{idx+1:02d}of{num_chunks:02d}" - -def get_chunk_paths(path, file_size): - num_chunks = math.ceil(file_size / CHUNK_SIZE) - return [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] - -def chunk_file(path, num_chunks): - with open(path, 'rb') as f: - data = f.read() - actual_num_chunks = max(1, math.ceil(len(data) / CHUNK_SIZE)) - assert num_chunks >= actual_num_chunks, f"Allowed {num_chunks} chunks but needs at least {actual_num_chunks}, for path {path}" - for i in range(num_chunks): - with open(get_chunk_name(path, i, num_chunks), 'wb') as f: - f.write(data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE]) - - -def read_file_chunked(path): - for num_chunks in range(1, 100): - if os.path.isfile(get_chunk_name(path, 0, num_chunks)): - files = [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] - return b''.join(Path(f).read_bytes() for f in files) - if os.path.isfile(path): - return Path(path).read_bytes() - raise FileNotFoundError(path) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 35be2acc04d..1808cfec2f5 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,18 +1,14 @@ import os import glob -from openpilot.common.file_chunker import chunk_file, get_chunk_paths Import('env', 'arch') -chunker_file = File("#common/file_chunker.py") lenv = env.Clone() +CHUNK_BYTES = int(os.environ.get("TG_CHUNK_BYTES", str(45 * 1024 * 1024))) tinygrad_root = env.Dir("#").abspath tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] -def estimate_pickle_max_size(onnx_size): - return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty - # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath @@ -30,34 +26,39 @@ image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -compile_warp_cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye warp_targets = [] for cam in [_ar_ox_fisheye, _os_fisheye]: w, h = cam.width, cam.height warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -def chunk_warps(target, source, env): - for t in warp_targets: - chunk_file(t, 1) -chunk_targets = sum([get_chunk_paths(t, estimate_pickle_max_size(0)) for t in warp_targets], []) -lenv.Command(chunk_targets, tinygrad_files + script_files + [chunker_file], - [compile_warp_cmd, chunk_warps]) +lenv.Command(warp_targets, tinygrad_files + script_files, cmd) def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath - pkl = fn + "_tinygrad.pkl" - onnx_path = fn + ".onnx" - chunk_targets = get_chunk_paths(pkl, estimate_pickle_max_size(os.path.getsize(onnx_path))) - def do_chunk(target, source, env): - chunk_file(pkl, len(chunk_targets)) - return lenv.Command( - chunk_targets, - [onnx_path] + tinygrad_files + [chunker_file], - [f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', - do_chunk] + + out = fn + "_tinygrad.pkl" + full = out + ".full" + parts = out + ".parts" + + full_node = lenv.Command( + full, + [fn + ".onnx"] + tinygrad_files, + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {full}' ) + split_script = File(Dir("#selfdrive/modeld").File("external_pickle.py").abspath) + parts_node = lenv.Command( + parts, + [full_node, split_script, Value(str(CHUNK_BYTES))], + [f'python3 {split_script.abspath} {full} {out} {CHUNK_BYTES}', Delete(full)], + ) + + lenv.NoCache(parts_node) + lenv.AlwaysBuild(parts_node) + return parts_node + # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: tg_compile(tg_flags, model_name) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 6befe210a44..956ea8a6a26 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -16,8 +16,8 @@ from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.system.camerad.cameras.nv12_info import get_nv12_info -from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp +from openpilot.selfdrive.modeld.external_pickle import load_external_pickle PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') @@ -45,7 +45,7 @@ def __init__(self): self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self._blob_cache : dict[int, Tensor] = {} self.image_warp = None - self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH))) + self.model_run = load_external_pickle(MODEL_PKL_PATH) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib @@ -55,7 +55,8 @@ def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple if self.image_warp is None: self.frame_buf_params = get_nv12_info(buf.width, buf.height) warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - self.image_warp = pickle.loads(read_file_chunked(str(warp_path))) + with open(warp_path, "rb") as f: + self.image_warp = pickle.load(f) ptr = buf.data.ctypes.data # There is a ringbuffer of imgs, just cache tensors pointing to all of them if ptr not in self._blob_cache: diff --git a/selfdrive/modeld/external_pickle.py b/selfdrive/modeld/external_pickle.py new file mode 100755 index 00000000000..d60a9632a64 --- /dev/null +++ b/selfdrive/modeld/external_pickle.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +import hashlib +import pickle +import sys +from pathlib import Path + +def split_pickle(full_path: Path, out_prefix: Path, chunk_bytes: int) -> None: + data = full_path.read_bytes() + out_dir = out_prefix.parent + + for p in out_dir.glob(f"{out_prefix.name}.data-*"): + p.unlink() + + total = (len(data) + chunk_bytes - 1) // chunk_bytes + names = [] + for i in range(0, len(data), chunk_bytes): + name = f"{out_prefix.name}.data-{(i // chunk_bytes) + 1:04d}-of-{total:04d}" + (out_dir / name).write_bytes(data[i:i + chunk_bytes]) + names.append(name) + + manifest = hashlib.sha256(data).hexdigest() + "\n" + "\n".join(names) + "\n" + (out_dir / (out_prefix.name + ".parts")).write_text(manifest) + +def load_external_pickle(prefix: Path): + parts = prefix.parent / (prefix.name + ".parts") + lines = parts.read_text().splitlines() + expected_hash, chunk_names = lines[0], lines[1:] + + data = bytearray() + for name in chunk_names: + data += (prefix.parent / name).read_bytes() + + if hashlib.sha256(data).hexdigest() != expected_hash: + raise RuntimeError(f"hash mismatch loading {prefix}") + return pickle.loads(data) + +if __name__ == "__main__": + split_pickle(Path(sys.argv[1]), Path(sys.argv[2]), int(sys.argv[3])) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 8cfbea02c8d..3fe3e0e6d6a 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -27,8 +27,8 @@ from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState -from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan +from openpilot.selfdrive.modeld.external_pickle import load_external_pickle PROCESS_NAME = "selfdrive.modeld.modeld" @@ -178,8 +178,8 @@ def __init__(self): self.parser = Parser() self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None - self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) - self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) + self.vision_run = load_external_pickle(VISION_PKL_PATH) + self.policy_run = load_external_pickle(POLICY_PKL_PATH) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -196,7 +196,8 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], w, h = bufs[key].width, bufs[key].height self.frame_buf_params[key] = get_nv12_info(w, h) warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - self.update_imgs = pickle.loads(read_file_chunked(str(warp_path))) + with open(warp_path, "rb") as f: + self.update_imgs = pickle.load(f) for key in bufs.keys(): ptr = bufs[key].data.ctypes.data From 23e1c4f49e63e550a44a85f0020b601bd9376a1d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 16:47:47 -0800 Subject: [PATCH 277/685] rm onnx (#37285) --- pyproject.toml | 3 -- scripts/reporter.py | 28 ++++++++++---- selfdrive/modeld/get_model_metadata.py | 40 +++++++++++++------ uv.lock | 53 -------------------------- 4 files changed, 49 insertions(+), 75 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b17af33def9..f8614a47e19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,9 +37,6 @@ dependencies = [ "libusb1", "spidev; platform_system == 'Linux'", - # modeld - "onnx >= 1.14.0", - # logging "pyzmq", "sentry-sdk", diff --git a/scripts/reporter.py b/scripts/reporter.py index 903fcc89111..d894b8af48a 100755 --- a/scripts/reporter.py +++ b/scripts/reporter.py @@ -1,17 +1,33 @@ #!/usr/bin/env python3 import os import glob -import onnx + +from tinygrad.nn.onnx import OnnxPBParser BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) + MASTER_PATH = os.getenv("MASTER_PATH", BASEDIR) MODEL_PATH = "/selfdrive/modeld/models/" + +class MetadataOnnxPBParser(OnnxPBParser): + def _parse_ModelProto(self) -> dict: + obj = {"metadata_props": []} + for fid, wire_type in self._parse_message(self.reader.len): + match fid: + case 14: + obj["metadata_props"].append(self._parse_StringStringEntryProto()) + case _: + self.reader.skip_field(wire_type) + return obj + + def get_checkpoint(f): - model = onnx.load(f) - metadata = {prop.key: prop.value for prop in model.metadata_props} + model = MetadataOnnxPBParser(f).parse() + metadata = {prop["key"]: prop["value"] for prop in model["metadata_props"]} return metadata['model_checkpoint'].split('/')[0] + if __name__ == "__main__": print("| | master | PR branch |") print("|-| ----- | --------- |") @@ -24,8 +40,4 @@ def get_checkpoint(f): fn = os.path.basename(f) master = get_checkpoint(MASTER_PATH + MODEL_PATH + fn) pr = get_checkpoint(BASEDIR + MODEL_PATH + fn) - print( - "|", fn, "|", - f"[{master}](https://reporter.comma.life/experiment/{master})", "|", - f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|" - ) + print("|", fn, "|", f"[{master}](https://reporter.comma.life/experiment/{master})", "|", f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|") diff --git a/selfdrive/modeld/get_model_metadata.py b/selfdrive/modeld/get_model_metadata.py index 2001d23d752..838b1e9f404 100755 --- a/selfdrive/modeld/get_model_metadata.py +++ b/selfdrive/modeld/get_model_metadata.py @@ -1,33 +1,51 @@ #!/usr/bin/env python3 import sys import pathlib -import onnx import codecs import pickle from typing import Any -def get_name_and_shape(value_info:onnx.ValueInfoProto) -> tuple[str, tuple[int,...]]: - shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) - name = value_info.name +from tinygrad.nn.onnx import OnnxPBParser + + +class MetadataOnnxPBParser(OnnxPBParser): + def _parse_ModelProto(self) -> dict: + obj: dict[str, Any] = {"graph": {"input": [], "output": []}, "metadata_props": []} + for fid, wire_type in self._parse_message(self.reader.len): + match fid: + case 7: + obj["graph"] = self._parse_GraphProto() + case 14: + obj["metadata_props"].append(self._parse_StringStringEntryProto()) + case _: + self.reader.skip_field(wire_type) + return obj + + +def get_name_and_shape(value_info: dict[str, Any]) -> tuple[str, tuple[int, ...]]: + shape = tuple(int(dim) if isinstance(dim, int) else 0 for dim in value_info["parsed_type"].shape) + name = value_info["name"] return name, shape -def get_metadata_value_by_name(model:onnx.ModelProto, name:str) -> str | Any: - for prop in model.metadata_props: - if prop.key == name: - return prop.value + +def get_metadata_value_by_name(model: dict[str, Any], name: str) -> str | Any: + for prop in model["metadata_props"]: + if prop["key"] == name: + return prop["value"] return None + if __name__ == "__main__": model_path = pathlib.Path(sys.argv[1]) - model = onnx.load(str(model_path)) + model = MetadataOnnxPBParser(model_path).parse() output_slices = get_metadata_value_by_name(model, 'output_slices') assert output_slices is not None, 'output_slices not found in metadata' metadata = { 'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'), 'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")), - 'input_shapes': dict([get_name_and_shape(x) for x in model.graph.input]), - 'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output]) + 'input_shapes': dict(get_name_and_shape(x) for x in model["graph"]["input"]), + 'output_shapes': dict(get_name_and_shape(x) for x in model["graph"]["output"]), } metadata_path = model_path.parent / (model_path.stem + '_metadata.pkl') diff --git a/uv.lock b/uv.lock index b3aa498e9a9..844b1db7058 100644 --- a/uv.lock +++ b/uv.lock @@ -663,22 +663,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, ] -[[package]] -name = "ml-dtypes" -version = "0.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, -] - [[package]] name = "mpmath" version = "1.3.0" @@ -734,26 +718,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, ] -[[package]] -name = "onnx" -version = "1.20.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ml-dtypes" }, - { name = "numpy" }, - { name = "protobuf" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, - { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, - { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216, upload-time = "2026-01-10T01:39:39.46Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207, upload-time = "2026-01-10T01:39:41.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155, upload-time = "2026-01-10T01:39:45.536Z" }, -] - [[package]] name = "opencv-python-headless" version = "4.13.0.92" @@ -788,7 +752,6 @@ dependencies = [ { name = "json-rpc" }, { name = "libusb1" }, { name = "numpy" }, - { name = "onnx" }, { name = "psutil" }, { name = "pyaudio" }, { name = "pycapnp" }, @@ -864,7 +827,6 @@ requires-dist = [ { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, { name = "mkdocs", marker = "extra == 'docs'" }, { name = "numpy", specifier = ">=2.0" }, - { name = "onnx", specifier = ">=1.14.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, @@ -1030,21 +992,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] -[[package]] -name = "protobuf" -version = "6.33.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, -] - [[package]] name = "psutil" version = "7.2.2" From c46cf9f536ef3accbf4c8e9ecb3c2d1682887a91 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 18:35:24 -0800 Subject: [PATCH 278/685] lil pyproject.toml cleanup --- pyproject.toml | 7 ++----- uv.lock | 8 +++----- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f8614a47e19..4becd175638 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,17 +20,15 @@ dependencies = [ # core "cffi", "scons", - "pycapnp==2.1.0", + "pycapnp", "Cython", "setuptools", "numpy >=2.0", # body / webrtcd + "av", "aiohttp", "aiortc", - # aiortc does not put an upper bound on pyopenssl and is now incompatible - # with the latest release - "pyopenssl < 24.3.0", "pyaudio", # panda @@ -90,7 +88,6 @@ testing = [ ] dev = [ - "av", "matplotlib", "opencv-python-headless", ] diff --git a/uv.lock b/uv.lock index 844b1db7058..8b8bfd639ba 100644 --- a/uv.lock +++ b/uv.lock @@ -743,6 +743,7 @@ source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "aiortc" }, + { name = "av" }, { name = "casadi" }, { name = "cffi" }, { name = "crcmod-plus" }, @@ -757,7 +758,6 @@ dependencies = [ { name = "pycapnp" }, { name = "pycryptodome" }, { name = "pyjwt" }, - { name = "pyopenssl" }, { name = "pyserial" }, { name = "pyzmq" }, { name = "qrcode" }, @@ -778,7 +778,6 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "av" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, ] @@ -809,7 +808,7 @@ tools = [ requires-dist = [ { name = "aiohttp" }, { name = "aiortc" }, - { name = "av", marker = "extra == 'dev'" }, + { name = "av" }, { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, @@ -831,10 +830,9 @@ requires-dist = [ { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, { name = "pyaudio" }, - { name = "pycapnp", specifier = "==2.1.0" }, + { name = "pycapnp" }, { name = "pycryptodome" }, { name = "pyjwt" }, - { name = "pyopenssl", specifier = "<24.3.0" }, { name = "pyserial" }, { name = "pytest", marker = "extra == 'testing'" }, { name = "pytest-asyncio", marker = "extra == 'testing'" }, From c98ba4ff4cf4da424add07d6f3c182f4924cb48e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 18:54:00 -0800 Subject: [PATCH 279/685] Qt is optional (#37295) * Qt is optional * cleanup --- SConstruct | 6 ++--- tools/cabana/.gitignore | 2 +- tools/cabana/SConscript | 26 ++++++++++++------- tools/cabana/cabana | 38 ++++++++++++++++++++++++++++ tools/install_ubuntu_dependencies.sh | 8 ------ tools/mac_setup.sh | 22 ---------------- 6 files changed, 58 insertions(+), 44 deletions(-) create mode 100755 tools/cabana/cabana diff --git a/SConstruct b/SConstruct index 4f04be624cf..3b8aadf9140 100644 --- a/SConstruct +++ b/SConstruct @@ -211,10 +211,8 @@ SConscript(['third_party/SConscript']) SConscript(['selfdrive/SConscript']) -if Dir('#tools/cabana/').exists() and GetOption('extras'): - SConscript(['tools/replay/SConscript']) - if arch != "larch64": - SConscript(['tools/cabana/SConscript']) +if Dir('#tools/cabana/').exists() and arch != "larch64": + SConscript(['tools/cabana/SConscript']) env.CompilationDatabase('compile_commands.json') diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 362a51f5c92..3d64f832046 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -1,6 +1,6 @@ moc_* *.moc -cabana +_cabana dbc/car_fingerprint_to_dbc.json tests/test_cabana diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index b79d046fcaf..025797d1e37 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -1,14 +1,27 @@ import subprocess import os +import shutil -Import('env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib', 'cereal') +Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') + +# Detect Qt - skip build if not available +if arch == "Darwin": + brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() + has_qt = os.path.isdir(os.path.join(brew_prefix, "opt/qt@5")) +else: + has_qt = shutil.which('qmake') is not None + +if not has_qt: + Return() + +SConscript(['#tools/replay/SConscript']) +Import('replay_lib') qt_env = env.Clone() qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"] qt_libs = [] if arch == "Darwin": - brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() qt_env['QTDIR'] = f"{brew_prefix}/opt/qt@5" qt_dirs = [ os.path.join(qt_env['QTDIR'], "include"), @@ -31,12 +44,7 @@ else: qt_dirs += [f"{qt_install_headers}/QtGui/{qt_gui_dirs[0]}/QtGui", ] if qt_gui_dirs else [] qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] - qt_libs = [f"Qt5{m}" for m in qt_modules] - if arch == "larch64": - qt_libs += ["GLESv2", "wayland-client"] - qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath) - elif arch != "Darwin": - qt_libs += ["GL"] + qt_libs = [f"Qt5{m}" for m in qt_modules] + ["GL"] qt_env['QT3DIR'] = qt_env['QTDIR'] qt_env.Tool('qt3') @@ -83,7 +91,7 @@ cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcans 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) -cabana_env.Program('cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): cabana_env.Program('tests/test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) diff --git a/tools/cabana/cabana b/tools/cabana/cabana new file mode 100755 index 00000000000..00709734a54 --- /dev/null +++ b/tools/cabana/cabana @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +ROOT="$(cd "$DIR/../../" && pwd)" + +install_qt() { + if [[ "$(uname)" == "Darwin" ]]; then + brew install qt@5 + brew link qt@5 || true + else + SUDO="" + if [[ ! $(id -u) -eq 0 ]]; then + SUDO="sudo" + fi + $SUDO apt-get install -y --no-install-recommends \ + qtbase5-dev \ + qtbase5-dev-tools \ + qttools5-dev-tools \ + libqt5charts5-dev \ + libqt5svg5-dev \ + libqt5serialbus5-dev \ + libqt5x11extras5-dev \ + libqt5opengl5-dev + fi +} + +# Install Qt if not found +if ! command -v qmake &> /dev/null; then + echo "Qt not found, installing dependencies..." + install_qt +fi + +# Build _cabana +cd "$ROOT" +scons -j"$(nproc)" tools/cabana/_cabana + +exec "$DIR/_cabana" "$@" diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index f428e9972a8..a20176419fb 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -52,18 +52,12 @@ function install_ubuntu_common_requirements() { libglfw3-dev \ libglib2.0-0 \ libjpeg-dev \ - libqt5charts5-dev \ libncurses5-dev \ libusb-1.0-0-dev \ libzmq3-dev \ libzstd-dev \ libsqlite3-dev \ portaudio19-dev \ - qttools5-dev-tools \ - libqt5svg5-dev \ - libqt5serialbus5-dev \ - libqt5x11extras5-dev \ - libqt5opengl5-dev \ gettext } @@ -73,8 +67,6 @@ function install_ubuntu_lts_latest_requirements() { $SUDO apt-get install -y --no-install-recommends \ g++-12 \ - qtbase5-dev \ - qtbase5-dev-tools \ python3-dev \ python3-venv } diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 82748e96131..a5a6eed0402 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -36,7 +36,6 @@ brew "glfw" brew "libusb" brew "libtool" brew "llvm" -brew "qt@5" brew "zeromq" cask "gcc-arm-embedded" brew "portaudio" @@ -56,27 +55,6 @@ export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/bzip2/include" $DIR/install_python_dependencies.sh echo "[ ] installed python dependencies t=$SECONDS" -# brew does not link qt5 by default -# check if qt5 can be linked, if not, prompt the user to link it -QT_BIN_LOCATION="$(command -v lupdate || :)" -if [ -n "$QT_BIN_LOCATION" ]; then - # if qt6 is linked, prompt the user to unlink it and link the right version - QT_BIN_VERSION="$(lupdate -version | awk '{print $NF}')" - if [[ ! "$QT_BIN_VERSION" =~ 5\.[0-9]+\.[0-9]+ ]]; then - echo - echo "lupdate/lrelease available at PATH is $QT_BIN_VERSION" - if [[ "$QT_BIN_LOCATION" == "$(brew --prefix)/"* ]]; then - echo "Run the following command to link qt5:" - echo "brew unlink qt@6 && brew link qt@5" - else - echo "Remove conflicting qt entries from PATH and run the following command to link qt5:" - echo "brew link qt@5" - fi - fi -else - brew link qt@5 -fi - echo echo "---- OPENPILOT SETUP DONE ----" echo "Open a new shell or configure your active shell env by running:" From 30350f4207b220b414b36b7ec4c9994050939b35 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 20 Feb 2026 19:00:27 -0800 Subject: [PATCH 280/685] ui: navigation stack (#37094) * initial * start to support nav stack in settings panels + fix some navwidget bugs * add deprecation warning and move more to new nav stack * fix overriding NavWidget enabled and do developer panel * fix interactive timeout and do main * more device, not done yet * minor network fixes * dcam dialog * start onboarding * fix onboarding * do mici setup * remove now useless CUSTOM_SOFTWARE * support big ui with old modal overlay * reset can be old modal overlay, but updater needs new since it uses wifiui * flip name truthiness to inspire excitement * all *should* work, but will do pass later * clean up main * clean up settiings * clean up dialog and developer * cleanup mici setup some * rm one more * fix keyboard * revert * might as well but clarify * fix networkinfopage buttons * lint * nice clean up from cursor * animate background fade with position * fix device overlays * cursor fix pt1 cursor fix pt2 * rm print * capital * temp fix from cursor for onboarding not freeing space after reviewing training guide * fix home screen scroller snap not resetting * stash * nice gradient on top * 40 * 20 * no gradient * return unused returns and always show regulatory btn * nice! * clean up * new_modal is always true! * more clean up * clean up * big only renders top 1 * fixup setup and updater * stash * Revert "stash" This reverts commit 3cfb226ccb51869ed1f7d630b5fdd6725ad094d5. * fix mici keys coming in from top * clean up * fix mici dialogs like tici, pop first incase call back pushes * clever way but not not * Revert "clever way but not not" This reverts commit f69d106df61262f049df20cc1a9064ca1e6feeb7. * more setup * mici keyboard: fix not disabling below * cmt * fix wifi callbacks not running in rare case * clean up network * clean up network * clean up dialog * pairing * rm * todo * fix replay * they push themselkves! * clean up ui_state * clean up application * clean up * stash * Revert "stash" This reverts commit 07d3f5f26c99ef891086b6fe03095d53a62b8631. * typing * lint --- selfdrive/ui/mici/layouts/main.py | 81 ++++------ selfdrive/ui/mici/layouts/onboarding.py | 14 +- .../ui/mici/layouts/settings/developer.py | 11 +- selfdrive/ui/mici/layouts/settings/device.py | 38 ++--- .../ui/mici/layouts/settings/firehose.py | 7 +- .../mici/layouts/settings/network/__init__.py | 42 ++--- .../mici/layouts/settings/network/wifi_ui.py | 45 ++---- .../ui/mici/layouts/settings/settings.py | 83 +++------- selfdrive/ui/mici/layouts/settings/toggles.py | 5 +- .../ui/mici/onroad/augmented_road_view.py | 2 +- .../ui/mici/onroad/driver_camera_dialog.py | 10 +- selfdrive/ui/mici/tests/test_widget_leaks.py | 2 - selfdrive/ui/mici/widgets/dialog.py | 38 ++--- selfdrive/ui/mici/widgets/pairing_dialog.py | 11 +- selfdrive/ui/onroad/augmented_road_view.py | 2 +- selfdrive/ui/onroad/driver_camera_dialog.py | 2 +- selfdrive/ui/tests/diff/replay.py | 8 +- selfdrive/ui/tests/profile_onroad.py | 5 +- selfdrive/ui/ui.py | 12 +- selfdrive/ui/widgets/pairing_dialog.py | 2 +- system/ui/lib/application.py | 102 ++++--------- system/ui/mici_reset.py | 8 +- system/ui/mici_setup.py | 143 +++++++----------- system/ui/mici_updater.py | 7 +- system/ui/tici_reset.py | 2 +- system/ui/tici_setup.py | 2 +- system/ui/tici_updater.py | 2 +- system/ui/widgets/__init__.py | 31 +++- system/ui/widgets/keyboard.py | 2 +- system/ui/widgets/mici_keyboard.py | 7 +- system/ui/widgets/network.py | 2 +- 31 files changed, 258 insertions(+), 470 deletions(-) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index b78a1d8eaf5..be2c4747f32 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -1,5 +1,4 @@ import pyray as rl -from enum import IntEnum import cereal.messaging as messaging from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout @@ -15,18 +14,12 @@ ONROAD_DELAY = 2.5 # seconds -class MainState(IntEnum): - MAIN = 0 - SETTINGS = 1 - - class MiciMainLayout(Widget): def __init__(self): super().__init__() self._pm = messaging.PubMaster(['bookmarkButton']) - self._current_mode: MainState | None = None self._prev_onroad = False self._prev_standstill = False self._onroad_time_delay: float | None = None @@ -49,38 +42,31 @@ def __init__(self): self._onroad_layout, ], spacing=0, pad_start=0, pad_end=0, scroll_indicator=False, edge_shadows=False) self._scroller.set_reset_scroll_at_show(False) + self._scroller.set_enabled(lambda: self.enabled) # for nav stack # Disable scrolling when onroad is interacting with bookmark self._scroller.set_scrolling_enabled(lambda: not self._onroad_layout.is_swiping_left()) - self._layouts = { - MainState.MAIN: self._scroller, - MainState.SETTINGS: self._settings_layout, - } - # Set callbacks self._setup_callbacks() - # Start onboarding if terms or training not completed + gui_app.push_widget(self) + + # Start onboarding if terms or training not completed, make sure to push after self self._onboarding_window = OnboardingWindow() if not self._onboarding_window.completed: - gui_app.set_modal_overlay(self._onboarding_window) + gui_app.push_widget(self._onboarding_window) def _setup_callbacks(self): - self._home_layout.set_callbacks(on_settings=self._on_settings_clicked) - self._settings_layout.set_callbacks(on_close=self._on_settings_closed) + self._home_layout.set_callbacks(on_settings=lambda: gui_app.push_widget(self._settings_layout)) self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) - device.add_interactive_timeout_callback(self._set_mode_for_started) + device.add_interactive_timeout_callback(self._on_interactive_timeout) def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) self._scroller.scroll_to(layout_x, smooth=True) def _render(self, _): - # Initial show event - if self._current_mode is None: - self._set_mode(MainState.MAIN) - if not self._setup: if self._alerts_layout.active_alerts() > 0: self._scroller.scroll_to(self._alerts_layout.rect.x) @@ -89,59 +75,50 @@ def _render(self, _): self._setup = True # Render - if self._current_mode == MainState.MAIN: - self._scroller.render(self._rect) - - elif self._current_mode == MainState.SETTINGS: - self._settings_layout.render(self._rect) + self._scroller.render(self._rect) self._handle_transitions() - def _set_mode(self, mode: MainState): - if mode != self._current_mode: - if self._current_mode is not None: - self._layouts[self._current_mode].hide_event() - self._layouts[mode].show_event() - self._current_mode = mode - def _handle_transitions(self): + # Don't pop if onboarding + if gui_app.get_active_widget() == self._onboarding_window: + return + if ui_state.started != self._prev_onroad: self._prev_onroad = ui_state.started + # onroad: after delay, pop nav stack and scroll to onroad + # offroad: immediately scroll to home, but don't pop nav stack (can stay in settings) if ui_state.started: self._onroad_time_delay = rl.get_time() else: - self._set_mode_for_started(True) + self._scroll_to(self._home_layout) - # delay so we show home for a bit after starting if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: - self._set_mode_for_started(True) + gui_app.pop_widgets_to(self) + self._scroll_to(self._onroad_layout) self._onroad_time_delay = None + # When car leaves standstill, pop nav stack and scroll to onroad CS = ui_state.sm["carState"] if not CS.standstill and self._prev_standstill: - self._set_mode(MainState.MAIN) + gui_app.pop_widgets_to(self) self._scroll_to(self._onroad_layout) self._prev_standstill = CS.standstill - def _set_mode_for_started(self, onroad_transition: bool = False): + def _on_interactive_timeout(self): + # Don't pop if onboarding + if gui_app.get_active_widget() == self._onboarding_window: + return + if ui_state.started: - CS = ui_state.sm["carState"] - # Only go onroad if car starts or is not at a standstill - if not CS.standstill or onroad_transition: - self._set_mode(MainState.MAIN) + # Don't pop if at standstill + if not ui_state.sm["carState"].standstill: + gui_app.pop_widgets_to(self) self._scroll_to(self._onroad_layout) else: - # Stay in settings if car turns off while in settings - if not onroad_transition or self._current_mode != MainState.SETTINGS: - self._set_mode(MainState.MAIN) - self._scroll_to(self._home_layout) - - def _on_settings_clicked(self): - self._set_mode(MainState.SETTINGS) - - def _on_settings_closed(self): - self._set_mode(MainState.MAIN) + gui_app.pop_widgets_to(self) + self._scroll_to(self._home_layout) def _on_bookmark_clicked(self): user_bookmark = messaging.new_message('bookmarkButton') diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index a399d7679cf..539074453cd 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -7,7 +7,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import FontWeight, gui_app -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets import Widget, NavWidget from openpilot.system.ui.widgets.button import SmallButton, SmallCircleIconButton from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.slider import SmallSlider @@ -42,7 +42,7 @@ def _render(self, rect): gui_label(rect, tr("camera starting"), font_size=64, font_weight=FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) rl.end_scissor_mode() - return -1 + return # Position dmoji on opposite side from driver is_rhd = self.driver_state_renderer.is_rhd @@ -55,7 +55,6 @@ def _render(self, rect): self._draw_face_detection(rect) rl.end_scissor_mode() - return -1 class TrainingGuidePreDMTutorial(SetupTermsPage): @@ -367,9 +366,9 @@ def _advance_step(self): self._completed_callback() def _render(self, _): + rl.draw_rectangle_rec(self._rect, rl.BLACK) if self._step < len(self._steps): self._steps[self._step].render(self._rect) - return -1 class DeclinePage(Widget): @@ -438,9 +437,11 @@ def _render_content(self, scroll_offset): )) -class OnboardingWindow(Widget): +class OnboardingWindow(NavWidget): def __init__(self): super().__init__() + self.set_back_enabled(False) + self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version @@ -473,7 +474,7 @@ def _on_decline_back(self): def close(self): ui_state.params.put_bool("IsDriverViewEnabled", False) - gui_app.set_modal_overlay(None) + gui_app.pop_widget() def _on_terms_accepted(self): ui_state.params.put("HasAcceptedTerms", terms_version) @@ -490,4 +491,3 @@ def _render(self, _): self._training_guide.render(self._rect) elif self._state == OnboardingState.DECLINE: self._decline_page.render(self._rect) - return -1 diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index ad68d6ee940..383c7ef9b97 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -1,5 +1,4 @@ import pyray as rl -from collections.abc import Callable from openpilot.common.time_helpers import system_time_valid from openpilot.system.ui.widgets.scroller import Scroller @@ -13,9 +12,9 @@ class DeveloperLayoutMici(NavWidget): - def __init__(self, back_callback: Callable): + def __init__(self): super().__init__() - self.set_back_callback(back_callback) + self.set_back_callback(gui_app.pop_widget) def github_username_callback(username: str): if username: @@ -25,16 +24,16 @@ def github_username_callback(username: str): self._ssh_keys_btn.set_value(username) else: dlg = BigDialog("", ssh_keys._error_message) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) def ssh_keys_callback(): github_username = ui_state.params.get("GithubUsername") or "" dlg = BigInputDialog("enter GitHub username...", github_username, confirm_callback=github_username_callback) if not system_time_valid(): dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "") - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) return - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 56, 64) github_username = ui_state.params.get("GithubUsername") or "" diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 13462674658..e1beae4fe30 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -28,7 +28,7 @@ class MiciFccModal(NavWidget): def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() - self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) + self.set_back_callback(gui_app.pop_widget) self._content = HtmlRenderer(file_path=file_path, text=text) self._scroll_panel = GuiScrollPanel2(horizontal=False) self._fcc_logo = gui_app.texture("icons_mici/settings/device/fcc_logo.png", 76, 64) @@ -47,8 +47,6 @@ def _render(self, rect: rl.Rectangle): rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE) - return -1 - def _engaged_confirmation_callback(callback: Callable, action_text: str): if not ui_state.engaged: @@ -74,10 +72,10 @@ def confirm_callback(): dlg: BigConfirmationDialogV2 | BigDialog = BigConfirmationDialogV2(f"slide to\n{action_text.lower()}", icon, red=red, exit_on_confirm=action_text == "reset", confirm_callback=confirm_callback) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) else: dlg = BigDialog(f"Disengage to {action_text}", "") - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) class DeviceInfoLayoutMici(Widget): @@ -147,7 +145,7 @@ def _handle_mouse_release(self, mouse_pos: MousePos): dlg = BigDialog(tr("Device must be registered with the comma.ai backend to pair"), "") else: dlg = PairingDialog() - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) UPDATER_TIMEOUT = 10.0 # seconds to wait for updater to respond @@ -173,7 +171,7 @@ def offroad_transition(self): def _handle_mouse_release(self, mouse_pos: MousePos): if not system_time_valid(): dlg = BigDialog(tr("Please connect to Wi-Fi to update"), "") - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) return self.set_enabled(False) @@ -268,12 +266,10 @@ def _update_state(self): class DeviceLayoutMici(NavWidget): - def __init__(self, back_callback: Callable): + def __init__(self): super().__init__() self._fcc_dialog: HtmlModal | None = None - self._driver_camera: DriverCameraDialog | None = None - self._training_guide: TrainingGuide | None = None def power_off_callback(): ui_state.params.put_bool("DoShutdown", True) @@ -309,11 +305,11 @@ def uninstall_openpilot_callback(): regulatory_btn.set_click_callback(self._on_regulatory) driver_cam_btn = BigButton("driver\ncamera preview", "", "icons_mici/settings/device/cameras.png") - driver_cam_btn.set_click_callback(self._show_driver_camera) + driver_cam_btn.set_click_callback(lambda: gui_app.push_widget(DriverCameraDialog())) driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png") - review_training_guide_btn.set_click_callback(self._on_review_training_guide) + review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget))) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) self._scroller = Scroller([ @@ -330,7 +326,8 @@ def uninstall_openpilot_callback(): ], snap_items=False) # Set up back navigation - self.set_back_callback(back_callback) + # TODO: can this somehow be generic in widgets/__init__.py or application.py? + self.set_back_callback(gui_app.pop_widget) # Hide power off button when onroad ui_state.add_offroad_transition_callback(self._offroad_transition) @@ -338,24 +335,11 @@ def uninstall_openpilot_callback(): def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = MiciFccModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/mici_fcc.html")) - gui_app.set_modal_overlay(self._fcc_dialog) + gui_app.push_widget(self._fcc_dialog) def _offroad_transition(self): self._power_off_btn.set_visible(ui_state.is_offroad()) - def _show_driver_camera(self): - if not self._driver_camera: - self._driver_camera = DriverCameraDialog() - gui_app.set_modal_overlay(self._driver_camera, callback=lambda result: setattr(self, '_driver_camera', None)) - - def _on_review_training_guide(self): - if not self._training_guide: - def completed_callback(): - gui_app.set_modal_overlay(None) - - self._training_guide = TrainingGuide(completed_callback=completed_callback) - gui_app.set_modal_overlay(self._training_guide, callback=lambda result: setattr(self, '_training_guide', None)) - def show_event(self): super().show_event() self._scroller.show_event() diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index d305906e13d..a2288752eaf 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -132,9 +132,6 @@ def _render(self, rect: rl.Rectangle): y = self._draw_wrapped_text(x, y, w, tr(answer), gui_app.font(FontWeight.ROMAN), 32, self.LIGHT_GRAY) y += 20 - # return value not used by NavWidget - return -1 - def _draw_wrapped_text(self, x, y, width, text, font, font_size, color): wrapped = wrap_text(font, text, font_size, width) for line in wrapped: @@ -223,6 +220,6 @@ def _update_loop(self): class FirehoseLayout(FirehoseLayoutBase, NavWidget): BACK_TOUCH_AREA_PERCENTAGE = 0.1 - def __init__(self, back_callback): + def __init__(self): super().__init__() - self.set_back_callback(back_callback) + self.set_back_callback(gui_app.pop_widget) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index cd0f4ee80f2..9f49521c36d 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -1,6 +1,4 @@ import pyray as rl -from enum import IntEnum -from collections.abc import Callable from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon @@ -13,21 +11,13 @@ from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, normalize_ssid -class NetworkPanelType(IntEnum): - NONE = 0 - WIFI = 1 - - class NetworkLayoutMici(NavWidget): - def __init__(self, back_callback: Callable): + def __init__(self): super().__init__() - self._current_panel = NetworkPanelType.WIFI - self.set_back_enabled(lambda: self._current_panel == NetworkPanelType.NONE) - self._wifi_manager = WifiManager() self._wifi_manager.set_active(False) - self._wifi_ui = WifiUIMici(self._wifi_manager, back_callback=lambda: self._switch_to_panel(NetworkPanelType.NONE)) + self._wifi_ui = WifiUIMici(self._wifi_manager) self._wifi_manager.add_callbacks( networks_updated=self._on_network_updated, @@ -52,7 +42,7 @@ def tethering_password_clicked(): tethering_password = self._wifi_manager.tethering_password dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, confirm_callback=tethering_password_callback) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) @@ -79,7 +69,7 @@ def network_metered_callback(value: str): self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47) self._wifi_button = BigButton("wi-fi", "not connected", self._wifi_slash_txt, scroll=True) - self._wifi_button.set_click_callback(lambda: self._switch_to_panel(NetworkPanelType.WIFI)) + self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) # ******** Advanced settings ******** # ******** Roaming toggle ******** @@ -111,7 +101,7 @@ def network_metered_callback(value: str): self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) # Set up back navigation - self.set_back_callback(back_callback) + self.set_back_callback(gui_app.pop_widget) def _update_state(self): super()._update_state() @@ -146,14 +136,18 @@ def _update_state(self): def show_event(self): super().show_event() - self._current_panel = NetworkPanelType.NONE self._wifi_manager.set_active(True) self._scroller.show_event() + # Process wifi callbacks while at any point in the nav stack + gui_app.set_nav_stack_tick(self._wifi_manager.process_callbacks) + def hide_event(self): super().hide_event() self._wifi_manager.set_active(False) + gui_app.set_nav_stack_tick(None) + def _toggle_roaming(self, checked: bool): self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) @@ -169,7 +163,7 @@ def update_apn(apn: str): current_apn = ui_state.params.get("GsmApn") or "" dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) def _toggle_cellular_metered(self, checked: bool): self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked) @@ -191,17 +185,5 @@ def _on_network_updated(self, networks: list[Network]): MeteredType.NO: 'unmetered' }.get(self._wifi_manager.current_network_metered, 'default')) - def _switch_to_panel(self, panel_type: NetworkPanelType): - if panel_type == NetworkPanelType.WIFI: - self._wifi_ui.show_event() - elif self._current_panel == NetworkPanelType.WIFI: - self._wifi_ui.hide_event() - self._current_panel = panel_type - def _render(self, rect: rl.Rectangle): - self._wifi_manager.process_callbacks() - - if self._current_panel == NetworkPanelType.WIFI: - self._wifi_ui.render(rect) - else: - self._scroller.render(rect) + self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 34e73e823eb..19183625275 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -186,10 +186,9 @@ def _render(self, _): class ForgetButton(Widget): HORIZONTAL_MARGIN = 8 - def __init__(self, forget_network: Callable, open_network_manage_page): + def __init__(self, forget_network: Callable): super().__init__() self._forget_network = forget_network - self._open_network_manage_page = open_network_manage_page self._bg_txt = gui_app.texture("icons_mici/settings/network/new/forget_button.png", 100, 100) self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/forget_button_pressed.png", 100, 100) @@ -200,7 +199,7 @@ def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) dlg = BigConfirmationDialogV2("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, confirm_callback=self._forget_network) - gui_app.set_modal_overlay(dlg, callback=self._open_network_manage_page) + gui_app.push_widget(dlg) def _render(self, _): bg_txt = self._bg_pressed_txt if self.is_pressed else self._bg_txt @@ -212,7 +211,7 @@ def _render(self, _): class NetworkInfoPage(NavWidget): - def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, open_network_manage_page: Callable, + def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, connecting_callback: Callable[[], str | None], connected_callback: Callable[[], str | None]): super().__init__() self._wifi_manager = wifi_manager @@ -220,8 +219,8 @@ def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Ca self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) self._wifi_icon = WifiIcon() - self._forget_btn = ForgetButton(lambda: forget_callback(self._network.ssid) if self._network is not None else None, - open_network_manage_page) + self._forget_btn = ForgetButton(lambda: forget_callback(self._network.ssid) if self._network is not None else None) + self._forget_btn.set_enabled(lambda: self.enabled) # for stack self._connect_btn = ConnectButton() self._connect_btn.set_click_callback(lambda: connect_callback(self._network.ssid) if self._network is not None else None) @@ -230,7 +229,7 @@ def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Ca self._subtitle = UnifiedLabel("", 36, FontWeight.ROMAN, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) - self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) + self.set_back_callback(gui_app.pop_widget) # State self._network: Network | None = None @@ -249,11 +248,13 @@ def update_networks(self, networks: dict[str, Network]): break else: # network disappeared, close page - gui_app.set_modal_overlay(None) + # TODO: pop_widgets_to, to close potentially open keyboard too + if gui_app.get_active_widget() == self: + gui_app.pop_widget() def _update_state(self): super()._update_state() - # Modal overlays stop main UI rendering, so we need to call here + # TODO: remove? only left for potential compatibility with setup/updater self._wifi_manager.process_callbacks() if self._network is None: @@ -271,7 +272,7 @@ def _update_state(self): self._connect_btn.set_enabled(False) else: # saved or unknown self._connect_btn.set_label("connect") - self._connect_btn.set_enabled(True) + self._connect_btn.set_enabled(self.enabled) self._title.set_text(normalize_ssid(self._network.ssid)) if self._network.security_type == SecurityType.OPEN: @@ -336,17 +337,15 @@ def _render(self, _): self._forget_btn.rect.height, )) - return -1 - class WifiUIMici(BigMultiOptionDialog): - def __init__(self, wifi_manager: WifiManager, back_callback: Callable): + def __init__(self, wifi_manager: WifiManager): super().__init__([], None) # Set up back navigation - self.set_back_callback(back_callback) + self.set_back_callback(gui_app.pop_widget) - self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, self._open_network_manage_page, + self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, lambda: wifi_manager.connecting_to_ssid, lambda: wifi_manager.connected_ssid) self._loading_animation = LoadingAnimation() @@ -370,11 +369,6 @@ def hide_event(self): super().hide_event() self._scroller.hide_event() - def _open_network_manage_page(self, result=None): - if self._network_info_page._network is not None and self._network_info_page._network.ssid in self._networks: - self._network_info_page.update_networks(self._networks) - gui_app.set_modal_overlay(self._network_info_page) - def _on_network_updated(self, networks: list[Network]): self._networks = {network.ssid: network for network in networks} self._update_buttons() @@ -413,7 +407,7 @@ def _on_option_selected(self, option: str): if option in self._networks: self._network_info_page.set_current_network(self._networks[option]) - self._open_network_manage_page() + gui_app.push_widget(self._network_info_page) def _connect_to_network(self, ssid: str): network = self._networks.get(ssid) @@ -434,14 +428,7 @@ def _on_need_auth(self, ssid, incorrect_password=True): hint = "wrong password..." if incorrect_password else "enter password..." dlg = BigInputDialog(hint, "", minimum_length=8, confirm_callback=lambda _password: self._connect_with_password(ssid, _password)) - - def on_close(result=None): - gui_app.set_modal_overlay_tick(None) - self._open_network_manage_page(result) - - # Process wifi callbacks while the keyboard is shown so forgotten clears connecting state - gui_app.set_modal_overlay_tick(self._wifi_manager.process_callbacks) - gui_app.set_modal_overlay(dlg, on_close) + gui_app.push_widget(dlg) def _render(self, _): super()._render(_) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index a6f59a0389a..2372104a007 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -1,7 +1,4 @@ import pyray as rl -from dataclasses import dataclass -from enum import IntEnum -from collections.abc import Callable from openpilot.common.params import Params from openpilot.system.ui.widgets.scroller import Scroller @@ -12,22 +9,7 @@ from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets import Widget, NavWidget - - -class PanelType(IntEnum): - TOGGLES = 0 - NETWORK = 1 - DEVICE = 2 - DEVELOPER = 3 - USER_MANUAL = 4 - FIREHOSE = 5 - - -@dataclass -class PanelInfo: - name: str - instance: Widget +from openpilot.system.ui.widgets import NavWidget class SettingsBigButton(BigButton): @@ -39,19 +21,26 @@ class SettingsLayout(NavWidget): def __init__(self): super().__init__() self._params = Params() - self._current_panel = None # PanelType.DEVICE + toggles_panel = TogglesLayoutMici() toggles_btn = SettingsBigButton("toggles", "", "icons_mici/settings.png") - toggles_btn.set_click_callback(lambda: self._set_current_panel(PanelType.TOGGLES)) + toggles_btn.set_click_callback(lambda: gui_app.push_widget(toggles_panel)) + + network_panel = NetworkLayoutMici() network_btn = SettingsBigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) - network_btn.set_click_callback(lambda: self._set_current_panel(PanelType.NETWORK)) + network_btn.set_click_callback(lambda: gui_app.push_widget(network_panel)) + + device_panel = DeviceLayoutMici() device_btn = SettingsBigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) - device_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVICE)) + device_btn.set_click_callback(lambda: gui_app.push_widget(device_panel)) + + developer_panel = DeveloperLayoutMici() developer_btn = SettingsBigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) - developer_btn.set_click_callback(lambda: self._set_current_panel(PanelType.DEVELOPER)) + developer_btn.set_click_callback(lambda: gui_app.push_widget(developer_panel)) + firehose_panel = FirehoseLayout() firehose_btn = SettingsBigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) - firehose_btn.set_click_callback(lambda: self._set_current_panel(PanelType.FIREHOSE)) + firehose_btn.set_click_callback(lambda: gui_app.push_widget(firehose_panel)) self._scroller = Scroller([ toggles_btn, @@ -64,55 +53,17 @@ def __init__(self): ], snap_items=False) # Set up back navigation - self.set_back_callback(self.close_settings) - self.set_back_enabled(lambda: self._current_panel is None) - - self._panels = { - PanelType.TOGGLES: PanelInfo("Toggles", TogglesLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.NETWORK: PanelInfo("Network", NetworkLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.DEVICE: PanelInfo("Device", DeviceLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.DEVELOPER: PanelInfo("Developer", DeveloperLayoutMici(back_callback=lambda: self._set_current_panel(None))), - PanelType.FIREHOSE: PanelInfo("Firehose", FirehoseLayout(back_callback=lambda: self._set_current_panel(None))), - } + self.set_back_callback(gui_app.pop_widget) self._font_medium = gui_app.font(FontWeight.MEDIUM) - # Callbacks - self._close_callback: Callable | None = None - def show_event(self): super().show_event() - self._set_current_panel(None) self._scroller.show_event() - if self._current_panel is not None: - self._panels[self._current_panel].instance.show_event() def hide_event(self): super().hide_event() - if self._current_panel is not None: - self._panels[self._current_panel].instance.hide_event() - - def set_callbacks(self, on_close: Callable): - self._close_callback = on_close + self._scroller.hide_event() def _render(self, rect: rl.Rectangle): - if self._current_panel is not None: - self._draw_current_panel() - else: - self._scroller.render(rect) - - def _draw_current_panel(self): - panel = self._panels[self._current_panel] - panel.instance.render(self._rect) - - def _set_current_panel(self, panel_type: PanelType | None): - if panel_type != self._current_panel: - if self._current_panel is not None: - self._panels[self._current_panel].instance.hide_event() - self._current_panel = panel_type - if self._current_panel is not None: - self._panels[self._current_panel].instance.show_event() - - def close_settings(self): - if self._close_callback: - self._close_callback() + self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index c16504fac8b..d6fb75a4d8d 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -1,5 +1,4 @@ import pyray as rl -from collections.abc import Callable from cereal import log from openpilot.system.ui.widgets.scroller import Scroller @@ -13,9 +12,9 @@ class TogglesLayoutMici(NavWidget): - def __init__(self, back_callback: Callable): + def __init__(self): super().__init__() - self.set_back_callback(back_callback) + self.set_back_callback(gui_app.pop_widget) self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"]) self._experimental_btn = BigParamControl("experimental mode", "ExperimentalMode") diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 69bcca401d9..99e33e8644d 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -363,7 +363,7 @@ def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: if __name__ == "__main__": gui_app.init_window("OnRoad Camera View") - road_camera_view = AugmentedRoadView(ROAD_CAM) + road_camera_view = AugmentedRoadView(lambda: None, stream_type=ROAD_CAM) print("***press space to switch camera view***") try: for _ in gui_app.render(): diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index bab3d6e6f1d..26a5d132c69 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -34,8 +34,8 @@ def __init__(self, no_escape=False): self._pm: messaging.PubMaster | None = None if not no_escape: # TODO: this can grow unbounded, should be given some thought - device.add_interactive_timeout_callback(lambda: gui_app.set_modal_overlay(None)) - self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) + device.add_interactive_timeout_callback(gui_app.pop_widget) + self.set_back_callback(gui_app.pop_widget) self.set_back_enabled(not no_escape) # Load eye icons @@ -87,7 +87,7 @@ def _render(self, rect): alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) rl.end_scissor_mode() self._publish_alert_sound(None) - return -1 + return driver_data = self._draw_face_detection(rect) if driver_data is not None: @@ -105,7 +105,7 @@ def _render(self, rect): self._render_dm_alerts(rect) rl.end_scissor_mode() - return -1 + return def _publish_alert_sound(self, dm_state): """Publish selfdriveState with only alertSound field set""" @@ -235,9 +235,9 @@ def _draw_eyes(self, rect: rl.Rectangle, driver_data): gui_app.init_window("Driver Camera View (mici)") driver_camera_view = DriverCameraDialog() + gui_app.push_widget(driver_camera_view) try: for _ in gui_app.render(): ui_state.update() - driver_camera_view.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) finally: driver_camera_view.close() diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index 12fa608b360..c7f21bb2f89 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -37,14 +37,12 @@ "openpilot.system.ui.widgets.confirm_dialog.ConfirmDialog", "openpilot.system.ui.widgets.label.Label", "openpilot.system.ui.widgets.button.Button", - "openpilot.selfdrive.ui.mici.widgets.dialog.BigDialog", "openpilot.system.ui.widgets.html_render.HtmlRenderer", "openpilot.system.ui.widgets.NavBar", "openpilot.system.ui.widgets.inputbox.InputBox", "openpilot.system.ui.widgets.scroller_tici.Scroller", "openpilot.system.ui.widgets.scroller.Scroller", "openpilot.system.ui.widgets.label.UnifiedLabel", - "openpilot.selfdrive.ui.mici.widgets.dialog.BigMultiOptionDialog", "openpilot.system.ui.widgets.mici_keyboard.MiciKeyboard", "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialogV2", "openpilot.system.ui.widgets.keyboard.Keyboard", diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 6b4cb92c16e..612f31b7537 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -4,7 +4,7 @@ from typing import Union from collections.abc import Callable from typing import cast -from openpilot.system.ui.widgets import Widget, NavWidget, DialogResult +from openpilot.system.ui.widgets import Widget, NavWidget from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard from openpilot.system.ui.lib.text_measure import measure_text_cached @@ -23,17 +23,8 @@ class BigDialogBase(NavWidget, abc.ABC): def __init__(self): super().__init__() - self._ret = DialogResult.NO_ACTION self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - self.set_back_callback(lambda: setattr(self, '_ret', DialogResult.CANCEL)) - - def _render(self, _) -> DialogResult: - """ - Allows `gui_app.set_modal_overlay(BigDialog(...))`. - The overlay runner keeps calling until result != NO_ACTION. - """ - - return self._ret + self.set_back_callback(gui_app.pop_widget) class BigDialog(BigDialogBase): @@ -44,7 +35,7 @@ def __init__(self, self._title = title self._description = description - def _render(self, _) -> DialogResult: + def _render(self, _): super()._render(_) # draw title @@ -74,8 +65,6 @@ def _render(self, _) -> DialogResult: gui_label(desc_rect, desc_wrapped, 30, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) - return self._ret - class BigConfirmationDialogV2(BigDialogBase): def __init__(self, title: str, icon: str, red: bool = False, @@ -91,22 +80,21 @@ def __init__(self, title: str, icon: str, red: bool = False, self._slider = RedBigSlider(title, icon_txt, confirm_callback=self._on_confirm) else: self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm) - self._slider.set_enabled(lambda: not self._swiping_away) + self._slider.set_enabled(lambda: self.enabled and not self._swiping_away) # self.enabled for nav stack def _on_confirm(self): + if self._exit_on_confirm: + gui_app.pop_widget() if self._confirm_callback: self._confirm_callback() - if self._exit_on_confirm: - self._ret = DialogResult.CONFIRM def _update_state(self): super()._update_state() if self._swiping_away and not self._slider.confirmed: self._slider.reset() - def _render(self, _) -> DialogResult: + def _render(self, _): self._slider.render(self._rect) - return self._ret class BigInputDialog(BigDialogBase): @@ -124,6 +112,7 @@ def __init__(self, font_weight=FontWeight.MEDIUM) self._keyboard = MiciKeyboard() self._keyboard.set_text(default_text) + self._keyboard.set_enabled(lambda: self.enabled) # for nav stack self._minimum_length = minimum_length self._backspace_held_time: float | None = None @@ -140,9 +129,10 @@ def __init__(self, self._top_right_button_rect = rl.Rectangle(0, 0, 0, 0) def confirm_callback_wrapper(): - self._ret = DialogResult.CONFIRM + text = self._keyboard.text() + gui_app.pop_widget() if confirm_callback: - confirm_callback(self._keyboard.text()) + confirm_callback(text) self._confirm_callback = confirm_callback_wrapper def _update_state(self): @@ -238,8 +228,6 @@ def _render(self, _): rl.draw_rectangle_lines_ex(self._top_right_button_rect, 1, rl.Color(0, 255, 0, 255)) rl.draw_rectangle_lines_ex(self._top_left_button_rect, 1, rl.Color(0, 255, 0, 255)) - return self._ret - def _handle_mouse_press(self, mouse_pos: MousePos): super()._handle_mouse_press(mouse_pos) # TODO: need to track where press was so enter and back can activate on release rather than press @@ -392,8 +380,6 @@ def _render(self, _): super()._render(_) self._scroller.render(self._rect) - return self._ret - class BigDialogButton(BigButton): def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", description: str = ""): @@ -404,4 +390,4 @@ def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) dlg = BigDialog(self.text, self._description) - gui_app.set_modal_overlay(dlg) + gui_app.push_widget(dlg) diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 88bab2d0011..088d2a0b6fd 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -19,7 +19,7 @@ class PairingDialog(NavWidget): def __init__(self): super().__init__() - self.set_back_callback(lambda: gui_app.set_modal_overlay(None)) + self.set_back_callback(gui_app.pop_widget) self._params = Params() self._qr_texture: rl.Texture | None = None self._last_qr_generation = float("-inf") @@ -72,7 +72,7 @@ def _update_state(self): if ui_state.prime_state.is_paired(): self._playing_dismiss_animation = True - def _render(self, rect: rl.Rectangle) -> int: + def _render(self, rect: rl.Rectangle): self._check_qr_refresh() self._render_qr_code() @@ -85,8 +85,6 @@ def _render(self, rect: rl.Rectangle) -> int: rl.draw_texture_ex(self._txt_pair, rl.Vector2(label_x, self._rect.y + self._rect.height - self._txt_pair.height - 16), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.35))) - return -1 - def _render_qr_code(self) -> None: if not self._qr_texture: error_font = gui_app.font(FontWeight.BOLD) @@ -107,10 +105,9 @@ def __del__(self): if __name__ == "__main__": gui_app.init_window("pairing device") pairing = PairingDialog() + gui_app.push_widget(pairing) try: for _ in gui_app.render(): - result = pairing.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - if result != -1: - break + pass finally: del pairing diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index f8fb589b617..17d89fbd509 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -219,7 +219,7 @@ def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: if __name__ == "__main__": - gui_app.init_window("OnRoad Camera View", new_modal=True) + gui_app.init_window("OnRoad Camera View") road_camera_view = AugmentedRoadView(ROAD_CAM) gui_app.push_widget(road_camera_view) print("***press space to switch camera view***") diff --git a/selfdrive/ui/onroad/driver_camera_dialog.py b/selfdrive/ui/onroad/driver_camera_dialog.py index a4518a25200..e66e04b8241 100644 --- a/selfdrive/ui/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/onroad/driver_camera_dialog.py @@ -100,7 +100,7 @@ def _calc_frame_matrix(self, rect: rl.Rectangle) -> np.ndarray: if __name__ == "__main__": - gui_app.init_window("Driver Camera View", new_modal=True) + gui_app.init_window("Driver Camera View") driver_camera_view = DriverCameraDialog() gui_app.push_widget(driver_camera_view) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 62a808209ac..7ed7ce93649 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -38,7 +38,7 @@ def run_replay(variant: LayoutVariant) -> None: from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage from openpilot.selfdrive.ui.tests.diff.replay_script import build_script - gui_app.init_window("ui diff test", fps=FPS, new_modal=variant == "tizi") + gui_app.init_window("ui diff test", fps=FPS) # Dynamically import main layout based on variant if variant == "mici": @@ -46,7 +46,6 @@ def run_replay(variant: LayoutVariant) -> None: else: from openpilot.selfdrive.ui.layouts.main import MainLayout main_layout = MainLayout() - main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) script = build_script(pm, main_layout, variant) @@ -59,7 +58,7 @@ def run_replay(variant: LayoutVariant) -> None: rl.get_time = lambda: frame / FPS # Main loop to replay events and render frames - for should_render in gui_app.render(): + for _ in gui_app.render(): # Handle all events for the current frame while script_index < len(script) and script[script_index][0] == frame: _, event = script[script_index] @@ -82,9 +81,6 @@ def run_replay(variant: LayoutVariant) -> None: ui_state.update() - if should_render: - main_layout.render() - frame += 1 if script_index >= len(script): diff --git a/selfdrive/ui/tests/profile_onroad.py b/selfdrive/ui/tests/profile_onroad.py index fde4f25ffed..18194d73630 100755 --- a/selfdrive/ui/tests/profile_onroad.py +++ b/selfdrive/ui/tests/profile_onroad.py @@ -83,7 +83,6 @@ def mock_update(timeout=None): gui_app.init_window("UI Profiling", fps=600) main_layout = MiciMainLayout() - main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) print("Running...") patch_submaster(message_chunks) @@ -95,15 +94,13 @@ def mock_update(timeout=None): yuv_buffer_size = W * H + (W // 2) * (H // 2) * 2 yuv_data = np.random.randint(0, 256, yuv_buffer_size, dtype=np.uint8).tobytes() with cProfile.Profile() as pr: - for should_render in gui_app.render(): + for _ in gui_app.render(): if ui_state.sm.frame >= len(message_chunks): break if ui_state.sm.frame % 3 == 0: eof = int((ui_state.sm.frame % 3) * 0.05 * 1e9) vipc.send(VisionStreamType.VISION_STREAM_ROAD, yuv_data, ui_state.sm.frame % 3, eof, eof) ui_state.update() - if should_render: - main_layout.render() pr.dump_stats(f'{args.output}_deterministic.stats') rl.close_window() diff --git a/selfdrive/ui/ui.py b/selfdrive/ui/ui.py index 0e271c72db0..e3cac2618e8 100755 --- a/selfdrive/ui/ui.py +++ b/selfdrive/ui/ui.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import os -import pyray as rl from openpilot.system.hardware import TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity @@ -16,20 +15,15 @@ def main(): cores = {5, } config_realtime_process(0, 51) + gui_app.init_window("UI") if BIG_UI: - gui_app.init_window("UI", new_modal=True) - main_layout = MainLayout() + MainLayout() else: - gui_app.init_window("UI") - main_layout = MiciMainLayout() - main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + MiciMainLayout() for should_render in gui_app.render(): ui_state.update() if should_render: - if not BIG_UI: - main_layout.render() - # reaffine after power save offlines our core if TICI and os.sched_getaffinity(0) != cores: try: diff --git a/selfdrive/ui/widgets/pairing_dialog.py b/selfdrive/ui/widgets/pairing_dialog.py index c07b2463f34..1ff550e4b6d 100644 --- a/selfdrive/ui/widgets/pairing_dialog.py +++ b/selfdrive/ui/widgets/pairing_dialog.py @@ -160,7 +160,7 @@ def __del__(self): if __name__ == "__main__": - gui_app.init_window("pairing device", new_modal=True) + gui_app.init_window("pairing device") pairing = PairingDialog() gui_app.push_widget(pairing) try: diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index f056fddbf0e..7c0bd2bf3cd 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -12,7 +12,6 @@ from contextlib import contextmanager from collections.abc import Callable from collections import deque -from dataclasses import dataclass from enum import StrEnum from pathlib import Path from typing import NamedTuple @@ -115,12 +114,6 @@ def font_fallback(font: rl.Font) -> rl.Font: return font -@dataclass -class ModalOverlay: - overlay: object = None - callback: Callable | None = None - - class MousePos(NamedTuple): x: float y: float @@ -226,13 +219,9 @@ def __init__(self, width: int | None = None, height: int | None = None): self._last_fps_log_time: float = time.monotonic() self._frame = 0 self._window_close_requested = False - self._modal_overlay = ModalOverlay() - self._modal_overlay_shown = False - self._modal_overlay_tick: Callable[[], None] | None = None - - # TODO: move over the entire ui and deprecate - self._new_modal = False self._nav_stack: list[object] = [] + self._nav_stack_tick: Callable[[], None] | None = None + self._nav_stack_widgets_to_render = 1 if self.big_ui() else 2 self._mouse = MouseState(self._scale) self._mouse_events: list[MouseEvent] = [] @@ -266,7 +255,7 @@ def target_fps(self): def request_close(self): self._window_close_requested = True - def init_window(self, title: str, fps: int = _DEFAULT_FPS, new_modal: bool = False): + def init_window(self, title: str, fps: int = _DEFAULT_FPS): with self._startup_profile_context(): def _close(sig, frame): self.close() @@ -274,8 +263,6 @@ def _close(sig, frame): signal.signal(signal.SIGINT, _close) atexit.register(self.close) - self._new_modal = new_modal - flags = rl.ConfigFlags.FLAG_MSAA_4X_HINT if ENABLE_VSYNC: flags |= rl.ConfigFlags.FLAG_VSYNC_HINT @@ -380,44 +367,48 @@ def _ffmpeg_writer_thread(self): break def push_widget(self, widget: object): - assert self._new_modal + if widget in self._nav_stack: + cloudlog.warning("Widget already in stack, cannot push again!") + return # disable previous widget to prevent input processing if len(self._nav_stack) > 0: prev_widget = self._nav_stack[-1] + # TODO: change these to touch_valid prev_widget.set_enabled(False) self._nav_stack.append(widget) widget.show_event() def pop_widget(self): - assert self._new_modal - if len(self._nav_stack) < 2: - cloudlog.warning("At least one widget should remain on the stack, ignoring pop") + cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") return # re-enable previous widget and pop current + # TODO: switch to touch_valid prev_widget = self._nav_stack[-2] prev_widget.set_enabled(True) widget = self._nav_stack.pop() widget.hide_event() - def set_modal_overlay(self, overlay, callback: Callable | None = None): - assert not self._new_modal, "set_modal_overlay is deprecated, use push_widget instead" - - if self._modal_overlay.overlay is not None: - if hasattr(self._modal_overlay.overlay, 'hide_event'): - self._modal_overlay.overlay.hide_event() + def pop_widgets_to(self, widget): + if widget not in self._nav_stack: + cloudlog.warning("Widget not in stack, cannot pop to it!") + return - if self._modal_overlay.callback is not None: - self._modal_overlay.callback(-1) + # pops all widgets after specified widget + while len(self._nav_stack) > 0 and self._nav_stack[-1] != widget: + self.pop_widget() - self._modal_overlay = ModalOverlay(overlay=overlay, callback=callback) + def get_active_widget(self): + if len(self._nav_stack) > 0: + return self._nav_stack[-1] + return None - def set_modal_overlay_tick(self, tick_function: Callable | None): - self._modal_overlay_tick = tick_function + def set_nav_stack_tick(self, tick_function: Callable | None): + self._nav_stack_tick = tick_function def set_should_render(self, should_render: bool): self._should_render = should_render @@ -561,23 +552,15 @@ def render(self): rl.begin_drawing() rl.clear_background(rl.BLACK) - if self._new_modal: - # Only render last widget - for widget in self._nav_stack[-1:]: - widget.render(rl.Rectangle(0, 0, self.width, self.height)) + # Allow a Widget to still run a function regardless of the stack depth + if self._nav_stack_tick is not None: + self._nav_stack_tick() - # Yield to allow caller to run non-rendering related code - yield True + # Only render top widgets + for widget in self._nav_stack[-self._nav_stack_widgets_to_render:]: + widget.render(rl.Rectangle(0, 0, self.width, self.height)) - else: - # Handle modal overlay rendering and input processing - if self._handle_modal_overlay(): - # Allow a Widget to still run a function while overlay is shown - if self._modal_overlay_tick is not None: - self._modal_overlay_tick() - yield False - else: - yield True + yield True if self._render_texture: rl.end_texture_mode() @@ -631,33 +614,6 @@ def width(self): def height(self): return self._height - def _handle_modal_overlay(self) -> bool: - if self._modal_overlay.overlay: - if hasattr(self._modal_overlay.overlay, 'render'): - result = self._modal_overlay.overlay.render(rl.Rectangle(0, 0, self.width, self.height)) - elif callable(self._modal_overlay.overlay): - result = self._modal_overlay.overlay() - else: - raise Exception - - # Send show event to Widget - if not self._modal_overlay_shown and hasattr(self._modal_overlay.overlay, 'show_event'): - self._modal_overlay.overlay.show_event() - self._modal_overlay_shown = True - - if result >= 0: - # Clear the overlay and execute the callback - original_modal = self._modal_overlay - self._modal_overlay = ModalOverlay() - if hasattr(original_modal.overlay, 'hide_event'): - original_modal.overlay.hide_event() - if original_modal.callback is not None: - original_modal.callback(result) - return True - else: - self._modal_overlay_shown = False - return False - def _load_fonts(self): for font_weight_file in FontWeight: with as_file(FONT_DIR) as fspath: diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 925afd7d10a..357e6729315 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -150,10 +150,10 @@ def main(): if mode == ResetMode.FORMAT: reset.start_reset() - for should_render in gui_app.render(): - if should_render: - if not reset.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)): - break + gui_app.push_widget(reset) + + for _ in gui_app.render(): + pass if __name__ == "__main__": diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index b5c0d052819..aa6f54c508b 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -19,7 +19,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 -from openpilot.system.ui.widgets import Widget, DialogResult +from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, SmallCircleIconButton, WidishRoundedButton, SmallRedPillButton, FullRoundedButton) @@ -96,10 +96,9 @@ class SetupState(IntEnum): NETWORK_SETUP = 1 NETWORK_SETUP_CUSTOM_SOFTWARE = 2 SOFTWARE_SELECTION = 3 - CUSTOM_SOFTWARE = 4 - DOWNLOADING = 5 - DOWNLOAD_FAILED = 6 - CUSTOM_SOFTWARE_WARNING = 7 + DOWNLOADING = 4 + DOWNLOAD_FAILED = 5 + CUSTOM_SOFTWARE_WARNING = 6 class StartPage(Widget): @@ -128,7 +127,9 @@ def __init__(self, use_openpilot_callback: Callable, super().__init__() self._openpilot_slider = LargerSlider("slide to use\nopenpilot", use_openpilot_callback) + self._openpilot_slider.set_enabled(lambda: self.enabled) self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False) + self._custom_software_slider.set_enabled(lambda: self.enabled) def reset(self): self._openpilot_slider.reset() @@ -390,9 +391,11 @@ def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: s self._reboot_button = SmallRedPillButton("reboot") self._reboot_button.set_click_callback(reboot_callback) + self._reboot_button.set_enabled(lambda: self.enabled) # for nav stack self._retry_button = WideRoundedButton("retry") self._retry_button.set_click_callback(retry_callback) + self._retry_button.set_enabled(lambda: self.enabled) # for nav stack def set_reason(self, reason: str): self._reason_label.set_text(reason) @@ -427,15 +430,10 @@ def _render(self, rect: rl.Rectangle): )) -class NetworkSetupState(IntEnum): - MAIN = 0 - WIFI_PANEL = 1 - - class NetworkSetupPage(Widget): def __init__(self, wifi_manager, continue_callback: Callable, back_callback: Callable): super().__init__() - self._wifi_ui = WifiUIMici(wifi_manager, back_callback=lambda: self.set_state(NetworkSetupState.MAIN)) + self._wifi_ui = WifiUIMici(wifi_manager) self._no_wifi_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 58, 50) self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 58, 50) @@ -445,78 +443,54 @@ def __init__(self, wifi_manager, continue_callback: Callable, back_callback: Cal back_txt = gui_app.texture("icons_mici/setup/back_new.png", 37, 32) self._back_button = SmallCircleIconButton(back_txt) self._back_button.set_click_callback(back_callback) + self._back_button.set_enabled(lambda: self.enabled) # for nav stack self._wifi_button = SmallerRoundedButton("wifi") - self._wifi_button.set_click_callback(lambda: self.set_state(NetworkSetupState.WIFI_PANEL)) + self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) + self._wifi_button.set_enabled(lambda: self.enabled) self._continue_button = WidishRoundedButton("continue") self._continue_button.set_enabled(False) self._continue_button.set_click_callback(continue_callback) - self._state = NetworkSetupState.MAIN - self._prev_has_internet = False - - def set_state(self, state: NetworkSetupState): - if self._state == NetworkSetupState.WIFI_PANEL and state != NetworkSetupState.WIFI_PANEL: - self._wifi_ui.hide_event() - self._state = state - if state == NetworkSetupState.WIFI_PANEL: - self._wifi_ui.show_event() - def set_has_internet(self, has_internet: bool): if has_internet: self._network_header.set_title("connected to internet") self._network_header.set_icon(self._wifi_full_txt) - self._continue_button.set_enabled(True) + self._continue_button.set_enabled(self.enabled) else: self._network_header.set_title(self._waiting_text) self._network_header.set_icon(self._no_wifi_txt) self._continue_button.set_enabled(False) - if has_internet and not self._prev_has_internet: - self.set_state(NetworkSetupState.MAIN) - self._prev_has_internet = has_internet + def _render(self, _): + self._network_header.render(rl.Rectangle( + self._rect.x + 16, + self._rect.y + 16, + self._rect.width - 32, + self._network_header.rect.height, + )) - def show_event(self): - super().show_event() - self._state = NetworkSetupState.MAIN + self._back_button.render(rl.Rectangle( + self._rect.x + 8, + self._rect.y + self._rect.height - self._back_button.rect.height, + self._back_button.rect.width, + self._back_button.rect.height, + )) - def hide_event(self): - super().hide_event() - if self._state == NetworkSetupState.WIFI_PANEL: - self._wifi_ui.hide_event() + self._wifi_button.render(rl.Rectangle( + self._rect.x + 8 + self._back_button.rect.width + 10, + self._rect.y + self._rect.height - self._wifi_button.rect.height, + self._wifi_button.rect.width, + self._wifi_button.rect.height, + )) - def _render(self, _): - if self._state == NetworkSetupState.MAIN: - self._network_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16, - self._rect.width - 32, - self._network_header.rect.height, - )) - - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) - - self._wifi_button.render(rl.Rectangle( - self._rect.x + 8 + self._back_button.rect.width + 10, - self._rect.y + self._rect.height - self._wifi_button.rect.height, - self._wifi_button.rect.width, - self._wifi_button.rect.height, - )) - - self._continue_button.render(rl.Rectangle( - self._rect.x + self._rect.width - self._continue_button.rect.width - 8, - self._rect.y + self._rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) - else: - self._wifi_ui.render(self._rect) + self._continue_button.render(rl.Rectangle( + self._rect.x + self._rect.width - self._continue_button.rect.width - 8, + self._rect.y + self._rect.height - self._continue_button.rect.height, + self._continue_button.rect.width, + self._continue_button.rect.height, + )) class Setup(Widget): @@ -533,28 +507,33 @@ def __init__(self): self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() self._prev_has_internet = False - gui_app.set_modal_overlay_tick(self._modal_overlay_tick) + gui_app.set_nav_stack_tick(self._nav_stack_tick) self._start_page = StartPage() self._start_page.set_click_callback(self._getting_started_button_callback) self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_button_callback, self._network_setup_back_button_callback) + # TODO: change these to touch_valid + self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack self._software_selection_page = SoftwareSelectionPage(self._software_selection_continue_button_callback, self._software_selection_custom_software_button_callback) + self._software_selection_page.set_enabled(lambda: self.enabled) # for nav stack self._download_failed_page = FailedPage(HARDWARE.reboot, self._download_failed_startover_button_callback) + self._download_failed_page.set_enabled(lambda: self.enabled) # for nav stack self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, self._custom_software_warning_back_button_callback) + self._custom_software_warning_page.set_enabled(lambda: self.enabled) # for nav stack self._downloading_page = DownloadingPage() - def _modal_overlay_tick(self): + def _nav_stack_tick(self): has_internet = self._network_monitor.network_connected.is_set() if has_internet and not self._prev_has_internet: - gui_app.set_modal_overlay(None) + gui_app.pop_widgets_to(self) self._prev_has_internet = has_internet def _update_state(self): @@ -582,8 +561,6 @@ def _render(self, rect: rl.Rectangle): self._software_selection_page.render(rect) elif self.state == SetupState.CUSTOM_SOFTWARE_WARNING: self._custom_software_warning_page.render(rect) - elif self.state == SetupState.CUSTOM_SOFTWARE: - self.render_custom_software() elif self.state == SetupState.DOWNLOADING: self.render_downloading(rect) elif self.state == SetupState.DOWNLOAD_FAILED: @@ -614,14 +591,19 @@ def _network_setup_continue_button_callback(self): if self.state == SetupState.NETWORK_SETUP: self.download(OPENPILOT_URL) elif self.state == SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE: - self._set_state(SetupState.CUSTOM_SOFTWARE) + def handle_keyboard_result(text): + url = text.strip() + if url: + self.download(url) + + keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) + gui_app.push_widget(keyboard) def close(self): self._network_monitor.stop() def render_network_setup(self, rect: rl.Rectangle): has_internet = self._network_monitor.network_connected.is_set() - self._prev_has_internet = has_internet self._network_setup_page.set_has_internet(has_internet) self._network_setup_page.render(rect) @@ -629,19 +611,6 @@ def render_downloading(self, rect: rl.Rectangle): self._downloading_page.set_progress(self.download_progress) self._downloading_page.render(rect) - def render_custom_software(self): - def handle_keyboard_result(text): - url = text.strip() - if url: - self.download(url) - - def handle_keyboard_exit(result): - if result == DialogResult.CANCEL: - self._set_state(SetupState.SOFTWARE_SELECTION) - - keyboard = BigInputDialog("custom software URL...", confirm_callback=handle_keyboard_result) - gui_app.set_modal_overlay(keyboard, callback=handle_keyboard_exit) - def use_openpilot(self): if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): os.remove(VALID_CACHE_PATH) @@ -738,9 +707,9 @@ def main(): try: gui_app.init_window("Setup") setup = Setup() - for should_render in gui_app.render(): - if should_render: - setup.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + gui_app.push_widget(setup) + for _ in gui_app.render(): + pass setup.close() except Exception as e: print(f"Setup error: {e}") diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 5c8748783a1..5de72ac8c49 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -38,6 +38,7 @@ def __init__(self, updater_path, manifest_path): self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_callback, self._network_setup_back_callback) + self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() @@ -182,9 +183,9 @@ def main(): try: gui_app.init_window("System Update") updater = Updater(updater_path, manifest_path) - for should_render in gui_app.render(): - if should_render: - updater.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) + gui_app.push_widget(updater) + for _ in gui_app.render(): + pass updater.close() except Exception as e: print(f"Updater error: {e}") diff --git a/system/ui/tici_reset.py b/system/ui/tici_reset.py index b22b2408508..23f6b344ec6 100755 --- a/system/ui/tici_reset.py +++ b/system/ui/tici_reset.py @@ -116,7 +116,7 @@ def main(): elif sys.argv[1] == "--format": mode = ResetMode.FORMAT - gui_app.init_window("System Reset", 20, new_modal=True) + gui_app.init_window("System Reset", 20) reset = Reset(mode) if mode == ResetMode.FORMAT: diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py index bb70b75b20d..39f95cc8a00 100755 --- a/system/ui/tici_setup.py +++ b/system/ui/tici_setup.py @@ -436,7 +436,7 @@ def download_failed(self, url: str, reason: str): def main(): try: - gui_app.init_window("Setup", 20, new_modal=True) + gui_app.init_window("Setup", 20) setup = Setup() gui_app.push_widget(setup) for _ in gui_app.render(): diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index c040e1a4041..9824638cd06 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -160,7 +160,7 @@ def main(): manifest_path = sys.argv[2] try: - gui_app.init_window("System Update", new_modal=True) + gui_app.init_window("System Update") gui_app.push_widget(Updater(updater_path, manifest_path)) for _ in gui_app.render(): pass diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index b3b1276a58f..25c908c72cc 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -108,6 +108,10 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: # Keep track of whether mouse down started within the widget's rectangle if self.enabled and self.__was_awake: self._process_mouse_events() + else: + # TODO: ideally we emit release events when going disabled + self.__is_pressed = [False] * MAX_TOUCH_SLOTS + self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS self.__was_awake = device.awake @@ -181,6 +185,7 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: def show_event(self): """Optionally handle show event. Parent must manually call this""" + # TODO: iterate through all child objects, check for subclassing from Widget/Layout (Scroller) def hide_event(self): """Optionally handle hide event. Parent must manually call this""" @@ -261,6 +266,7 @@ def set_back_callback(self, callback: Callable[[], None]) -> None: self._back_callback = callback def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: + # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down super()._handle_mouse_event(mouse_event) if not self.back_enabled: @@ -320,13 +326,14 @@ def _update_state(self): if not self._set_up: self._set_up = True if hasattr(self, '_scroller'): + # TODO: use touch_valid original_enabled = self._scroller._enabled - self._scroller.set_enabled(lambda: not self._swiping_away and (original_enabled() if callable(original_enabled) else - original_enabled)) + self._scroller.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else + original_enabled)) elif hasattr(self, '_scroll_panel'): original_enabled = self._scroll_panel.enabled - self._scroll_panel.set_enabled(lambda: not self._swiping_away and (original_enabled() if callable(original_enabled) else - original_enabled)) + self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else + original_enabled)) if self._trigger_animate_in: self._pos_filter.x = self._rect.height @@ -336,6 +343,10 @@ def _update_state(self): new_y = 0.0 + if not self.enabled: + self._back_button_start_pos = None + + # TODO: why is this not in handle_mouse_event? have to hack above if self._back_button_start_pos is not None: last_mouse_event = gui_app.last_mouse_event # push entire widget as user drags it away @@ -363,6 +374,14 @@ def _update_state(self): self.set_position(self._rect.x, new_y) + def _layout(self): + # Dim whatever is behind this widget, fading with position (runs after _update_state so position is correct) + overlay_alpha = int(200 * max(0.0, min(1.0, 1.0 - self._rect.y / self._rect.height))) if self._rect.height > 0 else 0 + rl.draw_rectangle(0, 0, int(self._rect.width), int(self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) + + bounce_height = 20 + rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height + bounce_height), rl.BLACK) + def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: ret = super().render(rect) @@ -379,10 +398,6 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: else: self._nav_bar_y_filter.update(NAV_BAR_MARGIN) - # draw black above widget when dismissing - if self._rect.y > 0: - rl.draw_rectangle(int(self._rect.x), 0, int(self._rect.width), int(self._rect.y), rl.BLACK) - self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) self._nav_bar.render() diff --git a/system/ui/widgets/keyboard.py b/system/ui/widgets/keyboard.py index 531725688a2..49c59a431f1 100644 --- a/system/ui/widgets/keyboard.py +++ b/system/ui/widgets/keyboard.py @@ -272,7 +272,7 @@ def callback(result: DialogResult): print("Canceled") gui_app.request_close() - gui_app.init_window("Keyboard", new_modal=True) + gui_app.init_window("Keyboard") keyboard = Keyboard(min_text_size=8, show_password_toggle=True, callback=callback) keyboard.set_title("Keyboard Input", "Type your text below") diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 6d2e08e0539..59a2451387d 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -61,7 +61,7 @@ def set_position(self, x: float, y: float, smooth: bool = True): self._x_filter.x = x self._y_filter.x = local_y # keep track of original position so dragging around feels consistent. also move touch area down a bit - self.original_position = rl.Vector2(x, y + KEY_TOUCH_AREA_OFFSET) + self.original_position = rl.Vector2(x, local_y + KEY_TOUCH_AREA_OFFSET) self._position_initialized = True if not smooth: @@ -227,6 +227,8 @@ def _set_keys(self, keys: list[list[Key]]): for current_row, row in zip(self._current_keys, keys, strict=False): # not all layouts have the same number of keys for current_key, key in zip_repeat(current_row, row): + # reset parent rect for new keys + key.set_parent_rect(self._rect) current_pos = current_key.get_position() key.set_position(current_pos[0], current_pos[1], smooth=False) @@ -264,7 +266,8 @@ def _get_closest_key(self) -> tuple[Key | None, float]: for key in row: mouse_pos = gui_app.last_mouse_event.pos # approximate distance for comparison is accurate enough - dist = abs(key.original_position.x - mouse_pos.x) + abs(key.original_position.y - mouse_pos.y) + # use local y coords so parent widget offset (e.g. during NavWidget animate-in) doesn't affect hit testing + dist = abs(key.original_position.x - mouse_pos.x) + abs(key.original_position.y - (mouse_pos.y - self._rect.y)) if dist < closest_key[1]: if self._closest_key[0] is None or key is self._closest_key[0] or dist < self._closest_key[1] - KEY_DRAG_HYSTERESIS: closest_key = (key, dist) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index fcd56607c0b..668565a033a 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -481,7 +481,7 @@ def _on_disconnected(self): def main(): - gui_app.init_window("Wi-Fi Manager", new_modal=True) + gui_app.init_window("Wi-Fi Manager") gui_app.push_widget(WifiManagerUI(WifiManager())) for _ in gui_app.render(): From 4e8a4f87f406f65d5f6e9a924a547a0f48f0f727 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 22:36:32 -0800 Subject: [PATCH 281/685] pj: handle no qt --- tools/plotjuggler/test_plotjuggler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py index a2c509f9432..26bad25c3ec 100644 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -1,9 +1,12 @@ import os import glob +import shutil import signal import subprocess import time +import pytest + from openpilot.common.basedir import BASEDIR from openpilot.common.timeout import Timeout from openpilot.tools.plotjuggler.juggle import DEMO_ROUTE, install @@ -12,6 +15,7 @@ class TestPlotJuggler: + @pytest.mark.skipif(not shutil.which('qmake'), reason="Qt not installed") def test_demo(self): install() From 468a50b6f6470c615e9f40a343f9b9f51034bba3 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Fri, 20 Feb 2026 23:38:51 -0700 Subject: [PATCH 282/685] fix: adb ssh on mac (#37298) * fix: adb ssh on mac * revert --- tools/scripts/adb_ssh.sh | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tools/scripts/adb_ssh.sh b/tools/scripts/adb_ssh.sh index ad65693722c..4527a0296d5 100755 --- a/tools/scripts/adb_ssh.sh +++ b/tools/scripts/adb_ssh.sh @@ -2,7 +2,9 @@ set -euo pipefail # Forward all openpilot service ports -mapfile -t SERVICE_PORTS < <(python3 - <<'PY' +while IFS=' ' read -r name port; do + adb forward "tcp:${port}" "tcp:${port}" > /dev/null +done < <(python3 - <<'PY' from cereal.services import SERVICE_LIST FNV_PRIME = 0x100000001b3 @@ -29,12 +31,6 @@ for name, port in sorted(ports): PY ) -for entry in "${SERVICE_PORTS[@]}"; do - name="${entry% *}" - port="${entry##* }" - adb forward "tcp:${port}" "tcp:${port}" > /dev/null -done - # Forward SSH port first for interactive shell access. adb forward tcp:2222 tcp:22 From a694d051b390204ff8c9154bd930d2d8f7efd721 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Feb 2026 22:39:03 -0800 Subject: [PATCH 283/685] trim unused ubuntu deps (#37297) * trim unused ubuntu deps * mac cleanup --- tools/install_ubuntu_dependencies.sh | 7 ------- tools/mac_setup.sh | 2 -- 2 files changed, 9 deletions(-) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index a20176419fb..aa98102d490 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -42,21 +42,15 @@ function install_ubuntu_common_requirements() { ffmpeg \ libavformat-dev \ libavcodec-dev \ - libavdevice-dev \ libavutil-dev \ - libavfilter-dev \ libbz2-dev \ libeigen3-dev \ - libffi-dev \ libgles2-mesa-dev \ - libglfw3-dev \ - libglib2.0-0 \ libjpeg-dev \ libncurses5-dev \ libusb-1.0-0-dev \ libzmq3-dev \ libzstd-dev \ - libsqlite3-dev \ portaudio19-dev \ gettext } @@ -66,7 +60,6 @@ function install_ubuntu_lts_latest_requirements() { install_ubuntu_common_requirements $SUDO apt-get install -y --no-install-recommends \ - g++-12 \ python3-dev \ python3-venv } diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index a5a6eed0402..3f13cbe74aa 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -32,9 +32,7 @@ brew "capnp" brew "coreutils" brew "eigen" brew "ffmpeg" -brew "glfw" brew "libusb" -brew "libtool" brew "llvm" brew "zeromq" cask "gcc-arm-embedded" From 06298b28f1c08aa3bdfb7cc91cc3c4f9a1c65d84 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 21 Feb 2026 10:17:51 -0800 Subject: [PATCH 284/685] ty: fix unused warnings --- system/ubloxd/binary_struct.py | 4 ++-- tools/clip/run.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/system/ubloxd/binary_struct.py b/system/ubloxd/binary_struct.py index 7b229620a2f..c144bd56962 100644 --- a/system/ubloxd/binary_struct.py +++ b/system/ubloxd/binary_struct.py @@ -174,7 +174,7 @@ def __init_subclass__(cls, **kwargs) -> None: if not is_dataclass(cls): dataclass(init=False)(cls) fields = list(getattr(cls, '__annotations__', {}).items()) - cls.__binary_fields__ = fields # type: ignore[attr-defined] + cls.__binary_fields__ = fields @classmethod def _read(inner_cls, reader: BinaryReader): @@ -184,7 +184,7 @@ def _read(inner_cls, reader: BinaryReader): setattr(obj, name, value) return obj - cls._read = _read # type: ignore[attr-defined] + cls._read = _read @classmethod def from_bytes(cls: type[T], data: bytes) -> T: diff --git a/tools/clip/run.py b/tools/clip/run.py index d338f097d66..5711cafa592 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -85,7 +85,7 @@ def _parse_and_chunk_segment(args: tuple) -> list[dict]: if not messages: return [] - dt_ns, chunks, current, next_time = 1e9 / fps, [], {}, messages[0].logMonoTime + 1e9 / fps # type: ignore[var-annotated] + dt_ns, chunks, current, next_time = 1e9 / fps, [], {}, messages[0].logMonoTime + 1e9 / fps for msg in messages: if msg.logMonoTime >= next_time: chunks.append(current) @@ -159,7 +159,7 @@ def iter_segment_frames(camera_paths, start_time, end_time, fps=20, use_qcam=Fal seg_frames = FrameReader(path, pix_fmt="nv12") assert seg_frames is not None - frame = seg_frames[local_idx] if use_qcam else seg_frames.get(local_idx) # type: ignore[index, union-attr] + frame = seg_frames[local_idx] if use_qcam else seg_frames.get(local_idx) yield global_idx, frame @@ -286,7 +286,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, if big: from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView else: - from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView # type: ignore[assignment] + from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight timer.lap("import") From 02e550e2cb7f83d8c822abb446d333e326d77303 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 21 Feb 2026 11:32:51 -0800 Subject: [PATCH 285/685] remove setup_vsound (#37305) --- .github/workflows/tests.yaml | 1 - selfdrive/test/setup_vsound.sh | 10 ---------- 2 files changed, 11 deletions(-) delete mode 100755 selfdrive/test/setup_vsound.sh diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e1765eb5626..71ff03cba3a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -233,7 +233,6 @@ jobs: timeout-minutes: ${{ (steps.setup-step.outputs.duration < 18) && 1 || 2 }} run: | source selfdrive/test/setup_xvfb.sh - source selfdrive/test/setup_vsound.sh pytest -s tools/sim/tests/test_metadrive_bridge.py create_ui_report: diff --git a/selfdrive/test/setup_vsound.sh b/selfdrive/test/setup_vsound.sh deleted file mode 100755 index aab14997448..00000000000 --- a/selfdrive/test/setup_vsound.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -{ - #start pulseaudio daemon - sudo pulseaudio -D - - # create a virtual null audio and set it to default device - sudo pactl load-module module-null-sink sink_name=virtual_audio - sudo pactl set-default-sink virtual_audio -} > /dev/null 2>&1 From f45f239774bcb4f3ba01ee4192877ba17ea4480a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 21 Feb 2026 11:34:32 -0800 Subject: [PATCH 286/685] CI: remove redundant build job (#37306) --- .github/workflows/tests.yaml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 71ff03cba3a..35f04e61c0a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -61,21 +61,6 @@ jobs: timeout-minutes: 3 run: release/check-submodules.sh - build: - runs-on: ${{ - (github.repository == 'commaai/openpilot') && - ((github.event_name != 'pull_request') || - (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') - || fromJSON('["ubuntu-24.04"]') }} - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - uses: ./.github/workflows/setup-with-retry - - uses: ./.github/workflows/compile-openpilot - timeout-minutes: 30 - build_mac: name: build macOS runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }} From f41d77b24fb897af23e25d00f709a2ae36352e4d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 21 Feb 2026 11:45:44 -0800 Subject: [PATCH 287/685] Actions cleanup (#37307) * rm those * more opt --- .github/workflows/tests.yaml | 8 +++----- pyproject.toml | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 35f04e61c0a..40dfaaa8010 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -19,8 +19,6 @@ concurrency: env: CI: 1 - PYTHONPATH: ${{ github.workspace }} - PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical jobs: build_release: @@ -131,8 +129,8 @@ jobs: run: | source selfdrive/test/setup_xvfb.sh # Pre-compile Python bytecode so each pytest worker doesn't need to - $PYTEST --collect-only -m 'not slow' -qq - MAX_EXAMPLES=1 $PYTEST -m 'not slow' + pytest --collect-only -m 'not slow' -qq + MAX_EXAMPLES=1 pytest -m 'not slow' process_replay: name: process replay @@ -195,7 +193,7 @@ jobs: timeout-minutes: 4 env: ONNXCPU: 1 - run: $PYTEST selfdrive/test/process_replay/test_regen.py + run: pytest selfdrive/test/process_replay/test_regen.py simulator_driving: name: simulator driving diff --git a/pyproject.toml b/pyproject.toml index 4becd175638..5aeb2ffab4a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,7 +112,7 @@ allow-direct-references = true [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=openpilot/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ --ignore=msgq/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" +addopts = "--ignore=openpilot/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ --ignore=msgq/ -Werror --strict-config --strict-markers --durations=20 --maxprocesses=8 -n auto --dist=loadgroup" cpp_files = "test_*" cpp_harness = "selfdrive/test/cpp_harness.py" python_files = "test_*.py" From ece999c54803655186244620112402698816af0f Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Sun, 22 Feb 2026 07:10:16 +0300 Subject: [PATCH 288/685] fix typos in contributing doc (#37309) --- docs/CONTRIBUTING.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 7583095eaf4..62468c7448f 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -13,13 +13,13 @@ Development is coordinated through [Discord](https://discord.comma.ai) and GitHu ## What contributions are we looking for? **openpilot's priorities are [safety](SAFETY.md), stability, quality, and features, in that order.** -openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. +openpilot is part of comma's mission to *solve self-driving cars while delivering shippable intermediaries*, and all development is towards that goal. ### What gets merged? The probability of a pull request being merged is a function of its value to the project and the effort it will take us to get it merged. If a PR offers *some* value but will take lots of time to get merged, it will be closed. -Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. +Simple, well-tested bug fixes are the easiest to merge, and new features are the hardest to get merged. All of these are examples of good PRs: * typo fix: https://github.com/commaai/openpilot/pull/30678 @@ -29,17 +29,17 @@ All of these are examples of good PRs: ### What doesn't get merged? -* **style changes**: code is art, and it's up to the author to make it beautiful +* **style changes**: code is art, and it's up to the author to make it beautiful * **500+ line PRs**: clean it up, break it up into smaller PRs, or both * **PRs without a clear goal**: every PR must have a singular and clear goal * **UI design**: we do not have a good review process for this yet * **New features**: We believe openpilot is mostly feature-complete, and the rest is a matter of refinement and fixing bugs. As a result of this, most feature PRs will be immediately closed, however the beauty of open source is that forks can and do offer features that upstream openpilot doesn't. -* **Negative expected value**: This a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged. +* **Negative expected value**: This is a class of PRs that makes an improvement, but the risk or validation costs more than the improvement. The risk can be mitigated by first getting a failing test merged. ### First contribution [Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty. -There's lot of bounties that don't require a comma 3X or a car. +There are a lot of bounties that don't require a comma 3X or a car. ## Pull Requests From 7cd9ab27e62444654bd0de19d4bb14a43003a5b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 21 Feb 2026 20:37:45 -0800 Subject: [PATCH 289/685] ui: split out NavWidget (#37312) * spliit * fix * fix imports --- selfdrive/ui/mici/layouts/onboarding.py | 3 +- .../ui/mici/layouts/settings/developer.py | 2 +- selfdrive/ui/mici/layouts/settings/device.py | 3 +- .../ui/mici/layouts/settings/firehose.py | 3 +- .../mici/layouts/settings/network/__init__.py | 2 +- .../mici/layouts/settings/network/wifi_ui.py | 3 +- .../ui/mici/layouts/settings/settings.py | 2 +- selfdrive/ui/mici/layouts/settings/toggles.py | 2 +- .../ui/mici/onroad/driver_camera_dialog.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 3 +- selfdrive/ui/mici/widgets/pairing_dialog.py | 2 +- system/ui/widgets/__init__.py | 221 ----------------- system/ui/widgets/nav_widget.py | 227 ++++++++++++++++++ 13 files changed, 243 insertions(+), 232 deletions(-) create mode 100644 system/ui/widgets/nav_widget.py diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 539074453cd..b7fafd894a4 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -7,7 +7,8 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import FontWeight, gui_app -from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.button import SmallButton, SmallCircleIconButton from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.slider import SmallSlider diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 383c7ef9b97..9b5db1c520f 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -5,7 +5,7 @@ from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl, BigCircleParamControl from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyAction diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index e1beae4fe30..4776a604258 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -16,7 +16,8 @@ from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets.label import MiciLabel from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index a2288752eaf..eb3331c8682 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -13,7 +13,8 @@ from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.lib.multilang import tr, trn, tr_noop -from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 9f49521c36d..e73383c0d4c 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -7,7 +7,7 @@ from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, normalize_ssid diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 19183625275..b974b502a1a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -7,7 +7,8 @@ from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigInputDialog, BigDialogOptionButton, BigConfirmationDialogV2 from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight -from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, WifiState, normalize_ssid diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 2372104a007..0dbe1b22044 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -9,7 +9,7 @@ from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.nav_widget import NavWidget class SettingsBigButton(BigButton): diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index d6fb75a4d8d..0e9be393ecc 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -4,7 +4,7 @@ from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 26a5d132c69..4fddc88f6d2 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -7,7 +7,7 @@ from openpilot.selfdrive.selfdrived.events import EVENTS, ET from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr -from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import gui_label EventName = log.OnroadEvent.EventName diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 612f31b7537..f57938c8ee8 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -4,7 +4,8 @@ from typing import Union from collections.abc import Callable from typing import cast -from openpilot.system.ui.widgets import Widget, NavWidget +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard from openpilot.system.ui.lib.text_measure import measure_text_cached diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 088d2a0b6fd..64b2c9a063d 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -7,7 +7,7 @@ from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.widgets import NavWidget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets.label import MiciLabel diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 25c908c72cc..37254c8b35c 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -4,7 +4,6 @@ import pyray as rl from enum import IntEnum from collections.abc import Callable -from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter from openpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS, MouseEvent try: @@ -189,223 +188,3 @@ def show_event(self): def hide_event(self): """Optionally handle hide event. Parent must manually call this""" - - -SWIPE_AWAY_THRESHOLD = 80 # px to dismiss after releasing -START_DISMISSING_THRESHOLD = 40 # px to start dismissing while dragging -BLOCK_SWIPE_AWAY_THRESHOLD = 60 # px horizontal movement to block swipe away - -NAV_BAR_MARGIN = 6 -NAV_BAR_WIDTH = 205 -NAV_BAR_HEIGHT = 8 - -DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing -DISMISS_TIME_SECONDS = 2.0 - - -class NavBar(Widget): - def __init__(self): - super().__init__() - self.set_rect(rl.Rectangle(0, 0, NAV_BAR_WIDTH, NAV_BAR_HEIGHT)) - self._alpha = 1.0 - self._alpha_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) - self._fade_time = 0.0 - - def set_alpha(self, alpha: float) -> None: - self._alpha = alpha - self._fade_time = rl.get_time() - - def show_event(self): - super().show_event() - self._alpha = 1.0 - self._alpha_filter.x = 1.0 - self._fade_time = rl.get_time() - - def _render(self, _): - if rl.get_time() - self._fade_time > DISMISS_TIME_SECONDS: - self._alpha = 0.0 - alpha = self._alpha_filter.update(self._alpha) - - # white bar with black border - rl.draw_rectangle_rounded(self._rect, 1.0, 6, rl.Color(255, 255, 255, int(255 * 0.9 * alpha))) - rl.draw_rectangle_rounded_lines_ex(self._rect, 1.0, 6, 2, rl.Color(0, 0, 0, int(255 * 0.3 * alpha))) - - -class NavWidget(Widget, abc.ABC): - """ - A full screen widget that supports back navigation by swiping down from the top. - """ - BACK_TOUCH_AREA_PERCENTAGE = 0.65 - - def __init__(self): - super().__init__() - self._back_callback: Callable[[], None] | None = None - self._back_button_start_pos: MousePos | None = None - self._swiping_away = False # currently swiping away - self._can_swipe_away = True # swipe away is blocked after certain horizontal movement - - self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._playing_dismiss_animation = False - self._trigger_animate_in = False - self._nav_bar_show_time = 0.0 - self._back_enabled: bool | Callable[[], bool] = True - self._nav_bar = NavBar() - - self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) - - self._set_up = False - - @property - def back_enabled(self) -> bool: - return self._back_enabled() if callable(self._back_enabled) else self._back_enabled - - def set_back_enabled(self, enabled: bool | Callable[[], bool]) -> None: - self._back_enabled = enabled - - def set_back_callback(self, callback: Callable[[], None]) -> None: - self._back_callback = callback - - def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: - # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down - super()._handle_mouse_event(mouse_event) - - if not self.back_enabled: - self._back_button_start_pos = None - self._swiping_away = False - self._can_swipe_away = True - return - - if mouse_event.left_pressed: - # user is able to swipe away if starting near top of screen, or anywhere if scroller is at top - self._pos_filter.update_alpha(0.04) - in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE - - scroller_at_top = False - vertical_scroller = False - # TODO: -20? snapping in WiFi dialog can make offset not be positive at the top - if hasattr(self, '_scroller'): - scroller_at_top = self._scroller.scroll_panel.get_offset() >= -20 and not self._scroller._horizontal - vertical_scroller = not self._scroller._horizontal - elif hasattr(self, '_scroll_panel'): - scroller_at_top = self._scroll_panel.get_offset() >= -20 and not self._scroll_panel._horizontal - vertical_scroller = not self._scroll_panel._horizontal - - # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes - if (not vertical_scroller and in_dismiss_area) or scroller_at_top: - self._can_swipe_away = True - self._back_button_start_pos = mouse_event.pos - - elif mouse_event.left_down: - if self._back_button_start_pos is not None: - # block swiping away if too much horizontal or upward movement - horizontal_movement = abs(mouse_event.pos.x - self._back_button_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD - upward_movement = mouse_event.pos.y - self._back_button_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD - if not self._swiping_away and (horizontal_movement or upward_movement): - self._can_swipe_away = False - self._back_button_start_pos = None - - # block horizontal swiping if now swiping away - if self._can_swipe_away: - if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: - self._swiping_away = True - - elif mouse_event.left_released: - self._pos_filter.update_alpha(0.1) - # if far enough, trigger back navigation callback - if self._back_button_start_pos is not None: - if mouse_event.pos.y - self._back_button_start_pos.y > SWIPE_AWAY_THRESHOLD: - self._playing_dismiss_animation = True - - self._back_button_start_pos = None - self._swiping_away = False - - def _update_state(self): - super()._update_state() - - # Disable self's scroller while swiping away - if not self._set_up: - self._set_up = True - if hasattr(self, '_scroller'): - # TODO: use touch_valid - original_enabled = self._scroller._enabled - self._scroller.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else - original_enabled)) - elif hasattr(self, '_scroll_panel'): - original_enabled = self._scroll_panel.enabled - self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else - original_enabled)) - - if self._trigger_animate_in: - self._pos_filter.x = self._rect.height - self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT - self._nav_bar_show_time = rl.get_time() - self._trigger_animate_in = False - - new_y = 0.0 - - if not self.enabled: - self._back_button_start_pos = None - - # TODO: why is this not in handle_mouse_event? have to hack above - if self._back_button_start_pos is not None: - last_mouse_event = gui_app.last_mouse_event - # push entire widget as user drags it away - new_y = max(last_mouse_event.pos.y - self._back_button_start_pos.y, 0) - if new_y < SWIPE_AWAY_THRESHOLD: - new_y /= 2 # resistance until mouse release would dismiss widget - - if self._swiping_away: - self._nav_bar.set_alpha(1.0) - - if self._playing_dismiss_animation: - new_y = self._rect.height + DISMISS_PUSH_OFFSET - - new_y = round(self._pos_filter.update(new_y)) - if abs(new_y) < 1 and self._pos_filter.velocity.x == 0.0: - new_y = self._pos_filter.x = 0.0 - - if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: - if self._back_callback is not None: - self._back_callback() - - self._playing_dismiss_animation = False - self._back_button_start_pos = None - self._swiping_away = False - - self.set_position(self._rect.x, new_y) - - def _layout(self): - # Dim whatever is behind this widget, fading with position (runs after _update_state so position is correct) - overlay_alpha = int(200 * max(0.0, min(1.0, 1.0 - self._rect.y / self._rect.height))) if self._rect.height > 0 else 0 - rl.draw_rectangle(0, 0, int(self._rect.width), int(self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) - - bounce_height = 20 - rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height + bounce_height), rl.BLACK) - - def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: - ret = super().render(rect) - - if self.back_enabled: - bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 - nav_bar_delayed = rl.get_time() - self._nav_bar_show_time < 0.4 - # User dragging or dismissing, nav bar follows NavWidget - if self._back_button_start_pos is not None or self._playing_dismiss_animation: - self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._pos_filter.x - # Waiting to show - elif nav_bar_delayed: - self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT - # Animate back to top - else: - self._nav_bar_y_filter.update(NAV_BAR_MARGIN) - - self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) - self._nav_bar.render() - - return ret - - def show_event(self): - super().show_event() - # FIXME: we don't know the height of the rect at first show_event since it's before the first render :( - # so we need this hacky bool for now - self._trigger_animate_in = True - self._nav_bar.show_event() diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py new file mode 100644 index 00000000000..c1b1df1292d --- /dev/null +++ b/system/ui/widgets/nav_widget.py @@ -0,0 +1,227 @@ +from __future__ import annotations + +import abc +import pyray as rl +from collections.abc import Callable +from openpilot.system.ui.widgets import Widget +from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter +from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent + +SWIPE_AWAY_THRESHOLD = 80 # px to dismiss after releasing +START_DISMISSING_THRESHOLD = 40 # px to start dismissing while dragging +BLOCK_SWIPE_AWAY_THRESHOLD = 60 # px horizontal movement to block swipe away + +NAV_BAR_MARGIN = 6 +NAV_BAR_WIDTH = 205 +NAV_BAR_HEIGHT = 8 + +DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing +DISMISS_TIME_SECONDS = 2.0 + + +class NavBar(Widget): + def __init__(self): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, NAV_BAR_WIDTH, NAV_BAR_HEIGHT)) + self._alpha = 1.0 + self._alpha_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._fade_time = 0.0 + + def set_alpha(self, alpha: float) -> None: + self._alpha = alpha + self._fade_time = rl.get_time() + + def show_event(self): + super().show_event() + self._alpha = 1.0 + self._alpha_filter.x = 1.0 + self._fade_time = rl.get_time() + + def _render(self, _): + if rl.get_time() - self._fade_time > DISMISS_TIME_SECONDS: + self._alpha = 0.0 + alpha = self._alpha_filter.update(self._alpha) + + # white bar with black border + rl.draw_rectangle_rounded(self._rect, 1.0, 6, rl.Color(255, 255, 255, int(255 * 0.9 * alpha))) + rl.draw_rectangle_rounded_lines_ex(self._rect, 1.0, 6, 2, rl.Color(0, 0, 0, int(255 * 0.3 * alpha))) + + +class NavWidget(Widget, abc.ABC): + """ + A full screen widget that supports back navigation by swiping down from the top. + """ + BACK_TOUCH_AREA_PERCENTAGE = 0.65 + + def __init__(self): + super().__init__() + self._back_callback: Callable[[], None] | None = None + self._back_button_start_pos: MousePos | None = None + self._swiping_away = False # currently swiping away + self._can_swipe_away = True # swipe away is blocked after certain horizontal movement + + self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) + self._playing_dismiss_animation = False + self._trigger_animate_in = False + self._nav_bar_show_time = 0.0 + self._back_enabled: bool | Callable[[], bool] = True + self._nav_bar = NavBar() + + self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + + self._set_up = False + + @property + def back_enabled(self) -> bool: + return self._back_enabled() if callable(self._back_enabled) else self._back_enabled + + def set_back_enabled(self, enabled: bool | Callable[[], bool]) -> None: + self._back_enabled = enabled + + def set_back_callback(self, callback: Callable[[], None]) -> None: + self._back_callback = callback + + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: + # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down + super()._handle_mouse_event(mouse_event) + + if not self.back_enabled: + self._back_button_start_pos = None + self._swiping_away = False + self._can_swipe_away = True + return + + if mouse_event.left_pressed: + # user is able to swipe away if starting near top of screen, or anywhere if scroller is at top + self._pos_filter.update_alpha(0.04) + in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE + + scroller_at_top = False + vertical_scroller = False + # TODO: -20? snapping in WiFi dialog can make offset not be positive at the top + if hasattr(self, '_scroller'): + scroller_at_top = self._scroller.scroll_panel.get_offset() >= -20 and not self._scroller._horizontal + vertical_scroller = not self._scroller._horizontal + elif hasattr(self, '_scroll_panel'): + scroller_at_top = self._scroll_panel.get_offset() >= -20 and not self._scroll_panel._horizontal + vertical_scroller = not self._scroll_panel._horizontal + + # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes + if (not vertical_scroller and in_dismiss_area) or scroller_at_top: + self._can_swipe_away = True + self._back_button_start_pos = mouse_event.pos + + elif mouse_event.left_down: + if self._back_button_start_pos is not None: + # block swiping away if too much horizontal or upward movement + horizontal_movement = abs(mouse_event.pos.x - self._back_button_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD + upward_movement = mouse_event.pos.y - self._back_button_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD + if not self._swiping_away and (horizontal_movement or upward_movement): + self._can_swipe_away = False + self._back_button_start_pos = None + + # block horizontal swiping if now swiping away + if self._can_swipe_away: + if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: + self._swiping_away = True + + elif mouse_event.left_released: + self._pos_filter.update_alpha(0.1) + # if far enough, trigger back navigation callback + if self._back_button_start_pos is not None: + if mouse_event.pos.y - self._back_button_start_pos.y > SWIPE_AWAY_THRESHOLD: + self._playing_dismiss_animation = True + + self._back_button_start_pos = None + self._swiping_away = False + + def _update_state(self): + super()._update_state() + + # Disable self's scroller while swiping away + if not self._set_up: + self._set_up = True + if hasattr(self, '_scroller'): + # TODO: use touch_valid + original_enabled = self._scroller._enabled + self._scroller.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else + original_enabled)) + elif hasattr(self, '_scroll_panel'): + original_enabled = self._scroll_panel.enabled + self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else + original_enabled)) + + if self._trigger_animate_in: + self._pos_filter.x = self._rect.height + self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + self._nav_bar_show_time = rl.get_time() + self._trigger_animate_in = False + + new_y = 0.0 + + if not self.enabled: + self._back_button_start_pos = None + + # TODO: why is this not in handle_mouse_event? have to hack above + if self._back_button_start_pos is not None: + last_mouse_event = gui_app.last_mouse_event + # push entire widget as user drags it away + new_y = max(last_mouse_event.pos.y - self._back_button_start_pos.y, 0) + if new_y < SWIPE_AWAY_THRESHOLD: + new_y /= 2 # resistance until mouse release would dismiss widget + + if self._swiping_away: + self._nav_bar.set_alpha(1.0) + + if self._playing_dismiss_animation: + new_y = self._rect.height + DISMISS_PUSH_OFFSET + + new_y = round(self._pos_filter.update(new_y)) + if abs(new_y) < 1 and self._pos_filter.velocity.x == 0.0: + new_y = self._pos_filter.x = 0.0 + + if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: + if self._back_callback is not None: + self._back_callback() + + self._playing_dismiss_animation = False + self._back_button_start_pos = None + self._swiping_away = False + + self.set_position(self._rect.x, new_y) + + def _layout(self): + # Dim whatever is behind this widget, fading with position (runs after _update_state so position is correct) + overlay_alpha = int(200 * max(0.0, min(1.0, 1.0 - self._rect.y / self._rect.height))) if self._rect.height > 0 else 0 + rl.draw_rectangle(0, 0, int(self._rect.width), int(self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) + + bounce_height = 20 + rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height + bounce_height), rl.BLACK) + + def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: + ret = super().render(rect) + + if self.back_enabled: + bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 + nav_bar_delayed = rl.get_time() - self._nav_bar_show_time < 0.4 + # User dragging or dismissing, nav bar follows NavWidget + if self._back_button_start_pos is not None or self._playing_dismiss_animation: + self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._pos_filter.x + # Waiting to show + elif nav_bar_delayed: + self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + # Animate back to top + else: + self._nav_bar_y_filter.update(NAV_BAR_MARGIN) + + self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) + self._nav_bar.render() + + return ret + + def show_event(self): + super().show_event() + # FIXME: we don't know the height of the rect at first show_event since it's before the first render :( + # so we need this hacky bool for now + self._trigger_animate_in = True + self._nav_bar.show_event() From c4393277fb7ea776fd28dc6dd7474b069f8a94b3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 21 Feb 2026 20:56:51 -0800 Subject: [PATCH 290/685] ui: draw debug rects (#37313) * debug * new --- system/ui/lib/application.py | 4 ++++ system/ui/widgets/__init__.py | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 7c0bd2bf3cd..5d284bf454d 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -248,6 +248,10 @@ def set_show_touches(self, show: bool): def set_show_fps(self, show: bool): self._show_fps = show + @property + def show_touches(self) -> bool: + return self._show_touches + @property def target_fps(self): return self._target_fps diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 37254c8b35c..cc8d72959fc 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -104,6 +104,9 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: self._layout() ret = self._render(self._rect) + if gui_app.show_touches: + self._draw_debug_rect() + # Keep track of whether mouse down started within the widget's rectangle if self.enabled and self.__was_awake: self._process_mouse_events() @@ -116,6 +119,10 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: return ret + def _draw_debug_rect(self) -> None: + rl.draw_rectangle_lines(int(self._rect.x), int(self._rect.y), + max(int(self._rect.width), 1), max(int(self._rect.height), 1), rl.RED) + def _process_mouse_events(self) -> None: hit_rect = self._hit_rect touch_valid = self._touch_valid() From 1304f959785e66393fc203cc66556bc0baeccc2f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 21 Feb 2026 22:26:07 -0800 Subject: [PATCH 291/685] mici ui: remove line separators (#37314) remove --- system/ui/widgets/scroller.py | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 9bb7883215d..f5d5738e9c1 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -19,21 +19,6 @@ DO_JELLO = False -class LineSeparator(Widget): - def __init__(self, height: int = 1): - super().__init__() - self._rect = rl.Rectangle(0, 0, 0, height) - - def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: - super().set_parent_rect(parent_rect) - self._rect.width = parent_rect.width - - def _render(self, _): - rl.draw_line(int(self._rect.x) + LINE_PADDING, int(self._rect.y), - int(self._rect.x + self._rect.width) - LINE_PADDING, int(self._rect.y), - LINE_COLOR) - - class ScrollIndicator(Widget): HORIZONTAL_MARGIN = 4 @@ -78,14 +63,13 @@ def _render(self, _): class Scroller(Widget): def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = True, spacing: int = ITEM_SPACING, - line_separator: bool = False, pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING, + pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING, scroll_indicator: bool = True, edge_shadows: bool = True): super().__init__() self._items: list[Widget] = [] self._horizontal = horizontal self._snap_items = snap_items self._spacing = spacing - self._line_separator = LineSeparator() if line_separator else None self._pad_start = pad_start self._pad_end = pad_end @@ -214,12 +198,6 @@ def _get_scroll(self, visible_items: list[Widget], content_size: float) -> float def _layout(self): self._visible_items = [item for item in self._items if item.is_visible] - # Add line separator between items - if self._line_separator is not None: - l = len(self._visible_items) - for i in range(1, len(self._visible_items)): - self._visible_items.insert(l - i, self._line_separator) - self._content_size = sum(item.rect.width if self._horizontal else item.rect.height for item in self._visible_items) self._content_size += self._spacing * (len(self._visible_items) - 1) self._content_size += self._pad_start + self._pad_end From cdcc2f676603467dd23b18d71272115a3873353a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 21 Feb 2026 22:31:05 -0800 Subject: [PATCH 292/685] mici scroller: remove double pad args (#37315) * remove double pad args * fix --- selfdrive/ui/mici/layouts/main.py | 2 +- selfdrive/ui/mici/layouts/offroad_alerts.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 2 +- system/ui/widgets/scroller.py | 10 ++++------ 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index be2c4747f32..928ea942da3 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -40,7 +40,7 @@ def __init__(self): self._alerts_layout, self._home_layout, self._onroad_layout, - ], spacing=0, pad_start=0, pad_end=0, scroll_indicator=False, edge_shadows=False) + ], spacing=0, pad=0, scroll_indicator=False, edge_shadows=False) self._scroller.set_reset_scroll_at_show(False) self._scroller.set_enabled(lambda: self.enabled) # for nav stack diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 60f64b31b06..778d7185526 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -197,7 +197,7 @@ def __init__(self): self._last_refresh = 0.0 # Create vertical scroller - self._scroller = Scroller([], horizontal=False, spacing=12, pad_start=0, pad_end=0, snap_items=False) + self._scroller = Scroller([], horizontal=False, spacing=12, pad=0, snap_items=False) # Create empty state label self._empty_label = UnifiedLabel(tr("no alerts"), 65, FontWeight.DISPLAY, rl.WHITE, diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index f57938c8ee8..8a1c5df65f3 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -297,7 +297,7 @@ def __init__(self, options: list[str], default: str | None): # Widget doesn't differentiate between click and drag self._can_click = True - self._scroller = Scroller([], horizontal=False, pad_start=100, pad_end=100, spacing=0, snap_items=True) + self._scroller = Scroller([], horizontal=False, pad=100, spacing=0, snap_items=True) for option in options: self._scroller.add_widget(BigDialogOptionButton(option)) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index f5d5738e9c1..94e00872d22 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -63,15 +63,13 @@ def _render(self, _): class Scroller(Widget): def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = True, spacing: int = ITEM_SPACING, - pad_start: int = ITEM_SPACING, pad_end: int = ITEM_SPACING, - scroll_indicator: bool = True, edge_shadows: bool = True): + pad: int = ITEM_SPACING, scroll_indicator: bool = True, edge_shadows: bool = True): super().__init__() self._items: list[Widget] = [] self._horizontal = horizontal self._snap_items = snap_items self._spacing = spacing - self._pad_start = pad_start - self._pad_end = pad_end + self._pad = pad self._reset_scroll_at_show = True @@ -200,7 +198,7 @@ def _layout(self): self._content_size = sum(item.rect.width if self._horizontal else item.rect.height for item in self._visible_items) self._content_size += self._spacing * (len(self._visible_items) - 1) - self._content_size += self._pad_start + self._pad_end + self._content_size += self._pad * 2 self._scroll_offset = self._get_scroll(self._visible_items, self._content_size) @@ -208,7 +206,7 @@ def _layout(self): cur_pos = 0 for idx, item in enumerate(self._visible_items): - spacing = self._spacing if (idx > 0) else self._pad_start + spacing = self._spacing if (idx > 0) else self._pad # Nicely lay out items horizontally/vertically if self._horizontal: x = self._rect.x + cur_pos + spacing From f99dc2eab27afdb494e1c410d24dac40dc68fcb2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 21 Feb 2026 22:35:41 -0800 Subject: [PATCH 293/685] mici scroller: default no snapping (#37316) * change default * fix --- selfdrive/ui/mici/layouts/main.py | 2 +- selfdrive/ui/mici/layouts/offroad_alerts.py | 2 +- selfdrive/ui/mici/layouts/settings/developer.py | 2 +- selfdrive/ui/mici/layouts/settings/device.py | 2 +- selfdrive/ui/mici/layouts/settings/network/__init__.py | 2 +- selfdrive/ui/mici/layouts/settings/settings.py | 2 +- selfdrive/ui/mici/layouts/settings/toggles.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 2 +- system/ui/widgets/scroller.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 928ea942da3..f6dbad89a14 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -40,7 +40,7 @@ def __init__(self): self._alerts_layout, self._home_layout, self._onroad_layout, - ], spacing=0, pad=0, scroll_indicator=False, edge_shadows=False) + ], snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False) self._scroller.set_reset_scroll_at_show(False) self._scroller.set_enabled(lambda: self.enabled) # for nav stack diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 778d7185526..565d27593d1 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -197,7 +197,7 @@ def __init__(self): self._last_refresh = 0.0 # Create vertical scroller - self._scroller = Scroller([], horizontal=False, spacing=12, pad=0, snap_items=False) + self._scroller = Scroller([], horizontal=False, spacing=12, pad=0) # Create empty state label self._empty_label = UnifiedLabel(tr("no alerts"), 65, FontWeight.DISPLAY, rl.WHITE, diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 9b5db1c520f..f107df7207a 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -65,7 +65,7 @@ def ssh_keys_callback(): self._long_maneuver_toggle, self._alpha_long_toggle, self._debug_mode_toggle, - ], snap_items=False) + ]) # Toggle lists self._refresh_toggles = ( diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 4776a604258..d919df1c16f 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -324,7 +324,7 @@ def uninstall_openpilot_callback(): regulatory_btn, reboot_btn, self._power_off_btn, - ], snap_items=False) + ]) # Set up back navigation # TODO: can this somehow be generic in widgets/__init__.py or application.py? diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index e73383c0d4c..ad4e43a1ac8 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -93,7 +93,7 @@ def network_metered_callback(value: str): self._apn_btn, self._cellular_metered_btn, # */ - ], snap_items=False) + ]) # Set initial config roaming_enabled = ui_state.params.get_bool("GsmRoaming") diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 0dbe1b22044..15fd6819966 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -50,7 +50,7 @@ def __init__(self): #BigDialogButton("manual", "", "icons_mici/settings/manual_icon.png", "Check out the mici user\nmanual at comma.ai/setup"), firehose_btn, developer_btn, - ], snap_items=False) + ]) # Set up back navigation self.set_back_callback(gui_app.pop_widget) diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index 0e9be393ecc..b0f7189230a 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -34,7 +34,7 @@ def __init__(self): record_front, record_mic, enable_openpilot, - ], snap_items=False) + ]) # Toggle lists self._refresh_toggles = ( diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 8a1c5df65f3..c695949e498 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -297,7 +297,7 @@ def __init__(self, options: list[str], default: str | None): # Widget doesn't differentiate between click and drag self._can_click = True - self._scroller = Scroller([], horizontal=False, pad=100, spacing=0, snap_items=True) + self._scroller = Scroller([], horizontal=False, snap_items=True, pad=100, spacing=0) for option in options: self._scroller.add_widget(BigDialogOptionButton(option)) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 94e00872d22..e50a48bbc53 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -62,7 +62,7 @@ def _render(self, _): class Scroller(Widget): - def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = True, spacing: int = ITEM_SPACING, + def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = False, spacing: int = ITEM_SPACING, pad: int = ITEM_SPACING, scroll_indicator: bool = True, edge_shadows: bool = True): super().__init__() self._items: list[Widget] = [] From a3f40dbac38214f2bfd3b5fda2f94cdde7f99a31 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 21 Feb 2026 22:50:59 -0800 Subject: [PATCH 294/685] ui: add Layout class (#37311) * split nav widget out * clean up * clean up * fix * work * small enough to not be function * nah we want intflag * clean up * always runs * more clean up * prep for scroller * opacity for settings * clean up layout * set enabled * rm --- selfdrive/ui/mici/layouts/home.py | 148 ++++++++++++++---------------- system/ui/widgets/icon_widget.py | 16 ++++ system/ui/widgets/layouts.py | 60 ++++++++++++ 3 files changed, 143 insertions(+), 81 deletions(-) create mode 100644 system/ui/widgets/icon_widget.py create mode 100644 system/ui/widgets/layouts.py diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index d4bbb749149..5e7e9bfea7b 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -3,8 +3,10 @@ from cereal import log import pyray as rl from collections.abc import Callable -from openpilot.system.ui.widgets.label import gui_label, MiciLabel, UnifiedLabel from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.layouts import HBoxLayout +from openpilot.system.ui.widgets.icon_widget import IconWidget +from openpilot.system.ui.widgets.label import gui_label, MiciLabel, UnifiedLabel from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR, MousePos from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.text import wrap_text @@ -77,24 +79,11 @@ def _render(self, _): font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) -class MiciHomeLayout(Widget): +class NetworkIcon(Widget): def __init__(self): super().__init__() - self._on_settings_click: Callable | None = None - - self._last_refresh = 0 - self._mouse_down_t: None | float = None - self._did_long_press = False - self._is_pressed_prev = False - - self._version_text = None - self._experimental_mode = False - - self._settings_txt = gui_app.texture("icons_mici/settings.png", 48, 48) - self._experimental_txt = gui_app.texture("icons_mici/experimental_mode.png", 48, 48) - self._mic_txt = gui_app.texture("icons_mici/microphone.png", 32, 46) - - self._net_type = NETWORK_TYPES.get(NetworkType.none) + self.set_rect(rl.Rectangle(0, 0, 54, 44)) # max size of all icons + self._net_type = NetworkType.none self._net_strength = 0 self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 50, 44) @@ -109,6 +98,63 @@ def __init__(self): self._cell_high_txt = gui_app.texture("icons_mici/settings/network/cell_strength_high.png", 54, 36) self._cell_full_txt = gui_app.texture("icons_mici/settings/network/cell_strength_full.png", 54, 36) + def _update_state(self): + device_state = ui_state.sm['deviceState'] + self._net_type = device_state.networkType + strength = device_state.networkStrength + self._net_strength = max(0, min(5, strength.raw + 1)) if strength.raw > 0 else 0 + + def _render(self, _): + # draw network + if self._net_type == NetworkType.wifi: + # There is no 1 + draw_net_txt = {0: self._wifi_none_txt, + 2: self._wifi_low_txt, + 3: self._wifi_medium_txt, + 4: self._wifi_full_txt, + 5: self._wifi_full_txt}.get(self._net_strength, self._wifi_low_txt) + elif self._net_type in (NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G): + draw_net_txt = {0: self._cell_none_txt, + 2: self._cell_low_txt, + 3: self._cell_medium_txt, + 4: self._cell_high_txt, + 5: self._cell_full_txt}.get(self._net_strength, self._cell_none_txt) + else: + draw_net_txt = self._wifi_slash_txt + + draw_x = self._rect.x + (self._rect.width - draw_net_txt.width) / 2 + draw_y = self._rect.y + (self._rect.height - draw_net_txt.height) / 2 + + if draw_net_txt == self._wifi_slash_txt: + # Offset by difference in height between slashless and slash icons to make center align match + draw_y -= (self._wifi_slash_txt.height - self._wifi_none_txt.height) / 2 + + rl.draw_texture(draw_net_txt, int(draw_x), int(draw_y), rl.Color(255, 255, 255, int(255 * 0.9))) + + +class MiciHomeLayout(Widget): + def __init__(self): + super().__init__() + self._on_settings_click: Callable | None = None + + self._last_refresh = 0 + self._mouse_down_t: None | float = None + self._did_long_press = False + self._is_pressed_prev = False + + self._version_text = None + self._experimental_mode = False + + self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48)) + self._mic_icon = IconWidget("icons_mici/microphone.png", (32, 46)) + + self._status_bar_layout = HBoxLayout([ + IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9), + NetworkIcon(), + self._experimental_icon, + self._mic_icon, + ], spacing=18) + self._openpilot_label = MiciLabel("openpilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN) @@ -118,7 +164,6 @@ def __init__(self): def show_event(self): self._version_text = self._get_version_text() - self._update_network_status(ui_state.sm['deviceState']) self._update_params() def _update_params(self): @@ -142,19 +187,11 @@ def _update_state(self): self._did_long_press = True if rl.get_time() - self._last_refresh > 5.0: - device_state = ui_state.sm['deviceState'] - self._update_network_status(device_state) - # Update version text self._version_text = self._get_version_text() self._last_refresh = rl.get_time() self._update_params() - def _update_network_status(self, device_state): - self._net_type = device_state.networkType - strength = device_state.networkStrength - self._net_strength = max(0, min(5, strength.raw + 1)) if strength.raw > 0 else 0 - def set_callbacks(self, on_settings: Callable | None = None): self._on_settings_click = on_settings @@ -206,60 +243,9 @@ def _render(self, _): self._version_commit_label.set_position(version_pos.x, version_pos.y + self._date_label.font_size + 7) self._version_commit_label.render() - self._render_bottom_status_bar() - - def _render_bottom_status_bar(self): # ***** Center-aligned bottom section icons ***** + self._experimental_icon.set_visible(self._experimental_mode) + self._mic_icon.set_visible(ui_state.recording_audio) - # TODO: refactor repeated icon drawing into a small loop - ITEM_SPACING = 18 - Y_CENTER = 24 - - last_x = self.rect.x + HOME_PADDING - - # Draw settings icon in bottom left corner - rl.draw_texture(self._settings_txt, int(last_x), int(self._rect.y + self.rect.height - self._settings_txt.height / 2 - Y_CENTER), - rl.Color(255, 255, 255, int(255 * 0.9))) - last_x = last_x + self._settings_txt.width + ITEM_SPACING - - # draw network - if self._net_type == NetworkType.wifi: - # There is no 1 - draw_net_txt = {0: self._wifi_none_txt, - 2: self._wifi_low_txt, - 3: self._wifi_medium_txt, - 4: self._wifi_full_txt, - 5: self._wifi_full_txt}.get(self._net_strength, self._wifi_low_txt) - rl.draw_texture(draw_net_txt, int(last_x), - int(self._rect.y + self.rect.height - draw_net_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, int(255 * 0.9))) - last_x += draw_net_txt.width + ITEM_SPACING - - elif self._net_type in (NetworkType.cell2G, NetworkType.cell3G, NetworkType.cell4G, NetworkType.cell5G): - draw_net_txt = {0: self._cell_none_txt, - 2: self._cell_low_txt, - 3: self._cell_medium_txt, - 4: self._cell_high_txt, - 5: self._cell_full_txt}.get(self._net_strength, self._cell_none_txt) - rl.draw_texture(draw_net_txt, int(last_x), - int(self._rect.y + self.rect.height - draw_net_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, int(255 * 0.9))) - last_x += draw_net_txt.width + ITEM_SPACING - - else: - # No network - # Offset by difference in height between slashless and slash icons to make center align match - rl.draw_texture(self._wifi_slash_txt, int(last_x), int(self._rect.y + self.rect.height - self._wifi_slash_txt.height / 2 - - (self._wifi_slash_txt.height - self._wifi_none_txt.height) / 2 - Y_CENTER), - rl.Color(255, 255, 255, int(255 * 0.9))) - last_x += self._wifi_slash_txt.width + ITEM_SPACING - - # draw experimental icon - if self._experimental_mode: - rl.draw_texture(self._experimental_txt, int(last_x), - int(self._rect.y + self.rect.height - self._experimental_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, 255)) - last_x += self._experimental_txt.width + ITEM_SPACING - - # draw microphone icon when recording audio is enabled - if ui_state.recording_audio: - rl.draw_texture(self._mic_txt, int(last_x), - int(self._rect.y + self.rect.height - self._mic_txt.height / 2 - Y_CENTER), rl.Color(255, 255, 255, 255)) - last_x += self._mic_txt.width + ITEM_SPACING + footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48) + self._status_bar_layout.render(footer_rect) diff --git a/system/ui/widgets/icon_widget.py b/system/ui/widgets/icon_widget.py new file mode 100644 index 00000000000..bf7790b937b --- /dev/null +++ b/system/ui/widgets/icon_widget.py @@ -0,0 +1,16 @@ +import pyray as rl +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import Widget + + +class IconWidget(Widget): + def __init__(self, image_path: str, size: tuple[int, int], opacity: float = 1.0): + super().__init__() + self._texture = gui_app.texture(image_path, size[0], size[1]) + self._opacity = opacity + self.set_rect(rl.Rectangle(0, 0, float(size[0]), float(size[1]))) + self.set_enabled(False) + + def _render(self, _) -> None: + color = rl.Color(255, 255, 255, int(self._opacity * 255)) + rl.draw_texture_ex(self._texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, 1.0, color) diff --git a/system/ui/widgets/layouts.py b/system/ui/widgets/layouts.py new file mode 100644 index 00000000000..6f97fe5ed85 --- /dev/null +++ b/system/ui/widgets/layouts.py @@ -0,0 +1,60 @@ +from enum import IntFlag +from openpilot.system.ui.widgets import Widget + + +class Alignment(IntFlag): + LEFT = 0 + # TODO: implement + # H_CENTER = 2 + # RIGHT = 4 + + TOP = 8 + V_CENTER = 16 + BOTTOM = 32 + + +class HBoxLayout(Widget): + """ + A Widget that lays out child Widgets horizontally. + """ + + def __init__(self, widgets: list[Widget] | None = None, spacing: int = 0, + alignment: Alignment = Alignment.LEFT | Alignment.V_CENTER): + super().__init__() + self._widgets: list[Widget] = [] + self._spacing = spacing + self._alignment = alignment + + if widgets is not None: + for widget in widgets: + self.add_widget(widget) + + @property + def widgets(self) -> list[Widget]: + return self._widgets + + def add_widget(self, widget: Widget) -> None: + self._widgets.append(widget) + + def _render(self, _): + visible_widgets = [w for w in self._widgets if w.is_visible] + + cur_offset_x = 0 + + for idx, widget in enumerate(visible_widgets): + spacing = self._spacing if (idx > 0) else 0 + + x = self._rect.x + cur_offset_x + spacing + cur_offset_x += widget.rect.width + spacing + + if self._alignment & Alignment.TOP: + y = self._rect.y + elif self._alignment & Alignment.BOTTOM: + y = self._rect.y + self._rect.height - widget.rect.height + else: # center + y = self._rect.y + (self._rect.height - widget.rect.height) / 2 + + # Update widget position and render + widget.set_position(round(x), round(y)) + widget.set_parent_rect(self._rect) + widget.render() From 8fa3f60de731026fdbec7d2c27dd11704c1d5516 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 21 Feb 2026 22:54:18 -0800 Subject: [PATCH 295/685] mici ui: remove DeviceStatus (#37317) rm --- selfdrive/ui/mici/layouts/home.py | 59 ++----------------------------- 1 file changed, 3 insertions(+), 56 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 5e7e9bfea7b..31884e5f183 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -6,11 +6,10 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.layouts import HBoxLayout from openpilot.system.ui.widgets.icon_widget import IconWidget -from openpilot.system.ui.widgets.label import gui_label, MiciLabel, UnifiedLabel -from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR, MousePos +from openpilot.system.ui.widgets.label import MiciLabel, UnifiedLabel +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.system.ui.text import wrap_text -from openpilot.system.version import training_version, RELEASE_BRANCHES +from openpilot.system.version import RELEASE_BRANCHES HEAD_BUTTON_FONT_SIZE = 40 HOME_PADDING = 8 @@ -28,57 +27,6 @@ } -class DeviceStatus(Widget): - def __init__(self): - super().__init__() - self.set_rect(rl.Rectangle(0, 0, 300, 175)) - self._update_state() - self._version_text = self._get_version_text() - - self._do_welcome() - - def _do_welcome(self): - ui_state.params.put("CompletedTrainingVersion", training_version) - - def refresh(self): - self._update_state() - self._version_text = self._get_version_text() - - def _get_version_text(self) -> str: - brand = "openpilot" - description = ui_state.params.get("UpdaterCurrentDescription") - return f"{brand} {description}" if description else brand - - def _update_state(self): - # TODO: refresh function that can be called periodically, not at 60 fps, so we can update version - # update system status - self._system_status = "SYSTEM READY ✓" if ui_state.panda_type != log.PandaState.PandaType.unknown else "BOOTING UP..." - - # update network status - strength = ui_state.sm['deviceState'].networkStrength.raw - strength_text = "● " * strength + "○ " * (4 - strength) # ◌ also works - network_type = NETWORK_TYPES[ui_state.sm['deviceState'].networkType.raw] - self._network_status = f"{network_type} {strength_text}" - - def _render(self, _): - # draw status - status_rect = rl.Rectangle(self._rect.x, self._rect.y, self._rect.width, 40) - gui_label(status_rect, self._system_status, font_size=HEAD_BUTTON_FONT_SIZE, color=DEFAULT_TEXT_COLOR, - font_weight=FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) - - # draw network status - network_rect = rl.Rectangle(self._rect.x, self._rect.y + 60, self._rect.width, 40) - gui_label(network_rect, self._network_status, font_size=40, color=DEFAULT_TEXT_COLOR, - font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) - - # draw version - version_font_size = 30 - version_rect = rl.Rectangle(self._rect.x, self._rect.y + 140, self._rect.width + 20, 40) - wrapped_text = '\n'.join(wrap_text(self._version_text, version_font_size, version_rect.width)) - gui_label(version_rect, wrapped_text, font_size=version_font_size, color=DEFAULT_TEXT_COLOR, - font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_LEFT) - - class NetworkIcon(Widget): def __init__(self): super().__init__() @@ -105,7 +53,6 @@ def _update_state(self): self._net_strength = max(0, min(5, strength.raw + 1)) if strength.raw > 0 else 0 def _render(self, _): - # draw network if self._net_type == NetworkType.wifi: # There is no 1 draw_net_txt = {0: self._wifi_none_txt, From 6fcd2187e18b295f36cceedc610bf3706fedb168 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 21 Feb 2026 23:33:47 -0800 Subject: [PATCH 296/685] scroller: items property --- .../ui/mici/layouts/settings/network/wifi_ui.py | 12 ++++++------ selfdrive/ui/mici/widgets/dialog.py | 8 ++++---- system/ui/widgets/scroller.py | 4 ++++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index b974b502a1a..1c5bd588714 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -363,7 +363,7 @@ def show_event(self): # Clear scroller items and update from latest scan results super().show_event() self._wifi_manager.set_active(True) - self._scroller._items.clear() + self._scroller.items.clear() self._update_buttons() def hide_event(self): @@ -379,22 +379,22 @@ def _update_buttons(self): # Only add new buttons to the end. Update existing buttons without re-sorting so user can freely scroll around for network in self._networks.values(): - network_button_idx = next((i for i, btn in enumerate(self._scroller._items) if btn.option == network.ssid), None) + network_button_idx = next((i for i, btn in enumerate(self._scroller.items) if btn.option == network.ssid), None) if network_button_idx is not None: # Update network on existing button - self._scroller._items[network_button_idx].set_current_network(network) + self._scroller.items[network_button_idx].set_current_network(network) else: network_button = WifiItem(network, lambda: self._wifi_manager.wifi_state) self._scroller.add_widget(network_button) # Move connecting/connected network to the start - connected_btn_idx = next((i for i, btn in enumerate(self._scroller._items) if self._wifi_manager.wifi_state.ssid == btn._network.ssid), None) + connected_btn_idx = next((i for i, btn in enumerate(self._scroller.items) if self._wifi_manager.wifi_state.ssid == btn._network.ssid), None) if connected_btn_idx is not None and connected_btn_idx > 0: - self._scroller._items.insert(0, self._scroller._items.pop(connected_btn_idx)) + self._scroller.items.insert(0, self._scroller.items.pop(connected_btn_idx)) self._scroller._layout() # fixes selected style single frame stutter # Disable networks no longer present - for btn in self._scroller._items: + for btn in self._scroller.items: if btn.option not in self._networks: btn.set_enabled(False) btn.set_network_missing(True) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index c695949e498..32624bbdd6d 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -313,7 +313,7 @@ def get_selected_option(self) -> str: def _on_option_selected(self, option: str): y_pos = 0.0 - for btn in self._scroller._items: + for btn in self._scroller.items: btn = cast(BigDialogOptionButton, btn) if btn.option == option: rect_center_y = self._rect.y + self._rect.height / 2 @@ -350,7 +350,7 @@ def _handle_mouse_release(self, mouse_pos: MousePos): return # select current option - for btn in self._scroller._items: + for btn in self._scroller.items: btn = cast(BigDialogOptionButton, btn) if btn.option == self._selected_option: self._on_option_selected(btn.option) @@ -362,13 +362,13 @@ def _update_state(self): # get selection by whichever button is closest to center center_y = self._rect.y + self._rect.height / 2 closest_btn = (None, float('inf')) - for btn in self._scroller._items: + for btn in self._scroller.items: dist_y = abs((btn.rect.y + btn.rect.height / 2) - center_y) if dist_y < closest_btn[1]: closest_btn = (btn, dist_y) if closest_btn[0]: - for btn in self._scroller._items: + for btn in self._scroller.items: btn.set_selected(btn.option == closest_btn[0].option) self._selected_option = closest_btn[0].option diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index e50a48bbc53..8de86fca9ea 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -117,6 +117,10 @@ def scroll_to(self, pos: float, smooth: bool = False): def is_auto_scrolling(self) -> bool: return self._scrolling_to is not None + @property + def items(self) -> list[Widget]: + return self._items + def add_widget(self, item: Widget) -> None: self._items.append(item) item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled) From 517289f3a5c1686801200337e8d762168c697d0b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 22 Feb 2026 03:36:31 -0800 Subject: [PATCH 297/685] mici scroller: add move animation (#37319) * already 90% of the way there and not 144 lines * nice * lift properly * lift, wait, move, wait, drop! * some clean up * epic, he ran a simulation to turn opacity filter into pixels * scroll independant move animation without layout! * move into function * clean up * rm * overlay behind moving item * Revert "overlay behind moving item" This reverts commit 598e22363eb66af6496fe5f1eea8e643d4c2adbb. * simpler overlay under lifted item * support multiple animations at once * Revert "support multiple animations at once" This reverts commit 3ce6c8281053ee4831ceb88cacf66c343fc7d7ff. * clean up * cmt * clean up * kinda works * Revert "kinda works" This reverts commit ff050c6afc058788b3189a0acc202ada17353504. * clean up clean up * clear overlay * diff report * don't break more --- system/ui/widgets/scroller.py | 126 ++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 14 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 8de86fca9ea..8630b4703fc 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -3,6 +3,7 @@ from collections.abc import Callable from openpilot.common.filter_simple import FirstOrderFilter, BounceFilter +from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2, ScrollState from openpilot.system.ui.widgets import Widget @@ -12,6 +13,9 @@ LINE_PADDING = 40 ANIMATION_SCALE = 0.6 +MOVE_LIFT = 20 +MOVE_OVERLAY_ALPHA = 0.65 + EDGE_SHADOW_WIDTH = 20 MIN_ZOOM_ANIMATION_TIME = 0.075 # seconds @@ -95,6 +99,15 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo self._scroll_indicator = ScrollIndicator() self._edge_shadows = edge_shadows and self._horizontal + # move animation state + # on move; lift src widget -> wait -> move all -> wait -> drop src widget + self._overlay_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps) + self._move_animations: dict[Widget, FirstOrderFilter] = {} + self._move_lift: dict[Widget, FirstOrderFilter] = {} + # these are used to wait before moving/dropping, also to move onto next part of the animation earlier for timing + self._pending_lift: set[Widget] = set() + self._pending_move: set[Widget] = set() + for item in items: self.add_widget(item) @@ -123,7 +136,8 @@ def items(self) -> list[Widget]: def add_widget(self, item: Widget) -> None: self._items.append(item) - item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled) + item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled and self._scrolling_to is None + and not self.moving_items) def set_scrolling_enabled(self, enabled: bool | Callable[[], bool]) -> None: """Set whether scrolling is enabled (does not affect widget enabled state).""" @@ -197,6 +211,69 @@ def _get_scroll(self, visible_items: list[Widget], content_size: float) -> float return self.scroll_panel.get_offset() + @property + def moving_items(self) -> bool: + return len(self._move_animations) > 0 or len(self._move_lift) > 0 + + def move_item(self, from_idx: int, to_idx: int): + assert self._horizontal + if from_idx == to_idx: + return + + if self.moving_items: + cloudlog.warning(f"Already moving items, cannot move from {from_idx} to {to_idx}") + return + + item = self._items.pop(from_idx) + self._items.insert(to_idx, item) + + # store original position in content space of all affected widgets to animate from + for idx in range(min(from_idx, to_idx), max(from_idx, to_idx) + 1): + affected_item = self._items[idx] + self._move_animations[affected_item] = FirstOrderFilter(affected_item.rect.x - self._scroll_offset, 0.15, 1 / gui_app.target_fps) + self._pending_move.add(affected_item) + + # lift only src widget to make it more clear which one is moving + self._move_lift[item] = FirstOrderFilter(0.0, 0.15, 1 / gui_app.target_fps) + self._pending_lift.add(item) + + def _do_move_animation(self, item: Widget, target_x: float, target_y: float) -> tuple[float, float]: + if item in self._move_lift: + lift_filter = self._move_lift[item] + + # Animate lift + if len(self._pending_move) > 0: + lift_filter.update(MOVE_LIFT) + # start moving when almost lifted + if abs(lift_filter.x - MOVE_LIFT) < 2: + self._pending_lift.discard(item) + else: + # if done moving, animate down + lift_filter.update(0) + if abs(lift_filter.x) < 1: + del self._move_lift[item] + target_y -= lift_filter.x + + # Animate move + if item in self._move_animations: + move_filter = self._move_animations[item] + + # compare/update in content space to match filter + content_x = target_x - self._scroll_offset + if len(self._pending_lift) == 0: + move_filter.update(content_x) + + # drop when close to target + if abs(move_filter.x - content_x) < 10: + self._pending_move.discard(item) + + # finished moving + if abs(move_filter.x - content_x) < 1: + del self._move_animations[item] + target_x = move_filter.x + self._scroll_offset + + return target_x, target_y + def _layout(self): self._visible_items = [item for item in self._items if item.is_visible] @@ -242,30 +319,46 @@ def _layout(self): [self._item_pos_filter.x, self._scroll_offset, self._item_pos_filter.x]) y -= np.clip(jello_offset, -20, 20) + # Animate moves if needed + x, y = self._do_move_animation(item, x, y) + # Update item state item.set_position(round(x), round(y)) # round to prevent jumping when settling item.set_parent_rect(self._rect) + def _render_item(self, item: Widget): + # Skip rendering if not in viewport + if not rl.check_collision_recs(item.rect, self._rect): + return + + # Scale each element around its own origin when scrolling + scale = self._zoom_filter.x + if scale != 1.0: + rl.rl_push_matrix() + rl.rl_scalef(scale, scale, 1.0) + rl.rl_translatef((1 - scale) * (item.rect.x + item.rect.width / 2) / scale, + (1 - scale) * (item.rect.y + item.rect.height / 2) / scale, 0) + item.render() + rl.rl_pop_matrix() + else: + item.render() + def _render(self, _): rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) for item in reversed(self._visible_items): - # Skip rendering if not in viewport - if not rl.check_collision_recs(item.rect, self._rect): + if item in self._move_lift: continue + self._render_item(item) - # Scale each element around its own origin when scrolling - scale = self._zoom_filter.x - if scale != 1.0: - rl.rl_push_matrix() - rl.rl_scalef(scale, scale, 1.0) - rl.rl_translatef((1 - scale) * (item.rect.x + item.rect.width / 2) / scale, - (1 - scale) * (item.rect.y + item.rect.height / 2) / scale, 0) - item.render() - rl.rl_pop_matrix() - else: - item.render() + # Dim background if moving items, lifted items are above + self._overlay_filter.update(MOVE_OVERLAY_ALPHA if self.moving_items else 0.0) + if self._overlay_filter.x > 0.01: + rl.draw_rectangle_rec(self._rect, rl.Color(0, 0, 0, int(255 * self._overlay_filter.x))) + + for item in self._move_lift: + self._render_item(item) rl.end_scissor_mode() @@ -295,5 +388,10 @@ def show_event(self): def hide_event(self): super().hide_event() + self._overlay_filter.x = 0.0 + self._move_animations.clear() + self._move_lift.clear() + self._pending_lift.clear() + self._pending_move.clear() for item in self._items: item.hide_event() From 1b262a5a52088bf8ea1b05e9cfc7c609a6841bcc Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 22 Feb 2026 03:55:13 -0800 Subject: [PATCH 298/685] Scroller: fix overlay --- system/ui/widgets/scroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 8630b4703fc..9393dd07ead 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -353,7 +353,7 @@ def _render(self, _): self._render_item(item) # Dim background if moving items, lifted items are above - self._overlay_filter.update(MOVE_OVERLAY_ALPHA if self.moving_items else 0.0) + self._overlay_filter.update(MOVE_OVERLAY_ALPHA if len(self._pending_move) else 0.0) if self._overlay_filter.x > 0.01: rl.draw_rectangle_rec(self._rect, rl.Color(0, 0, 0, int(255 * self._overlay_filter.x))) From 31ac5a216d9016fd54f24085fa023622d6e28dd8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 22 Feb 2026 05:49:27 -0800 Subject: [PATCH 299/685] WifiManager: fix NEED_AUTH for wrong network (#37320) * stash * test seemed to work * simplify * clean up * move under * Revert "move under" This reverts commit ce940cffb32378cbe5a69edaf6fc9d9cec202e54. * back * fix --- system/ui/lib/wifi_manager.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 25c4548e945..f95ac7d8865 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -146,6 +146,7 @@ class ConnectStatus(IntEnum): @dataclass class WifiState: ssid: str | None = None + prev_ssid: str | None = None status: ConnectStatus = ConnectStatus.DISCONNECTED @@ -284,7 +285,10 @@ def tethering_password(self) -> str: return self._tethering_password def _set_connecting(self, ssid: str | None): - self._wifi_state = WifiState(ssid=ssid, status=ConnectStatus.DISCONNECTED if ssid is None else ConnectStatus.CONNECTING) + # Track prev ssid so late NEED_AUTH signals target the right network + self._wifi_state = WifiState(ssid=ssid, + prev_ssid=self.connecting_to_ssid if ssid is not None else None, + status=ConnectStatus.DISCONNECTED if ssid is None else ConnectStatus.CONNECTING) def _enqueue_callbacks(self, cbs: list[Callable], *args): for cb in cbs: @@ -391,16 +395,18 @@ def _monitor_state(self): self._wifi_state = wifi_state - # BAD PASSWORD + # BAD PASSWORD - use prev if current has already moved on to a new connection # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT # - weak/gone network fails with FAILED+NO_SECRETS elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT) or (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): - if self._wifi_state.ssid: - self._enqueue_callbacks(self._need_auth, self._wifi_state.ssid) - - self._set_connecting(None) + failed_ssid = self._wifi_state.prev_ssid or self._wifi_state.ssid + if failed_ssid: + self._enqueue_callbacks(self._need_auth, failed_ssid) + self._wifi_state.prev_ssid = None + if self._wifi_state.ssid == failed_ssid: + self._set_connecting(None) elif new_state in (NMDeviceState.NEED_AUTH, NMDeviceState.IP_CONFIG, NMDeviceState.IP_CHECK, NMDeviceState.SECONDARIES, NMDeviceState.FAILED): @@ -410,7 +416,7 @@ def _monitor_state(self): # Note that IP address from Ip4Config may not be propagated immediately and could take until the next scan results self._update_networks() - wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTED) + wifi_state = replace(self._wifi_state, prev_ssid=None, status=ConnectStatus.CONNECTED) conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) if conn_path is None: From 5f722d2c93199ae22922a3cb2f8e8cbb6870892d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 22 Feb 2026 07:08:48 -0800 Subject: [PATCH 300/685] four: new wifi ui design (#37152) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * start * start * lil more * add forget * fix forget button scrolling * push right a bit * fix forget press * add divider * fix scroll panel * better forget and overriding * revert this * check icon * cursor merge conflict fix * fix rounding and forget btn placement * scroll indicator * 65% * calibrate * try loading animation * push to device * top right * bottom right * no red * top left * bottom left * down 2px * WHY DOES NETWORK MANAGER KEEP CRASHING AHHH * reduce round trip calls in update_networks * clean up and combine getallaccesspoint and activeaccesspoint * cmt * animate big button over smoothly. super hacky, need to clean up * animate * remove old widgets and images * remove status label, tune loading animation opac back * connecting is a little buggy still * add back missing network and don't pop * some fixes * "clean up" * fix lag in animation * fix adding saved connection to start * remove saved network to start, divider * animate up, over, and down * revert for now * remove fancy complex animation for now, sorry nick * remove divider + clean up * more clean up * more clean up * fix forget button press * cmt * tweak loading animation behavior * new lock fix wifi * rm old lock * great catch by opus * clean up * debug * fix touch events that are down -> up in one frame (why it only bugged on mici) * clean up * eager forgetting * this SHOULD be full eager forget, more than i thought * fix wifi slash positioning * move forgotten networks after saved networks * temp keep * test on device * fix * see 65 * 5 best * fix double render double brightness * can click bottom right now * disable touch while animating * fix animation * can scroll while animating, not tap * not great yet * clean up * didn't work * always update networks after activation * stash * move to update_state * debug * debug * temp * fix ip and metered flickering when updating at high freq (or rare race condition) * fix * if you give it less than 8 chars it never clears connecting * lock no int * better wrong password handling * shake when wrong password * nm set connecting when it connects on its own * loading bottom right * sort connecting first * sort by unquantized to put strongest first * clean up * clean up nm * clean up nm * shorter * fix crash * 0.5s * debug * revert and try something else * stash * no * rev * use signals * more * not wrong password if ever connected after wrong * similar to gnome shell, don't save connection that never successfully activated. we do this by creating temporary memory connection with persist: volatile that deletes itself if failed, and then only write to disk when activated * clean up * cover all states * clear if connecting too * remove pritn * might need this for CoxWifi * whoops * save last pass * Revert "whoops" This reverts commit 83a133955246ce32dcf119ededd8b01b3162a866. * Revert "might need this for CoxWifi" This reverts commit cddb8b35be152ed154462b188283f9d5a844583d. * this may be less noisy for low strength networks, but less accurate as previous was reflecting nm state better * Revert "this may be less noisy for low strength networks, but less accurate as previous was reflecting nm state better" This reverts commit 740286c846556f32125a96bfe6ecf128300af0d8. * race condition with volotile not removing conn fast enough/update networks not firing fast enough * Revert "save last pass" This reverts commit 7249a58a18b11487fd0370cee36e40a17f7ac521. * revert some wifiman stuff to master * not needed * rm active ap * remove old dead code * do after * always send forgotten callback so we can't be stuck in forgetting state forever * reproduce race condition where connection removed signal takes a while to remove, then update networks keep is_saved true * fix from merge * nice, we can remove some eager code now for treating is_saved as not saved after forgot since it's live * more * rm * simplify passed in callbacks * clean up * need this one check back for wrong password to hide forget for a split second * opus says this is simpler 🤔 * Revert "opus says this is simpler 🤔" This reverts commit 71472e5b383d7f2083d95ba1188070f41ae14775. * another attempt * Revert "another attempt" This reverts commit 31f30babe656f9cad24399bc2196bb6e7ab79bbd. * fix from merge * some lcean up * fix * fixes to make work with new animation * clean up * this works too * simplify loading animation behavior for now, revert wifi scan time * clean up * temporary fix * stash * Revert "stash" This reverts commit 7471dbdc452807b33b4868a98dd8565681b2e44d. * stash * Revert "stash" This reverts commit e0e5e6e861734320ce5dea5626086784577cb334. * this check was because is_connected could have been stale from Network as the source * nm can show connected/connecting to network with 0 aps for a while if strength is low, move out of range under those states * stash * Revert "stash" This reverts commit 5ec3b454d54392523947f6477f551657d3863a6d. * todo * todo * order * don't need temporary fix anymore * cmt * order * unused i --- .../settings/network/new/connect_button.png | 3 - .../network/new/connect_button_pressed.png | 3 - .../network/new/full_connect_button.png | 3 - .../new/full_connect_button_pressed.png | 3 - .../icons_mici/settings/network/new/lock.png | 4 +- .../settings/network/new/wifi_selected.png | 3 - .../mici/layouts/settings/network/wifi_ui.py | 524 ++++++++---------- system/ui/lib/networkmanager.py | 1 + system/ui/lib/wifi_manager.py | 7 +- system/ui/widgets/scroller.py | 18 +- 10 files changed, 266 insertions(+), 303 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/settings/network/new/connect_button.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png delete mode 100644 selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png diff --git a/selfdrive/assets/icons_mici/settings/network/new/connect_button.png b/selfdrive/assets/icons_mici/settings/network/new/connect_button.png deleted file mode 100644 index eae5af77f09..00000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/connect_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:04236fa0f2759a01c6e321ac7b1c86c7a039215a7953b1a23d250ecf2ef1fa87 -size 8563 diff --git a/selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png b/selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png deleted file mode 100644 index 0da6c384d91..00000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/connect_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4337098554af30c98ebd512e17ab08207db868ff34acca5f865fcbfc940286d3 -size 21123 diff --git a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png b/selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png deleted file mode 100644 index 905170fd10f..00000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ffd37d5e5d5980efa98fee1cd0e8ebbf4139149b41c099e7dc3d5bd402cffb92 -size 9072 diff --git a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png b/selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png deleted file mode 100644 index 88eb4ac2a3b..00000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/full_connect_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1b1d58704f8808dcb5a7ce9d86bc4212477759e96ac2419475f16f9184ee6a42 -size 21892 diff --git a/selfdrive/assets/icons_mici/settings/network/new/lock.png b/selfdrive/assets/icons_mici/settings/network/new/lock.png index 9fc152d3dbc..65bd71f6543 100644 --- a/selfdrive/assets/icons_mici/settings/network/new/lock.png +++ b/selfdrive/assets/icons_mici/settings/network/new/lock.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:782161f35b4925c7063c441b0c341331c814614cf241f21b4e70134280c630f0 -size 1182 +oid sha256:7488c1aa69b728387b2cf300a614cc64e3c2305d2b509c14cf44cad65d20d85c +size 2509 diff --git a/selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png b/selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png deleted file mode 100644 index 2a3e8371381..00000000000 --- a/selfdrive/assets/icons_mici/settings/network/new/wifi_selected.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:160f67162e075436200d6719e614ddf96caaa2b7c0a3943f728c2afef10aa4ad -size 2489 diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 1c5bd588714..c43a294e406 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -3,67 +3,78 @@ import pyray as rl from collections.abc import Callable +from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog -from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.selfdrive.ui.mici.widgets.dialog import BigMultiOptionDialog, BigInputDialog, BigDialogOptionButton, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR, LABEL_HORIZONTAL_PADDING, LABEL_VERTICAL_PADDING from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, WifiState, normalize_ssid +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, normalize_ssid class LoadingAnimation(Widget): + HIDE_TIME = 4 + + def __init__(self): + super().__init__() + self._opacity_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + self._opacity_target = 1.0 + self._hide_time = 0.0 + + def show_event(self): + self._opacity_target = 1.0 + self._hide_time = rl.get_time() + def _render(self, _): - cx = int(self._rect.x + 70) - cy = int(self._rect.y + self._rect.height / 2 - 50) + if rl.get_time() - self._hide_time > self.HIDE_TIME: + self._opacity_target = 0.0 + + self._opacity_filter.update(self._opacity_target) + + if self._opacity_filter.x < 0.01: + return + + cx = int(self._rect.x + self._rect.width / 2) + cy = int(self._rect.y + self._rect.height / 2) - y_mag = 20 - anim_scale = 5 - spacing = 28 + y_mag = 7 + anim_scale = 4 + spacing = 14 for i in range(3): x = cx - spacing + i * spacing y = int(cy + min(math.sin((rl.get_time() - i * 0.2) * anim_scale) * y_mag, 0)) - alpha = int(np.interp(cy - y, [0, y_mag], [255 * 0.45, 255 * 0.9])) - rl.draw_circle(x, y, 10, rl.Color(255, 255, 255, alpha)) + alpha = int(np.interp(cy - y, [0, y_mag], [255 * 0.45, 255 * 0.9]) * self._opacity_filter.x) + rl.draw_circle(x, y, 5, rl.Color(255, 255, 255, alpha)) class WifiIcon(Widget): - def __init__(self): + def __init__(self, network: Network): super().__init__() - self.set_rect(rl.Rectangle(0, 0, 86, 64)) + self.set_rect(rl.Rectangle(0, 0, 48 + 5, 36 + 5)) - self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 86, 64) - self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 86, 64) - self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 86, 64) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 86, 64) - self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 22, 32) + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 48, 42) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 48, 36) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 48, 36) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 48, 36) + self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 21, 27) - self._network: Network | None = None + self._network: Network = network self._network_missing = False # if network disappeared from scan results - self._scale = 1.0 - self._opacity = 1.0 - def set_current_network(self, network: Network): + def update_network(self, network: Network): self._network = network def set_network_missing(self, missing: bool): self._network_missing = missing - def set_scale(self, scale: float): - self._scale = scale - - def set_opacity(self, opacity: float): - self._opacity = opacity - @staticmethod def get_strength_icon_idx(strength: int) -> int: return round(strength / 100 * 2) def _render(self, _): - if self._network is None: - return - # Determine which wifi strength icon to use strength = self.get_strength_icon_idx(self._network.strength) if self._network_missing: @@ -75,126 +86,186 @@ def _render(self, _): else: strength_icon = self._wifi_low_txt - tint = rl.Color(255, 255, 255, int(255 * self._opacity)) - icon_x = int(self._rect.x + (self._rect.width - strength_icon.width * self._scale) // 2) - icon_y = int(self._rect.y + (self._rect.height - strength_icon.height * self._scale) // 2) - rl.draw_texture_ex(strength_icon, (icon_x, icon_y), 0.0, self._scale, tint) + rl.draw_texture_ex(strength_icon, (self._rect.x, self._rect.y + self._rect.height - strength_icon.height), 0.0, 1.0, rl.WHITE) # Render lock icon at lower right of wifi icon if secured if self._network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED): - lock_scale = self._scale * 1.1 - lock_x = int(icon_x + 1 + strength_icon.width * self._scale - self._lock_txt.width * lock_scale / 2) - lock_y = int(icon_y + 1 + strength_icon.height * self._scale - self._lock_txt.height * lock_scale / 2) - rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, lock_scale, tint) + lock_x = self._rect.x + self._rect.width - self._lock_txt.width + lock_y = self._rect.y + self._rect.height - self._lock_txt.height + 6 + rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, 1.0, rl.WHITE) -class WifiItem(BigDialogOptionButton): - LEFT_MARGIN = 20 +class WifiButton(BigButton): + LABEL_PADDING = 98 + LABEL_WIDTH = 402 - 98 - 28 # button width - left padding - right padding + SUB_LABEL_WIDTH = 402 - LABEL_HORIZONTAL_PADDING * 2 - def __init__(self, network: Network, wifi_state_callback: Callable[[], WifiState]): - super().__init__(network.ssid) + def __init__(self, network: Network, wifi_manager: WifiManager): + super().__init__(normalize_ssid(network.ssid), scroll=True) - self.set_rect(rl.Rectangle(0, 0, gui_app.width, self.HEIGHT)) + self._network = network + self._wifi_manager = wifi_manager + + self._wifi_icon = WifiIcon(network) + self._forget_btn = ForgetButton(self._forget_network) + self._check_txt = gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 32, 32) - self._selected_txt = gui_app.texture("icons_mici/settings/network/new/wifi_selected.png", 48, 96) + # Eager state (not sourced from Network) + self._network_missing = False + self._network_forgetting = False + self._wrong_password = False + self._shake_start: float | None = None + def update_network(self, network: Network): self._network = network - self._wifi_state_callback = wifi_state_callback - self._wifi_icon = WifiIcon() - self._wifi_icon.set_current_network(network) + self._wifi_icon.update_network(network) + + # We can assume network is not missing if got new Network + self._network_missing = False + self._wifi_icon.set_network_missing(False) + if self._is_connected or self._is_connecting: + self._wrong_password = False + + def _forget_network(self): + if self._network_forgetting: + return + + self._network_forgetting = True + self._forget_btn.set_visible(False) + self._wifi_manager.forget_connection(self._network.ssid) + + def on_forgotten(self): + self._network_forgetting = False + self._forget_btn.set_visible(True) def set_network_missing(self, missing: bool): + self._network_missing = missing self._wifi_icon.set_network_missing(missing) - def set_current_network(self, network: Network): - self._network = network - self._wifi_icon.set_current_network(network) - - # reset if we see the network again - self.set_enabled(True) - self.set_network_missing(False) + def set_wrong_password(self): + self._wrong_password = True + self._shake_start = rl.get_time() - def _render(self, _): - disabled_alpha = 0.35 if not self.enabled else 1.0 + @property + def network(self) -> Network: + return self._network - # connecting or connected - if self._wifi_state_callback().ssid == self._network.ssid: - selected_x = int(self._rect.x - self._selected_txt.width / 2) - selected_y = int(self._rect.y + (self._rect.height - self._selected_txt.height) / 2) - rl.draw_texture(self._selected_txt, selected_x, selected_y, rl.WHITE) + @property + def _show_forget_btn(self): + return (self._is_saved and not self._wrong_password) or self._is_connecting - self._wifi_icon.set_opacity(disabled_alpha) - self._wifi_icon.set_scale((1.0 if self._selected else 0.65) * 0.7) - self._wifi_icon.render(rl.Rectangle( - self._rect.x + self.LEFT_MARGIN, - self._rect.y, - self.SELECTED_HEIGHT, - self._rect.height - )) + def _handle_mouse_release(self, mouse_pos: MousePos): + if self._show_forget_btn and rl.check_collision_point_rec(mouse_pos, self._forget_btn.rect): + return + super()._handle_mouse_release(mouse_pos) - if self._selected: - self._label.set_font_size(self.SELECTED_HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9 * disabled_alpha))) - self._label.set_font_weight(FontWeight.DISPLAY) - else: - self._label.set_font_size(self.HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58 * disabled_alpha))) - self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) + def _get_label_font_size(self): + return 48 - label_offset = self.LEFT_MARGIN + self._wifi_icon.rect.width + 20 - label_rect = rl.Rectangle(self._rect.x + label_offset, self._rect.y, self._rect.width - label_offset, self._rect.height) - self._label.set_text(normalize_ssid(self._network.ssid)) + @property + def _shake_offset(self) -> float: + SHAKE_DURATION = 0.5 + SHAKE_AMPLITUDE = 24.0 + SHAKE_FREQUENCY = 32.0 + t = rl.get_time() - (self._shake_start or 0.0) + if t > SHAKE_DURATION: + return 0.0 + decay = 1.0 - t / SHAKE_DURATION + return decay * SHAKE_AMPLITUDE * math.sin(t * SHAKE_FREQUENCY) + + def set_position(self, x: float, y: float) -> None: + super().set_position(x + self._shake_offset, y) + + def _draw_content(self, btn_y: float): + self._label.set_color(LABEL_COLOR) + label_rect = rl.Rectangle(self._rect.x + self.LABEL_PADDING, btn_y + LABEL_VERTICAL_PADDING, + self.LABEL_WIDTH, self._rect.height - LABEL_VERTICAL_PADDING * 2) self._label.render(label_rect) + if self.value: + sub_label_x = self._rect.x + LABEL_HORIZONTAL_PADDING + label_y = btn_y + self._rect.height - LABEL_VERTICAL_PADDING + sub_label_w = self.SUB_LABEL_WIDTH - (self._forget_btn.rect.width if self._show_forget_btn else 0) + sub_label_height = self._sub_label.get_content_height(sub_label_w) -class ConnectButton(Widget): - def __init__(self): - super().__init__() - self._bg_txt = gui_app.texture("icons_mici/settings/network/new/connect_button.png", 410, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/connect_button_pressed.png", 410, 100) - self._bg_full_txt = gui_app.texture("icons_mici/settings/network/new/full_connect_button.png", 520, 100) - self._bg_full_pressed_txt = gui_app.texture("icons_mici/settings/network/new/full_connect_button_pressed.png", 520, 100) + if self._is_connected and not self._network_forgetting: + check_y = int(label_y - sub_label_height + (sub_label_height - self._check_txt.height) / 2) + rl.draw_texture(self._check_txt, int(sub_label_x), check_y, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))) + sub_label_x += self._check_txt.width + 14 - self._full: bool = False + sub_label_rect = rl.Rectangle(sub_label_x, label_y - sub_label_height, sub_label_w, sub_label_height) + self._sub_label.render(sub_label_rect) - self._label = UnifiedLabel("", 36, FontWeight.MEDIUM, rl.Color(255, 255, 255, int(255 * 0.9)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + # Wifi icon + self._wifi_icon.render(rl.Rectangle( + self._rect.x + 30, + btn_y + 30, + self._wifi_icon.rect.width, + self._wifi_icon.rect.height, + )) - @property - def full(self) -> bool: - return self._full + # Forget button + if self._show_forget_btn: + self._forget_btn.render(rl.Rectangle( + self._rect.x + self._rect.width - self._forget_btn.rect.width, + btn_y + self._rect.height - self._forget_btn.rect.height, + self._forget_btn.rect.width, + self._forget_btn.rect.height, + )) - def set_full(self, full: bool): - self._full = full - self.set_rect(rl.Rectangle(0, 0, 520 if self._full else 410, 100)) + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: + super().set_touch_valid_callback(lambda: touch_callback() and not self._forget_btn.is_pressed) + self._forget_btn.set_touch_valid_callback(touch_callback) - def set_label(self, text: str): - self._label.set_text(text) + @property + def _is_saved(self): + return self._wifi_manager.is_connection_saved(self._network.ssid) - def _render(self, _): - if self._full: - bg_txt = self._bg_full_pressed_txt if self.is_pressed and self.enabled else self._bg_full_txt - else: - bg_txt = self._bg_pressed_txt if self.is_pressed and self.enabled else self._bg_txt + @property + def _is_connecting(self): + return self._wifi_manager.connecting_to_ssid == self._network.ssid - rl.draw_texture(bg_txt, int(self._rect.x), int(self._rect.y), rl.WHITE) + @property + def _is_connected(self): + return self._wifi_manager.connected_ssid == self._network.ssid + + def _update_state(self): + if any((self._network_missing, self._is_connecting, self._is_connected, self._network_forgetting, + self._network.security_type == SecurityType.UNSUPPORTED)): + self.set_enabled(False) + self._sub_label.set_color(rl.Color(255, 255, 255, int(255 * 0.585))) + self._sub_label.set_font_weight(FontWeight.ROMAN) + + if self._network_forgetting: + self.set_value("forgetting...") + elif self._is_connecting: + self.set_value("connecting...") + elif self._is_connected: + self.set_value("connected") + elif self._network_missing: + # after connecting/connected since NM will still attempt to connect/stay connected for a while + self.set_value("not in range") + else: + self.set_value("unsupported") - self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9) if self.enabled else int(255 * 0.9 * 0.65))) - self._label.render(self._rect) + else: # saved, wrong password, or unknown + self.set_value("wrong password" if self._wrong_password else "connect") + self.set_enabled(True) + self._sub_label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._sub_label.set_font_weight(FontWeight.SEMI_BOLD) class ForgetButton(Widget): - HORIZONTAL_MARGIN = 8 + MARGIN = 12 # bottom and right def __init__(self, forget_network: Callable): super().__init__() self._forget_network = forget_network - self._bg_txt = gui_app.texture("icons_mici/settings/network/new/forget_button.png", 100, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/forget_button_pressed.png", 100, 100) - self._trash_txt = gui_app.texture("icons_mici/settings/network/new/trash.png", 35, 42) - self.set_rect(rl.Rectangle(0, 0, 100 + self.HORIZONTAL_MARGIN * 2, 100)) + self._bg_txt = gui_app.texture("icons_mici/settings/network/new/forget_button.png", 84, 84) + self._bg_pressed_txt = gui_app.texture("icons_mici/settings/network/new/forget_button_pressed.png", 84, 84) + self._trash_txt = gui_app.texture("icons_mici/settings/network/new/trash.png", 29, 35) + self.set_rect(rl.Rectangle(0, 0, 84 + self.MARGIN * 2, 84 + self.MARGIN * 2)) def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) @@ -204,150 +275,22 @@ def _handle_mouse_release(self, mouse_pos: MousePos): def _render(self, _): bg_txt = self._bg_pressed_txt if self.is_pressed else self._bg_txt - rl.draw_texture(bg_txt, int(self._rect.x + self.HORIZONTAL_MARGIN), int(self._rect.y), rl.WHITE) - - trash_x = int(self._rect.x + (self._rect.width - self._trash_txt.width) // 2) - trash_y = int(self._rect.y + (self._rect.height - self._trash_txt.height) // 2) - rl.draw_texture(self._trash_txt, trash_x, trash_y, rl.WHITE) - - -class NetworkInfoPage(NavWidget): - def __init__(self, wifi_manager, connect_callback: Callable, forget_callback: Callable, - connecting_callback: Callable[[], str | None], connected_callback: Callable[[], str | None]): - super().__init__() - self._wifi_manager = wifi_manager - - self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - - self._wifi_icon = WifiIcon() - self._forget_btn = ForgetButton(lambda: forget_callback(self._network.ssid) if self._network is not None else None) - self._forget_btn.set_enabled(lambda: self.enabled) # for stack - self._connect_btn = ConnectButton() - self._connect_btn.set_click_callback(lambda: connect_callback(self._network.ssid) if self._network is not None else None) - - self._title = UnifiedLabel("", 64, FontWeight.DISPLAY, rl.Color(255, 255, 255, int(255 * 0.9)), - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, scroll=True) - self._subtitle = UnifiedLabel("", 36, FontWeight.ROMAN, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) - - self.set_back_callback(gui_app.pop_widget) - - # State - self._network: Network | None = None - self._connecting_callback = connecting_callback - self._connected_callback = connected_callback + rl.draw_texture_ex(bg_txt, (self._rect.x + (self._rect.width - self._bg_txt.width) / 2, + self._rect.y + (self._rect.height - self._bg_txt.height) / 2), 0, 1.0, rl.WHITE) - def show_event(self): - super().show_event() - self._title.reset_scroll() - - def update_networks(self, networks: dict[str, Network]): - # update current network from latest scan results - for ssid, network in networks.items(): - if self._network is not None and ssid == self._network.ssid: - self.set_current_network(network) - break - else: - # network disappeared, close page - # TODO: pop_widgets_to, to close potentially open keyboard too - if gui_app.get_active_widget() == self: - gui_app.pop_widget() - - def _update_state(self): - super()._update_state() - # TODO: remove? only left for potential compatibility with setup/updater - self._wifi_manager.process_callbacks() - - if self._network is None: - return + trash_x = self._rect.x + (self._rect.width - self._trash_txt.width) / 2 + trash_y = self._rect.y + (self._rect.height - self._trash_txt.height) / 2 + rl.draw_texture_ex(self._trash_txt, (trash_x, trash_y), 0, 1.0, rl.WHITE) - self._connect_btn.set_full(not self._wifi_manager.is_connection_saved(self._network.ssid) and not self._is_connecting) - if self._is_connecting: - self._connect_btn.set_label("connecting...") - self._connect_btn.set_enabled(False) - elif self._is_connected: - self._connect_btn.set_label("connected") - self._connect_btn.set_enabled(False) - elif self._network.security_type == SecurityType.UNSUPPORTED: - self._connect_btn.set_label("connect") - self._connect_btn.set_enabled(False) - else: # saved or unknown - self._connect_btn.set_label("connect") - self._connect_btn.set_enabled(self.enabled) - - self._title.set_text(normalize_ssid(self._network.ssid)) - if self._network.security_type == SecurityType.OPEN: - self._subtitle.set_text("open") - elif self._network.security_type == SecurityType.UNSUPPORTED: - self._subtitle.set_text("unsupported") - else: - self._subtitle.set_text("secured") - - def set_current_network(self, network: Network): - self._network = network - self._wifi_icon.set_current_network(network) - - @property - def _is_connecting(self): - if self._network is None: - return False - is_connecting = self._connecting_callback() == self._network.ssid - return is_connecting - - @property - def _is_connected(self): - if self._network is None: - return False - is_connected = self._connected_callback() == self._network.ssid - return is_connected - - def _render(self, _): - self._wifi_icon.render(rl.Rectangle( - self._rect.x + 32, - self._rect.y + (self._rect.height - self._connect_btn.rect.height - self._wifi_icon.rect.height) / 2, - self._wifi_icon.rect.width, - self._wifi_icon.rect.height, - )) - self._title.render(rl.Rectangle( - self._rect.x + self._wifi_icon.rect.width + 32 + 32, - self._rect.y + 32 - 16, - self._rect.width - (self._wifi_icon.rect.width + 32 + 32), - 64, - )) - - self._subtitle.render(rl.Rectangle( - self._rect.x + self._wifi_icon.rect.width + 32 + 32, - self._rect.y + 32 + 64 - 16, - self._rect.width - (self._wifi_icon.rect.width + 32 + 32), - 48, - )) - - self._connect_btn.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._connect_btn.rect.height, - self._connect_btn.rect.width, - self._connect_btn.rect.height, - )) - - if not self._connect_btn.full: - self._forget_btn.render(rl.Rectangle( - self._rect.x + self._rect.width - self._forget_btn.rect.width, - self._rect.y + self._rect.height - self._forget_btn.rect.height, - self._forget_btn.rect.width, - self._forget_btn.rect.height, - )) - - -class WifiUIMici(BigMultiOptionDialog): +class WifiUIMici(NavWidget): def __init__(self, wifi_manager: WifiManager): - super().__init__([], None) + super().__init__() # Set up back navigation self.set_back_callback(gui_app.pop_widget) - self._network_info_page = NetworkInfoPage(wifi_manager, self._connect_to_network, wifi_manager.forget_connection, - lambda: wifi_manager.connecting_to_ssid, lambda: wifi_manager.connected_ssid) + self._scroller = Scroller([]) self._loading_animation = LoadingAnimation() @@ -356,12 +299,15 @@ def __init__(self, wifi_manager: WifiManager): self._wifi_manager.add_callbacks( need_auth=self._on_need_auth, + forgotten=self._on_forgotten, networks_updated=self._on_network_updated, ) def show_event(self): # Clear scroller items and update from latest scan results super().show_event() + self._scroller.show_event() + self._loading_animation.show_event() self._wifi_manager.set_active(True) self._scroller.items.clear() self._update_buttons() @@ -373,43 +319,38 @@ def hide_event(self): def _on_network_updated(self, networks: list[Network]): self._networks = {network.ssid: network for network in networks} self._update_buttons() - self._network_info_page.update_networks(self._networks) def _update_buttons(self): - # Only add new buttons to the end. Update existing buttons without re-sorting so user can freely scroll around + # Update existing buttons, add new ones to the end + existing = {btn.network.ssid: btn for btn in self._scroller.items if isinstance(btn, WifiButton)} for network in self._networks.values(): - network_button_idx = next((i for i, btn in enumerate(self._scroller.items) if btn.option == network.ssid), None) - if network_button_idx is not None: - # Update network on existing button - self._scroller.items[network_button_idx].set_current_network(network) + if network.ssid in existing: + existing[network.ssid].update_network(network) else: - network_button = WifiItem(network, lambda: self._wifi_manager.wifi_state) - self._scroller.add_widget(network_button) - - # Move connecting/connected network to the start - connected_btn_idx = next((i for i, btn in enumerate(self._scroller.items) if self._wifi_manager.wifi_state.ssid == btn._network.ssid), None) - if connected_btn_idx is not None and connected_btn_idx > 0: - self._scroller.items.insert(0, self._scroller.items.pop(connected_btn_idx)) - self._scroller._layout() # fixes selected style single frame stutter + btn = WifiButton(network, self._wifi_manager) + btn.set_click_callback(lambda ssid=network.ssid: self._connect_to_network(ssid)) + self._scroller.add_widget(btn) - # Disable networks no longer present + # Mark networks no longer in scan results (display handled by _update_state) for btn in self._scroller.items: - if btn.option not in self._networks: - btn.set_enabled(False) + if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: btn.set_network_missing(True) + # Move connecting/connected network to the front with animation + front_ssid = self._wifi_manager.wifi_state.ssid + front_btn_idx = next((i for i, btn in enumerate(self._scroller.items) + if isinstance(btn, WifiButton) and + btn.network.ssid == front_ssid), None) if front_ssid else None + + if front_btn_idx is not None and front_btn_idx > 0: + self._scroller.move_item(front_btn_idx, 0) + def _connect_with_password(self, ssid: str, password: str): self._wifi_manager.connect_to_network(ssid, password) + self._scroller.scroll_to(self._scroller.scroll_panel.get_offset(), smooth=True) self._update_buttons() - def _on_option_selected(self, option: str): - super()._on_option_selected(option) - - if option in self._networks: - self._network_info_page.set_current_network(self._networks[option]) - gui_app.push_widget(self._network_info_page) - def _connect_to_network(self, ssid: str): network = self._networks.get(ssid) if network is None: @@ -418,21 +359,46 @@ def _connect_to_network(self, ssid: str): if self._wifi_manager.is_connection_saved(network.ssid): self._wifi_manager.activate_connection(network.ssid) - self._update_buttons() elif network.security_type == SecurityType.OPEN: self._wifi_manager.connect_to_network(network.ssid, "") - self._update_buttons() else: self._on_need_auth(network.ssid, False) + return + + self._scroller.scroll_to(self._scroller.scroll_panel.get_offset(), smooth=True) + self._update_buttons() def _on_need_auth(self, ssid, incorrect_password=True): - hint = "wrong password..." if incorrect_password else "enter password..." - dlg = BigInputDialog(hint, "", minimum_length=8, + if incorrect_password: + for btn in self._scroller.items: + if isinstance(btn, WifiButton) and btn.network.ssid == ssid: + btn.set_wrong_password() + break + return + + dlg = BigInputDialog("enter password...", "", minimum_length=8, confirm_callback=lambda _password: self._connect_with_password(ssid, _password)) gui_app.push_widget(dlg) + def _on_forgotten(self, ssid): + # For eager UI forget + for btn in self._scroller.items: + if isinstance(btn, WifiButton) and btn.network.ssid == ssid: + btn.on_forgotten() + + def _update_state(self): + super()._update_state() + + # Show loading animation near end + max_scroll = max(self._scroller.content_size - self._scroller.rect.width, 1) + progress = -self._scroller.scroll_panel.get_offset() / max_scroll + if progress > 0.8 or len(self._scroller.items) <= 1: + self._loading_animation.show_event() + def _render(self, _): - super()._render(_) + self._scroller.render(self._rect) - if not self._networks: - self._loading_animation.render(self._rect) + anim_w = 90 + anim_x = self._rect.x + self._rect.width - anim_w + anim_y = self._rect.y + self._rect.height - 25 + 2 + self._loading_animation.render(rl.Rectangle(anim_x, anim_y, anim_w, 20)) diff --git a/system/ui/lib/networkmanager.py b/system/ui/lib/networkmanager.py index e04e3eeadca..c47928d8ba4 100644 --- a/system/ui/lib/networkmanager.py +++ b/system/ui/lib/networkmanager.py @@ -26,6 +26,7 @@ class NMDeviceStateReason(IntEnum): NO_SECRETS = 7 SUPPLICANT_DISCONNECT = 8 CONNECTION_REMOVED = 38 + SSID_NOT_FOUND = 53 NEW_ACTIVATION = 60 diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index f95ac7d8865..4ca0a382d44 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -378,6 +378,9 @@ def _monitor_state(self): while len(state_q): new_state, previous_state, change_reason = state_q.popleft().body + # TODO: Handle (FAILED, SSID_NOT_FOUND) and emit for ui to show error + # Happens when network drops off after starting connection + if new_state == NMDeviceState.DISCONNECTED: if change_reason != NMDeviceStateReason.NEW_ACTIVATION: # catches CONNECTION_REMOVED reason when connection is forgotten @@ -414,8 +417,6 @@ def _monitor_state(self): elif new_state == NMDeviceState.ACTIVATED: # Note that IP address from Ip4Config may not be propagated immediately and could take until the next scan results - self._update_networks() - wifi_state = replace(self._wifi_state, prev_ssid=None, status=ConnectStatus.CONNECTED) conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) @@ -423,10 +424,12 @@ def _monitor_state(self): cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") self._wifi_state = wifi_state self._enqueue_callbacks(self._activated) + self._update_networks() else: wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) self._wifi_state = wifi_state self._enqueue_callbacks(self._activated) + self._update_networks() # Persist volatile connections (created by AddAndActivateConnection2) to disk conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 9393dd07ead..0543b1395eb 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -15,6 +15,7 @@ MOVE_LIFT = 20 MOVE_OVERLAY_ALPHA = 0.65 +SCROLL_RC = 0.15 EDGE_SHADOW_WIDTH = 20 @@ -78,7 +79,7 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo self._reset_scroll_at_show = True self._scrolling_to: float | None = None - self._scroll_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + self._scroll_filter = FirstOrderFilter(0.0, SCROLL_RC, 1 / gui_app.target_fps) self._zoom_filter = FirstOrderFilter(1.0, 0.2, 1 / gui_app.target_fps) self._zoom_out_t: float = 0.0 @@ -134,6 +135,10 @@ def is_auto_scrolling(self) -> bool: def items(self) -> list[Widget]: return self._items + @property + def content_size(self) -> float: + return self._content_size + def add_widget(self, item: Widget) -> None: self._items.append(item) item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled and self._scrolling_to is None @@ -159,7 +164,7 @@ def _update_state(self): if self._scrolling_to is not None and (self.scroll_panel.state == ScrollState.PRESSED or self.scroll_panel.state == ScrollState.MANUAL_SCROLL): self._scrolling_to = None - if self._scrolling_to is not None: + if self._scrolling_to is not None and len(self._pending_lift) == 0: self._scroll_filter.update(self._scrolling_to) self.scroll_panel.set_offset(self._scroll_filter.x) @@ -230,14 +235,17 @@ def move_item(self, from_idx: int, to_idx: int): # store original position in content space of all affected widgets to animate from for idx in range(min(from_idx, to_idx), max(from_idx, to_idx) + 1): affected_item = self._items[idx] - self._move_animations[affected_item] = FirstOrderFilter(affected_item.rect.x - self._scroll_offset, 0.15, 1 / gui_app.target_fps) + self._move_animations[affected_item] = FirstOrderFilter(affected_item.rect.x - self._scroll_offset, SCROLL_RC, 1 / gui_app.target_fps) self._pending_move.add(affected_item) # lift only src widget to make it more clear which one is moving - self._move_lift[item] = FirstOrderFilter(0.0, 0.15, 1 / gui_app.target_fps) + self._move_lift[item] = FirstOrderFilter(0.0, SCROLL_RC, 1 / gui_app.target_fps) self._pending_lift.add(item) def _do_move_animation(self, item: Widget, target_x: float, target_y: float) -> tuple[float, float]: + # wait a frame before moving so we match potential pending scroll animation + can_start_move = len(self._pending_lift) == 0 + if item in self._move_lift: lift_filter = self._move_lift[item] @@ -260,7 +268,7 @@ def _do_move_animation(self, item: Widget, target_x: float, target_y: float) -> # compare/update in content space to match filter content_x = target_x - self._scroll_offset - if len(self._pending_lift) == 0: + if can_start_move: move_filter.update(content_x) # drop when close to target From ddf8abc14a272d5e95ffd2569d1ddfd2195c56f5 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sun, 22 Feb 2026 10:34:37 -0700 Subject: [PATCH 301/685] Revert "feat(lpa): `at` client + list profiles (#37271)" (#37322) This reverts commit 8bca2ca7588bae62a860b1e90a835e1678367596. --- system/hardware/tici/lpa.py | 229 +----------------------------------- 1 file changed, 2 insertions(+), 227 deletions(-) diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index 4d649fda8b0..9bd9d8c7b09 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -1,237 +1,12 @@ -# SGP.22 v2.3: https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2021/07/SGP.22-v2.3.pdf - -import atexit -import base64 -import os -import serial -import sys - -from collections.abc import Generator - from openpilot.system.hardware.base import LPABase, Profile -DEFAULT_DEVICE = "/dev/ttyUSB2" -DEFAULT_BAUD = 9600 -DEFAULT_TIMEOUT = 5.0 -# https://euicc-manual.osmocom.org/docs/lpa/applet-id/ -ISDR_AID = "A0000005591010FFFFFFFF8900000100" -ES10X_MSS = 120 -DEBUG = os.environ.get("DEBUG") == "1" - -# TLV Tags -TAG_ICCID = 0x5A -TAG_PROFILE_INFO_LIST = 0xBF2D - -STATE_LABELS = {0: "disabled", 1: "enabled", 255: "unknown"} -ICON_LABELS = {0: "jpeg", 1: "png", 255: "unknown"} -CLASS_LABELS = {0: "test", 1: "provisioning", 2: "operational", 255: "unknown"} - - -def b64e(data: bytes) -> str: - return base64.b64encode(data).decode("ascii") - - -class AtClient: - def __init__(self, device: str, baud: int, timeout: float, debug: bool) -> None: - self.serial = serial.Serial(device, baudrate=baud, timeout=timeout) - self.debug = debug - self.channel: str | None = None - self.serial.reset_input_buffer() - - def close(self) -> None: - try: - if self.channel: - self.query(f"AT+CCHC={self.channel}") - self.channel = None - finally: - self.serial.close() - - def send(self, cmd: str) -> None: - if self.debug: - print(f">> {cmd}", file=sys.stderr) - self.serial.write((cmd + "\r").encode("ascii")) - - def expect(self) -> list[str]: - lines: list[str] = [] - while True: - raw = self.serial.readline() - if not raw: - raise TimeoutError("AT command timed out") - line = raw.decode(errors="ignore").strip() - if not line: - continue - if self.debug: - print(f"<< {line}", file=sys.stderr) - if line == "OK": - return lines - if line == "ERROR" or line.startswith("+CME ERROR"): - raise RuntimeError(f"AT command failed: {line}") - lines.append(line) - - def query(self, cmd: str) -> list[str]: - self.send(cmd) - return self.expect() - - def open_isdr(self) -> None: - # close any stale logical channel from a previous crashed session - try: - self.query("AT+CCHC=1") - except RuntimeError: - pass - for line in self.query(f'AT+CCHO="{ISDR_AID}"'): - if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()): - self.channel = ch - return - raise RuntimeError("Failed to open ISD-R application") - - def send_apdu(self, apdu: bytes) -> tuple[bytes, int, int]: - if not self.channel: - raise RuntimeError("Logical channel is not open") - hex_payload = apdu.hex().upper() - for line in self.query(f'AT+CGLA={self.channel},{len(hex_payload)},"{hex_payload}"'): - if line.startswith("+CGLA:"): - parts = line.split(":", 1)[1].split(",", 1) - if len(parts) == 2: - data = bytes.fromhex(parts[1].strip().strip('"')) - if len(data) >= 2: - return data[:-2], data[-2], data[-1] - raise RuntimeError("Missing +CGLA response") - - -# --- TLV utilities --- - -def iter_tlv(data: bytes, with_positions: bool = False) -> Generator: - idx, length = 0, len(data) - while idx < length: - start_pos = idx - tag = data[idx] - idx += 1 - if tag & 0x1F == 0x1F: # Multi-byte tag - tag_value = tag - while idx < length: - next_byte = data[idx] - idx += 1 - tag_value = (tag_value << 8) | next_byte - if not (next_byte & 0x80): - break - else: - tag_value = tag - if idx >= length: - break - size = data[idx] - idx += 1 - if size & 0x80: # Multi-byte length - num_bytes = size & 0x7F - if idx + num_bytes > length: - break - size = int.from_bytes(data[idx : idx + num_bytes], "big") - idx += num_bytes - if idx + size > length: - break - value = data[idx : idx + size] - idx += size - yield (tag_value, value, start_pos, idx) if with_positions else (tag_value, value) - - -def find_tag(data: bytes, target: int) -> bytes | None: - return next((v for t, v in iter_tlv(data) if t == target), None) - - -def tbcd_to_string(raw: bytes) -> str: - return "".join(str(n) for b in raw for n in (b & 0x0F, b >> 4) if n <= 9) - - -# Profile field decoders: TLV tag -> (field_name, decoder) -_PROFILE_FIELDS = { - TAG_ICCID: ("iccid", tbcd_to_string), - 0x4F: ("isdpAid", lambda v: v.hex().upper()), - 0x9F70: ("profileState", lambda v: STATE_LABELS.get(v[0], "unknown")), - 0x90: ("profileNickname", lambda v: v.decode("utf-8", errors="ignore") or None), - 0x91: ("serviceProviderName", lambda v: v.decode("utf-8", errors="ignore") or None), - 0x92: ("profileName", lambda v: v.decode("utf-8", errors="ignore") or None), - 0x93: ("iconType", lambda v: ICON_LABELS.get(v[0], "unknown")), - 0x94: ("icon", b64e), - 0x95: ("profileClass", lambda v: CLASS_LABELS.get(v[0], "unknown")), -} - - -def _decode_profile_fields(data: bytes) -> dict: - """Parse known profile metadata TLV fields into a dict.""" - result = {} - for tag, value in iter_tlv(data): - if (field := _PROFILE_FIELDS.get(tag)): - result[field[0]] = field[1](value) - return result - - -# --- ES10x command transport --- - -def es10x_command(client: AtClient, data: bytes) -> bytes: - response = bytearray() - sequence = 0 - offset = 0 - while offset < len(data): - chunk = data[offset : offset + ES10X_MSS] - offset += len(chunk) - is_last = offset == len(data) - apdu = bytes([0x80, 0xE2, 0x91 if is_last else 0x11, sequence & 0xFF, len(chunk)]) + chunk - segment, sw1, sw2 = client.send_apdu(apdu) - response.extend(segment) - while True: - if sw1 == 0x61: # More data available - segment, sw1, sw2 = client.send_apdu(bytes([0x80, 0xC0, 0x00, 0x00, sw2 or 0])) - response.extend(segment) - continue - if (sw1 & 0xF0) == 0x90: - break - raise RuntimeError(f"APDU failed with SW={sw1:02X}{sw2:02X}") - sequence += 1 - return bytes(response) - - -# --- Profile operations --- - -def decode_profiles(blob: bytes) -> list[dict]: - root = find_tag(blob, TAG_PROFILE_INFO_LIST) - if root is None: - raise RuntimeError("Missing ProfileInfoList") - list_ok = find_tag(root, 0xA0) - if list_ok is None: - return [] - defaults = {name: None for name, _ in _PROFILE_FIELDS.values()} - return [{**defaults, **_decode_profile_fields(value)} for tag, value in iter_tlv(list_ok) if tag == 0xE3] - - -def list_profiles(client: AtClient) -> list[dict]: - return decode_profiles(es10x_command(client, TAG_PROFILE_INFO_LIST.to_bytes(2, "big") + b"\x00")) - - class TiciLPA(LPABase): - _instance = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - def __init__(self): - if hasattr(self, '_client'): - return - self._client = AtClient(DEFAULT_DEVICE, DEFAULT_BAUD, DEFAULT_TIMEOUT, debug=DEBUG) - self._client.open_isdr() - atexit.register(self._client.close) + pass def list_profiles(self) -> list[Profile]: - return [ - Profile( - iccid=p.get("iccid", ""), - nickname=p.get("profileNickname") or "", - enabled=p.get("profileState") == "enabled", - provider=p.get("serviceProviderName") or "", - ) - for p in list_profiles(self._client) - ] + return [] def get_active_profile(self) -> Profile | None: return None From afa9ec1138b398508ef9062f7a9c42a1d3c50acf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 16:27:59 -0800 Subject: [PATCH 302/685] bump panda: vendored toolchain (#37324) * bump panda: vendored toolchain * add * bump panda --- Dockerfile.openpilot_base | 4 +--- panda | 2 +- pyproject.toml | 1 + tools/install_ubuntu_dependencies.sh | 1 - tools/mac_setup.sh | 1 - uv.lock | 7 +++++++ 6 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 8a604129938..6ea1450bee4 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -14,9 +14,7 @@ ENV LC_ALL=en_US.UTF-8 COPY tools/install_ubuntu_dependencies.sh /tmp/tools/ RUN /tmp/tools/install_ubuntu_dependencies.sh && \ - rm -rf /var/lib/apt/lists/* /tmp/* && \ - cd /usr/lib/gcc/arm-none-eabi/* && \ - rm -rf arm/ thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp + rm -rf /var/lib/apt/lists/* /tmp/* ENV NVIDIA_VISIBLE_DEVICES=all ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute diff --git a/panda b/panda index e1da7dc918c..49f72e931f0 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e1da7dc918c0bcda6fbbdd9ee6f89c5428ec5039 +Subproject commit 49f72e931f09ceecb00c0c7937808fcaeecd3c17 diff --git a/pyproject.toml b/pyproject.toml index 5aeb2ffab4a..cc56eb3594f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -90,6 +90,7 @@ testing = [ dev = [ "matplotlib", "opencv-python-headless", + "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", ] tools = [ diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index aa98102d490..06ab8fda3a6 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -36,7 +36,6 @@ function install_ubuntu_common_requirements() { # TODO: vendor the rest of these in third_party/ $SUDO apt-get install -y --no-install-recommends \ - gcc-arm-none-eabi \ capnproto \ libcapnp-dev \ ffmpeg \ diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 3f13cbe74aa..dece4397523 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -35,7 +35,6 @@ brew "ffmpeg" brew "libusb" brew "llvm" brew "zeromq" -cask "gcc-arm-embedded" brew "portaudio" EOS diff --git a/uv.lock b/uv.lock index 8b8bfd639ba..aa03f6d5e29 100644 --- a/uv.lock +++ b/uv.lock @@ -416,6 +416,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9a/9a/e35b4a917281c0b8419d4207f4334c8e8c5dbf4f3f5f9ada73958d937dcc/frozenlist-1.8.0-py3-none-any.whl", hash = "sha256:0c18a16eab41e82c295618a77502e17b195883241c563b00f0aa5106fc4eaa0d", size = 13409, upload-time = "2025-10-06T05:38:16.721Z" }, ] +[[package]] +name = "gcc-arm-none-eabi" +version = "13.2.1" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#e769f658aad6ab46e98490bf0800e69b99e22f7a" } + [[package]] name = "ghp-import" version = "2.1.0" @@ -778,6 +783,7 @@ dependencies = [ [package.optional-dependencies] dev = [ + { name = "gcc-arm-none-eabi" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, ] @@ -816,6 +822,7 @@ requires-dist = [ { name = "crcmod-plus" }, { name = "cython" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, + { name = "gcc-arm-none-eabi", marker = "extra == 'dev'", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, From f881a9ba681de3fddf478bfe781cd0a7c885a12f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 19:00:29 -0800 Subject: [PATCH 303/685] rm vendor building workflow --- .github/workflows/vendor_third_party.yaml | 51 ----------------------- 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/vendor_third_party.yaml diff --git a/.github/workflows/vendor_third_party.yaml b/.github/workflows/vendor_third_party.yaml deleted file mode 100644 index df50cfad233..00000000000 --- a/.github/workflows/vendor_third_party.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: vendor third_party - -on: - workflow_dispatch: - -jobs: - build: - if: github.ref != 'refs/heads/master' - strategy: - matrix: - os: [ubuntu-24.04, macos-latest] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - name: Build - run: third_party/build.sh - - name: Package artifacts - run: | - git add -A third_party/ - git diff --cached --name-only -- third_party/ | tar -cf /tmp/third_party_build.tar -T - - - uses: actions/upload-artifact@v4 - with: - name: third-party-${{ runner.os }} - path: /tmp/third_party_build.tar - - commit: - needs: build - runs-on: ubuntu-24.04 - permissions: - contents: write - steps: - - uses: actions/checkout@v6 - - uses: actions/download-artifact@v4 - with: - path: /tmp/artifacts - - name: Commit vendored libraries - run: | - for f in /tmp/artifacts/*/third_party_build.tar; do - tar xf "$f" - done - git add third_party/ - if git diff --cached --quiet; then - echo "No changes to commit" - exit 0 - fi - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git commit -m "third_party: rebuild vendor libraries" - git push From 4bffe422e4304d590566947d353a548a14867b9e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 19:15:11 -0800 Subject: [PATCH 304/685] vendor capnproto and ffmpeg via dependencies repo (#37327) --- SConstruct | 10 ++++++++++ pyproject.toml | 4 ++++ system/loggerd/SConscript | 4 ++-- tools/cabana/SConscript | 2 +- tools/install_ubuntu_dependencies.sh | 7 ------- tools/mac_setup.sh | 2 -- tools/replay/SConscript | 2 +- uv.lock | 16 +++++++++++++++- 8 files changed, 33 insertions(+), 14 deletions(-) diff --git a/SConstruct b/SConstruct index 3b8aadf9140..3d611af0b20 100644 --- a/SConstruct +++ b/SConstruct @@ -38,6 +38,14 @@ assert arch in [ "Darwin", # macOS arm64 (x86 not supported) ] +if arch != "larch64": + import capnproto + import ffmpeg as ffmpeg_pkg + pkgs = [capnproto, ffmpeg_pkg] +else: + # TODO: remove when AGNOS has our new vendor pkgs + pkgs = [] + env = Environment( ENV={ "PATH": os.environ['PATH'], @@ -74,6 +82,7 @@ env = Environment( "#third_party/acados/include/hpipm/include", "#third_party/catch2/include", "#third_party/libyuv/include", + [x.INCLUDE_DIR for x in pkgs], ], LIBPATH=[ "#common", @@ -83,6 +92,7 @@ env = Environment( "#rednose/helpers", f"#third_party/libyuv/{arch}/lib", f"#third_party/acados/{arch}/lib", + [x.LIB_DIR for x in pkgs], ], RPATH=[], CYTHONCFILESUFFIX=".cpp", diff --git a/pyproject.toml b/pyproject.toml index cc56eb3594f..93d7b380b17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,10 @@ dependencies = [ "setuptools", "numpy >=2.0", + # vendored native dependencies + "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", + "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", + # body / webrtcd "av", "aiohttp", diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index cc8ef7c88f6..fecf448855a 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -1,8 +1,8 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, - 'avformat', 'avcodec', 'avutil', - 'yuv', 'pthread', 'zstd'] + 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', + 'yuv', 'pthread', 'z', 'm', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 025797d1e37..8ea59d0766d 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -75,7 +75,7 @@ qt_libs = base_libs cabana_env = qt_env.Clone() -cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 06ab8fda3a6..e4b8a645595 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -34,14 +34,7 @@ function install_ubuntu_common_requirements() { git-lfs \ xvfb - # TODO: vendor the rest of these in third_party/ $SUDO apt-get install -y --no-install-recommends \ - capnproto \ - libcapnp-dev \ - ffmpeg \ - libavformat-dev \ - libavcodec-dev \ - libavutil-dev \ libbz2-dev \ libeigen3-dev \ libgles2-mesa-dev \ diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index dece4397523..e2ede7fbb8c 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -28,10 +28,8 @@ fi brew bundle --file=- <<-EOS brew "git-lfs" -brew "capnp" brew "coreutils" brew "eigen" -brew "ffmpeg" brew "libusb" brew "llvm" brew "zeromq" diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 698ab9885d9..b39cf6dab16 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -12,7 +12,7 @@ if arch != "Darwin": replay_lib_src.append("qcom_decoder.cc") replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') -replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs +replay_libs = [replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/uv.lock b/uv.lock index aa03f6d5e29..9bfbd05dd56 100644 --- a/uv.lock +++ b/uv.lock @@ -119,6 +119,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/ff/48fa68888b8d5bae36d915556ff18f9e5fdc6b5ff5ae23dc4904c9713168/av-13.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ea0deab0e6a739cb742fba2a3983d8102f7516a3cdf3c46669f3cac0ed1f351", size = 25781343, upload-time = "2024-10-06T04:53:29.577Z" }, ] +[[package]] +name = "capnproto" +version = "1.0.1" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#acf53363042d92d3206b8ce6b9da4bfb75b200c7" } + [[package]] name = "casadi" version = "3.7.2" @@ -374,6 +379,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ab/84/02fc1827e8cdded4aa65baef11296a9bbe595c474f0d6d758af082d849fd/execnet-2.1.2-py3-none-any.whl", hash = "sha256:67fba928dd5a544b783f6056f449e5e3931a5c378b128bc18501f7ea79e296ec", size = 40708, upload-time = "2025-11-12T09:56:36.333Z" }, ] +[[package]] +name = "ffmpeg" +version = "7.1.0" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#acf53363042d92d3206b8ce6b9da4bfb75b200c7" } + [[package]] name = "fonttools" version = "4.61.1" @@ -419,7 +429,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#e769f658aad6ab46e98490bf0800e69b99e22f7a" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#acf53363042d92d3206b8ce6b9da4bfb75b200c7" } [[package]] name = "ghp-import" @@ -749,10 +759,12 @@ dependencies = [ { name = "aiohttp" }, { name = "aiortc" }, { name = "av" }, + { name = "capnproto" }, { name = "casadi" }, { name = "cffi" }, { name = "crcmod-plus" }, { name = "cython" }, + { name = "ffmpeg" }, { name = "inputs" }, { name = "jeepney" }, { name = "json-rpc" }, @@ -815,6 +827,7 @@ requires-dist = [ { name = "aiohttp" }, { name = "aiortc" }, { name = "av" }, + { name = "capnproto", git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases" }, { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, @@ -822,6 +835,7 @@ requires-dist = [ { name = "crcmod-plus" }, { name = "cython" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, + { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases" }, { name = "gcc-arm-none-eabi", marker = "extra == 'dev'", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, From fa2050ab1a34fcaeeced00ba723be41b1b3a4b06 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 19:21:56 -0800 Subject: [PATCH 305/685] rm unused dependencies (#37329) ok just libusb --- tools/install_ubuntu_dependencies.sh | 1 - tools/mac_setup.sh | 1 - 2 files changed, 2 deletions(-) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index e4b8a645595..0c9fada7575 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -40,7 +40,6 @@ function install_ubuntu_common_requirements() { libgles2-mesa-dev \ libjpeg-dev \ libncurses5-dev \ - libusb-1.0-0-dev \ libzmq3-dev \ libzstd-dev \ portaudio19-dev \ diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index e2ede7fbb8c..4a66beeec90 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -30,7 +30,6 @@ brew bundle --file=- <<-EOS brew "git-lfs" brew "coreutils" brew "eigen" -brew "libusb" brew "llvm" brew "zeromq" brew "portaudio" From f9114931772772d8496df8981b038b198c93b586 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 19:30:24 -0800 Subject: [PATCH 306/685] rm pyaudio (#37331) * rm pyaudio * those too --- pyproject.toml | 1 - system/webrtc/device/audio.py | 109 --------------------- system/webrtc/tests/test_stream_session.py | 17 ---- system/webrtc/webrtcd.py | 21 +--- tools/bodyteleop/web.py | 43 +------- tools/install_ubuntu_dependencies.sh | 1 - tools/mac_setup.sh | 1 - uv.lock | 12 --- 8 files changed, 4 insertions(+), 201 deletions(-) delete mode 100644 system/webrtc/device/audio.py diff --git a/pyproject.toml b/pyproject.toml index 93d7b380b17..508a6230b25 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,6 @@ dependencies = [ "av", "aiohttp", "aiortc", - "pyaudio", # panda "libusb1", diff --git a/system/webrtc/device/audio.py b/system/webrtc/device/audio.py deleted file mode 100644 index b1859518a17..00000000000 --- a/system/webrtc/device/audio.py +++ /dev/null @@ -1,109 +0,0 @@ -import asyncio -import io - -import aiortc -import av -import numpy as np -import pyaudio - - -class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): - PYAUDIO_TO_AV_FORMAT_MAP = { - pyaudio.paUInt8: 'u8', - pyaudio.paInt16: 's16', - pyaudio.paInt24: 's24', - pyaudio.paInt32: 's32', - pyaudio.paFloat32: 'flt', - } - - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: int | None = None): - super().__init__() - - self.p = pyaudio.PyAudio() - chunk_size = int(packet_time * rate) - self.stream = self.p.open(format=audio_format, - channels=channels, - rate=rate, - frames_per_buffer=chunk_size, - input=True, - input_device_index=device_index) - self.format = audio_format - self.rate = rate - self.channels = channels - self.packet_time = packet_time - self.chunk_size = chunk_size - self.pts = 0 - - async def recv(self): - mic_data = self.stream.read(self.chunk_size) - mic_array = np.frombuffer(mic_data, dtype=np.int16) - mic_array = np.expand_dims(mic_array, axis=0) - layout = 'stereo' if self.channels > 1 else 'mono' - frame = av.AudioFrame.from_ndarray(mic_array, format=self.PYAUDIO_TO_AV_FORMAT_MAP[self.format], layout=layout) - frame.rate = self.rate - frame.pts = self.pts - self.pts += frame.samples - - return frame - - -class AudioOutputSpeaker: - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: int | None = None): - - chunk_size = int(packet_time * rate) - self.p = pyaudio.PyAudio() - self.buffer = io.BytesIO() - self.channels = channels - self.stream = self.p.open(format=audio_format, - channels=channels, - rate=rate, - frames_per_buffer=chunk_size, - output=True, - output_device_index=device_index, - stream_callback=self.__pyaudio_callback) - self.tracks_and_tasks: list[tuple[aiortc.MediaStreamTrack, asyncio.Task | None]] = [] - - def __pyaudio_callback(self, in_data, frame_count, time_info, status): - if self.buffer.getbuffer().nbytes < frame_count * self.channels * 2: - buff = b'\x00\x00' * frame_count * self.channels - elif self.buffer.getbuffer().nbytes > 115200: # 3x the usual read size - self.buffer.seek(0) - buff = self.buffer.read(frame_count * self.channels * 4) - buff = buff[:frame_count * self.channels * 2] - self.buffer.seek(2) - else: - self.buffer.seek(0) - buff = self.buffer.read(frame_count * self.channels * 2) - self.buffer.seek(2) - return (buff, pyaudio.paContinue) - - async def __consume(self, track): - while True: - try: - frame = await track.recv() - except aiortc.MediaStreamError: - return - - self.buffer.write(bytes(frame.planes[0])) - - def hasTrack(self, track: aiortc.MediaStreamTrack) -> bool: - return any(t == track for t, _ in self.tracks_and_tasks) - - def addTrack(self, track: aiortc.MediaStreamTrack): - if not self.hasTrack(track): - self.tracks_and_tasks.append((track, None)) - - def start(self): - for index, (track, task) in enumerate(self.tracks_and_tasks): - if task is None: - self.tracks_and_tasks[index] = (track, asyncio.create_task(self.__consume(track))) - - def stop(self): - for _, task in self.tracks_and_tasks: - if task is not None: - task.cancel() - - self.tracks_and_tasks = [] - self.stream.stop_stream() - self.stream.close() - self.p.terminate() diff --git a/system/webrtc/tests/test_stream_session.py b/system/webrtc/tests/test_stream_session.py index e31fda37286..f44d217d58c 100644 --- a/system/webrtc/tests/test_stream_session.py +++ b/system/webrtc/tests/test_stream_session.py @@ -9,12 +9,10 @@ from aiortc import RTCDataChannel from aiortc.mediastreams import VIDEO_CLOCK_RATE, VIDEO_TIME_BASE import capnp -import pyaudio from cereal import messaging, log from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIncomingMessageProxy from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack -from openpilot.system.webrtc.device.audio import AudioInputStreamTrack class TestStreamSession: @@ -87,18 +85,3 @@ def test_livestream_track(self, mocker): assert abs(i + packet.pts - (start_pts + (((time.monotonic_ns() - start_ns) * VIDEO_CLOCK_RATE) // 1_000_000_000))) < 450 #5ms assert packet.size == 0 - def test_input_audio_track(self, mocker): - packet_time, rate = 0.02, 16000 - sample_count = int(packet_time * rate) - mocked_stream = mocker.MagicMock(spec=pyaudio.Stream) - mocked_stream.read.return_value = b"\x00" * 2 * sample_count - - config = {"open.side_effect": lambda *args, **kwargs: mocked_stream} - mocker.patch("pyaudio.PyAudio", spec=True, **config) - track = AudioInputStreamTrack(audio_format=pyaudio.paInt16, packet_time=packet_time, rate=rate) - - for i in range(5): - frame = self.loop.run_until_complete(track.recv()) - assert frame.rate == rate - assert frame.samples == sample_count - assert frame.pts == i * sample_count diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index c19f1bf9dd6..d2c90cafb5b 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -119,10 +119,8 @@ class StreamSession: shared_pub_master = DynamicPubMaster([]) def __init__(self, sdp: str, cameras: list[str], incoming_services: list[str], outgoing_services: list[str], debug_mode: bool = False): - from aiortc.mediastreams import VideoStreamTrack, AudioStreamTrack - from aiortc.contrib.media import MediaBlackhole + from aiortc.mediastreams import VideoStreamTrack from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack - from openpilot.system.webrtc.device.audio import AudioInputStreamTrack, AudioOutputSpeaker from teleoprtc import WebRTCAnswerBuilder from teleoprtc.info import parse_info_from_offer @@ -132,11 +130,6 @@ def __init__(self, sdp: str, cameras: list[str], incoming_services: list[str], o assert len(cameras) == config.n_expected_camera_tracks, "Incoming stream has misconfigured number of video tracks" for cam in cameras: builder.add_video_stream(cam, LiveStreamVideoStreamTrack(cam) if not debug_mode else VideoStreamTrack()) - if config.expected_audio_track: - builder.add_audio_stream(AudioInputStreamTrack() if not debug_mode else AudioStreamTrack()) - if config.incoming_audio_track: - self.audio_output_cls = AudioOutputSpeaker if not debug_mode else MediaBlackhole - builder.offer_to_receive_audio_stream() self.stream = builder.stream() self.identifier = str(uuid.uuid4()) @@ -151,11 +144,10 @@ def __init__(self, sdp: str, cameras: list[str], incoming_services: list[str], o self.outgoing_bridge = CerealOutgoingMessageProxy(messaging.SubMaster(outgoing_services)) self.outgoing_bridge_runner = CerealProxyRunner(self.outgoing_bridge) - self.audio_output: AudioOutputSpeaker | MediaBlackhole | None = None self.run_task: asyncio.Task | None = None self.logger = logging.getLogger("webrtcd") - self.logger.info("New stream session (%s), cameras %s, audio in %s out %s, incoming services %s, outgoing services %s", - self.identifier, cameras, config.incoming_audio_track, config.expected_audio_track, incoming_services, outgoing_services) + self.logger.info("New stream session (%s), cameras %s, incoming services %s, outgoing services %s", + self.identifier, cameras, incoming_services, outgoing_services) def start(self): self.run_task = asyncio.create_task(self.run()) @@ -188,11 +180,6 @@ async def run(self): channel = self.stream.get_messaging_channel() self.outgoing_bridge_runner.proxy.add_channel(channel) self.outgoing_bridge_runner.start() - if self.stream.has_incoming_audio_track(): - track = self.stream.get_incoming_audio_track(buffered=False) - self.audio_output = self.audio_output_cls() - self.audio_output.addTrack(track) - self.audio_output.start() self.logger.info("Stream session (%s) connected", self.identifier) await self.stream.wait_for_disconnection() @@ -206,8 +193,6 @@ async def post_run_cleanup(self): await self.stream.stop() if self.outgoing_bridge is not None: self.outgoing_bridge_runner.stop() - if self.audio_output: - self.audio_output.stop() @dataclass diff --git a/tools/bodyteleop/web.py b/tools/bodyteleop/web.py index fd8f691d199..f91d6a1441c 100644 --- a/tools/bodyteleop/web.py +++ b/tools/bodyteleop/web.py @@ -1,4 +1,3 @@ -import asyncio import dataclasses import json import logging @@ -6,8 +5,6 @@ import ssl import subprocess -import pyaudio -import wave from aiohttp import web from aiohttp import ClientSession @@ -22,35 +19,6 @@ WEBRTCD_HOST, WEBRTCD_PORT = "localhost", 5001 -## UTILS -async def play_sound(sound: str): - SOUNDS = { - "engage": "selfdrive/assets/sounds/engage.wav", - "disengage": "selfdrive/assets/sounds/disengage.wav", - "error": "selfdrive/assets/sounds/warning_immediate.wav", - } - assert sound in SOUNDS - - chunk = 5120 - with wave.open(os.path.join(BASEDIR, SOUNDS[sound]), "rb") as wf: - def callback(in_data, frame_count, time_info, status): - data = wf.readframes(frame_count) - return data, pyaudio.paContinue - - p = pyaudio.PyAudio() - stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), - channels=wf.getnchannels(), - rate=wf.getframerate(), - output=True, - frames_per_buffer=chunk, - stream_callback=callback) - stream.start_stream() - while stream.is_active(): - await asyncio.sleep(0) - stream.stop_stream() - stream.close() - p.terminate() - ## SSL def create_ssl_cert(cert_path: str, key_path: str): try: @@ -86,14 +54,6 @@ async def ping(request: 'web.Request'): return web.Response(text="pong") -async def sound(request: 'web.Request'): - params = await request.json() - sound_to_play = params["sound"] - - await play_sound(sound_to_play) - return web.json_response({"status": "ok"}) - - async def offer(request: 'web.Request'): params = await request.json() body = StreamRequestBody(params["sdp"], ["driver"], ["testJoystick"], ["carState"]) @@ -111,14 +71,13 @@ def main(): # Enable joystick debug mode Params().put_bool("JoystickDebugMode", True) - # App needs to be HTTPS for microphone and audio autoplay to work on the browser + # App needs to be HTTPS for WebRTC to work on the browser ssl_context = create_ssl_context() app = web.Application() app.router.add_get("/", index) app.router.add_get("/ping", ping, allow_head=True) app.router.add_post("/offer", offer) - app.router.add_post("/sound", sound) app.router.add_static('/static', os.path.join(TELEOPDIR, 'static')) web.run_app(app, access_log=None, host="0.0.0.0", port=5000, ssl_context=ssl_context) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 0c9fada7575..26fb45f0524 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -42,7 +42,6 @@ function install_ubuntu_common_requirements() { libncurses5-dev \ libzmq3-dev \ libzstd-dev \ - portaudio19-dev \ gettext } diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 4a66beeec90..3fe5cc59bfb 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -32,7 +32,6 @@ brew "coreutils" brew "eigen" brew "llvm" brew "zeromq" -brew "portaudio" EOS echo "[ ] finished brew install t=$SECONDS" diff --git a/uv.lock b/uv.lock index 9bfbd05dd56..fa1c4f61b6d 100644 --- a/uv.lock +++ b/uv.lock @@ -771,7 +771,6 @@ dependencies = [ { name = "libusb1" }, { name = "numpy" }, { name = "psutil" }, - { name = "pyaudio" }, { name = "pycapnp" }, { name = "pycryptodome" }, { name = "pyjwt" }, @@ -850,7 +849,6 @@ requires-dist = [ { name = "opencv-python-headless", marker = "extra == 'dev'" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, - { name = "pyaudio" }, { name = "pycapnp" }, { name = "pycryptodome" }, { name = "pyjwt" }, @@ -1027,16 +1025,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] -[[package]] -name = "pyaudio" -version = "0.2.14" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/26/1d/8878c7752febb0f6716a7e1a52cb92ac98871c5aa522cba181878091607c/PyAudio-0.2.14.tar.gz", hash = "sha256:78dfff3879b4994d1f4fc6485646a57755c6ee3c19647a491f790a0895bd2f87", size = 47066, upload-time = "2023-11-07T07:11:48.806Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/45/8d2b76e8f6db783f9326c1305f3f816d4a12c8eda5edc6a2e1d03c097c3b/PyAudio-0.2.14-cp312-cp312-win32.whl", hash = "sha256:5fce4bcdd2e0e8c063d835dbe2860dac46437506af509353c7f8114d4bacbd5b", size = 144750, upload-time = "2023-11-07T07:11:40.142Z" }, - { url = "https://files.pythonhosted.org/packages/b0/6a/d25812e5f79f06285767ec607b39149d02aa3b31d50c2269768f48768930/PyAudio-0.2.14-cp312-cp312-win_amd64.whl", hash = "sha256:12f2f1ba04e06ff95d80700a78967897a489c05e093e3bffa05a84ed9c0a7fa3", size = 164126, upload-time = "2023-11-07T07:11:41.539Z" }, -] - [[package]] name = "pycapnp" version = "2.1.0" From f4a36f7f743f2fb3499af4ab6769a8819150917f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 19:37:14 -0800 Subject: [PATCH 307/685] rm cpp bz2 (#37332) --- tools/cabana/SConscript | 2 +- tools/cabana/tests/test_cabana.cc | 2 +- tools/replay/SConscript | 2 +- tools/replay/logreader.cc | 4 +-- tools/replay/route.cc | 4 +-- tools/replay/tests/test_replay.cc | 4 +-- tools/replay/util.cc | 42 ------------------------------- tools/replay/util.h | 2 -- 8 files changed, 8 insertions(+), 54 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 8ea59d0766d..90de2126557 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -75,7 +75,7 @@ qt_libs = base_libs cabana_env = qt_env.Clone() -cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index d9fcae6f21a..4c11bfc8b87 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -5,7 +5,7 @@ #include "catch2/catch.hpp" #include "tools/cabana/dbc/dbcmanager.h" -const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; +const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.zst"; TEST_CASE("DBCFile::generateDBC") { QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "tesla_can"); diff --git a/tools/replay/SConscript b/tools/replay/SConscript index b39cf6dab16..47b25df1660 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -12,7 +12,7 @@ if arch != "Darwin": replay_lib_src.append("qcom_decoder.cc") replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') -replay_libs = [replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs +replay_libs = [replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/replay/logreader.cc b/tools/replay/logreader.cc index 75abb8417b5..0d9e053aba5 100644 --- a/tools/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -9,9 +9,7 @@ bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { std::string data = FileReader(local_cache, chunk_size, retries).read(url, abort); if (!data.empty()) { - if (url.find(".bz2") != std::string::npos || util::starts_with(data, "BZh9")) { - data = decompressBZ2(data, abort); - } else if (url.find(".zst") != std::string::npos || util::starts_with(data, "\x28\xB5\x2F\xFD")) { + if (url.find(".zst") != std::string::npos || util::starts_with(data, "\x28\xB5\x2F\xFD")) { data = decompressZST(data, abort); } } diff --git a/tools/replay/route.cc b/tools/replay/route.cc index ba008282675..663c4b43cb1 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -174,9 +174,9 @@ void Route::addFileToSegment(int n, const std::string &file) { auto pos = name.find_last_of("--"); name = pos != std::string::npos ? name.substr(pos + 2) : name; - if (name == "rlog.bz2" || name == "rlog.zst" || name == "rlog") { + if (name == "rlog.zst" || name == "rlog") { segments_[n].rlog = file; - } else if (name == "qlog.bz2" || name == "qlog.zst" || name == "qlog") { + } else if (name == "qlog.zst" || name == "qlog") { segments_[n].qlog = file; } else if (name == "fcamera.hevc") { segments_[n].road_cam = file; diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index aed3de59a8c..f4afc29968c 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -2,14 +2,14 @@ #include "catch2/catch.hpp" #include "tools/replay/replay.h" -const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; +const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.zst"; TEST_CASE("LogReader") { SECTION("corrupt log") { FileReader reader(true); std::string corrupt_content = reader.read(TEST_RLOG_URL); corrupt_content.resize(corrupt_content.length() / 2); - corrupt_content = decompressBZ2(corrupt_content); + corrupt_content = decompressZST(corrupt_content); LogReader log; REQUIRE(log.load(corrupt_content.data(), corrupt_content.size())); REQUIRE(log.events.size() > 0); diff --git a/tools/replay/util.cc b/tools/replay/util.cc index 481564322e4..cc37c19ecf7 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -1,6 +1,5 @@ #include "tools/replay/util.h" -#include #include #include @@ -280,47 +279,6 @@ bool httpDownload(const std::string &url, const std::string &file, size_t chunk_ return httpDownload(url, of, chunk_size, size, abort); } -std::string decompressBZ2(const std::string &in, std::atomic *abort) { - return decompressBZ2((std::byte *)in.data(), in.size(), abort); -} - -std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort) { - if (in_size == 0) return {}; - - bz_stream strm = {}; - int bzerror = BZ2_bzDecompressInit(&strm, 0, 0); - assert(bzerror == BZ_OK); - - strm.next_in = (char *)in; - strm.avail_in = in_size; - std::string out(in_size * 5, '\0'); - do { - strm.next_out = (char *)(&out[strm.total_out_lo32]); - strm.avail_out = out.size() - strm.total_out_lo32; - - const char *prev_write_pos = strm.next_out; - bzerror = BZ2_bzDecompress(&strm); - if (bzerror == BZ_OK && prev_write_pos == strm.next_out) { - // content is corrupt - bzerror = BZ_STREAM_END; - rWarning("decompressBZ2 error: content is corrupt"); - break; - } - - if (bzerror == BZ_OK && strm.avail_in > 0 && strm.avail_out == 0) { - out.resize(out.size() * 2); - } - } while (bzerror == BZ_OK && !(abort && *abort)); - - BZ2_bzDecompressEnd(&strm); - if (bzerror == BZ_STREAM_END && !(abort && *abort)) { - out.resize(strm.total_out_lo32); - out.shrink_to_fit(); - return out; - } - return {}; -} - std::string decompressZST(const std::string &in, std::atomic *abort) { return decompressZST((std::byte *)in.data(), in.size(), abort); } diff --git a/tools/replay/util.h b/tools/replay/util.h index 1f61951d213..fc4d2d54f95 100644 --- a/tools/replay/util.h +++ b/tools/replay/util.h @@ -48,8 +48,6 @@ class MonotonicBuffer { std::string sha256(const std::string &str); void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_requested); -std::string decompressBZ2(const std::string &in, std::atomic *abort = nullptr); -std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); std::string decompressZST(const std::string &in, std::atomic *abort = nullptr); std::string decompressZST(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); std::string getUrlWithoutQuery(const std::string &url); From cef81da1e936a6ef88f2bb3d1241b081b2666def Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 19:59:05 -0800 Subject: [PATCH 308/685] use vendored zeromq from dependencies repo (#37333) * use vendored zeromq from dependencies repo Co-Authored-By: Claude Opus 4.6 * lock * rm more crap --------- Co-authored-by: Claude Opus 4.6 --- SConstruct | 3 +- pyproject.toml | 1 + tools/install_ubuntu_dependencies.sh | 1 - tools/mac_setup.sh | 9 -- uv.lock | 185 ++++++++++++++------------- 5 files changed, 102 insertions(+), 97 deletions(-) diff --git a/SConstruct b/SConstruct index 3d611af0b20..148c119066a 100644 --- a/SConstruct +++ b/SConstruct @@ -41,7 +41,8 @@ assert arch in [ if arch != "larch64": import capnproto import ffmpeg as ffmpeg_pkg - pkgs = [capnproto, ffmpeg_pkg] + import zeromq + pkgs = [capnproto, ffmpeg_pkg, zeromq] else: # TODO: remove when AGNOS has our new vendor pkgs pkgs = [] diff --git a/pyproject.toml b/pyproject.toml index 508a6230b25..71e40e39142 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dependencies = [ # vendored native dependencies "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", + "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", # body / webrtcd "av", diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 26fb45f0524..7d24b6c09a3 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -40,7 +40,6 @@ function install_ubuntu_common_requirements() { libgles2-mesa-dev \ libjpeg-dev \ libncurses5-dev \ - libzmq3-dev \ libzstd-dev \ gettext } diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 3fe5cc59bfb..c4eee0f67da 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -31,19 +31,10 @@ brew "git-lfs" brew "coreutils" brew "eigen" brew "llvm" -brew "zeromq" EOS echo "[ ] finished brew install t=$SECONDS" -BREW_PREFIX=$(brew --prefix) - -# archive backend tools for pip dependencies -export LDFLAGS="$LDFLAGS -L${BREW_PREFIX}/opt/zlib/lib" -export LDFLAGS="$LDFLAGS -L${BREW_PREFIX}/opt/bzip2/lib" -export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/zlib/include" -export CPPFLAGS="$CPPFLAGS -I${BREW_PREFIX}/opt/bzip2/include" - # install python dependencies $DIR/install_python_dependencies.sh echo "[ ] installed python dependencies t=$SECONDS" diff --git a/uv.lock b/uv.lock index fa1c4f61b6d..3327ba4dbed 100644 --- a/uv.lock +++ b/uv.lock @@ -60,27 +60,20 @@ wheels = [ [[package]] name = "aiortc" -version = "1.10.1" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aioice" }, { name = "av" }, - { name = "cffi" }, { name = "cryptography" }, { name = "google-crc32c" }, { name = "pyee" }, { name = "pylibsrtp" }, { name = "pyopenssl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/f8/408e092748521889c9d33dddcef920afd9891cf6db4615ba6b6bfe114ff8/aiortc-1.10.1.tar.gz", hash = "sha256:64926ad86bde20c1a4dacb7c3a164e57b522606b70febe261fada4acf79641b5", size = 1179406, upload-time = "2025-02-02T17:36:38.684Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/9c/4e027bfe0195de0442da301e2389329496745d40ae44d2d7c4571c4290ce/aiortc-1.14.0.tar.gz", hash = "sha256:adc8a67ace10a085721e588e06a00358ed8eaf5f6b62f0a95358ff45628dd762", size = 1180864, upload-time = "2025-10-13T21:40:37.905Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/6b/74547a30d1ddcc81f905ef4ff7fcc2c89b7482cb2045688f2aaa4fa918aa/aiortc-1.10.1-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3bef536f38394b518aefae9dbf9cdd08f39e4c425f316f9692f0d8dc724810bd", size = 1218457, upload-time = "2025-02-02T17:36:23.172Z" }, - { url = "https://files.pythonhosted.org/packages/46/92/b4ccf39cd18e366ace2a11dc7d98ed55967b4b325707386b5788149db15e/aiortc-1.10.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8842c02e38513d9432ef22982572833487bb015f23348fa10a690616dbf55143", size = 898855, upload-time = "2025-02-02T17:36:25.9Z" }, - { url = "https://files.pythonhosted.org/packages/a4/e9/2676de48b493787d8b03129713e6bb2dfbacca2a565090f2a89cbad71f96/aiortc-1.10.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:954a420de01c0bf6b07a0c58b662029b1c4204ddbd8f5c4162bbdebd43f882b1", size = 1750403, upload-time = "2025-02-02T17:36:28.446Z" }, - { url = "https://files.pythonhosted.org/packages/c3/9d/ab6d09183cdaf5df060923d9bd5c9ed5fb1802661d9401dba35f3c85a57b/aiortc-1.10.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7c0d46fb30307a9d7deb4b7d66f0b0e73b77a7221b063fb6dc78821a5d2aa1e", size = 1867886, upload-time = "2025-02-02T17:36:30.209Z" }, - { url = "https://files.pythonhosted.org/packages/c2/71/0b5666e6b965dbd9a7f331aa827a6c3ab3eb4d582fefb686a7f4227b7954/aiortc-1.10.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89582f6923046f79f15d9045f432bc78191eacc95f6bed18714e86ec935188d9", size = 1893709, upload-time = "2025-02-02T17:36:32.342Z" }, - { url = "https://files.pythonhosted.org/packages/9d/0a/8c0c78fad79ef595a0ed6e2ab413900e6bd0eac65fc5c31c9d8736bff909/aiortc-1.10.1-cp39-abi3-win32.whl", hash = "sha256:d1cbe87f740b33ffaa8e905f21092773e74916be338b64b81c8b79af4c3847eb", size = 923265, upload-time = "2025-02-02T17:36:34.685Z" }, - { url = "https://files.pythonhosted.org/packages/73/12/a27dd588a4988021da88cb4d338d8ee65ac097afc14e9193ab0be4a48790/aiortc-1.10.1-cp39-abi3-win_amd64.whl", hash = "sha256:c9a5a0b23f8a77540068faec8837fa0a65b0396c20f09116bdb874b75e0b6abe", size = 1009488, upload-time = "2025-02-02T17:36:36.317Z" }, + { url = "https://files.pythonhosted.org/packages/57/ab/31646a49209568cde3b97eeade0d28bb78b400e6645c56422c101df68932/aiortc-1.14.0-py3-none-any.whl", hash = "sha256:4b244d7e482f4e1f67e685b3468269628eca1ec91fa5b329ab517738cfca086e", size = 93183, upload-time = "2025-10-13T21:40:36.59Z" }, ] [[package]] @@ -107,22 +100,23 @@ wheels = [ [[package]] name = "av" -version = "13.1.0" +version = "16.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/9d/486d31e76784cc0ad943f420c5e05867263b32b37e2f4b0f7f22fdc1ca3a/av-13.1.0.tar.gz", hash = "sha256:d3da736c55847d8596eb8c26c60e036f193001db3bc5c10da8665622d906c17e", size = 3957908, upload-time = "2024-10-06T04:54:57.507Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/cd/3a83ffbc3cc25b39721d174487fb0d51a76582f4a1703f98e46170ce83d4/av-16.1.0.tar.gz", hash = "sha256:a094b4fd87a3721dacf02794d3d2c82b8d712c85b9534437e82a8a978c175ffd", size = 4285203, upload-time = "2026-01-11T07:31:33.772Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/aa/4bdd8ce59173574fc6e0c282c71ee6f96fca82643d97bf172bc4cb5a5674/av-13.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:261dbc3f4b55f4f8f3375b10b2258fca7f2ab7a6365c01bc65e77a0d5327a195", size = 24268674, upload-time = "2024-10-06T04:53:11.251Z" }, - { url = "https://files.pythonhosted.org/packages/17/b4/b267dd5bad99eed49ec6731827c6bcb5ab03864bf732a7ebb81e3df79911/av-13.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83d259ef86b9054eb914bc7c6a7f6092a6d75cb939295e70ee979cfd92a67b99", size = 19475617, upload-time = "2024-10-06T04:53:13.832Z" }, - { url = "https://files.pythonhosted.org/packages/68/32/4209e51f54d7b54a1feb576d309c671ed1ff437b54fcc4ec68c239199e0a/av-13.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3b4d3ca159eceab97e3c0fb08fe756520fb95508417f76e48198fda2a5b0806", size = 32468873, upload-time = "2024-10-06T04:53:17.639Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d8/c174da5f06b24f3c9e36f91fd02a7411c39da9ce792c17964260d4be675e/av-13.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40e8f757e373b73a2dc4640852a00cce4a4a92ef19b2e642a96d6994cd1fffbf", size = 31818484, upload-time = "2024-10-06T04:53:21.509Z" }, - { url = "https://files.pythonhosted.org/packages/7f/22/0dd8d1d5cad415772bb707d16aea8b81cf75d340d11d3668eea43468c730/av-13.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8aaec2c0bfd024359db3821d679009d4e637e1bee0321d20f61c54ed6b20f41", size = 34398652, upload-time = "2024-10-06T04:53:25.798Z" }, - { url = "https://files.pythonhosted.org/packages/7b/ff/48fa68888b8d5bae36d915556ff18f9e5fdc6b5ff5ae23dc4904c9713168/av-13.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ea0deab0e6a739cb742fba2a3983d8102f7516a3cdf3c46669f3cac0ed1f351", size = 25781343, upload-time = "2024-10-06T04:53:29.577Z" }, + { url = "https://files.pythonhosted.org/packages/9c/84/2535f55edcd426cebec02eb37b811b1b0c163f26b8d3f53b059e2ec32665/av-16.1.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:640f57b93f927fba8689f6966c956737ee95388a91bd0b8c8b5e0481f73513d6", size = 26945785, upload-time = "2026-01-09T20:18:34.486Z" }, + { url = "https://files.pythonhosted.org/packages/b6/17/ffb940c9e490bf42e86db4db1ff426ee1559cd355a69609ec1efe4d3a9eb/av-16.1.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ae3fb658eec00852ebd7412fdc141f17f3ddce8afee2d2e1cf366263ad2a3b35", size = 21481147, upload-time = "2026-01-09T20:18:36.716Z" }, + { url = "https://files.pythonhosted.org/packages/15/c1/e0d58003d2d83c3921887d5c8c9b8f5f7de9b58dc2194356a2656a45cfdc/av-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:27ee558d9c02a142eebcbe55578a6d817fedfde42ff5676275504e16d07a7f86", size = 39517197, upload-time = "2026-01-11T09:57:31.937Z" }, + { url = "https://files.pythonhosted.org/packages/32/77/787797b43475d1b90626af76f80bfb0c12cfec5e11eafcfc4151b8c80218/av-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7ae547f6d5fa31763f73900d43901e8c5fa6367bb9a9840978d57b5a7ae14ed2", size = 41174337, upload-time = "2026-01-11T09:57:35.792Z" }, + { url = "https://files.pythonhosted.org/packages/8e/ac/d90df7f1e3b97fc5554cf45076df5045f1e0a6adf13899e10121229b826c/av-16.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8cf065f9d438e1921dc31fc7aa045790b58aee71736897866420d80b5450f62a", size = 40817720, upload-time = "2026-01-11T09:57:39.039Z" }, + { url = "https://files.pythonhosted.org/packages/80/6f/13c3a35f9dbcebafd03fe0c4cbd075d71ac8968ec849a3cfce406c35a9d2/av-16.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a345877a9d3cc0f08e2bc4ec163ee83176864b92587afb9d08dff50f37a9a829", size = 42267396, upload-time = "2026-01-11T09:57:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b9/275df9607f7fb44317ccb1d4be74827185c0d410f52b6e2cd770fe209118/av-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f49243b1d27c91cd8c66fdba90a674e344eb8eb917264f36117bf2b6879118fd", size = 31752045, upload-time = "2026-01-11T09:57:45.106Z" }, ] [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#acf53363042d92d3206b8ce6b9da4bfb75b200c7" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#5fc3994af75440c9222d627d14a24550f27a1e7f" } [[package]] name = "casadi" @@ -292,31 +286,41 @@ wheels = [ [[package]] name = "cryptography" -version = "43.0.3" +version = "46.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/05/07b55d1fa21ac18c3a8c79f764e2514e6f6a9698f1be44994f5adf0d29db/cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805", size = 686989, upload-time = "2024-10-18T15:58:32.918Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/f3/01fdf26701a26f4b4dbc337a26883ad5bccaa6f1bbbdd29cd89e22f18a1c/cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e", size = 6225303, upload-time = "2024-10-18T15:57:36.753Z" }, - { url = "https://files.pythonhosted.org/packages/a3/01/4896f3d1b392025d4fcbecf40fdea92d3df8662123f6835d0af828d148fd/cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e", size = 3760905, upload-time = "2024-10-18T15:57:39.166Z" }, - { url = "https://files.pythonhosted.org/packages/0a/be/f9a1f673f0ed4b7f6c643164e513dbad28dd4f2dcdf5715004f172ef24b6/cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f", size = 3977271, upload-time = "2024-10-18T15:57:41.227Z" }, - { url = "https://files.pythonhosted.org/packages/4e/49/80c3a7b5514d1b416d7350830e8c422a4d667b6d9b16a9392ebfd4a5388a/cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6", size = 3746606, upload-time = "2024-10-18T15:57:42.903Z" }, - { url = "https://files.pythonhosted.org/packages/0e/16/a28ddf78ac6e7e3f25ebcef69ab15c2c6be5ff9743dd0709a69a4f968472/cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18", size = 3986484, upload-time = "2024-10-18T15:57:45.434Z" }, - { url = "https://files.pythonhosted.org/packages/01/f5/69ae8da70c19864a32b0315049866c4d411cce423ec169993d0434218762/cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd", size = 3852131, upload-time = "2024-10-18T15:57:47.267Z" }, - { url = "https://files.pythonhosted.org/packages/fd/db/e74911d95c040f9afd3612b1f732e52b3e517cb80de8bf183be0b7d413c6/cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73", size = 4075647, upload-time = "2024-10-18T15:57:49.684Z" }, - { url = "https://files.pythonhosted.org/packages/56/48/7b6b190f1462818b324e674fa20d1d5ef3e24f2328675b9b16189cbf0b3c/cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2", size = 2623873, upload-time = "2024-10-18T15:57:51.822Z" }, - { url = "https://files.pythonhosted.org/packages/eb/b1/0ebff61a004f7f89e7b65ca95f2f2375679d43d0290672f7713ee3162aff/cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd", size = 3068039, upload-time = "2024-10-18T15:57:54.426Z" }, - { url = "https://files.pythonhosted.org/packages/30/d5/c8b32c047e2e81dd172138f772e81d852c51f0f2ad2ae8a24f1122e9e9a7/cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984", size = 6222984, upload-time = "2024-10-18T15:57:56.174Z" }, - { url = "https://files.pythonhosted.org/packages/2f/78/55356eb9075d0be6e81b59f45c7b48df87f76a20e73893872170471f3ee8/cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5", size = 3762968, upload-time = "2024-10-18T15:57:58.206Z" }, - { url = "https://files.pythonhosted.org/packages/2a/2c/488776a3dc843f95f86d2f957ca0fc3407d0242b50bede7fad1e339be03f/cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4", size = 3977754, upload-time = "2024-10-18T15:58:00.683Z" }, - { url = "https://files.pythonhosted.org/packages/7c/04/2345ca92f7a22f601a9c62961741ef7dd0127c39f7310dffa0041c80f16f/cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7", size = 3749458, upload-time = "2024-10-18T15:58:02.225Z" }, - { url = "https://files.pythonhosted.org/packages/ac/25/e715fa0bc24ac2114ed69da33adf451a38abb6f3f24ec207908112e9ba53/cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405", size = 3988220, upload-time = "2024-10-18T15:58:04.331Z" }, - { url = "https://files.pythonhosted.org/packages/21/ce/b9c9ff56c7164d8e2edfb6c9305045fbc0df4508ccfdb13ee66eb8c95b0e/cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16", size = 3853898, upload-time = "2024-10-18T15:58:06.113Z" }, - { url = "https://files.pythonhosted.org/packages/2a/33/b3682992ab2e9476b9c81fff22f02c8b0a1e6e1d49ee1750a67d85fd7ed2/cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73", size = 4076592, upload-time = "2024-10-18T15:58:08.673Z" }, - { url = "https://files.pythonhosted.org/packages/81/1e/ffcc41b3cebd64ca90b28fd58141c5f68c83d48563c88333ab660e002cd3/cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995", size = 2623145, upload-time = "2024-10-18T15:58:10.264Z" }, - { url = "https://files.pythonhosted.org/packages/87/5c/3dab83cc4aba1f4b0e733e3f0c3e7d4386440d660ba5b1e3ff995feb734d/cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362", size = 3068026, upload-time = "2024-10-18T15:58:11.916Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, ] [[package]] @@ -352,13 +356,12 @@ wheels = [ [[package]] name = "dearpygui" -version = "2.1.1" +version = "2.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/79/41/2146e8d03d28b5a66d5282beb26ffd9ab68a729a29d31e2fe91809271bf5/dearpygui-2.1.1-cp312-cp312-macosx_10_6_x86_64.whl", hash = "sha256:238aea7b4be7376f564dae8edd563b280ec1483a03786022969938507691e017", size = 2101529, upload-time = "2025-11-14T14:47:39.646Z" }, - { url = "https://files.pythonhosted.org/packages/b0/c5/fcc37ef834fe225241aa4f18d77aaa2903134f283077978d65a901c624c6/dearpygui-2.1.1-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:c27ca6ecd4913555b717f3bb341c0b6a27d6c9fdc9932f0b3c31ae2ef893ae35", size = 1895555, upload-time = "2025-11-14T14:47:48.149Z" }, - { url = "https://files.pythonhosted.org/packages/74/66/19f454ba02d5f03a847cc1dfee4a849cd2307d97add5ba26fecdca318adb/dearpygui-2.1.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:8c071e9c165d89217bdcdaf769c6069252fcaee50bf369489add524107932273", size = 2641509, upload-time = "2025-11-14T14:47:54.581Z" }, - { url = "https://files.pythonhosted.org/packages/5e/58/d01538556103d544a5a5b4cbcb00646ff92d8a97f0a6283a56bede4307c8/dearpygui-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f2291313d2035f8a4108e13f60d8c1a0e7c19af7554a7739a3fd15b3d5af8f7", size = 1808971, upload-time = "2025-11-14T14:47:28.15Z" }, + { url = "https://files.pythonhosted.org/packages/17/c8/b4afdac89c7bf458513366af3143f7383d7b09721637989c95788d93e24c/dearpygui-2.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:34ceae1ca1b65444e49012d6851312e44f08713da1b8cc0150cf41f1c207af9c", size = 1931443, upload-time = "2026-02-17T14:21:54.394Z" }, + { url = "https://files.pythonhosted.org/packages/43/93/a2d083b2e0edb095be815662cc41e40cf9ea7b65d6323e47bb30df7eb284/dearpygui-2.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:e1fae9ae59fec0e41773df64c80311a6ba67696219dde5506a2a4c013e8bcdfa", size = 2592645, upload-time = "2026-02-17T14:22:02.869Z" }, + { url = "https://files.pythonhosted.org/packages/80/ba/eae13acaad479f522db853e8b1ccd695a7bc8da2b9685c1d70a3b318df89/dearpygui-2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7d399543b5a26ab6426ef3bbd776e55520b491b3e169647bde5e6b2de3701b35", size = 1830531, upload-time = "2026-02-17T14:21:43.386Z" }, ] [[package]] @@ -382,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#acf53363042d92d3206b8ce6b9da4bfb75b200c7" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#5fc3994af75440c9222d627d14a24550f27a1e7f" } [[package]] name = "fonttools" @@ -429,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#acf53363042d92d3206b8ce6b9da4bfb75b200c7" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#5fc3994af75440c9222d627d14a24550f27a1e7f" } [[package]] name = "ghp-import" @@ -789,6 +792,7 @@ dependencies = [ { name = "tqdm" }, { name = "websocket-client" }, { name = "xattr" }, + { name = "zeromq" }, { name = "zstandard" }, ] @@ -872,9 +876,10 @@ requires-dist = [ { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, { name = "tqdm" }, - { name = "ty", marker = "extra == 'testing'" }, + { name = "ty", marker = "extra == 'testing'", specifier = "==0.0.17" }, { name = "websocket-client" }, { name = "xattr" }, + { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases" }, { name = "zstandard" }, ] provides-extras = ["docs", "testing", "dev", "tools"] @@ -1027,22 +1032,24 @@ wheels = [ [[package]] name = "pycapnp" -version = "2.1.0" +version = "2.2.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/86/a57e3c92acd3e1d2fc3dcad683ada191f722e4ac927e1a384b228ec2780a/pycapnp-2.1.0.tar.gz", hash = "sha256:69cc3d861fee1c9b26c73ad2e8a5d51e76ad87e4ff9be33a4fd2fc72f5846aec", size = 689734, upload-time = "2025-09-05T03:50:40.851Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/7b/b2f356bc24220068beffc03e94062e8059a1383addb837303794398aec36/pycapnp-2.2.2.tar.gz", hash = "sha256:7f6c23c2283173a3cb6f1a5086dd0114779d508a7cd1b138d25a6357857d02b6", size = 730142, upload-time = "2026-01-21T01:22:13.73Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/74/0e/66b41ba600e5f2523e900b7cc0d2e8496b397a1f2d6a5b7b323ab83418b7/pycapnp-2.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d2ec561bc948d11f64f43bf9601bede5d6a603d105ae311bd5583c7130624a4", size = 1619223, upload-time = "2025-09-05T03:48:54.64Z" }, - { url = "https://files.pythonhosted.org/packages/40/6e/9bcb30180bd40cb0534124ff7f8ba8746a735018d593f608bf40c97821c0/pycapnp-2.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:132cd97f57f6b6636323ca9b68d389dd90b96e87af38cde31e2b5c5a064f277e", size = 1484321, upload-time = "2025-09-05T03:48:55.85Z" }, - { url = "https://files.pythonhosted.org/packages/14/0a/9ee1c9ecaff499e4fd1df2f0335bc20f666ec6ce5cd80f8ab055007f3c9b/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:568e79268ba7c02a71fe558a8aec1ae3c0f0e6aff809ff618a46afe4964957d2", size = 5143502, upload-time = "2025-09-05T03:48:57.733Z" }, - { url = "https://files.pythonhosted.org/packages/4d/50/65837e1416f7a8861ca1e8fe4582a5aef37192d7ef5e2ecfe46880bfdf9c/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:bcbf6f882d78d368c8e4bb792295392f5c4d71ddffa13a48da27e7bd47b99e37", size = 5508134, upload-time = "2025-09-05T03:48:59.383Z" }, - { url = "https://files.pythonhosted.org/packages/a1/59/46df6db800e77dbc3cc940723fb3fd7bc837327c858edf464a0f904bf547/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:dc25b96e393410dde25c61c1df3ce644700ef94826c829426d58c2c6b3e2d2f5", size = 5631794, upload-time = "2025-09-05T03:49:03.511Z" }, - { url = "https://files.pythonhosted.org/packages/63/9d/18e978500d5f6bd8d152f4d6919e3cfb83ead8a71c14613bbb54322df8b9/pycapnp-2.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:48938e0436ab1be615fc0a41434119a2065490a6212b9a5e56949e89b0588b76", size = 5369378, upload-time = "2025-09-05T03:49:05.539Z" }, - { url = "https://files.pythonhosted.org/packages/96/dc/726f1917e9996dc29f9fd1cf30674a14546cdbdfa0777e1982b6bd1ad628/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0c20de0f6e0b3fa9fa1df3864cf46051db3511b63bc29514d1092af65f2b82a0", size = 5999140, upload-time = "2025-09-05T03:49:07.341Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3a/3bbc4c5776fc32fbf8a59df5c7c5810efd292b933cd6545eb4b16d896268/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:18caca6527862475167c10ea0809531130585aa8a86cc76cd1629eb87ee30637", size = 6454308, upload-time = "2025-09-05T03:49:08.998Z" }, - { url = "https://files.pythonhosted.org/packages/bf/dd/17e2d7808424f10ffddc47329b980488ed83ec716c504791787e593a7a93/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9dcc11237697007b66e3bfc500d2ad892bd79672c9b50d61fbf728c6aaf936de", size = 6544212, upload-time = "2025-09-05T03:49:10.675Z" }, - { url = "https://files.pythonhosted.org/packages/6a/5b/68090013128d7853f34c43828dd4dc80a7c8516fd1b56057b134e1e4c2c0/pycapnp-2.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c151edf78155b6416e7cb31e2e333d302d742ba52bb37d4dbdf71e75cc999d46", size = 6295279, upload-time = "2025-09-05T03:49:12.712Z" }, - { url = "https://files.pythonhosted.org/packages/5b/52/7d85212b4fcf127588888f71d3dbf5558ee7dc302eba760b12b1b325f9a3/pycapnp-2.1.0-cp312-cp312-win32.whl", hash = "sha256:c09b28419321dafafc644d60c57ff8ccaf3c3e686801b6060c612a7a3c580944", size = 1038995, upload-time = "2025-09-05T03:49:14.165Z" }, - { url = "https://files.pythonhosted.org/packages/f2/12/25d283ebf5c28717364647672e7494dc46196ca7a662f5420e4866f45687/pycapnp-2.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:560cb69cc02b0347e85b0629e4c2f0a316240900aa905392f9df6bab0a359989", size = 1176620, upload-time = "2025-09-05T03:49:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/8a/76/f8f81d32ddf950e934ec144facbc112e5acbef31a63ba5be0c5f34a00fd5/pycapnp-2.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:8b86cb8ea5b8011b562c4e022325a826a62f91196ceb5aa33a766c0bea0b8fd3", size = 1605194, upload-time = "2026-01-21T01:20:29.604Z" }, + { url = "https://files.pythonhosted.org/packages/50/dd/a31be782d56a8648fef899f39aeeab867cf544a6b170871e3f4cbfc58af6/pycapnp-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2353531cfa669e3eeb99be9f993573341650276abec46676d687cc12b3e6b6d9", size = 1486613, upload-time = "2026-01-21T01:20:31.415Z" }, + { url = "https://files.pythonhosted.org/packages/aa/bf/8da830dda94eb7327c6508d6c26fbd964897d742f8c1c0ec48623f0c515b/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ee27bdc78c7ccd8eaa0fe31e09f0ec4ef31deda3f475fc9373bb4b0de8083053", size = 5186701, upload-time = "2026-01-21T01:20:32.836Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a1/13d0baa2f337f4f6fe8c2142646ba437a26b9c433f5d7ce016a912bad052/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_i686.whl", hash = "sha256:a8ded808911d1d7a9a2197626c09eea6e269e74dc1276760789538b1efcf6cd5", size = 5239464, upload-time = "2026-01-21T01:20:34.793Z" }, + { url = "https://files.pythonhosted.org/packages/82/76/0451c64b5f0132e4b75a0afe8cec957c8bf8fa981264a7c0b264cb94663e/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_ppc64le.whl", hash = "sha256:59e92e1db40041d82a95eab0bd8de2676ce50c6b97c1457e2edde4d134b6d046", size = 5542887, upload-time = "2026-01-21T01:20:36.463Z" }, + { url = "https://files.pythonhosted.org/packages/04/00/d025d68d9a5330d55cbe2d018091cacfef0835c3ad422eb6778c4525041f/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_s390x.whl", hash = "sha256:ee1e9ac2f0b80fa892b922b60e36efc925d072ecf1204ba3e59d8d9ac7c3dc83", size = 5659696, upload-time = "2026-01-21T01:20:38.069Z" }, + { url = "https://files.pythonhosted.org/packages/58/b7/28f7c539a5f4cbaa12e55ec27d081d11473464230f2e801e4714606d3453/pycapnp-2.2.2-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:53273b385be78ed8ac997ff8697f2a4c760e93c190b509822a937de5531f4861", size = 5413827, upload-time = "2026-01-21T01:20:39.781Z" }, + { url = "https://files.pythonhosted.org/packages/3c/a7/83bc13d90675f0cee8a38d4ad8401bb2f8662c543b3a6622aeffb7b56b1e/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:812cbdd002bc542b63f969b85c6b9041dfdaf4185613635a6d4feea84c9092fa", size = 6046815, upload-time = "2026-01-21T01:20:42.172Z" }, + { url = "https://files.pythonhosted.org/packages/0d/8a/80f46baa1684bbcc4754ce22c5a44693a1209a64de6df2b256b85b8b8a97/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9c330218a44bd649b96f565dbf5326d183fdd20f9887bdedfeabd73f0366c2e1", size = 6367625, upload-time = "2026-01-21T01:20:44.004Z" }, + { url = "https://files.pythonhosted.org/packages/02/00/60e82eaf6b4e78d887157bf9f18234c852771cc575355e63d1114c4a5d79/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:796aa0ba18bcd4e6b2815471bbed059ad7ee8a815a30e81ac8a9aa030ec7818d", size = 6487265, upload-time = "2026-01-21T01:20:46.137Z" }, + { url = "https://files.pythonhosted.org/packages/57/6e/2dedd8f95dc22357c50a775ee2b8711b3d711f30344d244141e0e1962c3e/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:251a6abdd64b9b11d2a8e16fc365b922ef6ba6c968959b72a3a3d9d8ec8cc8d7", size = 6576699, upload-time = "2026-01-21T01:20:47.987Z" }, + { url = "https://files.pythonhosted.org/packages/2f/53/f7f69ed1d11ea30ea4f0f6d8319fbc18bc8781c480c118005e0a394492a7/pycapnp-2.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6aab811e0fcc27ae8bf5f04dedaa7e0af47e0d4db51d9c85ab0d2dad26a46bd7", size = 6344114, upload-time = "2026-01-21T01:20:50.367Z" }, + { url = "https://files.pythonhosted.org/packages/ab/78/ab78ee42797ff44c7e1fc0d1aa9396c6742cb05ff01a7cdf9c8f19e0defe/pycapnp-2.2.2-cp312-cp312-win32.whl", hash = "sha256:5061c85dd8f843b2656720ca6976d2a9b418845580c6f6d9602f7119fc2208d5", size = 1047207, upload-time = "2026-01-21T01:20:51.842Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fb/6edf56d5144c476270fa8b2e6a660ef5a188fb0097193e342618fbcb0210/pycapnp-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:700eb8c77405222903af3fb5a371c0d766f86139c3d51f4bff41ccd6403b51f9", size = 1185178, upload-time = "2026-01-21T01:20:53.429Z" }, ] [[package]] @@ -1127,14 +1134,15 @@ wheels = [ [[package]] name = "pyopenssl" -version = "24.2.1" +version = "25.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/70/ff56a63248562e77c0c8ee4aefc3224258f1856977e0c1472672b62dadb8/pyopenssl-24.2.1.tar.gz", hash = "sha256:4247f0dbe3748d560dcbb2ff3ea01af0f9a1a001ef5f7c4c647956ed8cbf0e95", size = 184323, upload-time = "2024-07-20T17:26:31.252Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d9/dd/e0aa7ebef5168c75b772eda64978c597a9129b46be17779054652a7999e4/pyOpenSSL-24.2.1-py3-none-any.whl", hash = "sha256:967d5719b12b243588573f39b0c677637145c7a1ffedcd495a487e58177fbb8d", size = 58390, upload-time = "2024-07-20T17:26:29.057Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, ] [[package]] @@ -1349,27 +1357,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" }, - { url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" }, - { url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" }, - { url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" }, - { url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" }, - { url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" }, - { url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" }, - { url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" }, - { url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" }, - { url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" }, - { url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" }, - { url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" }, - { url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" }, - { url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" }, +version = "0.15.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, + { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, + { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, + { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, + { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, + { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, + { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, + { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, + { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, + { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, + { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, ] [[package]] @@ -1605,6 +1613,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, ] +[[package]] +name = "zeromq" +version = "4.3.5" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#5fc3994af75440c9222d627d14a24550f27a1e7f" } + [[package]] name = "zstandard" version = "0.25.0" From 08b76d3de66458ba7bd74645b669c8fa70bd334a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 20:14:12 -0800 Subject: [PATCH 309/685] Use built-in clang on macOS (#37335) * rm extra LLVM install on macOS * update that * rm brew cache * no cache * Revert "no cache" This reverts commit a3f8eff234935d4bb27d4bd785ad8a710496a159. --- .github/workflows/tests.yaml | 8 -------- SConstruct | 1 - tools/mac_setup.sh | 1 - 3 files changed, 10 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 40dfaaa8010..79520105a4f 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -67,14 +67,6 @@ jobs: with: submodules: true - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - - name: Homebrew cache - uses: ./.github/workflows/auto-cache - with: - path: ~/Library/Caches/Homebrew - key: brew-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} - restore-keys: | - brew-macos-${{ env.CACHE_COMMIT_DATE }} - brew-macos - name: Install dependencies run: ./tools/mac_setup.sh env: diff --git a/SConstruct b/SConstruct index 148c119066a..5f450e6b576 100644 --- a/SConstruct +++ b/SConstruct @@ -117,7 +117,6 @@ elif arch == "Darwin": env.Append(LIBPATH=[ f"{brew_prefix}/lib", f"{brew_prefix}/opt/openssl@3.0/lib", - f"{brew_prefix}/opt/llvm/lib/c++", "/System/Library/Frameworks/OpenGL.framework/Libraries", ]) env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"]) diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index c4eee0f67da..1ab9ca02e7f 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -30,7 +30,6 @@ brew bundle --file=- <<-EOS brew "git-lfs" brew "coreutils" brew "eigen" -brew "llvm" EOS echo "[ ] finished brew install t=$SECONDS" From 0738c05d9f7ea910747b6f56dc290da922c7bd70 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 21:29:23 -0800 Subject: [PATCH 310/685] vendored git-lfs (#37338) * use vendored zeromq from dependencies repo Co-Authored-By: Claude Opus 4.6 * lock * rm more crap * use vendored git-lfs from dependencies repo Co-Authored-By: Claude Opus 4.6 * from releases --------- Co-authored-by: Claude Opus 4.6 --- pyproject.toml | 1 + tools/install_ubuntu_dependencies.sh | 1 - tools/mac_setup.sh | 1 - uv.lock | 15 +++++++++++---- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 71e40e39142..3697b004c75 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ dependencies = [ "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", + "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", # body / webrtcd "av", diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 7d24b6c09a3..d14a2a5350b 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -31,7 +31,6 @@ function install_ubuntu_common_requirements() { libcurl4-openssl-dev \ locales \ git \ - git-lfs \ xvfb $SUDO apt-get install -y --no-install-recommends \ diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 1ab9ca02e7f..f9f77320511 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -27,7 +27,6 @@ else fi brew bundle --file=- <<-EOS -brew "git-lfs" brew "coreutils" brew "eigen" EOS diff --git a/uv.lock b/uv.lock index 3327ba4dbed..9432df071c6 100644 --- a/uv.lock +++ b/uv.lock @@ -116,7 +116,7 @@ wheels = [ [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#5fc3994af75440c9222d627d14a24550f27a1e7f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } [[package]] name = "casadi" @@ -385,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#5fc3994af75440c9222d627d14a24550f27a1e7f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } [[package]] name = "fonttools" @@ -432,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#5fc3994af75440c9222d627d14a24550f27a1e7f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } [[package]] name = "ghp-import" @@ -446,6 +446,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, ] +[[package]] +name = "git-lfs" +version = "3.6.1" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } + [[package]] name = "google-crc32c" version = "1.8.0" @@ -768,6 +773,7 @@ dependencies = [ { name = "crcmod-plus" }, { name = "cython" }, { name = "ffmpeg" }, + { name = "git-lfs" }, { name = "inputs" }, { name = "jeepney" }, { name = "json-rpc" }, @@ -840,6 +846,7 @@ requires-dist = [ { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases" }, { name = "gcc-arm-none-eabi", marker = "extra == 'dev'", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, + { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, @@ -1616,7 +1623,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#5fc3994af75440c9222d627d14a24550f27a1e7f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } [[package]] name = "zstandard" From f96406b13f7a55e6187b895a1c2610e538ece8de Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 21:48:00 -0800 Subject: [PATCH 311/685] use vendored eigen from dependencies repo (#37339) * use vendored eigen from dependencies repo Co-Authored-By: Claude Opus 4.6 * lock --------- Co-authored-by: Claude Opus 4.6 --- SConstruct | 3 ++- pyproject.toml | 1 + tools/install_ubuntu_dependencies.sh | 1 - tools/mac_setup.sh | 1 - uv.lock | 17 ++++++++++++----- 5 files changed, 15 insertions(+), 8 deletions(-) diff --git a/SConstruct b/SConstruct index 5f450e6b576..cb1c97d158b 100644 --- a/SConstruct +++ b/SConstruct @@ -40,9 +40,10 @@ assert arch in [ if arch != "larch64": import capnproto + import eigen import ffmpeg as ffmpeg_pkg import zeromq - pkgs = [capnproto, ffmpeg_pkg, zeromq] + pkgs = [capnproto, eigen, ffmpeg_pkg, zeromq] else: # TODO: remove when AGNOS has our new vendor pkgs pkgs = [] diff --git a/pyproject.toml b/pyproject.toml index 3697b004c75..03ab5902ffc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,7 @@ dependencies = [ # vendored native dependencies "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", + "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index d14a2a5350b..effa68b9cad 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -35,7 +35,6 @@ function install_ubuntu_common_requirements() { $SUDO apt-get install -y --no-install-recommends \ libbz2-dev \ - libeigen3-dev \ libgles2-mesa-dev \ libjpeg-dev \ libncurses5-dev \ diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index f9f77320511..1a8a17bfeba 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -28,7 +28,6 @@ fi brew bundle --file=- <<-EOS brew "coreutils" -brew "eigen" EOS echo "[ ] finished brew install t=$SECONDS" diff --git a/uv.lock b/uv.lock index 9432df071c6..33c210a90f2 100644 --- a/uv.lock +++ b/uv.lock @@ -116,7 +116,7 @@ wheels = [ [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#96208e8726374ab5229366102a17401edb68076c" } [[package]] name = "casadi" @@ -373,6 +373,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" }, ] +[[package]] +name = "eigen" +version = "3.4.0" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#96208e8726374ab5229366102a17401edb68076c" } + [[package]] name = "execnet" version = "2.1.2" @@ -385,7 +390,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#96208e8726374ab5229366102a17401edb68076c" } [[package]] name = "fonttools" @@ -432,7 +437,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#96208e8726374ab5229366102a17401edb68076c" } [[package]] name = "ghp-import" @@ -449,7 +454,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#96208e8726374ab5229366102a17401edb68076c" } [[package]] name = "google-crc32c" @@ -772,6 +777,7 @@ dependencies = [ { name = "cffi" }, { name = "crcmod-plus" }, { name = "cython" }, + { name = "eigen" }, { name = "ffmpeg" }, { name = "git-lfs" }, { name = "inputs" }, @@ -844,6 +850,7 @@ requires-dist = [ { name = "crcmod-plus" }, { name = "cython" }, { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, + { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases" }, { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases" }, { name = "gcc-arm-none-eabi", marker = "extra == 'dev'", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases" }, @@ -1623,7 +1630,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#d9eaf5b01ed0b0f036a0463e13d6e101352ffdd6" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#96208e8726374ab5229366102a17401edb68076c" } [[package]] name = "zstandard" From ca058bcc8182de7dc70d4918f2def39e54ae9775 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 21:52:11 -0800 Subject: [PATCH 312/685] bye bye brew (#37340) * bye bye brew * drop the nproc it's simpler --- .github/workflows/tests.yaml | 2 +- tools/mac_setup.sh | 22 ---------------------- 2 files changed, 1 insertion(+), 23 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 79520105a4f..92cdd23768a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -81,7 +81,7 @@ jobs: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }} scons-${{ runner.arch }}-macos - name: Building openpilot - run: . .venv/bin/activate && scons -j$(nproc) + run: . .venv/bin/activate && scons static_analysis: name: static analysis diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 1a8a17bfeba..9607f70cf5b 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -4,34 +4,12 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" ROOT="$(cd $DIR/../ && pwd)" -# homebrew update is slow -export HOMEBREW_NO_AUTO_UPDATE=1 - if [[ $SHELL == "/bin/zsh" ]]; then RC_FILE="$HOME/.zshrc" elif [[ $SHELL == "/bin/bash" ]]; then RC_FILE="$HOME/.bash_profile" fi -# Install brew if required -if [[ $(command -v brew) == "" ]]; then - echo "Installing Homebrew" - /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - echo "[ ] installed brew t=$SECONDS" - - # make brew available now - echo 'eval "$(/opt/homebrew/bin/brew shellenv)"' >> $RC_FILE - eval "$(/opt/homebrew/bin/brew shellenv)" -else - brew up -fi - -brew bundle --file=- <<-EOS -brew "coreutils" -EOS - -echo "[ ] finished brew install t=$SECONDS" - # install python dependencies $DIR/install_python_dependencies.sh echo "[ ] installed python dependencies t=$SECONDS" From 2a0ac63fa578780daf88d2dfcb1dba9cd22f2199 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Feb 2026 22:17:15 -0800 Subject: [PATCH 313/685] remove libbz2 from ubuntu setup (#37342) --- tools/install_ubuntu_dependencies.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index effa68b9cad..4a1cf9a8cbf 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -34,7 +34,6 @@ function install_ubuntu_common_requirements() { xvfb $SUDO apt-get install -y --no-install-recommends \ - libbz2-dev \ libgles2-mesa-dev \ libjpeg-dev \ libncurses5-dev \ From 35e38f5fe4cbab410067f05aaf0b3be2ca26c83c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 01:21:40 -0800 Subject: [PATCH 314/685] mici ui: show lock in network panel (#37345) * disable forget for tethering * nets * put in wifiman * batch * revert * draw --- .../mici/layouts/settings/network/__init__.py | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index ad4e43a1ac8..299df5fea04 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -8,7 +8,27 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, normalize_ssid +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, SecurityType, normalize_ssid + + +class WifiNetworkButton(BigButton): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 28, 36) + self._draw_lock = False + + def set_draw_lock(self, draw: bool): + self._draw_lock = draw + + def _draw_content(self, btn_y: float): + super()._draw_content(btn_y) + # Render lock icon at lower right of wifi icon if secured + if self._draw_lock: + icon_x = self._rect.x + self._rect.width - 30 - self._txt_icon.width + icon_y = btn_y + 30 + lock_x = icon_x + self._txt_icon.width - self._lock_txt.width + 7 + lock_y = icon_y + self._txt_icon.height - self._lock_txt.height + 8 + rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, 1.0, rl.WHITE) class NetworkLayoutMici(NavWidget): @@ -68,7 +88,7 @@ def network_metered_callback(value: str): self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 64, 47) self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47) - self._wifi_button = BigButton("wi-fi", "not connected", self._wifi_slash_txt, scroll=True) + self._wifi_button = WifiNetworkButton("wi-fi", "not connected", self._wifi_slash_txt, scroll=True) self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) # ******** Advanced settings ******** @@ -131,8 +151,10 @@ def _update_state(self): if display_network is not None: strength = WifiIcon.get_strength_icon_idx(display_network.strength) self._wifi_button.set_icon(self._wifi_full_txt if strength == 2 else self._wifi_medium_txt if strength == 1 else self._wifi_low_txt) + self._wifi_button.set_draw_lock(display_network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED)) else: self._wifi_button.set_icon(self._wifi_slash_txt) + self._wifi_button.set_draw_lock(False) def show_event(self): super().show_event() From 2ecdd2810c6475dd40f9a981e2e10c67a4645a5d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 01:22:59 -0800 Subject: [PATCH 315/685] mici ui: disable forget on tethering and show full strength (#37344) * disable forget for tethering * nets * put in wifiman * batch * Revert "batch" This reverts commit 9af20c1c7513c22bf9283b2f02514373fa981f50. * clean up * more * more --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 3 +++ system/ui/lib/wifi_manager.py | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index c43a294e406..c9bc54a8b5a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -152,6 +152,9 @@ def network(self) -> Network: @property def _show_forget_btn(self): + if self._network.is_tethering: + return False + return (self._is_saved and not self._wrong_password) or self._is_connecting def _handle_mouse_release(self, mouse_pos: MousePos): diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 4ca0a382d44..4f02b706259 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -93,17 +93,19 @@ class Network: ssid: str strength: int security_type: SecurityType + is_tethering: bool @classmethod - def from_dbus(cls, ssid: str, aps: list["AccessPoint"]) -> "Network": + def from_dbus(cls, ssid: str, aps: list["AccessPoint"], is_tethering: bool) -> "Network": # we only want to show the strongest AP for each Network/SSID strongest_ap = max(aps, key=lambda ap: ap.strength) security_type = get_security_type(strongest_ap.flags, strongest_ap.wpa_flags, strongest_ap.rsn_flags) return cls( ssid=ssid, - strength=strongest_ap.strength, + strength=100 if is_tethering else strongest_ap.strength, security_type=security_type, + is_tethering=is_tethering, ) @@ -820,7 +822,7 @@ def worker(): # catch all for parsing errors cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - networks = [Network.from_dbus(ssid, ap_list) for ssid, ap_list in aps.items()] + networks = [Network.from_dbus(ssid, ap_list, ssid == self._tethering_ssid) for ssid, ap_list in aps.items()] networks.sort(key=lambda n: (n.ssid != self._wifi_state.ssid, not self.is_connection_saved(n.ssid), -n.strength, n.ssid.lower())) self._networks = networks From 8d0cb9c8cfd9efd03e8b9cd8f851fcfe5d844200 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 01:31:06 -0800 Subject: [PATCH 316/685] Unified label fix scroll fade (#37346) * disable forget for tethering * nets * put in wifiman * batch * revert * clean up --- system/ui/widgets/label.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index cb0cf66b144..865c8b38c3f 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -753,7 +753,12 @@ def _render(self, _): # draw black fade on left and right fade_width = 20 rl.draw_rectangle_gradient_h(int(self._rect.x + self._rect.width - fade_width), int(self._rect.y), fade_width, int(self._rect.height), rl.BLANK, rl.BLACK) - if self._scroll_state != ScrollState.STARTING: + + # stop drawing left fade once text scrolls past + text_width = visible_sizes[0].x if visible_sizes else 0 + first_copy_in_view = self._scroll_offset + text_width > 0 + draw_left_fade = self._scroll_state != ScrollState.STARTING and first_copy_in_view + if draw_left_fade: rl.draw_rectangle_gradient_h(int(self._rect.x), int(self._rect.y), fade_width, int(self._rect.height), rl.BLACK, rl.BLANK) rl.end_scissor_mode() From c16879f2b82178742a3cec8def3ee45faebf833c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 01:40:56 -0800 Subject: [PATCH 317/685] mici ui: fix missing home show event (#37347) fix missing --- selfdrive/ui/mici/layouts/main.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index f6dbad89a14..dc01aba56f5 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -62,6 +62,12 @@ def _setup_callbacks(self): self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) device.add_interactive_timeout_callback(self._on_interactive_timeout) + def show_event(self): + self._scroller.show_event() + + def hide_event(self): + self._scroller.hide_event() + def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) self._scroller.scroll_to(layout_x, smooth=True) From 0a14e198083b00763c3f20702da8655ccb322d66 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 09:45:29 -0800 Subject: [PATCH 318/685] CI: use setup action on macOS (#37352) --- .github/workflows/setup/action.yaml | 14 +++++++++++--- .github/workflows/tests.yaml | 17 ++--------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index 56f0096450e..958f60d2ead 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -38,10 +38,18 @@ runs: uses: ./.github/workflows/auto-cache with: path: /tmp/scons_cache - key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} + key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} restore-keys: | - scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }} - scons-${{ runner.arch }} + scons-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }} + scons-${{ runner.os }}-${{ runner.arch }} + # venv cache + - id: venv-cache + uses: ./.github/workflows/auto-cache + with: + path: ${{ github.workspace }}/.venv + key: venv-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('uv.lock') }} + restore-keys: | + venv-${{ runner.os }}-${{ runner.arch }} - shell: bash name: Run setup run: ./tools/op.sh setup diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 92cdd23768a..ff4fc390940 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -66,22 +66,9 @@ jobs: - uses: actions/checkout@v6 with: submodules: true - - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - - name: Install dependencies - run: ./tools/mac_setup.sh - env: - HOMEBREW_DISPLAY_INSTALL_TIMES: 1 - - run: git lfs pull - - name: Getting scons cache - uses: ./.github/workflows/auto-cache - with: - path: /tmp/scons_cache - key: scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} - restore-keys: | - scons-${{ runner.arch }}-macos-${{ env.CACHE_COMMIT_DATE }} - scons-${{ runner.arch }}-macos + - uses: ./.github/workflows/setup-with-retry - name: Building openpilot - run: . .venv/bin/activate && scons + run: scons static_analysis: name: static analysis From 7cc237aa4c54bc0f7442fdbfa90cbd20ed123679 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:58:58 -0800 Subject: [PATCH 319/685] [bot] Update Python packages (#37351) * Update Python packages * fix --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- docs/CARS.md | 3 +- opendbc_repo | 2 +- selfdrive/selfdrived/events.py | 2 +- selfdrive/ui/widgets/offroad_alerts.py | 2 +- uv.lock | 42 +++++++++++++------------- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 65f79cdba4d..5487ef1ee1f 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 328 Supported Cars +# 329 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -165,6 +165,7 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Forte 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|K5 2021-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|K7 2017|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|K8 Hybrid (with HDA II) 2023|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| diff --git a/opendbc_repo b/opendbc_repo index 647441e42db..b10b43105bc 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 647441e42db5735e249344449ad857eeeeff7224 +Subproject commit b10b43105bcd891f95920aac29d01177e0be4940 diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 6fb96b6e5a0..e10f67fa459 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -1107,7 +1107,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin for i, alerts in EVENTS.items(): for et, alert in alerts.items(): - if callable(alert): + if not isinstance(alert, Alert): alert = alert(CP, CS, sm, False, 1, log.LongitudinalPersonality.standard) alerts_by_type[et][alert.priority].append(event_names[i]) diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index 802243ff3eb..a87727e27f9 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -63,7 +63,7 @@ def __init__(self, text: str | Callable[[], str], style: ButtonStyle = ButtonSty @property def text(self) -> str: - return self._text() if callable(self._text) else self._text + return self._text if isinstance(self._text, str) else self._text() def _render(self, _): text_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), self.text, AlertConstants.FONT_SIZE) diff --git a/uv.lock b/uv.lock index 33c210a90f2..11ff9892781 100644 --- a/uv.lock +++ b/uv.lock @@ -890,7 +890,7 @@ requires-dist = [ { name = "spidev", marker = "sys_platform == 'linux'" }, { name = "sympy" }, { name = "tqdm" }, - { name = "ty", marker = "extra == 'testing'", specifier = "==0.0.17" }, + { name = "ty", marker = "extra == 'testing'" }, { name = "websocket-client" }, { name = "xattr" }, { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases" }, @@ -1509,26 +1509,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.17" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/c3/41ae6346443eedb65b96761abfab890a48ce2aa5a8a27af69c5c5d99064d/ty-0.0.17.tar.gz", hash = "sha256:847ed6c120913e280bf9b54d8eaa7a1049708acb8824ad234e71498e8ad09f97", size = 5167209, upload-time = "2026-02-13T13:26:36.835Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/01/0ef15c22a1c54b0f728ceff3f62d478dbf8b0dcf8ff7b80b954f79584f3e/ty-0.0.17-py3-none-linux_armv6l.whl", hash = "sha256:64a9a16555cc8867d35c2647c2f1afbd3cae55f68fd95283a574d1bb04fe93e0", size = 10192793, upload-time = "2026-02-13T13:27:13.943Z" }, - { url = "https://files.pythonhosted.org/packages/0f/2c/f4c322d9cded56edc016b1092c14b95cf58c8a33b4787316ea752bb9418e/ty-0.0.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eb2dbd8acd5c5a55f4af0d479523e7c7265a88542efe73ed3d696eb1ba7b6454", size = 10051977, upload-time = "2026-02-13T13:26:57.741Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a5/43746c1ff81e784f5fc303afc61fe5bcd85d0fcf3ef65cb2cef78c7486c7/ty-0.0.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f18f5fd927bc628deb9ea2df40f06b5f79c5ccf355db732025a3e8e7152801f6", size = 9564639, upload-time = "2026-02-13T13:26:42.781Z" }, - { url = "https://files.pythonhosted.org/packages/d6/b8/280b04e14a9c0474af574f929fba2398b5e1c123c1e7735893b4cd73d13c/ty-0.0.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5383814d1d7a5cc53b3b07661856bab04bb2aac7a677c8d33c55169acdaa83df", size = 10061204, upload-time = "2026-02-13T13:27:00.152Z" }, - { url = "https://files.pythonhosted.org/packages/2a/d7/493e1607d8dfe48288d8a768a2adc38ee27ef50e57f0af41ff273987cda0/ty-0.0.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c20423b8744b484f93e7bf2ef8a9724bca2657873593f9f41d08bd9f83444c9", size = 10013116, upload-time = "2026-02-13T13:26:34.543Z" }, - { url = "https://files.pythonhosted.org/packages/80/ef/22f3ed401520afac90dbdf1f9b8b7755d85b0d5c35c1cb35cf5bd11b59c2/ty-0.0.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6f5b1aba97db9af86517b911674b02f5bc310750485dc47603a105bd0e83ddd", size = 10533623, upload-time = "2026-02-13T13:26:31.449Z" }, - { url = "https://files.pythonhosted.org/packages/75/ce/744b15279a11ac7138832e3a55595706b4a8a209c9f878e3ab8e571d9032/ty-0.0.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:488bce1a9bea80b851a97cd34c4d2ffcd69593d6c3f54a72ae02e5c6e47f3d0c", size = 11069750, upload-time = "2026-02-13T13:26:48.638Z" }, - { url = "https://files.pythonhosted.org/packages/f2/be/1133c91f15a0e00d466c24f80df486d630d95d1b2af63296941f7473812f/ty-0.0.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8df66b91ec84239420985ec215e7f7549bfda2ac036a3b3c065f119d1c06825a", size = 10870862, upload-time = "2026-02-13T13:26:54.715Z" }, - { url = "https://files.pythonhosted.org/packages/3e/4a/a2ed209ef215b62b2d3246e07e833081e07d913adf7e0448fc204be443d6/ty-0.0.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:002139e807c53002790dfefe6e2f45ab0e04012e76db3d7c8286f96ec121af8f", size = 10628118, upload-time = "2026-02-13T13:26:45.439Z" }, - { url = "https://files.pythonhosted.org/packages/b3/0c/87476004cb5228e9719b98afffad82c3ef1f84334bde8527bcacba7b18cb/ty-0.0.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6c4e01f05ce82e5d489ab3900ca0899a56c4ccb52659453780c83e5b19e2b64c", size = 10038185, upload-time = "2026-02-13T13:27:02.693Z" }, - { url = "https://files.pythonhosted.org/packages/46/4b/98f0b3ba9aef53c1f0305519536967a4aa793a69ed72677b0a625c5313ac/ty-0.0.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2b226dd1e99c0d2152d218c7e440150d1a47ce3c431871f0efa073bbf899e881", size = 10047644, upload-time = "2026-02-13T13:27:05.474Z" }, - { url = "https://files.pythonhosted.org/packages/93/e0/06737bb80aa1a9103b8651d2eb691a7e53f1ed54111152be25f4a02745db/ty-0.0.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8b11f1da7859e0ad69e84b3c5ef9a7b055ceed376a432fad44231bdfc48061c2", size = 10231140, upload-time = "2026-02-13T13:27:10.844Z" }, - { url = "https://files.pythonhosted.org/packages/7c/79/e2a606bd8852383ba9abfdd578f4a227bd18504145381a10a5f886b4e751/ty-0.0.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c04e196809ff570559054d3e011425fd7c04161529eb551b3625654e5f2434cb", size = 10718344, upload-time = "2026-02-13T13:26:51.66Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2d/2663984ac11de6d78f74432b8b14ba64d170b45194312852b7543cf7fd56/ty-0.0.17-py3-none-win32.whl", hash = "sha256:305b6ed150b2740d00a817b193373d21f0767e10f94ac47abfc3b2e5a5aec809", size = 9672932, upload-time = "2026-02-13T13:27:08.522Z" }, - { url = "https://files.pythonhosted.org/packages/de/b5/39be78f30b31ee9f5a585969930c7248354db90494ff5e3d0756560fb731/ty-0.0.17-py3-none-win_amd64.whl", hash = "sha256:531828267527aee7a63e972f54e5eee21d9281b72baf18e5c2850c6b862add83", size = 10542138, upload-time = "2026-02-13T13:27:17.084Z" }, - { url = "https://files.pythonhosted.org/packages/40/b7/f875c729c5d0079640c75bad2c7e5d43edc90f16ba242f28a11966df8f65/ty-0.0.17-py3-none-win_arm64.whl", hash = "sha256:de9810234c0c8d75073457e10a84825b9cd72e6629826b7f01c7a0b266ae25b1", size = 10023068, upload-time = "2026-02-13T13:26:39.637Z" }, +version = "0.0.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/74/15/9682700d8d60fdca7afa4febc83a2354b29cdcd56e66e19c92b521db3b39/ty-0.0.18.tar.gz", hash = "sha256:04ab7c3db5dcbcdac6ce62e48940d3a0124f377c05499d3f3e004e264ae94b83", size = 5214774, upload-time = "2026-02-20T21:51:31.173Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ae/d8/920460d4c22ea68fcdeb0b2fb53ea2aeb9c6d7875bde9278d84f2ac767b6/ty-0.0.18-py3-none-linux_armv6l.whl", hash = "sha256:4e5e91b0a79857316ef893c5068afc4b9872f9d257627d9bc8ac4d2715750d88", size = 10280825, upload-time = "2026-02-20T21:51:25.03Z" }, + { url = "https://files.pythonhosted.org/packages/83/56/62587de582d3d20d78fcdddd0594a73822ac5a399a12ef512085eb7a4de6/ty-0.0.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee0e578b3f8416e2d5416da9553b78fd33857868aa1384cb7fefeceee5ff102d", size = 10118324, upload-time = "2026-02-20T21:51:22.27Z" }, + { url = "https://files.pythonhosted.org/packages/2f/2d/dbdace8d432a0755a7417f659bfd5b8a4261938ecbdfd7b42f4c454f5aa9/ty-0.0.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3f7a0487d36b939546a91d141f7fc3dbea32fab4982f618d5b04dc9d5b6da21e", size = 9605861, upload-time = "2026-02-20T21:51:16.066Z" }, + { url = "https://files.pythonhosted.org/packages/6b/d9/de11c0280f778d5fc571393aada7fe9b8bc1dd6a738f2e2c45702b8b3150/ty-0.0.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5e2fa8d45f57ca487a470e4bf66319c09b561150e98ae2a6b1a97ef04c1a4eb", size = 10092701, upload-time = "2026-02-20T21:51:26.862Z" }, + { url = "https://files.pythonhosted.org/packages/0f/94/068d4d591d791041732171e7b63c37a54494b2e7d28e88d2167eaa9ad875/ty-0.0.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d75652e9e937f7044b1aca16091193e7ef11dac1c7ec952b7fb8292b7ba1f5f2", size = 10109203, upload-time = "2026-02-20T21:51:11.59Z" }, + { url = "https://files.pythonhosted.org/packages/34/e4/526a4aa56dc0ca2569aaa16880a1ab105c3b416dd70e87e25a05688999f3/ty-0.0.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:563c868edceb8f6ddd5e91113c17d3676b028f0ed380bdb3829b06d9beb90e58", size = 10614200, upload-time = "2026-02-20T21:51:20.298Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3d/b68ab20a34122a395880922587fbfc3adf090d22e0fb546d4d20fe8c2621/ty-0.0.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:502e2a1f948bec563a0454fc25b074bf5cf041744adba8794d024277e151d3b0", size = 11153232, upload-time = "2026-02-20T21:51:14.121Z" }, + { url = "https://files.pythonhosted.org/packages/68/ea/678243c042343fcda7e6af36036c18676c355878dcdcd517639586d2cf9e/ty-0.0.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc881dea97021a3aa29134a476937fd8054775c4177d01b94db27fcfb7aab65b", size = 10832934, upload-time = "2026-02-20T21:51:32.92Z" }, + { url = "https://files.pythonhosted.org/packages/d8/bd/7f8d647cef8b7b346c0163230a37e903c7461c7248574840b977045c77df/ty-0.0.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:421fcc3bc64cab56f48edb863c7c1c43649ec4d78ff71a1acb5366ad723b6021", size = 10700888, upload-time = "2026-02-20T21:51:09.673Z" }, + { url = "https://files.pythonhosted.org/packages/6e/06/cb3620dc48c5d335ba7876edfef636b2f4498eff4a262ff90033b9e88408/ty-0.0.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0fe5038a7136a0e638a2fb1ad06e3d3c4045314c6ba165c9c303b9aeb4623d6c", size = 10078965, upload-time = "2026-02-20T21:51:07.678Z" }, + { url = "https://files.pythonhosted.org/packages/60/27/c77a5a84533fa3b685d592de7b4b108eb1f38851c40fac4e79cc56ec7350/ty-0.0.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d123600a52372677613a719bbb780adeb9b68f47fb5f25acb09171de390e0035", size = 10134659, upload-time = "2026-02-20T21:51:18.311Z" }, + { url = "https://files.pythonhosted.org/packages/43/6e/60af6b88c73469e628ba5253a296da6984e0aa746206f3034c31f1a04ed1/ty-0.0.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb4bc11d32a1bf96a829bf6b9696545a30a196ac77bbc07cc8d3dfee35e03723", size = 10297494, upload-time = "2026-02-20T21:51:39.631Z" }, + { url = "https://files.pythonhosted.org/packages/33/90/612dc0b68224c723faed6adac2bd3f930a750685db76dfe17e6b9e534a83/ty-0.0.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dda2efbf374ba4cd704053d04e32f2f784e85c2ddc2400006b0f96f5f7e4b667", size = 10791944, upload-time = "2026-02-20T21:51:37.13Z" }, + { url = "https://files.pythonhosted.org/packages/0d/da/f4ada0fd08a9e4138fe3fd2bcd3797753593f423f19b1634a814b9b2a401/ty-0.0.18-py3-none-win32.whl", hash = "sha256:c5768607c94977dacddc2f459ace6a11a408a0f57888dd59abb62d28d4fee4f7", size = 9677964, upload-time = "2026-02-20T21:51:42.039Z" }, + { url = "https://files.pythonhosted.org/packages/5e/fa/090ed9746e5c59fc26d8f5f96dc8441825171f1f47752f1778dad690b08b/ty-0.0.18-py3-none-win_amd64.whl", hash = "sha256:b78d0fa1103d36fc2fce92f2092adace52a74654ab7884d54cdaec8eb5016a4d", size = 10636576, upload-time = "2026-02-20T21:51:29.159Z" }, + { url = "https://files.pythonhosted.org/packages/92/4f/5dd60904c8105cda4d0be34d3a446c180933c76b84ae0742e58f02133713/ty-0.0.18-py3-none-win_arm64.whl", hash = "sha256:01770c3c82137c6b216aa3251478f0b197e181054ee92243772de553d3586398", size = 10095449, upload-time = "2026-02-20T21:51:34.914Z" }, ] [[package]] From b32227e69f8d62bc7f47a9d4c7685f2dcce843c4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 15:03:48 -0800 Subject: [PATCH 320/685] BigCircleButton: remove press_state_enabled --- selfdrive/ui/mici/widgets/button.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 3fb5bce9643..f81b7f0df50 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -36,7 +36,6 @@ def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (6 # State self.set_rect(rl.Rectangle(0, 0, 180, 180)) - self._press_state_enabled = True self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) # Icons @@ -49,9 +48,6 @@ def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (6 self._txt_btn_red_bg = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180) self._txt_btn_red_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_red_hover.png", 180, 180) - def set_enable_pressed_state(self, pressed: bool): - self._press_state_enabled = pressed - def _draw_content(self, btn_y: float): # draw icon icon_color = rl.Color(255, 255, 255, int(255 * 0.9)) if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) @@ -63,10 +59,10 @@ def _render(self, _): txt_bg = self._txt_btn_bg if not self._red else self._txt_btn_red_bg if not self.enabled: txt_bg = self._txt_btn_disabled_bg - elif self.is_pressed and self._press_state_enabled: + elif self.is_pressed: txt_bg = self._txt_btn_pressed_bg if not self._red else self._txt_btn_red_pressed_bg - scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed and self._press_state_enabled else 1.0) + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) From 90a9ef277c3253d8f2ffe77ffb824231411a8dda Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 15:17:21 -0800 Subject: [PATCH 321/685] ui: remove multiple option dialog (#37356) * rm * from here too --- selfdrive/ui/mici/tests/test_widget_leaks.py | 3 +- selfdrive/ui/mici/widgets/dialog.py | 146 +------------------ 2 files changed, 2 insertions(+), 147 deletions(-) diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index c7f21bb2f89..7441ed5a224 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog -from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2, BigInputDialog, BigMultiOptionDialog +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2, BigInputDialog from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal # tici dialogs @@ -72,7 +72,6 @@ def test_dialogs_do_not_leak(): lambda: BigDialog("test", "test"), lambda: BigConfirmationDialogV2("test", "icons_mici/settings/network/new/trash.png"), lambda: BigInputDialog("test"), - lambda: BigMultiOptionDialog(["a", "b"], "a"), lambda: MiciFccModal(text="test"), # tici TiciDriverCameraDialog, TiciOnboardingWindow, TiciPairingDialog, Keyboard, diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 32624bbdd6d..8e978066c6b 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -3,15 +3,12 @@ import pyray as rl from typing import Union from collections.abc import Callable -from typing import cast -from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, MouseEvent -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter from openpilot.selfdrive.ui.mici.widgets.button import BigButton @@ -241,147 +238,6 @@ def _handle_mouse_press(self, mouse_pos: MousePos): self._confirm_callback() -class BigDialogOptionButton(Widget): - HEIGHT = 64 - SELECTED_HEIGHT = 74 - - def __init__(self, option: str): - super().__init__() - self.option = option - self.set_rect(rl.Rectangle(0, 0, int(gui_app.width / 2 + 220), self.HEIGHT)) - - self._selected = False - - self._label = UnifiedLabel(option, font_size=70, text_color=rl.Color(255, 255, 255, int(255 * 0.58)), - font_weight=FontWeight.DISPLAY_REGULAR, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, - scroll=True) - - def show_event(self): - super().show_event() - self._label.reset_scroll() - - def set_selected(self, selected: bool): - self._selected = selected - self._rect.height = self.SELECTED_HEIGHT if selected else self.HEIGHT - - def _render(self, _): - if DEBUG: - rl.draw_rectangle_lines_ex(self._rect, 1, rl.Color(0, 255, 0, 255)) - - # FIXME: offset x by -45 because scroller centers horizontally - if self._selected: - self._label.set_font_size(self.SELECTED_HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.9))) - self._label.set_font_weight(FontWeight.DISPLAY) - else: - self._label.set_font_size(self.HEIGHT) - self._label.set_color(rl.Color(255, 255, 255, int(255 * 0.58))) - self._label.set_font_weight(FontWeight.DISPLAY_REGULAR) - - self._label.render(self._rect) - - -class BigMultiOptionDialog(BigDialogBase): - BACK_TOUCH_AREA_PERCENTAGE = 0.1 - - def __init__(self, options: list[str], default: str | None): - super().__init__() - self._options = options - if default is not None: - assert default in options - - self._default_option: str | None = default - self._selected_option: str = self._default_option or (options[0] if len(options) > 0 else "") - self._last_selected_option: str = self._selected_option - - # Widget doesn't differentiate between click and drag - self._can_click = True - - self._scroller = Scroller([], horizontal=False, snap_items=True, pad=100, spacing=0) - - for option in options: - self._scroller.add_widget(BigDialogOptionButton(option)) - - def show_event(self): - super().show_event() - self._scroller.show_event() - if self._default_option is not None: - self._on_option_selected(self._default_option) - - def get_selected_option(self) -> str: - return self._selected_option - - def _on_option_selected(self, option: str): - y_pos = 0.0 - for btn in self._scroller.items: - btn = cast(BigDialogOptionButton, btn) - if btn.option == option: - rect_center_y = self._rect.y + self._rect.height / 2 - if btn._selected: - height = btn.rect.height - else: - # when selecting an option under current, account for changing heights - btn_center_y = btn.rect.y + btn.rect.height / 2 # not accurate, just to determine direction - height_offset = BigDialogOptionButton.SELECTED_HEIGHT - BigDialogOptionButton.HEIGHT - height = (BigDialogOptionButton.HEIGHT - height_offset) if rect_center_y < btn_center_y else BigDialogOptionButton.SELECTED_HEIGHT - y_pos = rect_center_y - (btn.rect.y + height / 2) - break - - self._scroller.scroll_to(-y_pos) - - def _selected_option_changed(self): - pass - - def _handle_mouse_press(self, mouse_pos: MousePos): - super()._handle_mouse_press(mouse_pos) - self._can_click = True - - def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: - super()._handle_mouse_event(mouse_event) - - # # TODO: add generic _handle_mouse_click handler to Widget - if not self._scroller.scroll_panel.is_touch_valid(): - self._can_click = False - - def _handle_mouse_release(self, mouse_pos: MousePos): - super()._handle_mouse_release(mouse_pos) - - if not self._can_click: - return - - # select current option - for btn in self._scroller.items: - btn = cast(BigDialogOptionButton, btn) - if btn.option == self._selected_option: - self._on_option_selected(btn.option) - break - - def _update_state(self): - super()._update_state() - - # get selection by whichever button is closest to center - center_y = self._rect.y + self._rect.height / 2 - closest_btn = (None, float('inf')) - for btn in self._scroller.items: - dist_y = abs((btn.rect.y + btn.rect.height / 2) - center_y) - if dist_y < closest_btn[1]: - closest_btn = (btn, dist_y) - - if closest_btn[0]: - for btn in self._scroller.items: - btn.set_selected(btn.option == closest_btn[0].option) - self._selected_option = closest_btn[0].option - - # Signal to subclasses if selection changed - if self._selected_option != self._last_selected_option: - self._selected_option_changed() - self._last_selected_option = self._selected_option - - def _render(self, _): - super()._render(_) - self._scroller.render(self._rect) - - class BigDialogButton(BigButton): def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", description: str = ""): super().__init__(text, value, icon) From 76d084d87706372563a8094196fc3ab16451c662 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 16:34:42 -0800 Subject: [PATCH 322/685] switch to system compilers (GCC on Linux, Apple Clang on macOS) (#37355) --- SConstruct | 8 ++++---- common/prefix.h | 8 ++++---- common/ratekeeper.cc | 6 +++--- common/tests/test_util.cc | 10 +++++----- common/util.cc | 8 ++++---- common/util.h | 7 +++++++ msgq_repo | 2 +- opendbc_repo | 2 +- rednose_repo | 2 +- selfdrive/pandad/spi.cc | 2 +- selfdrive/pandad/tests/test_pandad_canprotocol.cc | 4 ++-- selfdrive/ui/SConscript | 2 +- system/loggerd/encoder/v4l_encoder.cc | 10 +++++----- system/loggerd/loggerd.cc | 4 ++-- system/loggerd/loggerd.h | 4 ++-- system/loggerd/tests/test_logger.cc | 2 +- tools/cabana/chart/chart.cc | 2 +- tools/cabana/signalview.cc | 4 ++-- tools/cabana/utils/util.cc | 4 ++-- tools/cabana/videowidget.cc | 13 +++++++------ tools/install_ubuntu_dependencies.sh | 1 - tools/replay/qcom_decoder.cc | 2 +- 22 files changed, 57 insertions(+), 50 deletions(-) diff --git a/SConstruct b/SConstruct index cb1c97d158b..446fcc417d3 100644 --- a/SConstruct +++ b/SConstruct @@ -56,15 +56,13 @@ env = Environment( "ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath, "TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer" }, - CC='clang', - CXX='clang++', CCFLAGS=[ "-g", "-fPIC", "-O2", "-Wunused", "-Werror", - "-Wshadow", + "-Wshadow" if arch in ("Darwin", "larch64") else "-Wshadow=local", "-Wno-unknown-warning-option", "-Wno-inconsistent-missing-override", "-Wno-c99-designator", @@ -106,6 +104,8 @@ env = Environment( # Arch-specific flags and paths if arch == "larch64": + env["CC"] = "clang" + env["CXX"] = "clang++" env.Append(LIBPATH=[ "/usr/local/lib", "/system/vendor/lib64", @@ -162,7 +162,7 @@ if os.environ.get('SCONS_PROGRESS'): py_include = sysconfig.get_paths()['include'] envCython = env.Clone() envCython["CPPPATH"] += [py_include, np.get_include()] -envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-shadow", "-Wno-deprecated-declarations"] +envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"] envCython["CCFLAGS"].remove("-Werror") envCython["LIBS"] = [] diff --git a/common/prefix.h b/common/prefix.h index 30ee18f6375..de3a94d71f2 100644 --- a/common/prefix.h +++ b/common/prefix.h @@ -27,14 +27,14 @@ class OpenpilotPrefix { auto param_path = Params().getParamPath(); if (util::file_exists(param_path)) { std::string real_path = util::readlink(param_path); - system(util::string_format("rm %s -rf", real_path.c_str()).c_str()); + util::check_system(util::string_format("rm %s -rf", real_path.c_str())); unlink(param_path.c_str()); } if (getenv("COMMA_CACHE") == nullptr) { - system(util::string_format("rm %s -rf", Path::download_cache_root().c_str()).c_str()); + util::check_system(util::string_format("rm %s -rf", Path::download_cache_root().c_str())); } - system(util::string_format("rm %s -rf", Path::comma_home().c_str()).c_str()); - system(util::string_format("rm %s -rf", msgq_path.c_str()).c_str()); + util::check_system(util::string_format("rm %s -rf", Path::comma_home().c_str())); + util::check_system(util::string_format("rm %s -rf", msgq_path.c_str())); unsetenv("OPENPILOT_PREFIX"); } diff --git a/common/ratekeeper.cc b/common/ratekeeper.cc index 7e63815168d..a79acd7d516 100644 --- a/common/ratekeeper.cc +++ b/common/ratekeeper.cc @@ -6,9 +6,9 @@ #include "common/timing.h" #include "common/util.h" -RateKeeper::RateKeeper(const std::string &name, float rate, float print_delay_threshold) - : name(name), - print_delay_threshold(std::max(0.f, print_delay_threshold)) { +RateKeeper::RateKeeper(const std::string &name_, float rate, float print_delay_threshold_) + : name(name_), + print_delay_threshold(std::max(0.f, print_delay_threshold_)) { interval = 1 / rate; last_monitor_time = seconds_since_boot(); next_frame_time = last_monitor_time + interval; diff --git a/common/tests/test_util.cc b/common/tests/test_util.cc index de87fa3e064..d927b98a4d1 100644 --- a/common/tests/test_util.cc +++ b/common/tests/test_util.cc @@ -36,7 +36,7 @@ TEST_CASE("util::read_file") { REQUIRE(util::read_file(filename).empty()); std::string content = random_bytes(64 * 1024); - write(fd, content.c_str(), content.size()); + REQUIRE(write(fd, content.c_str(), content.size()) == (ssize_t)content.size()); std::string ret = util::read_file(filename); bool equal = (ret == content); REQUIRE(equal); @@ -114,12 +114,12 @@ TEST_CASE("util::safe_fwrite") { } TEST_CASE("util::create_directories") { - system("rm /tmp/test_create_directories -rf"); + REQUIRE(system("rm /tmp/test_create_directories -rf") == 0); std::string dir = "/tmp/test_create_directories/a/b/c/d/e/f"; - auto check_dir_permissions = [](const std::string &dir, mode_t mode) -> bool { + auto check_dir_permissions = [](const std::string &path, mode_t mode) -> bool { struct stat st = {}; - return stat(dir.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode; + return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR && (st.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)) == mode; }; SECTION("create_directories") { @@ -132,7 +132,7 @@ TEST_CASE("util::create_directories") { } SECTION("a file exists with the same name") { REQUIRE(util::create_directories(dir, 0755)); - int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT); + int f = open((dir + "/file").c_str(), O_RDWR | O_CREAT, 0644); REQUIRE(f != -1); close(f); REQUIRE(util::create_directories(dir + "/file", 0755) == false); diff --git a/common/util.cc b/common/util.cc index 26a2bd60bc4..84b47e187ee 100644 --- a/common/util.cc +++ b/common/util.cc @@ -181,9 +181,9 @@ bool file_exists(const std::string& fn) { } static bool createDirectory(std::string dir, mode_t mode) { - auto verify_dir = [](const std::string& dir) -> bool { + auto verify_dir = [](const std::string& path) -> bool { struct stat st = {}; - return (stat(dir.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR); + return (stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFMT) == S_IFDIR); }; // remove trailing /'s while (dir.size() > 1 && dir.back() == '/') { @@ -288,7 +288,7 @@ std::string strip(const std::string &str) { std::string check_output(const std::string& command) { char buffer[128]; std::string result; - std::unique_ptr pipe(popen(command.c_str(), "r"), pclose); + std::unique_ptr pipe(popen(command.c_str(), "r"), pclose); if (!pipe) { return ""; @@ -303,7 +303,7 @@ std::string check_output(const std::string& command) { bool system_time_valid() { // Default to August 26, 2024 - tm min_tm = {.tm_year = 2024 - 1900, .tm_mon = 7, .tm_mday = 26}; + tm min_tm = {.tm_mday = 26, .tm_mon = 7, .tm_year = 2024 - 1900}; time_t min_date = mktime(&min_tm); struct stat st; diff --git a/common/util.h b/common/util.h index f46db4d9fa2..e4483ee7a57 100644 --- a/common/util.h +++ b/common/util.h @@ -96,6 +96,13 @@ bool create_directories(const std::string &dir, mode_t mode); std::string check_output(const std::string& command); +inline void check_system(const std::string& cmd) { + int ret = std::system(cmd.c_str()); + if (ret != 0) { + fprintf(stderr, "system command failed (%d): %s\n", ret, cmd.c_str()); + } +} + bool system_time_valid(); inline void sleep_for(const int milliseconds) { diff --git a/msgq_repo b/msgq_repo index fa514e7dd77..ed2777747d6 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit fa514e7dd77025952d94e893c622a5006e260321 +Subproject commit ed2777747d60de5a399b74ef1d4be4c1fb406ae1 diff --git a/opendbc_repo b/opendbc_repo index b10b43105bc..ffe10c0c809 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit b10b43105bcd891f95920aac29d01177e0be4940 +Subproject commit ffe10c0c809cc48726292c832b109a14fee603be diff --git a/rednose_repo b/rednose_repo index 7fddc8e6d49..6ccb8d05565 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit 7fddc8e6d49def83c952a78673179bdc62789214 +Subproject commit 6ccb8d055652cd9769b5e418edf116272fde4e09 diff --git a/selfdrive/pandad/spi.cc b/selfdrive/pandad/spi.cc index 25682e6b17c..f54c26e5069 100644 --- a/selfdrive/pandad/spi.cc +++ b/selfdrive/pandad/spi.cc @@ -32,7 +32,7 @@ const std::string SPI_DEVICE = "/dev/spidev0.0"; class LockEx { public: - LockEx(int fd, std::recursive_mutex &m) : fd(fd), m(m) { + LockEx(int fd_, std::recursive_mutex &m_) : fd(fd_), m(m_) { m.lock(); flock(fd, LOCK_EX); } diff --git a/selfdrive/pandad/tests/test_pandad_canprotocol.cc b/selfdrive/pandad/tests/test_pandad_canprotocol.cc index 9b53392f6ba..8499a1aab86 100644 --- a/selfdrive/pandad/tests/test_pandad_canprotocol.cc +++ b/selfdrive/pandad/tests/test_pandad_canprotocol.cc @@ -21,8 +21,8 @@ struct PandaTest : public Panda { capnp::List::Reader can_data_list; }; -PandaTest::PandaTest(int can_list_size, cereal::PandaState::PandaType hw_type) : can_list_size(can_list_size), Panda() { - this->hw_type = hw_type; +PandaTest::PandaTest(int can_list_size_, cereal::PandaState::PandaType hw_type_) : can_list_size(can_list_size_), Panda() { + this->hw_type = hw_type_; int data_limit = ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? std::size(dlc_to_len) : 8); // prepare test data for (int i = 0; i < data_limit; ++i) { diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 0de3e13c011..a03a26bea83 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -68,4 +68,4 @@ if GetOption('extras'): obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter, inter_bold, inter_light], LIBS=raylib_libs) # keep installers small - assert f[0].get_size() < 1900*1e3, f[0].get_size() + assert f[0].get_size() < 2500*1e3, f[0].get_size() diff --git a/system/loggerd/encoder/v4l_encoder.cc b/system/loggerd/encoder/v4l_encoder.cc index 6ee3af13b0b..383fa2f0f55 100644 --- a/system/loggerd/encoder/v4l_encoder.cc +++ b/system/loggerd/encoder/v4l_encoder.cc @@ -43,29 +43,29 @@ static void dequeue_buffer(int fd, v4l2_buf_type buf_type, unsigned int *index=N static void queue_buffer(int fd, v4l2_buf_type buf_type, unsigned int index, VisionBuf *buf, struct timeval timestamp={}) { v4l2_plane plane = { + .bytesused = (uint32_t)buf->len, .length = (unsigned int)buf->len, .m = { .userptr = (unsigned long)buf->addr, }, - .bytesused = (uint32_t)buf->len, .reserved = {(unsigned int)buf->fd} }; v4l2_buffer v4l_buf = { - .type = buf_type, .index = index, + .type = buf_type, + .flags = V4L2_BUF_FLAG_TIMESTAMP_COPY, + .timestamp = timestamp, .memory = V4L2_MEMORY_USERPTR, .m = { .planes = &plane, }, .length = 1, - .flags = V4L2_BUF_FLAG_TIMESTAMP_COPY, - .timestamp = timestamp }; util::safe_ioctl(fd, VIDIOC_QBUF, &v4l_buf, "VIDIOC_QBUF failed"); } static void request_buffers(int fd, v4l2_buf_type buf_type, unsigned int count) { struct v4l2_requestbuffers reqbuf = { + .count = count, .type = buf_type, .memory = V4L2_MEMORY_USERPTR, - .count = count }; util::safe_ioctl(fd, VIDIOC_REQBUFS, &reqbuf, "VIDIOC_REQBUFS failed"); } diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 47da321024c..37559296192 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -219,11 +219,11 @@ void handle_preserve_segment(LoggerdState *s) { void loggerd_thread() { // setup messaging - typedef struct ServiceState { + struct ServiceState { std::string name; int counter, freq; bool encoder, preserve_segment, record_audio; - } ServiceState; + }; std::unordered_map service_state; std::unordered_map remote_encoders; diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h index 8e3a74d2d98..6aa0c8be40b 100644 --- a/system/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -125,10 +125,10 @@ const EncoderInfo stream_driver_encoder_info = { const EncoderInfo qcam_encoder_info = { .publish_name = "qRoadEncodeData", .filename = "qcamera.ts", - .get_settings = [](int){return EncoderSettings::QcamEncoderSettings();}, + .include_audio = Params().getBool("RecordAudio"), .frame_width = 526, .frame_height = 330, - .include_audio = Params().getBool("RecordAudio"), + .get_settings = [](int){return EncoderSettings::QcamEncoderSettings();}, INIT_ENCODE_FUNCTIONS(QRoadEncode), }; diff --git a/system/loggerd/tests/test_logger.cc b/system/loggerd/tests/test_logger.cc index 40a45a68d5c..61509c256c9 100644 --- a/system/loggerd/tests/test_logger.cc +++ b/system/loggerd/tests/test_logger.cc @@ -56,7 +56,7 @@ void write_msg(LoggerState *logger) { TEST_CASE("logger") { const int segment_cnt = 100; const std::string log_root = "/tmp/test_logger"; - system(("rm " + log_root + " -rf").c_str()); + REQUIRE(system(("rm " + log_root + " -rf").c_str()) == 0); std::string route_name; { LoggerState logger(log_root); diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index bc2380e5501..14491b1effe 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -571,7 +571,7 @@ void ChartView::showTip(double sec) { if (s.series->isVisible()) { QString value = "--"; // use reverse iterator to find last item <= sec. - auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), sec, [](auto &p, double x) { return p.x() > x; }); + auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), sec, [](auto &p, double v) { return p.x() > v; }); if (it != s.vals.crend() && it->x() >= axis_x->min()) { value = s.sig->formatValue(it->y(), false); s.track_pt = *it; diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index 0a0c07a155a..a9ceacd8061 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -34,12 +34,12 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p } void SignalModel::insertItem(SignalModel::Item *root_item, int pos, const cabana::Signal *sig) { - Item *parent_item = new Item{.sig = sig, .parent = root_item, .title = sig->name, .type = Item::Sig}; + Item *parent_item = new Item{.type = Item::Sig, .parent = root_item, .sig = sig, .title = sig->name}; root_item->children.insert(pos, parent_item); QString titles[]{"Name", "Size", "Receiver Nodes", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Table"}; for (int i = 0; i < std::size(titles); ++i) { - auto item = new Item{.sig = sig, .parent = parent_item, .title = titles[i], .type = (Item::Type)(i + Item::Name)}; + auto item = new Item{.type = (Item::Type)(i + Item::Name), .parent = parent_item, .sig = sig, .title = titles[i]}; parent_item->children.push_back(item); if (item->type == Item::ExtraInfo) { parent_item = item; diff --git a/tools/cabana/utils/util.cc b/tools/cabana/utils/util.cc index ca069b358d2..61e4ee05140 100644 --- a/tools/cabana/utils/util.cc +++ b/tools/cabana/utils/util.cc @@ -166,13 +166,13 @@ UnixSignalHandler::~UnixSignalHandler() { } void UnixSignalHandler::signalHandler(int s) { - ::write(sig_fd[0], &s, sizeof(s)); + (void)!::write(sig_fd[0], &s, sizeof(s)); } void UnixSignalHandler::handleSigTerm() { sn->setEnabled(false); int tmp; - ::read(sig_fd[1], &tmp, sizeof(tmp)); + (void)!::read(sig_fd[1], &tmp, sizeof(tmp)); printf("\nexiting...\n"); qApp->closeAllWindows(); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 55018f28f08..a05bf24b4af 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -16,13 +16,14 @@ const int MIN_VIDEO_HEIGHT = 100; const int THUMBNAIL_MARGIN = 3; +// Indexed by TimelineType: None, Engaged, AlertInfo, AlertWarning, AlertCritical, UserBookmark static const QColor timeline_colors[] = { - [(int)TimelineType::None] = QColor(111, 143, 175), - [(int)TimelineType::Engaged] = QColor(0, 163, 108), - [(int)TimelineType::UserBookmark] = Qt::magenta, - [(int)TimelineType::AlertInfo] = Qt::green, - [(int)TimelineType::AlertWarning] = QColor(255, 195, 0), - [(int)TimelineType::AlertCritical] = QColor(199, 0, 57), + QColor(111, 143, 175), + QColor(0, 163, 108), + Qt::green, + QColor(255, 195, 0), + QColor(199, 0, 57), + Qt::magenta, }; static Replay *getReplay() { diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh index 4a1cf9a8cbf..6793cf2d19e 100755 --- a/tools/install_ubuntu_dependencies.sh +++ b/tools/install_ubuntu_dependencies.sh @@ -24,7 +24,6 @@ function install_ubuntu_common_requirements() { # normal stuff, mostly for the bare docker image $SUDO apt-get install -y --no-install-recommends \ ca-certificates \ - clang \ build-essential \ curl \ libssl-dev \ diff --git a/tools/replay/qcom_decoder.cc b/tools/replay/qcom_decoder.cc index eb5409daa33..44ff16ce4fa 100644 --- a/tools/replay/qcom_decoder.cc +++ b/tools/replay/qcom_decoder.cc @@ -175,8 +175,8 @@ bool MsmVidc::setPlaneFormat(enum v4l2_buf_type type, uint32_t fourcc) { bool MsmVidc::setFPS(uint32_t fps) { struct v4l2_streamparm streamparam = { .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, - .parm.output.timeperframe = {1, fps} }; + streamparam.parm.output.timeperframe = {1, fps}; util::safe_ioctl(fd, VIDIOC_S_PARM, &streamparam, "VIDIOC_S_PARM failed"); return true; } From 16dda06a0c040040b154ab8ea0360421fb5857ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 23 Feb 2026 16:49:48 -0800 Subject: [PATCH 323/685] Reapply chunker (#37292) * Reapply chunker * good size * rm glob * cleaner * back to 45mb * warp need not be fixed * add manifest path * lil cleaner --- .gitignore | 3 +- common/file_chunker.py | 37 +++++++++++++++++++++++++ selfdrive/modeld/SConscript | 40 ++++++++++++--------------- selfdrive/modeld/dmonitoringmodeld.py | 4 +-- selfdrive/modeld/external_pickle.py | 38 ------------------------- selfdrive/modeld/modeld.py | 6 ++-- 6 files changed, 60 insertions(+), 68 deletions(-) create mode 100644 common/file_chunker.py delete mode 100755 selfdrive/modeld/external_pickle.py diff --git a/.gitignore b/.gitignore index af18f06628a..062801d7874 100644 --- a/.gitignore +++ b/.gitignore @@ -64,8 +64,7 @@ flycheck_* cppcheck_report.txt comma*.sh -selfdrive/modeld/models/*.pkl -selfdrive/modeld/models/*.pkl.* +selfdrive/modeld/models/*.pkl* # openpilot log files *.bz2 diff --git a/common/file_chunker.py b/common/file_chunker.py new file mode 100644 index 00000000000..ac9ddbb3848 --- /dev/null +++ b/common/file_chunker.py @@ -0,0 +1,37 @@ +import math +import os +from pathlib import Path + +CHUNK_SIZE = 45 * 1024 * 1024 # 45MB, under GitHub's 50MB limit + +def get_chunk_name(name, idx, num_chunks): + return f"{name}.chunk{idx+1:02d}of{num_chunks:02d}" + +def get_manifest_path(name): + return f"{name}.chunkmanifest" + +def get_chunk_paths(path, file_size): + num_chunks = math.ceil(file_size / CHUNK_SIZE) + return [get_manifest_path(path)] + [get_chunk_name(path, i, num_chunks) for i in range(num_chunks)] + +def chunk_file(path, targets): + manifest_path, *chunk_paths = targets + with open(path, 'rb') as f: + data = f.read() + actual_num_chunks = max(1, math.ceil(len(data) / CHUNK_SIZE)) + assert len(chunk_paths) >= actual_num_chunks, f"Allowed {len(chunk_paths)} chunks but needs at least {actual_num_chunks}, for path {path}" + for i, chunk_path in enumerate(chunk_paths): + with open(chunk_path, 'wb') as f: + f.write(data[i * CHUNK_SIZE:(i + 1) * CHUNK_SIZE]) + Path(manifest_path).write_text(str(len(chunk_paths))) + os.remove(path) + + +def read_file_chunked(path): + manifest_path = get_manifest_path(path) + if os.path.isfile(manifest_path): + num_chunks = int(Path(manifest_path).read_text().strip()) + return b''.join(Path(get_chunk_name(path, i, num_chunks)).read_bytes() for i in range(num_chunks)) + if os.path.isfile(path): + return Path(path).read_bytes() + raise FileNotFoundError(path) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 1808cfec2f5..ed5c50beb3c 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,14 +1,18 @@ import os import glob +from openpilot.common.file_chunker import chunk_file, get_chunk_paths Import('env', 'arch') +chunker_file = File("#common/file_chunker.py") lenv = env.Clone() -CHUNK_BYTES = int(os.environ.get("TG_CHUNK_BYTES", str(45 * 1024 * 1024))) tinygrad_root = env.Dir("#").abspath tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + "/**", recursive=True, root_dir=tinygrad_root) if 'pycache' not in x and os.path.isfile(os.path.join(tinygrad_root, x))] +def estimate_pickle_max_size(onnx_size): + return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty + # Get model metadata for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath @@ -26,39 +30,29 @@ image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +compile_warp_cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye warp_targets = [] for cam in [_ar_ox_fisheye, _os_fisheye]: w, h = cam.width, cam.height warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command(warp_targets, tinygrad_files + script_files, cmd) +lenv.Command(warp_targets, tinygrad_files + script_files, compile_warp_cmd) def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' fn = File(f"models/{model_name}").abspath - - out = fn + "_tinygrad.pkl" - full = out + ".full" - parts = out + ".parts" - - full_node = lenv.Command( - full, - [fn + ".onnx"] + tinygrad_files, - f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {full}' + pkl = fn + "_tinygrad.pkl" + onnx_path = fn + ".onnx" + chunk_targets = get_chunk_paths(pkl, estimate_pickle_max_size(os.path.getsize(onnx_path))) + def do_chunk(target, source, env): + chunk_file(pkl, chunk_targets) + return lenv.Command( + chunk_targets, + [onnx_path] + tinygrad_files + [chunker_file], + [f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', + do_chunk] ) - split_script = File(Dir("#selfdrive/modeld").File("external_pickle.py").abspath) - parts_node = lenv.Command( - parts, - [full_node, split_script, Value(str(CHUNK_BYTES))], - [f'python3 {split_script.abspath} {full} {out} {CHUNK_BYTES}', Delete(full)], - ) - - lenv.NoCache(parts_node) - lenv.AlwaysBuild(parts_node) - return parts_node - # Compile small models for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: tg_compile(tg_flags, model_name) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 956ea8a6a26..28190db3e68 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -16,8 +16,8 @@ from openpilot.common.transformations.model import dmonitoringmodel_intrinsics from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.parse_model_outputs import sigmoid, safe_exp -from openpilot.selfdrive.modeld.external_pickle import load_external_pickle PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') @@ -45,7 +45,7 @@ def __init__(self): self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self._blob_cache : dict[int, Tensor] = {} self.image_warp = None - self.model_run = load_external_pickle(MODEL_PKL_PATH) + self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH))) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib diff --git a/selfdrive/modeld/external_pickle.py b/selfdrive/modeld/external_pickle.py deleted file mode 100755 index d60a9632a64..00000000000 --- a/selfdrive/modeld/external_pickle.py +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env python3 -import hashlib -import pickle -import sys -from pathlib import Path - -def split_pickle(full_path: Path, out_prefix: Path, chunk_bytes: int) -> None: - data = full_path.read_bytes() - out_dir = out_prefix.parent - - for p in out_dir.glob(f"{out_prefix.name}.data-*"): - p.unlink() - - total = (len(data) + chunk_bytes - 1) // chunk_bytes - names = [] - for i in range(0, len(data), chunk_bytes): - name = f"{out_prefix.name}.data-{(i // chunk_bytes) + 1:04d}-of-{total:04d}" - (out_dir / name).write_bytes(data[i:i + chunk_bytes]) - names.append(name) - - manifest = hashlib.sha256(data).hexdigest() + "\n" + "\n".join(names) + "\n" - (out_dir / (out_prefix.name + ".parts")).write_text(manifest) - -def load_external_pickle(prefix: Path): - parts = prefix.parent / (prefix.name + ".parts") - lines = parts.read_text().splitlines() - expected_hash, chunk_names = lines[0], lines[1:] - - data = bytearray() - for name in chunk_names: - data += (prefix.parent / name).read_bytes() - - if hashlib.sha256(data).hexdigest() != expected_hash: - raise RuntimeError(f"hash mismatch loading {prefix}") - return pickle.loads(data) - -if __name__ == "__main__": - split_pickle(Path(sys.argv[1]), Path(sys.argv[2]), int(sys.argv[3])) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 3fe3e0e6d6a..bff59366d6e 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -27,8 +27,8 @@ from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState +from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan -from openpilot.selfdrive.modeld.external_pickle import load_external_pickle PROCESS_NAME = "selfdrive.modeld.modeld" @@ -178,8 +178,8 @@ def __init__(self): self.parser = Parser() self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None - self.vision_run = load_external_pickle(VISION_PKL_PATH) - self.policy_run = load_external_pickle(POLICY_PKL_PATH) + self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) + self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} From 5af3f3215737a7741941232f51d4bd7b6b010cfa Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 16:56:58 -0800 Subject: [PATCH 324/685] simplify setup (#37358) * simplify setup * lil more * simplify dockedr * just run setup there: * don't need that junk * lil more --- .github/workflows/prebuilt.yaml | 2 +- .github/workflows/release.yaml | 13 +-- Dockerfile.openpilot | 36 +++++-- Dockerfile.openpilot_base | 42 -------- selfdrive/test/docker_build.sh | 16 ++-- selfdrive/test/docker_common.sh | 18 ---- tools/install_python_dependencies.sh | 29 ------ tools/install_ubuntu_dependencies.sh | 99 ------------------- tools/mac_setup.sh | 20 ---- tools/op.sh | 6 +- tools/setup_dependencies.sh | 137 +++++++++++++++++++++++++++ tools/ubuntu_setup.sh | 10 -- 12 files changed, 180 insertions(+), 248 deletions(-) delete mode 100644 Dockerfile.openpilot_base delete mode 100644 selfdrive/test/docker_common.sh delete mode 100755 tools/install_python_dependencies.sh delete mode 100755 tools/install_ubuntu_dependencies.sh delete mode 100755 tools/mac_setup.sh create mode 100755 tools/setup_dependencies.sh delete mode 100755 tools/ubuntu_setup.sh diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index c0a2baa8e3e..ecf1e8503ae 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -6,7 +6,7 @@ on: env: DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} - BUILD: selfdrive/test/docker_build.sh prebuilt + BUILD: selfdrive/test/docker_build.sh jobs: build_prebuilt: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 159902d5196..4d731965d7c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,20 +7,12 @@ on: jobs: build_masterci: name: build master-ci - env: - ImageOS: ubuntu24 - container: - image: ghcr.io/commaai/openpilot-base:latest runs-on: ubuntu-latest if: github.repository == 'commaai/openpilot' permissions: checks: read contents: write steps: - - name: Install wait-on-check-action dependencies - run: | - sudo apt-get update - sudo apt-get install -y libyaml-dev - name: Wait for green check mark if: ${{ github.event_name == 'schedule' }} uses: lewagon/wait-on-check-action@ccfb013c15c8afb7bf2b7c028fb74dc5a068cccc @@ -34,9 +26,6 @@ jobs: with: submodules: true fetch-depth: 0 - - name: Pull LFS - run: | - git config --global --add safe.directory '*' - git lfs pull + - uses: ./.github/workflows/setup-with-retry - name: Push master-ci run: BRANCH=__nightly release/build_stripped.sh diff --git a/Dockerfile.openpilot b/Dockerfile.openpilot index 106a06e3a20..72d874b022b 100644 --- a/Dockerfile.openpilot +++ b/Dockerfile.openpilot @@ -1,14 +1,38 @@ -FROM ghcr.io/commaai/openpilot-base:latest +FROM ubuntu:24.04 ENV PYTHONUNBUFFERED=1 -ENV OPENPILOT_PATH=/home/batman/openpilot +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && \ + apt-get install -y --no-install-recommends sudo tzdata locales && \ + rm -rf /var/lib/apt/lists/* +RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen +ENV LANG=en_US.UTF-8 +ENV LANGUAGE=en_US:en +ENV LC_ALL=en_US.UTF-8 + +ENV NVIDIA_VISIBLE_DEVICES=all +ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute + +ARG USER=batman +ARG USER_UID=1001 +RUN useradd -m -s /bin/bash -u $USER_UID $USER +RUN usermod -aG sudo $USER +RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers +USER $USER + +ENV OPENPILOT_PATH=/home/$USER/openpilot RUN mkdir -p ${OPENPILOT_PATH} WORKDIR ${OPENPILOT_PATH} -COPY . ${OPENPILOT_PATH}/ +COPY --chown=$USER . ${OPENPILOT_PATH}/ + +ENV UV_BIN="/home/$USER/.local/bin/" +ENV VIRTUAL_ENV=${OPENPILOT_PATH}/.venv +ENV PATH="$UV_BIN:$VIRTUAL_ENV/bin:$PATH" +RUN tools/setup_dependencies.sh && \ + sudo rm -rf /var/lib/apt/lists/* -ENV UV_BIN="/home/batman/.local/bin/" -ENV PATH="$UV_BIN:$PATH" -RUN UV_PROJECT_ENVIRONMENT=$VIRTUAL_ENV uv run scons --cache-readonly -j$(nproc) +USER root +RUN git config --global --add safe.directory '*' diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base deleted file mode 100644 index 6ea1450bee4..00000000000 --- a/Dockerfile.openpilot_base +++ /dev/null @@ -1,42 +0,0 @@ -FROM ubuntu:24.04 - -ENV PYTHONUNBUFFERED=1 - -ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && \ - apt-get install -y --no-install-recommends sudo tzdata locales ssh pulseaudio xvfb x11-xserver-utils gnome-screenshot python3-tk python3-dev && \ - rm -rf /var/lib/apt/lists/* - -RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen -ENV LANG=en_US.UTF-8 -ENV LANGUAGE=en_US:en -ENV LC_ALL=en_US.UTF-8 - -COPY tools/install_ubuntu_dependencies.sh /tmp/tools/ -RUN /tmp/tools/install_ubuntu_dependencies.sh && \ - rm -rf /var/lib/apt/lists/* /tmp/* - -ENV NVIDIA_VISIBLE_DEVICES=all -ENV NVIDIA_DRIVER_CAPABILITIES=graphics,utility,compute -ENV QTWEBENGINE_DISABLE_SANDBOX=1 - -RUN dbus-uuidgen > /etc/machine-id - -ARG USER=batman -ARG USER_UID=1001 -RUN useradd -m -s /bin/bash -u $USER_UID $USER -RUN usermod -aG sudo $USER -RUN echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers -USER $USER - -COPY --chown=$USER pyproject.toml uv.lock /home/$USER -COPY --chown=$USER tools/install_python_dependencies.sh /home/$USER/tools/ - -ENV VIRTUAL_ENV=/home/$USER/.venv -ENV PATH="$VIRTUAL_ENV/bin:$PATH" -RUN cd /home/$USER && \ - tools/install_python_dependencies.sh && \ - rm -rf tools/ pyproject.toml uv.lock .cache - -USER root -RUN sudo git config --global --add safe.directory /tmp/openpilot diff --git a/selfdrive/test/docker_build.sh b/selfdrive/test/docker_build.sh index 4d58a1507c2..8d1fa822498 100755 --- a/selfdrive/test/docker_build.sh +++ b/selfdrive/test/docker_build.sh @@ -1,12 +1,14 @@ #!/usr/bin/env bash set -e -# To build sim and docs, you can run the following to mount the scons cache to the same place as in CI: -# mkdir -p .ci_cache/scons_cache -# sudo mount --bind /tmp/scons_cache/ .ci_cache/scons_cache - SCRIPT_DIR=$(dirname "$0") OPENPILOT_DIR=$SCRIPT_DIR/../../ + +DOCKER_IMAGE=openpilot +DOCKER_FILE=Dockerfile.openpilot +DOCKER_REGISTRY=ghcr.io/commaai +COMMIT_SHA=$(git rev-parse HEAD) + if [ -n "$TARGET_ARCHITECTURE" ]; then PLATFORM="linux/$TARGET_ARCHITECTURE" TAG_SUFFIX="-$TARGET_ARCHITECTURE" @@ -15,9 +17,11 @@ else TAG_SUFFIX="" fi -source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX" +LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX +REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG +REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA -DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $DOCKER_IMAGE:latest -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR +DOCKER_BUILDKIT=1 docker buildx build --provenance false --pull --platform $PLATFORM --load -t $DOCKER_IMAGE:latest -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR if [ -n "$PUSH_IMAGE" ]; then docker push $REMOTE_TAG diff --git a/selfdrive/test/docker_common.sh b/selfdrive/test/docker_common.sh deleted file mode 100644 index 2887fff74bc..00000000000 --- a/selfdrive/test/docker_common.sh +++ /dev/null @@ -1,18 +0,0 @@ -if [ "$1" = "base" ]; then - export DOCKER_IMAGE=openpilot-base - export DOCKER_FILE=Dockerfile.openpilot_base -elif [ "$1" = "prebuilt" ]; then - export DOCKER_IMAGE=openpilot-prebuilt - export DOCKER_FILE=Dockerfile.openpilot -else - echo "Invalid docker build image: '$1'" - exit 1 -fi - -export DOCKER_REGISTRY=ghcr.io/commaai -export COMMIT_SHA=$(git rev-parse HEAD) - -TAG_SUFFIX=$2 -LOCAL_TAG=$DOCKER_IMAGE$TAG_SUFFIX -REMOTE_TAG=$DOCKER_REGISTRY/$LOCAL_TAG -REMOTE_SHA_TAG=$DOCKER_REGISTRY/$LOCAL_TAG:$COMMIT_SHA diff --git a/tools/install_python_dependencies.sh b/tools/install_python_dependencies.sh deleted file mode 100755 index 4454845fcd0..00000000000 --- a/tools/install_python_dependencies.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Increase the pip timeout to handle TimeoutError -export PIP_DEFAULT_TIMEOUT=200 - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -ROOT="$DIR"/../ -cd "$ROOT" - -if ! command -v "uv" > /dev/null 2>&1; then - echo "installing uv..." - curl -LsSf --retry 5 --retry-delay 5 --retry-all-errors https://astral.sh/uv/install.sh | sh - UV_BIN="$HOME/.local/bin" - PATH="$UV_BIN:$PATH" -fi - -echo "updating uv..." -# ok to fail, can also fail due to installing with brew -uv self update || true - -echo "installing python packages..." -uv sync --frozen --all-extras -source .venv/bin/activate - -if [[ "$(uname)" == 'Darwin' ]]; then - touch "$ROOT"/.env - echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> "$ROOT"/.env -fi diff --git a/tools/install_ubuntu_dependencies.sh b/tools/install_ubuntu_dependencies.sh deleted file mode 100755 index 6793cf2d19e..00000000000 --- a/tools/install_ubuntu_dependencies.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env bash -set -e - -SUDO="" - -# Use sudo if not root -if [[ ! $(id -u) -eq 0 ]]; then - if [[ -z $(which sudo) ]]; then - echo "Please install sudo or run as root" - exit 1 - fi - SUDO="sudo" -fi - -# Check if stdin is open -if [ -t 0 ]; then - INTERACTIVE=1 -fi - -# Install common packages -function install_ubuntu_common_requirements() { - $SUDO apt-get update - - # normal stuff, mostly for the bare docker image - $SUDO apt-get install -y --no-install-recommends \ - ca-certificates \ - build-essential \ - curl \ - libssl-dev \ - libcurl4-openssl-dev \ - locales \ - git \ - xvfb - - $SUDO apt-get install -y --no-install-recommends \ - libgles2-mesa-dev \ - libjpeg-dev \ - libncurses5-dev \ - libzstd-dev \ - gettext -} - -# Install Ubuntu 24.04 LTS packages -function install_ubuntu_lts_latest_requirements() { - install_ubuntu_common_requirements - - $SUDO apt-get install -y --no-install-recommends \ - python3-dev \ - python3-venv -} - -# Detect OS using /etc/os-release file -if [ -f "/etc/os-release" ]; then - source /etc/os-release - case "$VERSION_CODENAME" in - "jammy" | "kinetic" | "noble") - install_ubuntu_lts_latest_requirements - ;; - *) - echo "$ID $VERSION_ID is unsupported. This setup script is written for Ubuntu 24.04." - read -p "Would you like to attempt installation anyway? " -n 1 -r - echo "" - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi - install_ubuntu_lts_latest_requirements - esac - - if [[ -d "/etc/udev/rules.d/" ]]; then - # Setup jungle udev rules - $SUDO tee /etc/udev/rules.d/12-panda_jungle.rules > /dev/null < /dev/null < /dev/null </dev/null && pwd )" -ROOT="$(cd $DIR/../ && pwd)" - -if [[ $SHELL == "/bin/zsh" ]]; then - RC_FILE="$HOME/.zshrc" -elif [[ $SHELL == "/bin/bash" ]]; then - RC_FILE="$HOME/.bash_profile" -fi - -# install python dependencies -$DIR/install_python_dependencies.sh -echo "[ ] installed python dependencies t=$SECONDS" - -echo -echo "---- OPENPILOT SETUP DONE ----" -echo "Open a new shell or configure your active shell env by running:" -echo "source $RC_FILE" diff --git a/tools/op.sh b/tools/op.sh index 8c41926e0ce..966d4ba4336 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -216,11 +216,7 @@ function op_setup() { echo "Installing dependencies..." st="$(date +%s)" - if [[ "$OSTYPE" == "linux-gnu"* ]]; then - SETUP_SCRIPT="tools/ubuntu_setup.sh" - elif [[ "$OSTYPE" == "darwin"* ]]; then - SETUP_SCRIPT="tools/mac_setup.sh" - fi + SETUP_SCRIPT="tools/setup_dependencies.sh" if ! $OPENPILOT_ROOT/$SETUP_SCRIPT; then echo -e " ↳ [${RED}✗${NC}] Dependencies installation failed!" loge "ERROR_DEPENDENCIES_INSTALLATION" diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh new file mode 100755 index 00000000000..6dd1f294c14 --- /dev/null +++ b/tools/setup_dependencies.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +ROOT="$(cd "$DIR/../" && pwd)" + +function install_ubuntu_deps() { + SUDO="" + + if [[ ! $(id -u) -eq 0 ]]; then + if [[ -z $(which sudo) ]]; then + echo "Please install sudo or run as root" + exit 1 + fi + SUDO="sudo" + fi + + # Detect OS using /etc/os-release file + if [ -f "/etc/os-release" ]; then + source /etc/os-release + case "$VERSION_CODENAME" in + "jammy" | "kinetic" | "noble") + ;; + *) + echo "$ID $VERSION_ID is unsupported. This setup script is written for Ubuntu 24.04." + read -p "Would you like to attempt installation anyway? " -n 1 -r + echo "" + if [[ ! $REPLY =~ ^[Yy]$ ]]; then + exit 1 + fi + ;; + esac + else + echo "No /etc/os-release in the system. Make sure you're running on Ubuntu, or similar." + exit 1 + fi + + $SUDO apt-get update + + # normal stuff, mostly for the bare docker image + $SUDO apt-get install -y --no-install-recommends \ + ca-certificates \ + build-essential \ + curl \ + libssl-dev \ + libcurl4-openssl-dev \ + locales \ + git \ + xvfb + + $SUDO apt-get install -y --no-install-recommends \ + python3-dev \ + libgles2-mesa-dev \ + libjpeg-dev \ + libncurses5-dev \ + libzstd-dev \ + gettext + + if [[ -d "/etc/udev/rules.d/" ]]; then + # Setup jungle udev rules + $SUDO tee /etc/udev/rules.d/12-panda_jungle.rules > /dev/null < /dev/null < /dev/null < /dev/null 2>&1; then + echo "installing uv..." + curl -LsSf --retry 5 --retry-delay 5 --retry-all-errors https://astral.sh/uv/install.sh | sh + UV_BIN="$HOME/.local/bin" + PATH="$UV_BIN:$PATH" + fi + + echo "updating uv..." + # ok to fail, can also fail due to installing with brew + uv self update || true + + echo "installing python packages..." + uv sync --frozen --all-extras + source .venv/bin/activate + + if [[ "$(uname)" == 'Darwin' ]]; then + touch "$ROOT"/.env + echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> "$ROOT"/.env + fi +} + +# --- Main --- + +if [[ "$OSTYPE" == "linux-gnu"* ]]; then + install_ubuntu_deps + echo "[ ] installed system dependencies t=$SECONDS" +elif [[ "$OSTYPE" == "darwin"* ]]; then + if [[ $SHELL == "/bin/zsh" ]]; then + RC_FILE="$HOME/.zshrc" + elif [[ $SHELL == "/bin/bash" ]]; then + RC_FILE="$HOME/.bash_profile" + fi +fi + +if [ -f "$ROOT/pyproject.toml" ]; then + install_python_deps + echo "[ ] installed python dependencies t=$SECONDS" +fi + +if [[ "$OSTYPE" == "darwin"* ]] && [[ -n "${RC_FILE:-}" ]]; then + echo + echo "---- OPENPILOT SETUP DONE ----" + echo "Open a new shell or configure your active shell env by running:" + echo "source $RC_FILE" +fi diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh deleted file mode 100755 index be4cfb68fa9..00000000000 --- a/tools/ubuntu_setup.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -set -e - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" - -# NOTE: this is used in a docker build, so do not run any scripts here. - -"$DIR"/install_ubuntu_dependencies.sh -"$DIR"/install_python_dependencies.sh From 19459d2b2e608324c2c010c1486325644530e535 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:25:52 -0700 Subject: [PATCH 325/685] feat(lpa): at client + list profiles (#37337) * Reapply "feat(lpa): `at` client + list profiles (#37271)" (#37322) This reverts commit ddf8abc14a272d5e95ffd2569d1ddfd2195c56f5. * lpa: fall back to ModemManager D-Bus when serial port unavailable On older devices, ModemManager still claims /dev/ttyUSB2, so the direct serial open fails. Try serial first; if it can't be acquired, transparently route AT commands through MM's D-Bus Command() interface. Co-authored-by: Cursor * lpa: add serial/dbus transport labels to debug logs Co-authored-by: Cursor * no * lint * here * const --------- Co-authored-by: Cursor --- pyproject.toml | 2 +- system/hardware/tici/lpa.py | 261 +++++++++++++++++++++++++++++++++++- 2 files changed, 260 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 03ab5902ffc..da36ea9df4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -140,7 +140,7 @@ testpaths = [ [tool.codespell] quiet-level = 3 # if you've got a short variable name that's getting flagged, add it here -ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite" +ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite,ser" builtin = "clear,rare,informal,code,names,en-GB_to_en-US" skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index 9bd9d8c7b09..2e7e6a0ba97 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -1,12 +1,269 @@ +# SGP.22 v2.3: https://www.gsma.com/solutions-and-impact/technologies/esim/wp-content/uploads/2021/07/SGP.22-v2.3.pdf + +import atexit +import base64 +import math +import os +import serial +import sys + +from collections.abc import Generator + from openpilot.system.hardware.base import LPABase, Profile +DEFAULT_DEVICE = "/dev/ttyUSB2" +DEFAULT_BAUD = 9600 +DEFAULT_TIMEOUT = 5.0 +# https://euicc-manual.osmocom.org/docs/lpa/applet-id/ +ISDR_AID = "A0000005591010FFFFFFFF8900000100" +MM = "org.freedesktop.ModemManager1" +MM_MODEM = MM + ".Modem" +ES10X_MSS = 120 +DEBUG = os.environ.get("DEBUG") == "1" + +# TLV Tags +TAG_ICCID = 0x5A +TAG_PROFILE_INFO_LIST = 0xBF2D + +STATE_LABELS = {0: "disabled", 1: "enabled", 255: "unknown"} +ICON_LABELS = {0: "jpeg", 1: "png", 255: "unknown"} +CLASS_LABELS = {0: "test", 1: "provisioning", 2: "operational", 255: "unknown"} + + +def b64e(data: bytes) -> str: + return base64.b64encode(data).decode("ascii") + + +class AtClient: + def __init__(self, device: str, baud: int, timeout: float, debug: bool) -> None: + self.debug = debug + self.channel: str | None = None + self._timeout = timeout + self._serial: serial.Serial | None = None + try: + self._serial = serial.Serial(device, baudrate=baud, timeout=timeout) + self._serial.reset_input_buffer() + except (serial.SerialException, PermissionError, OSError): + pass + + def close(self) -> None: + try: + if self.channel: + self.query(f"AT+CCHC={self.channel}") + self.channel = None + finally: + if self._serial: + self._serial.close() + + def _send(self, cmd: str) -> None: + if self.debug: + print(f"SER >> {cmd}", file=sys.stderr) + self._serial.write((cmd + "\r").encode("ascii")) + + def _expect(self) -> list[str]: + lines: list[str] = [] + while True: + raw = self._serial.readline() + if not raw: + raise TimeoutError("AT command timed out") + line = raw.decode(errors="ignore").strip() + if not line: + continue + if self.debug: + print(f"SER << {line}", file=sys.stderr) + if line == "OK": + return lines + if line == "ERROR" or line.startswith("+CME ERROR"): + raise RuntimeError(f"AT command failed: {line}") + lines.append(line) + + def _get_modem(self): + import dbus + bus = dbus.SystemBus() + mm = bus.get_object(MM, '/org/freedesktop/ModemManager1') + objects = mm.GetManagedObjects(dbus_interface="org.freedesktop.DBus.ObjectManager", timeout=self._timeout) + modem_path = list(objects.keys())[0] + return bus.get_object(MM, modem_path) + + def _dbus_query(self, cmd: str) -> list[str]: + if self.debug: + print(f"DBUS >> {cmd}", file=sys.stderr) + try: + result = str(self._get_modem().Command(cmd, math.ceil(self._timeout), dbus_interface=MM_MODEM, timeout=self._timeout)) + except Exception as e: + raise RuntimeError(f"AT command failed: {e}") from e + lines = [line.strip() for line in result.splitlines() if line.strip()] + if self.debug: + for line in lines: + print(f"DBUS << {line}", file=sys.stderr) + return lines + + def query(self, cmd: str) -> list[str]: + if self._serial: + self._send(cmd) + return self._expect() + return self._dbus_query(cmd) + + def open_isdr(self) -> None: + # close any stale logical channel from a previous crashed session + try: + self.query("AT+CCHC=1") + except RuntimeError: + pass + for line in self.query(f'AT+CCHO="{ISDR_AID}"'): + if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()): + self.channel = ch + return + raise RuntimeError("Failed to open ISD-R application") + + def send_apdu(self, apdu: bytes) -> tuple[bytes, int, int]: + if not self.channel: + raise RuntimeError("Logical channel is not open") + hex_payload = apdu.hex().upper() + for line in self.query(f'AT+CGLA={self.channel},{len(hex_payload)},"{hex_payload}"'): + if line.startswith("+CGLA:"): + parts = line.split(":", 1)[1].split(",", 1) + if len(parts) == 2: + data = bytes.fromhex(parts[1].strip().strip('"')) + if len(data) >= 2: + return data[:-2], data[-2], data[-1] + raise RuntimeError("Missing +CGLA response") + + +# --- TLV utilities --- + +def iter_tlv(data: bytes, with_positions: bool = False) -> Generator: + idx, length = 0, len(data) + while idx < length: + start_pos = idx + tag = data[idx] + idx += 1 + if tag & 0x1F == 0x1F: # Multi-byte tag + tag_value = tag + while idx < length: + next_byte = data[idx] + idx += 1 + tag_value = (tag_value << 8) | next_byte + if not (next_byte & 0x80): + break + else: + tag_value = tag + if idx >= length: + break + size = data[idx] + idx += 1 + if size & 0x80: # Multi-byte length + num_bytes = size & 0x7F + if idx + num_bytes > length: + break + size = int.from_bytes(data[idx : idx + num_bytes], "big") + idx += num_bytes + if idx + size > length: + break + value = data[idx : idx + size] + idx += size + yield (tag_value, value, start_pos, idx) if with_positions else (tag_value, value) + + +def find_tag(data: bytes, target: int) -> bytes | None: + return next((v for t, v in iter_tlv(data) if t == target), None) + + +def tbcd_to_string(raw: bytes) -> str: + return "".join(str(n) for b in raw for n in (b & 0x0F, b >> 4) if n <= 9) + + +# Profile field decoders: TLV tag -> (field_name, decoder) +_PROFILE_FIELDS = { + TAG_ICCID: ("iccid", tbcd_to_string), + 0x4F: ("isdpAid", lambda v: v.hex().upper()), + 0x9F70: ("profileState", lambda v: STATE_LABELS.get(v[0], "unknown")), + 0x90: ("profileNickname", lambda v: v.decode("utf-8", errors="ignore") or None), + 0x91: ("serviceProviderName", lambda v: v.decode("utf-8", errors="ignore") or None), + 0x92: ("profileName", lambda v: v.decode("utf-8", errors="ignore") or None), + 0x93: ("iconType", lambda v: ICON_LABELS.get(v[0], "unknown")), + 0x94: ("icon", b64e), + 0x95: ("profileClass", lambda v: CLASS_LABELS.get(v[0], "unknown")), +} + + +def _decode_profile_fields(data: bytes) -> dict: + """Parse known profile metadata TLV fields into a dict.""" + result = {} + for tag, value in iter_tlv(data): + if (field := _PROFILE_FIELDS.get(tag)): + result[field[0]] = field[1](value) + return result + + +# --- ES10x command transport --- + +def es10x_command(client: AtClient, data: bytes) -> bytes: + response = bytearray() + sequence = 0 + offset = 0 + while offset < len(data): + chunk = data[offset : offset + ES10X_MSS] + offset += len(chunk) + is_last = offset == len(data) + apdu = bytes([0x80, 0xE2, 0x91 if is_last else 0x11, sequence & 0xFF, len(chunk)]) + chunk + segment, sw1, sw2 = client.send_apdu(apdu) + response.extend(segment) + while True: + if sw1 == 0x61: # More data available + segment, sw1, sw2 = client.send_apdu(bytes([0x80, 0xC0, 0x00, 0x00, sw2 or 0])) + response.extend(segment) + continue + if (sw1 & 0xF0) == 0x90: + break + raise RuntimeError(f"APDU failed with SW={sw1:02X}{sw2:02X}") + sequence += 1 + return bytes(response) + + +# --- Profile operations --- + +def decode_profiles(blob: bytes) -> list[dict]: + root = find_tag(blob, TAG_PROFILE_INFO_LIST) + if root is None: + raise RuntimeError("Missing ProfileInfoList") + list_ok = find_tag(root, 0xA0) + if list_ok is None: + return [] + defaults = {name: None for name, _ in _PROFILE_FIELDS.values()} + return [{**defaults, **_decode_profile_fields(value)} for tag, value in iter_tlv(list_ok) if tag == 0xE3] + + +def list_profiles(client: AtClient) -> list[dict]: + return decode_profiles(es10x_command(client, TAG_PROFILE_INFO_LIST.to_bytes(2, "big") + b"\x00")) + + class TiciLPA(LPABase): + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super().__new__(cls) + return cls._instance + def __init__(self): - pass + if hasattr(self, '_client'): + return + self._client = AtClient(DEFAULT_DEVICE, DEFAULT_BAUD, DEFAULT_TIMEOUT, debug=DEBUG) + self._client.open_isdr() + atexit.register(self._client.close) def list_profiles(self) -> list[Profile]: - return [] + return [ + Profile( + iccid=p.get("iccid", ""), + nickname=p.get("profileNickname") or "", + enabled=p.get("profileState") == "enabled", + provider=p.get("serviceProviderName") or "", + ) + for p in list_profiles(self._client) + ] def get_active_profile(self) -> Profile | None: return None From 91930c2d0deb0e9d49e874ff8a49c8e04c69896e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 19:37:59 -0800 Subject: [PATCH 326/685] UnifiedLabel: add set_line_height --- system/ui/widgets/label.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 865c8b38c3f..8ed9ec62f5c 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -489,6 +489,13 @@ def set_letter_spacing(self, letter_spacing: float): self._spacing_pixels = self._font_size * letter_spacing self._cached_text = None # Invalidate cache + def set_line_height(self, line_height: float): + """Update line height (multiplier, e.g., 1.0 = default).""" + new_line_height = line_height * 0.9 + if self._line_height != new_line_height: + self._line_height = new_line_height + self._cached_text = None # Invalidate cache (affects total height) + def set_font_weight(self, font_weight: FontWeight): """Update the font weight.""" if self._font_weight != font_weight: From ed8d1a65c3a86b237af4ecad3626c1379aec6dad Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 19:39:30 -0800 Subject: [PATCH 327/685] BigCircleButton: new pressed image --- selfdrive/assets/icons_mici/buttons/button_circle_hover.png | 3 --- selfdrive/assets/icons_mici/buttons/button_circle_pressed.png | 3 +++ selfdrive/ui/mici/widgets/button.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_hover.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_pressed.png diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_hover.png b/selfdrive/assets/icons_mici/buttons/button_circle_hover.png deleted file mode 100644 index 5cae1521065..00000000000 --- a/selfdrive/assets/icons_mici/buttons/button_circle_hover.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20024203288f144633014422e16119278477099f24fba5c155a804a1864a26b4 -size 7511 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png b/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png new file mode 100644 index 00000000000..9db0c2cd811 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:70d4236bcfd3aa8f100b81179c1e0f193c6ffbd84769c4a516be4381e62b270a +size 18666 diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index f81b7f0df50..5f76535ef75 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -43,7 +43,7 @@ def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (6 self._txt_btn_disabled_bg = gui_app.texture("icons_mici/buttons/button_circle_disabled.png", 180, 180) self._txt_btn_bg = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) - self._txt_btn_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_hover.png", 180, 180) + self._txt_btn_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_pressed.png", 180, 180) self._txt_btn_red_bg = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180) self._txt_btn_red_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_red_hover.png", 180, 180) From c5b65d072d9935dfaa4c17f43cb7ad6e70139974 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 19:53:07 -0800 Subject: [PATCH 328/685] no more xset --- selfdrive/test/setup_xvfb.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/selfdrive/test/setup_xvfb.sh b/selfdrive/test/setup_xvfb.sh index b185e2b431d..c1b74a850ee 100755 --- a/selfdrive/test/setup_xvfb.sh +++ b/selfdrive/test/setup_xvfb.sh @@ -19,5 +19,4 @@ do done touch ~/.Xauthority -export XDG_SESSION_TYPE="x11" -xset -q \ No newline at end of file +export XDG_SESSION_TYPE="x11" \ No newline at end of file From 0e127cf88b03d18d5e9869022109989b32a9b0fa Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 20:13:17 -0800 Subject: [PATCH 329/685] WifiManager: guard init wifi state (#37366) guard init wifi state --- system/ui/lib/wifi_manager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 4f02b706259..110fd72181b 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -223,6 +223,10 @@ def worker(): def _init_wifi_state(self, block: bool = True): def worker(): + if self._wifi_device is None: + cloudlog.warning("No WiFi device found") + return + dev_addr = DBusAddress(self._wifi_device, bus_name=NM, interface=NM_DEVICE_IFACE) dev_state = self._router_main.send_and_get_reply(Properties(dev_addr).get('State')).body[0][1] From 12f923445b4b4d61069d19b74b38881b106d2ce5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 20:14:06 -0800 Subject: [PATCH 330/685] Slider: call confirm callback after set state in case confirm callback resets the state immediately --- system/ui/widgets/slider.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 455cdeef712..0cdc513a036 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -107,8 +107,8 @@ def _update_state(self): # activate once animation completes, small threshold for small floats if self._scroll_x_circle_filter.x < (activated_pos + 1): if not self._confirm_callback_called and (rl.get_time() - self._confirmed_time) >= self.CONFIRM_DELAY: - self._on_confirm() self._confirm_callback_called = True + self._on_confirm() elif not self._is_dragging_circle: # reset back to right From 8543afc78ab883d3c2d3aba5e757cc94cb3f22b4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 20:19:41 -0800 Subject: [PATCH 331/685] Slider: add pressed state (#37365) * sliders have pressed state * more * new and pressed setup sliders --- .../assets/icons_mici/buttons/button_circle_red_hover.png | 3 --- .../icons_mici/buttons/button_circle_red_pressed.png | 3 +++ .../slider_black_rounded_rectangle_pressed.png | 3 +++ .../setup/small_slider/slider_green_rounded_rectangle.png | 4 ++-- .../slider_green_rounded_rectangle_pressed.png | 3 +++ .../setup/small_slider/slider_red_circle_pressed.png | 3 +++ selfdrive/ui/mici/widgets/button.py | 2 +- system/ui/widgets/slider.py | 7 ++++++- 8 files changed, 21 insertions(+), 7 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png create mode 100644 selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png b/selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png deleted file mode 100644 index 3696334d5e2..00000000000 --- a/selfdrive/assets/icons_mici/buttons/button_circle_red_hover.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:279c1d8f95eb9f4a3058dff76b0f316ce9eef7bc8f4296936ad25fd08703ce13 -size 10380 diff --git a/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png b/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png new file mode 100644 index 00000000000..e61a678c1c3 --- /dev/null +++ b/selfdrive/assets/icons_mici/buttons/button_circle_red_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ed07f72339cf1c3926a2cb7314f9baa099bcdb3f8bc89a9084661b71334b0526 +size 32599 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle_pressed.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle_pressed.png new file mode 100644 index 00000000000..470bfc50c0b --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_black_rounded_rectangle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d1dd642ae4708cc7a837e8ef8b4c75f578654d241f8c854249c2b1ade640ceca +size 14058 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png index 9ebff76b506..88e6985f126 100644 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bcd08444c77b3e559876eeb88d17808f72496adc26e27c3c21c00ff410879447 -size 10966 +oid sha256:5ba98ab2b75f0c1f8fdffb9eab0a742645b80ef4ca404c007f374a5e0fd48d8c +size 10254 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle_pressed.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle_pressed.png new file mode 100644 index 00000000000..999cdafcc78 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_green_rounded_rectangle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4545fdbaf67402b28b18644a7353a0620250ece6416c1b0ce0e27c758817b042 +size 26729 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png new file mode 100644 index 00000000000..eea6eded86c --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a804da77b268f0a625f93949642ae74cdfe5b5caa5baea1c52c4605ae25c80e4 +size 12916 diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 5f76535ef75..20da5a06cc2 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -46,7 +46,7 @@ def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (6 self._txt_btn_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_pressed.png", 180, 180) self._txt_btn_red_bg = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180) - self._txt_btn_red_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_red_hover.png", 180, 180) + self._txt_btn_red_pressed_bg = gui_app.texture("icons_mici/buttons/button_circle_red_pressed.png", 180, 180) def _draw_content(self, btn_y: float): # draw icon diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 0cdc513a036..734c5b5d65f 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -42,6 +42,7 @@ def _load_assets(self): self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg.png", 316, 100) self._circle_bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle.png", 100, 100) + self._circle_bg_pressed_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle_pressed.png", 100, 100) self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 37, 32) @property @@ -140,7 +141,8 @@ def _render(self, _): self._label.render(label_rect) # circle and arrow - rl.draw_texture_ex(self._circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) + circle_bg_txt = self._circle_bg_pressed_txt if self._is_dragging_circle or self._confirmed_time > 0 else self._circle_bg_txt + rl.draw_texture_ex(circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) arrow_x = btn_x + (self._circle_bg_txt.width - self._circle_arrow_txt.width) / 2 arrow_y = btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2 @@ -158,6 +160,7 @@ def _load_assets(self): self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg_larger.png", 520, 115) circle_fn = "slider_green_rounded_rectangle" if self._green else "slider_black_rounded_rectangle" self._circle_bg_txt = gui_app.texture(f"icons_mici/setup/small_slider/{circle_fn}.png", 180, 115) + self._circle_bg_pressed_txt = gui_app.texture(f"icons_mici/setup/small_slider/{circle_fn}_pressed.png", 180, 115) self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 55) @@ -174,6 +177,7 @@ def _load_assets(self): self._bg_txt = gui_app.texture("icons_mici/buttons/slider_bg.png", 520, 180) self._circle_bg_txt = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) + self._circle_bg_pressed_txt = gui_app.texture("icons_mici/buttons/button_circle_pressed.png", 180, 180) self._circle_arrow_txt = self._icon @@ -183,4 +187,5 @@ def _load_assets(self): self._bg_txt = gui_app.texture("icons_mici/buttons/slider_bg.png", 520, 180) self._circle_bg_txt = gui_app.texture("icons_mici/buttons/button_circle_red.png", 180, 180) + self._circle_bg_pressed_txt = gui_app.texture("icons_mici/buttons/button_circle_red_pressed.png", 180, 180) self._circle_arrow_txt = self._icon From bd3b7a1d87da44c360e40c9a35b9af4347a99332 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 20:20:44 -0800 Subject: [PATCH 332/685] Scroller: preserve original touch valid callback (#37363) preserve --- system/ui/widgets/scroller.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 0543b1395eb..7eff8ae594e 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -141,8 +141,12 @@ def content_size(self) -> float: def add_widget(self, item: Widget) -> None: self._items.append(item) + + # preserve original touch valid callback + original_touch_valid_callback = item._touch_valid_callback item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled and self._scrolling_to is None - and not self.moving_items) + and not self.moving_items and (original_touch_valid_callback() if + original_touch_valid_callback else True)) def set_scrolling_enabled(self, enabled: bool | Callable[[], bool]) -> None: """Set whether scrolling is enabled (does not affect widget enabled state).""" From 76a552715f65fa394fc8b542326e22845aa80c6c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 20:45:22 -0800 Subject: [PATCH 333/685] ui: move shake into BigButton (#37364) * move * fix --- .../mici/layouts/settings/network/wifi_ui.py | 17 +---------------- selfdrive/ui/mici/widgets/button.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index c9bc54a8b5a..2efc45f46a7 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -114,7 +114,6 @@ def __init__(self, network: Network, wifi_manager: WifiManager): self._network_missing = False self._network_forgetting = False self._wrong_password = False - self._shake_start: float | None = None def update_network(self, network: Network): self._network = network @@ -144,7 +143,7 @@ def set_network_missing(self, missing: bool): def set_wrong_password(self): self._wrong_password = True - self._shake_start = rl.get_time() + self.trigger_shake() @property def network(self) -> Network: @@ -165,20 +164,6 @@ def _handle_mouse_release(self, mouse_pos: MousePos): def _get_label_font_size(self): return 48 - @property - def _shake_offset(self) -> float: - SHAKE_DURATION = 0.5 - SHAKE_AMPLITUDE = 24.0 - SHAKE_FREQUENCY = 32.0 - t = rl.get_time() - (self._shake_start or 0.0) - if t > SHAKE_DURATION: - return 0.0 - decay = 1.0 - t / SHAKE_DURATION - return decay * SHAKE_AMPLITUDE * math.sin(t * SHAKE_FREQUENCY) - - def set_position(self, x: float, y: float) -> None: - super().set_position(x + self._shake_offset, y) - def _draw_content(self, btn_y: float): self._label.set_color(LABEL_COLOR) label_rect = rl.Rectangle(self._rect.x + self.LABEL_PADDING, btn_y + LABEL_VERTICAL_PADDING, diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 20da5a06cc2..14786245d70 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -1,3 +1,4 @@ +import math import pyray as rl from typing import Union from enum import Enum @@ -115,6 +116,7 @@ def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "" self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._shake_start: float | None = None self._rotate_icon_t: float | None = None @@ -174,6 +176,23 @@ def get_value(self) -> str: def get_text(self): return self.text + def trigger_shake(self): + self._shake_start = rl.get_time() + + @property + def _shake_offset(self) -> float: + SHAKE_DURATION = 0.5 + SHAKE_AMPLITUDE = 24.0 + SHAKE_FREQUENCY = 32.0 + t = rl.get_time() - (self._shake_start or 0.0) + if t > SHAKE_DURATION: + return 0.0 + decay = 1.0 - t / SHAKE_DURATION + return decay * SHAKE_AMPLITUDE * math.sin(t * SHAKE_FREQUENCY) + + def set_position(self, x: float, y: float) -> None: + super().set_position(x + self._shake_offset, y) + def _draw_content(self, btn_y: float): # LABEL ------------------------------------------------------------------ label_x = self._rect.x + LABEL_HORIZONTAL_PADDING From 21b8189a45a653e1df7c54a81a6176510b45019d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 20:45:41 -0800 Subject: [PATCH 334/685] ui: support asset flip (#37367) * support asset flip * clean up --- .../assets/icons_mici/onroad/blind_spot_right.png | 3 --- .../assets/icons_mici/onroad/turn_signal_right.png | 3 --- selfdrive/assets/icons_mici/turn_intent_right.png | 3 --- selfdrive/ui/mici/onroad/alert_renderer.py | 4 ++-- selfdrive/ui/mici/onroad/hud_renderer.py | 2 +- system/ui/lib/application.py | 12 ++++++++---- 6 files changed, 11 insertions(+), 16 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/onroad/blind_spot_right.png delete mode 100644 selfdrive/assets/icons_mici/onroad/turn_signal_right.png delete mode 100644 selfdrive/assets/icons_mici/turn_intent_right.png diff --git a/selfdrive/assets/icons_mici/onroad/blind_spot_right.png b/selfdrive/assets/icons_mici/onroad/blind_spot_right.png deleted file mode 100644 index b6cd7834ef5..00000000000 --- a/selfdrive/assets/icons_mici/onroad/blind_spot_right.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:584cea202afff6dd20d67ae1a9cd6d2b8cc07598bccb91a8d1bac0142567308e -size 45489 diff --git a/selfdrive/assets/icons_mici/onroad/turn_signal_right.png b/selfdrive/assets/icons_mici/onroad/turn_signal_right.png deleted file mode 100644 index 6bcb68dac55..00000000000 --- a/selfdrive/assets/icons_mici/onroad/turn_signal_right.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fae4872ab3c24d5e4c2be6150127a844f89bbdcadfccdff2dfed180e125d577 -size 45699 diff --git a/selfdrive/assets/icons_mici/turn_intent_right.png b/selfdrive/assets/icons_mici/turn_intent_right.png deleted file mode 100644 index e342778731d..00000000000 --- a/selfdrive/assets/icons_mici/turn_intent_right.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7b7e0194a8b9009e493cdce35cd15711596a54227c740e9d6419a3891c6c4037 -size 912 diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index 64dd04c3103..1c1fd8404f7 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -112,9 +112,9 @@ def __init__(self): def _load_icons(self): self._txt_turn_signal_left = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 104, 96) - self._txt_turn_signal_right = gui_app.texture('icons_mici/onroad/turn_signal_right.png', 104, 96) + self._txt_turn_signal_right = gui_app.texture('icons_mici/onroad/turn_signal_left.png', 104, 96, flip_x=True) self._txt_blind_spot_left = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 134, 150) - self._txt_blind_spot_right = gui_app.texture('icons_mici/onroad/blind_spot_right.png', 134, 150) + self._txt_blind_spot_right = gui_app.texture('icons_mici/onroad/blind_spot_left.png', 134, 150, flip_x=True) def get_alert(self, sm: messaging.SubMaster) -> Alert | None: """Generate the current alert based on selfdrive state.""" diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index a6fa1a62bba..3b056c3e0f6 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -50,7 +50,7 @@ def __init__(self): self._turn_intent_rotation_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) self._txt_turn_intent_left: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 20) - self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_right.png', 50, 20) + self._txt_turn_intent_right: rl.Texture = gui_app.texture('icons_mici/turn_intent_left.png', 50, 20, flip_x=True) def _render(self, _): if self._turn_intent_alpha_filter.x > 1e-2: diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 5d284bf454d..1640b0d0774 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -418,19 +418,19 @@ def set_should_render(self, should_render: bool): self._should_render = should_render def texture(self, asset_path: str, width: int | None = None, height: int | None = None, - alpha_premultiply=False, keep_aspect_ratio=True): - cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}{keep_aspect_ratio}" + alpha_premultiply=False, keep_aspect_ratio=True, flip_x: bool = False) -> rl.Texture: + cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}_{keep_aspect_ratio}_{flip_x}" if cache_key in self._textures: return self._textures[cache_key] with as_file(ASSETS_DIR.joinpath(asset_path)) as fspath: - image_obj = self._load_image_from_path(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio) + image_obj = self._load_image_from_path(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio, flip_x) texture_obj = self._load_texture_from_image(image_obj) self._textures[cache_key] = texture_obj return texture_obj def _load_image_from_path(self, image_path: str, width: int | None = None, height: int | None = None, - alpha_premultiply: bool = False, keep_aspect_ratio: bool = True) -> rl.Image: + alpha_premultiply: bool = False, keep_aspect_ratio: bool = True, flip_x: bool = False) -> rl.Image: """Load and resize an image, storing it for later automatic unloading.""" image = rl.load_image(image_path) @@ -459,6 +459,10 @@ def _load_image_from_path(self, image_path: str, width: int | None = None, heigh rl.image_resize(image, width, height) else: assert keep_aspect_ratio, "Cannot resize without specifying width and height" + + if flip_x: + rl.image_flip_horizontal(image) + return image def _load_texture_from_image(self, image: rl.Image) -> rl.Texture: From ded5e5c8d04aae88e2d202a2d3b9f70b7f5a2ab6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Feb 2026 20:46:17 -0800 Subject: [PATCH 335/685] BigButton: normal draw order if not scrolling (#37368) no scroll normal drawing --- selfdrive/ui/mici/widgets/button.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 14786245d70..3ea650ece39 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -235,12 +235,16 @@ def _render(self, _): btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 - # draw black background since images are transparent - scaled_rect = rl.Rectangle(btn_x, btn_y, self._rect.width * scale, self._rect.height * scale) - rl.draw_rectangle_rounded(scaled_rect, 0.4, 7, rl.Color(0, 0, 0, int(255 * 0.5))) + if self._scroll: + # draw black background since images are transparent + scaled_rect = rl.Rectangle(btn_x, btn_y, self._rect.width * scale, self._rect.height * scale) + rl.draw_rectangle_rounded(scaled_rect, 0.4, 7, rl.Color(0, 0, 0, int(255 * 0.5))) - self._draw_content(btn_y) - rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + self._draw_content(btn_y) + rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + else: + rl.draw_texture_ex(txt_bg, (btn_x, btn_y), 0, scale, rl.WHITE) + self._draw_content(btn_y) class BigToggle(BigButton): From 44cf6b358e6a3b99c0b507dc0d1e0b3622ce96c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Mon, 23 Feb 2026 20:57:21 -0800 Subject: [PATCH 336/685] ffmpeg: pipe (#37359) spec pipe --- tools/lib/framereader.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index c13f7fd3134..c9236515630 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -51,10 +51,10 @@ def decompress_video_data(rawdat, w, h, pix_fmt="rgb24", vid_fmt='hevc', hwaccel "-vsync", "0", "-f", vid_fmt, "-flags2", "showall", - "-i", "-", + "-i", "pipe:0", "-f", "rawvideo", "-pix_fmt", pix_fmt, - "-"] + "pipe:1"] dat = subprocess.check_output(args, input=rawdat) ret: np.ndarray @@ -71,7 +71,7 @@ def ffprobe(fn, fmt=None): cmd = ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", "-show_streams"] if fmt: cmd += ["-f", fmt] - cmd += ["-i", "-"] + cmd += ["-i", "pipe:0"] try: with FileReader(fn) as f: From 8bd806658960e0d820849408d59460e7bd594779 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 21:11:41 -0800 Subject: [PATCH 337/685] rm libjpeg (#37371) --- tools/setup_dependencies.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 6dd1f294c14..cae869f3c04 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -51,7 +51,6 @@ function install_ubuntu_deps() { $SUDO apt-get install -y --no-install-recommends \ python3-dev \ libgles2-mesa-dev \ - libjpeg-dev \ libncurses5-dev \ libzstd-dev \ gettext From 2ddf95d47f258eab323c19e2dbdff1cc83e4ca66 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 21:18:29 -0800 Subject: [PATCH 338/685] rm libgles2-mesa-dev (#37373) * rm libjpeg * rm-libgles2-mesa-dev --- tools/setup_dependencies.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index cae869f3c04..79f56333cf3 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -50,7 +50,6 @@ function install_ubuntu_deps() { $SUDO apt-get install -y --no-install-recommends \ python3-dev \ - libgles2-mesa-dev \ libncurses5-dev \ libzstd-dev \ gettext From a1e9cf9df91b2ce1e4aca6174a6f4eb979f08375 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 21:42:24 -0800 Subject: [PATCH 339/685] translations: replace gettext apt dependency with pure Python tools (#37372) --- selfdrive/ui/SConscript | 14 -- selfdrive/ui/translations/potools.py | 362 +++++++++++++++++++++++++++ selfdrive/ui/update_translations.py | 22 +- system/ui/lib/multilang.py | 154 ++++++++++-- tools/setup_dependencies.sh | 3 +- 5 files changed, 511 insertions(+), 44 deletions(-) create mode 100644 selfdrive/ui/translations/potools.py diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index a03a26bea83..20ba2e44d02 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,6 +1,4 @@ -import os import re -import json from pathlib import Path Import('env', 'arch', 'common') @@ -18,18 +16,6 @@ env.Command( action=f"python3 {generator}", ) -# compile gettext .po -> .mo translations -with open(File("translations/languages.json").abspath) as f: - languages = json.loads(f.read()) - -po_sources = [f"#selfdrive/ui/translations/app_{l}.po" for l in languages.values()] -po_sources = [src for src in po_sources if os.path.exists(File(src).abspath)] -mo_targets = [src.replace(".po", ".mo") for src in po_sources] -mo_build = [] -for src, tgt in zip(po_sources, mo_targets): - mo_build.append(env.Command(tgt, src, "msgfmt -o $TARGET $SOURCE")) -mo_alias = env.Alias('mo', mo_build) -env.AlwaysBuild(mo_alias) if GetOption('extras'): # build installers diff --git a/selfdrive/ui/translations/potools.py b/selfdrive/ui/translations/potools.py new file mode 100644 index 00000000000..7571cccdd64 --- /dev/null +++ b/selfdrive/ui/translations/potools.py @@ -0,0 +1,362 @@ +"""Pure Python tools for managing .po translation files. + +Replaces GNU gettext CLI tools (xgettext, msginit, msgmerge) with Python +implementations for extracting, creating, and updating .po files. +""" + +import ast +import os +import re +from dataclasses import dataclass, field +from datetime import UTC, datetime +from pathlib import Path + + +@dataclass +class POEntry: + msgid: str = "" + msgstr: str = "" + msgid_plural: str = "" + msgstr_plural: dict[int, str] = field(default_factory=dict) + comments: list[str] = field(default_factory=list) + source_refs: list[str] = field(default_factory=list) + flags: list[str] = field(default_factory=list) + + @property + def is_plural(self) -> bool: + return bool(self.msgid_plural) + + +# ──── PO file parsing ──── + +def _parse_quoted(s: str) -> str: + """Parse a PO-format quoted string, handling escape sequences.""" + s = s.strip() + if not (s.startswith('"') and s.endswith('"')): + raise ValueError(f"Expected quoted string: {s!r}") + s = s[1:-1] + result = [] + i = 0 + while i < len(s): + if s[i] == '\\' and i + 1 < len(s): + c = s[i + 1] + if c == 'n': + result.append('\n') + elif c == 't': + result.append('\t') + elif c == '"': + result.append('"') + elif c == '\\': + result.append('\\') + else: + result.append(s[i:i + 2]) + i += 2 + else: + result.append(s[i]) + i += 1 + return ''.join(result) + + +def parse_po(path: str | Path) -> tuple[POEntry | None, list[POEntry]]: + """Parse a .po/.pot file. Returns (header_entry, entries).""" + with open(path, encoding='utf-8') as f: + lines = f.readlines() + + entries: list[POEntry] = [] + header: POEntry | None = None + cur: POEntry | None = None + cur_field: str | None = None + plural_idx = 0 + + def finish(): + nonlocal cur, header + if cur is None: + return + if cur.msgid == "" and cur.msgstr: + header = cur + elif cur.msgid != "" or cur.is_plural: + entries.append(cur) + cur = None + + for raw in lines: + line = raw.rstrip('\n') + stripped = line.strip() + + if not stripped: + finish() + cur_field = None + continue + + # Skip obsolete entries + if stripped.startswith('#~'): + continue + + if stripped.startswith('#'): + if cur is None: + cur = POEntry() + if stripped.startswith('#:'): + cur.source_refs.append(stripped[2:].strip()) + elif stripped.startswith('#,'): + cur.flags.extend(f.strip() for f in stripped[2:].split(',') if f.strip()) + else: + cur.comments.append(line) + continue + + if stripped.startswith('msgid_plural '): + if cur is None: + cur = POEntry() + cur.msgid_plural = _parse_quoted(stripped[len('msgid_plural '):]) + cur_field = 'msgid_plural' + continue + + if stripped.startswith('msgid '): + if cur is None: + cur = POEntry() + cur.msgid = _parse_quoted(stripped[len('msgid '):]) + cur_field = 'msgid' + continue + + m = re.match(r'msgstr\[(\d+)]\s+(.*)', stripped) + if m: + plural_idx = int(m.group(1)) + cur.msgstr_plural[plural_idx] = _parse_quoted(m.group(2)) + cur_field = 'msgstr_plural' + continue + + if stripped.startswith('msgstr '): + cur.msgstr = _parse_quoted(stripped[len('msgstr '):]) + cur_field = 'msgstr' + continue + + if stripped.startswith('"'): + val = _parse_quoted(stripped) + if cur_field == 'msgid': + cur.msgid += val + elif cur_field == 'msgid_plural': + cur.msgid_plural += val + elif cur_field == 'msgstr': + cur.msgstr += val + elif cur_field == 'msgstr_plural': + cur.msgstr_plural[plural_idx] += val + + finish() + return header, entries + + +# ──── PO file writing ──── + +def _quote(s: str) -> str: + """Quote a string for .po file output.""" + s = s.replace('\\', '\\\\').replace('"', '\\"').replace('\t', '\\t') + if '\n' in s and s != '\n': + parts = s.split('\n') + lines = ['""'] + for i, part in enumerate(parts): + text = part + ('\\n' if i < len(parts) - 1 else '') + if text: + lines.append(f'"{text}"') + return '\n'.join(lines) + return f'"{s}"'.replace('\n', '\\n') + + +def write_po(path: str | Path, header: POEntry | None, entries: list[POEntry]) -> None: + """Write a .po/.pot file.""" + with open(path, 'w', encoding='utf-8') as f: + if header: + for c in header.comments: + f.write(c + '\n') + if header.flags: + f.write('#, ' + ', '.join(header.flags) + '\n') + f.write(f'msgid {_quote("")}\n') + f.write(f'msgstr {_quote(header.msgstr)}\n\n') + + for entry in entries: + for c in entry.comments: + f.write(c + '\n') + for ref in entry.source_refs: + f.write(f'#: {ref}\n') + if entry.flags: + f.write('#, ' + ', '.join(entry.flags) + '\n') + f.write(f'msgid {_quote(entry.msgid)}\n') + if entry.is_plural: + f.write(f'msgid_plural {_quote(entry.msgid_plural)}\n') + for idx in sorted(entry.msgstr_plural): + f.write(f'msgstr[{idx}] {_quote(entry.msgstr_plural[idx])}\n') + else: + f.write(f'msgstr {_quote(entry.msgstr)}\n') + f.write('\n') + + +# ──── String extraction (replaces xgettext) ──── + +def extract_strings(files: list[str], basedir: str) -> list[POEntry]: + """Extract tr/trn/tr_noop calls from Python source files.""" + seen: dict[str, POEntry] = {} + + for filepath in files: + full = os.path.join(basedir, filepath) + with open(full, encoding='utf-8') as f: + source = f.read() + try: + tree = ast.parse(source, filename=filepath) + except SyntaxError: + continue + + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + + func = node.func + if isinstance(func, ast.Name): + name = func.id + elif isinstance(func, ast.Attribute): + name = func.attr + else: + continue + + if name not in ('tr', 'trn', 'tr_noop'): + continue + + ref = f'{filepath}:{node.lineno}' + is_flagged = name in ('tr', 'trn') + + if name in ('tr', 'tr_noop'): + if not node.args or not isinstance(node.args[0], ast.Constant) or not isinstance(node.args[0].value, str): + continue + msgid = node.args[0].value + if msgid in seen: + if ref not in seen[msgid].source_refs: + seen[msgid].source_refs.append(ref) + else: + flags = ['python-format'] if is_flagged else [] + seen[msgid] = POEntry(msgid=msgid, source_refs=[ref], flags=flags) + + elif name == 'trn': + if len(node.args) < 2: + continue + a1, a2 = node.args[0], node.args[1] + if not (isinstance(a1, ast.Constant) and isinstance(a1.value, str)): + continue + if not (isinstance(a2, ast.Constant) and isinstance(a2.value, str)): + continue + msgid, msgid_plural = a1.value, a2.value + if msgid in seen: + if ref not in seen[msgid].source_refs: + seen[msgid].source_refs.append(ref) + else: + flags = ['python-format'] if is_flagged else [] + seen[msgid] = POEntry( + msgid=msgid, msgid_plural=msgid_plural, + source_refs=[ref], flags=flags, + msgstr_plural={0: '', 1: ''}, + ) + + return list(seen.values()) + + +# ──── POT generation ──── + +def generate_pot(entries: list[POEntry], pot_path: str | Path) -> None: + """Generate a .pot template file from extracted entries.""" + now = datetime.now(UTC).strftime('%Y-%m-%d %H:%M%z') + header = POEntry( + comments=[ + '# SOME DESCRIPTIVE TITLE.', + "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER", + '# This file is distributed under the same license as the PACKAGE package.', + '# FIRST AUTHOR , YEAR.', + '#', + ], + flags=['fuzzy'], + msgstr='Project-Id-Version: PACKAGE VERSION\n' + + 'Report-Msgid-Bugs-To: \n' + + f'POT-Creation-Date: {now}\n' + + 'PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n' + + 'Last-Translator: FULL NAME \n' + + 'Language-Team: LANGUAGE \n' + + 'Language: \n' + + 'MIME-Version: 1.0\n' + + 'Content-Type: text/plain; charset=UTF-8\n' + + 'Content-Transfer-Encoding: 8bit\n' + + 'Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n', + ) + write_po(pot_path, header, entries) + + +# ──── PO init (replaces msginit) ──── + +PLURAL_FORMS: dict[str, str] = { + 'en': 'nplurals=2; plural=(n != 1);', + 'de': 'nplurals=2; plural=(n != 1);', + 'fr': 'nplurals=2; plural=(n > 1);', + 'es': 'nplurals=2; plural=(n != 1);', + 'pt-BR': 'nplurals=2; plural=(n > 1);', + 'tr': 'nplurals=2; plural=(n != 1);', + 'uk': 'nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);', + 'th': 'nplurals=1; plural=0;', + 'zh-CHT': 'nplurals=1; plural=0;', + 'zh-CHS': 'nplurals=1; plural=0;', + 'ko': 'nplurals=1; plural=0;', + 'ja': 'nplurals=1; plural=0;', +} + + +def init_po(pot_path: str | Path, po_path: str | Path, language: str) -> None: + """Create a new .po file from a .pot template (replaces msginit).""" + _, entries = parse_po(pot_path) + plural_forms = PLURAL_FORMS.get(language, 'nplurals=2; plural=(n != 1);') + now = datetime.now(UTC).strftime('%Y-%m-%d %H:%M%z') + + header = POEntry( + comments=[ + f'# {language} translations for PACKAGE package.', + "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER", + '# This file is distributed under the same license as the PACKAGE package.', + '# Automatically generated.', + '#', + ], + msgstr='Project-Id-Version: PACKAGE VERSION\n' + + 'Report-Msgid-Bugs-To: \n' + + f'POT-Creation-Date: {now}\n' + + f'PO-Revision-Date: {now}\n' + + 'Last-Translator: Automatically generated\n' + + 'Language-Team: none\n' + + f'Language: {language}\n' + + 'MIME-Version: 1.0\n' + + 'Content-Type: text/plain; charset=UTF-8\n' + + 'Content-Transfer-Encoding: 8bit\n' + + f'Plural-Forms: {plural_forms}\n', + ) + + nplurals = int(re.search(r'nplurals=(\d+)', plural_forms).group(1)) + for e in entries: + if e.is_plural: + e.msgstr_plural = dict.fromkeys(range(nplurals), '') + + write_po(po_path, header, entries) + + +# ──── PO merge (replaces msgmerge) ──── + +def merge_po(po_path: str | Path, pot_path: str | Path) -> None: + """Update a .po file with entries from a .pot template (replaces msgmerge --update).""" + po_header, po_entries = parse_po(po_path) + _, pot_entries = parse_po(pot_path) + + existing = {e.msgid: e for e in po_entries} + merged = [] + + for pot_e in pot_entries: + if pot_e.msgid in existing: + old = existing[pot_e.msgid] + old.source_refs = pot_e.source_refs + old.flags = pot_e.flags + old.comments = pot_e.comments + if pot_e.is_plural: + old.msgid_plural = pot_e.msgid_plural + merged.append(old) + else: + merged.append(pot_e) + + merged.sort(key=lambda e: e.msgid) + write_po(po_path, po_header, merged) diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index bded80b2e5b..6ff3667d8a9 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -3,6 +3,7 @@ import os from openpilot.common.basedir import BASEDIR from openpilot.system.ui.lib.multilang import SYSTEM_UI_DIR, UI_DIR, TRANSLATIONS_DIR, multilang +from openpilot.selfdrive.ui.translations.potools import extract_strings, generate_pot, merge_po, init_po LANGUAGES_FILE = os.path.join(str(TRANSLATIONS_DIR), "languages.json") POT_FILE = os.path.join(str(TRANSLATIONS_DIR), "app.pot") @@ -18,24 +19,17 @@ def update_translations(): if filename.endswith(".py"): files.append(os.path.relpath(os.path.join(root, filename), BASEDIR)) - # Create main translation file - cmd = ("xgettext -L Python --keyword=tr --keyword=trn:1,2 --keyword=tr_noop --from-code=UTF-8 " + - "--flag=tr:1:python-brace-format --flag=trn:1:python-brace-format --flag=trn:2:python-brace-format " + - f"-D {BASEDIR} -o {POT_FILE} {' '.join(files)}") - - ret = os.system(cmd) - assert ret == 0 + # Extract translatable strings and generate .pot template + entries = extract_strings(files, BASEDIR) + generate_pot(entries, POT_FILE) # Generate/update translation files for each language for name in multilang.languages.values(): - if os.path.exists(os.path.join(TRANSLATIONS_DIR, f"app_{name}.po")): - cmd = f"msgmerge --update --no-fuzzy-matching --backup=none --sort-output {TRANSLATIONS_DIR}/app_{name}.po {POT_FILE}" - ret = os.system(cmd) - assert ret == 0 + po_file = os.path.join(TRANSLATIONS_DIR, f"app_{name}.po") + if os.path.exists(po_file): + merge_po(po_file, POT_FILE) else: - cmd = f"msginit -l {name} --no-translator --input {POT_FILE} --output-file {TRANSLATIONS_DIR}/app_{name}.po" - ret = os.system(cmd) - assert ret == 0 + init_po(POT_FILE, po_file, name) if __name__ == "__main__": diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index ad2a5f93997..343c06a1e86 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -1,7 +1,7 @@ from importlib.resources import files -import os import json -import gettext +import os +import re from openpilot.common.basedir import BASEDIR from openpilot.common.swaglog import cloudlog @@ -23,14 +23,137 @@ "ja", ] +# Plural form selectors for supported languages +PLURAL_SELECTORS = { + 'en': lambda n: 0 if n == 1 else 1, + 'de': lambda n: 0 if n == 1 else 1, + 'fr': lambda n: 0 if n <= 1 else 1, + 'pt-BR': lambda n: 0 if n <= 1 else 1, + 'es': lambda n: 0 if n == 1 else 1, + 'tr': lambda n: 0 if n == 1 else 1, + 'uk': lambda n: 0 if n % 10 == 1 and n % 100 != 11 else (1 if 2 <= n % 10 <= 4 and not 12 <= n % 100 <= 14 else 2), + 'th': lambda n: 0, + 'zh-CHT': lambda n: 0, + 'zh-CHS': lambda n: 0, + 'ko': lambda n: 0, + 'ja': lambda n: 0, +} + + +def _parse_quoted(s: str) -> str: + """Parse a PO-format quoted string.""" + s = s.strip() + if not (s.startswith('"') and s.endswith('"')): + raise ValueError(f"Expected quoted string: {s!r}") + s = s[1:-1] + result: list[str] = [] + i = 0 + while i < len(s): + if s[i] == '\\' and i + 1 < len(s): + c = s[i + 1] + if c == 'n': + result.append('\n') + elif c == 't': + result.append('\t') + elif c == '"': + result.append('"') + elif c == '\\': + result.append('\\') + else: + result.append(s[i:i + 2]) + i += 2 + else: + result.append(s[i]) + i += 1 + return ''.join(result) + + +def load_translations(path) -> tuple[dict[str, str], dict[str, list[str]]]: + """Parse a .po file and return (translations, plurals) dicts. + + translations: msgid -> msgstr + plurals: msgid -> [msgstr[0], msgstr[1], ...] + """ + with open(str(path), encoding='utf-8') as f: + lines = f.readlines() + + translations: dict[str, str] = {} + plurals: dict[str, list[str]] = {} + + # Parser state + msgid = msgid_plural = msgstr = "" + msgstr_plurals: dict[int, str] = {} + field: str | None = None + plural_idx = 0 + + def finish(): + nonlocal msgid, msgid_plural, msgstr, msgstr_plurals, field + if msgid: # skip header (empty msgid) + if msgid_plural: + max_idx = max(msgstr_plurals.keys()) if msgstr_plurals else 0 + plurals[msgid] = [msgstr_plurals.get(i, '') for i in range(max_idx + 1)] + else: + translations[msgid] = msgstr + msgid = msgid_plural = msgstr = "" + msgstr_plurals = {} + field = None + + for raw in lines: + line = raw.strip() + + if not line: + finish() + continue + + if line.startswith('#'): + continue + + if line.startswith('msgid_plural '): + msgid_plural = _parse_quoted(line[len('msgid_plural '):]) + field = 'msgid_plural' + continue + + if line.startswith('msgid '): + msgid = _parse_quoted(line[len('msgid '):]) + field = 'msgid' + continue + + m = re.match(r'msgstr\[(\d+)]\s+(.*)', line) + if m: + plural_idx = int(m.group(1)) + msgstr_plurals[plural_idx] = _parse_quoted(m.group(2)) + field = 'msgstr_plural' + continue + + if line.startswith('msgstr '): + msgstr = _parse_quoted(line[len('msgstr '):]) + field = 'msgstr' + continue + + if line.startswith('"'): + val = _parse_quoted(line) + if field == 'msgid': + msgid += val + elif field == 'msgid_plural': + msgid_plural += val + elif field == 'msgstr': + msgstr += val + elif field == 'msgstr_plural': + msgstr_plurals[plural_idx] += val + + finish() + return translations, plurals + class Multilang: def __init__(self): self._params = Params() if Params is not None else None self._language: str = "en" - self.languages = {} - self.codes = {} - self._translation: gettext.NullTranslations | gettext.GNUTranslations = gettext.NullTranslations() + self.languages: dict[str, str] = {} + self.codes: dict[str, str] = {} + self._translations: dict[str, str] = {} + self._plurals: dict[str, list[str]] = {} + self._plural_selector = PLURAL_SELECTORS.get('en', lambda n: 0) self._load_languages() @property @@ -43,27 +166,30 @@ def requires_unifont(self) -> bool: def setup(self): try: - with TRANSLATIONS_DIR.joinpath(f'app_{self._language}.mo').open('rb') as fh: - translation = gettext.GNUTranslations(fh) - translation.install() - self._translation = translation + po_path = TRANSLATIONS_DIR.joinpath(f'app_{self._language}.po') + self._translations, self._plurals = load_translations(po_path) + self._plural_selector = PLURAL_SELECTORS.get(self._language, lambda n: 0) cloudlog.debug(f"Loaded translations for language: {self._language}") except FileNotFoundError: cloudlog.error(f"No translation file found for language: {self._language}, using default.") - gettext.install('app') - self._translation = gettext.NullTranslations() + self._translations = {} + self._plurals = {} def change_language(self, language_code: str) -> None: - # Reinstall gettext with the selected language self._params.put("LanguageSetting", language_code) self._language = language_code self.setup() def tr(self, text: str) -> str: - return self._translation.gettext(text) + return self._translations.get(text, text) def trn(self, singular: str, plural: str, n: int) -> str: - return self._translation.ngettext(singular, plural, n) + if singular in self._plurals: + idx = self._plural_selector(n) + forms = self._plurals[singular] + if idx < len(forms) and forms[idx]: + return forms[idx] + return singular if n == 1 else plural def _load_languages(self): with LANGUAGES_FILE.open(encoding='utf-8') as f: diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 79f56333cf3..98f1d878f8d 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -51,8 +51,7 @@ function install_ubuntu_deps() { $SUDO apt-get install -y --no-install-recommends \ python3-dev \ libncurses5-dev \ - libzstd-dev \ - gettext + libzstd-dev if [[ -d "/etc/udev/rules.d/" ]]; then # Setup jungle udev rules From 8952c947d15dd460849af14bdfc1f74985f1680c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 21:48:13 -0800 Subject: [PATCH 340/685] only build installer on device --- selfdrive/ui/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 20ba2e44d02..5556883ef87 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -17,7 +17,7 @@ env.Command( ) -if GetOption('extras'): +if GetOption('extras') and arch == "larch64": # build installers if arch != "Darwin": raylib_env = env.Clone() From 79bc6c3a52d5f86746b51e2ddc20799e82589300 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 21:59:39 -0800 Subject: [PATCH 341/685] replace python3-dev apt install with vendored package (#37374) * replace python3-dev apt install with vendored package Co-Authored-By: Claude Opus 4.6 * keep for agnos * cleaner --------- Co-authored-by: Claude Opus 4.6 --- SConstruct | 4 +++- pyproject.toml | 1 + tools/setup_dependencies.sh | 1 - uv.lock | 19 +++++++++++++------ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/SConstruct b/SConstruct index 446fcc417d3..bee043c5457 100644 --- a/SConstruct +++ b/SConstruct @@ -42,11 +42,14 @@ if arch != "larch64": import capnproto import eigen import ffmpeg as ffmpeg_pkg + import python3_dev import zeromq pkgs = [capnproto, eigen, ffmpeg_pkg, zeromq] + py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs pkgs = [] + py_include = sysconfig.get_paths()['include'] env = Environment( ENV={ @@ -159,7 +162,6 @@ if os.environ.get('SCONS_PROGRESS'): Progress(progress_function, interval=node_interval) # ********** Cython build environment ********** -py_include = sysconfig.get_paths()['include'] envCython = env.Clone() envCython["CPPPATH"] += [py_include, np.get_include()] envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"] diff --git a/pyproject.toml b/pyproject.toml index da36ea9df4d..3c4b1897a40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,7 @@ dependencies = [ "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", + "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 98f1d878f8d..368c6f20084 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -49,7 +49,6 @@ function install_ubuntu_deps() { xvfb $SUDO apt-get install -y --no-install-recommends \ - python3-dev \ libncurses5-dev \ libzstd-dev diff --git a/uv.lock b/uv.lock index 11ff9892781..2a03eb740de 100644 --- a/uv.lock +++ b/uv.lock @@ -116,7 +116,7 @@ wheels = [ [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#96208e8726374ab5229366102a17401edb68076c" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#7c17c720197a770e2ff9754c4aaebd75ba17b95c" } [[package]] name = "casadi" @@ -376,7 +376,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#96208e8726374ab5229366102a17401edb68076c" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#7c17c720197a770e2ff9754c4aaebd75ba17b95c" } [[package]] name = "execnet" @@ -390,7 +390,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#96208e8726374ab5229366102a17401edb68076c" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#7c17c720197a770e2ff9754c4aaebd75ba17b95c" } [[package]] name = "fonttools" @@ -437,7 +437,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#96208e8726374ab5229366102a17401edb68076c" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#7c17c720197a770e2ff9754c4aaebd75ba17b95c" } [[package]] name = "ghp-import" @@ -454,7 +454,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#96208e8726374ab5229366102a17401edb68076c" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#7c17c720197a770e2ff9754c4aaebd75ba17b95c" } [[package]] name = "google-crc32c" @@ -790,6 +790,7 @@ dependencies = [ { name = "pycryptodome" }, { name = "pyjwt" }, { name = "pyserial" }, + { name = "python3-dev" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib" }, @@ -877,6 +878,7 @@ requires-dist = [ { name = "pytest-mock", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, + { name = "python3-dev", git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib", specifier = ">5.5.0.3" }, @@ -1264,6 +1266,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python3-dev" +version = "3.12.8" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#7c17c720197a770e2ff9754c4aaebd75ba17b95c" } + [[package]] name = "pyyaml" version = "6.0.3" @@ -1630,7 +1637,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#96208e8726374ab5229366102a17401edb68076c" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#7c17c720197a770e2ff9754c4aaebd75ba17b95c" } [[package]] name = "zstandard" From 542e14306ec95b18a735a2b0cd73791106166b1a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Feb 2026 23:02:53 -0800 Subject: [PATCH 342/685] vendor zstd and ncurses (#37376) --- SConstruct | 4 +++- pyproject.toml | 2 ++ tools/setup_dependencies.sh | 4 ---- uv.lock | 28 +++++++++++++++++++++------- 4 files changed, 26 insertions(+), 12 deletions(-) diff --git a/SConstruct b/SConstruct index bee043c5457..16c1d8ee734 100644 --- a/SConstruct +++ b/SConstruct @@ -42,9 +42,11 @@ if arch != "larch64": import capnproto import eigen import ffmpeg as ffmpeg_pkg + import ncurses import python3_dev import zeromq - pkgs = [capnproto, eigen, ffmpeg_pkg, zeromq] + import zstd + pkgs = [capnproto, eigen, ffmpeg_pkg, ncurses, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs diff --git a/pyproject.toml b/pyproject.toml index 3c4b1897a40..f983aca8ae6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,8 @@ dependencies = [ "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", + "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", + "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 368c6f20084..df06d4ab1d7 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -48,10 +48,6 @@ function install_ubuntu_deps() { git \ xvfb - $SUDO apt-get install -y --no-install-recommends \ - libncurses5-dev \ - libzstd-dev - if [[ -d "/etc/udev/rules.d/" ]]; then # Setup jungle udev rules $SUDO tee /etc/udev/rules.d/12-panda_jungle.rules > /dev/null < Date: Tue, 24 Feb 2026 00:29:20 -0800 Subject: [PATCH 343/685] Make WifiNetworkButton self-contained --- .../mici/layouts/settings/network/__init__.py | 68 ++++++++++--------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 299df5fea04..8f9b18b9a08 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -12,13 +12,43 @@ class WifiNetworkButton(BigButton): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) + def __init__(self, wifi_manager: WifiManager): + self._wifi_manager = wifi_manager self._lock_txt = gui_app.texture("icons_mici/settings/network/new/lock.png", 28, 36) self._draw_lock = False - def set_draw_lock(self, draw: bool): - self._draw_lock = draw + self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 64, 56) + self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 64, 47) + self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 64, 47) + self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47) + + super().__init__("wi-fi", "not connected", self._wifi_slash_txt, scroll=True) + + def _update_state(self): + super()._update_state() + + # Update wi-fi button with ssid and ip address + # TODO: make sure we handle hidden ssids + wifi_state = self._wifi_manager.wifi_state + display_network = next((n for n in self._wifi_manager.networks if n.ssid == wifi_state.ssid), None) + if wifi_state.status == ConnectStatus.CONNECTING: + self.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) + self.set_value("connecting...") + elif wifi_state.status == ConnectStatus.CONNECTED: + self.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) + self.set_value(self._wifi_manager.ipv4_address or "obtaining IP...") + else: + display_network = None + self.set_text("wi-fi") + self.set_value("not connected") + + if display_network is not None: + strength = WifiIcon.get_strength_icon_idx(display_network.strength) + self.set_icon(self._wifi_full_txt if strength == 2 else self._wifi_medium_txt if strength == 1 else self._wifi_low_txt) + self._draw_lock = display_network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED) + else: + self.set_icon(self._wifi_slash_txt) + self._draw_lock = False def _draw_content(self, btn_y: float): super()._draw_content(btn_y) @@ -83,12 +113,7 @@ def network_metered_callback(value: str): self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) self._network_metered_btn.set_enabled(False) - self._wifi_slash_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 64, 56) - self._wifi_low_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_low.png", 64, 47) - self._wifi_medium_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_medium.png", 64, 47) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 64, 47) - - self._wifi_button = WifiNetworkButton("wi-fi", "not connected", self._wifi_slash_txt, scroll=True) + self._wifi_button = WifiNetworkButton(self._wifi_manager) self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) # ******** Advanced settings ******** @@ -133,29 +158,6 @@ def _update_state(self): self._apn_btn.set_visible(show_cell_settings) self._cellular_metered_btn.set_visible(show_cell_settings) - # Update wi-fi button with ssid and ip address - # TODO: make sure we handle hidden ssids - wifi_state = self._wifi_manager.wifi_state - display_network = next((n for n in self._wifi_manager.networks if n.ssid == wifi_state.ssid), None) - if wifi_state.status == ConnectStatus.CONNECTING: - self._wifi_button.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) - self._wifi_button.set_value("connecting...") - elif wifi_state.status == ConnectStatus.CONNECTED: - self._wifi_button.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) - self._wifi_button.set_value(self._wifi_manager.ipv4_address or "obtaining IP...") - else: - display_network = None - self._wifi_button.set_text("wi-fi") - self._wifi_button.set_value("not connected") - - if display_network is not None: - strength = WifiIcon.get_strength_icon_idx(display_network.strength) - self._wifi_button.set_icon(self._wifi_full_txt if strength == 2 else self._wifi_medium_txt if strength == 1 else self._wifi_low_txt) - self._wifi_button.set_draw_lock(display_network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED)) - else: - self._wifi_button.set_icon(self._wifi_slash_txt) - self._wifi_button.set_draw_lock(False) - def show_event(self): super().show_event() self._wifi_manager.set_active(True) From 9f7002fdf1519d9c1c30556ccef908e8d3ce1c44 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 00:30:40 -0800 Subject: [PATCH 344/685] mici setup: set core affinity --- system/ui/mici_setup.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index aa6f54c508b..51feb63c051 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -14,8 +14,10 @@ import pyray as rl from cereal import log +from openpilot.common.realtime import config_realtime_process, set_core_affinity +from openpilot.common.swaglog import cloudlog from openpilot.common.utils import run_cmd -from openpilot.system.hardware import HARDWARE +from openpilot.system.hardware import HARDWARE, TICI from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 @@ -704,6 +706,14 @@ def download_failed(self, url: str, reason: str): def main(): + config_realtime_process(0, 51) + # attempt to affine. AGNOS will start setup with all cores, should only fail when manually launching with screen off + if TICI: + try: + set_core_affinity([5]) + except OSError: + cloudlog.exception("Failed to set core affinity for setup process") + try: gui_app.init_window("Setup") setup = Setup() From cf083711bbe12babad1425b802b48bcd3c3ee2ec Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 00:34:03 -0800 Subject: [PATCH 345/685] mici setup: match tici network timeout --- system/ui/mici_setup.py | 7 +++---- system/ui/tici_setup.py | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 51feb63c051..6c73f14e3ac 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -51,11 +51,10 @@ class NetworkConnectivityMonitor: - def __init__(self, should_check: Callable[[], bool] | None = None, check_interval: float = 1.0): + def __init__(self, should_check: Callable[[], bool] | None = None): self.network_connected = threading.Event() self.wifi_connected = threading.Event() self._should_check = should_check or (lambda: True) - self._check_interval = check_interval self._stop_event = threading.Event() self._thread: threading.Thread | None = None @@ -80,7 +79,7 @@ def _run(self): if self._should_check(): try: request = urllib.request.Request(OPENPILOT_URL, method="HEAD") - urllib.request.urlopen(request, timeout=1.0) + urllib.request.urlopen(request, timeout=2.0) self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() @@ -89,7 +88,7 @@ def _run(self): else: self.reset() - if self._stop_event.wait(timeout=self._check_interval): + if self._stop_event.wait(timeout=1.0): break diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py index 39f95cc8a00..8098e9ea273 100755 --- a/system/ui/tici_setup.py +++ b/system/ui/tici_setup.py @@ -218,7 +218,7 @@ def check_network_connectivity(self): while not self.stop_network_check_thread.is_set(): if self.state == SetupState.NETWORK_SETUP: try: - urllib.request.urlopen(OPENPILOT_URL, timeout=2) + urllib.request.urlopen(OPENPILOT_URL, timeout=2.0) self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() @@ -226,7 +226,7 @@ def check_network_connectivity(self): self.wifi_connected.clear() except Exception: self.network_connected.clear() - time.sleep(1) + time.sleep(1.0) def start_network_check(self): if self.network_check_thread is None or not self.network_check_thread.is_alive(): From faa23595af7e589a0581a4353f84b6f29f95b35b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 00:35:51 -0800 Subject: [PATCH 346/685] mici buttons and sliders: use semi bold --- system/ui/widgets/button.py | 2 +- system/ui/widgets/slider.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 9c0ea75b428..67125d70919 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -232,7 +232,7 @@ def __init__(self, text: str): self._load_assets() - self._label = UnifiedLabel(text, 36, font_weight=FontWeight.MEDIUM, + self._label = UnifiedLabel(text, 36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 734c5b5d65f..8f4bbfc0112 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -33,7 +33,7 @@ def __init__(self, title: str, confirm_callback: Callable | None = None): self._is_dragging_circle = False - self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.MEDIUM, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), + self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9) From 081bb51e586a4f48ad00d5eb05b9b0616b0c3fe7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 00:50:32 -0800 Subject: [PATCH 347/685] mici: add missing Scroller hide events --- selfdrive/ui/mici/layouts/main.py | 2 ++ selfdrive/ui/mici/layouts/offroad_alerts.py | 5 +++++ selfdrive/ui/mici/layouts/settings/developer.py | 4 ++++ selfdrive/ui/mici/layouts/settings/device.py | 4 ++++ selfdrive/ui/mici/layouts/settings/network/__init__.py | 1 + selfdrive/ui/mici/layouts/settings/toggles.py | 4 ++++ 6 files changed, 20 insertions(+) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index dc01aba56f5..3e3948eeaba 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -63,9 +63,11 @@ def _setup_callbacks(self): device.add_interactive_timeout_callback(self._on_interactive_timeout) def show_event(self): + super().show_event() self._scroller.show_event() def hide_event(self): + super().hide_event() self._scroller.hide_event() def _scroll_to(self, layout: Widget): diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 565d27593d1..bc1cd02c5d0 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -289,10 +289,15 @@ def refresh(self) -> int: def show_event(self): """Reset scroll position when shown and refresh alerts.""" + super().show_event() self._scroller.show_event() self._last_refresh = time.monotonic() self.refresh() + def hide_event(self): + super().hide_event() + self._scroller.hide_event() + def _update_state(self): """Periodically refresh alerts.""" # Refresh alerts periodically, not every frame diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index f107df7207a..b04d6968234 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -104,6 +104,10 @@ def show_event(self): self._scroller.show_event() self._update_toggles() + def hide_event(self): + super().hide_event() + self._scroller.hide_event() + def _render(self, rect: rl.Rectangle): self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index d919df1c16f..cd7172455f5 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -345,5 +345,9 @@ def show_event(self): super().show_event() self._scroller.show_event() + def hide_event(self): + super().hide_event() + self._scroller.hide_event() + def _render(self, rect: rl.Rectangle): self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 8f9b18b9a08..bdae9245669 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -168,6 +168,7 @@ def show_event(self): def hide_event(self): super().hide_event() + self._scroller.hide_event() self._wifi_manager.set_active(False) gui_app.set_nav_stack_tick(None) diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index b0f7189230a..d6a91b40f72 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -71,6 +71,10 @@ def show_event(self): self._scroller.show_event() self._update_toggles() + def hide_event(self): + super().hide_event() + self._scroller.hide_event() + def _update_toggles(self): ui_state.update_params() From 3352e48c517f8e8f19df95cb3a4bf88031f31cb2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 00:50:47 -0800 Subject: [PATCH 348/685] Scroller: add blocking scroll to (#37378) * rename * make tuple * blocking --- system/ui/widgets/scroller.py | 43 +++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 7eff8ae594e..b01b34be14a 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -78,8 +78,8 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo self._reset_scroll_at_show = True - self._scrolling_to: float | None = None - self._scroll_filter = FirstOrderFilter(0.0, SCROLL_RC, 1 / gui_app.target_fps) + self._scrolling_to: tuple[float | None, bool] = (None, False) # target offset, block user scrolling + self._scrolling_to_filter = FirstOrderFilter(0.0, SCROLL_RC, 1 / gui_app.target_fps) self._zoom_filter = FirstOrderFilter(1.0, 0.2, 1 / gui_app.target_fps) self._zoom_out_t: float = 0.0 @@ -115,7 +115,9 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo def set_reset_scroll_at_show(self, scroll: bool): self._reset_scroll_at_show = scroll - def scroll_to(self, pos: float, smooth: bool = False): + def scroll_to(self, pos: float, smooth: bool = False, block: bool = False): + assert not block or smooth, "Instant scroll cannot be blocking" + # already there if abs(pos) < 1: return @@ -123,13 +125,14 @@ def scroll_to(self, pos: float, smooth: bool = False): # FIXME: the padding correction doesn't seem correct scroll_offset = self.scroll_panel.get_offset() - pos if smooth: - self._scrolling_to = scroll_offset + self._scrolling_to_filter.x = self.scroll_panel.get_offset() + self._scrolling_to = scroll_offset, block else: self.scroll_panel.set_offset(scroll_offset) @property def is_auto_scrolling(self) -> bool: - return self._scrolling_to is not None + return self._scrolling_to[0] is not None @property def items(self) -> list[Widget]: @@ -144,7 +147,7 @@ def add_widget(self, item: Widget) -> None: # preserve original touch valid callback original_touch_valid_callback = item._touch_valid_callback - item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled and self._scrolling_to is None + item.set_touch_valid_callback(lambda: self.scroll_panel.is_touch_valid() and self.enabled and self._scrolling_to[0] is None and not self.moving_items and (original_touch_valid_callback() if original_touch_valid_callback else True)) @@ -154,7 +157,7 @@ def set_scrolling_enabled(self, enabled: bool | Callable[[], bool]) -> None: def _update_state(self): if DO_ZOOM: - if self._scrolling_to is not None or self.scroll_panel.state != ScrollState.STEADY: + if self._scrolling_to[0] is not None or self.scroll_panel.state != ScrollState.STEADY: self._zoom_out_t = rl.get_time() + MIN_ZOOM_ANIMATION_TIME self._zoom_filter.update(0.85) else: @@ -164,24 +167,22 @@ def _update_state(self): else: self._zoom_filter.update(0.85) - # Cancel auto-scroll if user starts manually scrolling - if self._scrolling_to is not None and (self.scroll_panel.state == ScrollState.PRESSED or self.scroll_panel.state == ScrollState.MANUAL_SCROLL): - self._scrolling_to = None + # Cancel auto-scroll if user starts manually scrolling (unless blocking) + if (self.scroll_panel.state in (ScrollState.PRESSED, ScrollState.MANUAL_SCROLL) and + self._scrolling_to[0] is not None and not self._scrolling_to[1]): + self._scrolling_to = None, False - if self._scrolling_to is not None and len(self._pending_lift) == 0: - self._scroll_filter.update(self._scrolling_to) - self.scroll_panel.set_offset(self._scroll_filter.x) + if self._scrolling_to[0] is not None and len(self._pending_lift) == 0: + self._scrolling_to_filter.update(self._scrolling_to[0]) + self.scroll_panel.set_offset(self._scrolling_to_filter.x) - if abs(self._scroll_filter.x - self._scrolling_to) < 1: - self.scroll_panel.set_offset(self._scrolling_to) - self._scrolling_to = None - else: - # keep current scroll position up to date - self._scroll_filter.x = self.scroll_panel.get_offset() + if abs(self._scrolling_to_filter.x - self._scrolling_to[0]) < 1: + self.scroll_panel.set_offset(self._scrolling_to[0]) + self._scrolling_to = None, False def _get_scroll(self, visible_items: list[Widget], content_size: float) -> float: scroll_enabled = self._scroll_enabled() if callable(self._scroll_enabled) else self._scroll_enabled - self.scroll_panel.set_enabled(scroll_enabled and self.enabled) + self.scroll_panel.set_enabled(scroll_enabled and self.enabled and not self._scrolling_to[1]) self.scroll_panel.update(self._rect, content_size) if not self._snap_items: return round(self.scroll_panel.get_offset()) @@ -405,5 +406,7 @@ def hide_event(self): self._move_lift.clear() self._pending_lift.clear() self._pending_move.clear() + self._scrolling_to = None, False + self._scrolling_to_filter.x = 0.0 for item in self._items: item.hide_event() From c787507449024fe0dae3cb0f19f1a481613ea63e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 24 Feb 2026 09:43:47 -0800 Subject: [PATCH 349/685] Revert "rm onnx (#37285)" (#37379) This reverts commit 23e1c4f49e63e550a44a85f0020b601bd9376a1d. --- pyproject.toml | 3 ++ scripts/reporter.py | 28 ++++---------- selfdrive/modeld/get_model_metadata.py | 40 ++++++------------- uv.lock | 53 ++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 49 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f983aca8ae6..b40bed23d1c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,9 @@ dependencies = [ "libusb1", "spidev; platform_system == 'Linux'", + # modeld + "onnx >= 1.14.0", + # logging "pyzmq", "sentry-sdk", diff --git a/scripts/reporter.py b/scripts/reporter.py index d894b8af48a..903fcc89111 100755 --- a/scripts/reporter.py +++ b/scripts/reporter.py @@ -1,33 +1,17 @@ #!/usr/bin/env python3 import os import glob - -from tinygrad.nn.onnx import OnnxPBParser +import onnx BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) - MASTER_PATH = os.getenv("MASTER_PATH", BASEDIR) MODEL_PATH = "/selfdrive/modeld/models/" - -class MetadataOnnxPBParser(OnnxPBParser): - def _parse_ModelProto(self) -> dict: - obj = {"metadata_props": []} - for fid, wire_type in self._parse_message(self.reader.len): - match fid: - case 14: - obj["metadata_props"].append(self._parse_StringStringEntryProto()) - case _: - self.reader.skip_field(wire_type) - return obj - - def get_checkpoint(f): - model = MetadataOnnxPBParser(f).parse() - metadata = {prop["key"]: prop["value"] for prop in model["metadata_props"]} + model = onnx.load(f) + metadata = {prop.key: prop.value for prop in model.metadata_props} return metadata['model_checkpoint'].split('/')[0] - if __name__ == "__main__": print("| | master | PR branch |") print("|-| ----- | --------- |") @@ -40,4 +24,8 @@ def get_checkpoint(f): fn = os.path.basename(f) master = get_checkpoint(MASTER_PATH + MODEL_PATH + fn) pr = get_checkpoint(BASEDIR + MODEL_PATH + fn) - print("|", fn, "|", f"[{master}](https://reporter.comma.life/experiment/{master})", "|", f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|") + print( + "|", fn, "|", + f"[{master}](https://reporter.comma.life/experiment/{master})", "|", + f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|" + ) diff --git a/selfdrive/modeld/get_model_metadata.py b/selfdrive/modeld/get_model_metadata.py index 838b1e9f404..2001d23d752 100755 --- a/selfdrive/modeld/get_model_metadata.py +++ b/selfdrive/modeld/get_model_metadata.py @@ -1,51 +1,33 @@ #!/usr/bin/env python3 import sys import pathlib +import onnx import codecs import pickle from typing import Any -from tinygrad.nn.onnx import OnnxPBParser - - -class MetadataOnnxPBParser(OnnxPBParser): - def _parse_ModelProto(self) -> dict: - obj: dict[str, Any] = {"graph": {"input": [], "output": []}, "metadata_props": []} - for fid, wire_type in self._parse_message(self.reader.len): - match fid: - case 7: - obj["graph"] = self._parse_GraphProto() - case 14: - obj["metadata_props"].append(self._parse_StringStringEntryProto()) - case _: - self.reader.skip_field(wire_type) - return obj - - -def get_name_and_shape(value_info: dict[str, Any]) -> tuple[str, tuple[int, ...]]: - shape = tuple(int(dim) if isinstance(dim, int) else 0 for dim in value_info["parsed_type"].shape) - name = value_info["name"] +def get_name_and_shape(value_info:onnx.ValueInfoProto) -> tuple[str, tuple[int,...]]: + shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) + name = value_info.name return name, shape - -def get_metadata_value_by_name(model: dict[str, Any], name: str) -> str | Any: - for prop in model["metadata_props"]: - if prop["key"] == name: - return prop["value"] +def get_metadata_value_by_name(model:onnx.ModelProto, name:str) -> str | Any: + for prop in model.metadata_props: + if prop.key == name: + return prop.value return None - if __name__ == "__main__": model_path = pathlib.Path(sys.argv[1]) - model = MetadataOnnxPBParser(model_path).parse() + model = onnx.load(str(model_path)) output_slices = get_metadata_value_by_name(model, 'output_slices') assert output_slices is not None, 'output_slices not found in metadata' metadata = { 'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'), 'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")), - 'input_shapes': dict(get_name_and_shape(x) for x in model["graph"]["input"]), - 'output_shapes': dict(get_name_and_shape(x) for x in model["graph"]["output"]), + 'input_shapes': dict([get_name_and_shape(x) for x in model.graph.input]), + 'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output]) } metadata_path = model_path.parent / (model_path.stem + '_metadata.pkl') diff --git a/uv.lock b/uv.lock index 1dcc4892273..b27732b18b0 100644 --- a/uv.lock +++ b/uv.lock @@ -691,6 +691,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, ] +[[package]] +name = "ml-dtypes" +version = "0.5.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, + { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, + { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, + { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, + { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, +] + [[package]] name = "mpmath" version = "1.3.0" @@ -751,6 +767,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, ] +[[package]] +name = "onnx" +version = "1.20.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ml-dtypes" }, + { name = "numpy" }, + { name = "protobuf" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, + { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, + { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, + { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216, upload-time = "2026-01-10T01:39:39.46Z" }, + { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207, upload-time = "2026-01-10T01:39:41.955Z" }, + { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155, upload-time = "2026-01-10T01:39:45.536Z" }, +] + [[package]] name = "opencv-python-headless" version = "4.13.0.92" @@ -791,6 +827,7 @@ dependencies = [ { name = "libusb1" }, { name = "ncurses" }, { name = "numpy" }, + { name = "onnx" }, { name = "psutil" }, { name = "pycapnp" }, { name = "pycryptodome" }, @@ -873,6 +910,7 @@ requires-dist = [ { name = "mkdocs", marker = "extra == 'docs'" }, { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, { name = "numpy", specifier = ">=2.0" }, + { name = "onnx", specifier = ">=1.14.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, @@ -1039,6 +1077,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] +[[package]] +name = "protobuf" +version = "6.33.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, + { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, + { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, + { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, + { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, +] + [[package]] name = "psutil" version = "7.2.2" From a064de7ceb483360fafba22f6bd05cc7081827e7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 24 Feb 2026 12:00:39 -0800 Subject: [PATCH 350/685] use vendored libjpeg-turbo (#37381) --- SConstruct | 11 +++-------- pyproject.toml | 2 ++ tools/setup_dependencies.sh | 1 - uv.lock | 32 +++++++++++++++++++++++--------- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/SConstruct b/SConstruct index 16c1d8ee734..8d58cc012af 100644 --- a/SConstruct +++ b/SConstruct @@ -28,7 +28,6 @@ AddOption('--minimal', arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip() if platform.system() == "Darwin": arch = "Darwin" - brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() elif arch == "aarch64" and os.path.isfile('/TICI'): arch = "larch64" assert arch in [ @@ -42,11 +41,13 @@ if arch != "larch64": import capnproto import eigen import ffmpeg as ffmpeg_pkg + import libjpeg import ncurses + import openssl3 import python3_dev import zeromq import zstd - pkgs = [capnproto, eigen, ffmpeg_pkg, ncurses, zeromq, zstd] + pkgs = [capnproto, eigen, ffmpeg_pkg, libjpeg, ncurses, openssl3, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs @@ -121,16 +122,10 @@ if arch == "larch64": env.Append(CXXFLAGS=arch_flags) elif arch == "Darwin": env.Append(LIBPATH=[ - f"{brew_prefix}/lib", - f"{brew_prefix}/opt/openssl@3.0/lib", "/System/Library/Frameworks/OpenGL.framework/Libraries", ]) env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"]) env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"]) - env.Append(CPPPATH=[ - f"{brew_prefix}/include", - f"{brew_prefix}/opt/openssl@3.0/include", - ]) else: env.Append(LIBPATH=[ "/usr/lib", diff --git a/pyproject.toml b/pyproject.toml index b40bed23d1c..e7debf78dca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,8 @@ dependencies = [ "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", + "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", + "openssl3 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=openssl3", "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index df06d4ab1d7..7c0bf7d7085 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -42,7 +42,6 @@ function install_ubuntu_deps() { ca-certificates \ build-essential \ curl \ - libssl-dev \ libcurl4-openssl-dev \ locales \ git \ diff --git a/uv.lock b/uv.lock index b27732b18b0..658f62a3315 100644 --- a/uv.lock +++ b/uv.lock @@ -116,7 +116,7 @@ wheels = [ [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#736268e60bb617876ebaa05c5a4db9b0d70a4793" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#31af284805d0787a689e129311d992bec14a2400" } [[package]] name = "casadi" @@ -376,7 +376,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#736268e60bb617876ebaa05c5a4db9b0d70a4793" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#31af284805d0787a689e129311d992bec14a2400" } [[package]] name = "execnet" @@ -390,7 +390,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#736268e60bb617876ebaa05c5a4db9b0d70a4793" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#31af284805d0787a689e129311d992bec14a2400" } [[package]] name = "fonttools" @@ -437,7 +437,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#736268e60bb617876ebaa05c5a4db9b0d70a4793" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#31af284805d0787a689e129311d992bec14a2400" } [[package]] name = "ghp-import" @@ -454,7 +454,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#736268e60bb617876ebaa05c5a4db9b0d70a4793" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#31af284805d0787a689e129311d992bec14a2400" } [[package]] name = "google-crc32c" @@ -569,6 +569,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, ] +[[package]] +name = "libjpeg" +version = "3.1.0" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#31af284805d0787a689e129311d992bec14a2400" } + [[package]] name = "libusb1" version = "3.3.1" @@ -746,7 +751,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#736268e60bb617876ebaa05c5a4db9b0d70a4793" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#31af284805d0787a689e129311d992bec14a2400" } [[package]] name = "numpy" @@ -824,10 +829,12 @@ dependencies = [ { name = "inputs" }, { name = "jeepney" }, { name = "json-rpc" }, + { name = "libjpeg" }, { name = "libusb1" }, { name = "ncurses" }, { name = "numpy" }, { name = "onnx" }, + { name = "openssl3" }, { name = "psutil" }, { name = "pycapnp" }, { name = "pycryptodome" }, @@ -904,6 +911,7 @@ requires-dist = [ { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, + { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases" }, { name = "libusb1" }, { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, @@ -912,6 +920,7 @@ requires-dist = [ { name = "numpy", specifier = ">=2.0" }, { name = "onnx", specifier = ">=1.14.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, + { name = "openssl3", git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, { name = "pycapnp" }, @@ -947,6 +956,11 @@ requires-dist = [ ] provides-extras = ["docs", "testing", "dev", "tools"] +[[package]] +name = "openssl3" +version = "3.4.1" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#31af284805d0787a689e129311d992bec14a2400" } + [[package]] name = "packaging" version = "26.0" @@ -1331,7 +1345,7 @@ wheels = [ [[package]] name = "python3-dev" version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#736268e60bb617876ebaa05c5a4db9b0d70a4793" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#31af284805d0787a689e129311d992bec14a2400" } [[package]] name = "pyyaml" @@ -1699,7 +1713,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#736268e60bb617876ebaa05c5a4db9b0d70a4793" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#31af284805d0787a689e129311d992bec14a2400" } [[package]] name = "zstandard" @@ -1729,4 +1743,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#736268e60bb617876ebaa05c5a4db9b0d70a4793" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#31af284805d0787a689e129311d992bec14a2400" } From 6db6d792111c523455d9bdc0ac817e2f534f50e7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 15:34:48 -0800 Subject: [PATCH 351/685] WifiUi: decouple button update from move/scroll (#37383) * meh * hmm * can also do this * keep behavior * rm --- .../mici/layouts/settings/network/wifi_ui.py | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 2efc45f46a7..ec6b0c9dc5e 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -325,19 +325,11 @@ def _update_buttons(self): if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: btn.set_network_missing(True) - # Move connecting/connected network to the front with animation - front_ssid = self._wifi_manager.wifi_state.ssid - front_btn_idx = next((i for i, btn in enumerate(self._scroller.items) - if isinstance(btn, WifiButton) and - btn.network.ssid == front_ssid), None) if front_ssid else None - - if front_btn_idx is not None and front_btn_idx > 0: - self._scroller.move_item(front_btn_idx, 0) + self._move_network_to_front(self._wifi_manager.wifi_state.ssid) def _connect_with_password(self, ssid: str, password: str): self._wifi_manager.connect_to_network(ssid, password) - self._scroller.scroll_to(self._scroller.scroll_panel.get_offset(), smooth=True) - self._update_buttons() + self._move_network_to_front(ssid, scroll=True) def _connect_to_network(self, ssid: str): network = self._networks.get(ssid) @@ -353,8 +345,7 @@ def _connect_to_network(self, ssid: str): self._on_need_auth(network.ssid, False) return - self._scroller.scroll_to(self._scroller.scroll_panel.get_offset(), smooth=True) - self._update_buttons() + self._move_network_to_front(ssid, scroll=True) def _on_need_auth(self, ssid, incorrect_password=True): if incorrect_password: @@ -374,6 +365,19 @@ def _on_forgotten(self, ssid): if isinstance(btn, WifiButton) and btn.network.ssid == ssid: btn.on_forgotten() + def _move_network_to_front(self, ssid: str | None, scroll: bool = False): + # Move connecting/connected network to the front with animation + front_btn_idx = next((i for i, btn in enumerate(self._scroller.items) + if isinstance(btn, WifiButton) and + btn.network.ssid == ssid), None) if ssid else None + + if front_btn_idx is not None and front_btn_idx > 0: + self._scroller.move_item(front_btn_idx, 0) + + if scroll: + # Scroll to the new position of the network + self._scroller.scroll_to(self._scroller.scroll_panel.get_offset(), smooth=True) + def _update_state(self): super()._update_state() From 159d3a30e318c65cef66fd8ff5ff2345e44a9ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 24 Feb 2026 15:35:52 -0800 Subject: [PATCH 352/685] RM onnx (#37377) * Give tf flags to onnx parse * rm onnx again * update lock --- pyproject.toml | 3 -- scripts/reporter.py | 28 ++++++++++---- selfdrive/modeld/SConscript | 15 ++++---- selfdrive/modeld/get_model_metadata.py | 40 +++++++++++++------ uv.lock | 53 -------------------------- 5 files changed, 57 insertions(+), 82 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e7debf78dca..bdcbd778019 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,9 +46,6 @@ dependencies = [ "libusb1", "spidev; platform_system == 'Linux'", - # modeld - "onnx >= 1.14.0", - # logging "pyzmq", "sentry-sdk", diff --git a/scripts/reporter.py b/scripts/reporter.py index 903fcc89111..d894b8af48a 100755 --- a/scripts/reporter.py +++ b/scripts/reporter.py @@ -1,17 +1,33 @@ #!/usr/bin/env python3 import os import glob -import onnx + +from tinygrad.nn.onnx import OnnxPBParser BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) + MASTER_PATH = os.getenv("MASTER_PATH", BASEDIR) MODEL_PATH = "/selfdrive/modeld/models/" + +class MetadataOnnxPBParser(OnnxPBParser): + def _parse_ModelProto(self) -> dict: + obj = {"metadata_props": []} + for fid, wire_type in self._parse_message(self.reader.len): + match fid: + case 14: + obj["metadata_props"].append(self._parse_StringStringEntryProto()) + case _: + self.reader.skip_field(wire_type) + return obj + + def get_checkpoint(f): - model = onnx.load(f) - metadata = {prop.key: prop.value for prop in model.metadata_props} + model = MetadataOnnxPBParser(f).parse() + metadata = {prop["key"]: prop["value"] for prop in model["metadata_props"]} return metadata['model_checkpoint'].split('/')[0] + if __name__ == "__main__": print("| | master | PR branch |") print("|-| ----- | --------- |") @@ -24,8 +40,4 @@ def get_checkpoint(f): fn = os.path.basename(f) master = get_checkpoint(MASTER_PATH + MODEL_PATH + fn) pr = get_checkpoint(BASEDIR + MODEL_PATH + fn) - print( - "|", fn, "|", - f"[{master}](https://reporter.comma.life/experiment/{master})", "|", - f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|" - ) + print("|", fn, "|", f"[{master}](https://reporter.comma.life/experiment/{master})", "|", f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|") diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index ed5c50beb3c..95ac06bb1af 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -13,19 +13,20 @@ tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + " def estimate_pickle_max_size(onnx_size): return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty -# Get model metadata -for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: - fn = File(f"models/{model_name}").abspath - script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)] - cmd = f'python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' - lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) - # compile warp # THREADS=0 is need to prevent bug: https://github.com/tinygrad/tinygrad/issues/14689 tg_flags = { 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', 'Darwin': f'DEV=CPU THREADS=0 HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env }.get(arch, 'DEV=CPU CPU_LLVM=1 THREADS=0') + +# Get model metadata +for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: + fn = File(f"models/{model_name}").abspath + script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)] + cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' + lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) + image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') diff --git a/selfdrive/modeld/get_model_metadata.py b/selfdrive/modeld/get_model_metadata.py index 2001d23d752..838b1e9f404 100755 --- a/selfdrive/modeld/get_model_metadata.py +++ b/selfdrive/modeld/get_model_metadata.py @@ -1,33 +1,51 @@ #!/usr/bin/env python3 import sys import pathlib -import onnx import codecs import pickle from typing import Any -def get_name_and_shape(value_info:onnx.ValueInfoProto) -> tuple[str, tuple[int,...]]: - shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) - name = value_info.name +from tinygrad.nn.onnx import OnnxPBParser + + +class MetadataOnnxPBParser(OnnxPBParser): + def _parse_ModelProto(self) -> dict: + obj: dict[str, Any] = {"graph": {"input": [], "output": []}, "metadata_props": []} + for fid, wire_type in self._parse_message(self.reader.len): + match fid: + case 7: + obj["graph"] = self._parse_GraphProto() + case 14: + obj["metadata_props"].append(self._parse_StringStringEntryProto()) + case _: + self.reader.skip_field(wire_type) + return obj + + +def get_name_and_shape(value_info: dict[str, Any]) -> tuple[str, tuple[int, ...]]: + shape = tuple(int(dim) if isinstance(dim, int) else 0 for dim in value_info["parsed_type"].shape) + name = value_info["name"] return name, shape -def get_metadata_value_by_name(model:onnx.ModelProto, name:str) -> str | Any: - for prop in model.metadata_props: - if prop.key == name: - return prop.value + +def get_metadata_value_by_name(model: dict[str, Any], name: str) -> str | Any: + for prop in model["metadata_props"]: + if prop["key"] == name: + return prop["value"] return None + if __name__ == "__main__": model_path = pathlib.Path(sys.argv[1]) - model = onnx.load(str(model_path)) + model = MetadataOnnxPBParser(model_path).parse() output_slices = get_metadata_value_by_name(model, 'output_slices') assert output_slices is not None, 'output_slices not found in metadata' metadata = { 'model_checkpoint': get_metadata_value_by_name(model, 'model_checkpoint'), 'output_slices': pickle.loads(codecs.decode(output_slices.encode(), "base64")), - 'input_shapes': dict([get_name_and_shape(x) for x in model.graph.input]), - 'output_shapes': dict([get_name_and_shape(x) for x in model.graph.output]) + 'input_shapes': dict(get_name_and_shape(x) for x in model["graph"]["input"]), + 'output_shapes': dict(get_name_and_shape(x) for x in model["graph"]["output"]), } metadata_path = model_path.parent / (model_path.stem + '_metadata.pkl') diff --git a/uv.lock b/uv.lock index 658f62a3315..90944176939 100644 --- a/uv.lock +++ b/uv.lock @@ -696,22 +696,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, ] -[[package]] -name = "ml-dtypes" -version = "0.5.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/0e/4a/c27b42ed9b1c7d13d9ba8b6905dece787d6259152f2309338aed29b2447b/ml_dtypes-0.5.4.tar.gz", hash = "sha256:8ab06a50fb9bf9666dd0fe5dfb4676fa2b0ac0f31ecff72a6c3af8e22c063453", size = 692314, upload-time = "2025-11-17T22:32:31.031Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/b8/3c70881695e056f8a32f8b941126cf78775d9a4d7feba8abcb52cb7b04f2/ml_dtypes-0.5.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a174837a64f5b16cab6f368171a1a03a27936b31699d167684073ff1c4237dac", size = 676927, upload-time = "2025-11-17T22:31:48.182Z" }, - { url = "https://files.pythonhosted.org/packages/54/0f/428ef6881782e5ebb7eca459689448c0394fa0a80bea3aa9262cba5445ea/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7f7c643e8b1320fd958bf098aa7ecf70623a42ec5154e3be3be673f4c34d900", size = 5028464, upload-time = "2025-11-17T22:31:50.135Z" }, - { url = "https://files.pythonhosted.org/packages/3a/cb/28ce52eb94390dda42599c98ea0204d74799e4d8047a0eb559b6fd648056/ml_dtypes-0.5.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ad459e99793fa6e13bd5b7e6792c8f9190b4e5a1b45c63aba14a4d0a7f1d5ff", size = 5009002, upload-time = "2025-11-17T22:31:52.001Z" }, - { url = "https://files.pythonhosted.org/packages/f5/f0/0cfadd537c5470378b1b32bd859cf2824972174b51b873c9d95cfd7475a5/ml_dtypes-0.5.4-cp312-cp312-win_amd64.whl", hash = "sha256:c1a953995cccb9e25a4ae19e34316671e4e2edaebe4cf538229b1fc7109087b7", size = 212222, upload-time = "2025-11-17T22:31:53.742Z" }, - { url = "https://files.pythonhosted.org/packages/16/2e/9acc86985bfad8f2c2d30291b27cd2bb4c74cea08695bd540906ed744249/ml_dtypes-0.5.4-cp312-cp312-win_arm64.whl", hash = "sha256:9bad06436568442575beb2d03389aa7456c690a5b05892c471215bfd8cf39460", size = 160793, upload-time = "2025-11-17T22:31:55.358Z" }, -] - [[package]] name = "mpmath" version = "1.3.0" @@ -772,26 +756,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, ] -[[package]] -name = "onnx" -version = "1.20.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "ml-dtypes" }, - { name = "numpy" }, - { name = "protobuf" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/3b/8a/335c03a8683a88a32f9a6bb98899ea6df241a41df64b37b9696772414794/onnx-1.20.1.tar.gz", hash = "sha256:ded16de1df563d51fbc1ad885f2a426f814039d8b5f4feb77febe09c0295ad67", size = 12048980, upload-time = "2026-01-10T01:40:03.043Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/4c/4b17e82f91ab9aa07ff595771e935ca73547b035030dc5f5a76e63fbfea9/onnx-1.20.1-cp312-abi3-macosx_12_0_universal2.whl", hash = "sha256:1d923bb4f0ce1b24c6859222a7e6b2f123e7bfe7623683662805f2e7b9e95af2", size = 17903547, upload-time = "2026-01-10T01:39:31.015Z" }, - { url = "https://files.pythonhosted.org/packages/64/5e/1bfa100a9cb3f2d3d5f2f05f52f7e60323b0e20bb0abace1ae64dbc88f25/onnx-1.20.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ddc0b7d8b5a94627dc86c533d5e415af94cbfd103019a582669dad1f56d30281", size = 17412021, upload-time = "2026-01-10T01:39:33.885Z" }, - { url = "https://files.pythonhosted.org/packages/fb/71/d3fec0dcf9a7a99e7368112d9c765154e81da70fcba1e3121131a45c245b/onnx-1.20.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9336b6b8e6efcf5c490a845f6afd7e041c89a56199aeda384ed7d58fb953b080", size = 17510450, upload-time = "2026-01-10T01:39:36.589Z" }, - { url = "https://files.pythonhosted.org/packages/74/a7/edce1403e05a46e59b502fae8e3350ceeac5841f8e8f1561e98562ed9b09/onnx-1.20.1-cp312-abi3-win32.whl", hash = "sha256:564c35a94811979808ab5800d9eb4f3f32c12daedba7e33ed0845f7c61ef2431", size = 16238216, upload-time = "2026-01-10T01:39:39.46Z" }, - { url = "https://files.pythonhosted.org/packages/8b/c7/8690c81200ae652ac550c1df52f89d7795e6cc941f3cb38c9ef821419e80/onnx-1.20.1-cp312-abi3-win_amd64.whl", hash = "sha256:9fe7f9a633979d50984b94bda8ceb7807403f59a341d09d19342dc544d0ca1d5", size = 16389207, upload-time = "2026-01-10T01:39:41.955Z" }, - { url = "https://files.pythonhosted.org/packages/01/a0/4fb0e6d36eaf079af366b2c1f68bafe92df6db963e2295da84388af64abc/onnx-1.20.1-cp312-abi3-win_arm64.whl", hash = "sha256:21d747348b1c8207406fa2f3e12b82f53e0d5bb3958bcd0288bd27d3cb6ebb00", size = 16344155, upload-time = "2026-01-10T01:39:45.536Z" }, -] - [[package]] name = "opencv-python-headless" version = "4.13.0.92" @@ -833,7 +797,6 @@ dependencies = [ { name = "libusb1" }, { name = "ncurses" }, { name = "numpy" }, - { name = "onnx" }, { name = "openssl3" }, { name = "psutil" }, { name = "pycapnp" }, @@ -918,7 +881,6 @@ requires-dist = [ { name = "mkdocs", marker = "extra == 'docs'" }, { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, { name = "numpy", specifier = ">=2.0" }, - { name = "onnx", specifier = ">=1.14.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, { name = "openssl3", git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, @@ -1091,21 +1053,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5b/5a/bc7b4a4ef808fa59a816c17b20c4bef6884daebbdf627ff2a161da67da19/propcache-0.4.1-py3-none-any.whl", hash = "sha256:af2a6052aeb6cf17d3e46ee169099044fd8224cbaf75c76a2ef596e8163e2237", size = 13305, upload-time = "2025-10-08T19:49:00.792Z" }, ] -[[package]] -name = "protobuf" -version = "6.33.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/25/7c72c307aafc96fa87062aa6291d9f7c94836e43214d43722e86037aac02/protobuf-6.33.5.tar.gz", hash = "sha256:6ddcac2a081f8b7b9642c09406bc6a4290128fce5f471cddd165960bb9119e5c", size = 444465, upload-time = "2026-01-29T21:51:33.494Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b1/79/af92d0a8369732b027e6d6084251dd8e782c685c72da161bd4a2e00fbabb/protobuf-6.33.5-cp310-abi3-win32.whl", hash = "sha256:d71b040839446bac0f4d162e758bea99c8251161dae9d0983a3b88dee345153b", size = 425769, upload-time = "2026-01-29T21:51:21.751Z" }, - { url = "https://files.pythonhosted.org/packages/55/75/bb9bc917d10e9ee13dee8607eb9ab963b7cf8be607c46e7862c748aa2af7/protobuf-6.33.5-cp310-abi3-win_amd64.whl", hash = "sha256:3093804752167bcab3998bec9f1048baae6e29505adaf1afd14a37bddede533c", size = 437118, upload-time = "2026-01-29T21:51:24.022Z" }, - { url = "https://files.pythonhosted.org/packages/a2/6b/e48dfc1191bc5b52950246275bf4089773e91cb5ba3592621723cdddca62/protobuf-6.33.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:a5cb85982d95d906df1e2210e58f8e4f1e3cdc088e52c921a041f9c9a0386de5", size = 427766, upload-time = "2026-01-29T21:51:25.413Z" }, - { url = "https://files.pythonhosted.org/packages/4e/b1/c79468184310de09d75095ed1314b839eb2f72df71097db9d1404a1b2717/protobuf-6.33.5-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:9b71e0281f36f179d00cbcb119cb19dec4d14a81393e5ea220f64b286173e190", size = 324638, upload-time = "2026-01-29T21:51:26.423Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f5/65d838092fd01c44d16037953fd4c2cc851e783de9b8f02b27ec4ffd906f/protobuf-6.33.5-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8afa18e1d6d20af15b417e728e9f60f3aa108ee76f23c3b2c07a2c3b546d3afd", size = 339411, upload-time = "2026-01-29T21:51:27.446Z" }, - { url = "https://files.pythonhosted.org/packages/9b/53/a9443aa3ca9ba8724fdfa02dd1887c1bcd8e89556b715cfbacca6b63dbec/protobuf-6.33.5-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:cbf16ba3350fb7b889fca858fb215967792dc125b35c7976ca4818bee3521cf0", size = 323465, upload-time = "2026-01-29T21:51:28.925Z" }, - { url = "https://files.pythonhosted.org/packages/57/bf/2086963c69bdac3d7cff1cc7ff79b8ce5ea0bec6797a017e1be338a46248/protobuf-6.33.5-py3-none-any.whl", hash = "sha256:69915a973dd0f60f31a08b8318b73eab2bd6a392c79184b3612226b0a3f8ec02", size = 170687, upload-time = "2026-01-29T21:51:32.557Z" }, -] - [[package]] name = "psutil" version = "7.2.2" From 0b6da2077f70540e952ac98cf01a04c42e830021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 24 Feb 2026 15:41:00 -0800 Subject: [PATCH 353/685] parse planplus (#37386) --- selfdrive/modeld/parse_model_outputs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 038f51ca9cf..5c11e8ca185 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -113,6 +113,8 @@ def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndar plan_mhp = self.is_mhp(outs, 'plan', ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH) plan_in_N, plan_out_N = (ModelConstants.PLAN_MHP_N, ModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0) self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)) + if 'planplus' in outs: + self.parse_mdn('planplus', outs, in_N=0, out_N=0, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)) self.parse_categorical_crossentropy('desire_state', outs, out_shape=(ModelConstants.DESIRE_PRED_WIDTH,)) return outs From 8810948eca11f39d2497429f82ffdd9dc2b15e0c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 24 Feb 2026 18:49:59 -0800 Subject: [PATCH 354/685] CI: ensure no brew (#37387) --- .github/workflows/setup/action.yaml | 4 ---- .github/workflows/tests.yaml | 4 ++++ tools/cabana/SConscript | 7 +++++-- tools/op.sh | 2 ++ 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml index 958f60d2ead..f3a1a395098 100644 --- a/.github/workflows/setup/action.yaml +++ b/.github/workflows/setup/action.yaml @@ -26,10 +26,6 @@ runs: exit 1 fi - # do this after checkout to ensure our custom LFS config is used to pull from GitLab - - shell: bash - run: git lfs pull - # build cache - id: date shell: bash diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ff4fc390940..fa327ea74bf 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -66,6 +66,10 @@ jobs: - uses: actions/checkout@v6 with: submodules: true + - name: Remove Homebrew from environment + run: | + FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':') + echo "PATH=${FILTERED}/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" >> $GITHUB_ENV - uses: ./.github/workflows/setup-with-retry - name: Building openpilot run: scons diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 90de2126557..137e77d85b1 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -6,8 +6,11 @@ Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') # Detect Qt - skip build if not available if arch == "Darwin": - brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() - has_qt = os.path.isdir(os.path.join(brew_prefix, "opt/qt@5")) + try: + brew_prefix = subprocess.check_output(['brew', '--prefix'], encoding='utf8').strip() + has_qt = os.path.isdir(os.path.join(brew_prefix, "opt/qt@5")) + except (FileNotFoundError, subprocess.CalledProcessError): + has_qt = False else: has_qt = shutil.which('qmake') is not None diff --git a/tools/op.sh b/tools/op.sh index 966d4ba4336..f5c5b6082a4 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -225,6 +225,8 @@ function op_setup() { et="$(date +%s)" echo -e " ↳ [${GREEN}✔${NC}] Dependencies installed successfully in $((et - st)) seconds." + op_activate_venv + echo "Getting git submodules..." st="$(date +%s)" if ! git submodule update --jobs 4 --init --recursive; then From ed34c4cfd63561dbc856daae281431e917f7cc06 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 20:42:50 -0800 Subject: [PATCH 355/685] NavWidget: reset some state on show --- system/ui/widgets/nav_widget.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index c1b1df1292d..2944f47a767 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -225,3 +225,8 @@ def show_event(self): # so we need this hacky bool for now self._trigger_animate_in = True self._nav_bar.show_event() + + # Reset state + self._pos_filter.update_alpha(0.1) + self._back_button_start_pos = None + self._swiping_away = False From 6442752486e49d67373a52631d8e4b01ca6fa89d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 22:29:25 -0800 Subject: [PATCH 356/685] Scroller: reset state on show (#37391) * one time test * fix! * cleanm up * cleanm up --- system/ui/widgets/scroller.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index b01b34be14a..b2aeb557446 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -393,14 +393,12 @@ def _render(self, _): def show_event(self): super().show_event() - if self._reset_scroll_at_show: - self.scroll_panel.set_offset(0.0) - for item in self._items: item.show_event() - def hide_event(self): - super().hide_event() + if self._reset_scroll_at_show: + self.scroll_panel.set_offset(0.0) + self._overlay_filter.x = 0.0 self._move_animations.clear() self._move_lift.clear() @@ -408,5 +406,8 @@ def hide_event(self): self._pending_move.clear() self._scrolling_to = None, False self._scrolling_to_filter.x = 0.0 + + def hide_event(self): + super().hide_event() for item in self._items: item.hide_event() From 571937da84805a4500737c5e26d55402868b100e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 22:42:09 -0800 Subject: [PATCH 357/685] WifiUi: sort networks on show event (#37390) * should fail * this works but i think i know a better way * something like this * hmm * this works * rm useless test * good stash * Revert "good stash" This reverts commit c2dddf0810286cb56e2418dd6f7085c2239e5109. --- .../ui/mici/layouts/settings/network/wifi_ui.py | 3 ++- system/ui/lib/wifi_manager.py | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index ec6b0c9dc5e..c69dc8b225b 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -298,7 +298,8 @@ def show_event(self): self._loading_animation.show_event() self._wifi_manager.set_active(True) self._scroller.items.clear() - self._update_buttons() + # trigger button update on latest sorted networks + self._on_network_updated(self._wifi_manager.networks) def hide_event(self): super().hide_event() diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 110fd72181b..00d7e45897e 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -154,7 +154,7 @@ class WifiState: class WifiManager: def __init__(self): - self._networks: list[Network] = [] # a network can be comprised of multiple APs + self._networks: list[Network] = [] # an unsorted list of available Networks. a Network can be comprised of multiple APs self._active = True # used to not run when not in settings self._exit = False @@ -264,7 +264,8 @@ def add_callbacks(self, need_auth: Callable[[str], None] | None = None, @property def networks(self) -> list[Network]: - return self._networks + # Sort by connected/connecting, then known, then strength, then alphabetically. This is a pure UI ordering and should not affect underlying state. + return sorted(self._networks, key=lambda n: (n.ssid != self._wifi_state.ssid, not self.is_connection_saved(n.ssid), -n.strength, n.ssid.lower())) @property def wifi_state(self) -> WifiState: @@ -826,13 +827,9 @@ def worker(): # catch all for parsing errors cloudlog.exception(f"Failed to parse AP properties for {ap_path}") - networks = [Network.from_dbus(ssid, ap_list, ssid == self._tethering_ssid) for ssid, ap_list in aps.items()] - networks.sort(key=lambda n: (n.ssid != self._wifi_state.ssid, not self.is_connection_saved(n.ssid), -n.strength, n.ssid.lower())) - self._networks = networks - + self._networks = [Network.from_dbus(ssid, ap_list, ssid == self._tethering_ssid) for ssid, ap_list in aps.items()] self._update_active_connection_info() - - self._enqueue_callbacks(self._networks_updated, self._networks) + self._enqueue_callbacks(self._networks_updated, self.networks) # sorted if block: worker() From 1792a600535de37e238fe958cd0c8f115343f23a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 24 Feb 2026 23:24:08 -0800 Subject: [PATCH 358/685] WifiManager: split out state machine (#37395) split out state machine --- system/ui/lib/wifi_manager.py | 150 +++++++++++++++++----------------- 1 file changed, 77 insertions(+), 73 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 00d7e45897e..ed8d65bae98 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -372,82 +372,86 @@ def _monitor_state(self): self._update_networks() # Device state changes - # TODO: known race conditions when switching networks (e.g. forget A, connect to B): - # 1. DEACTIVATING/DISCONNECTED + CONNECTION_REMOVED: fires before NewConnection for B - # arrives, so _set_connecting(None) clears B's CONNECTING state causing UI flicker. - # DEACTIVATING(CONNECTION_REMOVED): wifi_state (B, CONNECTING) -> (None, DISCONNECTED) - # Fix: make DEACTIVATING a no-op, and guard DISCONNECTED with - # `if wifi_state.ssid not in _connections` (NewConnection arrives between the two). - # 2. PREPARE/CONFIG ssid lookup: DBus may return stale A's conn_path, overwriting B. - # PREPARE(0): wifi_state (B, CONNECTING) -> (A, CONNECTING) - # Fix: only do DBus lookup when wifi_state.ssid is None (auto-connections); - # user-initiated connections already have ssid set via _set_connecting. while len(state_q): new_state, previous_state, change_reason = state_q.popleft().body - # TODO: Handle (FAILED, SSID_NOT_FOUND) and emit for ui to show error - # Happens when network drops off after starting connection - - if new_state == NMDeviceState.DISCONNECTED: - if change_reason != NMDeviceStateReason.NEW_ACTIVATION: - # catches CONNECTION_REMOVED reason when connection is forgotten - self._set_connecting(None) - - elif new_state in (NMDeviceState.PREPARE, NMDeviceState.CONFIG): - # Set connecting status when NetworkManager connects to known networks on its own - wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) - - conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) - if conn_path is None: - cloudlog.warning("Failed to get active wifi connection during PREPARE/CONFIG state") - else: - wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) - - self._wifi_state = wifi_state - - # BAD PASSWORD - use prev if current has already moved on to a new connection - # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT - # - weak/gone network fails with FAILED+NO_SECRETS - elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT) or - (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): - - failed_ssid = self._wifi_state.prev_ssid or self._wifi_state.ssid - if failed_ssid: - self._enqueue_callbacks(self._need_auth, failed_ssid) - self._wifi_state.prev_ssid = None - if self._wifi_state.ssid == failed_ssid: - self._set_connecting(None) - - elif new_state in (NMDeviceState.NEED_AUTH, NMDeviceState.IP_CONFIG, NMDeviceState.IP_CHECK, - NMDeviceState.SECONDARIES, NMDeviceState.FAILED): - pass - - elif new_state == NMDeviceState.ACTIVATED: - # Note that IP address from Ip4Config may not be propagated immediately and could take until the next scan results - wifi_state = replace(self._wifi_state, prev_ssid=None, status=ConnectStatus.CONNECTED) - - conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) - if conn_path is None: - cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") - self._wifi_state = wifi_state - self._enqueue_callbacks(self._activated) - self._update_networks() - else: - wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) - self._wifi_state = wifi_state - self._enqueue_callbacks(self._activated) - self._update_networks() - - # Persist volatile connections (created by AddAndActivateConnection2) to disk - conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) - save_reply = self._conn_monitor.send_and_get_reply(new_method_call(conn_addr, 'Save')) - if save_reply.header.message_type == MessageType.error: - cloudlog.warning(f"Failed to persist connection to disk: {save_reply}") - - elif new_state == NMDeviceState.DEACTIVATING: - if change_reason == NMDeviceStateReason.CONNECTION_REMOVED: - # When connection is forgotten - self._set_connecting(None) + self._handle_state_change(new_state, previous_state, change_reason) + + def _handle_state_change(self, new_state: int, _: int, change_reason: int): + # TODO: known race conditions when switching networks (e.g. forget A, connect to B): + # 1. DEACTIVATING/DISCONNECTED + CONNECTION_REMOVED: fires before NewConnection for B + # arrives, so _set_connecting(None) clears B's CONNECTING state causing UI flicker. + # DEACTIVATING(CONNECTION_REMOVED): wifi_state (B, CONNECTING) -> (None, DISCONNECTED) + # Fix: make DEACTIVATING a no-op, and guard DISCONNECTED with + # `if wifi_state.ssid not in _connections` (NewConnection arrives between the two). + # 2. PREPARE/CONFIG ssid lookup: DBus may return stale A's conn_path, overwriting B. + # PREPARE(0): wifi_state (B, CONNECTING) -> (A, CONNECTING) + # Fix: only do DBus lookup when wifi_state.ssid is None (auto-connections); + # user-initiated connections already have ssid set via _set_connecting. + + # TODO: Handle (FAILED, SSID_NOT_FOUND) and emit for ui to show error + # Happens when network drops off after starting connection + + if new_state == NMDeviceState.DISCONNECTED: + if change_reason != NMDeviceStateReason.NEW_ACTIVATION: + # catches CONNECTION_REMOVED reason when connection is forgotten + self._set_connecting(None) + + elif new_state in (NMDeviceState.PREPARE, NMDeviceState.CONFIG): + # Set connecting status when NetworkManager connects to known networks on its own + wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) + + conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + if conn_path is None: + cloudlog.warning("Failed to get active wifi connection during PREPARE/CONFIG state") + else: + wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + + self._wifi_state = wifi_state + + # BAD PASSWORD - use prev if current has already moved on to a new connection + # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT + # - weak/gone network fails with FAILED+NO_SECRETS + elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT) or + (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): + + failed_ssid = self._wifi_state.prev_ssid or self._wifi_state.ssid + if failed_ssid: + self._enqueue_callbacks(self._need_auth, failed_ssid) + self._wifi_state.prev_ssid = None + if self._wifi_state.ssid == failed_ssid: + self._set_connecting(None) + + elif new_state in (NMDeviceState.NEED_AUTH, NMDeviceState.IP_CONFIG, NMDeviceState.IP_CHECK, + NMDeviceState.SECONDARIES, NMDeviceState.FAILED): + pass + + elif new_state == NMDeviceState.ACTIVATED: + # Note that IP address from Ip4Config may not be propagated immediately and could take until the next scan results + wifi_state = replace(self._wifi_state, prev_ssid=None, status=ConnectStatus.CONNECTED) + + conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + if conn_path is None: + cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") + self._wifi_state = wifi_state + self._enqueue_callbacks(self._activated) + self._update_networks() + else: + wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + self._wifi_state = wifi_state + self._enqueue_callbacks(self._activated) + self._update_networks() + + # Persist volatile connections (created by AddAndActivateConnection2) to disk + conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) + save_reply = self._conn_monitor.send_and_get_reply(new_method_call(conn_addr, 'Save')) + if save_reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to persist connection to disk: {save_reply}") + + elif new_state == NMDeviceState.DEACTIVATING: + if change_reason == NMDeviceStateReason.CONNECTION_REMOVED: + # When connection is forgotten + self._set_connecting(None) def _network_scanner(self): while not self._exit: From d9b5a1e30b7a29c6146d6ebb85f09eb860dcc227 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 25 Feb 2026 01:59:19 -0800 Subject: [PATCH 359/685] WifiManager: add test for state machine (#37396) * test wifi state machine * clean up and another few tests * no unittest :(( * clean up * clean up * try to repro on device * try to repro on device * nice, the flicker is covered by test_user_initiated_skips_dbus_lookup! * add todo soon to be all fixed * documentaiton * test the thread races too * _fire -> fire * duplication * new state * fix some tests * format * combine similar tests * use process_callbacks * clean up * collapse two tests * rm nl * previous messy test * delete old * asked another to ask questions --- system/ui/lib/networkmanager.py | 2 + .../ui/lib/tests/test_handle_state_change.py | 678 ++++++++++++++++++ system/ui/lib/wifi_manager.py | 15 + 3 files changed, 695 insertions(+) create mode 100644 system/ui/lib/tests/test_handle_state_change.py diff --git a/system/ui/lib/networkmanager.py b/system/ui/lib/networkmanager.py index c47928d8ba4..c0e9fd289a4 100644 --- a/system/ui/lib/networkmanager.py +++ b/system/ui/lib/networkmanager.py @@ -23,9 +23,11 @@ class NMDeviceStateReason(IntEnum): # https://networkmanager.dev/docs/api/1.46/nm-dbus-types.html#NMDeviceStateReason NONE = 0 UNKNOWN = 1 + IP_CONFIG_UNAVAILABLE = 5 NO_SECRETS = 7 SUPPLICANT_DISCONNECT = 8 CONNECTION_REMOVED = 38 + USER_REQUESTED = 39 SSID_NOT_FOUND = 53 NEW_ACTIVATION = 60 diff --git a/system/ui/lib/tests/test_handle_state_change.py b/system/ui/lib/tests/test_handle_state_change.py new file mode 100644 index 00000000000..eefd83c6282 --- /dev/null +++ b/system/ui/lib/tests/test_handle_state_change.py @@ -0,0 +1,678 @@ +"""Tests for WifiManager._handle_state_change. + +Tests the state machine in isolation by constructing a WifiManager with mocked +DBus, then calling _handle_state_change directly with NM state transitions. + +Many tests assert *desired* behavior that the current code doesn't implement yet. +These are marked with pytest.mark.xfail and document the intended fix. +""" +import pytest +from pytest_mock import MockerFixture + +from openpilot.system.ui.lib.networkmanager import NMDeviceState, NMDeviceStateReason +from openpilot.system.ui.lib.wifi_manager import WifiManager, WifiState, ConnectStatus + + +def _make_wm(mocker: MockerFixture, connections=None): + """Create a WifiManager with only the fields _handle_state_change touches.""" + mocker.patch.object(WifiManager, '_initialize') + wm = WifiManager.__new__(WifiManager) + wm._exit = True # prevent stop() from doing anything in __del__ + wm._conn_monitor = mocker.MagicMock() + wm._connections = dict(connections or {}) + wm._wifi_state = WifiState() + wm._callback_queue = [] + wm._need_auth = [] + wm._activated = [] + wm._update_networks = mocker.MagicMock() + wm._get_active_wifi_connection = mocker.MagicMock(return_value=(None, None)) + return wm + + +def fire(wm: WifiManager, new_state: int, prev_state: int = NMDeviceState.UNKNOWN, + reason: int = NMDeviceStateReason.NONE) -> None: + """Feed a state change into the handler.""" + wm._handle_state_change(new_state, prev_state, reason) + + +def fire_wpa_connect(wm: WifiManager) -> None: + """WPA handshake then IP negotiation through ACTIVATED, as seen on device.""" + fire(wm, NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.PREPARE, prev_state=NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.IP_CONFIG) + fire(wm, NMDeviceState.IP_CHECK) + fire(wm, NMDeviceState.SECONDARIES) + fire(wm, NMDeviceState.ACTIVATED) + + +# --------------------------------------------------------------------------- +# Basic transitions +# --------------------------------------------------------------------------- + +class TestDisconnected: + def test_generic_disconnect_clears_state(self, mocker): + wm = _make_wm(mocker) + wm._wifi_state = WifiState(ssid="Net", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.UNKNOWN) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + wm._update_networks.assert_not_called() + + def test_new_activation_is_noop(self, mocker): + """NEW_ACTIVATION means NM is about to connect to another network — don't clear.""" + wm = _make_wm(mocker) + wm._wifi_state = WifiState(ssid="OldNet", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.NEW_ACTIVATION) + + assert wm._wifi_state.ssid == "OldNet" + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + @pytest.mark.xfail(reason="TODO: CONNECTION_REMOVED should only clear if ssid not in _connections") + def test_connection_removed_keeps_other_connecting(self, mocker): + """Forget A while connecting to B: CONNECTION_REMOVED for A must not clear B.""" + wm = _make_wm(mocker, connections={"B": "/path/B"}) + wm._set_connecting("B") + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.CONNECTION_REMOVED) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + def test_connection_removed_clears_when_forgotten(self, mocker): + """Forget A: A is no longer in _connections, so state should clear.""" + wm = _make_wm(mocker, connections={}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.CONNECTION_REMOVED) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + +class TestDeactivating: + @pytest.mark.xfail(reason="TODO: DEACTIVATING should be a no-op") + def test_deactivating_is_noop(self, mocker): + """DEACTIVATING should be a no-op — DISCONNECTED follows with correct state. + + Fix: remove the entire DEACTIVATING elif block — do nothing for any reason. + """ + wm = _make_wm(mocker) + wm._wifi_state = WifiState(ssid="Net", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DEACTIVATING, reason=NMDeviceStateReason.CONNECTION_REMOVED) + + assert wm._wifi_state.ssid == "Net" + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + +class TestPrepareConfig: + @pytest.mark.xfail(reason="TODO: should skip DBus lookup when ssid already set") + def test_user_initiated_skips_dbus_lookup(self, mocker): + """User called _set_connecting('B') — PREPARE must not overwrite via DBus. + + Reproduced on device: rapidly tap A then B. PREPARE's DBus lookup returns A's + stale conn_path, overwriting ssid to A for 1-2 frames. UI shows the "connecting" + indicator briefly jump to the wrong network row then back. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._set_connecting("B") + wm._get_active_wifi_connection.return_value = ("/path/A", {}) + + fire(wm, NMDeviceState.PREPARE) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + wm._get_active_wifi_connection.assert_not_called() + + @pytest.mark.parametrize("state", [NMDeviceState.PREPARE, NMDeviceState.CONFIG]) + def test_auto_connect_looks_up_ssid(self, mocker, state): + """Auto-connection (ssid=None): PREPARE and CONFIG must look up ssid from NM.""" + wm = _make_wm(mocker, connections={"AutoNet": "/path/auto"}) + wm._get_active_wifi_connection.return_value = ("/path/auto", {}) + + fire(wm, state) + + assert wm._wifi_state.ssid == "AutoNet" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + def test_auto_connect_dbus_fails(self, mocker): + """Auto-connection but DBus returns None: ssid stays None, status CONNECTING.""" + wm = _make_wm(mocker) + + fire(wm, NMDeviceState.PREPARE) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + def test_auto_connect_conn_path_not_in_connections(self, mocker): + """DBus returns a conn_path that doesn't match any known connection.""" + wm = _make_wm(mocker, connections={"Other": "/path/other"}) + wm._get_active_wifi_connection.return_value = ("/path/unknown", {}) + + fire(wm, NMDeviceState.PREPARE) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + +class TestNeedAuth: + def test_wrong_password_fires_callback(self, mocker): + """NEED_AUTH+SUPPLICANT_DISCONNECT from CONFIG = real wrong password.""" + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._set_connecting("SecNet") + + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.CONFIG, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert len(wm._callback_queue) == 1 + wm.process_callbacks() + cb.assert_called_once_with("SecNet") + + def test_failed_no_secrets_fires_callback(self, mocker): + """FAILED+NO_SECRETS = wrong password (weak/gone network).""" + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._set_connecting("WeakNet") + + fire(wm, NMDeviceState.FAILED, reason=NMDeviceStateReason.NO_SECRETS) + + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert len(wm._callback_queue) == 1 + wm.process_callbacks() + cb.assert_called_once_with("WeakNet") + + def test_need_auth_then_failed_no_double_fire(self, mocker): + """Real device sends NEED_AUTH(SUPPLICANT_DISCONNECT) then FAILED(NO_SECRETS) back-to-back. + + The first clears ssid, so the second must not fire a duplicate callback. + Real device sequence: NEED_AUTH(CONFIG, SUPPLICANT_DISCONNECT) → FAILED(NEED_AUTH, NO_SECRETS) + """ + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._set_connecting("BadPass") + + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.CONFIG, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + assert len(wm._callback_queue) == 1 + + fire(wm, NMDeviceState.FAILED, prev_state=NMDeviceState.NEED_AUTH, + reason=NMDeviceStateReason.NO_SECRETS) + assert len(wm._callback_queue) == 1 # no duplicate + + wm.process_callbacks() + cb.assert_called_once_with("BadPass") + + def test_no_ssid_no_callback(self, mocker): + """If ssid is None when NEED_AUTH fires, no callback enqueued.""" + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + + fire(wm, NMDeviceState.NEED_AUTH, reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + + assert len(wm._callback_queue) == 0 + + @pytest.mark.xfail(reason="TODO: interrupted auth (prev=DISCONNECTED) should be ignored") + def test_interrupted_auth_ignored(self, mocker): + """Switching A->B: NEED_AUTH from A (prev=DISCONNECTED) must not fire callback. + + Reproduced on device: rapidly switching between two saved networks can trigger a + rare false "wrong password" dialog for the previous network, even though both have + correct passwords. The stale NEED_AUTH has prev_state=DISCONNECTED (not CONFIG). + + Fix: the handler's second param is currently `_` (unused). Rename to `prev_state` + and only fire the NEED_AUTH callback when prev_state indicates a real auth failure + (e.g. prev_state == CONFIG), not a stale signal from a prior connection. + """ + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._set_connecting("A") + wm._set_connecting("B") + + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.DISCONNECTED, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + assert len(wm._callback_queue) == 0 + + def test_need_auth_targets_previous_ssid_via_prev_ssid(self, mocker): + """Switch A→B, late NEED_AUTH arrives: prev_ssid mechanism fires callback for A. + + This tests current prev_ssid behavior which we plan to remove. + Migration: (1) add prev_state guard to NEED_AUTH (see test_interrupted_auth_ignored), + (2) remove prev_ssid from WifiState and handler, (3) delete this test — it becomes + redundant with test_interrupted_auth_ignored once prev_state is the guard mechanism. + """ + wm = _make_wm(mocker) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._set_connecting("A") + wm._set_connecting("B") + + fire(wm, NMDeviceState.NEED_AUTH, reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + + assert len(wm._callback_queue) == 1 + wm.process_callbacks() + cb.assert_called_once_with("A") + + +class TestPassthroughStates: + """NEED_AUTH (generic), IP_CONFIG, IP_CHECK, SECONDARIES, FAILED (generic) are no-ops.""" + + @pytest.mark.parametrize("state", [ + NMDeviceState.NEED_AUTH, + NMDeviceState.IP_CONFIG, + NMDeviceState.IP_CHECK, + NMDeviceState.SECONDARIES, + NMDeviceState.FAILED, + ]) + def test_passthrough_is_noop(self, mocker, state): + wm = _make_wm(mocker) + wm._set_connecting("Net") + + fire(wm, state, reason=NMDeviceStateReason.NONE) + + assert wm._wifi_state.ssid == "Net" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + assert len(wm._callback_queue) == 0 + + +class TestActivated: + def test_sets_connected(self, mocker): + """ACTIVATED sets status to CONNECTED and fires callback.""" + wm = _make_wm(mocker, connections={"MyNet": "/path/mynet"}) + cb = mocker.MagicMock() + wm.add_callbacks(activated=cb) + wm._set_connecting("MyNet") + wm._get_active_wifi_connection.return_value = ("/path/mynet", {}) + + fire(wm, NMDeviceState.ACTIVATED) + + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "MyNet" + assert len(wm._callback_queue) == 1 + wm.process_callbacks() + cb.assert_called_once() + + def test_conn_path_none_still_connected(self, mocker): + """ACTIVATED but DBus returns None: status CONNECTED, ssid unchanged.""" + wm = _make_wm(mocker) + wm._set_connecting("MyNet") + + fire(wm, NMDeviceState.ACTIVATED) + + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "MyNet" + + def test_activated_side_effects(self, mocker): + """ACTIVATED persists the volatile connection to disk and triggers _update_networks.""" + wm = _make_wm(mocker, connections={"Net": "/path/net"}) + wm._set_connecting("Net") + wm._get_active_wifi_connection.return_value = ("/path/net", {}) + + fire(wm, NMDeviceState.ACTIVATED) + + wm._conn_monitor.send_and_get_reply.assert_called_once() + wm._update_networks.assert_called_once() + + +# --------------------------------------------------------------------------- +# Thread races: _set_connecting on main thread vs _handle_state_change on monitor thread. +# Uses side_effect on the DBus mock to simulate _set_connecting running mid-handler. +# --------------------------------------------------------------------------- +# The deterministic fixes (skip DBus lookup when ssid already set, prev_state guard +# on NEED_AUTH) also shrink these race windows to near-zero. If races are still +# visible after, make WifiState frozen (replace() + single atomic assignment) and/or +# add a narrow lock around _wifi_state reads/writes (not around DBus calls). +# The NEED_AUTH prev_ssid mutation race is eliminated by removing prev_ssid entirely. + +class TestThreadRaces: + @pytest.mark.xfail(reason="TODO: PREPARE overwrites _set_connecting via stale DBus lookup") + def test_prepare_race_user_tap_during_dbus(self, mocker): + """User taps B while PREPARE's DBus call is in flight for auto-connect. + + Monitor thread reads wifi_state (ssid=None), starts DBus call. + Main thread: _set_connecting("B"). Monitor thread writes back stale ssid from DBus. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + + def user_taps_b_during_dbus(*args, **kwargs): + wm._set_connecting("B") + return ("/path/A", {}) + + wm._get_active_wifi_connection.side_effect = user_taps_b_during_dbus + + fire(wm, NMDeviceState.PREPARE) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + @pytest.mark.xfail(reason="TODO: ACTIVATED overwrites _set_connecting with stale CONNECTED state") + def test_activated_race_user_tap_during_dbus(self, mocker): + """User taps B right as A finishes connecting (ACTIVATED handler running). + + Monitor thread reads wifi_state (A, CONNECTING), starts DBus call. + Main thread: _set_connecting("B"). Monitor thread writes (A, CONNECTED), losing B. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._set_connecting("A") + + def user_taps_b_during_dbus(*args, **kwargs): + wm._set_connecting("B") + return ("/path/A", {}) + + wm._get_active_wifi_connection.side_effect = user_taps_b_during_dbus + + fire(wm, NMDeviceState.ACTIVATED) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + +# --------------------------------------------------------------------------- +# Full sequences (NM signal order from real devices) +# --------------------------------------------------------------------------- + +class TestFullSequences: + def test_normal_connect(self, mocker): + """User connects to saved network: full happy path. + + Real device sequence (switching from another connected network): + DEACTIVATING(ACTIVATED, NEW_ACTIVATION) → DISCONNECTED(DEACTIVATING, NEW_ACTIVATION) + PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH, NONE) → CONFIG + → IP_CONFIG → IP_CHECK → SECONDARIES → ACTIVATED + """ + wm = _make_wm(mocker, connections={"Home": "/path/home"}) + wm._get_active_wifi_connection.return_value = ("/path/home", {}) + + wm._set_connecting("Home") + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.NEED_AUTH) # WPA handshake (reason=NONE) + fire(wm, NMDeviceState.PREPARE, prev_state=NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.CONFIG) + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.IP_CONFIG) + fire(wm, NMDeviceState.IP_CHECK) + fire(wm, NMDeviceState.SECONDARIES) + fire(wm, NMDeviceState.ACTIVATED) + + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "Home" + + def test_wrong_password_then_retry(self, mocker): + """Wrong password → NEED_AUTH → FAILED → NM auto-reconnects to saved network. + + Real device sequence: + PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) ← WPA handshake + → PREPARE(NEED_AUTH, NONE) → CONFIG + → NEED_AUTH(CONFIG, SUPPLICANT_DISCONNECT) ← wrong password + → FAILED(NEED_AUTH, NO_SECRETS) ← NM gives up + → DISCONNECTED(FAILED, NONE) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE ← auto-reconnect + → CONFIG → IP_CONFIG → ... → ACTIVATED + """ + wm = _make_wm(mocker, connections={"Sec": "/path/sec"}) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + + wm._set_connecting("Sec") + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.NEED_AUTH) # WPA handshake (reason=NONE) + fire(wm, NMDeviceState.PREPARE, prev_state=NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.CONFIG) + + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.CONFIG, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert len(wm._callback_queue) == 1 + + # FAILED(NO_SECRETS) follows but ssid is already cleared — no double-fire + fire(wm, NMDeviceState.FAILED, reason=NMDeviceStateReason.NO_SECRETS) + assert len(wm._callback_queue) == 1 + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.FAILED) + + # Retry + wm._callback_queue.clear() + wm._set_connecting("Sec") + wm._get_active_wifi_connection.return_value = ("/path/sec", {}) + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + def test_switch_saved_networks(self, mocker): + """Switch from A to B (both saved): NM signal sequence from real device. + + Real device sequence: + DEACTIVATING(ACTIVATED, NEW_ACTIVATION) → DISCONNECTED(DEACTIVATING, NEW_ACTIVATION) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH, NONE) → CONFIG + → IP_CONFIG → IP_CHECK → SECONDARIES → ACTIVATED + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + wm._get_active_wifi_connection.return_value = ("/path/B", {}) + + wm._set_connecting("B") + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.NEW_ACTIVATION) + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.NEW_ACTIVATION) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "B" + + @pytest.mark.xfail(reason="TODO: interrupted auth from switching should not fire need_auth") + def test_rapid_switch_no_false_wrong_password(self, mocker): + """Switch A→B quickly: A's interrupted NEED_AUTH must NOT show wrong password. + + NOTE: The late NEED_AUTH(DISCONNECTED, SUPPLICANT_DISCONNECT) is common when rapidly + switching between networks with wrong/new passwords. Less common when switching between + saved networks with correct passwords. Not guaranteed — some switches skip it and go + straight from DISCONNECTED to PREPARE. The prev_state is consistently DISCONNECTED + for stale signals, so the prev_state guard reliably distinguishes them. + + Worst-case signal sequence this protects against: + DEACTIVATING(NEW_ACTIVATION) → DISCONNECTED(NEW_ACTIVATION) + → NEED_AUTH(DISCONNECTED, SUPPLICANT_DISCONNECT) ← A's stale auth failure + → PREPARE → CONFIG → ... → ACTIVATED ← B connects + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + wm._get_active_wifi_connection.return_value = ("/path/B", {}) + + wm._set_connecting("B") + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.NEW_ACTIVATION) + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.NEW_ACTIVATION) + fire(wm, NMDeviceState.NEED_AUTH, prev_state=NMDeviceState.DISCONNECTED, + reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + assert len(wm._callback_queue) == 0 + + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + @pytest.mark.xfail(reason="TODO: forget A while connecting to B should not clear B") + def test_forget_A_connect_B(self, mocker): + """Forget A while connecting to B: full signal sequence. + + Real device sequence: + DEACTIVATING(ACTIVATED, CONNECTION_REMOVED) → DISCONNECTED(DEACTIVATING, CONNECTION_REMOVED) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH, NONE) → CONFIG + → IP_CONFIG → IP_CHECK → SECONDARIES → ACTIVATED + + Signal order: + 1. User: _set_connecting("B"), forget("A") removes A from _connections + 2. NewConnection for B arrives → _connections["B"] = ... + 3. DEACTIVATING(CONNECTION_REMOVED) — should be no-op + 4. DISCONNECTED(CONNECTION_REMOVED) — B is in _connections, must not clear + 5. PREPARE → CONFIG → NEED_AUTH → PREPARE → CONFIG → ... → ACTIVATED + """ + wm = _make_wm(mocker, connections={"A": "/path/A"}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + + wm._set_connecting("B") + del wm._connections["A"] + wm._connections["B"] = "/path/B" + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + wm._get_active_wifi_connection.return_value = ("/path/B", {}) + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "B" + + @pytest.mark.xfail(reason="TODO: forget A while connecting to B should not clear B") + def test_forget_A_connect_B_late_new_connection(self, mocker): + """Forget A, connect B: NewConnection for B arrives AFTER DISCONNECTED. + + This is the worst-case race: B isn't in _connections when DISCONNECTED fires, + so the guard can't protect it and state clears. PREPARE must recover by doing + the DBus lookup (ssid is None at that point). + + Signal order: + 1. User: _set_connecting("B"), forget("A") removes A from _connections + 2. DEACTIVATING(CONNECTION_REMOVED) — B NOT in _connections, should be no-op + 3. DISCONNECTED(CONNECTION_REMOVED) — B STILL NOT in _connections, clears state + 4. NewConnection for B arrives late → _connections["B"] = ... + 5. PREPARE (ssid=None, so DBus lookup recovers) → CONFIG → ACTIVATED + """ + wm = _make_wm(mocker, connections={"A": "/path/A"}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + + wm._set_connecting("B") + del wm._connections["A"] + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + # B not in _connections yet, so state clears — this is the known edge case + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + # NewConnection arrives late + wm._connections["B"] = "/path/B" + wm._get_active_wifi_connection.return_value = ("/path/B", {}) + + # PREPARE recovers: ssid is None so it looks up from DBus + fire(wm, NMDeviceState.PREPARE) + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "B" + + def test_auto_connect(self, mocker): + """NM auto-connects (no user action, ssid starts None).""" + wm = _make_wm(mocker, connections={"AutoNet": "/path/auto"}) + wm._get_active_wifi_connection.return_value = ("/path/auto", {}) + + fire(wm, NMDeviceState.PREPARE) + assert wm._wifi_state.ssid == "AutoNet" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "AutoNet" + + @pytest.mark.xfail(reason="TODO: FAILED(SSID_NOT_FOUND) should emit error for UI") + def test_ssid_not_found(self, mocker): + """Network drops off after connection starts. + + NM docs: SSID_NOT_FOUND (53) = "The WiFi network could not be found" + Expected sequence: PREPARE → CONFIG → FAILED(SSID_NOT_FOUND) → DISCONNECTED + + NOTE: SSID_NOT_FOUND is rare. On-device testing with a disappearing hotspot typically + produces FAILED(NO_SECRETS) instead. May be driver-specific or require the network + to vanish from scan results mid-connection. + + The UI error callback mechanism is intentionally deferred — for now just clear state. + """ + wm = _make_wm(mocker, connections={"GoneNet": "/path/gone"}) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + + wm._set_connecting("GoneNet") + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.FAILED, reason=NMDeviceStateReason.SSID_NOT_FOUND) + + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert wm._wifi_state.ssid is None + + def test_failed_then_disconnected_clears_state(self, mocker): + """After FAILED, NM always transitions to DISCONNECTED to clean up. + + NM docs: FAILED (120) = "failed to connect, cleaning up the connection request" + Full sequence: ... → FAILED(reason) → DISCONNECTED(NONE) + """ + wm = _make_wm(mocker) + wm._set_connecting("Net") + + fire(wm, NMDeviceState.FAILED, reason=NMDeviceStateReason.NONE) + assert wm._wifi_state.status == ConnectStatus.CONNECTING # FAILED(NONE) is a no-op + + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.NONE) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + def test_user_requested_disconnect(self, mocker): + """User explicitly disconnects from the network. + + NM docs: USER_REQUESTED (39) = "Device disconnected by user or client" + Expected sequence: DEACTIVATING(USER_REQUESTED) → DISCONNECTED(USER_REQUESTED) + """ + wm = _make_wm(mocker) + wm._wifi_state = WifiState(ssid="MyNet", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DEACTIVATING, reason=NMDeviceStateReason.USER_REQUESTED) + fire(wm, NMDeviceState.DISCONNECTED, reason=NMDeviceStateReason.USER_REQUESTED) + + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index ed8d65bae98..5d6cd8a7857 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -389,6 +389,21 @@ def _handle_state_change(self, new_state: int, _: int, change_reason: int): # Fix: only do DBus lookup when wifi_state.ssid is None (auto-connections); # user-initiated connections already have ssid set via _set_connecting. + # TODO: Thread safety — _wifi_state is read/written by both the monitor thread (this + # handler) and the main thread (_set_connecting via connect/activate). The GIL makes + # individual assignments atomic, but read-then-write patterns can lose main thread writes: + # PREPARE/CONFIG: reads _wifi_state, does slow DBus call, writes back — if + # _set_connecting runs in between, the handler overwrites it with stale state. + # This is both a deterministic bug (stale DBus data) and a thread race. The + # deterministic fix (skip DBus lookup when ssid is set) also shrinks the race + # window to near-zero since the read/write happen back-to-back under the GIL. + # ACTIVATED: same read-then-write pattern with a DBus call in between. + # NEED_AUTH: mutates _wifi_state.prev_ssid in place, which can corrupt a new + # WifiState created by _set_connecting on the main thread. + # The deterministic fixes (skip DBus lookup when ssid set, prev_state guard) shrink + # the race windows significantly. If still visible, add a narrow lock around + # _wifi_state reads/writes (not around DBus calls, to avoid blocking the UI thread). + # TODO: Handle (FAILED, SSID_NOT_FOUND) and emit for ui to show error # Happens when network drops off after starting connection From 72ecc330e2083c2a6e639ff08d5404ae5e77f74f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 25 Feb 2026 02:54:15 -0800 Subject: [PATCH 360/685] WifiManager: don't emit need auth for partially connected networks (#37397) * fix a few * document * now remove unused prev_ssid * more --- .../ui/lib/tests/test_handle_state_change.py | 27 ----------------- system/ui/lib/wifi_manager.py | 29 ++++++++----------- 2 files changed, 12 insertions(+), 44 deletions(-) diff --git a/system/ui/lib/tests/test_handle_state_change.py b/system/ui/lib/tests/test_handle_state_change.py index eefd83c6282..9e7c39be55b 100644 --- a/system/ui/lib/tests/test_handle_state_change.py +++ b/system/ui/lib/tests/test_handle_state_change.py @@ -221,17 +221,12 @@ def test_no_ssid_no_callback(self, mocker): assert len(wm._callback_queue) == 0 - @pytest.mark.xfail(reason="TODO: interrupted auth (prev=DISCONNECTED) should be ignored") def test_interrupted_auth_ignored(self, mocker): """Switching A->B: NEED_AUTH from A (prev=DISCONNECTED) must not fire callback. Reproduced on device: rapidly switching between two saved networks can trigger a rare false "wrong password" dialog for the previous network, even though both have correct passwords. The stale NEED_AUTH has prev_state=DISCONNECTED (not CONFIG). - - Fix: the handler's second param is currently `_` (unused). Rename to `prev_state` - and only fire the NEED_AUTH callback when prev_state indicates a real auth failure - (e.g. prev_state == CONFIG), not a stale signal from a prior connection. """ wm = _make_wm(mocker) cb = mocker.MagicMock() @@ -246,26 +241,6 @@ def test_interrupted_auth_ignored(self, mocker): assert wm._wifi_state.status == ConnectStatus.CONNECTING assert len(wm._callback_queue) == 0 - def test_need_auth_targets_previous_ssid_via_prev_ssid(self, mocker): - """Switch A→B, late NEED_AUTH arrives: prev_ssid mechanism fires callback for A. - - This tests current prev_ssid behavior which we plan to remove. - Migration: (1) add prev_state guard to NEED_AUTH (see test_interrupted_auth_ignored), - (2) remove prev_ssid from WifiState and handler, (3) delete this test — it becomes - redundant with test_interrupted_auth_ignored once prev_state is the guard mechanism. - """ - wm = _make_wm(mocker) - cb = mocker.MagicMock() - wm.add_callbacks(need_auth=cb) - wm._set_connecting("A") - wm._set_connecting("B") - - fire(wm, NMDeviceState.NEED_AUTH, reason=NMDeviceStateReason.SUPPLICANT_DISCONNECT) - - assert len(wm._callback_queue) == 1 - wm.process_callbacks() - cb.assert_called_once_with("A") - class TestPassthroughStates: """NEED_AUTH (generic), IP_CONFIG, IP_CHECK, SECONDARIES, FAILED (generic) are no-ops.""" @@ -335,7 +310,6 @@ def test_activated_side_effects(self, mocker): # on NEED_AUTH) also shrink these race windows to near-zero. If races are still # visible after, make WifiState frozen (replace() + single atomic assignment) and/or # add a narrow lock around _wifi_state reads/writes (not around DBus calls). -# The NEED_AUTH prev_ssid mutation race is eliminated by removing prev_ssid entirely. class TestThreadRaces: @pytest.mark.xfail(reason="TODO: PREPARE overwrites _set_connecting via stale DBus lookup") @@ -482,7 +456,6 @@ def test_switch_saved_networks(self, mocker): assert wm._wifi_state.status == ConnectStatus.CONNECTED assert wm._wifi_state.ssid == "B" - @pytest.mark.xfail(reason="TODO: interrupted auth from switching should not fire need_auth") def test_rapid_switch_no_false_wrong_password(self, mocker): """Switch A→B quickly: A's interrupted NEED_AUTH must NOT show wrong password. diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 5d6cd8a7857..4b5711428b8 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -148,7 +148,6 @@ class ConnectStatus(IntEnum): @dataclass class WifiState: ssid: str | None = None - prev_ssid: str | None = None status: ConnectStatus = ConnectStatus.DISCONNECTED @@ -292,10 +291,7 @@ def tethering_password(self) -> str: return self._tethering_password def _set_connecting(self, ssid: str | None): - # Track prev ssid so late NEED_AUTH signals target the right network - self._wifi_state = WifiState(ssid=ssid, - prev_ssid=self.connecting_to_ssid if ssid is not None else None, - status=ConnectStatus.DISCONNECTED if ssid is None else ConnectStatus.CONNECTING) + self._wifi_state = WifiState(ssid=ssid, status=ConnectStatus.DISCONNECTED if ssid is None else ConnectStatus.CONNECTING) def _enqueue_callbacks(self, cbs: list[Callable], *args): for cb in cbs: @@ -377,7 +373,7 @@ def _monitor_state(self): self._handle_state_change(new_state, previous_state, change_reason) - def _handle_state_change(self, new_state: int, _: int, change_reason: int): + def _handle_state_change(self, new_state: int, prev_state: int, change_reason: int): # TODO: known race conditions when switching networks (e.g. forget A, connect to B): # 1. DEACTIVATING/DISCONNECTED + CONNECTION_REMOVED: fires before NewConnection for B # arrives, so _set_connecting(None) clears B's CONNECTING state causing UI flicker. @@ -398,8 +394,6 @@ def _handle_state_change(self, new_state: int, _: int, change_reason: int): # deterministic fix (skip DBus lookup when ssid is set) also shrinks the race # window to near-zero since the read/write happen back-to-back under the GIL. # ACTIVATED: same read-then-write pattern with a DBus call in between. - # NEED_AUTH: mutates _wifi_state.prev_ssid in place, which can corrupt a new - # WifiState created by _set_connecting on the main thread. # The deterministic fixes (skip DBus lookup when ssid set, prev_state guard) shrink # the race windows significantly. If still visible, add a narrow lock around # _wifi_state reads/writes (not around DBus calls, to avoid blocking the UI thread). @@ -424,18 +418,19 @@ def _handle_state_change(self, new_state: int, _: int, change_reason: int): self._wifi_state = wifi_state - # BAD PASSWORD - use prev if current has already moved on to a new connection + # BAD PASSWORD # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT # - weak/gone network fails with FAILED+NO_SECRETS - elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT) or + # prev_state guard: real auth failures come from CONFIG (supplicant handshake). + # Stale NEED_AUTH from a prior connection during network switching arrives with + # prev_state=DISCONNECTED and must be ignored to avoid a false wrong-password callback. + elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT + and prev_state == NMDeviceState.CONFIG) or (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): - failed_ssid = self._wifi_state.prev_ssid or self._wifi_state.ssid - if failed_ssid: - self._enqueue_callbacks(self._need_auth, failed_ssid) - self._wifi_state.prev_ssid = None - if self._wifi_state.ssid == failed_ssid: - self._set_connecting(None) + if self._wifi_state.ssid: + self._enqueue_callbacks(self._need_auth, self._wifi_state.ssid) + self._set_connecting(None) elif new_state in (NMDeviceState.NEED_AUTH, NMDeviceState.IP_CONFIG, NMDeviceState.IP_CHECK, NMDeviceState.SECONDARIES, NMDeviceState.FAILED): @@ -443,7 +438,7 @@ def _handle_state_change(self, new_state: int, _: int, change_reason: int): elif new_state == NMDeviceState.ACTIVATED: # Note that IP address from Ip4Config may not be propagated immediately and could take until the next scan results - wifi_state = replace(self._wifi_state, prev_ssid=None, status=ConnectStatus.CONNECTED) + wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTED) conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) if conn_path is None: From 238fca23348bb6f14453fb487fc210e3066b892e Mon Sep 17 00:00:00 2001 From: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Date: Wed, 25 Feb 2026 08:03:08 -0800 Subject: [PATCH 361/685] tools: fix darwin compile errors (#37399) --- tools/cabana/SConscript | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 137e77d85b1..89e69e7dd42 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -77,6 +77,9 @@ else: qt_libs = base_libs cabana_env = qt_env.Clone() +if arch == "Darwin": + cabana_env['CPPPATH'] += [f"{brew_prefix}/include"] + cabana_env['LIBPATH'] += [f"{brew_prefix}/lib"] cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) From f2c47494205c08408543260630846cd638308ee5 Mon Sep 17 00:00:00 2001 From: Alexandre Nobuharu Sato <66435071+AlexandreSato@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:40:13 -0300 Subject: [PATCH 362/685] update docs (#37293) * update docs * Update paths for brand-specific safety files --- docs/README.md | 2 +- docs/car-porting/what-is-a-car-port.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index 08dd4fa8bcc..12d0b6f5dd8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -8,7 +8,7 @@ NOTE: Those commands must be run in the root directory of openpilot, **not /docs **1. Install the docs dependencies** ``` bash -pip install .[docs] +uv pip install .[docs] ``` **2. Build the new site** diff --git a/docs/car-porting/what-is-a-car-port.md b/docs/car-porting/what-is-a-car-port.md index 55cce94da1f..3480e4e5d5c 100644 --- a/docs/car-porting/what-is-a-car-port.md +++ b/docs/car-porting/what-is-a-car-port.md @@ -21,10 +21,10 @@ Each car brand is supported by a standard interface structure in `opendbc/car/[b * `values.py`: Limits for actuation, general constants for cars, and supported car documentation * `radar_interface.py`: Interface for parsing radar points from the car, if applicable -## panda +## safety -* `board/safety/safety_[brand].h`: Brand-specific safety logic -* `tests/safety/test_[brand].py`: Brand-specific safety CI tests +* `opendbc_repo/opendbc/safety/modes/[brand].h`: Brand-specific safety logic +* `opendbc_repo/opendbc/safety/tests/test_[brand].py`: Brand-specific safety CI tests ## openpilot From 7835b9aa17d6cc0fa7ef694bbec54c6f57a632d1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 25 Feb 2026 15:24:33 -0800 Subject: [PATCH 363/685] WifiManager: no need to update networks in as many places v2 (#37405) * debug * todo * clean up * clean up * fix test --- system/ui/lib/tests/test_handle_state_change.py | 6 ++++-- system/ui/lib/wifi_manager.py | 6 +++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/system/ui/lib/tests/test_handle_state_change.py b/system/ui/lib/tests/test_handle_state_change.py index 9e7c39be55b..a714eab88b9 100644 --- a/system/ui/lib/tests/test_handle_state_change.py +++ b/system/ui/lib/tests/test_handle_state_change.py @@ -25,6 +25,7 @@ def _make_wm(mocker: MockerFixture, connections=None): wm._need_auth = [] wm._activated = [] wm._update_networks = mocker.MagicMock() + wm._update_active_connection_info = mocker.MagicMock() wm._get_active_wifi_connection = mocker.MagicMock(return_value=(None, None)) return wm @@ -291,7 +292,7 @@ def test_conn_path_none_still_connected(self, mocker): assert wm._wifi_state.ssid == "MyNet" def test_activated_side_effects(self, mocker): - """ACTIVATED persists the volatile connection to disk and triggers _update_networks.""" + """ACTIVATED persists the volatile connection to disk and updates active connection info.""" wm = _make_wm(mocker, connections={"Net": "/path/net"}) wm._set_connecting("Net") wm._get_active_wifi_connection.return_value = ("/path/net", {}) @@ -299,7 +300,8 @@ def test_activated_side_effects(self, mocker): fire(wm, NMDeviceState.ACTIVATED) wm._conn_monitor.send_and_get_reply.assert_called_once() - wm._update_networks.assert_called_once() + wm._update_active_connection_info.assert_called_once() + wm._update_networks.assert_not_called() # --------------------------------------------------------------------------- diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 4b5711428b8..4433114b192 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -424,6 +424,7 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i # prev_state guard: real auth failures come from CONFIG (supplicant handshake). # Stale NEED_AUTH from a prior connection during network switching arrives with # prev_state=DISCONNECTED and must be ignored to avoid a false wrong-password callback. + # TODO: sometimes on PC it's observed no future signals are fired if mouse is held down blocking wrong password dialog elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT and prev_state == NMDeviceState.CONFIG) or (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): @@ -445,12 +446,12 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") self._wifi_state = wifi_state self._enqueue_callbacks(self._activated) - self._update_networks() + self._update_active_connection_info() else: wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) self._wifi_state = wifi_state self._enqueue_callbacks(self._activated) - self._update_networks() + self._update_active_connection_info() # Persist volatile connections (created by AddAndActivateConnection2) to disk conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) @@ -652,7 +653,6 @@ def worker(): conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) self._router_main.send_and_get_reply(new_method_call(conn_addr, 'Delete')) - self._update_networks() self._enqueue_callbacks(self._forgotten, ssid) if block: From bcb4a6a3e36d2c36cb9164cec6fe50732f7961e2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 25 Feb 2026 17:25:31 -0800 Subject: [PATCH 364/685] WifiManager: fix deterministic state mismatches (#37407) * hmm * revert to master * context too big * fresh context * early return * early return * tests * restore cmts * lester nester * note * add * final review * cmt --- .../ui/lib/tests/test_handle_state_change.py | 22 ++---- system/ui/lib/wifi_manager.py | 68 +++++++++---------- 2 files changed, 38 insertions(+), 52 deletions(-) diff --git a/system/ui/lib/tests/test_handle_state_change.py b/system/ui/lib/tests/test_handle_state_change.py index a714eab88b9..66e0ff26ea5 100644 --- a/system/ui/lib/tests/test_handle_state_change.py +++ b/system/ui/lib/tests/test_handle_state_change.py @@ -3,8 +3,8 @@ Tests the state machine in isolation by constructing a WifiManager with mocked DBus, then calling _handle_state_change directly with NM state transitions. -Many tests assert *desired* behavior that the current code doesn't implement yet. -These are marked with pytest.mark.xfail and document the intended fix. +Remaining xfail tests cover thread races (monitor vs main thread) and deferred +features (SSID_NOT_FOUND UI error). """ import pytest from pytest_mock import MockerFixture @@ -72,7 +72,6 @@ def test_new_activation_is_noop(self, mocker): assert wm._wifi_state.ssid == "OldNet" assert wm._wifi_state.status == ConnectStatus.CONNECTED - @pytest.mark.xfail(reason="TODO: CONNECTION_REMOVED should only clear if ssid not in _connections") def test_connection_removed_keeps_other_connecting(self, mocker): """Forget A while connecting to B: CONNECTION_REMOVED for A must not clear B.""" wm = _make_wm(mocker, connections={"B": "/path/B"}) @@ -95,12 +94,8 @@ def test_connection_removed_clears_when_forgotten(self, mocker): class TestDeactivating: - @pytest.mark.xfail(reason="TODO: DEACTIVATING should be a no-op") def test_deactivating_is_noop(self, mocker): - """DEACTIVATING should be a no-op — DISCONNECTED follows with correct state. - - Fix: remove the entire DEACTIVATING elif block — do nothing for any reason. - """ + """DEACTIVATING is a no-op — DISCONNECTED follows with the correct reason.""" wm = _make_wm(mocker) wm._wifi_state = WifiState(ssid="Net", status=ConnectStatus.CONNECTED) @@ -111,7 +106,6 @@ def test_deactivating_is_noop(self, mocker): class TestPrepareConfig: - @pytest.mark.xfail(reason="TODO: should skip DBus lookup when ssid already set") def test_user_initiated_skips_dbus_lookup(self, mocker): """User called _set_connecting('B') — PREPARE must not overwrite via DBus. @@ -309,9 +303,9 @@ def test_activated_side_effects(self, mocker): # Uses side_effect on the DBus mock to simulate _set_connecting running mid-handler. # --------------------------------------------------------------------------- # The deterministic fixes (skip DBus lookup when ssid already set, prev_state guard -# on NEED_AUTH) also shrink these race windows to near-zero. If races are still -# visible after, make WifiState frozen (replace() + single atomic assignment) and/or -# add a narrow lock around _wifi_state reads/writes (not around DBus calls). +# on NEED_AUTH, DEACTIVATING no-op, CONNECTION_REMOVED guard) shrink these race +# windows to near-zero. If still visible, make WifiState frozen (replace() + single +# atomic assignment) and/or add a narrow lock around _wifi_state reads/writes. class TestThreadRaces: @pytest.mark.xfail(reason="TODO: PREPARE overwrites _set_connecting via stale DBus lookup") @@ -496,7 +490,6 @@ def test_rapid_switch_no_false_wrong_password(self, mocker): fire_wpa_connect(wm) assert wm._wifi_state.status == ConnectStatus.CONNECTED - @pytest.mark.xfail(reason="TODO: forget A while connecting to B should not clear B") def test_forget_A_connect_B(self, mocker): """Forget A while connecting to B: full signal sequence. @@ -508,7 +501,7 @@ def test_forget_A_connect_B(self, mocker): Signal order: 1. User: _set_connecting("B"), forget("A") removes A from _connections 2. NewConnection for B arrives → _connections["B"] = ... - 3. DEACTIVATING(CONNECTION_REMOVED) — should be no-op + 3. DEACTIVATING(CONNECTION_REMOVED) — no-op 4. DISCONNECTED(CONNECTION_REMOVED) — B is in _connections, must not clear 5. PREPARE → CONFIG → NEED_AUTH → PREPARE → CONFIG → ... → ACTIVATED """ @@ -536,7 +529,6 @@ def test_forget_A_connect_B(self, mocker): assert wm._wifi_state.status == ConnectStatus.CONNECTED assert wm._wifi_state.ssid == "B" - @pytest.mark.xfail(reason="TODO: forget A while connecting to B should not clear B") def test_forget_A_connect_B_late_new_connection(self, mocker): """Forget A, connect B: NewConnection for B arrives AFTER DISCONNECTED. diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 4433114b192..67325fb4792 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -374,40 +374,36 @@ def _monitor_state(self): self._handle_state_change(new_state, previous_state, change_reason) def _handle_state_change(self, new_state: int, prev_state: int, change_reason: int): - # TODO: known race conditions when switching networks (e.g. forget A, connect to B): - # 1. DEACTIVATING/DISCONNECTED + CONNECTION_REMOVED: fires before NewConnection for B - # arrives, so _set_connecting(None) clears B's CONNECTING state causing UI flicker. - # DEACTIVATING(CONNECTION_REMOVED): wifi_state (B, CONNECTING) -> (None, DISCONNECTED) - # Fix: make DEACTIVATING a no-op, and guard DISCONNECTED with - # `if wifi_state.ssid not in _connections` (NewConnection arrives between the two). - # 2. PREPARE/CONFIG ssid lookup: DBus may return stale A's conn_path, overwriting B. - # PREPARE(0): wifi_state (B, CONNECTING) -> (A, CONNECTING) - # Fix: only do DBus lookup when wifi_state.ssid is None (auto-connections); - # user-initiated connections already have ssid set via _set_connecting. - # TODO: Thread safety — _wifi_state is read/written by both the monitor thread (this # handler) and the main thread (_set_connecting via connect/activate). The GIL makes - # individual assignments atomic, but read-then-write patterns can lose main thread writes: - # PREPARE/CONFIG: reads _wifi_state, does slow DBus call, writes back — if - # _set_connecting runs in between, the handler overwrites it with stale state. - # This is both a deterministic bug (stale DBus data) and a thread race. The - # deterministic fix (skip DBus lookup when ssid is set) also shrinks the race - # window to near-zero since the read/write happen back-to-back under the GIL. - # ACTIVATED: same read-then-write pattern with a DBus call in between. - # The deterministic fixes (skip DBus lookup when ssid set, prev_state guard) shrink - # the race windows significantly. If still visible, add a narrow lock around - # _wifi_state reads/writes (not around DBus calls, to avoid blocking the UI thread). - - # TODO: Handle (FAILED, SSID_NOT_FOUND) and emit for ui to show error + # individual assignments atomic, but ACTIVATED still has a read-then-write pattern with + # a DBus call in between: if _set_connecting runs mid-call, the handler overwrites it. + # The deterministic fixes (skip DBus lookup when ssid set, prev_state guard, DEACTIVATING + # no-op, CONNECTION_REMOVED guard) shrink the race windows significantly. If still + # visible, add a narrow lock around _wifi_state reads/writes (not around DBus calls). + + # TODO: Handle (FAILED, SSID_NOT_FOUND) and emit for UI to show error # Happens when network drops off after starting connection if new_state == NMDeviceState.DISCONNECTED: - if change_reason != NMDeviceStateReason.NEW_ACTIVATION: - # catches CONNECTION_REMOVED reason when connection is forgotten - self._set_connecting(None) + if change_reason == NMDeviceStateReason.NEW_ACTIVATION: + return + + # Guard: forget A while connecting to B fires CONNECTION_REMOVED. Don't clear B's state + # if B is still a known connection. If B hasn't arrived in _connections yet (late + # NewConnection), state clears here but PREPARE recovers via DBus lookup. + if (change_reason == NMDeviceStateReason.CONNECTION_REMOVED and self._wifi_state.ssid and + self._wifi_state.ssid in self._connections): + return + + self._set_connecting(None) elif new_state in (NMDeviceState.PREPARE, NMDeviceState.CONFIG): - # Set connecting status when NetworkManager connects to known networks on its own + if self._wifi_state.ssid is not None: + self._wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) + return + + # Auto-connection when NetworkManager connects to known networks on its own (ssid=None): look up ssid from NM wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) @@ -444,25 +440,23 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) if conn_path is None: cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") - self._wifi_state = wifi_state - self._enqueue_callbacks(self._activated) - self._update_active_connection_info() else: wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) - self._wifi_state = wifi_state - self._enqueue_callbacks(self._activated) - self._update_active_connection_info() - # Persist volatile connections (created by AddAndActivateConnection2) to disk + self._wifi_state = wifi_state + self._enqueue_callbacks(self._activated) + self._update_active_connection_info() + + # Persist volatile connections (created by AddAndActivateConnection2) to disk + if conn_path is not None: conn_addr = DBusAddress(conn_path, bus_name=NM, interface=NM_CONNECTION_IFACE) save_reply = self._conn_monitor.send_and_get_reply(new_method_call(conn_addr, 'Save')) if save_reply.header.message_type == MessageType.error: cloudlog.warning(f"Failed to persist connection to disk: {save_reply}") elif new_state == NMDeviceState.DEACTIVATING: - if change_reason == NMDeviceStateReason.CONNECTION_REMOVED: - # When connection is forgotten - self._set_connecting(None) + # no-op — DISCONNECTED always follows with the correct reason + pass def _network_scanner(self): while not self._exit: From 1550520b631422fd1db19fcbb1832841ea0b828b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 25 Feb 2026 18:41:28 -0800 Subject: [PATCH 365/685] WifiManager: connect/activate failure resets ssid (#37410) fix connect/activate failure resetting connected/connecting ssid --- .../ui/lib/tests/test_handle_state_change.py | 70 +++++++++++++++++++ system/ui/lib/wifi_manager.py | 12 ++-- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/system/ui/lib/tests/test_handle_state_change.py b/system/ui/lib/tests/test_handle_state_change.py index 66e0ff26ea5..a6a6240149f 100644 --- a/system/ui/lib/tests/test_handle_state_change.py +++ b/system/ui/lib/tests/test_handle_state_change.py @@ -7,6 +7,7 @@ features (SSID_NOT_FOUND UI error). """ import pytest +from jeepney.low_level import MessageType from pytest_mock import MockerFixture from openpilot.system.ui.lib.networkmanager import NMDeviceState, NMDeviceStateReason @@ -643,3 +644,72 @@ def test_user_requested_disconnect(self, mocker): assert wm._wifi_state.ssid is None assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + +# --------------------------------------------------------------------------- +# Worker error recovery: DBus errors in activate/connect re-sync with NM +# --------------------------------------------------------------------------- +# Verified on device: when ActivateConnection returns UnknownConnection error, +# NM emits no state signals. The worker error path is the only recovery point. + +class TestWorkerErrorRecovery: + """Worker threads re-sync with NM via _init_wifi_state on DBus errors, + preserving actual NM state instead of blindly clearing to DISCONNECTED.""" + + def _mock_init_restores(self, wm, mocker, ssid, status): + """Replace _init_wifi_state with a mock that simulates NM reporting the given state.""" + mock = mocker.MagicMock( + side_effect=lambda: setattr(wm, '_wifi_state', WifiState(ssid=ssid, status=status)) + ) + wm._init_wifi_state = mock + return mock + + def test_activate_dbus_error_resyncs(self, mocker): + """ActivateConnection returns DBus error while A is connected. + NM rejects the request — no state signals emitted. Worker must re-read NM + state to discover A is still connected, not clear to DISCONNECTED. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._wifi_device = "/dev/wifi0" + wm._nm = mocker.MagicMock() + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + wm._router_main = mocker.MagicMock() + + error_reply = mocker.MagicMock() + error_reply.header.message_type = MessageType.error + wm._router_main.send_and_get_reply.return_value = error_reply + + mock_init = self._mock_init_restores(wm, mocker, "A", ConnectStatus.CONNECTED) + + wm.activate_connection("B", block=True) + + mock_init.assert_called_once() + assert wm._wifi_state.ssid == "A" + assert wm._wifi_state.status == ConnectStatus.CONNECTED + + def test_connect_to_network_dbus_error_resyncs(self, mocker): + """AddAndActivateConnection2 returns DBus error while A is connected.""" + wm = _make_wm(mocker, connections={"A": "/path/A"}) + wm._wifi_device = "/dev/wifi0" + wm._nm = mocker.MagicMock() + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + wm._router_main = mocker.MagicMock() + wm._forgotten = [] + + error_reply = mocker.MagicMock() + error_reply.header.message_type = MessageType.error + wm._router_main.send_and_get_reply.return_value = error_reply + + mock_init = self._mock_init_restores(wm, mocker, "A", ConnectStatus.CONNECTED) + + # Run worker thread synchronously + workers = [] + mocker.patch('openpilot.system.ui.lib.wifi_manager.threading.Thread', + side_effect=lambda target, **kw: type('T', (), {'start': lambda self: workers.append(target)})()) + + wm.connect_to_network("B", "password123") + workers[-1]() + + mock_init.assert_called_once() + assert wm._wifi_state.ssid == "A" + assert wm._wifi_state.status == ConnectStatus.CONNECTED diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 67325fb4792..7c7f2d82800 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -626,7 +626,8 @@ def worker(): # Persisted to disk on ACTIVATED via Save() if self._wifi_device is None: cloudlog.warning("No WiFi device found") - self._set_connecting(None) + # TODO: expose a failed connection state in the UI + self._init_wifi_state() return reply = self._router_main.send_and_get_reply(new_method_call(self._nm, 'AddAndActivateConnection2', 'a{sa{sv}}ooa{sv}', @@ -634,7 +635,8 @@ def worker(): if reply.header.message_type == MessageType.error: cloudlog.warning(f"Failed to add and activate connection for {ssid}: {reply}") - self._set_connecting(None) + # TODO: expose a failed connection state in the UI + self._init_wifi_state() threading.Thread(target=worker, daemon=True).start() @@ -661,7 +663,8 @@ def worker(): conn_path = self._connections.get(ssid, None) if conn_path is None or self._wifi_device is None: cloudlog.warning(f"Failed to activate connection for {ssid}: conn_path={conn_path}, wifi_device={self._wifi_device}") - self._set_connecting(None) + # TODO: expose a failed connection state in the UI + self._init_wifi_state() return reply = self._router_main.send_and_get_reply(new_method_call(self._nm, 'ActivateConnection', 'ooo', @@ -669,7 +672,8 @@ def worker(): if reply.header.message_type == MessageType.error: cloudlog.warning(f"Failed to activate connection for {ssid}: {reply}") - self._set_connecting(None) + # TODO: expose a failed connection state in the UI + self._init_wifi_state() if block: worker() From c2a7437972fa25a5046673b54daf32b5dabdf727 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 25 Feb 2026 19:09:11 -0800 Subject: [PATCH 366/685] WifiManager: fix some threading race conditions (#37406) * interesting epoch approach * repro * determ fix * cmts * new issue * test * clean up * cmt * add back * reorg cmt * cmt * clean up * cmt --- .../ui/lib/tests/test_handle_state_change.py | 11 ++---- system/ui/lib/wifi_manager.py | 37 ++++++++++++++----- 2 files changed, 31 insertions(+), 17 deletions(-) diff --git a/system/ui/lib/tests/test_handle_state_change.py b/system/ui/lib/tests/test_handle_state_change.py index a6a6240149f..1365b84a83d 100644 --- a/system/ui/lib/tests/test_handle_state_change.py +++ b/system/ui/lib/tests/test_handle_state_change.py @@ -2,9 +2,6 @@ Tests the state machine in isolation by constructing a WifiManager with mocked DBus, then calling _handle_state_change directly with NM state transitions. - -Remaining xfail tests cover thread races (monitor vs main thread) and deferred -features (SSID_NOT_FOUND UI error). """ import pytest from jeepney.low_level import MessageType @@ -22,6 +19,7 @@ def _make_wm(mocker: MockerFixture, connections=None): wm._conn_monitor = mocker.MagicMock() wm._connections = dict(connections or {}) wm._wifi_state = WifiState() + wm._user_epoch = 0 wm._callback_queue = [] wm._need_auth = [] wm._activated = [] @@ -302,14 +300,14 @@ def test_activated_side_effects(self, mocker): # --------------------------------------------------------------------------- # Thread races: _set_connecting on main thread vs _handle_state_change on monitor thread. # Uses side_effect on the DBus mock to simulate _set_connecting running mid-handler. +# The epoch counter detects that a user action occurred during the slow DBus call +# and discards the stale update. # --------------------------------------------------------------------------- # The deterministic fixes (skip DBus lookup when ssid already set, prev_state guard # on NEED_AUTH, DEACTIVATING no-op, CONNECTION_REMOVED guard) shrink these race -# windows to near-zero. If still visible, make WifiState frozen (replace() + single -# atomic assignment) and/or add a narrow lock around _wifi_state reads/writes. +# windows significantly. The epoch counter closes the remaining gaps. class TestThreadRaces: - @pytest.mark.xfail(reason="TODO: PREPARE overwrites _set_connecting via stale DBus lookup") def test_prepare_race_user_tap_during_dbus(self, mocker): """User taps B while PREPARE's DBus call is in flight for auto-connect. @@ -329,7 +327,6 @@ def user_taps_b_during_dbus(*args, **kwargs): assert wm._wifi_state.ssid == "B" assert wm._wifi_state.status == ConnectStatus.CONNECTING - @pytest.mark.xfail(reason="TODO: ACTIVATED overwrites _set_connecting with stale CONNECTED state") def test_activated_race_user_tap_during_dbus(self, mocker): """User taps B right as A finishes connecting (ACTIVATED handler running). diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 7c7f2d82800..81c4ec0515c 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -175,6 +175,7 @@ def __init__(self): # State self._connections: dict[str, str] = {} # ssid -> connection path, updated via NM signals self._wifi_state: WifiState = WifiState() + self._user_epoch: int = 0 self._ipv4_address: str = "" self._current_network_metered: MeteredType = MeteredType.UNKNOWN self._tethering_password: str = "" @@ -291,6 +292,8 @@ def tethering_password(self) -> str: return self._tethering_password def _set_connecting(self, ssid: str | None): + # Called by user action, or sequentially from state change handler + self._user_epoch += 1 self._wifi_state = WifiState(ssid=ssid, status=ConnectStatus.DISCONNECTED if ssid is None else ConnectStatus.CONNECTING) def _enqueue_callbacks(self, cbs: list[Callable], *args): @@ -374,13 +377,16 @@ def _monitor_state(self): self._handle_state_change(new_state, previous_state, change_reason) def _handle_state_change(self, new_state: int, prev_state: int, change_reason: int): - # TODO: Thread safety — _wifi_state is read/written by both the monitor thread (this - # handler) and the main thread (_set_connecting via connect/activate). The GIL makes - # individual assignments atomic, but ACTIVATED still has a read-then-write pattern with - # a DBus call in between: if _set_connecting runs mid-call, the handler overwrites it. - # The deterministic fixes (skip DBus lookup when ssid set, prev_state guard, DEACTIVATING - # no-op, CONNECTION_REMOVED guard) shrink the race windows significantly. If still - # visible, add a narrow lock around _wifi_state reads/writes (not around DBus calls). + # Thread safety: _wifi_state is read/written by both the monitor thread (this handler) + # and the main thread (_set_connecting via connect/activate). PREPARE/CONFIG and ACTIVATED + # have a read-then-write pattern with a slow DBus call in between — if _set_connecting + # runs mid-call, the handler would overwrite the user's newer state with stale data. + # + # The _user_epoch counter solves this without locks. _set_connecting increments the epoch + # on every user action. Handlers snapshot the epoch before their DBus call and compare + # after: if it changed, a user action occurred during the call and the stale result is + # discarded. Combined with deterministic fixes (skip DBus lookup when ssid already set, + # DEACTIVATING no-op, CONNECTION_REMOVED guard), all known race windows are closed. # TODO: Handle (FAILED, SSID_NOT_FOUND) and emit for UI to show error # Happens when network drops off after starting connection @@ -399,6 +405,8 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i self._set_connecting(None) elif new_state in (NMDeviceState.PREPARE, NMDeviceState.CONFIG): + epoch = self._user_epoch + if self._wifi_state.ssid is not None: self._wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) return @@ -407,6 +415,10 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTING) conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + + if self._user_epoch != epoch: + return + if conn_path is None: cloudlog.warning("Failed to get active wifi connection during PREPARE/CONFIG state") else: @@ -417,14 +429,14 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i # BAD PASSWORD # - strong network rejects with NEED_AUTH+SUPPLICANT_DISCONNECT # - weak/gone network fails with FAILED+NO_SECRETS - # prev_state guard: real auth failures come from CONFIG (supplicant handshake). - # Stale NEED_AUTH from a prior connection during network switching arrives with - # prev_state=DISCONNECTED and must be ignored to avoid a false wrong-password callback. # TODO: sometimes on PC it's observed no future signals are fired if mouse is held down blocking wrong password dialog elif ((new_state == NMDeviceState.NEED_AUTH and change_reason == NMDeviceStateReason.SUPPLICANT_DISCONNECT and prev_state == NMDeviceState.CONFIG) or (new_state == NMDeviceState.FAILED and change_reason == NMDeviceStateReason.NO_SECRETS)): + # prev_state guard: real auth failures come from CONFIG (supplicant handshake). + # Stale NEED_AUTH from a prior connection during network switching arrives with + # prev_state=DISCONNECTED and must be ignored to avoid a false wrong-password callback. if self._wifi_state.ssid: self._enqueue_callbacks(self._need_auth, self._wifi_state.ssid) self._set_connecting(None) @@ -435,9 +447,14 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i elif new_state == NMDeviceState.ACTIVATED: # Note that IP address from Ip4Config may not be propagated immediately and could take until the next scan results + epoch = self._user_epoch wifi_state = replace(self._wifi_state, status=ConnectStatus.CONNECTED) conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + + if self._user_epoch != epoch: + return + if conn_path is None: cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") else: From 5c630b20a9ce75975170f5dc9eb65b63957d91f0 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Wed, 25 Feb 2026 19:29:55 -0800 Subject: [PATCH 367/685] panda sound output level (#37408) parse sound output level --- cereal/log.capnp | 1 + panda | 2 +- selfdrive/pandad/pandad.cc | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 119cf299999..80a604d8601 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -592,6 +592,7 @@ struct PandaState @0xa7649e2575e4591e { harnessStatus @21 :HarnessStatus; sbu1Voltage @35 :Float32; sbu2Voltage @36 :Float32; + soundOutputLevel @37 :UInt16; # can health canState0 @29 :PandaCanState; diff --git a/panda b/panda index 49f72e931f0..3ffe9591a73 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 49f72e931f09ceecb00c0c7937808fcaeecd3c17 +Subproject commit 3ffe9591a7305c71f67a70355f8098c9b5d2a611 diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index d048dbd0c3d..28d459f458a 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -130,6 +130,7 @@ void fill_panda_state(cereal::PandaState::Builder &ps, cereal::PandaState::Panda ps.setSpiErrorCount(health.spi_error_count_pkt); ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f); ps.setSbu2Voltage(health.sbu2_voltage_mV / 1000.0f); + ps.setSoundOutputLevel(health.sound_output_level_pkt); } void fill_panda_can_state(cereal::PandaState::PandaCanState::Builder &cs, const can_health_t &can_health) { From 496ae85f6763757b0f122400da408c45a99ab3b3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 25 Feb 2026 19:30:02 -0800 Subject: [PATCH 368/685] WifiManager: guard init_wifi_state (#37413) * failing test * fix * rename * better --- .../ui/lib/tests/test_handle_state_change.py | 27 +++++++++++++++++++ system/ui/lib/wifi_manager.py | 16 ++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/system/ui/lib/tests/test_handle_state_change.py b/system/ui/lib/tests/test_handle_state_change.py index 1365b84a83d..30d83854289 100644 --- a/system/ui/lib/tests/test_handle_state_change.py +++ b/system/ui/lib/tests/test_handle_state_change.py @@ -347,6 +347,33 @@ def user_taps_b_during_dbus(*args, **kwargs): assert wm._wifi_state.ssid == "B" assert wm._wifi_state.status == ConnectStatus.CONNECTING + def test_init_wifi_state_race_user_tap_during_dbus(self, mocker): + """User taps B while _init_wifi_state's DBus calls are in flight. + + _init_wifi_state runs from set_active(True) or worker error paths. It does + 2 DBus calls (device State property + _get_active_wifi_connection) then + unconditionally writes _wifi_state. If the user taps a network during those + calls, _set_connecting("B") is overwritten with stale NM ground truth. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "B": "/path/B"}) + wm._wifi_device = "/dev/wifi0" + wm._router_main = mocker.MagicMock() + + state_reply = mocker.MagicMock() + state_reply.body = [('u', NMDeviceState.ACTIVATED)] + wm._router_main.send_and_get_reply.return_value = state_reply + + def user_taps_b_during_dbus(*args, **kwargs): + wm._set_connecting("B") + return ("/path/A", {}) + + wm._get_active_wifi_connection.side_effect = user_taps_b_during_dbus + + wm._init_wifi_state() + + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + # --------------------------------------------------------------------------- # Full sequences (NM signal order from real devices) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 81c4ec0515c..6e39fdf5231 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -197,7 +197,7 @@ def __init__(self): self._networks_updated: list[Callable[[list[Network]], None]] = [] self._disconnected: list[Callable[[], None]] = [] - self._lock = threading.Lock() + self._scan_lock = threading.Lock() self._scan_thread = threading.Thread(target=self._network_scanner, daemon=True) self._state_thread = threading.Thread(target=self._monitor_state, daemon=True) self._initialize() @@ -227,6 +227,8 @@ def worker(): cloudlog.warning("No WiFi device found") return + epoch = self._user_epoch + dev_addr = DBusAddress(self._wifi_device, bus_name=NM, interface=NM_DEVICE_IFACE) dev_state = self._router_main.send_and_get_reply(Properties(dev_addr).get('State')).body[0][1] @@ -239,6 +241,10 @@ def worker(): conn_path, _ = self._get_active_wifi_connection() if conn_path: wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + + if self._user_epoch != epoch: + return + self._wifi_state = wifi_state if block: @@ -281,11 +287,13 @@ def current_network_metered(self) -> MeteredType: @property def connecting_to_ssid(self) -> str | None: - return self._wifi_state.ssid if self._wifi_state.status == ConnectStatus.CONNECTING else None + wifi_state = self._wifi_state + return wifi_state.ssid if wifi_state.status == ConnectStatus.CONNECTING else None @property def connected_ssid(self) -> str | None: - return self._wifi_state.ssid if self._wifi_state.status == ConnectStatus.CONNECTED else None + wifi_state = self._wifi_state + return wifi_state.ssid if wifi_state.status == ConnectStatus.CONNECTED else None @property def tethering_password(self) -> str: @@ -822,7 +830,7 @@ def _update_networks(self, block: bool = True): return def worker(): - with self._lock: + with self._scan_lock: if self._wifi_device is None: cloudlog.warning("No WiFi device found") return From 561c490b2ad04727fce7774746b28061b4660ac5 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Wed, 25 Feb 2026 20:32:44 -0800 Subject: [PATCH 369/685] Replay: keep ref history (#37357) keep history --- .github/workflows/tests.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index fa327ea74bf..f95fe7d2e22 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -162,15 +162,15 @@ jobs: if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' working-directory: ${{ github.workspace }}/ci-artifacts run: | - git checkout --orphan process-replay - git rm -rf . git config user.name "GitHub Actions Bot" git config user.email "<>" + git fetch origin process-replay || true + git checkout process-replay 2>/dev/null || git checkout --orphan process-replay cp ${{ github.workspace }}/selfdrive/test/process_replay/fakedata/*.zst . echo "${{ github.sha }}" > ref_commit git add . - git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" - git push origin process-replay --force + git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit" + git push origin process-replay - name: Run regen if: false timeout-minutes: 4 From cf5ae3cbca1b3cda30832cd681aa59e9f7cd659e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 01:10:35 -0800 Subject: [PATCH 370/685] WifiManager: fix connect flash while forgetting (#37416) * real traces for some tests combine and new test for low strength/turn off hotspot while connecting revert wifiui * stupid llm * clean up --- system/ui/lib/networkmanager.py | 1 + .../ui/lib/tests/test_handle_state_change.py | 197 ++++++++++++++++-- system/ui/lib/wifi_manager.py | 11 +- 3 files changed, 191 insertions(+), 18 deletions(-) diff --git a/system/ui/lib/networkmanager.py b/system/ui/lib/networkmanager.py index c0e9fd289a4..d2d6b30b107 100644 --- a/system/ui/lib/networkmanager.py +++ b/system/ui/lib/networkmanager.py @@ -26,6 +26,7 @@ class NMDeviceStateReason(IntEnum): IP_CONFIG_UNAVAILABLE = 5 NO_SECRETS = 7 SUPPLICANT_DISCONNECT = 8 + SUPPLICANT_TIMEOUT = 11 CONNECTION_REMOVED = 38 USER_REQUESTED = 39 SSID_NOT_FOUND = 53 diff --git a/system/ui/lib/tests/test_handle_state_change.py b/system/ui/lib/tests/test_handle_state_change.py index 30d83854289..69aae6fdf31 100644 --- a/system/ui/lib/tests/test_handle_state_change.py +++ b/system/ui/lib/tests/test_handle_state_change.py @@ -93,16 +93,42 @@ def test_connection_removed_clears_when_forgotten(self, mocker): class TestDeactivating: - def test_deactivating_is_noop(self, mocker): - """DEACTIVATING is a no-op — DISCONNECTED follows with the correct reason.""" + def test_deactivating_noop_for_non_connection_removed(self, mocker): + """DEACTIVATING with non-CONNECTION_REMOVED reason is a no-op.""" wm = _make_wm(mocker) wm._wifi_state = WifiState(ssid="Net", status=ConnectStatus.CONNECTED) - fire(wm, NMDeviceState.DEACTIVATING, reason=NMDeviceStateReason.CONNECTION_REMOVED) + fire(wm, NMDeviceState.DEACTIVATING, reason=NMDeviceStateReason.USER_REQUESTED) assert wm._wifi_state.ssid == "Net" assert wm._wifi_state.status == ConnectStatus.CONNECTED + @pytest.mark.parametrize("status, expected_clears", [ + (ConnectStatus.CONNECTED, True), + (ConnectStatus.CONNECTING, False), + ]) + def test_deactivating_connection_removed(self, mocker, status, expected_clears): + """DEACTIVATING(CONNECTION_REMOVED) clears CONNECTED but preserves CONNECTING. + + CONNECTED: forgetting the current network. The forgotten callback fires between + DEACTIVATING and DISCONNECTED — must clear here so the UI doesn't flash "connected" + after the eager _network_forgetting flag resets. + + CONNECTING: forget A while connecting to B. DEACTIVATING fires for A's removal, + but B's CONNECTING state must be preserved. + """ + wm = _make_wm(mocker, connections={"B": "/path/B"}) + wm._wifi_state = WifiState(ssid="B" if status == ConnectStatus.CONNECTING else "A", status=status) + + fire(wm, NMDeviceState.DEACTIVATING, reason=NMDeviceStateReason.CONNECTION_REMOVED) + + if expected_clears: + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + else: + assert wm._wifi_state.ssid == "B" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + class TestPrepareConfig: def test_user_initiated_skips_dbus_lookup(self, mocker): @@ -170,7 +196,17 @@ def test_wrong_password_fires_callback(self, mocker): cb.assert_called_once_with("SecNet") def test_failed_no_secrets_fires_callback(self, mocker): - """FAILED+NO_SECRETS = wrong password (weak/gone network).""" + """FAILED+NO_SECRETS = wrong password (weak/gone network). + + Confirmed on device: also fires when a hotspot turns off during connection. + NM can't complete the WPA handshake (AP vanished) and reports NO_SECRETS + rather than SSID_NOT_FOUND. The need_auth callback fires, so the UI shows + "wrong password" — a false positive, but same signal path. + + Real device sequence (new connection, hotspot turned off immediately): + PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → NEED_AUTH(CONFIG, NONE) → FAILED(NEED_AUTH, NO_SECRETS) → DISCONNECTED(FAILED, NONE) + """ wm = _make_wm(mocker) cb = mocker.MagicMock() wm.add_callbacks(need_auth=cb) @@ -304,8 +340,9 @@ def test_activated_side_effects(self, mocker): # and discards the stale update. # --------------------------------------------------------------------------- # The deterministic fixes (skip DBus lookup when ssid already set, prev_state guard -# on NEED_AUTH, DEACTIVATING no-op, CONNECTION_REMOVED guard) shrink these race -# windows significantly. The epoch counter closes the remaining gaps. +# on NEED_AUTH, DEACTIVATING clears CONNECTED on CONNECTION_REMOVED, CONNECTION_REMOVED +# guard) shrink these race windows significantly. The epoch counter closes the +# remaining gaps. class TestThreadRaces: def test_prepare_race_user_tap_during_dbus(self, mocker): @@ -410,14 +447,17 @@ def test_normal_connect(self, mocker): def test_wrong_password_then_retry(self, mocker): """Wrong password → NEED_AUTH → FAILED → NM auto-reconnects to saved network. - Real device sequence: - PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) ← WPA handshake + Confirmed on device: wrong password for Shane's iPhone, NM auto-connected to unifi. + + Real device sequence (switching from a connected network): + DEACTIVATING(ACTIVATED, NEW_ACTIVATION) → DISCONNECTED(DEACTIVATING, NEW_ACTIVATION) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) ← WPA handshake → PREPARE(NEED_AUTH, NONE) → CONFIG → NEED_AUTH(CONFIG, SUPPLICANT_DISCONNECT) ← wrong password → FAILED(NEED_AUTH, NO_SECRETS) ← NM gives up → DISCONNECTED(FAILED, NONE) - → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE ← auto-reconnect - → CONFIG → IP_CONFIG → ... → ACTIVATED + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → IP_CONFIG → IP_CHECK → SECONDARIES → ACTIVATED ← auto-reconnect to other saved network """ wm = _make_wm(mocker, connections={"Sec": "/path/sec"}) cb = mocker.MagicMock() @@ -515,6 +555,80 @@ def test_rapid_switch_no_false_wrong_password(self, mocker): fire_wpa_connect(wm) assert wm._wifi_state.status == ConnectStatus.CONNECTED + def test_forget_while_connecting(self, mocker): + """Forget the network we're currently connecting to (not yet ACTIVATED). + + Confirmed on device: connected to unifi, tapped Shane's iPhone, then forgot + Shane's iPhone while at CONFIG. NM auto-connected to unifi afterward. + + Real device sequence (switching then forgetting mid-connection): + DEACTIVATING(ACTIVATED, NEW_ACTIVATION) → DISCONNECTED(DEACTIVATING, NEW_ACTIVATION) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → DEACTIVATING(CONFIG, CONNECTION_REMOVED) ← forget at CONFIG + → DISCONNECTED(DEACTIVATING, CONNECTION_REMOVED) + → PREPARE → CONFIG → ... → ACTIVATED ← NM auto-connects to other saved network + + Note: DEACTIVATING fires from CONFIG (not ACTIVATED). wifi_state.status is + CONNECTING, so the DEACTIVATING handler is a no-op. DISCONNECTED clears state + (ssid removed from _connections by ConnectionRemoved), then PREPARE recovers + via DBus lookup for the auto-connect. + """ + wm = _make_wm(mocker, connections={"A": "/path/A", "Other": "/path/other"}) + wm._get_active_wifi_connection.return_value = ("/path/other", {}) + + wm._set_connecting("A") + + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + assert wm._wifi_state.ssid == "A" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + # User forgets A: ConnectionRemoved processed first, then state changes + del wm._connections["A"] + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.CONFIG, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid == "A" + assert wm._wifi_state.status == ConnectStatus.CONNECTING # DEACTIVATING preserves CONNECTING + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + # NM auto-connects to another saved network + fire(wm, NMDeviceState.PREPARE) + assert wm._wifi_state.ssid == "Other" + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + fire(wm, NMDeviceState.CONFIG) + fire_wpa_connect(wm) + assert wm._wifi_state.status == ConnectStatus.CONNECTED + assert wm._wifi_state.ssid == "Other" + + def test_forget_connected_network(self, mocker): + """Forget the currently connected network (not switching to another). + + Real device sequence: + DEACTIVATING(ACTIVATED, CONNECTION_REMOVED) → DISCONNECTED(DEACTIVATING, CONNECTION_REMOVED) + + ConnectionRemoved signal may or may not have been processed before state changes. + Either way, state must clear — we're forgetting what we're connected to, not switching. + """ + wm = _make_wm(mocker, connections={"A": "/path/A"}) + wm._wifi_state = WifiState(ssid="A", status=ConnectStatus.CONNECTED) + + fire(wm, NMDeviceState.DEACTIVATING, prev_state=NMDeviceState.ACTIVATED, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + # DISCONNECTED follows — harmless since state is already cleared + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.DEACTIVATING, + reason=NMDeviceStateReason.CONNECTION_REMOVED) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + def test_forget_A_connect_B(self, mocker): """Forget A while connecting to B: full signal sequence. @@ -613,16 +727,69 @@ def test_auto_connect(self, mocker): assert wm._wifi_state.status == ConnectStatus.CONNECTED assert wm._wifi_state.ssid == "AutoNet" + def test_network_lost_during_connection(self, mocker): + """Hotspot turned off while connecting (before ACTIVATED). + + Confirmed on device: started new connection to Shane's iPhone, immediately + turned off the hotspot. NM can't complete WPA handshake and reports + FAILED(NO_SECRETS) — same signal as wrong password (false positive). + + Real device sequence: + PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → NEED_AUTH(CONFIG, NONE) → FAILED(NEED_AUTH, NO_SECRETS) → DISCONNECTED(FAILED, NONE) + + Note: no DEACTIVATING, no SUPPLICANT_DISCONNECT. The NEED_AUTH(CONFIG, NONE) is the + normal WPA handshake (not an error). NM gives up with NO_SECRETS because the AP + vanished mid-handshake. + """ + wm = _make_wm(mocker, connections={"Hotspot": "/path/hs"}) + cb = mocker.MagicMock() + wm.add_callbacks(need_auth=cb) + + wm._set_connecting("Hotspot") + fire(wm, NMDeviceState.PREPARE) + fire(wm, NMDeviceState.CONFIG) + fire(wm, NMDeviceState.NEED_AUTH) # WPA handshake (reason=NONE) + fire(wm, NMDeviceState.PREPARE, prev_state=NMDeviceState.NEED_AUTH) + fire(wm, NMDeviceState.CONFIG) + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + # Second NEED_AUTH(CONFIG, NONE) — NM retries handshake, AP vanishing + fire(wm, NMDeviceState.NEED_AUTH) + assert wm._wifi_state.status == ConnectStatus.CONNECTING + + # NM gives up — reports NO_SECRETS (same as wrong password) + fire(wm, NMDeviceState.FAILED, prev_state=NMDeviceState.NEED_AUTH, + reason=NMDeviceStateReason.NO_SECRETS) + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + assert len(wm._callback_queue) == 1 + + fire(wm, NMDeviceState.DISCONNECTED, prev_state=NMDeviceState.FAILED) + assert wm._wifi_state.ssid is None + assert wm._wifi_state.status == ConnectStatus.DISCONNECTED + + wm.process_callbacks() + cb.assert_called_once_with("Hotspot") + @pytest.mark.xfail(reason="TODO: FAILED(SSID_NOT_FOUND) should emit error for UI") def test_ssid_not_found(self, mocker): - """Network drops off after connection starts. + """Network drops off while connected — hotspot turned off. NM docs: SSID_NOT_FOUND (53) = "The WiFi network could not be found" - Expected sequence: PREPARE → CONFIG → FAILED(SSID_NOT_FOUND) → DISCONNECTED - NOTE: SSID_NOT_FOUND is rare. On-device testing with a disappearing hotspot typically - produces FAILED(NO_SECRETS) instead. May be driver-specific or require the network - to vanish from scan results mid-connection. + Confirmed on device: connected to Shane's iPhone, then turned off the hotspot. + No DEACTIVATING fires — NM goes straight from ACTIVATED to FAILED(SSID_NOT_FOUND). + NM retries connecting (PREPARE → CONFIG → ... → FAILED(CONFIG, SSID_NOT_FOUND)) + before finally giving up with DISCONNECTED. + + NOTE: turning off a hotspot during initial connection (before ACTIVATED) typically + produces FAILED(NO_SECRETS) instead of SSID_NOT_FOUND (see test_failed_no_secrets). + + Real device sequence (hotspot turned off while connected): + FAILED(ACTIVATED, SSID_NOT_FOUND) → DISCONNECTED(FAILED, NONE) + → PREPARE → CONFIG → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → NEED_AUTH(CONFIG, NONE) → PREPARE(NEED_AUTH) → CONFIG + → FAILED(CONFIG, SSID_NOT_FOUND) → DISCONNECTED(FAILED, NONE) The UI error callback mechanism is intentionally deferred — for now just clear state. """ diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 6e39fdf5231..1beaeb736c6 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -394,7 +394,8 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i # on every user action. Handlers snapshot the epoch before their DBus call and compare # after: if it changed, a user action occurred during the call and the stale result is # discarded. Combined with deterministic fixes (skip DBus lookup when ssid already set, - # DEACTIVATING no-op, CONNECTION_REMOVED guard), all known race windows are closed. + # DEACTIVATING clears CONNECTED on CONNECTION_REMOVED, CONNECTION_REMOVED guard), + # all known race windows are closed. # TODO: Handle (FAILED, SSID_NOT_FOUND) and emit for UI to show error # Happens when network drops off after starting connection @@ -480,8 +481,12 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i cloudlog.warning(f"Failed to persist connection to disk: {save_reply}") elif new_state == NMDeviceState.DEACTIVATING: - # no-op — DISCONNECTED always follows with the correct reason - pass + # Must clear state when forgetting the currently connected network so the UI + # doesn't flash "connected" after the eager "forgetting..." state resets + # (the forgotten callback fires between DEACTIVATING and DISCONNECTED). + # Only clear CONNECTED — CONNECTING must be preserved for forget-A-connect-B. + if change_reason == NMDeviceStateReason.CONNECTION_REMOVED and self._wifi_state.status == ConnectStatus.CONNECTED: + self._set_connecting(None) def _network_scanner(self): while not self._exit: From b2e94548b9d29ec64c5be43d9cce49ab3b7bf399 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 01:20:06 -0800 Subject: [PATCH 371/685] ui: move connected wifi buttons to front independent of scan results (#37417) * move items * clean up * wtf * debg --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index c69dc8b225b..8b8522ba196 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -1,4 +1,6 @@ import math +import time + import numpy as np import pyray as rl from collections.abc import Callable @@ -326,8 +328,6 @@ def _update_buttons(self): if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: btn.set_network_missing(True) - self._move_network_to_front(self._wifi_manager.wifi_state.ssid) - def _connect_with_password(self, ssid: str, password: str): self._wifi_manager.connect_to_network(ssid, password) self._move_network_to_front(ssid, scroll=True) @@ -382,6 +382,10 @@ def _move_network_to_front(self, ssid: str | None, scroll: bool = False): def _update_state(self): super()._update_state() + t = time.monotonic() + self._move_network_to_front(self._wifi_manager.wifi_state.ssid) + print('took', (time.monotonic() - t) * 1000, 'ms to move network to front') + # Show loading animation near end max_scroll = max(self._scroller.content_size - self._scroller.rect.width, 1) progress = -self._scroller.scroll_panel.get_offset() / max_scroll From 811363cab9ec23c82a7932398249392f7a7e7cb3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 01:21:32 -0800 Subject: [PATCH 372/685] clean up --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 8b8522ba196..e1548e6a6bc 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -382,9 +382,7 @@ def _move_network_to_front(self, ssid: str | None, scroll: bool = False): def _update_state(self): super()._update_state() - t = time.monotonic() self._move_network_to_front(self._wifi_manager.wifi_state.ssid) - print('took', (time.monotonic() - t) * 1000, 'ms to move network to front') # Show loading animation near end max_scroll = max(self._scroller.content_size - self._scroller.rect.width, 1) From 4cd5c1b3c2abbee1832cbd3a23bcd1b1a513abf4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 02:24:12 -0800 Subject: [PATCH 373/685] clean up --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index e1548e6a6bc..9604db5362b 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -1,6 +1,4 @@ import math -import time - import numpy as np import pyray as rl from collections.abc import Callable From 146c64b0f197a54d81e0efab10193594f294a36e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 02:24:59 -0800 Subject: [PATCH 374/685] mici ui: improve tethering a bit (#37418) * try this * deactivate * faiilures! * starting * try * ... * starting * fix strength * revert * debug * more * override for display network * try * nvm it fixes a few things * cmt * clean up --- .../ui/mici/layouts/settings/network/__init__.py | 6 +++++- .../ui/mici/layouts/settings/network/wifi_ui.py | 4 ++-- system/ui/lib/wifi_manager.py | 12 ++++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index bdae9245669..e5049a9ee75 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -33,7 +33,7 @@ def _update_state(self): display_network = next((n for n in self._wifi_manager.networks if n.ssid == wifi_state.ssid), None) if wifi_state.status == ConnectStatus.CONNECTING: self.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) - self.set_value("connecting...") + self.set_value("starting" if self._wifi_manager.is_tethering_active() else "connecting...") elif wifi_state.status == ConnectStatus.CONNECTED: self.set_text(normalize_ssid(wifi_state.ssid or "wi-fi")) self.set_value(self._wifi_manager.ipv4_address or "obtaining IP...") @@ -46,6 +46,10 @@ def _update_state(self): strength = WifiIcon.get_strength_icon_idx(display_network.strength) self.set_icon(self._wifi_full_txt if strength == 2 else self._wifi_medium_txt if strength == 1 else self._wifi_low_txt) self._draw_lock = display_network.security_type not in (SecurityType.OPEN, SecurityType.UNSUPPORTED) + elif self._wifi_manager.is_tethering_active(): + # takes a while to get Network + self.set_icon(self._wifi_full_txt) + self._draw_lock = True else: self.set_icon(self._wifi_slash_txt) self._draw_lock = False diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 9604db5362b..062e28d3b18 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -227,9 +227,9 @@ def _update_state(self): if self._network_forgetting: self.set_value("forgetting...") elif self._is_connecting: - self.set_value("connecting...") + self.set_value("starting..." if self._network.is_tethering else "connecting...") elif self._is_connected: - self.set_value("connected") + self.set_value("tethering" if self._network.is_tethering else "connected") elif self._network_missing: # after connecting/connected since NM will still attempt to connect/stay connected for a while self.set_value("not in range") diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 1beaeb736c6..1a997f3c70e 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -713,11 +713,19 @@ def worker(): def _deactivate_connection(self, ssid: str): for active_conn in self._get_active_connections(): conn_addr = DBusAddress(active_conn, bus_name=NM, interface=NM_ACTIVE_CONNECTION_IFACE) - specific_obj_path = self._router_main.send_and_get_reply(Properties(conn_addr).get('SpecificObject')).body[0][1] + reply = self._router_main.send_and_get_reply(Properties(conn_addr).get('SpecificObject')) + if reply.header.message_type == MessageType.error: + continue # object gone (e.g. rapid connect/disconnect) + + specific_obj_path = reply.body[0][1] if specific_obj_path != "/": ap_addr = DBusAddress(specific_obj_path, bus_name=NM, interface=NM_ACCESS_POINT_IFACE) - ap_ssid = bytes(self._router_main.send_and_get_reply(Properties(ap_addr).get('Ssid')).body[0][1]).decode("utf-8", "replace") + ap_reply = self._router_main.send_and_get_reply(Properties(ap_addr).get('Ssid')) + if ap_reply.header.message_type == MessageType.error: + continue # AP gone (e.g. mode switch) + + ap_ssid = bytes(ap_reply.body[0][1]).decode("utf-8", "replace") if ap_ssid == ssid: self._router_main.send_and_get_reply(new_method_call(self._nm, 'DeactivateConnection', 'o', (active_conn,))) From 93a96695eabf4f0f07f3853813217ce4c74f24b7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 03:46:40 -0800 Subject: [PATCH 375/685] WifiManager: frozen WifiState (#37420) froze --- system/ui/lib/wifi_manager.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 1a997f3c70e..0fcff70abdb 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -145,7 +145,7 @@ class ConnectStatus(IntEnum): CONNECTED = 2 -@dataclass +@dataclass(frozen=True) class WifiState: ssid: str | None = None status: ConnectStatus = ConnectStatus.DISCONNECTED @@ -232,20 +232,21 @@ def worker(): dev_addr = DBusAddress(self._wifi_device, bus_name=NM, interface=NM_DEVICE_IFACE) dev_state = self._router_main.send_and_get_reply(Properties(dev_addr).get('State')).body[0][1] - wifi_state = WifiState() + ssid: str | None = None + status = ConnectStatus.DISCONNECTED if NMDeviceState.PREPARE <= dev_state <= NMDeviceState.SECONDARIES and dev_state != NMDeviceState.NEED_AUTH: - wifi_state.status = ConnectStatus.CONNECTING + status = ConnectStatus.CONNECTING elif dev_state == NMDeviceState.ACTIVATED: - wifi_state.status = ConnectStatus.CONNECTED + status = ConnectStatus.CONNECTED conn_path, _ = self._get_active_wifi_connection() if conn_path: - wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + ssid = next((s for s, p in self._connections.items() if p == conn_path), None) if self._user_epoch != epoch: return - self._wifi_state = wifi_state + self._wifi_state = WifiState(ssid=ssid, status=status) if block: worker() @@ -431,7 +432,7 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i if conn_path is None: cloudlog.warning("Failed to get active wifi connection during PREPARE/CONFIG state") else: - wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + wifi_state = replace(wifi_state, ssid=next((s for s, p in self._connections.items() if p == conn_path), None)) self._wifi_state = wifi_state @@ -467,7 +468,7 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i if conn_path is None: cloudlog.warning("Failed to get active wifi connection during ACTIVATED state") else: - wifi_state.ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + wifi_state = replace(wifi_state, ssid=next((s for s, p in self._connections.items() if p == conn_path), None)) self._wifi_state = wifi_state self._enqueue_callbacks(self._activated) From 608a1c2baae6ef1df8551b3cf14cf4bd10b581c1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 03:47:40 -0800 Subject: [PATCH 376/685] Add comment about epoch guard --- system/ui/lib/wifi_manager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 0fcff70abdb..4820c7aaba7 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -243,6 +243,7 @@ def worker(): if conn_path: ssid = next((s for s, p in self._connections.items() if p == conn_path), None) + # Discard if user acted during DBus calls if self._user_epoch != epoch: return @@ -426,6 +427,7 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + # Discard if user acted during DBus call if self._user_epoch != epoch: return @@ -462,6 +464,7 @@ def _handle_state_change(self, new_state: int, prev_state: int, change_reason: i conn_path, _ = self._get_active_wifi_connection(self._conn_monitor) + # Discard if user acted during DBus call if self._user_epoch != epoch: return From 91696ba6c8298fbaf7d21969dca66599eefbf3d1 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Thu, 26 Feb 2026 15:58:52 -0800 Subject: [PATCH 377/685] fix module for model_review (#37428) * install tg instead of onnx * fix python path --------- Co-authored-by: Bruce Wayne --- .github/workflows/model_review.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/model_review.yaml b/.github/workflows/model_review.yaml index 6b8ce143dbf..2775dbc5743 100644 --- a/.github/workflows/model_review.yaml +++ b/.github/workflows/model_review.yaml @@ -17,6 +17,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + submodules: true - name: Checkout master uses: actions/checkout@v6 with: @@ -25,14 +27,12 @@ jobs: - run: git lfs pull - run: cd base && git lfs pull - - run: pip install onnx - - name: scripts/reporter.py id: report run: | echo "content<> $GITHUB_OUTPUT echo "## Model Review" >> $GITHUB_OUTPUT - MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT + PYTHONPATH=${{ github.workspace }} MASTER_PATH=${{ github.workspace }}/base python scripts/reporter.py >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT - name: Post model report comment From 94ee6b0f435bdc8a006fa5cf9b5d2a3cf2ac6b55 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 16:01:30 -0800 Subject: [PATCH 378/685] BigButton: move parameters into class (#37429) * BigButton: move parameters into class * fix --- .../ui/mici/layouts/settings/network/wifi_ui.py | 12 ++++++------ selfdrive/ui/mici/widgets/button.py | 17 +++++++++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 062e28d3b18..dda1d12220a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -6,7 +6,7 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR, LABEL_HORIZONTAL_PADDING, LABEL_VERTICAL_PADDING +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget @@ -98,7 +98,7 @@ def _render(self, _): class WifiButton(BigButton): LABEL_PADDING = 98 LABEL_WIDTH = 402 - 98 - 28 # button width - left padding - right padding - SUB_LABEL_WIDTH = 402 - LABEL_HORIZONTAL_PADDING * 2 + SUB_LABEL_WIDTH = 402 - BigButton.LABEL_HORIZONTAL_PADDING * 2 def __init__(self, network: Network, wifi_manager: WifiManager): super().__init__(normalize_ssid(network.ssid), scroll=True) @@ -166,13 +166,13 @@ def _get_label_font_size(self): def _draw_content(self, btn_y: float): self._label.set_color(LABEL_COLOR) - label_rect = rl.Rectangle(self._rect.x + self.LABEL_PADDING, btn_y + LABEL_VERTICAL_PADDING, - self.LABEL_WIDTH, self._rect.height - LABEL_VERTICAL_PADDING * 2) + label_rect = rl.Rectangle(self._rect.x + self.LABEL_PADDING, btn_y + self.LABEL_VERTICAL_PADDING, + self.LABEL_WIDTH, self._rect.height - self.LABEL_VERTICAL_PADDING * 2) self._label.render(label_rect) if self.value: - sub_label_x = self._rect.x + LABEL_HORIZONTAL_PADDING - label_y = btn_y + self._rect.height - LABEL_VERTICAL_PADDING + sub_label_x = self._rect.x + self.LABEL_HORIZONTAL_PADDING + label_y = btn_y + self._rect.height - self.LABEL_VERTICAL_PADDING sub_label_w = self.SUB_LABEL_WIDTH - (self._forget_btn.rect.width if self._show_forget_btn else 0) sub_label_height = self._sub_label.get_content_height(sub_label_w) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 3ea650ece39..2855e2e4e16 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -17,8 +17,6 @@ SCROLLING_SPEED_PX_S = 50 COMPLICATION_SIZE = 36 LABEL_COLOR = rl.Color(255, 255, 255, int(255 * 0.9)) -LABEL_HORIZONTAL_PADDING = 40 -LABEL_VERTICAL_PADDING = 23 # visually matches 30 in figma COMPLICATION_GREY = rl.Color(0xAA, 0xAA, 0xAA, 255) PRESSED_SCALE = 1.15 if DO_ZOOM else 1.07 @@ -103,6 +101,9 @@ def _draw_content(self, btn_y: float): class BigButton(Widget): + LABEL_HORIZONTAL_PADDING = 40 + LABEL_VERTICAL_PADDING = 23 # visually matches 30 in figma + """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64), @@ -145,7 +146,7 @@ def _load_images(self): def _width_hint(self) -> int: # Single line if scrolling, so hide behind icon if exists icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0 - return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - icon_size) + return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): if len(self.text) <= 18: @@ -195,16 +196,16 @@ def set_position(self, x: float, y: float) -> None: def _draw_content(self, btn_y: float): # LABEL ------------------------------------------------------------------ - label_x = self._rect.x + LABEL_HORIZONTAL_PADDING + label_x = self._rect.x + self.LABEL_HORIZONTAL_PADDING label_color = LABEL_COLOR if self.enabled else rl.Color(255, 255, 255, int(255 * 0.35)) self._label.set_color(label_color) - label_rect = rl.Rectangle(label_x, btn_y + LABEL_VERTICAL_PADDING, self._width_hint(), - self._rect.height - LABEL_VERTICAL_PADDING * 2) + label_rect = rl.Rectangle(label_x, btn_y + self.LABEL_VERTICAL_PADDING, self._width_hint(), + self._rect.height - self.LABEL_VERTICAL_PADDING * 2) self._label.render(label_rect) if self.value: - label_y = btn_y + self._rect.height - LABEL_VERTICAL_PADDING + label_y = btn_y + self._rect.height - self.LABEL_VERTICAL_PADDING sub_label_height = self._sub_label.get_content_height(self._width_hint()) sub_label_rect = rl.Rectangle(label_x, label_y - sub_label_height, self._width_hint(), sub_label_height) self._sub_label.render(sub_label_rect) @@ -293,7 +294,7 @@ def __init__(self, text: str, options: list[str], toggle_callback: Callable | No self.set_value(self._options[0]) def _width_hint(self) -> int: - return int(self._rect.width - LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width) + return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - self._txt_enabled_toggle.width) def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) From 7f1def00b227e567e836ccd9457fe5252ec84c64 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 16:04:53 -0800 Subject: [PATCH 379/685] BigButton: handle background function (#37430) * move * fix --- selfdrive/ui/mici/widgets/button.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 2855e2e4e16..1faf3222d84 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -194,6 +194,19 @@ def _shake_offset(self) -> float: def set_position(self, x: float, y: float) -> None: super().set_position(x + self._shake_offset, y) + def _handle_background(self) -> tuple[rl.Texture, float, float, float]: + # draw _txt_default_bg + txt_bg = self._txt_default_bg + if not self.enabled: + txt_bg = self._txt_disabled_bg + elif self.is_pressed: + txt_bg = self._txt_pressed_bg + + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) + btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 + btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 + return txt_bg, btn_x, btn_y, scale + def _draw_content(self, btn_y: float): # LABEL ------------------------------------------------------------------ label_x = self._rect.x + self.LABEL_HORIZONTAL_PADDING @@ -225,16 +238,7 @@ def _draw_content(self, btn_y: float): rl.draw_texture_pro(self._txt_icon, source_rec, dest_rec, origin, rotation, rl.Color(255, 255, 255, int(255 * 0.9))) def _render(self, _): - # draw _txt_default_bg - txt_bg = self._txt_default_bg - if not self.enabled: - txt_bg = self._txt_disabled_bg - elif self.is_pressed: - txt_bg = self._txt_pressed_bg - - scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) - btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 - btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 + txt_bg, btn_x, btn_y, scale = self._handle_background() if self._scroll: # draw black background since images are transparent From 04dcdf46bc7c3d84f8a20f6e81cfb5b38dab8689 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Thu, 26 Feb 2026 16:10:57 -0800 Subject: [PATCH 380/685] DM: Le Mans GT3 Model (#37425) * 81248b12-6592-4a5c-9b59-a44c64123b2b * install tg instead of onnx * fix python path --------- Co-authored-by: Bruce Wayne --- selfdrive/modeld/models/dmonitoring_model.onnx | 2 +- selfdrive/monitoring/helpers.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index 24234d4c50d..dc621bed03a 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e8de9dc7df306700cce9c22b992e25b95a38f894c47adaea742a9cf8ba78e1a +oid sha256:7aff7ff1dc08bbaf562a8f77380ab5e5914f8557dba2f760d87e4d953f5604b0 size 7307246 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 91ddaaa9c13..0b54504b647 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -35,7 +35,7 @@ def __init__(self, device_type): self._EYE_THRESHOLD = 0.65 self._SG_THRESHOLD = 0.9 self._BLINK_THRESHOLD = 0.865 - self._PHONE_THRESH = 0.68 + self._PHONE_THRESH = 0.5 self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 From ac08c79139413ff703fc6fdcdeeb5fb9c7c0008e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 16:19:12 -0800 Subject: [PATCH 381/685] BigButton: sublabel takes all available space (#37431) change --- selfdrive/ui/mici/widgets/button.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 1faf3222d84..231dafa8eb7 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -218,9 +218,9 @@ def _draw_content(self, btn_y: float): self._label.render(label_rect) if self.value: - label_y = btn_y + self._rect.height - self.LABEL_VERTICAL_PADDING - sub_label_height = self._sub_label.get_content_height(self._width_hint()) - sub_label_rect = rl.Rectangle(label_x, label_y - sub_label_height, self._width_hint(), sub_label_height) + label_y = btn_y + self.LABEL_VERTICAL_PADDING + self._label.get_content_height(self._width_hint()) + sub_label_height = btn_y + self._rect.height - self.LABEL_VERTICAL_PADDING - label_y + sub_label_rect = rl.Rectangle(label_x, label_y, self._width_hint(), sub_label_height) self._sub_label.render(sub_label_rect) # ICON ------------------------------------------------------------------- From 3cc4683eb737718b9b80554e40dc2c019182efe4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 17:34:26 -0800 Subject: [PATCH 382/685] mici reset: fix cancel closes application (#37434) * fix * match tici * rm --- system/ui/mici_reset.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 357e6729315..a459927eeb4 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -38,18 +38,13 @@ def __init__(self, mode): self._reset_state = ResetState.NONE self._cancel_button = SmallButton("cancel") - self._cancel_button.set_click_callback(self._cancel_callback) + self._cancel_button.set_click_callback(gui_app.request_close) self._reboot_button = FullRoundedButton("reboot") self._reboot_button.set_click_callback(self._do_reboot) self._confirm_slider = SmallSlider("reset", self._confirm) - self._render_status = True - - def _cancel_callback(self): - self._render_status = False - def _do_reboot(self): if PC: return @@ -121,8 +116,6 @@ def _render(self, rect: rl.Rectangle): self._reboot_button.rect.width, self._reboot_button.rect.height)) - return self._render_status - def _confirm(self): self.start_reset() From 6d559c4219695afd45d04a590b696b5412d63d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Thu, 26 Feb 2026 19:47:07 -0800 Subject: [PATCH 383/685] lagd: min_lag (#37402) * Add min_lag * Split line * Clip lag * Test should run with 3 lag frames too * Update selfdrive/locationd/lagd.py --- selfdrive/locationd/lagd.py | 16 +++++++++------- selfdrive/locationd/test/test_lagd.py | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index d7834f7f1fd..361bb79cce5 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -24,6 +24,7 @@ MAX_YAW_RATE_SANITY_CHECK = 1.0 MIN_NCC = 0.95 MAX_LAG = 1.0 +MIN_LAG = 0.15 MAX_LAG_STD = 0.1 MAX_LAT_ACCEL = 2.0 MAX_LAT_ACCEL_DIFF = 0.6 @@ -215,7 +216,7 @@ def get_msg(self, valid: bool, debug: bool = False) -> capnp._DynamicStructBuild liveDelay.status = log.LiveDelayData.Status.unestimated if liveDelay.status == log.LiveDelayData.Status.estimated: - liveDelay.lateralDelay = valid_mean_lag + liveDelay.lateralDelay = min(MAX_LAG, max(MIN_LAG, valid_mean_lag)) else: liveDelay.lateralDelay = self.initial_lag @@ -298,7 +299,7 @@ def update_estimate(self): new_values_start_idx = next(-i for i, t in enumerate(reversed(times)) if t <= self.last_estimate_t) is_valid = is_valid and not (new_values_start_idx == 0 or not np.any(okay[new_values_start_idx:])) - delay, corr, confidence = self.actuator_delay(desired, actual, okay, self.dt, MAX_LAG) + delay, corr, confidence = self.actuator_delay(desired, actual, okay, self.dt, MIN_LAG, MAX_LAG) if corr < self.min_ncc or confidence < self.min_confidence or not is_valid: return @@ -306,22 +307,23 @@ def update_estimate(self): self.last_estimate_t = self.t @staticmethod - def actuator_delay(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, dt: float, max_lag: float) -> tuple[float, float, float]: + def actuator_delay(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, + dt: float, min_lag: float, max_lag: float) -> tuple[float, float, float]: assert len(expected_sig) == len(actual_sig) - max_lag_samples = int(max_lag / dt) + min_lag_samples, max_lag_samples = int(round(min_lag / dt)), int(round(max_lag / dt)) padded_size = fft_next_good_size(len(expected_sig) + max_lag_samples) ncc = masked_normalized_cross_correlation(expected_sig, actual_sig, mask, padded_size) - # only consider lags from 0 to max_lag - roi = np.s_[len(expected_sig) - 1: len(expected_sig) - 1 + max_lag_samples] + # only consider lags from min_lag to max_lag + roi = np.s_[len(expected_sig) - 1 + min_lag_samples: len(expected_sig) - 1 + max_lag_samples] extended_roi = np.s_[roi.start - CORR_BORDER_OFFSET: roi.stop + CORR_BORDER_OFFSET] roi_ncc = ncc[roi] extended_roi_ncc = ncc[extended_roi] max_corr_index = np.argmax(roi_ncc) corr = roi_ncc[max_corr_index] - lag = parabolic_peak_interp(roi_ncc, max_corr_index) * dt + lag = parabolic_peak_interp(roi_ncc, max_corr_index) * dt + min_lag # to estimate lag confidence, gather all high-correlation candidates and see how spread they are # if e.g. 0.8 and 0.4 are both viable, this is an ambiguous case diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py index e9b5aff6d4e..4728413d9dd 100644 --- a/selfdrive/locationd/test/test_lagd.py +++ b/selfdrive/locationd/test/test_lagd.py @@ -97,7 +97,7 @@ def test_empty_estimator(self): assert msg.liveDelay.calPerc == 0 def test_estimator_basics(self, subtests): - for lag_frames in range(5): + for lag_frames in range(3, 10): with subtests.test(msg=f"lag_frames={lag_frames}"): mocked_CP = car.CarParams(steerActuatorDelay=0.8) estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0) @@ -111,7 +111,7 @@ def test_estimator_basics(self, subtests): assert msg.liveDelay.calPerc == 100 def test_estimator_masking(self): - mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(1, 19) + mocked_CP, lag_frames = car.CarParams(steerActuatorDelay=0.8), random.randint(3, 19) estimator = LateralLagEstimator(mocked_CP, DT, min_recovery_buffer_sec=0.0, min_yr=0.0, min_valid_block_count=1) process_messages(estimator, lag_frames, (int(MIN_OKAY_WINDOW_SEC / DT) + BLOCK_SIZE) * 2, rejection_threshold=0.4) msg = estimator.get_msg(True) From 2ef29967e8dfa25b73c85e50f92d5b4f286c9f28 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 26 Feb 2026 20:42:18 -0800 Subject: [PATCH 384/685] tici: rm cavli modem config --- system/hardware/tici/hardware.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 2295ca3cba5..2080341c88f 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -464,6 +464,7 @@ def configure_modem(self): cmds = [] + # Quectel EG25 if self.get_device_type() in ("tizi", ): # clear out old blue prime initial APN os.system('mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn="') @@ -478,16 +479,8 @@ def configure_modem(self): 'AT+QNVFW="/nv/item_files/ims/IMS_enable",00', 'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01', ] - elif manufacturer == 'Cavli Inc.': - cmds += [ - 'AT^SIMSWAP=1', # use SIM slot, instead of internal eSIM - 'AT$QCSIMSLEEP=0', # disable SIM sleep - 'AT$QCSIMCFG=SimPowerSave,0', # more sleep disable - # ethernet config - 'AT$QCPCFG=usbNet,0', - 'AT$QCNETDEVCTL=3,1', - ] + # Quectel EG916 else: # this modem gets upset with too many AT commands if sim_id is None or len(sim_id) == 0: From 245d5bba9c325ad4f662c491aa6f124b6f900466 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 26 Feb 2026 20:49:18 -0800 Subject: [PATCH 385/685] make ruff happy --- system/hardware/tici/hardware.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 2080341c88f..8219f0a5872 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -456,13 +456,8 @@ def initialize_hardware(self): def configure_modem(self): sim_id = self.get_sim_info().get('sim_id', '') - modem = self.get_modem() - try: - manufacturer = str(modem.Get(MM_MODEM, 'Manufacturer', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)) - except Exception: - manufacturer = None - cmds = [] + modem = self.get_modem() # Quectel EG25 if self.get_device_type() in ("tizi", ): From 0977a91d656660c22874bdf6e91267e8d0a4edd6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 26 Feb 2026 21:17:00 -0800 Subject: [PATCH 386/685] CI for the people: no cache (#37437) * sympathize with our first time cloners * venv * rm compile openpilot * retry for all * rm setup action --- .github/workflows/auto-cache/action.yaml | 55 ------------------ .github/workflows/badges.yaml | 2 +- .../workflows/compile-openpilot/action.yaml | 21 ------- .github/workflows/release.yaml | 2 +- .github/workflows/repo-maintenance.yaml | 4 +- .../workflows/setup-with-retry/action.yaml | 48 ---------------- .github/workflows/setup/action.yaml | 56 ------------------- .github/workflows/tests.yaml | 41 ++++++-------- tools/op.sh | 24 +++++++- 9 files changed, 42 insertions(+), 211 deletions(-) delete mode 100644 .github/workflows/auto-cache/action.yaml delete mode 100644 .github/workflows/compile-openpilot/action.yaml delete mode 100644 .github/workflows/setup-with-retry/action.yaml delete mode 100644 .github/workflows/setup/action.yaml diff --git a/.github/workflows/auto-cache/action.yaml b/.github/workflows/auto-cache/action.yaml deleted file mode 100644 index 42c8f8fd2db..00000000000 --- a/.github/workflows/auto-cache/action.yaml +++ /dev/null @@ -1,55 +0,0 @@ -name: 'automatically cache based on current runner' - -inputs: - path: - description: 'path to cache' - required: true - key: - description: 'key' - required: true - restore-keys: - description: 'restore-keys' - required: true - save: - description: 'whether to save the cache' - default: 'true' - required: false -outputs: - cache-hit: - description: 'cache hit occurred' - value: ${{ (contains(runner.name, 'nsc') && steps.ns-cache.outputs.cache-hit) || - (!contains(runner.name, 'nsc') && inputs.save != 'false' && steps.gha-cache.outputs.cache-hit) || - (!contains(runner.name, 'nsc') && inputs.save == 'false' && steps.gha-cache-ro.outputs.cache-hit) }} - -runs: - using: "composite" - steps: - - name: setup namespace cache - id: ns-cache - if: ${{ contains(runner.name, 'nsc') }} - uses: namespacelabs/nscloud-cache-action@v1 - with: - path: ${{ inputs.path }} - - - name: setup github cache - id: gha-cache - if: ${{ !contains(runner.name, 'nsc') && inputs.save != 'false' }} - uses: 'actions/cache@v4' - with: - path: ${{ inputs.path }} - key: ${{ inputs.key }} - restore-keys: ${{ inputs.restore-keys }} - - - name: setup github cache - id: gha-cache-ro - if: ${{ !contains(runner.name, 'nsc') && inputs.save == 'false' }} - uses: 'actions/cache/restore@v4' - with: - path: ${{ inputs.path }} - key: ${{ inputs.key }} - restore-keys: ${{ inputs.restore-keys }} - - # make the directory manually in case we didn't get a hit, so it doesn't fail on future steps - - id: scons-cache-setup - shell: bash - run: mkdir -p ${{ inputs.path }} diff --git a/.github/workflows/badges.yaml b/.github/workflows/badges.yaml index 23f2c135d5d..9b99c4f1fe7 100644 --- a/.github/workflows/badges.yaml +++ b/.github/workflows/badges.yaml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Push badges run: | python3 selfdrive/ui/translations/create_badges.py diff --git a/.github/workflows/compile-openpilot/action.yaml b/.github/workflows/compile-openpilot/action.yaml deleted file mode 100644 index 627b4845aa6..00000000000 --- a/.github/workflows/compile-openpilot/action.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: 'compile openpilot' - -runs: - using: "composite" - steps: - - shell: bash - name: Build openpilot with all flags - run: | - scons -j$(nproc) - release/check-dirty.sh - - shell: bash - name: Cleanup scons cache and rebuild - run: | - rm -rf /tmp/scons_cache/* - scons -j$(nproc) --cache-populate - - name: Save scons cache - uses: actions/cache/save@v4 - if: github.ref == 'refs/heads/master' - with: - path: /tmp/scons_cache - key: scons-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 4d731965d7c..db0e12234bc 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,6 +26,6 @@ jobs: with: submodules: true fetch-depth: 0 - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Push master-ci run: BRANCH=__nightly release/build_stripped.sh diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index d2c2447d7ac..2c5d049e4c3 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Update translations run: python3 selfdrive/ui/update_translations.py --vanish - name: Create Pull Request @@ -39,7 +39,7 @@ jobs: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: uv lock run: uv lock --upgrade - name: uv pip tree diff --git a/.github/workflows/setup-with-retry/action.yaml b/.github/workflows/setup-with-retry/action.yaml deleted file mode 100644 index 923cc3aadbd..00000000000 --- a/.github/workflows/setup-with-retry/action.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: 'openpilot env setup, with retry on failure' - -inputs: - sleep_time: - description: 'Time to sleep between retries' - required: false - default: 30 - -outputs: - duration: - description: 'Duration of the setup process in seconds' - value: ${{ steps.get_duration.outputs.duration }} - -runs: - using: "composite" - steps: - - id: start_time - shell: bash - run: echo "START_TIME=$(date +%s)" >> $GITHUB_ENV - - id: setup1 - uses: ./.github/workflows/setup - continue-on-error: true - with: - is_retried: true - - if: steps.setup1.outcome == 'failure' - shell: bash - run: sleep ${{ inputs.sleep_time }} - - id: setup2 - if: steps.setup1.outcome == 'failure' - uses: ./.github/workflows/setup - continue-on-error: true - with: - is_retried: true - - if: steps.setup2.outcome == 'failure' - shell: bash - run: sleep ${{ inputs.sleep_time }} - - id: setup3 - if: steps.setup2.outcome == 'failure' - uses: ./.github/workflows/setup - with: - is_retried: true - - id: get_duration - shell: bash - run: | - END_TIME=$(date +%s) - DURATION=$((END_TIME - START_TIME)) - echo "Total duration: $DURATION seconds" - echo "duration=$DURATION" >> $GITHUB_OUTPUT diff --git a/.github/workflows/setup/action.yaml b/.github/workflows/setup/action.yaml deleted file mode 100644 index f3a1a395098..00000000000 --- a/.github/workflows/setup/action.yaml +++ /dev/null @@ -1,56 +0,0 @@ -name: 'openpilot env setup' - -inputs: - is_retried: - description: 'A mock param that asserts that we use the setup-with-retry instead of this action directly' - required: false - default: 'false' - -runs: - using: "composite" - steps: - # assert that this action is retried using the setup-with-retry - - shell: bash - if: ${{ inputs.is_retried == 'false' }} - run: | - echo "You should not run this action directly. Use setup-with-retry instead" - exit 1 - - - shell: bash - name: No retries! - run: | - if [ "${{ github.run_attempt }}" -gt 1 ]; then - echo -e "\033[0;31m##################################################" - echo -e "\033[0;31m Retries not allowed! Fix the flaky test! " - echo -e "\033[0;31m##################################################\033[0m" - exit 1 - fi - - # build cache - - id: date - shell: bash - run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV - - id: scons-cache - uses: ./.github/workflows/auto-cache - with: - path: /tmp/scons_cache - key: scons-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }} - restore-keys: | - scons-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_COMMIT_DATE }} - scons-${{ runner.os }}-${{ runner.arch }} - # venv cache - - id: venv-cache - uses: ./.github/workflows/auto-cache - with: - path: ${{ github.workspace }}/.venv - key: venv-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('uv.lock') }} - restore-keys: | - venv-${{ runner.os }}-${{ runner.arch }} - - shell: bash - name: Run setup - run: ./tools/op.sh setup - - shell: bash - name: Setup cache dirs - run: | - mkdir -p /tmp/comma_download_cache - echo "$GITHUB_WORKSPACE/.venv/bin" >> $GITHUB_PATH diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f95fe7d2e22..00fdceda0b3 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -27,7 +27,7 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} env: STRIPPED_DIR: /tmp/releasepilot @@ -45,7 +45,7 @@ jobs: - name: Build devel timeout-minutes: 1 run: TARGET_DIR=$STRIPPED_DIR release/build_stripped.sh - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Build openpilot and run checks timeout-minutes: 30 working-directory: ${{ env.STRIPPED_DIR }} @@ -70,7 +70,7 @@ jobs: run: | FILTERED=$(echo "$PATH" | tr ':' '\n' | grep -v '/opt/homebrew' | tr '\n' ':') echo "PATH=${FILTERED}/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" >> $GITHUB_ENV - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Building openpilot run: scons @@ -80,13 +80,13 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Static analysis timeout-minutes: 1 run: scripts/lint/lint.sh @@ -97,18 +97,17 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry - id: setup-step + - run: ./tools/op.sh setup - name: Build openpilot run: scons -j$(nproc) - name: Run unit tests - timeout-minutes: ${{ contains(runner.name, 'nsc') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }} + timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }} run: | source selfdrive/test/setup_xvfb.sh # Pre-compile Python bytecode so each pytest worker doesn't need to @@ -121,24 +120,17 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry - id: setup-step - - name: Cache test routes - id: dependency-cache - uses: actions/cache@v5 - with: - path: /tmp/comma_download_cache - key: proc-replay-${{ hashFiles('selfdrive/test/process_replay/test_processes.py') }} + - run: ./tools/op.sh setup - name: Build openpilot run: scons -j$(nproc) - name: Run replay - timeout-minutes: ${{ contains(runner.name, 'nsc') && (steps.dependency-cache.outputs.cache-hit == 'true') && ((steps.setup-step.outputs.duration < 18) && 1 || 2) || 20 }} + timeout-minutes: ${{ contains(runner.name, 'nsc') && 2 || 20 }} continue-on-error: ${{ github.ref == 'refs/heads/master' }} run: selfdrive/test/process_replay/test_processes.py -j$(nproc) - name: Print diff @@ -184,19 +176,18 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} if: false # FIXME: Started to timeout recently steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry - id: setup-step + - run: ./tools/op.sh setup - name: Build openpilot run: scons -j$(nproc) - name: Driving test - timeout-minutes: ${{ (steps.setup-step.outputs.duration < 18) && 1 || 2 }} + timeout-minutes: 2 run: | source selfdrive/test/setup_xvfb.sh pytest -s tools/sim/tests/test_metadrive_bridge.py @@ -207,13 +198,13 @@ jobs: (github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot')) - && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]') + && fromJSON('["namespace-profile-amd64-8x16"]') || fromJSON('["ubuntu-24.04"]') }} steps: - uses: actions/checkout@v6 with: submodules: true - - uses: ./.github/workflows/setup-with-retry + - run: ./tools/op.sh setup - name: Build openpilot run: scons -j$(nproc) - name: Create UI Report diff --git a/tools/op.sh b/tools/op.sh index f5c5b6082a4..9f3d4ee13b2 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -35,6 +35,21 @@ function loge() { fi } +function retry() { + local attempts=$1 + shift + for i in $(seq 1 "$attempts"); do + if "$@"; then + return 0 + fi + if [ "$i" -lt "$attempts" ]; then + echo " Attempt $i/$attempts failed, retrying in 5s..." + sleep 5 + fi + done + return 1 +} + function op_run_command() { CMD="$@" @@ -229,7 +244,7 @@ function op_setup() { echo "Getting git submodules..." st="$(date +%s)" - if ! git submodule update --jobs 4 --init --recursive; then + if ! retry 3 git submodule update --jobs 4 --init --recursive; then echo -e " ↳ [${RED}✗${NC}] Getting git submodules failed!" loge "ERROR_GIT_SUBMODULES" return 1 @@ -239,7 +254,7 @@ function op_setup() { echo "Pulling git lfs files..." st="$(date +%s)" - if ! git lfs pull; then + if ! retry 3 git lfs pull; then echo -e " ↳ [${RED}✗${NC}] Pulling git lfs files failed!" loge "ERROR_GIT_LFS" return 1 @@ -260,6 +275,11 @@ function op_activate_venv() { set +e source $OPENPILOT_ROOT/.venv/bin/activate &> /dev/null || true set -e + + # persist venv on PATH across GitHub Actions steps + if [ -n "$GITHUB_PATH" ]; then + echo "$OPENPILOT_ROOT/.venv/bin" >> "$GITHUB_PATH" + fi } function op_venv() { From 286c4f8403901beb7c7aac3ff12b396791207e14 Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Fri, 27 Feb 2026 07:24:51 +0200 Subject: [PATCH 387/685] op.sh: fallback to script's own location for openpilot root (#37326) op: fallback to script's own location for openpilot root --- tools/op.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/op.sh b/tools/op.sh index 9f3d4ee13b2..2d833d68968 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -77,7 +77,8 @@ function op_get_openpilot_dir() { done # Fallback to hardcoded directories if not found - for dir in "$HOME/openpilot" "/data/openpilot"; do + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + for dir in "${SCRIPT_DIR%/tools}" "$HOME/openpilot" "/data/openpilot"; do if [[ -f "$dir/launch_openpilot.sh" ]]; then OPENPILOT_ROOT="$dir" return 0 From de8f7c45842abaf431807ebc94c578bbc3af08da Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 23:24:21 -0800 Subject: [PATCH 388/685] Scroller: rename scroll_to(block) --- system/ui/widgets/scroller.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index b2aeb557446..2c10ff432a0 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -78,7 +78,7 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo self._reset_scroll_at_show = True - self._scrolling_to: tuple[float | None, bool] = (None, False) # target offset, block user scrolling + self._scrolling_to: tuple[float | None, bool] = (None, False) # target offset, block_interaction self._scrolling_to_filter = FirstOrderFilter(0.0, SCROLL_RC, 1 / gui_app.target_fps) self._zoom_filter = FirstOrderFilter(1.0, 0.2, 1 / gui_app.target_fps) self._zoom_out_t: float = 0.0 @@ -115,8 +115,8 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo def set_reset_scroll_at_show(self, scroll: bool): self._reset_scroll_at_show = scroll - def scroll_to(self, pos: float, smooth: bool = False, block: bool = False): - assert not block or smooth, "Instant scroll cannot be blocking" + def scroll_to(self, pos: float, smooth: bool = False, block_interaction: bool = False): + assert not block_interaction or smooth, "Instant scroll cannot block user interaction" # already there if abs(pos) < 1: @@ -126,7 +126,7 @@ def scroll_to(self, pos: float, smooth: bool = False, block: bool = False): scroll_offset = self.scroll_panel.get_offset() - pos if smooth: self._scrolling_to_filter.x = self.scroll_panel.get_offset() - self._scrolling_to = scroll_offset, block + self._scrolling_to = scroll_offset, block_interaction else: self.scroll_panel.set_offset(scroll_offset) @@ -167,7 +167,7 @@ def _update_state(self): else: self._zoom_filter.update(0.85) - # Cancel auto-scroll if user starts manually scrolling (unless blocking) + # Cancel auto-scroll if user starts manually scrolling (unless block_interaction) if (self.scroll_panel.state in (ScrollState.PRESSED, ScrollState.MANUAL_SCROLL) and self._scrolling_to[0] is not None and not self._scrolling_to[1]): self._scrolling_to = None, False From 0437998bcef43c19b4769058f03aae22218b825c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 26 Feb 2026 23:25:48 -0800 Subject: [PATCH 389/685] Scroller: add_widgets helper --- system/ui/widgets/scroller.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 2c10ff432a0..65378688e66 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -109,8 +109,7 @@ def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: boo self._pending_lift: set[Widget] = set() self._pending_move: set[Widget] = set() - for item in items: - self.add_widget(item) + self.add_widgets(items) def set_reset_scroll_at_show(self, scroll: bool): self._reset_scroll_at_show = scroll @@ -151,6 +150,10 @@ def add_widget(self, item: Widget) -> None: and not self.moving_items and (original_touch_valid_callback() if original_touch_valid_callback else True)) + def add_widgets(self, items: list[Widget]) -> None: + for item in items: + self.add_widget(item) + def set_scrolling_enabled(self, enabled: bool | Callable[[], bool]) -> None: """Set whether scrolling is enabled (does not affect widget enabled state).""" self._scroll_enabled = enabled From fe39ffa55ae735c3e18043b677887a4376676e4f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 27 Feb 2026 00:56:41 -0800 Subject: [PATCH 390/685] mici ui: clear ssh key (#37449) * clear ssh * rev --- selfdrive/ui/mici/layouts/settings/developer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index b04d6968234..ee0856a20e2 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -25,10 +25,14 @@ def github_username_callback(username: str): else: dlg = BigDialog("", ssh_keys._error_message) gui_app.push_widget(dlg) + else: + ui_state.params.remove("GithubUsername") + ui_state.params.remove("GithubSshKeys") + self._ssh_keys_btn.set_value("Not set") def ssh_keys_callback(): github_username = ui_state.params.get("GithubUsername") or "" - dlg = BigInputDialog("enter GitHub username...", github_username, confirm_callback=github_username_callback) + dlg = BigInputDialog("enter GitHub username...", github_username, minimum_length=0, confirm_callback=github_username_callback) if not system_time_valid(): dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "") gui_app.push_widget(dlg) From 1bf0fb385149796b994186ac56b05f3868c67aa6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 27 Feb 2026 02:37:02 -0800 Subject: [PATCH 391/685] mici ui: Scroller widget helpers (#37451) * it's so dumb * niceeee * oh this is interesting * this is actually epic * clean up * more clean up * cmt * super * forgot * top --- selfdrive/ui/mici/layouts/main.py | 19 +++-------- selfdrive/ui/mici/layouts/offroad_alerts.py | 13 ++------ .../ui/mici/layouts/settings/developer.py | 17 ++-------- selfdrive/ui/mici/layouts/settings/device.py | 18 +++-------- .../ui/mici/layouts/settings/firehose.py | 1 + .../mici/layouts/settings/network/__init__.py | 12 ++----- .../mici/layouts/settings/network/wifi_ui.py | 14 ++------ .../ui/mici/layouts/settings/settings.py | 20 ++---------- selfdrive/ui/mici/layouts/settings/toggles.py | 16 ++-------- system/ui/widgets/nav_widget.py | 16 +--------- system/ui/widgets/scroller.py | 32 ++++++++++++++++++- 11 files changed, 60 insertions(+), 118 deletions(-) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 3e3948eeaba..e39a228daf5 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -14,9 +14,9 @@ ONROAD_DELAY = 2.5 # seconds -class MiciMainLayout(Widget): +class MiciMainLayout(Scroller): def __init__(self): - super().__init__() + super().__init__(snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False) self._pm = messaging.PubMaster(['bookmarkButton']) @@ -36,13 +36,12 @@ def __init__(self): # TODO: set parent rect and use it if never passed rect from render (like in Scroller) widget.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - self._scroller = Scroller([ + self._scroller.add_widgets([ self._alerts_layout, self._home_layout, self._onroad_layout, - ], snap_items=True, spacing=0, pad=0, scroll_indicator=False, edge_shadows=False) + ]) self._scroller.set_reset_scroll_at_show(False) - self._scroller.set_enabled(lambda: self.enabled) # for nav stack # Disable scrolling when onroad is interacting with bookmark self._scroller.set_scrolling_enabled(lambda: not self._onroad_layout.is_swiping_left()) @@ -62,14 +61,6 @@ def _setup_callbacks(self): self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) device.add_interactive_timeout_callback(self._on_interactive_timeout) - def show_event(self): - super().show_event() - self._scroller.show_event() - - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) self._scroller.scroll_to(layout_x, smooth=True) @@ -83,7 +74,7 @@ def _render(self, _): self._setup = True # Render - self._scroller.render(self._rect) + super()._render(self._rect) self._handle_transitions() diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index bc1cd02c5d0..5ccb815da65 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -186,19 +186,17 @@ def _render(self, _): rl.draw_texture(icon_texture, int(icon_x), int(icon_y), rl.WHITE) -class MiciOffroadAlerts(Widget): +class MiciOffroadAlerts(Scroller): """Offroad alerts layout with vertical scrolling.""" def __init__(self): - super().__init__() + # Create vertical scroller + super().__init__(horizontal=False, spacing=12, pad=0) self.params = Params() self.sorted_alerts: list[AlertData] = [] self.alert_items: list[AlertItem] = [] self._last_refresh = 0.0 - # Create vertical scroller - self._scroller = Scroller([], horizontal=False, spacing=12, pad=0) - # Create empty state label self._empty_label = UnifiedLabel(tr("no alerts"), 65, FontWeight.DISPLAY, rl.WHITE, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, @@ -290,14 +288,9 @@ def refresh(self) -> int: def show_event(self): """Reset scroll position when shown and refresh alerts.""" super().show_event() - self._scroller.show_event() self._last_refresh = time.monotonic() self.refresh() - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - def _update_state(self): """Periodically refresh alerts.""" # Refresh alerts periodically, not every frame diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index ee0856a20e2..4d6bdfc3bb4 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -1,17 +1,14 @@ -import pyray as rl - from openpilot.common.time_helpers import system_time_valid -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl, BigCircleParamControl from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyAction -class DeveloperLayoutMici(NavWidget): +class DeveloperLayoutMici(NavScroller): def __init__(self): super().__init__() self.set_back_callback(gui_app.pop_widget) @@ -61,7 +58,7 @@ def ssh_keys_callback(): toggle_callback=lambda checked: (gui_app.set_show_touches(checked), gui_app.set_show_fps(checked))) - self._scroller = Scroller([ + self._scroller.add_widgets([ self._adb_toggle, self._ssh_toggle, self._ssh_keys_btn, @@ -105,16 +102,8 @@ def ssh_keys_callback(): def show_event(self): super().show_event() - self._scroller.show_event() self._update_toggles() - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - - def _render(self, rect: rl.Rectangle): - self._scroller.render(rect) - def _update_toggles(self): ui_state.update_params() diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index cd7172455f5..7383393542f 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -7,7 +7,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.time_helpers import system_time_valid -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 @@ -32,6 +32,7 @@ def __init__(self, file_path: str | None = None, text: str | None = None): self.set_back_callback(gui_app.pop_widget) self._content = HtmlRenderer(file_path=file_path, text=text) self._scroll_panel = GuiScrollPanel2(horizontal=False) + self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away) self._fcc_logo = gui_app.texture("icons_mici/settings/device/fcc_logo.png", 76, 64) def _render(self, rect: rl.Rectangle): @@ -266,7 +267,7 @@ def _update_state(self): self._waiting_for_updater_t = None -class DeviceLayoutMici(NavWidget): +class DeviceLayoutMici(NavScroller): def __init__(self): super().__init__() @@ -313,7 +314,7 @@ def uninstall_openpilot_callback(): review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget))) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) - self._scroller = Scroller([ + self._scroller.add_widgets([ DeviceInfoLayoutMici(), UpdateOpenpilotBigButton(), PairBigButton(), @@ -340,14 +341,3 @@ def _on_regulatory(self): def _offroad_transition(self): self._power_off_btn.set_visible(ui_state.is_offroad()) - - def show_event(self): - super().show_event() - self._scroller.show_event() - - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - - def _render(self, rect: rl.Rectangle): - self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index eb3331c8682..b2c06d72992 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -224,3 +224,4 @@ class FirehoseLayout(FirehoseLayoutBase, NavWidget): def __init__(self): super().__init__() self.set_back_callback(gui_app.pop_widget) + self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index e5049a9ee75..ce3f1a817ee 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -1,13 +1,12 @@ import pyray as rl -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, SecurityType, normalize_ssid @@ -65,7 +64,7 @@ def _draw_content(self, btn_y: float): rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, 1.0, rl.WHITE) -class NetworkLayoutMici(NavWidget): +class NetworkLayoutMici(NavScroller): def __init__(self): super().__init__() @@ -132,7 +131,7 @@ def network_metered_callback(value: str): self._cellular_metered_btn = BigParamControl("cellular metered", "GsmMetered", toggle_callback=self._toggle_cellular_metered) # Main scroller ---------------------------------- - self._scroller = Scroller([ + self._scroller.add_widgets([ self._wifi_button, self._network_metered_btn, self._tethering_toggle_btn, @@ -165,14 +164,12 @@ def _update_state(self): def show_event(self): super().show_event() self._wifi_manager.set_active(True) - self._scroller.show_event() # Process wifi callbacks while at any point in the nav stack gui_app.set_nav_stack_tick(self._wifi_manager.process_callbacks) def hide_event(self): super().hide_event() - self._scroller.hide_event() self._wifi_manager.set_active(False) gui_app.set_nav_stack_tick(None) @@ -213,6 +210,3 @@ def _on_network_updated(self, networks: list[Network]): MeteredType.YES: 'metered', MeteredType.NO: 'unmetered' }.get(self._wifi_manager.current_network_metered, 'default')) - - def _render(self, rect: rl.Rectangle): - self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index dda1d12220a..2fbe23c1915 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -9,8 +9,7 @@ from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, SecurityType, normalize_ssid @@ -271,15 +270,13 @@ def _render(self, _): rl.draw_texture_ex(self._trash_txt, (trash_x, trash_y), 0, 1.0, rl.WHITE) -class WifiUIMici(NavWidget): +class WifiUIMici(NavScroller): def __init__(self, wifi_manager: WifiManager): super().__init__() # Set up back navigation self.set_back_callback(gui_app.pop_widget) - self._scroller = Scroller([]) - self._loading_animation = LoadingAnimation() self._wifi_manager = wifi_manager @@ -294,17 +291,12 @@ def __init__(self, wifi_manager: WifiManager): def show_event(self): # Clear scroller items and update from latest scan results super().show_event() - self._scroller.show_event() self._loading_animation.show_event() self._wifi_manager.set_active(True) self._scroller.items.clear() # trigger button update on latest sorted networks self._on_network_updated(self._wifi_manager.networks) - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - def _on_network_updated(self, networks: list[Network]): self._networks = {network.ssid: network for network in networks} self._update_buttons() @@ -389,7 +381,7 @@ def _update_state(self): self._loading_animation.show_event() def _render(self, _): - self._scroller.render(self._rect) + super()._render(self._rect) anim_w = 90 anim_x = self._rect.x + self._rect.width - anim_w diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 15fd6819966..d996e01fed3 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -1,7 +1,5 @@ -import pyray as rl - from openpilot.common.params import Params -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici @@ -9,7 +7,6 @@ from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets.nav_widget import NavWidget class SettingsBigButton(BigButton): @@ -17,7 +14,7 @@ def _get_label_font_size(self): return 64 -class SettingsLayout(NavWidget): +class SettingsLayout(NavScroller): def __init__(self): super().__init__() self._params = Params() @@ -42,7 +39,7 @@ def __init__(self): firehose_btn = SettingsBigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) firehose_btn.set_click_callback(lambda: gui_app.push_widget(firehose_panel)) - self._scroller = Scroller([ + self._scroller.add_widgets([ toggles_btn, network_btn, device_btn, @@ -56,14 +53,3 @@ def __init__(self): self.set_back_callback(gui_app.pop_widget) self._font_medium = gui_app.font(FontWeight.MEDIUM) - - def show_event(self): - super().show_event() - self._scroller.show_event() - - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - - def _render(self, rect: rl.Rectangle): - self._scroller.render(rect) diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index d6a91b40f72..a7a7bff6e28 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -1,17 +1,15 @@ -import pyray as rl from cereal import log -from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants -class TogglesLayoutMici(NavWidget): +class TogglesLayoutMici(NavScroller): def __init__(self): super().__init__() self.set_back_callback(gui_app.pop_widget) @@ -25,7 +23,7 @@ def __init__(self): record_mic = BigParamControl("record & upload mic audio", "RecordAudio", toggle_callback=restart_needed_callback) enable_openpilot = BigParamControl("enable openpilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback) - self._scroller = Scroller([ + self._scroller.add_widgets([ self._personality_toggle, self._experimental_btn, is_metric_toggle, @@ -68,13 +66,8 @@ def _update_state(self): def show_event(self): super().show_event() - self._scroller.show_event() self._update_toggles() - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - def _update_toggles(self): ui_state.update_params() @@ -93,6 +86,3 @@ def _update_toggles(self): # Refresh toggles from params to mirror external changes for key, item in self._refresh_toggles: item.set_checked(ui_state.params.get_bool(key)) - - def _render(self, rect: rl.Rectangle): - self._scroller.render(rect) diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 2944f47a767..02afc911b2e 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -69,8 +69,6 @@ def __init__(self): self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) - self._set_up = False - @property def back_enabled(self) -> bool: return self._back_enabled() if callable(self._back_enabled) else self._back_enabled @@ -96,6 +94,7 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: self._pos_filter.update_alpha(0.04) in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE + # TODO: remove vertical scrolling and then this hacky logic to check if scroller is at top scroller_at_top = False vertical_scroller = False # TODO: -20? snapping in WiFi dialog can make offset not be positive at the top @@ -138,19 +137,6 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: def _update_state(self): super()._update_state() - # Disable self's scroller while swiping away - if not self._set_up: - self._set_up = True - if hasattr(self, '_scroller'): - # TODO: use touch_valid - original_enabled = self._scroller._enabled - self._scroller.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else - original_enabled)) - elif hasattr(self, '_scroll_panel'): - original_enabled = self._scroll_panel.enabled - self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away and (original_enabled() if callable(original_enabled) else - original_enabled)) - if self._trigger_animate_in: self._pos_filter.x = self._rect.height self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 65378688e66..9d9f5663b87 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -7,6 +7,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2, ScrollState from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget ITEM_SPACING = 20 LINE_COLOR = rl.GRAY @@ -66,7 +67,8 @@ def _render(self, _): rl.Color(255, 255, 255, int(255 * 0.45))) -class Scroller(Widget): +class _Scroller(Widget): + """Should use wrapper below to reduce boilerplate""" def __init__(self, items: list[Widget], horizontal: bool = True, snap_items: bool = False, spacing: int = ITEM_SPACING, pad: int = ITEM_SPACING, scroll_indicator: bool = True, edge_shadows: bool = True): super().__init__() @@ -414,3 +416,31 @@ def hide_event(self): super().hide_event() for item in self._items: item.hide_event() + + +class Scroller(Widget): + """Wrapper for _Scroller so that children do not need to call events or pass down enabled for nav stack.""" + def __init__(self, **kwargs): + super().__init__() + self._scroller = _Scroller([], **kwargs) + # pass down enabled to child widget for nav stack + self._scroller.set_enabled(lambda: self.enabled) + + def show_event(self): + super().show_event() + self._scroller.show_event() + + def hide_event(self): + super().hide_event() + self._scroller.hide_event() + + def _render(self, _): + self._scroller.render(self._rect) + + +class NavScroller(NavWidget, Scroller): + """Full screen Scroller that properly supports nav stack w/ animations""" + def __init__(self, **kwargs): + super().__init__(**kwargs) + # pass down enabled to child widget for nav stack + disable while swiping away NavWidget + self._scroller.set_enabled(lambda: self.enabled and not self._swiping_away) From c5372e904127a44aed4fd01749b7bf5dfefa667b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 27 Feb 2026 08:04:24 -0800 Subject: [PATCH 392/685] new demo route (#37456) --- selfdrive/debug/mem_usage.py | 9 ++--- tools/clip/run.py | 68 ++++++++++++++++++++++++++++-------- tools/jotpluggler/pluggle.py | 29 ++++++++++----- tools/plotjuggler/juggle.py | 5 ++- tools/replay/replay.h | 2 +- 5 files changed, 81 insertions(+), 32 deletions(-) diff --git a/selfdrive/debug/mem_usage.py b/selfdrive/debug/mem_usage.py index 3451bfc3d61..66e742f3e6b 100755 --- a/selfdrive/debug/mem_usage.py +++ b/selfdrive/debug/mem_usage.py @@ -8,13 +8,14 @@ from openpilot.common.utils import tabulate from openpilot.tools.lib.logreader import LogReader -DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" MB = 1024 * 1024 TABULATE_OPTS = dict(tablefmt="simple_grid", stralign="center", numalign="center") def _get_procs(): from openpilot.selfdrive.test.test_onroad import PROCS + return PROCS @@ -137,9 +138,9 @@ def print_process_tables(op_procs, other_procs, total_mb, use_pss): op_rows, op_total = process_table_rows(op_procs, total_mb, use_pss, show_detail) # filter other: >5MB avg and not bare interpreter paths (test infra noise) - other_filtered = {n: v for n, v in other_procs.items() - if np.mean(v['pss' if use_pss else 'rss']) > 5.0 - and os.path.basename(n.split()[0]) not in ('python', 'python3')} + other_filtered = { + n: v for n, v in other_procs.items() if np.mean(v['pss' if use_pss else 'rss']) > 5.0 and os.path.basename(n.split()[0]) not in ('python', 'python3') + } other_rows, other_total = process_table_rows(other_filtered, total_mb, use_pss, show_detail) rows = op_rows diff --git a/tools/clip/run.py b/tools/clip/run.py index 5711cafa592..0aa90ec0a27 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -24,7 +24,7 @@ from msgq.visionipc import VisionIpcServer, VisionStreamType FRAMERATE = 20 -DEMO_ROUTE, DEMO_START, DEMO_END = 'a2a0ccea32023010/2023-07-27--13-01-19', 90, 105 +DEMO_ROUTE, DEMO_START, DEMO_END = '5beb9b58bd12b691/0000010a--a51155e496', 90, 105 logger = logging.getLogger('clip') @@ -81,6 +81,7 @@ def _download_segment(path: str) -> bytes: def _parse_and_chunk_segment(args: tuple) -> list[dict]: raw_data, fps = args from openpilot.tools.lib.logreader import _LogFileReader + messages = migrate_all(list(_LogFileReader("", dat=raw_data, sort_by_time=True))) if not messages: return [] @@ -122,6 +123,7 @@ def mock_update(timeout=None): sm.data[svc] = getattr(msg.as_builder(), svc) sm.logMonoTime[svc], sm.recv_time[svc], sm.recv_frame[svc] = msg.logMonoTime, t, sm.frame sm.frame += 1 + ui_state.sm.update = mock_update @@ -150,8 +152,7 @@ def iter_segment_frames(camera_paths, start_time, end_time, fps=20, use_qcam=Fal if use_qcam: w, h = frame_size or get_frame_dimensions(path) with FileReader(path) as f: - result = subprocess.run(["ffmpeg", "-v", "quiet", "-i", "-", "-f", "rawvideo", "-pix_fmt", "nv12", "-"], - input=f.read(), capture_output=True) + result = subprocess.run(["ffmpeg", "-v", "quiet", "-i", "-", "-f", "rawvideo", "-pix_fmt", "nv12", "-"], input=f.read(), capture_output=True) if result.returncode != 0: raise RuntimeError(f"ffmpeg failed: {result.stderr.decode()}") seg_frames = np.frombuffer(result.stdout, dtype=np.uint8).reshape(-1, w * h * 3 // 2) @@ -172,8 +173,7 @@ def __init__(self, camera_paths, start_time, end_time, fps=20, prefetch_count=60 self.frame_w, self.frame_h = get_frame_dimensions(first_path) self._queue, self._stop, self._error = queue.Queue(maxsize=prefetch_count), threading.Event(), None - self._thread = threading.Thread(target=self._worker, - args=(camera_paths, start_time, end_time, fps, use_qcam, (self.frame_w, self.frame_h)), daemon=True) + self._thread = threading.Thread(target=self._worker, args=(camera_paths, start_time, end_time, fps, use_qcam, (self.frame_w, self.frame_h)), daemon=True) self._thread.start() def _worker(self, camera_paths, start_time, end_time, fps, use_qcam, frame_size): @@ -208,6 +208,7 @@ def stop(self): def load_route_metadata(route): from openpilot.common.params import Params, UnknownKeyName + path = next((item for item in route.log_paths() if item), None) if not path: raise Exception('error getting route metadata: cannot find any uploaded logs') @@ -223,15 +224,20 @@ def load_route_metadata(route): origin = init_data.gitRemote.split('/')[3] if len(init_data.gitRemote.split('/')) > 3 else 'unknown' return { - 'version': init_data.version, 'route': route.name.canonical_name, - 'car': car_params.carFingerprint if car_params else 'unknown', 'origin': origin, - 'branch': init_data.gitBranch, 'commit': init_data.gitCommit[:7], 'modified': str(init_data.dirty).lower(), + 'version': init_data.version, + 'route': route.name.canonical_name, + 'car': car_params.carFingerprint if car_params else 'unknown', + 'origin': origin, + 'branch': init_data.gitBranch, + 'commit': init_data.gitCommit[:7], + 'modified': str(init_data.dirty).lower(), } def draw_text_box(text, x, y, size, gui_app, font, color=None, center=False): import pyray as rl from openpilot.system.ui.lib.text_measure import measure_text_cached + box_color, text_color = rl.Color(0, 0, 0, 85), color or rl.WHITE text_size = measure_text_cached(font, text, size) text_width, text_height = int(text_size.x), int(text_size.y) @@ -244,6 +250,7 @@ def draw_text_box(text, x, y, size, gui_app, font, color=None, center=False): def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, show_metadata, show_time): from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text + metadata_size = 16 if big else 12 title_size = 32 if big else 24 time_size = 24 if big else 16 @@ -259,8 +266,17 @@ def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, # Metadata overlay (first 5 seconds) if show_metadata and metadata and frame_idx < FRAMERATE * 5: m = metadata - text = ", ".join([f"openpilot v{m['version']}", f"route: {m['route']}", f"car: {m['car']}", f"origin: {m['origin']}", - f"branch: {m['branch']}", f"commit: {m['commit']}", f"modified: {m['modified']}"]) + text = ", ".join( + [ + f"openpilot v{m['version']}", + f"route: {m['route']}", + f"car: {m['car']}", + f"origin: {m['origin']}", + f"branch: {m['branch']}", + f"commit: {m['commit']}", + f"modified: {m['modified']}", + ] + ) # Wrap text if too wide (leave margin on each side) margin = 2 * (time_width + 10 if show_time else 20) # leave enough margin for time overlay max_width = gui_app.width - margin @@ -278,17 +294,29 @@ def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, draw_text_box(title, 0, 60, title_size, gui_app, font, center=True) -def clip(route: Route, output: str, start: int, end: int, headless: bool = True, big: bool = False, - title: str | None = None, show_metadata: bool = True, show_time: bool = True, use_qcam: bool = False): +def clip( + route: Route, + output: str, + start: int, + end: int, + headless: bool = True, + big: bool = False, + title: str | None = None, + show_metadata: bool = True, + show_time: bool = True, + use_qcam: bool = False, +): timer, duration = Timer(), end - start import pyray as rl + if big: from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView else: from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight + timer.lap("import") logger.info(f"Clipping {route.name.canonical_name}, {start}s-{end}s ({duration}s)") @@ -297,7 +325,7 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, timer.lap("logs") frame_start = (start - seg_start * 60) * FRAMERATE - message_chunks = all_chunks[frame_start:frame_start + duration * FRAMERATE] + message_chunks = all_chunks[frame_start : frame_start + duration * FRAMERATE] if not message_chunks: logger.error("No messages to render") sys.exit(1) @@ -350,8 +378,18 @@ def main(): args = parse_args() setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start) - clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, not args.windowed, - args.big, args.title, not args.no_metadata, not args.no_time_overlay, args.qcam) + clip( + Route(args.route, data_dir=args.data_dir), + args.output, + args.start, + args.end, + not args.windowed, + args.big, + args.title, + not args.no_metadata, + not args.no_time_overlay, + args.qcam, + ) if __name__ == "__main__": diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 92664ae5b32..67531ce90f1 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -12,7 +12,7 @@ from openpilot.tools.jotpluggler.datatree import DataTree from openpilot.tools.jotpluggler.layout import LayoutManager -DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" class WorkerManager: @@ -120,6 +120,7 @@ def remove_x_axis_observer(self, callback): if callback in self.x_axis_observers: self.x_axis_observers.remove(callback) + class MainController: def __init__(self, scale: float = 1.0): self.scale = scale @@ -197,8 +198,12 @@ def save_layout_dialog(self): if dpg.does_item_exist("save_layout_dialog"): dpg.delete_item("save_layout_dialog") with dpg.file_dialog( - callback=self._save_layout_callback, tag="save_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), - default_filename="layout", default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") + callback=self._save_layout_callback, + tag="save_layout_dialog", + width=int(700 * self.scale), + height=int(400 * self.scale), + default_filename="layout", + default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts"), ): dpg.add_file_extension(".yaml") @@ -206,8 +211,11 @@ def load_layout_dialog(self): if dpg.does_item_exist("load_layout_dialog"): dpg.delete_item("load_layout_dialog") with dpg.file_dialog( - callback=self._load_layout_callback, tag="load_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), - default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") + callback=self._load_layout_callback, + tag="load_layout_dialog", + width=int(700 * self.scale), + height=int(400 * self.scale), + default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts"), ): dpg.add_file_extension(".yaml") @@ -314,21 +322,23 @@ def main(route_to_load=None, layout_to_load=None): dpg.create_context() # TODO: find better way of calculating display scaling - #try: + # try: # w, h = next(tuple(map(int, l.split()[0].split('x'))) for l in subprocess.check_output(['xrandr']).decode().split('\n') if '*' in l) # actual resolution # scale = pyautogui.size()[0] / w # scaled resolution - #except Exception: + # except Exception: # scale = 1 scale = 1 with dpg.font_registry(): - default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale * 2)) # 2x then scale for hidpi + default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale * 2)) # 2x then scale for hidpi dpg.bind_font(default_font) dpg.set_global_font_scale(0.5) viewport_width, viewport_height = int(1200 * scale), int(800 * scale) dpg.create_viewport( - title='JotPluggler', width=viewport_width, height=viewport_height, + title='JotPluggler', + width=viewport_width, + height=viewport_height, ) dpg.setup_dearpygui() @@ -358,6 +368,7 @@ def main(route_to_load=None, layout_to_load=None): controller.shutdown() dpg.destroy_context() + if __name__ == "__main__": parser = argparse.ArgumentParser(description="A tool for visualizing openpilot logs.") parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index c04efd50b49..0cab39bc693 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -21,7 +21,7 @@ os.environ['LD_LIBRARY_PATH'] = os.environ.get('LD_LIBRARY_PATH', '') + f":{juggle_dir}/bin/" -DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" RELEASES_URL = "https://github.com/commaai/PlotJuggler/releases/download/latest" INSTALL_DIR = os.path.join(juggle_dir, "bin") PLOTJUGGLER_BIN = os.path.join(juggle_dir, "bin/plotjuggler") @@ -105,8 +105,7 @@ def juggle_route(route_or_segment_name, can, layout, dbc, should_migrate): if __name__ == "__main__": - parser = argparse.ArgumentParser(description="A helper to run PlotJuggler on openpilot routes", - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser(description="A helper to run PlotJuggler on openpilot routes", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") parser.add_argument("--can", action="store_true", help="Parse CAN data") diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 58c1b71b8a8..3e2bc7c00e8 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -12,7 +12,7 @@ #include "tools/replay/seg_mgr.h" #include "tools/replay/timeline.h" -#define DEMO_ROUTE "a2a0ccea32023010|2023-07-27--13-01-19" +#define DEMO_ROUTE "5beb9b58bd12b691/0000010a--a51155e496" enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, From d899834b63b452c4f7e87b04368bc8ee106c23d2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 27 Feb 2026 08:04:45 -0800 Subject: [PATCH 393/685] Revert "new demo route (#37456)" This reverts commit c5372e904127a44aed4fd01749b7bf5dfefa667b. --- selfdrive/debug/mem_usage.py | 9 +++-- tools/clip/run.py | 68 ++++++++---------------------------- tools/jotpluggler/pluggle.py | 29 +++++---------- tools/plotjuggler/juggle.py | 5 +-- tools/replay/replay.h | 2 +- 5 files changed, 32 insertions(+), 81 deletions(-) diff --git a/selfdrive/debug/mem_usage.py b/selfdrive/debug/mem_usage.py index 66e742f3e6b..3451bfc3d61 100755 --- a/selfdrive/debug/mem_usage.py +++ b/selfdrive/debug/mem_usage.py @@ -8,14 +8,13 @@ from openpilot.common.utils import tabulate from openpilot.tools.lib.logreader import LogReader -DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" +DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" MB = 1024 * 1024 TABULATE_OPTS = dict(tablefmt="simple_grid", stralign="center", numalign="center") def _get_procs(): from openpilot.selfdrive.test.test_onroad import PROCS - return PROCS @@ -138,9 +137,9 @@ def print_process_tables(op_procs, other_procs, total_mb, use_pss): op_rows, op_total = process_table_rows(op_procs, total_mb, use_pss, show_detail) # filter other: >5MB avg and not bare interpreter paths (test infra noise) - other_filtered = { - n: v for n, v in other_procs.items() if np.mean(v['pss' if use_pss else 'rss']) > 5.0 and os.path.basename(n.split()[0]) not in ('python', 'python3') - } + other_filtered = {n: v for n, v in other_procs.items() + if np.mean(v['pss' if use_pss else 'rss']) > 5.0 + and os.path.basename(n.split()[0]) not in ('python', 'python3')} other_rows, other_total = process_table_rows(other_filtered, total_mb, use_pss, show_detail) rows = op_rows diff --git a/tools/clip/run.py b/tools/clip/run.py index 0aa90ec0a27..5711cafa592 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -24,7 +24,7 @@ from msgq.visionipc import VisionIpcServer, VisionStreamType FRAMERATE = 20 -DEMO_ROUTE, DEMO_START, DEMO_END = '5beb9b58bd12b691/0000010a--a51155e496', 90, 105 +DEMO_ROUTE, DEMO_START, DEMO_END = 'a2a0ccea32023010/2023-07-27--13-01-19', 90, 105 logger = logging.getLogger('clip') @@ -81,7 +81,6 @@ def _download_segment(path: str) -> bytes: def _parse_and_chunk_segment(args: tuple) -> list[dict]: raw_data, fps = args from openpilot.tools.lib.logreader import _LogFileReader - messages = migrate_all(list(_LogFileReader("", dat=raw_data, sort_by_time=True))) if not messages: return [] @@ -123,7 +122,6 @@ def mock_update(timeout=None): sm.data[svc] = getattr(msg.as_builder(), svc) sm.logMonoTime[svc], sm.recv_time[svc], sm.recv_frame[svc] = msg.logMonoTime, t, sm.frame sm.frame += 1 - ui_state.sm.update = mock_update @@ -152,7 +150,8 @@ def iter_segment_frames(camera_paths, start_time, end_time, fps=20, use_qcam=Fal if use_qcam: w, h = frame_size or get_frame_dimensions(path) with FileReader(path) as f: - result = subprocess.run(["ffmpeg", "-v", "quiet", "-i", "-", "-f", "rawvideo", "-pix_fmt", "nv12", "-"], input=f.read(), capture_output=True) + result = subprocess.run(["ffmpeg", "-v", "quiet", "-i", "-", "-f", "rawvideo", "-pix_fmt", "nv12", "-"], + input=f.read(), capture_output=True) if result.returncode != 0: raise RuntimeError(f"ffmpeg failed: {result.stderr.decode()}") seg_frames = np.frombuffer(result.stdout, dtype=np.uint8).reshape(-1, w * h * 3 // 2) @@ -173,7 +172,8 @@ def __init__(self, camera_paths, start_time, end_time, fps=20, prefetch_count=60 self.frame_w, self.frame_h = get_frame_dimensions(first_path) self._queue, self._stop, self._error = queue.Queue(maxsize=prefetch_count), threading.Event(), None - self._thread = threading.Thread(target=self._worker, args=(camera_paths, start_time, end_time, fps, use_qcam, (self.frame_w, self.frame_h)), daemon=True) + self._thread = threading.Thread(target=self._worker, + args=(camera_paths, start_time, end_time, fps, use_qcam, (self.frame_w, self.frame_h)), daemon=True) self._thread.start() def _worker(self, camera_paths, start_time, end_time, fps, use_qcam, frame_size): @@ -208,7 +208,6 @@ def stop(self): def load_route_metadata(route): from openpilot.common.params import Params, UnknownKeyName - path = next((item for item in route.log_paths() if item), None) if not path: raise Exception('error getting route metadata: cannot find any uploaded logs') @@ -224,20 +223,15 @@ def load_route_metadata(route): origin = init_data.gitRemote.split('/')[3] if len(init_data.gitRemote.split('/')) > 3 else 'unknown' return { - 'version': init_data.version, - 'route': route.name.canonical_name, - 'car': car_params.carFingerprint if car_params else 'unknown', - 'origin': origin, - 'branch': init_data.gitBranch, - 'commit': init_data.gitCommit[:7], - 'modified': str(init_data.dirty).lower(), + 'version': init_data.version, 'route': route.name.canonical_name, + 'car': car_params.carFingerprint if car_params else 'unknown', 'origin': origin, + 'branch': init_data.gitBranch, 'commit': init_data.gitCommit[:7], 'modified': str(init_data.dirty).lower(), } def draw_text_box(text, x, y, size, gui_app, font, color=None, center=False): import pyray as rl from openpilot.system.ui.lib.text_measure import measure_text_cached - box_color, text_color = rl.Color(0, 0, 0, 85), color or rl.WHITE text_size = measure_text_cached(font, text, size) text_width, text_height = int(text_size.x), int(text_size.y) @@ -250,7 +244,6 @@ def draw_text_box(text, x, y, size, gui_app, font, color=None, center=False): def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, show_metadata, show_time): from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wrap_text import wrap_text - metadata_size = 16 if big else 12 title_size = 32 if big else 24 time_size = 24 if big else 16 @@ -266,17 +259,8 @@ def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, # Metadata overlay (first 5 seconds) if show_metadata and metadata and frame_idx < FRAMERATE * 5: m = metadata - text = ", ".join( - [ - f"openpilot v{m['version']}", - f"route: {m['route']}", - f"car: {m['car']}", - f"origin: {m['origin']}", - f"branch: {m['branch']}", - f"commit: {m['commit']}", - f"modified: {m['modified']}", - ] - ) + text = ", ".join([f"openpilot v{m['version']}", f"route: {m['route']}", f"car: {m['car']}", f"origin: {m['origin']}", + f"branch: {m['branch']}", f"commit: {m['commit']}", f"modified: {m['modified']}"]) # Wrap text if too wide (leave margin on each side) margin = 2 * (time_width + 10 if show_time else 20) # leave enough margin for time overlay max_width = gui_app.width - margin @@ -294,29 +278,17 @@ def render_overlays(gui_app, font, big, metadata, title, start_time, frame_idx, draw_text_box(title, 0, 60, title_size, gui_app, font, center=True) -def clip( - route: Route, - output: str, - start: int, - end: int, - headless: bool = True, - big: bool = False, - title: str | None = None, - show_metadata: bool = True, - show_time: bool = True, - use_qcam: bool = False, -): +def clip(route: Route, output: str, start: int, end: int, headless: bool = True, big: bool = False, + title: str | None = None, show_metadata: bool = True, show_time: bool = True, use_qcam: bool = False): timer, duration = Timer(), end - start import pyray as rl - if big: from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView else: from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app, FontWeight - timer.lap("import") logger.info(f"Clipping {route.name.canonical_name}, {start}s-{end}s ({duration}s)") @@ -325,7 +297,7 @@ def clip( timer.lap("logs") frame_start = (start - seg_start * 60) * FRAMERATE - message_chunks = all_chunks[frame_start : frame_start + duration * FRAMERATE] + message_chunks = all_chunks[frame_start:frame_start + duration * FRAMERATE] if not message_chunks: logger.error("No messages to render") sys.exit(1) @@ -378,18 +350,8 @@ def main(): args = parse_args() setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start) - clip( - Route(args.route, data_dir=args.data_dir), - args.output, - args.start, - args.end, - not args.windowed, - args.big, - args.title, - not args.no_metadata, - not args.no_time_overlay, - args.qcam, - ) + clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, not args.windowed, + args.big, args.title, not args.no_metadata, not args.no_time_overlay, args.qcam) if __name__ == "__main__": diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 67531ce90f1..92664ae5b32 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -12,7 +12,7 @@ from openpilot.tools.jotpluggler.datatree import DataTree from openpilot.tools.jotpluggler.layout import LayoutManager -DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" +DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" class WorkerManager: @@ -120,7 +120,6 @@ def remove_x_axis_observer(self, callback): if callback in self.x_axis_observers: self.x_axis_observers.remove(callback) - class MainController: def __init__(self, scale: float = 1.0): self.scale = scale @@ -198,12 +197,8 @@ def save_layout_dialog(self): if dpg.does_item_exist("save_layout_dialog"): dpg.delete_item("save_layout_dialog") with dpg.file_dialog( - callback=self._save_layout_callback, - tag="save_layout_dialog", - width=int(700 * self.scale), - height=int(400 * self.scale), - default_filename="layout", - default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts"), + callback=self._save_layout_callback, tag="save_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), + default_filename="layout", default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") ): dpg.add_file_extension(".yaml") @@ -211,11 +206,8 @@ def load_layout_dialog(self): if dpg.does_item_exist("load_layout_dialog"): dpg.delete_item("load_layout_dialog") with dpg.file_dialog( - callback=self._load_layout_callback, - tag="load_layout_dialog", - width=int(700 * self.scale), - height=int(400 * self.scale), - default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts"), + callback=self._load_layout_callback, tag="load_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), + default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") ): dpg.add_file_extension(".yaml") @@ -322,23 +314,21 @@ def main(route_to_load=None, layout_to_load=None): dpg.create_context() # TODO: find better way of calculating display scaling - # try: + #try: # w, h = next(tuple(map(int, l.split()[0].split('x'))) for l in subprocess.check_output(['xrandr']).decode().split('\n') if '*' in l) # actual resolution # scale = pyautogui.size()[0] / w # scaled resolution - # except Exception: + #except Exception: # scale = 1 scale = 1 with dpg.font_registry(): - default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale * 2)) # 2x then scale for hidpi + default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale * 2)) # 2x then scale for hidpi dpg.bind_font(default_font) dpg.set_global_font_scale(0.5) viewport_width, viewport_height = int(1200 * scale), int(800 * scale) dpg.create_viewport( - title='JotPluggler', - width=viewport_width, - height=viewport_height, + title='JotPluggler', width=viewport_width, height=viewport_height, ) dpg.setup_dearpygui() @@ -368,7 +358,6 @@ def main(route_to_load=None, layout_to_load=None): controller.shutdown() dpg.destroy_context() - if __name__ == "__main__": parser = argparse.ArgumentParser(description="A tool for visualizing openpilot logs.") parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index 0cab39bc693..c04efd50b49 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -21,7 +21,7 @@ os.environ['LD_LIBRARY_PATH'] = os.environ.get('LD_LIBRARY_PATH', '') + f":{juggle_dir}/bin/" -DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" +DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" RELEASES_URL = "https://github.com/commaai/PlotJuggler/releases/download/latest" INSTALL_DIR = os.path.join(juggle_dir, "bin") PLOTJUGGLER_BIN = os.path.join(juggle_dir, "bin/plotjuggler") @@ -105,7 +105,8 @@ def juggle_route(route_or_segment_name, can, layout, dbc, should_migrate): if __name__ == "__main__": - parser = argparse.ArgumentParser(description="A helper to run PlotJuggler on openpilot routes", formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser(description="A helper to run PlotJuggler on openpilot routes", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") parser.add_argument("--can", action="store_true", help="Parse CAN data") diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 3e2bc7c00e8..58c1b71b8a8 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -12,7 +12,7 @@ #include "tools/replay/seg_mgr.h" #include "tools/replay/timeline.h" -#define DEMO_ROUTE "5beb9b58bd12b691/0000010a--a51155e496" +#define DEMO_ROUTE "a2a0ccea32023010|2023-07-27--13-01-19" enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, From a1f4ba55bf55a2d5565d500d703ef0f0e58e833b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 27 Feb 2026 08:05:06 -0800 Subject: [PATCH 394/685] nicer scons output (#37455) --- SConstruct | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/SConstruct b/SConstruct index 8d58cc012af..9b16dddccdd 100644 --- a/SConstruct +++ b/SConstruct @@ -18,6 +18,7 @@ AddOption('--asan', action='store_true', help='turn on ASAN') AddOption('--ubsan', action='store_true', help='turn on UBSan') AddOption('--mutation', action='store_true', help='generate mutation-ready code') AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line') +AddOption('--verbose', action='store_true', default=False, help='show full build commands') AddOption('--minimal', action='store_false', dest='extras', @@ -148,6 +149,22 @@ if _extra_cc: if arch != "Darwin": env.Append(LINKFLAGS=["-Wl,--as-needed", "-Wl,--no-undefined"]) +# Shorter build output: show brief descriptions instead of full commands. +# Full command lines are still printed on failure by scons. +if not GetOption('verbose'): + for action, short in ( + ("CC", "CC"), + ("CXX", "CXX"), + ("LINK", "LINK"), + ("SHCC", "CC"), + ("SHCXX", "CXX"), + ("SHLINK", "LINK"), + ("AR", "AR"), + ("RANLIB", "RANLIB"), + ("AS", "AS"), + ): + env[f"{action}COMSTR"] = f" [{short}] $TARGET" + # progress output node_interval = 5 node_count = 0 From e1a4189c1fbf9cd023ac0eb259f2af40c407ce9f Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Fri, 27 Feb 2026 21:51:01 +0200 Subject: [PATCH 395/685] op.sh: add 'op script' subcommand with som-debug (#37325) * op: add som-debug command for SOM serial debug via panda * op: namespace som-debug under 'op script' subcommand --- tools/op.sh | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tools/op.sh b/tools/op.sh index 2d833d68968..7c20403a27f 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -311,6 +311,19 @@ function op_ssh() { op_run_command tools/scripts/ssh.py "$@" } +function op_script() { + op_before_cmd + + case $1 in + som-debug ) op_run_command panda/scripts/som_debug.sh "${@:2}" ;; + * ) + echo -e "Unknown script '$1'. Available scripts:" + echo -e " ${BOLD}som-debug${NC} SOM serial debug console via panda" + return 1 + ;; + esac +} + function op_check() { VERBOSE=1 op_before_cmd @@ -441,6 +454,9 @@ function op_default() { echo -e " ${BOLD}adb${NC} Run adb shell" echo -e " ${BOLD}ssh${NC} comma prime SSH helper" echo "" + echo -e "${BOLD}${UNDERLINE}Commands [Scripts]:${NC}" + echo -e " ${BOLD}script${NC} Run a script (e.g. op script som-debug)" + echo "" echo -e "${BOLD}${UNDERLINE}Commands [Testing]:${NC}" echo -e " ${BOLD}sim${NC} Run openpilot in a simulator" echo -e " ${BOLD}lint${NC} Run the linter" @@ -500,6 +516,7 @@ function _op() { post-commit ) shift 1; op_install_post_commit "$@" ;; adb ) shift 1; op_adb "$@" ;; ssh ) shift 1; op_ssh "$@" ;; + script ) shift 1; op_script "$@" ;; * ) op_default "$@" ;; esac } From 276713ddf9ee3e4fd7d2c3c0402fa0d7450547e8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 27 Feb 2026 12:10:38 -0800 Subject: [PATCH 396/685] add back bz2 support with vendored bzip2 (#37459) * add back bz2 support with vendored bzip2 Reverts f4a36f7f7 ("rm cpp bz2") to restore bzip2 decompression support in replay/cabana tools, and replaces the system libbz2-dev with a vendored bzip2 package from commaai/dependencies. Co-Authored-By: Claude Opus 4.6 * relock bzip2 from releases branch Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- SConstruct | 3 +- pyproject.toml | 1 + tools/cabana/SConscript | 2 +- tools/cabana/tests/test_cabana.cc | 2 +- tools/replay/SConscript | 2 +- tools/replay/logreader.cc | 4 +- tools/replay/route.cc | 4 +- tools/replay/tests/test_replay.cc | 4 +- tools/replay/util.cc | 42 +++++++++++ tools/replay/util.h | 2 + uv.lock | 113 ++++++++++++++++-------------- 11 files changed, 117 insertions(+), 62 deletions(-) diff --git a/SConstruct b/SConstruct index 9b16dddccdd..59ffaf4c76a 100644 --- a/SConstruct +++ b/SConstruct @@ -39,6 +39,7 @@ assert arch in [ ] if arch != "larch64": + import bzip2 import capnproto import eigen import ffmpeg as ffmpeg_pkg @@ -48,7 +49,7 @@ if arch != "larch64": import python3_dev import zeromq import zstd - pkgs = [capnproto, eigen, ffmpeg_pkg, libjpeg, ncurses, openssl3, zeromq, zstd] + pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, ncurses, openssl3, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs diff --git a/pyproject.toml b/pyproject.toml index bdcbd778019..699c3af6f06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,6 +26,7 @@ dependencies = [ "numpy >=2.0", # vendored native dependencies + "bzip2 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=bzip2", "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 89e69e7dd42..e172278d914 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -81,7 +81,7 @@ if arch == "Darwin": cabana_env['CPPPATH'] += [f"{brew_prefix}/include"] cabana_env['LIBPATH'] += [f"{brew_prefix}/lib"] -cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index 4c11bfc8b87..d9fcae6f21a 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -5,7 +5,7 @@ #include "catch2/catch.hpp" #include "tools/cabana/dbc/dbcmanager.h" -const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.zst"; +const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; TEST_CASE("DBCFile::generateDBC") { QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "tesla_can"); diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 47b25df1660..b39cf6dab16 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -12,7 +12,7 @@ if arch != "Darwin": replay_lib_src.append("qcom_decoder.cc") replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') -replay_libs = [replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs +replay_libs = [replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/replay/logreader.cc b/tools/replay/logreader.cc index 0d9e053aba5..75abb8417b5 100644 --- a/tools/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -9,7 +9,9 @@ bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { std::string data = FileReader(local_cache, chunk_size, retries).read(url, abort); if (!data.empty()) { - if (url.find(".zst") != std::string::npos || util::starts_with(data, "\x28\xB5\x2F\xFD")) { + if (url.find(".bz2") != std::string::npos || util::starts_with(data, "BZh9")) { + data = decompressBZ2(data, abort); + } else if (url.find(".zst") != std::string::npos || util::starts_with(data, "\x28\xB5\x2F\xFD")) { data = decompressZST(data, abort); } } diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 663c4b43cb1..ba008282675 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -174,9 +174,9 @@ void Route::addFileToSegment(int n, const std::string &file) { auto pos = name.find_last_of("--"); name = pos != std::string::npos ? name.substr(pos + 2) : name; - if (name == "rlog.zst" || name == "rlog") { + if (name == "rlog.bz2" || name == "rlog.zst" || name == "rlog") { segments_[n].rlog = file; - } else if (name == "qlog.zst" || name == "qlog") { + } else if (name == "qlog.bz2" || name == "qlog.zst" || name == "qlog") { segments_[n].qlog = file; } else if (name == "fcamera.hevc") { segments_[n].road_cam = file; diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index f4afc29968c..aed3de59a8c 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -2,14 +2,14 @@ #include "catch2/catch.hpp" #include "tools/replay/replay.h" -const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.zst"; +const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; TEST_CASE("LogReader") { SECTION("corrupt log") { FileReader reader(true); std::string corrupt_content = reader.read(TEST_RLOG_URL); corrupt_content.resize(corrupt_content.length() / 2); - corrupt_content = decompressZST(corrupt_content); + corrupt_content = decompressBZ2(corrupt_content); LogReader log; REQUIRE(log.load(corrupt_content.data(), corrupt_content.size())); REQUIRE(log.events.size() > 0); diff --git a/tools/replay/util.cc b/tools/replay/util.cc index cc37c19ecf7..481564322e4 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -1,5 +1,6 @@ #include "tools/replay/util.h" +#include #include #include @@ -279,6 +280,47 @@ bool httpDownload(const std::string &url, const std::string &file, size_t chunk_ return httpDownload(url, of, chunk_size, size, abort); } +std::string decompressBZ2(const std::string &in, std::atomic *abort) { + return decompressBZ2((std::byte *)in.data(), in.size(), abort); +} + +std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort) { + if (in_size == 0) return {}; + + bz_stream strm = {}; + int bzerror = BZ2_bzDecompressInit(&strm, 0, 0); + assert(bzerror == BZ_OK); + + strm.next_in = (char *)in; + strm.avail_in = in_size; + std::string out(in_size * 5, '\0'); + do { + strm.next_out = (char *)(&out[strm.total_out_lo32]); + strm.avail_out = out.size() - strm.total_out_lo32; + + const char *prev_write_pos = strm.next_out; + bzerror = BZ2_bzDecompress(&strm); + if (bzerror == BZ_OK && prev_write_pos == strm.next_out) { + // content is corrupt + bzerror = BZ_STREAM_END; + rWarning("decompressBZ2 error: content is corrupt"); + break; + } + + if (bzerror == BZ_OK && strm.avail_in > 0 && strm.avail_out == 0) { + out.resize(out.size() * 2); + } + } while (bzerror == BZ_OK && !(abort && *abort)); + + BZ2_bzDecompressEnd(&strm); + if (bzerror == BZ_STREAM_END && !(abort && *abort)) { + out.resize(strm.total_out_lo32); + out.shrink_to_fit(); + return out; + } + return {}; +} + std::string decompressZST(const std::string &in, std::atomic *abort) { return decompressZST((std::byte *)in.data(), in.size(), abort); } diff --git a/tools/replay/util.h b/tools/replay/util.h index fc4d2d54f95..1f61951d213 100644 --- a/tools/replay/util.h +++ b/tools/replay/util.h @@ -48,6 +48,8 @@ class MonotonicBuffer { std::string sha256(const std::string &str); void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_requested); +std::string decompressBZ2(const std::string &in, std::atomic *abort = nullptr); +std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); std::string decompressZST(const std::string &in, std::atomic *abort = nullptr); std::string decompressZST(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); std::string getUrlWithoutQuery(const std::string &url); diff --git a/uv.lock b/uv.lock index 90944176939..63c8c4cffa9 100644 --- a/uv.lock +++ b/uv.lock @@ -113,10 +113,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c8/b9/275df9607f7fb44317ccb1d4be74827185c0d410f52b6e2cd770fe209118/av-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:f49243b1d27c91cd8c66fdba90a674e344eb8eb917264f36117bf2b6879118fd", size = 31752045, upload-time = "2026-01-11T09:57:45.106Z" }, ] +[[package]] +name = "bzip2" +version = "1.0.8" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } + [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "casadi" @@ -137,11 +142,11 @@ wheels = [ [[package]] name = "certifi" -version = "2026.1.4" +version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, ] [[package]] @@ -376,7 +381,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "execnet" @@ -390,7 +395,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "fonttools" @@ -437,7 +442,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "ghp-import" @@ -454,7 +459,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "google-crc32c" @@ -572,7 +577,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "libusb1" @@ -735,7 +740,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "numpy" @@ -782,6 +787,7 @@ dependencies = [ { name = "aiohttp" }, { name = "aiortc" }, { name = "av" }, + { name = "bzip2" }, { name = "capnproto" }, { name = "casadi" }, { name = "cffi" }, @@ -857,6 +863,7 @@ requires-dist = [ { name = "aiohttp" }, { name = "aiortc" }, { name = "av" }, + { name = "bzip2", git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases" }, { name = "capnproto", git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases" }, { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, @@ -921,7 +928,7 @@ provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "openssl3" version = "3.4.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "packaging" @@ -1292,7 +1299,7 @@ wheels = [ [[package]] name = "python3-dev" version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "pyyaml" @@ -1401,27 +1408,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/06/04/eab13a954e763b0606f460443fcbf6bb5a0faf06890ea3754ff16523dce5/ruff-0.15.2.tar.gz", hash = "sha256:14b965afee0969e68bb871eba625343b8673375f457af4abe98553e8bbb98342", size = 4558148, upload-time = "2026-02-19T22:32:20.271Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/70/3a4dc6d09b13cb3e695f28307e5d889b2e1a66b7af9c5e257e796695b0e6/ruff-0.15.2-py3-none-linux_armv6l.whl", hash = "sha256:120691a6fdae2f16d65435648160f5b81a9625288f75544dc40637436b5d3c0d", size = 10430565, upload-time = "2026-02-19T22:32:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/71/0b/bb8457b56185ece1305c666dc895832946d24055be90692381c31d57466d/ruff-0.15.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:a89056d831256099658b6bba4037ac6dd06f49d194199215befe2bb10457ea5e", size = 10820354, upload-time = "2026-02-19T22:32:07.366Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c1/e0532d7f9c9e0b14c46f61b14afd563298b8b83f337b6789ddd987e46121/ruff-0.15.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e36dee3a64be0ebd23c86ffa3aa3fd3ac9a712ff295e192243f814a830b6bd87", size = 10170767, upload-time = "2026-02-19T22:32:13.188Z" }, - { url = "https://files.pythonhosted.org/packages/47/e8/da1aa341d3af017a21c7a62fb5ec31d4e7ad0a93ab80e3a508316efbcb23/ruff-0.15.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9fb47b6d9764677f8c0a193c0943ce9a05d6763523f132325af8a858eadc2b9", size = 10529591, upload-time = "2026-02-19T22:32:02.547Z" }, - { url = "https://files.pythonhosted.org/packages/93/74/184fbf38e9f3510231fbc5e437e808f0b48c42d1df9434b208821efcd8d6/ruff-0.15.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f376990f9d0d6442ea9014b19621d8f2aaf2b8e39fdbfc79220b7f0c596c9b80", size = 10260771, upload-time = "2026-02-19T22:32:36.938Z" }, - { url = "https://files.pythonhosted.org/packages/05/ac/605c20b8e059a0bc4b42360414baa4892ff278cec1c91fff4be0dceedefd/ruff-0.15.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2dcc987551952d73cbf5c88d9fdee815618d497e4df86cd4c4824cc59d5dd75f", size = 11045791, upload-time = "2026-02-19T22:32:31.642Z" }, - { url = "https://files.pythonhosted.org/packages/fd/52/db6e419908f45a894924d410ac77d64bdd98ff86901d833364251bd08e22/ruff-0.15.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:42a47fd785cbe8c01b9ff45031af875d101b040ad8f4de7bbb716487c74c9a77", size = 11879271, upload-time = "2026-02-19T22:32:29.305Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d8/7992b18f2008bdc9231d0f10b16df7dda964dbf639e2b8b4c1b4e91b83af/ruff-0.15.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cbe9f49354866e575b4c6943856989f966421870e85cd2ac94dccb0a9dcb2fea", size = 11303707, upload-time = "2026-02-19T22:32:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/d7/02/849b46184bcfdd4b64cde61752cc9a146c54759ed036edd11857e9b8443b/ruff-0.15.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7a672c82b5f9887576087d97be5ce439f04bbaf548ee987b92d3a7dede41d3a", size = 11149151, upload-time = "2026-02-19T22:32:44.234Z" }, - { url = "https://files.pythonhosted.org/packages/70/04/f5284e388bab60d1d3b99614a5a9aeb03e0f333847e2429bebd2aaa1feec/ruff-0.15.2-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:72ecc64f46f7019e2bcc3cdc05d4a7da958b629a5ab7033195e11a438403d956", size = 11091132, upload-time = "2026-02-19T22:32:24.691Z" }, - { url = "https://files.pythonhosted.org/packages/fa/ae/88d844a21110e14d92cf73d57363fab59b727ebeabe78009b9ccb23500af/ruff-0.15.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:8dcf243b15b561c655c1ef2f2b0050e5d50db37fe90115507f6ff37d865dc8b4", size = 10504717, upload-time = "2026-02-19T22:32:26.75Z" }, - { url = "https://files.pythonhosted.org/packages/64/27/867076a6ada7f2b9c8292884ab44d08fd2ba71bd2b5364d4136f3cd537e1/ruff-0.15.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:dab6941c862c05739774677c6273166d2510d254dac0695c0e3f5efa1b5585de", size = 10263122, upload-time = "2026-02-19T22:32:10.036Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ef/faf9321d550f8ebf0c6373696e70d1758e20ccdc3951ad7af00c0956be7c/ruff-0.15.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b9164f57fc36058e9a6806eb92af185b0697c9fe4c7c52caa431c6554521e5c", size = 10735295, upload-time = "2026-02-19T22:32:39.227Z" }, - { url = "https://files.pythonhosted.org/packages/2f/55/e8089fec62e050ba84d71b70e7834b97709ca9b7aba10c1a0b196e493f97/ruff-0.15.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:80d24fcae24d42659db7e335b9e1531697a7102c19185b8dc4a028b952865fd8", size = 11241641, upload-time = "2026-02-19T22:32:34.617Z" }, - { url = "https://files.pythonhosted.org/packages/23/01/1c30526460f4d23222d0fabd5888868262fd0e2b71a00570ca26483cd993/ruff-0.15.2-py3-none-win32.whl", hash = "sha256:fd5ff9e5f519a7e1bd99cbe8daa324010a74f5e2ebc97c6242c08f26f3714f6f", size = 10507885, upload-time = "2026-02-19T22:32:15.635Z" }, - { url = "https://files.pythonhosted.org/packages/5c/10/3d18e3bbdf8fc50bbb4ac3cc45970aa5a9753c5cb51bf9ed9a3cd8b79fa3/ruff-0.15.2-py3-none-win_amd64.whl", hash = "sha256:d20014e3dfa400f3ff84830dfb5755ece2de45ab62ecea4af6b7262d0fb4f7c5", size = 11623725, upload-time = "2026-02-19T22:32:04.947Z" }, - { url = "https://files.pythonhosted.org/packages/6d/78/097c0798b1dab9f8affe73da9642bb4500e098cb27fd8dc9724816ac747b/ruff-0.15.2-py3-none-win_arm64.whl", hash = "sha256:cabddc5822acdc8f7b5527b36ceac55cc51eec7b1946e60181de8fe83ca8876e", size = 10941649, upload-time = "2026-02-19T22:32:18.108Z" }, +version = "0.15.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, + { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, + { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, + { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, + { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, + { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, + { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, + { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, + { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, + { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, + { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, + { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, ] [[package]] @@ -1539,26 +1546,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.18" +version = "0.0.19" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/74/15/9682700d8d60fdca7afa4febc83a2354b29cdcd56e66e19c92b521db3b39/ty-0.0.18.tar.gz", hash = "sha256:04ab7c3db5dcbcdac6ce62e48940d3a0124f377c05499d3f3e004e264ae94b83", size = 5214774, upload-time = "2026-02-20T21:51:31.173Z" } +sdist = { url = "https://files.pythonhosted.org/packages/84/5e/da108b9eeb392e02ff0478a34e9651490b36af295881cb56575b83f0cc3a/ty-0.0.19.tar.gz", hash = "sha256:ee3d9ed4cb586e77f6efe3d0fe5a855673ca438a3d533a27598e1d3502a2948a", size = 5220026, upload-time = "2026-02-26T12:13:15.215Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ae/d8/920460d4c22ea68fcdeb0b2fb53ea2aeb9c6d7875bde9278d84f2ac767b6/ty-0.0.18-py3-none-linux_armv6l.whl", hash = "sha256:4e5e91b0a79857316ef893c5068afc4b9872f9d257627d9bc8ac4d2715750d88", size = 10280825, upload-time = "2026-02-20T21:51:25.03Z" }, - { url = "https://files.pythonhosted.org/packages/83/56/62587de582d3d20d78fcdddd0594a73822ac5a399a12ef512085eb7a4de6/ty-0.0.18-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ee0e578b3f8416e2d5416da9553b78fd33857868aa1384cb7fefeceee5ff102d", size = 10118324, upload-time = "2026-02-20T21:51:22.27Z" }, - { url = "https://files.pythonhosted.org/packages/2f/2d/dbdace8d432a0755a7417f659bfd5b8a4261938ecbdfd7b42f4c454f5aa9/ty-0.0.18-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3f7a0487d36b939546a91d141f7fc3dbea32fab4982f618d5b04dc9d5b6da21e", size = 9605861, upload-time = "2026-02-20T21:51:16.066Z" }, - { url = "https://files.pythonhosted.org/packages/6b/d9/de11c0280f778d5fc571393aada7fe9b8bc1dd6a738f2e2c45702b8b3150/ty-0.0.18-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5e2fa8d45f57ca487a470e4bf66319c09b561150e98ae2a6b1a97ef04c1a4eb", size = 10092701, upload-time = "2026-02-20T21:51:26.862Z" }, - { url = "https://files.pythonhosted.org/packages/0f/94/068d4d591d791041732171e7b63c37a54494b2e7d28e88d2167eaa9ad875/ty-0.0.18-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d75652e9e937f7044b1aca16091193e7ef11dac1c7ec952b7fb8292b7ba1f5f2", size = 10109203, upload-time = "2026-02-20T21:51:11.59Z" }, - { url = "https://files.pythonhosted.org/packages/34/e4/526a4aa56dc0ca2569aaa16880a1ab105c3b416dd70e87e25a05688999f3/ty-0.0.18-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:563c868edceb8f6ddd5e91113c17d3676b028f0ed380bdb3829b06d9beb90e58", size = 10614200, upload-time = "2026-02-20T21:51:20.298Z" }, - { url = "https://files.pythonhosted.org/packages/fd/3d/b68ab20a34122a395880922587fbfc3adf090d22e0fb546d4d20fe8c2621/ty-0.0.18-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:502e2a1f948bec563a0454fc25b074bf5cf041744adba8794d024277e151d3b0", size = 11153232, upload-time = "2026-02-20T21:51:14.121Z" }, - { url = "https://files.pythonhosted.org/packages/68/ea/678243c042343fcda7e6af36036c18676c355878dcdcd517639586d2cf9e/ty-0.0.18-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc881dea97021a3aa29134a476937fd8054775c4177d01b94db27fcfb7aab65b", size = 10832934, upload-time = "2026-02-20T21:51:32.92Z" }, - { url = "https://files.pythonhosted.org/packages/d8/bd/7f8d647cef8b7b346c0163230a37e903c7461c7248574840b977045c77df/ty-0.0.18-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:421fcc3bc64cab56f48edb863c7c1c43649ec4d78ff71a1acb5366ad723b6021", size = 10700888, upload-time = "2026-02-20T21:51:09.673Z" }, - { url = "https://files.pythonhosted.org/packages/6e/06/cb3620dc48c5d335ba7876edfef636b2f4498eff4a262ff90033b9e88408/ty-0.0.18-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0fe5038a7136a0e638a2fb1ad06e3d3c4045314c6ba165c9c303b9aeb4623d6c", size = 10078965, upload-time = "2026-02-20T21:51:07.678Z" }, - { url = "https://files.pythonhosted.org/packages/60/27/c77a5a84533fa3b685d592de7b4b108eb1f38851c40fac4e79cc56ec7350/ty-0.0.18-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d123600a52372677613a719bbb780adeb9b68f47fb5f25acb09171de390e0035", size = 10134659, upload-time = "2026-02-20T21:51:18.311Z" }, - { url = "https://files.pythonhosted.org/packages/43/6e/60af6b88c73469e628ba5253a296da6984e0aa746206f3034c31f1a04ed1/ty-0.0.18-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bb4bc11d32a1bf96a829bf6b9696545a30a196ac77bbc07cc8d3dfee35e03723", size = 10297494, upload-time = "2026-02-20T21:51:39.631Z" }, - { url = "https://files.pythonhosted.org/packages/33/90/612dc0b68224c723faed6adac2bd3f930a750685db76dfe17e6b9e534a83/ty-0.0.18-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dda2efbf374ba4cd704053d04e32f2f784e85c2ddc2400006b0f96f5f7e4b667", size = 10791944, upload-time = "2026-02-20T21:51:37.13Z" }, - { url = "https://files.pythonhosted.org/packages/0d/da/f4ada0fd08a9e4138fe3fd2bcd3797753593f423f19b1634a814b9b2a401/ty-0.0.18-py3-none-win32.whl", hash = "sha256:c5768607c94977dacddc2f459ace6a11a408a0f57888dd59abb62d28d4fee4f7", size = 9677964, upload-time = "2026-02-20T21:51:42.039Z" }, - { url = "https://files.pythonhosted.org/packages/5e/fa/090ed9746e5c59fc26d8f5f96dc8441825171f1f47752f1778dad690b08b/ty-0.0.18-py3-none-win_amd64.whl", hash = "sha256:b78d0fa1103d36fc2fce92f2092adace52a74654ab7884d54cdaec8eb5016a4d", size = 10636576, upload-time = "2026-02-20T21:51:29.159Z" }, - { url = "https://files.pythonhosted.org/packages/92/4f/5dd60904c8105cda4d0be34d3a446c180933c76b84ae0742e58f02133713/ty-0.0.18-py3-none-win_arm64.whl", hash = "sha256:01770c3c82137c6b216aa3251478f0b197e181054ee92243772de553d3586398", size = 10095449, upload-time = "2026-02-20T21:51:34.914Z" }, + { url = "https://files.pythonhosted.org/packages/5a/31/fd8c6067abb275bea11523d21ecf64e1d870b1ce80cac529cf6636df1471/ty-0.0.19-py3-none-linux_armv6l.whl", hash = "sha256:29bed05d34c8a7597567b8e327c53c1aed4a07dcfbe6c81e6d60c7444936ad77", size = 10268470, upload-time = "2026-02-26T12:13:42.881Z" }, + { url = "https://files.pythonhosted.org/packages/15/de/16a11bbf7d98c75849fc41f5d008b89bb5d080a4b10dc8ea851ee2bd371b/ty-0.0.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79140870c688c97ec68e723c28935ddef9d91a76d48c68e665fe7c851e628b8a", size = 10098562, upload-time = "2026-02-26T12:13:31.618Z" }, + { url = "https://files.pythonhosted.org/packages/e7/4f/086d6ff6686eadf903913c45b53ab96694b62bbfee1d8cf3e55a9b5aa4b2/ty-0.0.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6e9c1f9cfa6a26f7881d14d75cf963af743f6c4189e6aa3e3b4056a65f22e730", size = 9604073, upload-time = "2026-02-26T12:13:24.645Z" }, + { url = "https://files.pythonhosted.org/packages/95/13/888a6b6c7ed4a880fee91bec997f775153ce86215ee4c56b868516314734/ty-0.0.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbca43b050edf1db2e64ae7b79add233c2aea2855b8a876081bbd032edcd0610", size = 10106295, upload-time = "2026-02-26T12:13:40.584Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e8/05a372cae8da482de73b8246fb43236bf11e24ac28c879804568108759db/ty-0.0.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8acaa88ab1955ca6b15a0ccc274011c4961377fe65c3948e5d2b212f2517b87c", size = 10098234, upload-time = "2026-02-26T12:13:33.725Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f1/5b0958e9e9576e7662192fe689bbb3dc88e631a4e073db3047793a547d58/ty-0.0.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a901b6a6dd9d17d5b3b2e7bafc3057294e88da3f5de507347316687d7f191a1", size = 10607218, upload-time = "2026-02-26T12:13:17.576Z" }, + { url = "https://files.pythonhosted.org/packages/fb/ab/358c78b77844f58ff5aca368550ab16c719f1ab0ec892ceb1114d7500f4e/ty-0.0.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8deafdaaaee65fd121c66064da74a922d8501be4a2d50049c71eab521a23eff7", size = 11160593, upload-time = "2026-02-26T12:13:36.008Z" }, + { url = "https://files.pythonhosted.org/packages/95/59/827fc346d66a59fe48e9689a5ceb67dbbd5b4de2e8d4625371af39a2e8b7/ty-0.0.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e56071af280897441018f74f921b97d53aec0856f8af85f4f949df8eda07d", size = 10822392, upload-time = "2026-02-26T12:13:29.415Z" }, + { url = "https://files.pythonhosted.org/packages/81/f9/3bbfbbe35478de9bcd63848f4bc9bffda72278dd9732dbad3efc3978432e/ty-0.0.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdf5885130393ce74501dba792f48ce0a515756ec81c33a4b324bdf3509df6e", size = 10707139, upload-time = "2026-02-26T12:13:20.148Z" }, + { url = "https://files.pythonhosted.org/packages/12/9e/597023b183ec4ade83a36a0cea5c103f3bffa34f70813d46386c61447fb8/ty-0.0.19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:877e89005c8f9d1dbff5ad14cbac9f35c528406fde38926f9b44f24830de8d6a", size = 10096933, upload-time = "2026-02-26T12:13:45.266Z" }, + { url = "https://files.pythonhosted.org/packages/1e/76/d0d2f6e674db2a17c8efa5e26682b9dfa8d34774705f35902a7b45ebd3bd/ty-0.0.19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:39bd1da051c1e4d316efaf79dbed313255633f7c6ad6e24d29f4d9c6ffaf4de6", size = 10109547, upload-time = "2026-02-26T12:13:22.17Z" }, + { url = "https://files.pythonhosted.org/packages/a4/b0/76026c06b852a3aa4fdb5bd329fdc2175aaf3c64a3fafece9cc4df167cee/ty-0.0.19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87df8415a6c9cb27b8f1382fcdc6052e59f5b9f50f78bc14663197eb5c8d3699", size = 10289110, upload-time = "2026-02-26T12:13:38.29Z" }, + { url = "https://files.pythonhosted.org/packages/14/6c/f3b3a189816b4f079b20fe5d0d7ee38e38a472f53cc6770bb6571147e3de/ty-0.0.19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:89b6bb23c332ed5c38dd859eb5793f887abcc936f681a40d4ea68e35eac1af33", size = 10796479, upload-time = "2026-02-26T12:13:10.992Z" }, + { url = "https://files.pythonhosted.org/packages/3d/18/caee33d1ce9dd50bd94c26cde7cda4f6971e22e474e7d72a5c86d745ad58/ty-0.0.19-py3-none-win32.whl", hash = "sha256:19b33df3aa7af7b1a9eaa4e1175c3b4dec0f5f2e140243e3492c8355c37418f3", size = 9677215, upload-time = "2026-02-26T12:13:08.519Z" }, + { url = "https://files.pythonhosted.org/packages/81/41/18fc0771d0b1da7d7cc2fc9af278d3122b754fe8b521a748734f4e16ecfd/ty-0.0.19-py3-none-win_amd64.whl", hash = "sha256:b9052c61464cdd76bc8e6796f2588c08700f25d0dcbc225bb165e390ea9d96a4", size = 10651252, upload-time = "2026-02-26T12:13:13.035Z" }, + { url = "https://files.pythonhosted.org/packages/8b/8c/26f7ce8863eb54510082747b3dfb1046ba24f16fc11de18c0e5feb36ff18/ty-0.0.19-py3-none-win_arm64.whl", hash = "sha256:9329804b66dcbae8e7af916ef4963221ed53b8ec7d09b0793591c5ae8a0f3270", size = 10093195, upload-time = "2026-02-26T12:13:26.816Z" }, ] [[package]] @@ -1660,7 +1667,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } [[package]] name = "zstandard" @@ -1690,4 +1697,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#31af284805d0787a689e129311d992bec14a2400" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } From 010a32bb9bbf0889cdda27e157c4d17ce76b2827 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 27 Feb 2026 14:56:01 -0800 Subject: [PATCH 397/685] WifiUi: single source for forget btn visible (#37450) single --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 2fbe23c1915..a73a397d1ac 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -129,12 +129,10 @@ def _forget_network(self): return self._network_forgetting = True - self._forget_btn.set_visible(False) self._wifi_manager.forget_connection(self._network.ssid) def on_forgotten(self): self._network_forgetting = False - self._forget_btn.set_visible(True) def set_network_missing(self, missing: bool): self._network_missing = missing @@ -150,7 +148,7 @@ def network(self) -> Network: @property def _show_forget_btn(self): - if self._network.is_tethering: + if self._network.is_tethering or self._network_forgetting: return False return (self._is_saved and not self._wrong_password) or self._is_connecting From cc21fd3ac347e9e0e8c04b3e5b685adad26447a6 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 27 Feb 2026 15:04:37 -0800 Subject: [PATCH 398/685] ci: remove weekly eval jobs --- .github/workflows/ci_weekly_report.yaml | 101 ------------------------ .github/workflows/ci_weekly_run.yaml | 17 ---- 2 files changed, 118 deletions(-) delete mode 100644 .github/workflows/ci_weekly_report.yaml delete mode 100644 .github/workflows/ci_weekly_run.yaml diff --git a/.github/workflows/ci_weekly_report.yaml b/.github/workflows/ci_weekly_report.yaml deleted file mode 100644 index c7f5ec34f0b..00000000000 --- a/.github/workflows/ci_weekly_report.yaml +++ /dev/null @@ -1,101 +0,0 @@ -name: weekly CI test report -on: - schedule: - - cron: '37 9 * * 1' # 9:37AM UTC -> 2:37AM PST every monday - workflow_dispatch: - inputs: - ci_runs: - description: 'The amount of runs to trigger in CI test report' -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - CI_RUNS: ${{ github.event.inputs.ci_runs || '50' }} - -jobs: - setup: - if: github.repository == 'commaai/openpilot' - runs-on: ubuntu-latest - outputs: - ci_runs: ${{ steps.ci_runs_setup.outputs.matrix }} - steps: - - id: ci_runs_setup - name: CI_RUNS=${{ env.CI_RUNS }} - run: | - matrix=$(python3 -c "import json; print(json.dumps({ 'run_number' : list(range(${{ env.CI_RUNS }})) }))") - echo "matrix=$matrix" >> $GITHUB_OUTPUT - - ci_matrix_run: - needs: [ setup ] - strategy: - fail-fast: false - matrix: ${{fromJSON(needs.setup.outputs.ci_runs)}} - uses: commaai/openpilot/.github/workflows/ci_weekly_run.yaml@master - with: - run_number: ${{ matrix.run_number }} - - report: - needs: [ci_matrix_run] - runs-on: ubuntu-latest - if: always() && github.repository == 'commaai/openpilot' - steps: - - name: Get job results - uses: actions/github-script@v8 - id: get-job-results - with: - script: | - const jobs = await github - .paginate("GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt}/jobs", { - owner: "commaai", - repo: "${{ github.event.repository.name }}", - run_id: "${{ github.run_id }}", - attempt: "${{ github.run_attempt }}", - }) - var report = {} - jobs.slice(1, jobs.length-1).forEach(job => { - if (job.conclusion === "skipped") return; - const jobName = job.name.split(" / ")[2]; - const runRegex = /\((.*?)\)/; - const run = job.name.match(runRegex)[1]; - report[jobName] = report[jobName] || { successes: [], failures: [], canceled: [] }; - switch (job.conclusion) { - case "success": - report[jobName].successes.push({ "run_number": run, "link": job.html_url}); break; - case "failure": - report[jobName].failures.push({ "run_number": run, "link": job.html_url }); break; - case "canceled": - report[jobName].canceled.push({ "run_number": run, "link": job.html_url }); break; - } - }); - return JSON.stringify({"jobs": report}); - - - name: Add job results to summary - env: - JOB_RESULTS: ${{ fromJSON(steps.get-job-results.outputs.result) }} - run: | - cat <> template.html - - - - - - - - - - - {% for key in jobs.keys() %} - - - - - - {% endfor %} -
Job✅ Passing❌ Failure Details
{% for i in range(5) %}{% if i+1 <= (5 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }}) %}🟩{% else %}🟥{% endif %}{% endfor%}{{ key }}{{ 100 * jobs[key]["successes"]|length // ${{ env.CI_RUNS }} }}%{% if jobs[key]["failures"]|length > 0 %}
{% for failure in jobs[key]["failures"] %}Log for run #{{ failure['run_number'] }}
{% endfor %}
{% else %}{% endif %}
- EOF - - pip install jinja2-cli - echo $JOB_RESULTS | jinja2 template.html > report.html - echo "# CI Test Report - ${{ env.CI_RUNS }} Runs" >> $GITHUB_STEP_SUMMARY - cat report.html >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/ci_weekly_run.yaml b/.github/workflows/ci_weekly_run.yaml deleted file mode 100644 index acd24de1639..00000000000 --- a/.github/workflows/ci_weekly_run.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: weekly CI test run -on: - workflow_call: - inputs: - run_number: - required: true - type: string - -concurrency: - group: ci-run-${{ inputs.run_number }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - tests: - uses: commaai/openpilot/.github/workflows/tests.yaml@master - with: - run_number: ${{ inputs.run_number }} From 6e8f32502412245b8e8963c6bec1b04cea43c512 Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Fri, 27 Feb 2026 15:05:01 -0800 Subject: [PATCH 399/685] Fix mic clipping on comma four (#37461) * 6dB reduction on four * wrong submodule --- panda | 2 +- selfdrive/ui/soundd.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/panda b/panda index 3ffe9591a73..d1410f7f7b0 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 3ffe9591a7305c71f67a70355f8098c9b5d2a611 +Subproject commit d1410f7f7b061171c3702d84d975a3da3afce109 diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index d88410ada35..6a203d3afcb 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -21,11 +21,12 @@ SELFDRIVE_STATE_TIMEOUT = 5 # 5 seconds FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES) -AMBIENT_DB = 30 # DB where MIN_VOLUME is applied +AMBIENT_DB = 24 # DB where MIN_VOLUME is applied DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied VOLUME_BASE = 20 if HARDWARE.get_device_type() == "tizi": + AMBIENT_DB = 30 VOLUME_BASE = 10 AudibleAlert = car.CarControl.HUDControl.AudibleAlert From 1b17bf40cd9a2af58415b03913b999080fd19c83 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 27 Feb 2026 15:47:54 -0800 Subject: [PATCH 400/685] Revert "UI: only show `onroad_fade.png` when engaged" (#37466) Revert "UI: only show `onroad_fade.png` when engaged (#37051)" This reverts commit 39dcc883301f47c4061e3206cba6d5d50ab72778. --- selfdrive/ui/mici/onroad/augmented_road_view.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 99e33e8644d..dbd2bae886f 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -14,7 +14,7 @@ from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets import Widget -from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter +from openpilot.common.filter_simple import BounceFilter from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCameraConfig, view_frame_from_device_frame from openpilot.common.transformations.orientation import rot_from_euler from enum import IntEnum @@ -165,7 +165,6 @@ def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = Visio alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png") - self._fade_alpha_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) # debug self._pm = messaging.PubMaster(['uiDebug']) @@ -218,11 +217,8 @@ def _render(self, _): # Draw all UI overlays self._model_renderer.render(self._content_rect) - # Fade out bottom of overlays for looks (only when engaged) - fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) - if fade_alpha > 1e-2: - rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, - rl.Color(255, 255, 255, int(255 * fade_alpha))) + # Fade out bottom of overlays for looks + rl.draw_texture_ex(self._fade_texture, rl.Vector2(self._content_rect.x, self._content_rect.y), 0.0, 1.0, rl.WHITE) alert_to_render, not_animating_out = self._alert_renderer.will_render() From 3a958b882ab6e13948e0375406f9d65282a81627 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 27 Feb 2026 15:47:56 -0800 Subject: [PATCH 401/685] Revert "onroad: fill bookmark icon when activated" (#37465) Revert "onroad: fill bookmark icon when activated (#37034)" This reverts commit 0b958f7c9ae682e0ab95d0dc9f45f605be0dfce0. --- selfdrive/ui/mici/onroad/augmented_road_view.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index dbd2bae886f..92874068219 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -46,8 +46,6 @@ def __init__(self, bookmark_callback): super().__init__() self._bookmark_callback = bookmark_callback self._icon = gui_app.texture("icons_mici/onroad/bookmark.png", 180, 180) - self._icon_fill = gui_app.texture("icons_mici/onroad/bookmark_fill.png", 180, 180) - self._active_icon = self._icon self._offset_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps) # State @@ -86,7 +84,6 @@ def _update_state(self): if self._offset_filter.x < 1e-3: self._interacting = False - self._active_icon = self._icon def _handle_mouse_event(self, mouse_event: MouseEvent): if not ui_state.started: @@ -99,7 +96,6 @@ def _handle_mouse_event(self, mouse_event: MouseEvent): self._is_swiping = True self._is_swiping_left = False self._state = BookmarkState.DRAGGING - self._active_icon = self._icon elif mouse_event.left_down and self._is_swiping: self._swipe_current_x = mouse_event.pos.x @@ -116,7 +112,6 @@ def _handle_mouse_event(self, mouse_event: MouseEvent): if swipe_distance > self.PEEK_THRESHOLD: self._state = BookmarkState.TRIGGERED self._triggered_time = rl.get_time() - self._active_icon = self._icon_fill self._bookmark_callback() else: # Otherwise, transition back to hidden @@ -130,8 +125,8 @@ def _render(self, _): """Render the bookmark icon.""" if self._offset_filter.x > 0: icon_x = self.rect.x + self.rect.width - round(self._offset_filter.x) - icon_y = self.rect.y + (self.rect.height - self._active_icon.height) / 2 # Vertically centered - rl.draw_texture(self._active_icon, int(icon_x), int(icon_y), rl.WHITE) + icon_y = self.rect.y + (self.rect.height - self._icon.height) / 2 # Vertically centered + rl.draw_texture(self._icon, int(icon_x), int(icon_y), rl.WHITE) class AugmentedRoadView(CameraView): From 2e42bf9fa4ac298ce84de7bac3b5449933892d5f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 27 Feb 2026 18:32:28 -0800 Subject: [PATCH 402/685] mici ui: fix onroad transitions if in settings (#37467) * fix * type --- selfdrive/ui/mici/layouts/main.py | 3 +-- .../ui/mici/layouts/settings/network/__init__.py | 4 ++-- system/ui/lib/application.py | 15 ++++++++++----- system/ui/mici_setup.py | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index e39a228daf5..1a0c6bc0942 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -49,6 +49,7 @@ def __init__(self): # Set callbacks self._setup_callbacks() + gui_app.add_nav_stack_tick(self._handle_transitions) gui_app.push_widget(self) # Start onboarding if terms or training not completed, make sure to push after self @@ -76,8 +77,6 @@ def _render(self, _): # Render super()._render(self._rect) - self._handle_transitions() - def _handle_transitions(self): # Don't pop if onboarding if gui_app.get_active_widget() == self._onboarding_window: diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index ce3f1a817ee..ae7d085e01d 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -166,13 +166,13 @@ def show_event(self): self._wifi_manager.set_active(True) # Process wifi callbacks while at any point in the nav stack - gui_app.set_nav_stack_tick(self._wifi_manager.process_callbacks) + gui_app.add_nav_stack_tick(self._wifi_manager.process_callbacks) def hide_event(self): super().hide_event() self._wifi_manager.set_active(False) - gui_app.set_nav_stack_tick(None) + gui_app.remove_nav_stack_tick(self._wifi_manager.process_callbacks) def _toggle_roaming(self, checked: bool): self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 1640b0d0774..34aed4f6a6d 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -220,7 +220,7 @@ def __init__(self, width: int | None = None, height: int | None = None): self._frame = 0 self._window_close_requested = False self._nav_stack: list[object] = [] - self._nav_stack_tick: Callable[[], None] | None = None + self._nav_stack_ticks: list[Callable[[], None]] = [] self._nav_stack_widgets_to_render = 1 if self.big_ui() else 2 self._mouse = MouseState(self._scale) @@ -411,8 +411,13 @@ def get_active_widget(self): return self._nav_stack[-1] return None - def set_nav_stack_tick(self, tick_function: Callable | None): - self._nav_stack_tick = tick_function + def add_nav_stack_tick(self, tick_function: Callable[[], None]): + if tick_function not in self._nav_stack_ticks: + self._nav_stack_ticks.append(tick_function) + + def remove_nav_stack_tick(self, tick_function: Callable[[], None]): + if tick_function in self._nav_stack_ticks: + self._nav_stack_ticks.remove(tick_function) def set_should_render(self, should_render: bool): self._should_render = should_render @@ -561,8 +566,8 @@ def render(self): rl.clear_background(rl.BLACK) # Allow a Widget to still run a function regardless of the stack depth - if self._nav_stack_tick is not None: - self._nav_stack_tick() + for tick in self._nav_stack_ticks: + tick() # Only render top widgets for widget in self._nav_stack[-self._nav_stack_widgets_to_render:]: diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 6c73f14e3ac..ca635444392 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -508,7 +508,7 @@ def __init__(self): self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() self._prev_has_internet = False - gui_app.set_nav_stack_tick(self._nav_stack_tick) + gui_app.add_nav_stack_tick(self._nav_stack_tick) self._start_page = StartPage() self._start_page.set_click_callback(self._getting_started_button_callback) From 10f3f5680102a5a1310666db6e0aa411a2fd59cb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 27 Feb 2026 20:20:50 -0800 Subject: [PATCH 403/685] mici ui: get version from build metadata (#37470) * get version from build * fix test --- selfdrive/ui/mici/layouts/home.py | 28 +++++++++++++++++----------- selfdrive/ui/tests/diff/replay.py | 7 +++++++ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 31884e5f183..730a7ca7b75 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -1,3 +1,4 @@ +import datetime import time from cereal import log @@ -149,17 +150,22 @@ def _handle_mouse_release(self, mouse_pos: MousePos): self._did_long_press = False def _get_version_text(self) -> tuple[str, str, str, str] | None: - description = ui_state.params.get("UpdaterCurrentDescription") - - if description is not None and len(description) > 0: - # Expect "version / branch / commit / date"; be tolerant of other formats - try: - version, branch, commit, date = description.split(" / ") - return version, branch, commit, date - except Exception: - return None - - return None + version = ui_state.params.get("Version") + branch = ui_state.params.get("GitBranch") + commit = ui_state.params.get("GitCommit") + + if not all((version, branch, commit)): + return None + + commit_date_raw = ui_state.params.get("GitCommitDate") + try: + # GitCommitDate format from get_commit_date(): '%ct %ci' e.g. "'1708012345 2024-02-15 ...'" + unix_ts = int(commit_date_raw.strip("'").split()[0]) + date_str = datetime.datetime.fromtimestamp(unix_ts).strftime("%b %d") + except (ValueError, IndexError, TypeError, AttributeError): + date_str = "" + + return version, branch, commit[:7], date_str def _render(self, _): # TODO: why is there extra space here to get it to be flush? diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 7ed7ce93649..9860969efbb 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -23,8 +23,15 @@ def setup_state(): params.put("HasAcceptedTerms", terms_version) params.put("CompletedTrainingVersion", training_version) params.put("DongleId", "test123456789") + # Combined description for layouts that still use it (BIG home, settings/software) params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30") + # Params for mici home + params.put("Version", "0.10.1") + params.put("GitBranch", "test-branch") + params.put("GitCommit", "abc12340ff9131237ba23a1d0fbd8edf9c80e87") + params.put("GitCommitDate", "'1732924800 2024-11-30 00:00:00 +0000'") + def run_replay(variant: LayoutVariant) -> None: if HEADLESS: From 47ca2c93817e265f6784bb40695e59ab9f742790 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 27 Feb 2026 21:21:33 -0800 Subject: [PATCH 404/685] ui: widgets animate out (#37321) * stash * widgets animate out * Revert "stash" This reverts commit eac3493509cff6f2c64111d803c7fef21a1aa2dd. * abstract * works also * works also * support pop_widget * only animate top * callback in request pop * tune it * fix * fix * try this * Revert "try this" This reverts commit 191373a1b35917ee3a361afe73b16eeb60d0a20e. * debug * debug * clean up * simple test * clean up * clean up * clean up * clean up * clean up * clean up * clkean up * re sort * fine * yes --- selfdrive/ui/mici/layouts/main.py | 7 ++-- selfdrive/ui/mici/widgets/dialog.py | 8 ++--- system/ui/lib/application.py | 43 ++++++++++++++++++++++--- system/ui/mici_setup.py | 2 +- system/ui/tests/test_nav_stack.py | 50 +++++++++++++++++++++++++++++ system/ui/widgets/nav_widget.py | 16 ++++++++- 6 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 system/ui/tests/test_nav_stack.py diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 1a0c6bc0942..b8958993f2a 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -93,14 +93,14 @@ def _handle_transitions(self): self._scroll_to(self._home_layout) if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: - gui_app.pop_widgets_to(self) + gui_app.request_pop_widgets_to(self) self._scroll_to(self._onroad_layout) self._onroad_time_delay = None # When car leaves standstill, pop nav stack and scroll to onroad CS = ui_state.sm["carState"] if not CS.standstill and self._prev_standstill: - gui_app.pop_widgets_to(self) + gui_app.request_pop_widgets_to(self) self._scroll_to(self._onroad_layout) self._prev_standstill = CS.standstill @@ -112,9 +112,10 @@ def _on_interactive_timeout(self): if ui_state.started: # Don't pop if at standstill if not ui_state.sm["carState"].standstill: - gui_app.pop_widgets_to(self) + gui_app.request_pop_widgets_to(self) self._scroll_to(self._onroad_layout) else: + # Screen turns off on timeout offroad, so pop immediately without animation gui_app.pop_widgets_to(self) self._scroll_to(self._home_layout) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 8e978066c6b..1191f031bea 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -82,8 +82,8 @@ def __init__(self, title: str, icon: str, red: bool = False, def _on_confirm(self): if self._exit_on_confirm: - gui_app.pop_widget() - if self._confirm_callback: + gui_app.request_pop_widget(self._confirm_callback) + elif self._confirm_callback: self._confirm_callback() def _update_state(self): @@ -128,9 +128,7 @@ def __init__(self, def confirm_callback_wrapper(): text = self._keyboard.text() - gui_app.pop_widget() - if confirm_callback: - confirm_callback(text) + gui_app.request_pop_widget(lambda: confirm_callback(text) if confirm_callback else None) self._confirm_callback = confirm_callback_wrapper def _update_state(self): diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 34aed4f6a6d..ebfa385ea0c 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -384,17 +384,25 @@ def push_widget(self, widget: object): self._nav_stack.append(widget) widget.show_event() - def pop_widget(self): + # pop_widget and pop_widgets_to are immediate (no animation). Use request_* variants for animated dismiss. + def pop_widget(self, idx: int | None = None): if len(self._nav_stack) < 2: cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") return - # re-enable previous widget and pop current + if idx is None: + idx = -1 + else: + if idx < 1 or idx >= len(self._nav_stack): + return + + # re-enable widget below, and re-enable popped widget so it's clean if pushed again # TODO: switch to touch_valid - prev_widget = self._nav_stack[-2] + prev_widget = self._nav_stack[idx - 1] prev_widget.set_enabled(True) - widget = self._nav_stack.pop() + widget = self._nav_stack.pop(idx) + widget.set_enabled(True) widget.hide_event() def pop_widgets_to(self, widget): @@ -406,6 +414,33 @@ def pop_widgets_to(self, widget): while len(self._nav_stack) > 0 and self._nav_stack[-1] != widget: self.pop_widget() + def request_pop_widget(self, callback: Callable | None = None): + """Request the top widget to close. NavWidgets dismiss (animate then pop); others pop immediately. Callback runs after pop.""" + if len(self._nav_stack) < 2: + cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") + return + top = self._nav_stack[-1] + if hasattr(top, "dismiss"): + top.dismiss(callback) + else: + self.pop_widget() + if callback: + callback() + + def request_pop_widgets_to(self, widget): + """Request to close widgets down to the given widget. Middle widgets are removed via pop_widget logic; only the top animates down.""" + if widget not in self._nav_stack: + cloudlog.warning("Widget not in stack, cannot pop to it!") + return + + if len(self._nav_stack) < 2 or self._nav_stack[-1] == widget: + return + + # Pop second-from-top repeatedly until stack is [target, top]; each goes through re-enable + hide_event + while len(self._nav_stack) > 2 and self._nav_stack[-2] != widget: + self.pop_widget(len(self._nav_stack) - 2) + self.request_pop_widget() + def get_active_widget(self): if len(self._nav_stack) > 0: return self._nav_stack[-1] diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index ca635444392..961cbe7531b 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -534,7 +534,7 @@ def __init__(self): def _nav_stack_tick(self): has_internet = self._network_monitor.network_connected.is_set() if has_internet and not self._prev_has_internet: - gui_app.pop_widgets_to(self) + gui_app.request_pop_widgets_to(self) self._prev_has_internet = has_internet def _update_state(self): diff --git a/system/ui/tests/test_nav_stack.py b/system/ui/tests/test_nav_stack.py new file mode 100644 index 00000000000..eae5d78c1a2 --- /dev/null +++ b/system/ui/tests/test_nav_stack.py @@ -0,0 +1,50 @@ +import pytest +from openpilot.system.ui.lib.application import gui_app + + +class Widget: + def __init__(self): + self.enabled, self.shown, self.hidden = True, False, False + + def set_enabled(self, e): self.enabled = e + def show_event(self): self.shown = True + def hide_event(self): self.hidden = True + + +@pytest.fixture(autouse=True) +def clean_stack(): + gui_app._nav_stack = [] + yield + gui_app._nav_stack = [] + + +def test_push(): + a, b = Widget(), Widget() + gui_app.push_widget(a) + gui_app.push_widget(b) + assert not a.enabled and not a.hidden + assert b.enabled and b.shown + + +def test_pop_re_enables(): + widgets = [Widget() for _ in range(4)] + for w in widgets: + gui_app.push_widget(w) + assert all(not w.enabled for w in widgets[:-1]) + gui_app.pop_widget() + assert widgets[-2].enabled + + +@pytest.mark.parametrize("pop_fn", [gui_app.pop_widgets_to, gui_app.request_pop_widgets_to]) +def test_pop_widgets_to(pop_fn): + widgets = [Widget() for _ in range(4)] + for w in widgets: + gui_app.push_widget(w) + + root = widgets[0] + pop_fn(root) + + assert gui_app._nav_stack == [root] + assert root.enabled and not root.hidden + for w in widgets[1:]: + assert w.enabled and w.hidden and w.shown diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 02afc911b2e..ad8d5d313a5 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -17,6 +17,7 @@ DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing DISMISS_TIME_SECONDS = 2.0 +DISMISS_ANIMATION_RC = 0.2 # time constant for non-user triggered dismiss animation class NavBar(Widget): @@ -62,6 +63,7 @@ def __init__(self): self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) self._playing_dismiss_animation = False + self._dismiss_callback: Callable | None = None self._trigger_animate_in = False self._nav_bar_show_time = 0.0 self._back_enabled: bool | Callable[[], bool] = True @@ -83,7 +85,8 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down super()._handle_mouse_event(mouse_event) - if not self.back_enabled: + # Don't let touch events change filter state during dismiss animation + if not self.back_enabled or self._playing_dismiss_animation: self._back_button_start_pos = None self._swiping_away = False self._can_swipe_away = True @@ -170,6 +173,10 @@ def _update_state(self): if self._back_callback is not None: self._back_callback() + if self._dismiss_callback is not None: + self._dismiss_callback() + self._dismiss_callback = None + self._playing_dismiss_animation = False self._back_button_start_pos = None self._swiping_away = False @@ -205,6 +212,13 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: return ret + def dismiss(self, callback: Callable | None = None): + """Programmatically trigger the dismiss animation. Calls pop_widget when done, then callback.""" + if not self._playing_dismiss_animation: + self._pos_filter.update_alpha(DISMISS_ANIMATION_RC) + self._playing_dismiss_animation = True + self._dismiss_callback = callback + def show_event(self): super().show_event() # FIXME: we don't know the height of the rect at first show_event since it's before the first render :( From 876ac69047403705400e48406ad319c9f6fc3a25 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 27 Feb 2026 23:48:56 -0800 Subject: [PATCH 405/685] mici ui: power button visible on ignition (#37475) visilbe when not ignition --- selfdrive/ui/mici/layouts/settings/device.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 7383393542f..077411c95a0 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -302,6 +302,7 @@ def uninstall_openpilot_callback(): self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True, icon_size=(64, 66)) self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) + self._power_off_btn.set_visible(lambda: not ui_state.ignition) regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") regulatory_btn.set_click_callback(self._on_regulatory) @@ -331,13 +332,7 @@ def uninstall_openpilot_callback(): # TODO: can this somehow be generic in widgets/__init__.py or application.py? self.set_back_callback(gui_app.pop_widget) - # Hide power off button when onroad - ui_state.add_offroad_transition_callback(self._offroad_transition) - def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = MiciFccModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/mici_fcc.html")) gui_app.push_widget(self._fcc_dialog) - - def _offroad_transition(self): - self._power_off_btn.set_visible(ui_state.is_offroad()) From 6266feeed2c314e48b1fa5f735b1cc56f2a03284 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 00:13:34 -0800 Subject: [PATCH 406/685] Revert "ui: widgets animate out" (#37478) Revert "ui: widgets animate out (#37321)" This reverts commit 47ca2c93817e265f6784bb40695e59ab9f742790. --- selfdrive/ui/mici/layouts/main.py | 7 ++-- selfdrive/ui/mici/widgets/dialog.py | 8 +++-- system/ui/lib/application.py | 43 +++---------------------- system/ui/mici_setup.py | 2 +- system/ui/tests/test_nav_stack.py | 50 ----------------------------- system/ui/widgets/nav_widget.py | 16 +-------- 6 files changed, 14 insertions(+), 112 deletions(-) delete mode 100644 system/ui/tests/test_nav_stack.py diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index b8958993f2a..1a0c6bc0942 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -93,14 +93,14 @@ def _handle_transitions(self): self._scroll_to(self._home_layout) if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: - gui_app.request_pop_widgets_to(self) + gui_app.pop_widgets_to(self) self._scroll_to(self._onroad_layout) self._onroad_time_delay = None # When car leaves standstill, pop nav stack and scroll to onroad CS = ui_state.sm["carState"] if not CS.standstill and self._prev_standstill: - gui_app.request_pop_widgets_to(self) + gui_app.pop_widgets_to(self) self._scroll_to(self._onroad_layout) self._prev_standstill = CS.standstill @@ -112,10 +112,9 @@ def _on_interactive_timeout(self): if ui_state.started: # Don't pop if at standstill if not ui_state.sm["carState"].standstill: - gui_app.request_pop_widgets_to(self) + gui_app.pop_widgets_to(self) self._scroll_to(self._onroad_layout) else: - # Screen turns off on timeout offroad, so pop immediately without animation gui_app.pop_widgets_to(self) self._scroll_to(self._home_layout) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 1191f031bea..8e978066c6b 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -82,8 +82,8 @@ def __init__(self, title: str, icon: str, red: bool = False, def _on_confirm(self): if self._exit_on_confirm: - gui_app.request_pop_widget(self._confirm_callback) - elif self._confirm_callback: + gui_app.pop_widget() + if self._confirm_callback: self._confirm_callback() def _update_state(self): @@ -128,7 +128,9 @@ def __init__(self, def confirm_callback_wrapper(): text = self._keyboard.text() - gui_app.request_pop_widget(lambda: confirm_callback(text) if confirm_callback else None) + gui_app.pop_widget() + if confirm_callback: + confirm_callback(text) self._confirm_callback = confirm_callback_wrapper def _update_state(self): diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index ebfa385ea0c..34aed4f6a6d 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -384,25 +384,17 @@ def push_widget(self, widget: object): self._nav_stack.append(widget) widget.show_event() - # pop_widget and pop_widgets_to are immediate (no animation). Use request_* variants for animated dismiss. - def pop_widget(self, idx: int | None = None): + def pop_widget(self): if len(self._nav_stack) < 2: cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") return - if idx is None: - idx = -1 - else: - if idx < 1 or idx >= len(self._nav_stack): - return - - # re-enable widget below, and re-enable popped widget so it's clean if pushed again + # re-enable previous widget and pop current # TODO: switch to touch_valid - prev_widget = self._nav_stack[idx - 1] + prev_widget = self._nav_stack[-2] prev_widget.set_enabled(True) - widget = self._nav_stack.pop(idx) - widget.set_enabled(True) + widget = self._nav_stack.pop() widget.hide_event() def pop_widgets_to(self, widget): @@ -414,33 +406,6 @@ def pop_widgets_to(self, widget): while len(self._nav_stack) > 0 and self._nav_stack[-1] != widget: self.pop_widget() - def request_pop_widget(self, callback: Callable | None = None): - """Request the top widget to close. NavWidgets dismiss (animate then pop); others pop immediately. Callback runs after pop.""" - if len(self._nav_stack) < 2: - cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") - return - top = self._nav_stack[-1] - if hasattr(top, "dismiss"): - top.dismiss(callback) - else: - self.pop_widget() - if callback: - callback() - - def request_pop_widgets_to(self, widget): - """Request to close widgets down to the given widget. Middle widgets are removed via pop_widget logic; only the top animates down.""" - if widget not in self._nav_stack: - cloudlog.warning("Widget not in stack, cannot pop to it!") - return - - if len(self._nav_stack) < 2 or self._nav_stack[-1] == widget: - return - - # Pop second-from-top repeatedly until stack is [target, top]; each goes through re-enable + hide_event - while len(self._nav_stack) > 2 and self._nav_stack[-2] != widget: - self.pop_widget(len(self._nav_stack) - 2) - self.request_pop_widget() - def get_active_widget(self): if len(self._nav_stack) > 0: return self._nav_stack[-1] diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 961cbe7531b..ca635444392 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -534,7 +534,7 @@ def __init__(self): def _nav_stack_tick(self): has_internet = self._network_monitor.network_connected.is_set() if has_internet and not self._prev_has_internet: - gui_app.request_pop_widgets_to(self) + gui_app.pop_widgets_to(self) self._prev_has_internet = has_internet def _update_state(self): diff --git a/system/ui/tests/test_nav_stack.py b/system/ui/tests/test_nav_stack.py deleted file mode 100644 index eae5d78c1a2..00000000000 --- a/system/ui/tests/test_nav_stack.py +++ /dev/null @@ -1,50 +0,0 @@ -import pytest -from openpilot.system.ui.lib.application import gui_app - - -class Widget: - def __init__(self): - self.enabled, self.shown, self.hidden = True, False, False - - def set_enabled(self, e): self.enabled = e - def show_event(self): self.shown = True - def hide_event(self): self.hidden = True - - -@pytest.fixture(autouse=True) -def clean_stack(): - gui_app._nav_stack = [] - yield - gui_app._nav_stack = [] - - -def test_push(): - a, b = Widget(), Widget() - gui_app.push_widget(a) - gui_app.push_widget(b) - assert not a.enabled and not a.hidden - assert b.enabled and b.shown - - -def test_pop_re_enables(): - widgets = [Widget() for _ in range(4)] - for w in widgets: - gui_app.push_widget(w) - assert all(not w.enabled for w in widgets[:-1]) - gui_app.pop_widget() - assert widgets[-2].enabled - - -@pytest.mark.parametrize("pop_fn", [gui_app.pop_widgets_to, gui_app.request_pop_widgets_to]) -def test_pop_widgets_to(pop_fn): - widgets = [Widget() for _ in range(4)] - for w in widgets: - gui_app.push_widget(w) - - root = widgets[0] - pop_fn(root) - - assert gui_app._nav_stack == [root] - assert root.enabled and not root.hidden - for w in widgets[1:]: - assert w.enabled and w.hidden and w.shown diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index ad8d5d313a5..02afc911b2e 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -17,7 +17,6 @@ DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing DISMISS_TIME_SECONDS = 2.0 -DISMISS_ANIMATION_RC = 0.2 # time constant for non-user triggered dismiss animation class NavBar(Widget): @@ -63,7 +62,6 @@ def __init__(self): self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) self._playing_dismiss_animation = False - self._dismiss_callback: Callable | None = None self._trigger_animate_in = False self._nav_bar_show_time = 0.0 self._back_enabled: bool | Callable[[], bool] = True @@ -85,8 +83,7 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down super()._handle_mouse_event(mouse_event) - # Don't let touch events change filter state during dismiss animation - if not self.back_enabled or self._playing_dismiss_animation: + if not self.back_enabled: self._back_button_start_pos = None self._swiping_away = False self._can_swipe_away = True @@ -173,10 +170,6 @@ def _update_state(self): if self._back_callback is not None: self._back_callback() - if self._dismiss_callback is not None: - self._dismiss_callback() - self._dismiss_callback = None - self._playing_dismiss_animation = False self._back_button_start_pos = None self._swiping_away = False @@ -212,13 +205,6 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: return ret - def dismiss(self, callback: Callable | None = None): - """Programmatically trigger the dismiss animation. Calls pop_widget when done, then callback.""" - if not self._playing_dismiss_animation: - self._pos_filter.update_alpha(DISMISS_ANIMATION_RC) - self._playing_dismiss_animation = True - self._dismiss_callback = callback - def show_event(self): super().show_event() # FIXME: we don't know the height of the rect at first show_event since it's before the first render :( From b6f3692b56890e09428f9ffd9f2388fcea6d0dee Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 00:29:15 -0800 Subject: [PATCH 407/685] NavWidget: standardize back callback (#37479) clean this up --- selfdrive/ui/mici/layouts/settings/developer.py | 1 - selfdrive/ui/mici/layouts/settings/device.py | 5 ----- selfdrive/ui/mici/layouts/settings/firehose.py | 1 - selfdrive/ui/mici/layouts/settings/network/__init__.py | 3 --- selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 3 --- selfdrive/ui/mici/layouts/settings/settings.py | 3 --- selfdrive/ui/mici/layouts/settings/toggles.py | 1 - selfdrive/ui/mici/onroad/driver_camera_dialog.py | 1 - selfdrive/ui/mici/widgets/dialog.py | 1 - selfdrive/ui/mici/widgets/pairing_dialog.py | 1 - system/ui/widgets/nav_widget.py | 7 +------ 11 files changed, 1 insertion(+), 26 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 4d6bdfc3bb4..4e7796814ee 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -11,7 +11,6 @@ class DeveloperLayoutMici(NavScroller): def __init__(self): super().__init__() - self.set_back_callback(gui_app.pop_widget) def github_username_callback(username: str): if username: diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 077411c95a0..d754b9f9800 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -29,7 +29,6 @@ class MiciFccModal(NavWidget): def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() - self.set_back_callback(gui_app.pop_widget) self._content = HtmlRenderer(file_path=file_path, text=text) self._scroll_panel = GuiScrollPanel2(horizontal=False) self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away) @@ -328,10 +327,6 @@ def uninstall_openpilot_callback(): self._power_off_btn, ]) - # Set up back navigation - # TODO: can this somehow be generic in widgets/__init__.py or application.py? - self.set_back_callback(gui_app.pop_widget) - def _on_regulatory(self): if not self._fcc_dialog: self._fcc_dialog = MiciFccModal(os.path.join(BASEDIR, "selfdrive/assets/offroad/mici_fcc.html")) diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index b2c06d72992..d16a04019fc 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -223,5 +223,4 @@ class FirehoseLayout(FirehoseLayoutBase, NavWidget): def __init__(self): super().__init__() - self.set_back_callback(gui_app.pop_widget) self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away) diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index ae7d085e01d..553a74fc601 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -148,9 +148,6 @@ def network_metered_callback(value: str): metered = ui_state.params.get_bool("GsmMetered") self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) - # Set up back navigation - self.set_back_callback(gui_app.pop_widget) - def _update_state(self): super()._update_state() diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index a73a397d1ac..cc45daf24b8 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -272,9 +272,6 @@ class WifiUIMici(NavScroller): def __init__(self, wifi_manager: WifiManager): super().__init__() - # Set up back navigation - self.set_back_callback(gui_app.pop_widget) - self._loading_animation = LoadingAnimation() self._wifi_manager = wifi_manager diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index d996e01fed3..c7fb3201f56 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -49,7 +49,4 @@ def __init__(self): developer_btn, ]) - # Set up back navigation - self.set_back_callback(gui_app.pop_widget) - self._font_medium = gui_app.font(FontWeight.MEDIUM) diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index a7a7bff6e28..acb502fda0a 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -12,7 +12,6 @@ class TogglesLayoutMici(NavScroller): def __init__(self): super().__init__() - self.set_back_callback(gui_app.pop_widget) self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"]) self._experimental_btn = BigParamControl("experimental mode", "ExperimentalMode") diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 4fddc88f6d2..dfa8beeb779 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -35,7 +35,6 @@ def __init__(self, no_escape=False): if not no_escape: # TODO: this can grow unbounded, should be given some thought device.add_interactive_timeout_callback(gui_app.pop_widget) - self.set_back_callback(gui_app.pop_widget) self.set_back_enabled(not no_escape) # Load eye icons diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 8e978066c6b..dbd3ae0d75b 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -22,7 +22,6 @@ class BigDialogBase(NavWidget, abc.ABC): def __init__(self): super().__init__() self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) - self.set_back_callback(gui_app.pop_widget) class BigDialog(BigDialogBase): diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 64b2c9a063d..7476a3b6597 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -19,7 +19,6 @@ class PairingDialog(NavWidget): def __init__(self): super().__init__() - self.set_back_callback(gui_app.pop_widget) self._params = Params() self._qr_texture: rl.Texture | None = None self._last_qr_generation = float("-inf") diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 02afc911b2e..fdbea275af6 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -55,7 +55,6 @@ class NavWidget(Widget, abc.ABC): def __init__(self): super().__init__() - self._back_callback: Callable[[], None] | None = None self._back_button_start_pos: MousePos | None = None self._swiping_away = False # currently swiping away self._can_swipe_away = True # swipe away is blocked after certain horizontal movement @@ -76,9 +75,6 @@ def back_enabled(self) -> bool: def set_back_enabled(self, enabled: bool | Callable[[], bool]) -> None: self._back_enabled = enabled - def set_back_callback(self, callback: Callable[[], None]) -> None: - self._back_callback = callback - def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down super()._handle_mouse_event(mouse_event) @@ -167,8 +163,7 @@ def _update_state(self): new_y = self._pos_filter.x = 0.0 if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: - if self._back_callback is not None: - self._back_callback() + gui_app.pop_widget() self._playing_dismiss_animation = False self._back_button_start_pos = None From 8f328f17fcb99efd4e779a6080e3e1ad522e4c66 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 02:44:27 -0800 Subject: [PATCH 408/685] NavWidget: rm useless state variable --- system/ui/widgets/nav_widget.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index fdbea275af6..edbaf7a98b0 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -55,9 +55,8 @@ class NavWidget(Widget, abc.ABC): def __init__(self): super().__init__() - self._back_button_start_pos: MousePos | None = None + self._back_button_start_pos: MousePos | None = None # cleared after certain amount of horizontal movement self._swiping_away = False # currently swiping away - self._can_swipe_away = True # swipe away is blocked after certain horizontal movement self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) self._playing_dismiss_animation = False @@ -82,7 +81,6 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: if not self.back_enabled: self._back_button_start_pos = None self._swiping_away = False - self._can_swipe_away = True return if mouse_event.left_pressed: @@ -103,7 +101,6 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes if (not vertical_scroller and in_dismiss_area) or scroller_at_top: - self._can_swipe_away = True self._back_button_start_pos = mouse_event.pos elif mouse_event.left_down: @@ -111,14 +108,14 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: # block swiping away if too much horizontal or upward movement horizontal_movement = abs(mouse_event.pos.x - self._back_button_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD upward_movement = mouse_event.pos.y - self._back_button_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD - if not self._swiping_away and (horizontal_movement or upward_movement): - self._can_swipe_away = False - self._back_button_start_pos = None - # block horizontal swiping if now swiping away - if self._can_swipe_away: + if not (horizontal_movement or upward_movement): + # no blocking movement, check if we should start dismissing if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: self._swiping_away = True + else: + if not self._swiping_away: + self._back_button_start_pos = None elif mouse_event.left_released: self._pos_filter.update_alpha(0.1) From b5855bcadee95c5fb2805b767388887cf7c699d0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 02:53:27 -0800 Subject: [PATCH 409/685] NavWidget: clean up names (#37481) * better names * better names * fix * order * rm! --- selfdrive/ui/mici/layouts/settings/device.py | 2 +- .../ui/mici/layouts/settings/firehose.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 4 +- system/ui/widgets/nav_widget.py | 74 +++++++++---------- system/ui/widgets/scroller.py | 2 +- 5 files changed, 42 insertions(+), 42 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index d754b9f9800..d7313cb5a91 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -31,7 +31,7 @@ def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() self._content = HtmlRenderer(file_path=file_path, text=text) self._scroll_panel = GuiScrollPanel2(horizontal=False) - self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away) + self._scroll_panel.set_enabled(lambda: self.enabled and not self._dragging_down) self._fcc_logo = gui_app.texture("icons_mici/settings/device/fcc_logo.png", 76, 64) def _render(self, rect: rl.Rectangle): diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index d16a04019fc..9ad43a6a46f 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -223,4 +223,4 @@ class FirehoseLayout(FirehoseLayoutBase, NavWidget): def __init__(self): super().__init__() - self._scroll_panel.set_enabled(lambda: self.enabled and not self._swiping_away) + self._scroll_panel.set_enabled(lambda: self.enabled and not self._dragging_down) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index dbd3ae0d75b..9ccc37e253b 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -77,7 +77,7 @@ def __init__(self, title: str, icon: str, red: bool = False, self._slider = RedBigSlider(title, icon_txt, confirm_callback=self._on_confirm) else: self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm) - self._slider.set_enabled(lambda: self.enabled and not self._swiping_away) # self.enabled for nav stack + self._slider.set_enabled(lambda: self.enabled and not self._dragging_down) # self.enabled for nav stack def _on_confirm(self): if self._exit_on_confirm: @@ -87,7 +87,7 @@ def _on_confirm(self): def _update_state(self): super()._update_state() - if self._swiping_away and not self._slider.confirmed: + if self._dragging_down and not self._slider.confirmed: self._slider.reset() def _render(self, _): diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index edbaf7a98b0..6daf723ba37 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -55,16 +55,16 @@ class NavWidget(Widget, abc.ABC): def __init__(self): super().__init__() - self._back_button_start_pos: MousePos | None = None # cleared after certain amount of horizontal movement - self._swiping_away = False # currently swiping away + self._drag_start_pos: MousePos | None = None # cleared after certain amount of horizontal movement + self._dragging_down = False # swiped down enough to trigger dismissing on release + self._playing_dismiss_animation = False # released and animating away + self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._playing_dismiss_animation = False self._trigger_animate_in = False - self._nav_bar_show_time = 0.0 self._back_enabled: bool | Callable[[], bool] = True - self._nav_bar = NavBar() + self._nav_bar = NavBar() + self._nav_bar_show_time = 0.0 self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) @property @@ -79,13 +79,13 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: super()._handle_mouse_event(mouse_event) if not self.back_enabled: - self._back_button_start_pos = None - self._swiping_away = False + self._drag_start_pos = None + self._dragging_down = False return if mouse_event.left_pressed: # user is able to swipe away if starting near top of screen, or anywhere if scroller is at top - self._pos_filter.update_alpha(0.04) + self._y_pos_filter.update_alpha(0.04) in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE # TODO: remove vertical scrolling and then this hacky logic to check if scroller is at top @@ -101,37 +101,37 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes if (not vertical_scroller and in_dismiss_area) or scroller_at_top: - self._back_button_start_pos = mouse_event.pos + self._drag_start_pos = mouse_event.pos elif mouse_event.left_down: - if self._back_button_start_pos is not None: + if self._drag_start_pos is not None: # block swiping away if too much horizontal or upward movement - horizontal_movement = abs(mouse_event.pos.x - self._back_button_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD - upward_movement = mouse_event.pos.y - self._back_button_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD + horizontal_movement = abs(mouse_event.pos.x - self._drag_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD + upward_movement = mouse_event.pos.y - self._drag_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD if not (horizontal_movement or upward_movement): # no blocking movement, check if we should start dismissing - if mouse_event.pos.y - self._back_button_start_pos.y > START_DISMISSING_THRESHOLD: - self._swiping_away = True + if mouse_event.pos.y - self._drag_start_pos.y > START_DISMISSING_THRESHOLD: + self._dragging_down = True else: - if not self._swiping_away: - self._back_button_start_pos = None + if not self._dragging_down: + self._drag_start_pos = None elif mouse_event.left_released: - self._pos_filter.update_alpha(0.1) + self._y_pos_filter.update_alpha(0.1) # if far enough, trigger back navigation callback - if self._back_button_start_pos is not None: - if mouse_event.pos.y - self._back_button_start_pos.y > SWIPE_AWAY_THRESHOLD: + if self._drag_start_pos is not None: + if mouse_event.pos.y - self._drag_start_pos.y > SWIPE_AWAY_THRESHOLD: self._playing_dismiss_animation = True - self._back_button_start_pos = None - self._swiping_away = False + self._drag_start_pos = None + self._dragging_down = False def _update_state(self): super()._update_state() if self._trigger_animate_in: - self._pos_filter.x = self._rect.height + self._y_pos_filter.x = self._rect.height self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT self._nav_bar_show_time = rl.get_time() self._trigger_animate_in = False @@ -139,32 +139,32 @@ def _update_state(self): new_y = 0.0 if not self.enabled: - self._back_button_start_pos = None + self._drag_start_pos = None # TODO: why is this not in handle_mouse_event? have to hack above - if self._back_button_start_pos is not None: + if self._drag_start_pos is not None: last_mouse_event = gui_app.last_mouse_event # push entire widget as user drags it away - new_y = max(last_mouse_event.pos.y - self._back_button_start_pos.y, 0) + new_y = max(last_mouse_event.pos.y - self._drag_start_pos.y, 0) if new_y < SWIPE_AWAY_THRESHOLD: new_y /= 2 # resistance until mouse release would dismiss widget - if self._swiping_away: + if self._dragging_down: self._nav_bar.set_alpha(1.0) if self._playing_dismiss_animation: new_y = self._rect.height + DISMISS_PUSH_OFFSET - new_y = round(self._pos_filter.update(new_y)) - if abs(new_y) < 1 and self._pos_filter.velocity.x == 0.0: - new_y = self._pos_filter.x = 0.0 + new_y = round(self._y_pos_filter.update(new_y)) + if abs(new_y) < 1 and self._y_pos_filter.velocity.x == 0.0: + new_y = self._y_pos_filter.x = 0.0 if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: gui_app.pop_widget() self._playing_dismiss_animation = False - self._back_button_start_pos = None - self._swiping_away = False + self._drag_start_pos = None + self._dragging_down = False self.set_position(self._rect.x, new_y) @@ -183,8 +183,8 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 nav_bar_delayed = rl.get_time() - self._nav_bar_show_time < 0.4 # User dragging or dismissing, nav bar follows NavWidget - if self._back_button_start_pos is not None or self._playing_dismiss_animation: - self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._pos_filter.x + if self._drag_start_pos is not None or self._playing_dismiss_animation: + self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._y_pos_filter.x # Waiting to show elif nav_bar_delayed: self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT @@ -205,6 +205,6 @@ def show_event(self): self._nav_bar.show_event() # Reset state - self._pos_filter.update_alpha(0.1) - self._back_button_start_pos = None - self._swiping_away = False + self._y_pos_filter.update_alpha(0.1) + self._drag_start_pos = None + self._dragging_down = False diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 9d9f5663b87..ca6492ae18d 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -443,4 +443,4 @@ class NavScroller(NavWidget, Scroller): def __init__(self, **kwargs): super().__init__(**kwargs) # pass down enabled to child widget for nav stack + disable while swiping away NavWidget - self._scroller.set_enabled(lambda: self.enabled and not self._swiping_away) + self._scroller.set_enabled(lambda: self.enabled and not self._dragging_down) From 256ee6cf6fb26662af58b637a2a77cb5307bde7d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 03:03:49 -0800 Subject: [PATCH 410/685] rm hacky trigger --- system/ui/widgets/nav_widget.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 6daf723ba37..55eea04fe1b 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -60,7 +60,6 @@ def __init__(self): self._playing_dismiss_animation = False # released and animating away self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._trigger_animate_in = False self._back_enabled: bool | Callable[[], bool] = True self._nav_bar = NavBar() @@ -130,12 +129,6 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: def _update_state(self): super()._update_state() - if self._trigger_animate_in: - self._y_pos_filter.x = self._rect.height - self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT - self._nav_bar_show_time = rl.get_time() - self._trigger_animate_in = False - new_y = 0.0 if not self.enabled: @@ -199,12 +192,14 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: def show_event(self): super().show_event() - # FIXME: we don't know the height of the rect at first show_event since it's before the first render :( - # so we need this hacky bool for now - self._trigger_animate_in = True self._nav_bar.show_event() # Reset state - self._y_pos_filter.update_alpha(0.1) self._drag_start_pos = None self._dragging_down = False + # Start NavWidget off-screen, no matter how tall it is + self._y_pos_filter.update_alpha(0.1) + self._y_pos_filter.x = gui_app.height + + self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + self._nav_bar_show_time = rl.get_time() From 940c5b3b3ff5ba3f03b458e73bb38eda489ea5d3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 03:17:57 -0800 Subject: [PATCH 411/685] NavWidget: remove back enabled (#37482) * free navwidget! * clean up * clean up --- selfdrive/ui/mici/layouts/onboarding.py | 12 +++-- .../ui/mici/onroad/driver_camera_dialog.py | 17 ++++--- system/ui/widgets/nav_widget.py | 44 ++++++------------- 3 files changed, 30 insertions(+), 43 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index b7fafd894a4..7b619f31ec9 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -8,14 +8,13 @@ from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.button import SmallButton, SmallCircleIconButton from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.slider import SmallSlider from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer -from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog +from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.lib.multilang import tr from openpilot.system.version import terms_version, training_version @@ -27,9 +26,9 @@ class OnboardingState(IntEnum): DECLINE = 2 -class DriverCameraSetupDialog(DriverCameraDialog): +class DriverCameraSetupDialog(BaseDriverCameraDialog): def __init__(self): - super().__init__(no_escape=True) + super().__init__() self.driver_state_renderer = DriverStateRenderer(inset=True) self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 120, 120)) self.driver_state_renderer.load_icons() @@ -438,11 +437,9 @@ def _render_content(self, scroll_offset): )) -class OnboardingWindow(NavWidget): +class OnboardingWindow(Widget): def __init__(self): super().__init__() - self.set_back_enabled(False) - self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version @@ -486,6 +483,7 @@ def _on_completed_training(self): self.close() def _render(self, _): + rl.draw_rectangle_rec(self._rect, rl.BLACK) if self._state == OnboardingState.TERMS: self._terms.render(self._rect) elif self._state == OnboardingState.ONBOARDING: diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index dfa8beeb779..e8321b099c7 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -7,6 +7,7 @@ from openpilot.selfdrive.selfdrived.events import EVENTS, ET from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import gui_label @@ -24,18 +25,15 @@ def _calc_frame_matrix(self, rect: rl.Rectangle): return base -class DriverCameraDialog(NavWidget): - def __init__(self, no_escape=False): +class BaseDriverCameraDialog(Widget): + # Not a NavWidget so training guide can use this without back navigation + def __init__(self): super().__init__() self._camera_view = DriverCameraView("camerad", VisionStreamType.VISION_STREAM_DRIVER) self.driver_state_renderer = DriverStateRenderer(lines=True) self.driver_state_renderer.set_rect(rl.Rectangle(0, 0, 200, 200)) self.driver_state_renderer.load_icons() self._pm: messaging.PubMaster | None = None - if not no_escape: - # TODO: this can grow unbounded, should be given some thought - device.add_interactive_timeout_callback(gui_app.pop_widget) - self.set_back_enabled(not no_escape) # Load eye icons self._eye_fill_texture = None @@ -230,6 +228,13 @@ def _draw_eyes(self, rect: rl.Rectangle, driver_data): rl.draw_texture_v(self._glasses_texture, glasses_pos, rl.Color(70, 80, 161, int(255 * glasses_prob))) +class DriverCameraDialog(NavWidget, BaseDriverCameraDialog): + def __init__(self): + super().__init__() + # TODO: this can grow unbounded, should be given some thought + device.add_interactive_timeout_callback(gui_app.pop_widget) + + if __name__ == "__main__": gui_app.init_window("Driver Camera View (mici)") diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 55eea04fe1b..d7d39421575 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -2,7 +2,6 @@ import abc import pyray as rl -from collections.abc import Callable from openpilot.system.ui.widgets import Widget from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent @@ -60,28 +59,14 @@ def __init__(self): self._playing_dismiss_animation = False # released and animating away self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._back_enabled: bool | Callable[[], bool] = True - self._nav_bar = NavBar() self._nav_bar_show_time = 0.0 self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) - @property - def back_enabled(self) -> bool: - return self._back_enabled() if callable(self._back_enabled) else self._back_enabled - - def set_back_enabled(self, enabled: bool | Callable[[], bool]) -> None: - self._back_enabled = enabled - def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down super()._handle_mouse_event(mouse_event) - if not self.back_enabled: - self._drag_start_pos = None - self._dragging_down = False - return - if mouse_event.left_pressed: # user is able to swipe away if starting near top of screen, or anywhere if scroller is at top self._y_pos_filter.update_alpha(0.04) @@ -172,21 +157,20 @@ def _layout(self): def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: ret = super().render(rect) - if self.back_enabled: - bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 - nav_bar_delayed = rl.get_time() - self._nav_bar_show_time < 0.4 - # User dragging or dismissing, nav bar follows NavWidget - if self._drag_start_pos is not None or self._playing_dismiss_animation: - self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._y_pos_filter.x - # Waiting to show - elif nav_bar_delayed: - self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT - # Animate back to top - else: - self._nav_bar_y_filter.update(NAV_BAR_MARGIN) - - self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) - self._nav_bar.render() + bar_x = self._rect.x + (self._rect.width - self._nav_bar.rect.width) / 2 + nav_bar_delayed = rl.get_time() - self._nav_bar_show_time < 0.4 + # User dragging or dismissing, nav bar follows NavWidget + if self._drag_start_pos is not None or self._playing_dismiss_animation: + self._nav_bar_y_filter.x = NAV_BAR_MARGIN + self._y_pos_filter.x + # Waiting to show + elif nav_bar_delayed: + self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT + # Animate back to top + else: + self._nav_bar_y_filter.update(NAV_BAR_MARGIN) + + self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) + self._nav_bar.render() return ret From d016071df366eba47a74d4a0761494ed97469997 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 03:26:18 -0800 Subject: [PATCH 412/685] NavWidget: clean up scroller access (#37480) * clean up * more * great clean ups * better name * remove useless _can_swipe_away * reorder * rename * state machine is nice but might be too much * Revert "state machine is nice but might be too much" This reverts commit f8952969243a2eac3ed5f84793ba7b0c0cdf24bf. * got a better name out of it though * clean up * clean up * rm! * rm * and this * and * clean up --- selfdrive/ui/mici/layouts/settings/device.py | 10 +---- .../ui/mici/layouts/settings/firehose.py | 10 ++--- system/ui/widgets/nav_widget.py | 42 +++++++++---------- system/ui/widgets/scroller.py | 23 ++++++++++ 4 files changed, 48 insertions(+), 37 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index d7313cb5a91..b6f6f71d69b 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -7,8 +7,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.time_helpers import system_time_valid -from openpilot.system.ui.widgets.scroller import NavScroller -from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 +from openpilot.system.ui.widgets.scroller import NavRawScrollPanel, NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog @@ -17,21 +16,16 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets.label import MiciLabel from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID -class MiciFccModal(NavWidget): - BACK_TOUCH_AREA_PERCENTAGE = 0.1 - +class MiciFccModal(NavRawScrollPanel): def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() self._content = HtmlRenderer(file_path=file_path, text=text) - self._scroll_panel = GuiScrollPanel2(horizontal=False) - self._scroll_panel.set_enabled(lambda: self.enabled and not self._dragging_down) self._fcc_logo = gui_app.texture("icons_mici/settings/device/fcc_logo.png", 76, 64) def _render(self, rect: rl.Rectangle): diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index 9ad43a6a46f..e5b6301acfd 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -14,7 +14,7 @@ from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.lib.multilang import tr, trn, tr_noop from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.nav_widget import NavWidget +from openpilot.system.ui.widgets.scroller import NavRawScrollPanel TITLE = tr_noop("Firehose Mode") DESCRIPTION = tr_noop( @@ -218,9 +218,5 @@ def _update_loop(self): time.sleep(self.UPDATE_INTERVAL) -class FirehoseLayout(FirehoseLayoutBase, NavWidget): - BACK_TOUCH_AREA_PERCENTAGE = 0.1 - - def __init__(self): - super().__init__() - self._scroll_panel.set_enabled(lambda: self.enabled and not self._dragging_down) +class FirehoseLayout(NavRawScrollPanel, FirehoseLayoutBase): + pass diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index d7d39421575..acbec5eb621 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -14,11 +14,12 @@ NAV_BAR_WIDTH = 205 NAV_BAR_HEIGHT = 8 -DISMISS_PUSH_OFFSET = 50 + NAV_BAR_MARGIN + NAV_BAR_HEIGHT # px extra to push down when dismissing -DISMISS_TIME_SECONDS = 2.0 +DISMISS_PUSH_OFFSET = NAV_BAR_MARGIN + NAV_BAR_HEIGHT + 50 # px extra to push down when dismissing class NavBar(Widget): + FADE_AFTER_SECONDS = 2.0 + def __init__(self): super().__init__() self.set_rect(rl.Rectangle(0, 0, NAV_BAR_WIDTH, NAV_BAR_HEIGHT)) @@ -37,7 +38,7 @@ def show_event(self): self._fade_time = rl.get_time() def _render(self, _): - if rl.get_time() - self._fade_time > DISMISS_TIME_SECONDS: + if rl.get_time() - self._fade_time > self.FADE_AFTER_SECONDS: self._alpha = 0.0 alpha = self._alpha_filter.update(self._alpha) @@ -54,42 +55,37 @@ class NavWidget(Widget, abc.ABC): def __init__(self): super().__init__() + # State self._drag_start_pos: MousePos | None = None # cleared after certain amount of horizontal movement self._dragging_down = False # swiped down enough to trigger dismissing on release self._playing_dismiss_animation = False # released and animating away self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) + # TODO: move this state into NavBar self._nav_bar = NavBar() self._nav_bar_show_time = 0.0 self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) + def _back_enabled(self) -> bool: + # Children can override this to block swipe away, like when not at + # the top of a vertical scroll panel to prevent erroneous swipes + return True + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: - # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down super()._handle_mouse_event(mouse_event) if mouse_event.left_pressed: - # user is able to swipe away if starting near top of screen, or anywhere if scroller is at top + # user is able to swipe away if starting near top of screen self._y_pos_filter.update_alpha(0.04) in_dismiss_area = mouse_event.pos.y < self._rect.height * self.BACK_TOUCH_AREA_PERCENTAGE - # TODO: remove vertical scrolling and then this hacky logic to check if scroller is at top - scroller_at_top = False - vertical_scroller = False - # TODO: -20? snapping in WiFi dialog can make offset not be positive at the top - if hasattr(self, '_scroller'): - scroller_at_top = self._scroller.scroll_panel.get_offset() >= -20 and not self._scroller._horizontal - vertical_scroller = not self._scroller._horizontal - elif hasattr(self, '_scroll_panel'): - scroller_at_top = self._scroll_panel.get_offset() >= -20 and not self._scroll_panel._horizontal - vertical_scroller = not self._scroll_panel._horizontal - - # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes - if (not vertical_scroller and in_dismiss_area) or scroller_at_top: + if in_dismiss_area and self._back_enabled(): self._drag_start_pos = mouse_event.pos elif mouse_event.left_down: if self._drag_start_pos is not None: # block swiping away if too much horizontal or upward movement + # block (lock-in) threshold is higher than start dismissing horizontal_movement = abs(mouse_event.pos.x - self._drag_start_pos.x) > BLOCK_SWIPE_AWAY_THRESHOLD upward_movement = mouse_event.pos.y - self._drag_start_pos.y < -BLOCK_SWIPE_AWAY_THRESHOLD @@ -102,7 +98,9 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: self._drag_start_pos = None elif mouse_event.left_released: + # reset rc for either slide up or down animation self._y_pos_filter.update_alpha(0.1) + # if far enough, trigger back navigation callback if self._drag_start_pos is not None: if mouse_event.pos.y - self._drag_start_pos.y > SWIPE_AWAY_THRESHOLD: @@ -116,10 +114,13 @@ def _update_state(self): new_y = 0.0 + if self._dragging_down: + self._nav_bar.set_alpha(1.0) + + # FIXME: disabling this widget on new push_widget still causes this widget to track mouse events without mouse down if not self.enabled: self._drag_start_pos = None - # TODO: why is this not in handle_mouse_event? have to hack above if self._drag_start_pos is not None: last_mouse_event = gui_app.last_mouse_event # push entire widget as user drags it away @@ -127,9 +128,6 @@ def _update_state(self): if new_y < SWIPE_AWAY_THRESHOLD: new_y /= 2 # resistance until mouse release would dismiss widget - if self._dragging_down: - self._nav_bar.set_alpha(1.0) - if self._playing_dismiss_animation: new_y = self._rect.height + DISMISS_PUSH_OFFSET diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index ca6492ae18d..fb47f690b21 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -444,3 +444,26 @@ def __init__(self, **kwargs): super().__init__(**kwargs) # pass down enabled to child widget for nav stack + disable while swiping away NavWidget self._scroller.set_enabled(lambda: self.enabled and not self._dragging_down) + + def _back_enabled(self) -> bool: + # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes + # TODO: only used for offroad alerts, remove when horizontal + return self._scroller._horizontal or self._scroller.scroll_panel.get_offset() >= -20 # some tolerance + + +# TODO: only used for a few vertical scrollers, remove when horizontal +class NavRawScrollPanel(NavWidget): + # can swipe anywhere, only when at top + BACK_TOUCH_AREA_PERCENTAGE = 1.0 + + def __init__(self): + super().__init__() + self._scroll_panel = GuiScrollPanel2(horizontal=False) + self._scroll_panel.set_enabled(lambda: self.enabled and not self._dragging_down) + + def show_event(self): + super().show_event() + self._scroll_panel.set_offset(0) + + def _back_enabled(self) -> bool: + return self._scroll_panel.get_offset() >= -20 From 87c495b7610cd1212017264dc88cb813ba6a6cac Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 06:16:03 -0800 Subject: [PATCH 413/685] Update test_widget_leaks.py --- selfdrive/ui/mici/tests/test_widget_leaks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index 7441ed5a224..be12839cd7c 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -38,10 +38,10 @@ "openpilot.system.ui.widgets.label.Label", "openpilot.system.ui.widgets.button.Button", "openpilot.system.ui.widgets.html_render.HtmlRenderer", - "openpilot.system.ui.widgets.NavBar", + "openpilot.system.ui.widgets.nav_widget.NavBar", + "openpilot.selfdrive.ui.mici.layouts.settings.device.MiciFccModal", "openpilot.system.ui.widgets.inputbox.InputBox", "openpilot.system.ui.widgets.scroller_tici.Scroller", - "openpilot.system.ui.widgets.scroller.Scroller", "openpilot.system.ui.widgets.label.UnifiedLabel", "openpilot.system.ui.widgets.mici_keyboard.MiciKeyboard", "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialogV2", From 2af7b3441e2fac013b0c25fcdc6957e6a8135006 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 06:19:43 -0800 Subject: [PATCH 414/685] Nav stack: clean up (#37484) guards --- selfdrive/ui/mici/widgets/dialog.py | 14 +++++++++++--- system/ui/widgets/mici_keyboard.py | 2 +- system/ui/widgets/nav_widget.py | 5 +++++ system/ui/widgets/scroller.py | 4 ++-- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 9ccc37e253b..6cfb6455bfe 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -77,7 +77,7 @@ def __init__(self, title: str, icon: str, red: bool = False, self._slider = RedBigSlider(title, icon_txt, confirm_callback=self._on_confirm) else: self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm) - self._slider.set_enabled(lambda: self.enabled and not self._dragging_down) # self.enabled for nav stack + self._slider.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget def _on_confirm(self): if self._exit_on_confirm: @@ -87,7 +87,7 @@ def _on_confirm(self): def _update_state(self): super()._update_state() - if self._dragging_down and not self._slider.confirmed: + if self.is_dismissing and not self._slider.confirmed: self._slider.reset() def _render(self, _): @@ -109,7 +109,7 @@ def __init__(self, font_weight=FontWeight.MEDIUM) self._keyboard = MiciKeyboard() self._keyboard.set_text(default_text) - self._keyboard.set_enabled(lambda: self.enabled) # for nav stack + self._keyboard.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget self._minimum_length = minimum_length self._backspace_held_time: float | None = None @@ -135,6 +135,10 @@ def confirm_callback_wrapper(): def _update_state(self): super()._update_state() + if self.is_dismissing: + self._backspace_held_time = None + return + last_mouse_event = gui_app.last_mouse_event if last_mouse_event.left_down and rl.check_collision_point_rec(last_mouse_event.pos, self._top_right_button_rect) and self._backspace_img_alpha.x > 1: if self._backspace_held_time is None: @@ -229,6 +233,10 @@ def _handle_mouse_press(self, mouse_pos: MousePos): super()._handle_mouse_press(mouse_pos) # TODO: need to track where press was so enter and back can activate on release rather than press # or turn into icon widgets :eyes_open: + + if self.is_dismissing: + return + # handle backspace icon click if rl.check_collision_point_rec(mouse_pos, self._top_right_button_rect) and self._backspace_img_alpha.x > 254: self._keyboard.backspace() diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 59a2451387d..18384fd905d 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -322,7 +322,7 @@ def _update_state(self): self._selected_key_filter.update(self._closest_key[0] is not None) # unselect key after animation plays - if self._unselect_key_t is not None and rl.get_time() > self._unselect_key_t: + if (self._unselect_key_t is not None and rl.get_time() > self._unselect_key_t) or not self.enabled: self._closest_key = (None, float('inf')) self._unselect_key_t = None self._selected_key_t = None diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index acbec5eb621..fb680a0b5a8 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -172,6 +172,10 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: return ret + @property + def is_dismissing(self) -> bool: + return self._dragging_down or self._playing_dismiss_animation + def show_event(self): super().show_event() self._nav_bar.show_event() @@ -179,6 +183,7 @@ def show_event(self): # Reset state self._drag_start_pos = None self._dragging_down = False + self._playing_dismiss_animation = False # Start NavWidget off-screen, no matter how tall it is self._y_pos_filter.update_alpha(0.1) self._y_pos_filter.x = gui_app.height diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index fb47f690b21..c48be6b80b1 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -443,7 +443,7 @@ class NavScroller(NavWidget, Scroller): def __init__(self, **kwargs): super().__init__(**kwargs) # pass down enabled to child widget for nav stack + disable while swiping away NavWidget - self._scroller.set_enabled(lambda: self.enabled and not self._dragging_down) + self._scroller.set_enabled(lambda: self.enabled and not self.is_dismissing) def _back_enabled(self) -> bool: # Vertical scrollers need to be at the top to swipe away to prevent erroneous swipes @@ -459,7 +459,7 @@ class NavRawScrollPanel(NavWidget): def __init__(self): super().__init__() self._scroll_panel = GuiScrollPanel2(horizontal=False) - self._scroll_panel.set_enabled(lambda: self.enabled and not self._dragging_down) + self._scroll_panel.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): super().show_event() From b15390d351b059511b8f972e7a98565fc648ed84 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 06:33:51 -0800 Subject: [PATCH 415/685] mici ui: add interaction timeout fixme + fix navwidget guard --- selfdrive/ui/mici/layouts/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 1a0c6bc0942..bd9167e828d 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -92,6 +92,7 @@ def _handle_transitions(self): else: self._scroll_to(self._home_layout) + # FIXME: these two pops can interrupt user interacting in the settings if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: gui_app.pop_widgets_to(self) self._scroll_to(self._onroad_layout) From e244aabe88cbfff302befdc823dfd72535615e80 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 06:38:47 -0800 Subject: [PATCH 416/685] mici ui: fix navwidget guard --- system/ui/widgets/nav_widget.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index fb680a0b5a8..3ac4c903ba8 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -74,6 +74,10 @@ def _back_enabled(self) -> bool: def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: super()._handle_mouse_event(mouse_event) + # Don't let touch events change filter state during dismiss animation + if self._playing_dismiss_animation: + return + if mouse_event.left_pressed: # user is able to swipe away if starting near top of screen self._y_pos_filter.update_alpha(0.04) From 9cc0d7a1c96ea27dcc2247a66c0f3e930c075aaf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 07:16:06 -0800 Subject: [PATCH 417/685] NavWidget: support programmatic dismiss (#37486) * add dismiss support * add to widget * use in dialogs * good catch * fix!! * fix!! * it works eitehr way * frick * good catch --- selfdrive/ui/mici/widgets/dialog.py | 8 +++----- selfdrive/ui/mici/widgets/pairing_dialog.py | 4 ++-- system/ui/widgets/__init__.py | 6 ++++++ system/ui/widgets/nav_widget.py | 15 +++++++++++++++ 4 files changed, 26 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 6cfb6455bfe..619c1ca28f4 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -81,8 +81,8 @@ def __init__(self, title: str, icon: str, red: bool = False, def _on_confirm(self): if self._exit_on_confirm: - gui_app.pop_widget() - if self._confirm_callback: + self.dismiss(self._confirm_callback) + elif self._confirm_callback: self._confirm_callback() def _update_state(self): @@ -127,9 +127,7 @@ def __init__(self, def confirm_callback_wrapper(): text = self._keyboard.text() - gui_app.pop_widget() - if confirm_callback: - confirm_callback(text) + self.dismiss((lambda: confirm_callback(text)) if confirm_callback else None) self._confirm_callback = confirm_callback_wrapper def _update_state(self): diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 7476a3b6597..991cb05a8cc 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -68,8 +68,8 @@ def _check_qr_refresh(self) -> None: def _update_state(self): super()._update_state() - if ui_state.prime_state.is_paired(): - self._playing_dismiss_animation = True + if ui_state.prime_state.is_paired() and not self.is_dismissing: + self.dismiss() def _render(self, rect: rl.Rectangle): self._check_qr_refresh() diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index cc8d72959fc..1e2f7838113 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -195,3 +195,9 @@ def show_event(self): def hide_event(self): """Optionally handle hide event. Parent must manually call this""" + + def dismiss(self, callback: Callable[[], None] | None = None): + """Immediately dismiss the widget, firing the callback after.""" + gui_app.pop_widget() + if callback: + callback() diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 3ac4c903ba8..fe17f12a8f1 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -2,6 +2,7 @@ import abc import pyray as rl +from collections.abc import Callable from openpilot.system.ui.widgets import Widget from openpilot.common.filter_simple import BounceFilter, FirstOrderFilter from openpilot.system.ui.lib.application import gui_app, MousePos, MouseEvent @@ -15,6 +16,7 @@ NAV_BAR_HEIGHT = 8 DISMISS_PUSH_OFFSET = NAV_BAR_MARGIN + NAV_BAR_HEIGHT + 50 # px extra to push down when dismissing +DISMISS_ANIMATION_RC = 0.2 # slightly slower for non-user triggered dismiss animation class NavBar(Widget): @@ -61,6 +63,8 @@ def __init__(self): self._playing_dismiss_animation = False # released and animating away self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) + self._dismiss_callback: Callable[[], None] | None = None + # TODO: move this state into NavBar self._nav_bar = NavBar() self._nav_bar_show_time = 0.0 @@ -141,6 +145,9 @@ def _update_state(self): if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: gui_app.pop_widget() + if self._dismiss_callback is not None: + self._dismiss_callback() + self._dismiss_callback = None self._playing_dismiss_animation = False self._drag_start_pos = None @@ -180,6 +187,13 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: def is_dismissing(self) -> bool: return self._dragging_down or self._playing_dismiss_animation + def dismiss(self, callback: Callable[[], None] | None = None): + """Programmatically trigger the dismiss animation. Calls pop_widget when done, then callback.""" + if not self._playing_dismiss_animation: + self._playing_dismiss_animation = True + self._y_pos_filter.update_alpha(DISMISS_ANIMATION_RC) + self._dismiss_callback = callback + def show_event(self): super().show_event() self._nav_bar.show_event() @@ -188,6 +202,7 @@ def show_event(self): self._drag_start_pos = None self._dragging_down = False self._playing_dismiss_animation = False + self._dismiss_callback = None # Start NavWidget off-screen, no matter how tall it is self._y_pos_filter.update_alpha(0.1) self._y_pos_filter.x = gui_app.height From 6cbef7bc13d393ee71c6aa06f5506deef67b1925 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 08:00:07 -0800 Subject: [PATCH 418/685] ui: widgets animate out v2 (#37483) * i like this better * clean up * debug * fix able to click navwidgets that are closing (tested at rc 10) * add dismiss guards * fix keyboard so it unselects * pairing: use dismiss * main todo * rm pop_widgets_to! * reset dismiss state on show event * debug pop animation bugs * Revert "debug pop animation bugs" This reverts commit 9239f2e12cf79b1f75d15d39262fdd15ff5a5200. * revert * cmt * type * clean up * now do the todo * treat using widgets, not idxs, as a separate clean up for later * actually if not navwidget this is buggy * fix * short * simpler --- selfdrive/ui/mici/layouts/main.py | 12 ++++------ system/ui/lib/application.py | 38 +++++++++++++++++++++++-------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index bd9167e828d..2c3fea0d322 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -94,15 +94,13 @@ def _handle_transitions(self): # FIXME: these two pops can interrupt user interacting in the settings if self._onroad_time_delay is not None and rl.get_time() - self._onroad_time_delay >= ONROAD_DELAY: - gui_app.pop_widgets_to(self) - self._scroll_to(self._onroad_layout) + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) self._onroad_time_delay = None # When car leaves standstill, pop nav stack and scroll to onroad CS = ui_state.sm["carState"] if not CS.standstill and self._prev_standstill: - gui_app.pop_widgets_to(self) - self._scroll_to(self._onroad_layout) + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) self._prev_standstill = CS.standstill def _on_interactive_timeout(self): @@ -113,10 +111,10 @@ def _on_interactive_timeout(self): if ui_state.started: # Don't pop if at standstill if not ui_state.sm["carState"].standstill: - gui_app.pop_widgets_to(self) - self._scroll_to(self._onroad_layout) + gui_app.pop_widgets_to(self, lambda: self._scroll_to(self._onroad_layout)) else: - gui_app.pop_widgets_to(self) + # Screen turns off on timeout offroad, so pop immediately without animation + gui_app.pop_widgets_to(self, instant=True) self._scroll_to(self._home_layout) def _on_bookmark_clicked(self): diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 34aed4f6a6d..98e05e1127c 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -383,27 +383,47 @@ def push_widget(self, widget: object): self._nav_stack.append(widget) widget.show_event() + widget.set_enabled(True) - def pop_widget(self): + def pop_widget(self, idx: int | None = None): + # Pops widget instantly without animation if len(self._nav_stack) < 2: cloudlog.warning("At least one widget should remain on the stack, ignoring pop!") return - # re-enable previous widget and pop current - # TODO: switch to touch_valid - prev_widget = self._nav_stack[-2] - prev_widget.set_enabled(True) + idx_to_pop = len(self._nav_stack) - 1 if idx is None else idx + if idx_to_pop <= 0 or idx_to_pop >= len(self._nav_stack): + cloudlog.warning(f"Invalid index {idx_to_pop} to pop, ignoring!") + return + + # only re-enable previous widget if popping top widget + if idx_to_pop == len(self._nav_stack) - 1: + prev_widget = self._nav_stack[idx_to_pop - 1] + prev_widget.set_enabled(True) - widget = self._nav_stack.pop() + widget = self._nav_stack.pop(idx_to_pop) widget.hide_event() - def pop_widgets_to(self, widget): + def pop_widgets_to(self, widget: object, callback: Callable[[], None] | None = None, instant: bool = False): + # Pops middle widgets instantly without animation then dismisses top, animated out if NavWidget if widget not in self._nav_stack: cloudlog.warning("Widget not in stack, cannot pop to it!") return - # pops all widgets after specified widget - while len(self._nav_stack) > 0 and self._nav_stack[-1] != widget: + # Nothing to pop, ensure we still run callback + top_widget = self._nav_stack[-1] + if top_widget == widget: + if callback: + callback() + return + + # instantly pop widgets in between, then dismiss top widget for animation + while len(self._nav_stack) > 1 and self._nav_stack[-2] != widget: + self.pop_widget(len(self._nav_stack) - 2) + + if not instant: + top_widget.dismiss(callback) + else: self.pop_widget() def get_active_widget(self): From 870430e19fb686ee09f8bfad79e7d354e7fc435b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 08:11:25 -0800 Subject: [PATCH 419/685] Revert "Actions cleanup" (#37463) Revert "Actions cleanup (#37307)" This reverts commit f41d77b24fb897af23e25d00f709a2ae36352e4d. --- .github/workflows/tests.yaml | 8 +++++--- pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 00fdceda0b3..8add04c4654 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -19,6 +19,8 @@ concurrency: env: CI: 1 + PYTHONPATH: ${{ github.workspace }} + PYTEST: pytest --continue-on-collection-errors --durations=0 -n logical jobs: build_release: @@ -111,8 +113,8 @@ jobs: run: | source selfdrive/test/setup_xvfb.sh # Pre-compile Python bytecode so each pytest worker doesn't need to - pytest --collect-only -m 'not slow' -qq - MAX_EXAMPLES=1 pytest -m 'not slow' + $PYTEST --collect-only -m 'not slow' -qq + MAX_EXAMPLES=1 $PYTEST -m 'not slow' process_replay: name: process replay @@ -168,7 +170,7 @@ jobs: timeout-minutes: 4 env: ONNXCPU: 1 - run: pytest selfdrive/test/process_replay/test_regen.py + run: $PYTEST selfdrive/test/process_replay/test_regen.py simulator_driving: name: simulator driving diff --git a/pyproject.toml b/pyproject.toml index 699c3af6f06..c3467342b62 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -125,7 +125,7 @@ allow-direct-references = true [tool.pytest.ini_options] minversion = "6.0" -addopts = "--ignore=openpilot/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ --ignore=msgq/ -Werror --strict-config --strict-markers --durations=20 --maxprocesses=8 -n auto --dist=loadgroup" +addopts = "--ignore=openpilot/ --ignore=opendbc/ --ignore=panda/ --ignore=rednose_repo/ --ignore=tinygrad_repo/ --ignore=teleoprtc_repo/ --ignore=msgq/ -Werror --strict-config --strict-markers --durations=10 -n auto --dist=loadgroup" cpp_files = "test_*" cpp_harness = "selfdrive/test/cpp_harness.py" python_files = "test_*.py" From a27efe5796e31f77e97efb33762522f2ddc3b0d0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 28 Feb 2026 10:39:13 -0800 Subject: [PATCH 420/685] setup: add retry for transient network fails on uv install (#37490) --- tools/setup_dependencies.sh | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 7c0bf7d7085..a3b2f68bfda 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -4,6 +4,21 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" ROOT="$(cd "$DIR/../" && pwd)" +function retry() { + local attempts=$1 + shift + for i in $(seq 1 "$attempts"); do + if "$@"; then + return 0 + fi + if [ "$i" -lt "$attempts" ]; then + echo " Attempt $i/$attempts failed, retrying in 5s..." + sleep 5 + fi + done + return 1 +} + function install_ubuntu_deps() { SUDO="" @@ -83,7 +98,8 @@ function install_python_deps() { if ! command -v "uv" > /dev/null 2>&1; then echo "installing uv..." - curl -LsSf --retry 5 --retry-delay 5 --retry-all-errors https://astral.sh/uv/install.sh | sh + # TODO: outer retry can be removed once https://github.com/axodotdev/cargo-dist/pull/2311 is merged + retry 3 sh -c 'curl --retry 5 --retry-delay 5 --retry-all-errors -LsSf https://astral.sh/uv/install.sh | UV_GITHUB_TOKEN="${GITHUB_TOKEN:-}" sh' UV_BIN="$HOME/.local/bin" PATH="$UV_BIN:$PATH" fi From ca5234a32f9b0331b11ed33f418a82669c5ac8e1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 28 Feb 2026 16:44:00 -0800 Subject: [PATCH 421/685] tools/setup: remove vestigial mac .env file --- tools/setup_dependencies.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index a3b2f68bfda..8132cd16dc1 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -111,11 +111,6 @@ function install_python_deps() { echo "installing python packages..." uv sync --frozen --all-extras source .venv/bin/activate - - if [[ "$(uname)" == 'Darwin' ]]; then - touch "$ROOT"/.env - echo "export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES" >> "$ROOT"/.env - fi } # --- Main --- From a6b562e0c1319e7f0b67c6cb2e7c77ffe785563f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 28 Feb 2026 20:51:31 -0800 Subject: [PATCH 422/685] jenkins: move panda tests before camera tests (#37498) * jenkins: move panda tests before camera tests * force this time * Revert "force this time" This reverts commit 53508225d39d63b97ff7ecc3a0181a27b5948d1b. --- Jenkinsfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index e58ac817ebf..c5ebf6162bd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -210,7 +210,6 @@ node { 'HW + Unit Tests': { deviceStage("tizi-hardware", "tizi-common", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), step("test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"), step("test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py", [diffPaths: ["system/loggerd/"]]), step("test manager", "pytest system/manager/test/test_manager.py"), @@ -219,12 +218,14 @@ node { 'camerad OX03C10': { deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'camerad OS04C10': { deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, From e7cc70f3fa8345662be574e4b0aae4c0a5662b05 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 28 Feb 2026 21:09:02 -0800 Subject: [PATCH 423/685] consolidate file downloading from C++ to Python (#37497) --- system/loggerd/SConscript | 2 +- tools/cabana/SConscript | 4 +- tools/cabana/mainwin.cc | 2 + tools/cabana/streams/routes.cc | 91 +++++++----- tools/cabana/streams/routes.h | 9 +- tools/cabana/utils/api.cc | 171 ----------------------- tools/cabana/utils/api.h | 47 ------- tools/lib/file_downloader.py | 148 ++++++++++++++++++++ tools/replay/SConscript | 4 +- tools/replay/api.cc | 162 ---------------------- tools/replay/api.h | 15 -- tools/replay/consoleui.cc | 1 + tools/replay/filereader.cc | 47 +------ tools/replay/filereader.h | 8 +- tools/replay/framereader.cc | 15 +- tools/replay/framereader.h | 4 +- tools/replay/logreader.cc | 4 +- tools/replay/logreader.h | 2 +- tools/replay/main.cc | 5 + tools/replay/py_downloader.cc | 218 +++++++++++++++++++++++++++++ tools/replay/py_downloader.h | 24 ++++ tools/replay/route.cc | 59 ++++---- tools/replay/route.h | 5 +- tools/replay/tests/test_replay.cc | 1 + tools/replay/timeline.cc | 2 +- tools/replay/util.cc | 221 ------------------------------ tools/replay/util.h | 6 - 27 files changed, 514 insertions(+), 763 deletions(-) delete mode 100644 tools/cabana/utils/api.cc delete mode 100644 tools/cabana/utils/api.h create mode 100755 tools/lib/file_downloader.py delete mode 100644 tools/replay/api.cc delete mode 100644 tools/replay/api.h create mode 100644 tools/replay/py_downloader.cc create mode 100644 tools/replay/py_downloader.h diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index fecf448855a..827c1ce5d0e 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -20,4 +20,4 @@ env.Program('encoderd', ['encoderd.cc'], LIBS=libs + ["jpeg"]) env.Program('bootlog.cc', LIBS=libs) if GetOption('extras'): - env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_logger.cc', 'tests/test_zstd_writer.cc'], LIBS=libs + ['curl', 'crypto']) + env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_logger.cc', 'tests/test_zstd_writer.cc'], LIBS=libs) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index e172278d914..cecb5ed8d92 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -81,7 +81,7 @@ if arch == "Darwin": cabana_env['CPPPATH'] += [f"{brew_prefix}/include"] cabana_env['LIBPATH'] += [f"{brew_prefix}/lib"] -cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'curl', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + qt_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] @@ -93,7 +93,7 @@ cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "asset cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', - 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', 'utils/api.cc', + 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 1ea3733ed0f..a7040f891e1 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -24,6 +24,8 @@ #include "tools/cabana/streamselector.h" #include "tools/cabana/tools/findsignal.h" #include "tools/cabana/utils/export.h" +#include "tools/replay/py_downloader.h" +#include "tools/replay/util.h" MainWindow::MainWindow(AbstractStream *stream, const QString &dbc_file) : QMainWindow() { loadFingerprints(); diff --git a/tools/cabana/streams/routes.cc b/tools/cabana/streams/routes.cc index 5915c98065d..8539a00b5ba 100644 --- a/tools/cabana/streams/routes.cc +++ b/tools/cabana/streams/routes.cc @@ -1,27 +1,33 @@ #include "tools/cabana/streams/routes.h" +#include #include #include #include #include #include +#include #include #include #include +#include +#include -class OneShotHttpRequest : public HttpRequest { -public: - OneShotHttpRequest(QObject *parent) : HttpRequest(parent, false) {} - void send(const QString &url) { - if (reply) { - reply->disconnect(); - reply->abort(); - reply->deleteLater(); - reply = nullptr; - } - sendRequest(url); +#include "tools/replay/py_downloader.h" + +namespace { + +// Parse a PyDownloader JSON response into (success, error_code). +std::pair checkApiResponse(const std::string &result) { + if (result.empty()) return {false, 500}; + auto doc = QJsonDocument::fromJson(QByteArray::fromStdString(result)); + if (doc.isObject() && doc.object().contains("error")) { + return {false, doc.object()["error"].toString() == "unauthorized" ? 401 : 500}; } -}; + return {true, 0}; +} + +} // namespace // The RouteListWidget class extends QListWidget to display a custom message when empty class RouteListWidget : public QListWidget { @@ -41,7 +47,7 @@ class RouteListWidget : public QListWidget { QString empty_text_ = tr("No items"); }; -RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent), route_requester_(new OneShotHttpRequest(this)) { +RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Remote routes")); QFormLayout *layout = new QFormLayout(this); @@ -52,41 +58,40 @@ RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent), route_requester_( layout->addRow(button_box); device_list_->addItem(tr("Loading...")); - // Populate period selector with predefined durations period_selector_->addItem(tr("Last week"), 7); period_selector_->addItem(tr("Last 2 weeks"), 14); period_selector_->addItem(tr("Last month"), 30); period_selector_->addItem(tr("Last 6 months"), 180); period_selector_->addItem(tr("Preserved"), -1); - // Connect signals and slots - QObject::connect(route_requester_, &HttpRequest::requestDone, this, &RoutesDialog::parseRouteList); connect(device_list_, QOverload::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes); connect(period_selector_, QOverload::of(&QComboBox::currentIndexChanged), this, &RoutesDialog::fetchRoutes); connect(route_list_, &QListWidget::itemDoubleClicked, this, &QDialog::accept); - QObject::connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); - QObject::connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(button_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(button_box, &QDialogButtonBox::rejected, this, &QDialog::reject); - // Send request to fetch devices - HttpRequest *http = new HttpRequest(this, false); - QObject::connect(http, &HttpRequest::requestDone, this, &RoutesDialog::parseDeviceList); - http->sendRequest(CommaApi::BASE_URL + "/v1/me/devices/"); + // Fetch devices + QPointer self = this; + QtConcurrent::run([self]() { + std::string result = PyDownloader::getDevices(); + auto [success, error_code] = checkApiResponse(result); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code]() { + if (self) self->parseDeviceList(r, success, error_code); + }, Qt::QueuedConnection); + }); } -void RoutesDialog::parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err) { +void RoutesDialog::parseDeviceList(const QString &json, bool success, int error_code) { if (success) { device_list_->clear(); - auto devices = QJsonDocument::fromJson(json.toUtf8()).array(); - for (const QJsonValue &device : devices) { + for (const QJsonValue &device : QJsonDocument::fromJson(json.toUtf8()).array()) { QString dongle_id = device["dongle_id"].toString(); device_list_->addItem(dongle_id, dongle_id); } } else { - bool unauthorized = (err == QNetworkReply::ContentAccessDenied || err == QNetworkReply::AuthenticationRequiredError); - QMessageBox::warning(this, tr("Error"), unauthorized ? tr("Unauthorized, Authenticate with tools/lib/auth.py") : tr("Network error")); + QMessageBox::warning(this, tr("Error"), error_code == 401 ? tr("Unauthorized. Authenticate with tools/lib/auth.py") : tr("Network error")); reject(); } - sender()->deleteLater(); } void RoutesDialog::fetchRoutes() { @@ -95,21 +100,31 @@ void RoutesDialog::fetchRoutes() { route_list_->clear(); route_list_->setEmptyText(tr("Loading...")); - // Construct URL with selected device and date range - QString url = QString("%1/v1/devices/%2").arg(CommaApi::BASE_URL, device_list_->currentText()); + + std::string did = device_list_->currentText().toStdString(); int period = period_selector_->currentData().toInt(); - if (period == -1) { - url += "/routes/preserved"; - } else { + + bool preserved = (period == -1); + int64_t start_ms = 0, end_ms = 0; + if (!preserved) { QDateTime now = QDateTime::currentDateTime(); - url += QString("/routes_segments?start=%1&end=%2") - .arg(now.addDays(-period).toMSecsSinceEpoch()) - .arg(now.toMSecsSinceEpoch()); + start_ms = now.addDays(-period).toMSecsSinceEpoch(); + end_ms = now.toMSecsSinceEpoch(); } - route_requester_->send(url); + + int request_id = ++fetch_id_; + QPointer self = this; + QtConcurrent::run([self, did, start_ms, end_ms, preserved, request_id]() { + std::string result = PyDownloader::getDeviceRoutes(did, start_ms, end_ms, preserved); + if (!self || self->fetch_id_ != request_id) return; + auto [success, error_code] = checkApiResponse(result); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code, request_id]() { + if (self && self->fetch_id_ == request_id) self->parseRouteList(r, success, error_code); + }, Qt::QueuedConnection); + }); } -void RoutesDialog::parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err) { +void RoutesDialog::parseRouteList(const QString &json, bool success, int error_code) { if (success) { for (const QJsonValue &route : QJsonDocument::fromJson(json.toUtf8()).array()) { QDateTime from, to; diff --git a/tools/cabana/streams/routes.h b/tools/cabana/streams/routes.h index 045dc67220d..99fa67ef8c1 100644 --- a/tools/cabana/streams/routes.h +++ b/tools/cabana/streams/routes.h @@ -1,11 +1,10 @@ #pragma once +#include #include #include -#include "tools/cabana/utils/api.h" class RouteListWidget; -class OneShotHttpRequest; class RoutesDialog : public QDialog { Q_OBJECT @@ -14,12 +13,12 @@ class RoutesDialog : public QDialog { QString route(); protected: - void parseDeviceList(const QString &json, bool success, QNetworkReply::NetworkError err); - void parseRouteList(const QString &json, bool success, QNetworkReply::NetworkError err); + void parseDeviceList(const QString &json, bool success, int error_code); + void parseRouteList(const QString &json, bool success, int error_code); void fetchRoutes(); QComboBox *device_list_; QComboBox *period_selector_; RouteListWidget *route_list_; - OneShotHttpRequest *route_requester_; + std::atomic fetch_id_{0}; }; diff --git a/tools/cabana/utils/api.cc b/tools/cabana/utils/api.cc deleted file mode 100644 index 89379a84341..00000000000 --- a/tools/cabana/utils/api.cc +++ /dev/null @@ -1,171 +0,0 @@ -#include "tools/cabana/utils/api.h" - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "common/params.h" -#include "common/util.h" -#include "system/hardware/hw.h" -#include "tools/cabana/utils/util.h" - -QString getVersion() { - static QString version = QString::fromStdString(Params().get("Version")); - return version; -} - -QString getUserAgent() { - return "openpilot-" + getVersion(); -} - -std::optional getDongleId() { - std::string id = Params().get("DongleId"); - - if (!id.empty() && (id != "UnregisteredDevice")) { - return QString::fromStdString(id); - } else { - return {}; - } -} - -namespace CommaApi { - -EVP_PKEY *get_private_key() { - static std::unique_ptr pkey(nullptr, EVP_PKEY_free); - if (!pkey) { - FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); - if (!fp) { - qDebug() << "No private key found, please run manager.py or registration.py"; - return nullptr; - } - pkey.reset(PEM_read_PrivateKey(fp, nullptr, nullptr, nullptr)); - fclose(fp); - } - return pkey.get(); -} - -QByteArray rsa_sign(const QByteArray &data) { - EVP_PKEY *pkey = get_private_key(); - if (!pkey) return {}; - - EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); - if (!mdctx) return {}; - - QByteArray sig(EVP_PKEY_size(pkey), Qt::Uninitialized); - size_t sig_len = sig.size(); - - int ret = EVP_DigestSignInit(mdctx, nullptr, EVP_sha256(), nullptr, pkey); - ret &= EVP_DigestSignUpdate(mdctx, data.data(), data.size()); - ret &= EVP_DigestSignFinal(mdctx, (unsigned char*)sig.data(), &sig_len); - - EVP_MD_CTX_free(mdctx); - - if (ret != 1) return {}; - sig.resize(sig_len); - return sig; -} - -QString create_jwt(const QJsonObject &payloads, int expiry) { - QJsonObject header = {{"alg", "RS256"}}; - - auto t = QDateTime::currentSecsSinceEpoch(); - QJsonObject payload = {{"identity", getDongleId().value_or("")}, {"nbf", t}, {"iat", t}, {"exp", t + expiry}}; - for (auto it = payloads.begin(); it != payloads.end(); ++it) { - payload.insert(it.key(), it.value()); - } - - auto b64_opts = QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals; - QString jwt = QJsonDocument(header).toJson(QJsonDocument::Compact).toBase64(b64_opts) + '.' + - QJsonDocument(payload).toJson(QJsonDocument::Compact).toBase64(b64_opts); - - auto hash = QCryptographicHash::hash(jwt.toUtf8(), QCryptographicHash::Sha256); - return jwt + "." + rsa_sign(hash).toBase64(b64_opts); -} - -} // namespace CommaApi - -HttpRequest::HttpRequest(QObject *parent, bool create_jwt, int timeout) : create_jwt(create_jwt), QObject(parent) { - networkTimer = new QTimer(this); - networkTimer->setSingleShot(true); - networkTimer->setInterval(timeout); - connect(networkTimer, &QTimer::timeout, this, &HttpRequest::requestTimeout); -} - -bool HttpRequest::active() const { - return reply != nullptr; -} - -bool HttpRequest::timeout() const { - return reply && reply->error() == QNetworkReply::OperationCanceledError; -} - -void HttpRequest::sendRequest(const QString &requestURL, const HttpRequest::Method method) { - if (active()) { - qDebug() << "HttpRequest is active"; - return; - } - QString token; - if (create_jwt) { - token = CommaApi::create_jwt(); - } else { - QString token_json = QString::fromStdString(util::read_file(util::getenv("HOME") + "/.comma/auth.json")); - QJsonDocument json_d = QJsonDocument::fromJson(token_json.toUtf8()); - token = json_d["access_token"].toString(); - } - - QNetworkRequest request; - request.setUrl(QUrl(requestURL)); - request.setRawHeader("User-Agent", getUserAgent().toUtf8()); - - if (!token.isEmpty()) { - request.setRawHeader(QByteArray("Authorization"), ("JWT " + token).toUtf8()); - } - - if (method == HttpRequest::Method::GET) { - reply = nam()->get(request); - } else if (method == HttpRequest::Method::DELETE) { - reply = nam()->deleteResource(request); - } - - networkTimer->start(); - connect(reply, &QNetworkReply::finished, this, &HttpRequest::requestFinished); -} - -void HttpRequest::requestTimeout() { - reply->abort(); -} - -void HttpRequest::requestFinished() { - networkTimer->stop(); - - if (reply->error() == QNetworkReply::NoError) { - emit requestDone(reply->readAll(), true, reply->error()); - } else { - QString error; - if (reply->error() == QNetworkReply::OperationCanceledError) { - nam()->clearAccessCache(); - nam()->clearConnectionCache(); - error = "Request timed out"; - } else { - error = reply->errorString(); - } - emit requestDone(error, false, reply->error()); - } - - reply->deleteLater(); - reply = nullptr; -} - -QNetworkAccessManager *HttpRequest::nam() { - static QNetworkAccessManager *networkAccessManager = new QNetworkAccessManager(qApp); - return networkAccessManager; -} diff --git a/tools/cabana/utils/api.h b/tools/cabana/utils/api.h deleted file mode 100644 index ad64d7e7228..00000000000 --- a/tools/cabana/utils/api.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -#include "common/util.h" - -namespace CommaApi { - -const QString BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str(); -QByteArray rsa_sign(const QByteArray &data); -QString create_jwt(const QJsonObject &payloads = {}, int expiry = 3600); - -} // namespace CommaApi - -/** - * Makes a request to the request endpoint. - */ - -class HttpRequest : public QObject { - Q_OBJECT - -public: - enum class Method {GET, DELETE}; - - explicit HttpRequest(QObject* parent, bool create_jwt = true, int timeout = 20000); - void sendRequest(const QString &requestURL, const Method method = Method::GET); - bool active() const; - bool timeout() const; - -signals: - void requestDone(const QString &response, bool success, QNetworkReply::NetworkError error); - -protected: - QNetworkReply *reply = nullptr; - -private: - static QNetworkAccessManager *nam(); - QTimer *networkTimer = nullptr; - bool create_jwt; - -private slots: - void requestTimeout(); - void requestFinished(); -}; diff --git a/tools/lib/file_downloader.py b/tools/lib/file_downloader.py new file mode 100755 index 00000000000..c9c26bb3070 --- /dev/null +++ b/tools/lib/file_downloader.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +""" +CLI tool for downloading files and querying the comma API. +Called by C++ replay/cabana via subprocess. + +Subcommands: + route-files - Get route file URLs as JSON + download - Download URL to local cache, print local path + devices - List user's devices as JSON + device-routes - List routes for a device as JSON +""" +import argparse +import hashlib +import json +import os +import sys +import tempfile +import shutil + +from openpilot.system.hardware.hw import Paths +from openpilot.tools.lib.api import CommaApi, UnauthorizedError, APIError +from openpilot.tools.lib.auth_config import get_token +from openpilot.tools.lib.url_file import URLFile + + +def api_call(func): + """Run an API call, outputting JSON result or error to stdout.""" + try: + result = func(CommaApi(get_token())) + json.dump(result, sys.stdout) + except UnauthorizedError: + json.dump({"error": "unauthorized"}, sys.stdout) + except APIError as e: + error = "not_found" if getattr(e, 'status_code', 0) == 404 else str(e) + json.dump({"error": error}, sys.stdout) + except Exception as e: + json.dump({"error": str(e)}, sys.stdout) + sys.stdout.write("\n") + sys.stdout.flush() + + +def cache_file_path(url): + url_without_query = url.split("?")[0] + return os.path.join(Paths.download_cache_root(), hashlib.sha256(url_without_query.encode()).hexdigest()) + + +def cmd_route_files(args): + api_call(lambda api: api.get(f"v1/route/{args.route}/files")) + + +def cmd_download(args): + url = args.url + use_cache = not args.no_cache + + if use_cache: + local_path = cache_file_path(url) + if os.path.exists(local_path): + sys.stdout.write(local_path + "\n") + sys.stdout.flush() + return + + try: + uf = URLFile(url, cache=False) + total = uf.get_length() + if total <= 0: + sys.stderr.write("ERROR:File not found or empty\n") + sys.stderr.flush() + sys.exit(1) + + os.makedirs(Paths.download_cache_root(), exist_ok=True) + tmp_fd, tmp_path = tempfile.mkstemp(dir=Paths.download_cache_root()) + try: + downloaded = 0 + chunk_size = 1024 * 1024 + with os.fdopen(tmp_fd, 'wb') as f: + while downloaded < total: + data = uf.read(min(chunk_size, total - downloaded)) + f.write(data) + downloaded += len(data) + sys.stderr.write(f"PROGRESS:{downloaded}:{total}\n") + sys.stderr.flush() + + if use_cache: + shutil.move(tmp_path, local_path) + sys.stdout.write(local_path + "\n") + else: + sys.stdout.write(tmp_path + "\n") + except Exception: + try: + os.unlink(tmp_path) + except OSError: + pass + raise + + except Exception as e: + sys.stderr.write(f"ERROR:{e}\n") + sys.stderr.flush() + sys.exit(1) + + sys.stdout.flush() + + +def cmd_devices(args): + api_call(lambda api: api.get("v1/me/devices/")) + + +def cmd_device_routes(args): + def fetch(api): + if args.preserved: + return api.get(f"v1/devices/{args.dongle_id}/routes/preserved") + params = {} + if args.start is not None: + params['start'] = args.start + if args.end is not None: + params['end'] = args.end + return api.get(f"v1/devices/{args.dongle_id}/routes_segments", params=params) + api_call(fetch) + + +def main(): + parser = argparse.ArgumentParser(description="File downloader CLI for openpilot tools") + subparsers = parser.add_subparsers(dest="command", required=True) + + p_rf = subparsers.add_parser("route-files") + p_rf.add_argument("route") + p_rf.set_defaults(func=cmd_route_files) + + p_dl = subparsers.add_parser("download") + p_dl.add_argument("url") + p_dl.add_argument("--no-cache", action="store_true") + p_dl.set_defaults(func=cmd_download) + + p_dev = subparsers.add_parser("devices") + p_dev.set_defaults(func=cmd_devices) + + p_dr = subparsers.add_parser("device-routes") + p_dr.add_argument("dongle_id") + p_dr.add_argument("--start", type=int, default=None) + p_dr.add_argument("--end", type=int, default=None) + p_dr.add_argument("--preserved", action="store_true") + p_dr.set_defaults(func=cmd_device_routes) + + args = parser.parse_args() + args.func(args) + + +if __name__ == "__main__": + main() diff --git a/tools/replay/SConscript b/tools/replay/SConscript index b39cf6dab16..3efa970b376 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -7,12 +7,12 @@ base_frameworks = [] base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", - "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "api.cc"] + "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "py_downloader.cc"] if arch != "Darwin": replay_lib_src.append("qcom_decoder.cc") replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') -replay_libs = [replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'curl', 'yuv', 'ncurses'] + base_libs +replay_libs = [replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'ncurses'] + base_libs replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/replay/api.cc b/tools/replay/api.cc deleted file mode 100644 index 85e4e52b282..00000000000 --- a/tools/replay/api.cc +++ /dev/null @@ -1,162 +0,0 @@ - -#include "tools/replay/api.h" - -#include -#include -#include -#include - -#include -#include -#include - -#include "common/params.h" -#include "common/version.h" -#include "system/hardware/hw.h" - -namespace CommaApi2 { - -// Base64 URL-safe character set (uses '-' and '_' instead of '+' and '/') -static const std::string base64url_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789-_"; - -std::string base64url_encode(const std::string &in) { - std::string out; - int val = 0, valb = -6; - for (unsigned char c : in) { - val = (val << 8) + c; - valb += 8; - while (valb >= 0) { - out.push_back(base64url_chars[(val >> valb) & 0x3F]); - valb -= 6; - } - } - if (valb > -6) { - out.push_back(base64url_chars[((val << 8) >> (valb + 8)) & 0x3F]); - } - - return out; -} - -EVP_PKEY *get_rsa_private_key() { - static std::unique_ptr rsa_private(nullptr, EVP_PKEY_free); - if (!rsa_private) { - FILE *fp = fopen(Path::rsa_file().c_str(), "rb"); - if (!fp) { - std::cerr << "No RSA private key found, please run manager.py or registration.py" << std::endl; - return nullptr; - } - rsa_private.reset(PEM_read_PrivateKey(fp, NULL, NULL, NULL)); - fclose(fp); - } - return rsa_private.get(); -} - -std::string rsa_sign(const std::string &data) { - EVP_PKEY *private_key = get_rsa_private_key(); - if (!private_key) return {}; - - EVP_MD_CTX *mdctx = EVP_MD_CTX_new(); - assert(mdctx != nullptr); - - std::vector sig(EVP_PKEY_size(private_key)); - uint32_t sig_len; - - EVP_SignInit(mdctx, EVP_sha256()); - EVP_SignUpdate(mdctx, data.data(), data.size()); - int ret = EVP_SignFinal(mdctx, sig.data(), &sig_len, private_key); - - EVP_MD_CTX_free(mdctx); - - assert(ret == 1); - assert(sig.size() == sig_len); - return std::string(sig.begin(), sig.begin() + sig_len); -} - -std::string create_jwt(const json11::Json &extra, int exp_time) { - int now = std::chrono::seconds(std::time(nullptr)).count(); - std::string dongle_id = Params().get("DongleId"); - - // Create header and payload - json11::Json header = json11::Json::object{{"alg", "RS256"}}; - auto payload = json11::Json::object{ - {"identity", dongle_id}, - {"iat", now}, - {"nbf", now}, - {"exp", now + exp_time}, - }; - // Merge extra payload - for (const auto &item : extra.object_items()) { - payload[item.first] = item.second; - } - - // JWT construction - std::string jwt = base64url_encode(header.dump()) + '.' + - base64url_encode(json11::Json(payload).dump()); - - // Hash and sign - std::string hash(SHA256_DIGEST_LENGTH, '\0'); - SHA256((uint8_t *)jwt.data(), jwt.size(), (uint8_t *)hash.data()); - std::string signature = rsa_sign(hash); - - return jwt + "." + base64url_encode(signature); -} - -std::string create_token(bool use_jwt, const json11::Json &payloads, int expiry) { - if (use_jwt) { - return create_jwt(payloads, expiry); - } - - std::string token_json = util::read_file(util::getenv("HOME") + "/.comma/auth.json"); - std::string err; - auto json = json11::Json::parse(token_json, err); - if (!err.empty()) { - std::cerr << "Error parsing auth.json " << err << std::endl; - return ""; - } - return json["access_token"].string_value(); -} - -std::string httpGet(const std::string &url, long *response_code) { - CURL *curl = curl_easy_init(); - assert(curl); - - std::string readBuffer; - const std::string token = CommaApi2::create_token(!Hardware::PC()); - - // Set up the lambda for the write callback - // The '+' makes the lambda non-capturing, allowing it to be used as a C function pointer - auto writeCallback = +[](char *contents, size_t size, size_t nmemb, std::string *userp) ->size_t{ - size_t totalSize = size * nmemb; - userp->append((char *)contents, totalSize); - return totalSize; - }; - - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - - // Handle headers - struct curl_slist *headers = nullptr; - headers = curl_slist_append(headers, "User-Agent: openpilot-" COMMA_VERSION); - if (!token.empty()) { - headers = curl_slist_append(headers, ("Authorization: JWT " + token).c_str()); - } - curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); - - CURLcode res = curl_easy_perform(curl); - - if (response_code) { - curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, response_code); - } - - curl_slist_free_all(headers); - curl_easy_cleanup(curl); - - return res == CURLE_OK ? readBuffer : std::string{}; -} - -} // namespace CommaApi diff --git a/tools/replay/api.h b/tools/replay/api.h deleted file mode 100644 index dff59c06590..00000000000 --- a/tools/replay/api.h +++ /dev/null @@ -1,15 +0,0 @@ -#pragma once - -#include -#include - -#include "common/util.h" -#include "third_party/json11/json11.hpp" - -namespace CommaApi2 { - -const std::string BASE_URL = util::getenv("API_HOST", "https://api.commadotai.com").c_str(); -std::string create_token(bool use_jwt, const json11::Json& payloads = {}, int expiry = 3600); -std::string httpGet(const std::string &url, long *response_code = nullptr); - -} // namespace CommaApi2 diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 4d43df2da83..2d21b4efc02 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -9,6 +9,7 @@ #include "common/ratekeeper.h" #include "common/util.h" #include "common/version.h" +#include "tools/replay/py_downloader.h" namespace { diff --git a/tools/replay/filereader.cc b/tools/replay/filereader.cc index cb13775a34b..93a6a1193f3 100644 --- a/tools/replay/filereader.cc +++ b/tools/replay/filereader.cc @@ -1,49 +1,14 @@ #include "tools/replay/filereader.h" -#include - #include "common/util.h" -#include "system/hardware/hw.h" -#include "tools/replay/util.h" - -std::string cacheFilePath(const std::string &url) { - static std::string cache_path = [] { - const std::string comma_cache = Path::download_cache_root(); - util::create_directories(comma_cache, 0755); - return comma_cache.back() == '/' ? comma_cache : comma_cache + "/"; - }(); - - return cache_path + sha256(getUrlWithoutQuery(url)); -} +#include "tools/replay/py_downloader.h" std::string FileReader::read(const std::string &file, std::atomic *abort) { const bool is_remote = (file.find("https://") == 0) || (file.find("http://") == 0); - const std::string local_file = is_remote ? cacheFilePath(file) : file; - std::string result; - - if ((!is_remote || cache_to_local_) && util::file_exists(local_file)) { - result = util::read_file(local_file); - } else if (is_remote) { - result = download(file, abort); - if (cache_to_local_ && !result.empty()) { - std::ofstream fs(local_file, std::ios::binary | std::ios::out); - fs.write(result.data(), result.size()); - } - } - return result; -} - -std::string FileReader::download(const std::string &url, std::atomic *abort) { - for (int i = 0; i <= max_retries_ && !(abort && *abort); ++i) { - if (i > 0) { - rWarning("download failed, retrying %d", i); - util::sleep_for(3000); - } - - std::string result = httpGet(url, chunk_size_, abort); - if (!result.empty()) { - return result; - } + if (is_remote) { + std::string local_path = PyDownloader::download(file, cache_to_local_, abort); + if (local_path.empty()) return {}; + return util::read_file(local_path); } - return {}; + return util::read_file(file); } diff --git a/tools/replay/filereader.h b/tools/replay/filereader.h index 34aa91e858e..30740cd0ac3 100644 --- a/tools/replay/filereader.h +++ b/tools/replay/filereader.h @@ -5,16 +5,10 @@ class FileReader { public: - FileReader(bool cache_to_local, size_t chunk_size = 0, int retries = 3) - : cache_to_local_(cache_to_local), chunk_size_(chunk_size), max_retries_(retries) {} + FileReader(bool cache_to_local) : cache_to_local_(cache_to_local) {} virtual ~FileReader() {} std::string read(const std::string &file, std::atomic *abort = nullptr); private: - std::string download(const std::string &url, std::atomic *abort); - size_t chunk_size_; - int max_retries_; bool cache_to_local_; }; - -std::string cacheFilePath(const std::string &url); diff --git a/tools/replay/framereader.cc b/tools/replay/framereader.cc index 96ec5915b45..13d4497a58f 100644 --- a/tools/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -7,6 +7,7 @@ #include "common/util.h" #include "third_party/libyuv/include/libyuv.h" +#include "tools/replay/py_downloader.h" #include "tools/replay/util.h" #include "system/hardware/hw.h" @@ -71,13 +72,13 @@ FrameReader::~FrameReader() { if (input_ctx) avformat_close_input(&input_ctx); } -bool FrameReader::load(CameraType type, const std::string &url, bool no_hw_decoder, std::atomic *abort, bool local_cache, int chunk_size, int retries) { - auto local_file_path = (url.find("https://") == 0 || url.find("http://") == 0) ? cacheFilePath(url) : url; - if (!util::file_exists(local_file_path)) { - FileReader f(local_cache, chunk_size, retries); - if (f.read(url, abort).empty()) { - return false; - } +bool FrameReader::load(CameraType type, const std::string &url, bool no_hw_decoder, std::atomic *abort, bool local_cache) { + std::string local_file_path; + if (url.find("https://") == 0 || url.find("http://") == 0) { + local_file_path = PyDownloader::download(url, local_cache, abort); + if (local_file_path.empty()) return false; + } else { + local_file_path = url; } return loadFromFile(type, local_file_path, no_hw_decoder, abort); } diff --git a/tools/replay/framereader.h b/tools/replay/framereader.h index d8e86fce0f1..3609d64f8b2 100644 --- a/tools/replay/framereader.h +++ b/tools/replay/framereader.h @@ -4,7 +4,6 @@ #include #include "msgq/visionipc/visionbuf.h" -#include "tools/replay/filereader.h" #include "tools/replay/util.h" #ifndef __APPLE__ @@ -22,8 +21,7 @@ class FrameReader { public: FrameReader(); ~FrameReader(); - bool load(CameraType type, const std::string &url, bool no_hw_decoder = false, std::atomic *abort = nullptr, bool local_cache = false, - int chunk_size = -1, int retries = 0); + bool load(CameraType type, const std::string &url, bool no_hw_decoder = false, std::atomic *abort = nullptr, bool local_cache = false); bool loadFromFile(CameraType type, const std::string &file, bool no_hw_decoder = false, std::atomic *abort = nullptr); bool get(int idx, VisionBuf *buf); size_t getFrameCount() const { return packets_info.size(); } diff --git a/tools/replay/logreader.cc b/tools/replay/logreader.cc index 75abb8417b5..997a4bc00f0 100644 --- a/tools/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -6,8 +6,8 @@ #include "tools/replay/util.h" #include "common/util.h" -bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, int chunk_size, int retries) { - std::string data = FileReader(local_cache, chunk_size, retries).read(url, abort); +bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache) { + std::string data = FileReader(local_cache).read(url, abort); if (!data.empty()) { if (url.find(".bz2") != std::string::npos || util::starts_with(data, "BZh9")) { data = decompressBZ2(data, abort); diff --git a/tools/replay/logreader.h b/tools/replay/logreader.h index f8d60ffadd7..9219878ace0 100644 --- a/tools/replay/logreader.h +++ b/tools/replay/logreader.h @@ -28,7 +28,7 @@ class LogReader { public: LogReader(const std::vector &filters = {}) { filters_ = filters; } bool load(const std::string &url, std::atomic *abort = nullptr, - bool local_cache = false, int chunk_size = -1, int retries = 0); + bool local_cache = false); bool load(const char *data, size_t size, std::atomic *abort = nullptr); std::vector events; diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 30f85b1700f..bdb9cf4f35a 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -1,5 +1,6 @@ #include +#include #include #include #include @@ -132,6 +133,10 @@ int main(int argc, char *argv[]) { util::set_file_descriptor_limit(1024); #endif + // The vendored ncurses static library has a wrong compiled-in terminfo path. + // Point it at the system terminfo database if not already set. + setenv("TERMINFO_DIRS", "/usr/share/terminfo:/lib/terminfo:/usr/lib/terminfo", 0); + ReplayConfig config; if (!parseArgs(argc, argv, config)) { diff --git a/tools/replay/py_downloader.cc b/tools/replay/py_downloader.cc new file mode 100644 index 00000000000..efaf3c93a25 --- /dev/null +++ b/tools/replay/py_downloader.cc @@ -0,0 +1,218 @@ +#include "tools/replay/py_downloader.h" + +#include +#include +#include +#include +#include + +#include "tools/replay/util.h" + +namespace { + +static std::mutex handler_mutex; +static DownloadProgressHandler progress_handler = nullptr; + +// Run a Python command and capture stdout. Optionally parse stderr for PROGRESS lines. +// Returns stdout content. If abort is signaled, kills the child process. +std::string runPython(const std::vector &args, std::atomic *abort = nullptr, bool parse_progress = false) { + // Build argv for execvp + std::vector argv; + argv.push_back("python3"); + argv.push_back("-m"); + argv.push_back("openpilot.tools.lib.file_downloader"); + for (const auto &a : args) { + argv.push_back(a.c_str()); + } + argv.push_back(nullptr); + + int stdout_pipe[2]; + int stderr_pipe[2]; + if (pipe(stdout_pipe) != 0) { + rWarning("py_downloader: pipe() failed"); + return {}; + } + if (pipe(stderr_pipe) != 0) { + rWarning("py_downloader: pipe() failed"); + close(stdout_pipe[0]); close(stdout_pipe[1]); + return {}; + } + + pid_t pid = fork(); + if (pid < 0) { + rWarning("py_downloader: fork() failed"); + close(stdout_pipe[0]); close(stdout_pipe[1]); + close(stderr_pipe[0]); close(stderr_pipe[1]); + return {}; + } + + if (pid == 0) { + // Child process — detach from controlling terminal so Python + // cannot corrupt terminal settings needed by ncurses in the parent. + setsid(); + int devnull = open("/dev/null", O_RDONLY); + if (devnull >= 0) { + dup2(devnull, STDIN_FILENO); + if (devnull > STDERR_FILENO) close(devnull); + } + + // Clear OPENPILOT_PREFIX so the Python process uses default paths + // (e.g. ~/.comma/auth.json). The prefix is only for IPC in the parent. + unsetenv("OPENPILOT_PREFIX"); + + close(stdout_pipe[0]); + close(stderr_pipe[0]); + dup2(stdout_pipe[1], STDOUT_FILENO); + dup2(stderr_pipe[1], STDERR_FILENO); + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + execvp("python3", const_cast(argv.data())); + _exit(127); + } + + // Parent process + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + std::string stdout_data; + std::string stderr_buf; + char buf[4096]; + + // Use select() to read from both pipes + fd_set rfds; + int max_fd = std::max(stdout_pipe[0], stderr_pipe[0]); + bool stdout_open = true, stderr_open = true; + + while (stdout_open || stderr_open) { + if (abort && *abort) { + kill(pid, SIGTERM); + break; + } + + FD_ZERO(&rfds); + if (stdout_open) FD_SET(stdout_pipe[0], &rfds); + if (stderr_open) FD_SET(stderr_pipe[0], &rfds); + + struct timeval tv = {0, 100000}; // 100ms timeout + int ret = select(max_fd + 1, &rfds, nullptr, nullptr, &tv); + if (ret < 0) break; + + if (stdout_open && FD_ISSET(stdout_pipe[0], &rfds)) { + ssize_t n = read(stdout_pipe[0], buf, sizeof(buf)); + if (n <= 0) { + stdout_open = false; + } else { + stdout_data.append(buf, n); + } + } + + if (stderr_open && FD_ISSET(stderr_pipe[0], &rfds)) { + ssize_t n = read(stderr_pipe[0], buf, sizeof(buf)); + if (n <= 0) { + stderr_open = false; + } else { + stderr_buf.append(buf, n); + // Parse complete lines from stderr + size_t pos; + while ((pos = stderr_buf.find('\n')) != std::string::npos) { + std::string line = stderr_buf.substr(0, pos); + stderr_buf.erase(0, pos + 1); + + if (parse_progress && line.rfind("PROGRESS:", 0) == 0) { + // Parse "PROGRESS::" + auto colon1 = line.find(':', 9); + if (colon1 != std::string::npos) { + try { + uint64_t cur = std::stoull(line.c_str() + 9); + uint64_t total = std::stoull(line.c_str() + colon1 + 1); + std::lock_guard lk(handler_mutex); + if (progress_handler) { + progress_handler(cur, total, true); + } + } catch (...) {} + } + } else if (line.rfind("ERROR:", 0) == 0) { + rWarning("py_downloader: %s", line.c_str() + 6); + } + } + } + } + } + + // Drain remaining pipe data to prevent child from blocking on write + for (int fd : {stdout_pipe[0], stderr_pipe[0]}) { + while (read(fd, buf, sizeof(buf)) > 0) {} + close(fd); + } + + int status; + waitpid(pid, &status, 0); + + bool failed = (abort && *abort) || + (WIFEXITED(status) && WEXITSTATUS(status) != 0) || + WIFSIGNALED(status); + if (failed) { + if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + rWarning("py_downloader: process exited with code %d", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + rWarning("py_downloader: process killed by signal %d", WTERMSIG(status)); + } + std::lock_guard lk(handler_mutex); + if (progress_handler) { + progress_handler(0, 0, false); + } + return {}; + } + + // Trim trailing newline + while (!stdout_data.empty() && (stdout_data.back() == '\n' || stdout_data.back() == '\r')) { + stdout_data.pop_back(); + } + + return stdout_data; +} + +} // namespace + +void installDownloadProgressHandler(DownloadProgressHandler handler) { + std::lock_guard lk(handler_mutex); + progress_handler = handler; +} + +namespace PyDownloader { + +std::string download(const std::string &url, bool use_cache, std::atomic *abort) { + std::vector args = {"download", url}; + if (!use_cache) { + args.push_back("--no-cache"); + } + return runPython(args, abort, true); +} + +std::string getRouteFiles(const std::string &route) { + return runPython({"route-files", route}); +} + +std::string getDevices() { + return runPython({"devices"}); +} + +std::string getDeviceRoutes(const std::string &dongle_id, int64_t start_ms, int64_t end_ms, bool preserved) { + std::vector args = {"device-routes", dongle_id}; + if (preserved) { + args.push_back("--preserved"); + } else { + if (start_ms > 0) { + args.push_back("--start"); + args.push_back(std::to_string(start_ms)); + } + if (end_ms > 0) { + args.push_back("--end"); + args.push_back(std::to_string(end_ms)); + } + } + return runPython(args); +} + +} // namespace PyDownloader diff --git a/tools/replay/py_downloader.h b/tools/replay/py_downloader.h new file mode 100644 index 00000000000..535189784c8 --- /dev/null +++ b/tools/replay/py_downloader.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +typedef std::function DownloadProgressHandler; +void installDownloadProgressHandler(DownloadProgressHandler handler); + +namespace PyDownloader { + +// Downloads url to local cache, returns local file path. Reports progress via installDownloadProgressHandler. +std::string download(const std::string &url, bool use_cache = true, std::atomic *abort = nullptr); + +// Returns JSON string of route files (same format as /v1/route/.../files API) +std::string getRouteFiles(const std::string &route); + +// Returns JSON string of user's devices +std::string getDevices(); + +// Returns JSON string of device routes +std::string getDeviceRoutes(const std::string &dongle_id, int64_t start_ms = 0, int64_t end_ms = 0, bool preserved = false); + +} // namespace PyDownloader diff --git a/tools/replay/route.cc b/tools/replay/route.cc index ba008282675..e7b8ed6bba5 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -6,7 +6,7 @@ #include "third_party/json11/json11.hpp" #include "system/hardware/hw.h" -#include "tools/replay/api.h" +#include "tools/replay/py_downloader.h" #include "tools/replay/replay.h" #include "tools/replay/util.h" @@ -103,43 +103,44 @@ bool Route::loadFromAutoSource() { return !segments_.empty(); } -bool Route::loadFromServer(int retries) { - const std::string url = CommaApi2::BASE_URL + "/v1/route/" + route_.str + "/files"; - for (int i = 1; i <= retries; ++i) { - long response_code = 0; - std::string result = CommaApi2::httpGet(url, &response_code); - if (response_code == 200) { - return loadFromJson(result); - } +bool Route::loadFromServer() { + std::string result = PyDownloader::getRouteFiles(route_.str); + if (result.empty()) { + err_ = RouteLoadError::NetworkError; + rWarning("Failed to fetch route files from server"); + return false; + } + + // Check for error field in JSON response + std::string parse_err; + auto json = json11::Json::parse(result, parse_err); + if (!parse_err.empty()) { + err_ = RouteLoadError::NetworkError; + rWarning("Failed to parse route files response"); + return false; + } - if (response_code == 401 || response_code == 403) { + if (json.is_object() && json["error"].is_string()) { + const std::string &error = json["error"].string_value(); + if (error == "unauthorized") { rWarning(">> Unauthorized. Authenticate with tools/lib/auth.py <<"); err_ = RouteLoadError::Unauthorized; - break; - } - if (response_code == 404) { + } else if (error == "not_found") { rWarning("The specified route could not be found on the server."); err_ = RouteLoadError::FileNotFound; - break; + } else { + rWarning("API error: %s", error.c_str()); + err_ = RouteLoadError::NetworkError; } - - err_ = RouteLoadError::NetworkError; - rWarning("Retrying %d/%d", i, retries); - util::sleep_for(3000); + return false; } - return false; + return loadFromJson(json); } -bool Route::loadFromJson(const std::string &json) { +bool Route::loadFromJson(const json11::Json &json) { const static std::regex rx(R"(\/(\d+)\/)"); - std::string err; - auto jsonData = json11::Json::parse(json, err); - if (!err.empty()) { - rWarning("JSON parsing error: %s", err.c_str()); - return false; - } - for (const auto &value : jsonData.object_items()) { + for (const auto &value : json.object_items()) { const auto &urlArray = value.second.array_items(); for (const auto &url : urlArray) { std::string url_str = url.string_value(); @@ -225,10 +226,10 @@ void Segment::loadFile(int id, const std::string file) { bool success = false; if (id < MAX_CAMERAS) { frames[id] = std::make_unique(); - success = frames[id]->load((CameraType)id, file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache, 20 * 1024 * 1024, 3); + success = frames[id]->load((CameraType)id, file, flags & REPLAY_FLAG_NO_HW_DECODER, &abort_, local_cache); } else { log = std::make_unique(filters_); - success = log->load(file, &abort_, local_cache, 0, 3); + success = log->load(file, &abort_, local_cache); } if (!success) { diff --git a/tools/replay/route.h b/tools/replay/route.h index 0375252a199..119a81152e0 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -8,6 +8,7 @@ #include #include +#include "third_party/json11/json11.hpp" #include "tools/replay/framereader.h" #include "tools/replay/logreader.h" #include "tools/replay/util.h" @@ -55,8 +56,8 @@ class Route { bool loadSegments(); bool loadFromAutoSource(); bool loadFromLocal(); - bool loadFromServer(int retries = 3); - bool loadFromJson(const std::string &json); + bool loadFromServer(); + bool loadFromJson(const json11::Json &json); void addFileToSegment(int seg_num, const std::string &file); RouteIdentifier route_ = {}; std::string data_dir_; diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index aed3de59a8c..45fcc981915 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -1,5 +1,6 @@ #define CATCH_CONFIG_MAIN #include "catch2/catch.hpp" +#include "tools/replay/filereader.h" #include "tools/replay/replay.h" const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; diff --git a/tools/replay/timeline.cc b/tools/replay/timeline.cc index 39ea8b1bedf..08dca5c89eb 100644 --- a/tools/replay/timeline.cc +++ b/tools/replay/timeline.cc @@ -55,7 +55,7 @@ void Timeline::buildTimeline(const Route &route, uint64_t route_start_ts, bool l if (should_exit_) break; auto log = std::make_shared(); - if (!log->load(segment.second.qlog, &should_exit_, local_cache, 0, 3) || log->events.empty()) { + if (!log->load(segment.second.qlog, &should_exit_, local_cache) || log->events.empty()) { continue; // Skip if log loading fails or no events } diff --git a/tools/replay/util.cc b/tools/replay/util.cc index 481564322e4..7294de82820 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -1,20 +1,13 @@ #include "tools/replay/util.h" #include -#include #include #include -#include -#include #include #include -#include #include -#include #include -#include -#include #include #include "common/timing.h" @@ -51,91 +44,6 @@ void logMessage(ReplyMsgType type, const char *fmt, ...) { free(msg_buf); } -namespace { - -struct CURLGlobalInitializer { - CURLGlobalInitializer() { curl_global_init(CURL_GLOBAL_DEFAULT); } - ~CURLGlobalInitializer() { curl_global_cleanup(); } -}; - -static CURLGlobalInitializer curl_initializer; - -template -struct MultiPartWriter { - T *buf; - size_t *total_written; - size_t offset; - size_t end; - - size_t write(char *data, size_t size, size_t count) { - size_t bytes = size * count; - if ((offset + bytes) > end) return 0; - - if constexpr (std::is_same::value) { - memcpy(buf->data() + offset, data, bytes); - } else if constexpr (std::is_same::value) { - buf->seekp(offset); - buf->write(data, bytes); - } - - offset += bytes; - *total_written += bytes; - return bytes; - } -}; - -template -size_t write_cb(char *data, size_t size, size_t count, void *userp) { - auto w = (MultiPartWriter *)userp; - return w->write(data, size, count); -} - -size_t dumy_write_cb(char *data, size_t size, size_t count, void *userp) { return size * count; } - -struct DownloadStats { - void installDownloadProgressHandler(DownloadProgressHandler handler) { - std::lock_guard lk(lock); - download_progress_handler = handler; - } - - void add(const std::string &url, uint64_t total_bytes) { - std::lock_guard lk(lock); - items[url] = {0, total_bytes}; - } - - void remove(const std::string &url) { - std::lock_guard lk(lock); - items.erase(url); - } - - void update(const std::string &url, uint64_t downloaded, bool success = true) { - std::lock_guard lk(lock); - items[url].first = downloaded; - - auto stat = std::accumulate(items.begin(), items.end(), std::pair{}, [=](auto &a, auto &b){ - return std::pair{a.first + b.second.first, a.second + b.second.second}; - }); - double tm = millis_since_boot(); - if (download_progress_handler && ((tm - prev_tm) > 500 || !success || stat.first >= stat.second)) { - download_progress_handler(stat.first, stat.second, success); - prev_tm = tm; - } - } - - std::mutex lock; - std::map> items; - double prev_tm = 0; - DownloadProgressHandler download_progress_handler = nullptr; -}; - -static DownloadStats download_stats; - -} // namespace - -void installDownloadProgressHandler(DownloadProgressHandler handler) { - download_stats.installDownloadProgressHandler(handler); -} - std::string formattedDataSize(size_t size) { if (size < 1024) { return std::to_string(size) + " B"; @@ -146,140 +54,11 @@ std::string formattedDataSize(size_t size) { } } -size_t getRemoteFileSize(const std::string &url, std::atomic *abort) { - CURL *curl = curl_easy_init(); - if (!curl) return -1; - - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dumy_write_cb); - curl_easy_setopt(curl, CURLOPT_HEADER, 1); - curl_easy_setopt(curl, CURLOPT_NOBODY, 1); - curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); - - CURLM *cm = curl_multi_init(); - curl_multi_add_handle(cm, curl); - int still_running = 1; - while (still_running > 0 && !(abort && *abort)) { - CURLMcode mc = curl_multi_perform(cm, &still_running); - if (mc != CURLM_OK) break; - if (still_running > 0) { - curl_multi_wait(cm, nullptr, 0, 1000, nullptr); - } - } - - double content_length = -1; - curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); - curl_multi_remove_handle(cm, curl); - curl_easy_cleanup(curl); - curl_multi_cleanup(cm); - return content_length > 0 ? (size_t)content_length : 0; -} - std::string getUrlWithoutQuery(const std::string &url) { size_t idx = url.find("?"); return (idx == std::string::npos ? url : url.substr(0, idx)); } -template -bool httpDownload(const std::string &url, T &buf, size_t chunk_size, size_t content_length, std::atomic *abort) { - download_stats.add(url, content_length); - - int parts = 1; - if (chunk_size > 0 && content_length > 10 * 1024 * 1024) { - parts = std::nearbyint(content_length / (float)chunk_size); - parts = std::clamp(parts, 1, 5); - } - - CURLM *cm = curl_multi_init(); - size_t written = 0; - std::map> writers; - const int part_size = content_length / parts; - for (int i = 0; i < parts; ++i) { - CURL *eh = curl_easy_init(); - writers[eh] = { - .buf = &buf, - .total_written = &written, - .offset = (size_t)(i * part_size), - .end = i == parts - 1 ? content_length : (i + 1) * part_size, - }; - curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, write_cb); - curl_easy_setopt(eh, CURLOPT_WRITEDATA, (void *)(&writers[eh])); - curl_easy_setopt(eh, CURLOPT_URL, url.c_str()); - curl_easy_setopt(eh, CURLOPT_RANGE, util::string_format("%d-%d", writers[eh].offset, writers[eh].end - 1).c_str()); - curl_easy_setopt(eh, CURLOPT_HTTPGET, 1); - curl_easy_setopt(eh, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(eh, CURLOPT_FOLLOWLOCATION, 1); - - curl_multi_add_handle(cm, eh); - } - - int still_running = 1; - size_t prev_written = 0; - while (still_running > 0 && !(abort && *abort)) { - CURLMcode mc = curl_multi_perform(cm, &still_running); - if (mc != CURLM_OK) { - break; - } - if (still_running > 0) { - curl_multi_wait(cm, nullptr, 0, 1000, nullptr); - } - - if (((written - prev_written) / (double)content_length) >= 0.01) { - download_stats.update(url, written); - prev_written = written; - } - } - - CURLMsg *msg; - int msgs_left = -1; - int complete = 0; - while ((msg = curl_multi_info_read(cm, &msgs_left)) && !(abort && *abort)) { - if (msg->msg == CURLMSG_DONE) { - if (msg->data.result == CURLE_OK) { - long res_status = 0; - curl_easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &res_status); - if (res_status == 206) { - complete++; - } else { - rWarning("Download failed: http error code: %d", res_status); - } - } else { - rWarning("Download failed: connection failure: %d", msg->data.result); - } - } - } - - bool success = complete == parts; - download_stats.update(url, written, success); - download_stats.remove(url); - - for (const auto &[e, w] : writers) { - curl_multi_remove_handle(cm, e); - curl_easy_cleanup(e); - } - curl_multi_cleanup(cm); - - return success; -} - -std::string httpGet(const std::string &url, size_t chunk_size, std::atomic *abort) { - size_t size = getRemoteFileSize(url, abort); - if (size == 0) return {}; - - std::string result(size, '\0'); - return httpDownload(url, result, chunk_size, size, abort) ? result : ""; -} - -bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size, std::atomic *abort) { - size_t size = getRemoteFileSize(url, abort); - if (size == 0) return false; - - std::ofstream of(file, std::ios::binary | std::ios::out); - of.seekp(size - 1).write("\0", 1); - return httpDownload(url, of, chunk_size, size, abort); -} - std::string decompressBZ2(const std::string &in, std::atomic *abort) { return decompressBZ2((std::byte *)in.data(), in.size(), abort); } diff --git a/tools/replay/util.h b/tools/replay/util.h index 1f61951d213..ee92190337d 100644 --- a/tools/replay/util.h +++ b/tools/replay/util.h @@ -53,12 +53,6 @@ std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic std::string decompressZST(const std::string &in, std::atomic *abort = nullptr); std::string decompressZST(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); std::string getUrlWithoutQuery(const std::string &url); -size_t getRemoteFileSize(const std::string &url, std::atomic *abort = nullptr); -std::string httpGet(const std::string &url, size_t chunk_size = 0, std::atomic *abort = nullptr); - -typedef std::function DownloadProgressHandler; -void installDownloadProgressHandler(DownloadProgressHandler); -bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size = 0, std::atomic *abort = nullptr); std::string formattedDataSize(size_t size); std::string extractFileName(const std::string& file); std::vector split(std::string_view source, char delimiter); From 8856585129e374ccb2a149dadb091478606d7c42 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 28 Feb 2026 21:14:51 -0800 Subject: [PATCH 424/685] new demo route (#37457) --- selfdrive/debug/mem_usage.py | 2 +- tools/cabana/README.md | 6 +++--- tools/clip/run.py | 2 +- tools/jotpluggler/README.md | 10 +++++----- tools/jotpluggler/pluggle.py | 2 +- tools/plotjuggler/README.md | 8 ++++---- tools/plotjuggler/juggle.py | 2 +- tools/replay/README.md | 8 ++++---- tools/replay/replay.h | 2 +- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/selfdrive/debug/mem_usage.py b/selfdrive/debug/mem_usage.py index 3451bfc3d61..bc0e97e7cae 100755 --- a/selfdrive/debug/mem_usage.py +++ b/selfdrive/debug/mem_usage.py @@ -8,7 +8,7 @@ from openpilot.common.utils import tabulate from openpilot.tools.lib.logreader import LogReader -DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" MB = 1024 * 1024 TABULATE_OPTS = dict(tablefmt="simple_grid", stralign="center", numalign="center") diff --git a/tools/cabana/README.md b/tools/cabana/README.md index 7933098e341..a721b1aa13b 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -45,17 +45,17 @@ cabana --demo To load a specific route for replay, provide the route as an argument: ```shell -cabana "a2a0ccea32023010|2023-07-27--13-01-19" +cabana "5beb9b58bd12b691/0000010a--a51155e496" ``` -Replace "0ccea32023010|2023-07-27--13-01-19" with your desired route identifier. +Replace "5beb9b58bd12b691/0000010a--a51155e496" with your desired route identifier. ### Running Cabana with multiple cameras To run Cabana with multiple cameras, use the following command: ```shell -cabana "a2a0ccea32023010|2023-07-27--13-01-19" --dcam --ecam +cabana "5beb9b58bd12b691/0000010a--a51155e496" --dcam --ecam ``` ### Streaming CAN Messages from a comma Device diff --git a/tools/clip/run.py b/tools/clip/run.py index 5711cafa592..a5587f239b2 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -24,7 +24,7 @@ from msgq.visionipc import VisionIpcServer, VisionStreamType FRAMERATE = 20 -DEMO_ROUTE, DEMO_START, DEMO_END = 'a2a0ccea32023010/2023-07-27--13-01-19', 90, 105 +DEMO_ROUTE, DEMO_START, DEMO_END = '5beb9b58bd12b691/0000010a--a51155e496', 90, 105 logger = logging.getLogger('clip') diff --git a/tools/jotpluggler/README.md b/tools/jotpluggler/README.md index c5b43dbd3ad..d5e4b8ab0f5 100644 --- a/tools/jotpluggler/README.md +++ b/tools/jotpluggler/README.md @@ -21,17 +21,17 @@ options: Example using route name: -`./pluggle.py "a2a0ccea32023010/2023-07-27--13-01-19"` +`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496"` Examples using segment: -`./pluggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1"` +`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/1"` -`./pluggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1/q" # use qlogs` +`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/1/q" # use qlogs` Example using segment range: -`./pluggle.py "a2a0ccea32023010/2023-07-27--13-01-19/0:1"` +`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/0:1"` ## Demo @@ -41,7 +41,7 @@ For a quick demo, run this command: ## Basic Usage/Features: -- The text box to load a route is a the top left of the page, accepts standard openpilot format routes (e.g. `a2a0ccea32023010/2023-07-27--13-01-19/0:1`, `https://connect.comma.ai/a2a0ccea32023010/2023-07-27--13-01-19/`) +- The text box to load a route is a the top left of the page, accepts standard openpilot format routes (e.g. `5beb9b58bd12b691/0000010a--a51155e496/0:1`, `https://connect.comma.ai/5beb9b58bd12b691/0000010a--a51155e496/`) - The Play/Pause button is at the bottom of the screen, you can drag the bottom slider to seek. The timeline in timeseries plots are synced with the slider. - The Timeseries List sidebar has several dropdowns, the fields each show the field name and value, synced with the timeline (will show N/A until the time of the first message in that field is reached). - There is a search bar for the timeseries list, you can search for structs or fields, or both by separating with a "/" diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py index 92664ae5b32..2fb6e3e2f4e 100755 --- a/tools/jotpluggler/pluggle.py +++ b/tools/jotpluggler/pluggle.py @@ -12,7 +12,7 @@ from openpilot.tools.jotpluggler.datatree import DataTree from openpilot.tools.jotpluggler.layout import LayoutManager -DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" class WorkerManager: diff --git a/tools/plotjuggler/README.md b/tools/plotjuggler/README.md index 62905a252d1..3249971bfcf 100644 --- a/tools/plotjuggler/README.md +++ b/tools/plotjuggler/README.md @@ -35,17 +35,17 @@ optional arguments: Example using route name: -`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19"` +`./juggle.py "5beb9b58bd12b691/0000010a--a51155e496"` Examples using segment: -`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1"` +`./juggle.py "5beb9b58bd12b691/0000010a--a51155e496/1"` -`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19/1/q" # use qlogs` +`./juggle.py "5beb9b58bd12b691/0000010a--a51155e496/1/q" # use qlogs` Example using segment range: -`./juggle.py "a2a0ccea32023010/2023-07-27--13-01-19/0:1"` +`./juggle.py "5beb9b58bd12b691/0000010a--a51155e496/0:1"` ## Streaming diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index c04efd50b49..c86945ef6bb 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -21,7 +21,7 @@ os.environ['LD_LIBRARY_PATH'] = os.environ.get('LD_LIBRARY_PATH', '') + f":{juggle_dir}/bin/" -DEMO_ROUTE = "a2a0ccea32023010|2023-07-27--13-01-19" +DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" RELEASES_URL = "https://github.com/commaai/PlotJuggler/releases/download/latest" INSTALL_DIR = os.path.join(juggle_dir, "bin") PLOTJUGGLER_BIN = os.path.join(juggle_dir, "bin/plotjuggler") diff --git a/tools/replay/README.md b/tools/replay/README.md index 794c08f6a39..d2beda99403 100644 --- a/tools/replay/README.md +++ b/tools/replay/README.md @@ -19,7 +19,7 @@ You can replay a route from your comma account by specifying the route name. tools/replay/replay # Example: -tools/replay/replay 'a2a0ccea32023010|2023-07-27--13-01-19' +tools/replay/replay '5beb9b58bd12b691/0000010a--a51155e496' # Replay the default demo route: tools/replay/replay --demo @@ -34,10 +34,10 @@ tools/replay/replay --data_dir="/path_to/route" # Example: # If you have a local route stored at /path_to_routes with segments like: -# a2a0ccea32023010|2023-07-27--13-01-19--0 -# a2a0ccea32023010|2023-07-27--13-01-19--1 +# 5beb9b58bd12b691/0000010a--a51155e496--0 +# 5beb9b58bd12b691/0000010a--a51155e496--1 # You can replay it like this: -tools/replay/replay "a2a0ccea32023010|2023-07-27--13-01-19" --data_dir="/path_to_routes" +tools/replay/replay "5beb9b58bd12b691/0000010a--a51155e496" --data_dir="/path_to_routes" ``` ## Send Messages via ZMQ diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 58c1b71b8a8..3e2bc7c00e8 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -12,7 +12,7 @@ #include "tools/replay/seg_mgr.h" #include "tools/replay/timeline.h" -#define DEMO_ROUTE "a2a0ccea32023010|2023-07-27--13-01-19" +#define DEMO_ROUTE "5beb9b58bd12b691/0000010a--a51155e496" enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, From d634894300b901fe84b7e575522c834a48f929ba Mon Sep 17 00:00:00 2001 From: Robbe Derks Date: Sat, 28 Feb 2026 21:16:48 -0800 Subject: [PATCH 425/685] Fix thermal sensor readouts on four (#37310) --- cereal/log.capnp | 3 ++- system/hardware/base.py | 3 ++- system/hardware/tici/hardware.py | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 80a604d8601..d1f85d325ca 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -499,7 +499,8 @@ struct DeviceState @0xa4d8b5af2aa492eb { pmicTempC @39 :List(Float32); intakeTempC @46 :Float32; exhaustTempC @47 :Float32; - caseTempC @48 :Float32; + gnssTempC @48 :Float32; + bottomSocTempC @50 :Float32; maxTempC @44 :Float32; # max of other temps, used to control fan thermalZones @38 :List(ThermalZone); thermalStatus @14 :ThermalStatus; diff --git a/system/hardware/base.py b/system/hardware/base.py index c12c0758f50..1a19f908c62 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -52,7 +52,8 @@ class ThermalConfig: memory: ThermalZone | None = None intake: ThermalZone | None = None exhaust: ThermalZone | None = None - case: ThermalZone | None = None + gnss: ThermalZone | None = None + bottomSoc: ThermalZone | None = None def get_msg(self): ret = {} diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 8219f0a5872..15c6e416955 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -321,11 +321,12 @@ def shutdown(self): os.system("sudo poweroff") def get_thermal_config(self): - intake, exhaust, case = None, None, None + intake, exhaust, gnss, bottomSoc = None, None, None, None if self.get_device_type() == "mici": - case = ThermalZone("case") + gnss = ThermalZone("gnss") intake = ThermalZone("intake") exhaust = ThermalZone("exhaust") + bottomSoc = ThermalZone("bottom_soc") return ThermalConfig(cpu=[ThermalZone(f"cpu{i}-silver-usr") for i in range(4)] + [ThermalZone(f"cpu{i}-gold-usr") for i in range(4)], gpu=[ThermalZone("gpu0-usr"), ThermalZone("gpu1-usr")], @@ -334,7 +335,8 @@ def get_thermal_config(self): pmic=[ThermalZone("pm8998_tz"), ThermalZone("pm8005_tz")], intake=intake, exhaust=exhaust, - case=case) + gnss=gnss, + bottomSoc=bottomSoc) def set_display_power(self, on): try: From d44fde71176764e93112dd06ddc018bb509d0a3b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 21:50:59 -0800 Subject: [PATCH 426/685] multilang: return original string if missing (#37487) should return og if not there --- system/ui/lib/multilang.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 343c06a1e86..7f191bc75e2 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -181,7 +181,7 @@ def change_language(self, language_code: str) -> None: self.setup() def tr(self, text: str) -> str: - return self._translations.get(text, text) + return self._translations.get(text, text) or text def trn(self, singular: str, plural: str, n: int) -> str: if singular in self._plurals: From b10c2ada79029de2c8dc166ed40d5b3ecce5821f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 21:52:37 -0800 Subject: [PATCH 427/685] ui: match updater/setup/installer figma text styles (#37500) * from figma * match setup figma now * lint --- selfdrive/ui/installer/installer.cc | 10 +++---- system/ui/mici_setup.py | 41 ++++++++++++++--------------- system/ui/mici_updater.py | 40 ++++++++++++++++------------ 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 072fa4e24b0..75994541946 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -30,7 +30,7 @@ const std::string VALID_CACHE_PATH = "/data/.openpilot_cache"; #define TMP_INSTALL_PATH "/data/tmppilot" -const int FONT_SIZE = 120; +const int FONT_SIZE = 160; extern const uint8_t str_continue[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_start"); extern const uint8_t str_continue_end[] asm("_binary_selfdrive_ui_installer_continue_openpilot_sh_end"); @@ -88,7 +88,7 @@ void finishInstall() { int text_width = MeasureText(m, FONT_SIZE); DrawTextEx(font_display, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE); } else { - DrawTextEx(font_display, "finishing setup", (Vector2){8, 10}, 82, 0, WHITE); + DrawTextEx(font_display, "finishing setup", (Vector2){12, 0}, 77, 0, (Color){255, 255, 255, (unsigned char)(255 * 0.9)}); } EndDrawing(); util::sleep_for(60 * 1000); @@ -106,10 +106,10 @@ void renderProgress(int progress) { DrawRectangleRec(bar, (Color){70, 91, 234, 255}); DrawTextEx(font_inter, (std::to_string(progress) + "%").c_str(), (Vector2){150, 670}, 85, 0, WHITE); } else { - DrawTextEx(font_display, "installing", (Vector2){8, 10}, 82, 0, WHITE); + DrawTextEx(font_display, "installing...", (Vector2){12, 0}, 77, 0, (Color){255, 255, 255, (unsigned char)(255 * 0.9)}); const std::string percent_str = std::to_string(progress) + "%"; - DrawTextEx(font_roman, percent_str.c_str(), (Vector2){6, (float)(GetScreenHeight() - 128 + 18)}, 128, 0, - (Color){255, 255, 255, (unsigned char)(255 * 0.9 * 0.35)}); + DrawTextEx(font_inter, percent_str.c_str(), (Vector2){12, (float)(GetScreenHeight() - 154 + 20)}, 154, 0, + (Color){255, 255, 255, (unsigned char)(255 * 0.9 * 0.65)}); } EndDrawing(); diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index ca635444392..79965f8c6c5 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -23,8 +23,7 @@ from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, - SmallCircleIconButton, WidishRoundedButton, SmallRedPillButton, - FullRoundedButton) + SmallCircleIconButton, WidishRoundedButton, FullRoundedButton) from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiUIMici @@ -355,9 +354,9 @@ class DownloadingPage(Widget): def __init__(self): super().__init__() - self._title_label = UnifiedLabel("downloading", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + self._title_label = UnifiedLabel("downloading...", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) - self._progress_label = UnifiedLabel("", 128, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.35)), + self._progress_label = UnifiedLabel("", 132, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) self._progress = 0 @@ -367,15 +366,15 @@ def set_progress(self, progress: int): def _render(self, rect: rl.Rectangle): self._title_label.render(rl.Rectangle( - rect.x + 20, - rect.y + 10, + rect.x + 12, + rect.y + 2, rect.width, 64, )) self._progress_label.render(rl.Rectangle( - rect.x + 20, - rect.y + 20, + rect.x + 12, + rect.y + 18, rect.width, rect.height, )) @@ -390,11 +389,10 @@ def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: s self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), font_weight=FontWeight.ROMAN) - self._reboot_button = SmallRedPillButton("reboot") - self._reboot_button.set_click_callback(reboot_callback) - self._reboot_button.set_enabled(lambda: self.enabled) # for nav stack + self._reboot_slider = SmallSlider("reboot", reboot_callback) + self._reboot_slider.set_enabled(lambda: self.enabled) # for nav stack - self._retry_button = WideRoundedButton("retry") + self._retry_button = SmallButton("retry") self._retry_button.set_click_callback(retry_callback) self._retry_button.set_enabled(lambda: self.enabled) # for nav stack @@ -416,20 +414,21 @@ def _render(self, rect: rl.Rectangle): 36, )) - self._reboot_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._reboot_button.rect.height, - self._reboot_button.rect.width, - self._reboot_button.rect.height, - )) - + self._retry_button.set_opacity(1 - self._reboot_slider.slider_percentage) self._retry_button.render(rl.Rectangle( - rect.x + 8 + self._reboot_button.rect.width + 8, - rect.y + rect.height - self._retry_button.rect.height, + self._rect.x + 8, + self._rect.y + self._rect.height - self._retry_button.rect.height, self._retry_button.rect.width, self._retry_button.rect.height, )) + self._reboot_slider.render(rl.Rectangle( + self._rect.x + self._rect.width - self._reboot_slider.rect.width, + self._rect.y + self._rect.height - self._reboot_slider.rect.height, + self._reboot_slider.rect.width, + self._reboot_slider.rect.height, + )) + class NetworkSetupPage(Widget): def __init__(self, wifi_manager, continue_callback: Callable, back_callback: Callable): diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 5de72ac8c49..c98b3107097 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -7,10 +7,9 @@ from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import gui_text_box, gui_label, UnifiedLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.button import FullRoundedButton from openpilot.system.ui.mici_setup import NetworkSetupPage, FailedPage, NetworkConnectivityMonitor @@ -47,7 +46,7 @@ def __init__(self, updater_path, manifest_path): self._continue_button = FullRoundedButton("continue") self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI)) - self._title_label = UnifiedLabel("update required", 48, text_color=rl.Color(255, 115, 0, 255), + self._title_label = UnifiedLabel("update required", 48, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._subtitle_label = UnifiedLabel("The download size is approximately 1GB.", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), @@ -56,6 +55,12 @@ def __init__(self, updater_path, manifest_path): self._update_failed_page = FailedPage(HARDWARE.reboot, self._update_failed_retry_callback, title="update failed") + self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.DISPLAY, line_height=0.8) + self._progress_percent_label = UnifiedLabel("", 132, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), + font_weight=FontWeight.ROMAN, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + def _network_setup_back_callback(self): self.set_current_screen(Screen.PROMPT) @@ -139,20 +144,21 @@ def render_prompt_screen(self, rect: rl.Rectangle): )) def render_progress_screen(self, rect: rl.Rectangle): - title_rect = rl.Rectangle(self._rect.x + 6, self._rect.y - 5, self._rect.width - 12, self._rect.height - 8) - if ' ' in self.progress_text: - font_size = 62 - else: - font_size = 82 - gui_text_box(title_rect, self.progress_text, font_size, font_weight=FontWeight.DISPLAY, - color=rl.Color(255, 255, 255, int(255 * 0.9))) - - progress_value = f"{self.progress_value}%" - text_height = measure_text_cached(gui_app.font(FontWeight.ROMAN), progress_value, 128).y - progress_rect = rl.Rectangle(self._rect.x + 6, self._rect.y + self._rect.height - text_height + 18, - self._rect.width - 12, text_height) - gui_label(progress_rect, progress_value, 128, font_weight=FontWeight.ROMAN, - color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.35))) + self._progress_title_label.set_text(self.progress_text.replace("_", "_\n") + "...") + self._progress_title_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 2, + rect.width, + self._progress_title_label.get_content_height(int(rect.width - 20)), + )) + + self._progress_percent_label.set_text(f"{self.progress_value}%") + self._progress_percent_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 18, + rect.width, + rect.height, + )) def _update_state(self): self._wifi_manager.process_callbacks() From 24d3f07a2f5556ae1a495f1a2ac4de5148742e5b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 22:02:42 -0800 Subject: [PATCH 428/685] Add review terms & conditions to device settings w only accept --- selfdrive/ui/mici/layouts/settings/device.py | 9 +++++++-- system/ui/mici_setup.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index b6f6f71d69b..a810a5d1a06 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog -from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide +from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget @@ -308,15 +308,20 @@ def uninstall_openpilot_callback(): review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget))) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) + terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png") + terms_btn.set_click_callback(lambda: gui_app.push_widget(TermsPage(on_accept=gui_app.pop_widget))) + terms_btn.set_enabled(lambda: ui_state.is_offroad()) + self._scroller.add_widgets([ DeviceInfoLayoutMici(), UpdateOpenpilotBigButton(), PairBigButton(), review_training_guide_btn, driver_cam_btn, + terms_btn, + regulatory_btn, reset_calibration_btn, uninstall_openpilot_btn, - regulatory_btn, reboot_btn, self._power_off_btn, ]) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 79965f8c6c5..16a781aa915 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -249,6 +249,7 @@ def _render_content(self, scroll_offset): pass def _render(self, _): + rl.draw_rectangle_rec(self._rect, rl.BLACK) scroll_offset = round(self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16)) if scroll_offset <= self._scrolled_down_offset: From 5ef0040ac6d08c81c8f89281ac4d1e0189956364 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 28 Feb 2026 23:51:56 -0800 Subject: [PATCH 429/685] ui: delay click callback (#37502) * delay click callback * actually may be better * clean up * clean up --- selfdrive/ui/mici/layouts/settings/device.py | 6 ++++++ selfdrive/ui/mici/layouts/settings/network/wifi_ui.py | 2 ++ selfdrive/ui/mici/widgets/button.py | 2 ++ system/ui/widgets/__init__.py | 10 +++++++++- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index a810a5d1a06..b7ee5b6f45a 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -117,6 +117,8 @@ def _get_label_font_size(self): return 64 def _update_state(self): + super()._update_state() + if ui_state.prime_state.is_paired(): self.set_text("paired") if ui_state.prime_state.is_prime(): @@ -164,6 +166,8 @@ def offroad_transition(self): self.set_enabled(True) def _handle_mouse_release(self, mouse_pos: MousePos): + super()._handle_mouse_release(mouse_pos) + if not system_time_valid(): dlg = BigDialog(tr("Please connect to Wi-Fi to update"), "") gui_app.push_widget(dlg) @@ -191,6 +195,8 @@ def set_value(self, value: str): self.set_text("update openpilot") def _update_state(self): + super()._update_state() + if ui_state.started: self.set_enabled(False) return diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index cc45daf24b8..22d3d1d0da9 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -215,6 +215,8 @@ def _is_connected(self): return self._wifi_manager.connected_ssid == self._network.ssid def _update_state(self): + super()._update_state() + if any((self._network_missing, self._is_connecting, self._is_connected, self._network_forgetting, self._network.security_type == SecurityType.UNSUPPORTED)): self.set_enabled(False) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 231dafa8eb7..b5bd65e2de8 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -36,6 +36,7 @@ def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (6 # State self.set_rect(rl.Rectangle(0, 0, 180, 180)) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._click_delay = 0.075 # Icons self._txt_icon = gui_app.texture(icon, *icon_size) @@ -117,6 +118,7 @@ def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "" self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._click_delay = 0.075 self._shake_start: float | None = None self._rotate_icon_t: float | None = None diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 1e2f7838113..568f58b9854 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -30,6 +30,8 @@ def __init__(self): self._enabled: bool | Callable[[], bool] = True self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None + self._click_delay: float | None = None # seconds to hold is_pressed after release + self._click_release_time: float | None = None self._click_callback: Callable[[], None] | None = None self._multi_touch = False self.__was_awake = True @@ -51,7 +53,8 @@ def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: @property def is_pressed(self) -> bool: - return any(self.__is_pressed) + # if actually pressed or holding after release + return any(self.__is_pressed) or self._click_release_time is not None @property def enabled(self) -> bool: @@ -98,6 +101,9 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: self._update_state() + if self._click_release_time is not None and rl.get_time() >= self._click_release_time: + self._click_release_time = None + if not self.is_visible: return None @@ -182,6 +188,8 @@ def _handle_mouse_press(self, mouse_pos: MousePos) -> None: def _handle_mouse_release(self, mouse_pos: MousePos) -> None: """Optionally handle mouse release events.""" + if self._click_delay is not None: + self._click_release_time = rl.get_time() + self._click_delay if self._click_callback: self._click_callback() From 61658fbfe3bf308eaf8ff0d2593d704d8c946e85 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 1 Mar 2026 00:56:52 -0800 Subject: [PATCH 430/685] mici setup: new start button (#37501) * pressable * slow * fast and looks great * 0.075 * clean up * fix missing * clean up --- .../assets/icons_mici/setup/green_button.png | 3 --- .../icons_mici/setup/green_button_pressed.png | 3 --- .../assets/icons_mici/setup/start_button.png | 3 +++ .../icons_mici/setup/start_button_pressed.png | 3 +++ system/ui/mici_setup.py | 18 ++++++++++++------ 5 files changed, 18 insertions(+), 12 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/setup/green_button.png delete mode 100644 selfdrive/assets/icons_mici/setup/green_button_pressed.png create mode 100644 selfdrive/assets/icons_mici/setup/start_button.png create mode 100644 selfdrive/assets/icons_mici/setup/start_button_pressed.png diff --git a/selfdrive/assets/icons_mici/setup/green_button.png b/selfdrive/assets/icons_mici/setup/green_button.png deleted file mode 100644 index 9708cfe2847..00000000000 --- a/selfdrive/assets/icons_mici/setup/green_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:163ac31cb990bdddfe552efef9a68870404caadb1c40fa8a5042b5ae956e6b4c -size 24687 diff --git a/selfdrive/assets/icons_mici/setup/green_button_pressed.png b/selfdrive/assets/icons_mici/setup/green_button_pressed.png deleted file mode 100644 index 030ce61d5b6..00000000000 --- a/selfdrive/assets/icons_mici/setup/green_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6e4614adb2d3d0e44c64a855c221ec462a7aee22fff26132ad551035141c1a53 -size 62056 diff --git a/selfdrive/assets/icons_mici/setup/start_button.png b/selfdrive/assets/icons_mici/setup/start_button.png new file mode 100644 index 00000000000..58d8a8b7488 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/start_button.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5e993247160edcbc9c3cba3efa93169028568d484bcfd0bf64f3e3a7ec7556c0 +size 18608 diff --git a/selfdrive/assets/icons_mici/setup/start_button_pressed.png b/selfdrive/assets/icons_mici/setup/start_button_pressed.png new file mode 100644 index 00000000000..564de0bef78 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/start_button_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1c4f1002ecde9a2b33779c2e784a39b492b4c8d76abc063e935ce0aa971925dd +size 65513 diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 16a781aa915..33e0b8cf684 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -14,6 +14,7 @@ import pyray as rl from cereal import log +from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.common.swaglog import cloudlog from openpilot.common.utils import run_cmd @@ -109,16 +110,21 @@ def __init__(self): font_weight=FontWeight.DISPLAY, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) - self._start_bg_txt = gui_app.texture("icons_mici/setup/green_button.png", 520, 224) - self._start_bg_pressed_txt = gui_app.texture("icons_mici/setup/green_button_pressed.png", 520, 224) + self._start_bg_txt = gui_app.texture("icons_mici/setup/start_button.png", 500, 224, keep_aspect_ratio=False) + self._start_bg_pressed_txt = gui_app.texture("icons_mici/setup/start_button_pressed.png", 500, 224, keep_aspect_ratio=False) + self._scale_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._click_delay = 0.075 def _render(self, rect: rl.Rectangle): - draw_x = rect.x + (rect.width - self._start_bg_txt.width) / 2 - draw_y = rect.y + (rect.height - self._start_bg_txt.height) / 2 + scale = self._scale_filter.update(1.07 if self.is_pressed else 1.0) + base_draw_x = rect.x + (rect.width - self._start_bg_txt.width) / 2 + base_draw_y = rect.y + (rect.height - self._start_bg_txt.height) / 2 + draw_x = base_draw_x + (self._start_bg_txt.width * (1 - scale)) / 2 + draw_y = base_draw_y + (self._start_bg_txt.height * (1 - scale)) / 2 texture = self._start_bg_pressed_txt if self.is_pressed else self._start_bg_txt - rl.draw_texture(texture, int(draw_x), int(draw_y), rl.WHITE) + rl.draw_texture_ex(texture, (draw_x, draw_y), 0, scale, rl.WHITE) - self._title.render(rect) + self._title.render(rl.Rectangle(rect.x, rect.y + (draw_y - base_draw_y), rect.width, rect.height)) class SoftwareSelectionPage(Widget): From a7de971334b79fbc0de0386f8be8b70b99e69158 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 1 Mar 2026 02:41:51 -0800 Subject: [PATCH 431/685] mici setup: use nav stack (#37507) * pressable * slow * fast and looks great * 0.075 * clean up * fix missing * clean up * mici setup use nav stack! * remove flat state! * todo * clean up * clean up ordering * clean up * reset progress on show, dont mutate nav stack from thread * reset text on show too * rename * clean up --- system/ui/mici_setup.py | 243 +++++++++++++++----------------- system/ui/widgets/nav_widget.py | 10 +- 2 files changed, 123 insertions(+), 130 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 33e0b8cf684..9656702c18b 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -7,7 +7,6 @@ import urllib.request import urllib.error from urllib.parse import urlparse -from enum import IntEnum import shutil from collections.abc import Callable @@ -23,6 +22,7 @@ from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, SmallCircleIconButton, WidishRoundedButton, FullRoundedButton) from openpilot.system.ui.widgets.label import UnifiedLabel @@ -92,16 +92,6 @@ def _run(self): break -class SetupState(IntEnum): - GETTING_STARTED = 0 - NETWORK_SETUP = 1 - NETWORK_SETUP_CUSTOM_SOFTWARE = 2 - SOFTWARE_SELECTION = 3 - DOWNLOADING = 4 - DOWNLOAD_FAILED = 5 - CUSTOM_SOFTWARE_WARNING = 6 - - class StartPage(Widget): def __init__(self): super().__init__() @@ -127,15 +117,24 @@ def _render(self, rect: rl.Rectangle): self._title.render(rl.Rectangle(rect.x, rect.y + (draw_y - base_draw_y), rect.width, rect.height)) -class SoftwareSelectionPage(Widget): +class SoftwareSelectionPage(NavWidget): def __init__(self, use_openpilot_callback: Callable, use_custom_software_callback: Callable): super().__init__() self._openpilot_slider = LargerSlider("slide to use\nopenpilot", use_openpilot_callback) - self._openpilot_slider.set_enabled(lambda: self.enabled) + self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False) - self._custom_software_slider.set_enabled(lambda: self.enabled) + self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 + + def _update_state(self): + super()._update_state() + if self.is_dismissing: + self.reset() def reset(self): self._openpilot_slider.reset() @@ -367,11 +366,16 @@ def __init__(self): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) self._progress = 0 + def show_event(self): + super().show_event() + self.set_progress(0) + def set_progress(self, progress: int): self._progress = progress self._progress_label.set_text(f"{progress}%") def _render(self, rect: rl.Rectangle): + rl.draw_rectangle_rec(rect, rl.BLACK) self._title_label.render(rl.Rectangle( rect.x + 12, rect.y + 2, @@ -387,9 +391,10 @@ def _render(self, rect: rl.Rectangle): )) -class FailedPage(Widget): +class FailedPage(NavWidget): def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): super().__init__() + self.set_back_callback(retry_callback) self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) @@ -406,6 +411,10 @@ def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: s def set_reason(self, reason: str): self._reason_label.set_text(reason) + def show_event(self): + super().show_event() + self._reboot_slider.reset() + def _render(self, rect: rl.Rectangle): self._title_label.render(rl.Rectangle( rect.x + 8, @@ -437,10 +446,18 @@ def _render(self, rect: rl.Rectangle): )) -class NetworkSetupPage(Widget): - def __init__(self, wifi_manager, continue_callback: Callable, back_callback: Callable): +class NetworkSetupPage(NavWidget): + def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], + back_callback: Callable[[], None] | None): super().__init__() - self._wifi_ui = WifiUIMici(wifi_manager) + self.set_back_callback(back_callback) + + self._wifi_manager = WifiManager() + self._wifi_manager.set_active(True) + self._network_monitor = network_monitor + self._custom_software = False + self._prev_has_internet = False + self._wifi_ui = WifiUIMici(self._wifi_manager) self._no_wifi_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 58, 50) self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 58, 50) @@ -458,9 +475,27 @@ def __init__(self, wifi_manager, continue_callback: Callable, back_callback: Cal self._continue_button = WidishRoundedButton("continue") self._continue_button.set_enabled(False) - self._continue_button.set_click_callback(continue_callback) + self._continue_button.set_click_callback(lambda: continue_callback(self._custom_software)) + + gui_app.add_nav_stack_tick(self._nav_stack_tick) + + def show_event(self): + super().show_event() + self._prev_has_internet = False + self._network_monitor.reset() + self._set_has_internet(False) + + def _nav_stack_tick(self): + self._wifi_manager.process_callbacks() + + has_internet = self._network_monitor.network_connected.is_set() + if has_internet != self._prev_has_internet: + self._set_has_internet(has_internet) + if has_internet: + gui_app.pop_widgets_to(self) + self._prev_has_internet = has_internet - def set_has_internet(self, has_internet: bool): + def _set_has_internet(self, has_internet: bool): if has_internet: self._network_header.set_title("connected to internet") self._network_header.set_icon(self._wifi_full_txt) @@ -470,6 +505,9 @@ def set_has_internet(self, has_internet: bool): self._network_header.set_icon(self._no_wifi_txt) self._continue_button.set_enabled(False) + def set_custom_software(self, custom_software: bool): + self._custom_software = custom_software + def _render(self, _): self._network_header.render(rl.Rectangle( self._rect.x + 16, @@ -503,122 +541,55 @@ def _render(self, _): class Setup(Widget): def __init__(self): super().__init__() - self.state = SetupState.GETTING_STARTED - self.failed_url = "" - self.failed_reason = "" self.download_url = "" self.download_progress = 0 self.download_thread = None - self._wifi_manager = WifiManager() - self._wifi_manager.set_active(True) + self._download_failed_reason: str | None = None + self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() - self._prev_has_internet = False - gui_app.add_nav_stack_tick(self._nav_stack_tick) - self._start_page = StartPage() - self._start_page.set_click_callback(self._getting_started_button_callback) + def getting_started_button_callback(): + self._software_selection_page.reset() + gui_app.push_widget(self._software_selection_page) - self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_button_callback, - self._network_setup_back_button_callback) - # TODO: change these to touch_valid - self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack + self._start_page = StartPage() + self._start_page.set_click_callback(getting_started_button_callback) + self._start_page.set_enabled(lambda: self.enabled) # for nav stack - self._software_selection_page = SoftwareSelectionPage(self._software_selection_continue_button_callback, - self._software_selection_custom_software_button_callback) - self._software_selection_page.set_enabled(lambda: self.enabled) # for nav stack + self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_button_callback, + self._pop_to_software_selection) + self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) - self._download_failed_page = FailedPage(HARDWARE.reboot, self._download_failed_startover_button_callback) - self._download_failed_page.set_enabled(lambda: self.enabled) # for nav stack + self._download_failed_page = FailedPage(HARDWARE.reboot, self._pop_to_software_selection) - self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, - self._custom_software_warning_back_button_callback) - self._custom_software_warning_page.set_enabled(lambda: self.enabled) # for nav stack + self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, self._pop_to_software_selection) self._downloading_page = DownloadingPage() - def _nav_stack_tick(self): - has_internet = self._network_monitor.network_connected.is_set() - if has_internet and not self._prev_has_internet: - gui_app.pop_widgets_to(self) - self._prev_has_internet = has_internet - - def _update_state(self): - self._wifi_manager.process_callbacks() + gui_app.add_nav_stack_tick(self._nav_stack_tick) - def _set_state(self, state: SetupState): - self.state = state - if self.state == SetupState.SOFTWARE_SELECTION: - self._software_selection_page.reset() - elif self.state == SetupState.CUSTOM_SOFTWARE_WARNING: - self._custom_software_warning_page.reset() + def _nav_stack_tick(self): + self._downloading_page.set_progress(self.download_progress) - if self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE): - self._network_setup_page.show_event() - self._network_monitor.reset() - else: - self._network_setup_page.hide_event() + if self._download_failed_reason is not None: + reason = self._download_failed_reason + self._download_failed_reason = None + self._download_failed_page.set_reason(reason) + gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders + gui_app.push_widget(self._download_failed_page) def _render(self, rect: rl.Rectangle): - if self.state == SetupState.GETTING_STARTED: - self._start_page.render(rect) - elif self.state in (SetupState.NETWORK_SETUP, SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE): - self.render_network_setup(rect) - elif self.state == SetupState.SOFTWARE_SELECTION: - self._software_selection_page.render(rect) - elif self.state == SetupState.CUSTOM_SOFTWARE_WARNING: - self._custom_software_warning_page.render(rect) - elif self.state == SetupState.DOWNLOADING: - self.render_downloading(rect) - elif self.state == SetupState.DOWNLOAD_FAILED: - self._download_failed_page.render(rect) - - def _custom_software_warning_back_button_callback(self): - self._set_state(SetupState.SOFTWARE_SELECTION) - - def _getting_started_button_callback(self): - self._set_state(SetupState.SOFTWARE_SELECTION) - - def _software_selection_continue_button_callback(self): - self.use_openpilot() - - def _software_selection_custom_software_button_callback(self): - self._set_state(SetupState.CUSTOM_SOFTWARE_WARNING) - - def _software_selection_custom_software_continue(self): - self._set_state(SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE) - - def _download_failed_startover_button_callback(self): - self._set_state(SetupState.GETTING_STARTED) - - def _network_setup_back_button_callback(self): - self._set_state(SetupState.SOFTWARE_SELECTION) - - def _network_setup_continue_button_callback(self): - if self.state == SetupState.NETWORK_SETUP: - self.download(OPENPILOT_URL) - elif self.state == SetupState.NETWORK_SETUP_CUSTOM_SOFTWARE: - def handle_keyboard_result(text): - url = text.strip() - if url: - self.download(url) - - keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) - gui_app.push_widget(keyboard) + self._start_page.render(rect) def close(self): self._network_monitor.stop() - def render_network_setup(self, rect: rl.Rectangle): - has_internet = self._network_monitor.network_connected.is_set() - self._network_setup_page.set_has_internet(has_internet) - self._network_setup_page.render(rect) + def _pop_to_software_selection(self): + # reset sliders after dismiss completes + gui_app.pop_widgets_to(self._software_selection_page, self._software_selection_page.reset) - def render_downloading(self, rect: rl.Rectangle): - self._downloading_page.set_progress(self.download_progress) - self._downloading_page.render(rect) - - def use_openpilot(self): + def _use_openpilot(self): if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): os.remove(VALID_CACHE_PATH) with open(TMP_CONTINUE_PATH, "w") as f: @@ -631,17 +602,40 @@ def use_openpilot(self): time.sleep(0.1) gui_app.request_close() else: - self._set_state(SetupState.NETWORK_SETUP) + self._push_network_setup(custom_software=False) + + def _push_network_setup(self, custom_software: bool): + self._network_setup_page.set_custom_software(custom_software) + gui_app.push_widget(self._network_setup_page) + + def _software_selection_custom_software_continue(self): + gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders + self._push_network_setup(custom_software=True) - def download(self, url: str): + def _network_setup_continue_button_callback(self, custom_software): + if not custom_software: + gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders + self._download(OPENPILOT_URL) + else: + def handle_keyboard_result(text): + url = text.strip() + if url: + gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders + self._download(url) + + keyboard = BigInputDialog("custom software URL", "openpilot.comma.ai", confirm_callback=handle_keyboard_result) + gui_app.push_widget(keyboard) + + def _download(self, url: str): # autocomplete incomplete URLs if re.match("^([^/.]+)/([^/]+)$", url): url = f"https://installer.comma.ai/{url}" parsed = urlparse(url, scheme='https') self.download_url = (urlparse(f"https://{url}") if not parsed.netloc else parsed).geturl() + self.download_progress = 0 - self._set_state(SetupState.DOWNLOADING) + gui_app.push_widget(self._downloading_page) self.download_thread = threading.Thread(target=self._download_thread, daemon=True) self.download_thread.start() @@ -672,7 +666,6 @@ def _download_thread(self): if total_size: self.download_progress = int(downloaded * 100 / total_size) - self._downloading_page.set_progress(self.download_progress) is_elf = False with open(tmpfile, 'rb') as f: @@ -680,7 +673,7 @@ def _download_thread(self): is_elf = header == b'\x7fELF' if not is_elf: - self.download_failed(self.download_url, "No custom software found at this URL.") + self._download_failed_reason = "No custom software found at this URL." return # AGNOS might try to execute the installer before this process exits. @@ -697,17 +690,9 @@ def _download_thread(self): except urllib.error.HTTPError as e: if e.code == 409: - error_msg = "Incompatible openpilot version" - self.download_failed(self.download_url, error_msg) + self._download_failed_reason = "Incompatible openpilot version" except Exception: - error_msg = "Invalid URL" - self.download_failed(self.download_url, error_msg) - - def download_failed(self, url: str, reason: str): - self.failed_url = url - self.failed_reason = reason - self._download_failed_page.set_reason(reason) - self._set_state(SetupState.DOWNLOAD_FAILED) + self._download_failed_reason = "Invalid URL" def main(): diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index fe17f12a8f1..67203d53f49 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -63,7 +63,8 @@ def __init__(self): self._playing_dismiss_animation = False # released and animating away self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._dismiss_callback: Callable[[], None] | None = None + self._back_callback: Callable[[], None] | None = None # persistent callback for any back navigation + self._dismiss_callback: Callable[[], None] | None = None # transient callback for programmatic dismiss # TODO: move this state into NavBar self._nav_bar = NavBar() @@ -75,6 +76,9 @@ def _back_enabled(self) -> bool: # the top of a vertical scroll panel to prevent erroneous swipes return True + def set_back_callback(self, callback: Callable[[], None]) -> None: + self._back_callback = callback + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: super()._handle_mouse_event(mouse_event) @@ -145,6 +149,10 @@ def _update_state(self): if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: gui_app.pop_widget() + + if self._back_callback is not None: + self._back_callback() + if self._dismiss_callback is not None: self._dismiss_callback() self._dismiss_callback = None From 308475fcc933664c545a18ef31f73578a65b0288 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 1 Mar 2026 03:27:41 -0800 Subject: [PATCH 432/685] Fix continue being enabled under WifiUi --- system/ui/mici_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 9656702c18b..28da2f1a4b7 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -499,7 +499,7 @@ def _set_has_internet(self, has_internet: bool): if has_internet: self._network_header.set_title("connected to internet") self._network_header.set_icon(self._wifi_full_txt) - self._continue_button.set_enabled(self.enabled) + self._continue_button.set_enabled(lambda: self.enabled) else: self._network_header.set_title(self._waiting_text) self._network_header.set_icon(self._no_wifi_txt) From c244a5d4856554e64f206f507d551fad43d8f923 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 1 Mar 2026 03:41:20 -0800 Subject: [PATCH 433/685] Update BigInputDialog to remove default URL Remove default URL from custom software input dialog. --- system/ui/mici_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 28da2f1a4b7..75ea316ece1 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -623,7 +623,7 @@ def handle_keyboard_result(text): gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(url) - keyboard = BigInputDialog("custom software URL", "openpilot.comma.ai", confirm_callback=handle_keyboard_result) + keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) gui_app.push_widget(keyboard) def _download(self, url: str): From 041606de4ce60d4ce0705ba20e59357612764dc2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 10:01:41 -0800 Subject: [PATCH 434/685] fix font output targets (#37511) --- selfdrive/ui/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 5556883ef87..4d7448c62f8 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -6,7 +6,7 @@ Import('env', 'arch', 'common') generator = File("#selfdrive/assets/fonts/process.py") source_files = Glob("#selfdrive/assets/fonts/*.ttf") + Glob("#selfdrive/assets/fonts/*.otf") output_files = [ - (f.abspath.split('.')[0] + ".fnt", f.abspath.split('.')[0] + ".png") + (f"#{Path(f.path).with_suffix('.fnt')}", f"#{Path(f.path).with_suffix('.png')}") for f in source_files if "NotoColor" not in f.name ] From f9b5d1e9e5e0c3e4371f7338c3373a81d0513fe3 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 10:46:26 -0800 Subject: [PATCH 435/685] use vendored libyuv from dependencies (#37512) * vendor libyuv from dependencies * relock libyuv to latest vendor branch * install cmake in macOS setup when missing * lock * unused? * rm that * no yuv for the larch --- SConstruct | 5 +- pyproject.toml | 1 + system/loggerd/SConscript | 3 +- system/loggerd/encoder/ffmpeg_encoder.cc | 2 +- system/loggerd/encoder/v4l_encoder.cc | 1 - third_party/libyuv/.gitignore | 2 - third_party/libyuv/Darwin/lib/libyuv.a | 3 - third_party/libyuv/LICENSE | 29 - third_party/libyuv/aarch64 | 1 - third_party/libyuv/build.sh | 37 - third_party/libyuv/include/libyuv.h | 32 - .../libyuv/include/libyuv/basic_types.h | 118 - third_party/libyuv/include/libyuv/compare.h | 78 - .../libyuv/include/libyuv/compare_row.h | 84 - third_party/libyuv/include/libyuv/convert.h | 259 --- .../libyuv/include/libyuv/convert_argb.h | 319 --- .../libyuv/include/libyuv/convert_from.h | 179 -- .../libyuv/include/libyuv/convert_from_argb.h | 190 -- third_party/libyuv/include/libyuv/cpu_id.h | 81 - .../libyuv/include/libyuv/macros_msa.h | 76 - .../libyuv/include/libyuv/mjpeg_decoder.h | 192 -- .../libyuv/include/libyuv/planar_functions.h | 529 ----- third_party/libyuv/include/libyuv/rotate.h | 117 - .../libyuv/include/libyuv/rotate_argb.h | 33 - .../libyuv/include/libyuv/rotate_row.h | 121 - third_party/libyuv/include/libyuv/row.h | 1963 ----------------- third_party/libyuv/include/libyuv/scale.h | 103 - .../libyuv/include/libyuv/scale_argb.h | 56 - third_party/libyuv/include/libyuv/scale_row.h | 503 ----- third_party/libyuv/include/libyuv/version.h | 16 - .../libyuv/include/libyuv/video_common.h | 184 -- third_party/libyuv/larch64/lib/libyuv.a | 3 - third_party/libyuv/x86_64/lib/libyuv.a | 3 - tools/replay/framereader.cc | 2 +- tools/setup_dependencies.sh | 12 + uv.lock | 31 +- 36 files changed, 38 insertions(+), 5330 deletions(-) delete mode 100644 third_party/libyuv/.gitignore delete mode 100644 third_party/libyuv/Darwin/lib/libyuv.a delete mode 100644 third_party/libyuv/LICENSE delete mode 120000 third_party/libyuv/aarch64 delete mode 100755 third_party/libyuv/build.sh delete mode 100644 third_party/libyuv/include/libyuv.h delete mode 100644 third_party/libyuv/include/libyuv/basic_types.h delete mode 100644 third_party/libyuv/include/libyuv/compare.h delete mode 100644 third_party/libyuv/include/libyuv/compare_row.h delete mode 100644 third_party/libyuv/include/libyuv/convert.h delete mode 100644 third_party/libyuv/include/libyuv/convert_argb.h delete mode 100644 third_party/libyuv/include/libyuv/convert_from.h delete mode 100644 third_party/libyuv/include/libyuv/convert_from_argb.h delete mode 100644 third_party/libyuv/include/libyuv/cpu_id.h delete mode 100644 third_party/libyuv/include/libyuv/macros_msa.h delete mode 100644 third_party/libyuv/include/libyuv/mjpeg_decoder.h delete mode 100644 third_party/libyuv/include/libyuv/planar_functions.h delete mode 100644 third_party/libyuv/include/libyuv/rotate.h delete mode 100644 third_party/libyuv/include/libyuv/rotate_argb.h delete mode 100644 third_party/libyuv/include/libyuv/rotate_row.h delete mode 100644 third_party/libyuv/include/libyuv/row.h delete mode 100644 third_party/libyuv/include/libyuv/scale.h delete mode 100644 third_party/libyuv/include/libyuv/scale_argb.h delete mode 100644 third_party/libyuv/include/libyuv/scale_row.h delete mode 100644 third_party/libyuv/include/libyuv/version.h delete mode 100644 third_party/libyuv/include/libyuv/video_common.h delete mode 100644 third_party/libyuv/larch64/lib/libyuv.a delete mode 100644 third_party/libyuv/x86_64/lib/libyuv.a diff --git a/SConstruct b/SConstruct index 59ffaf4c76a..da70bc39247 100644 --- a/SConstruct +++ b/SConstruct @@ -44,12 +44,13 @@ if arch != "larch64": import eigen import ffmpeg as ffmpeg_pkg import libjpeg + import libyuv import ncurses import openssl3 import python3_dev import zeromq import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, ncurses, openssl3, zeromq, zstd] + pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs @@ -89,7 +90,6 @@ env = Environment( "#third_party/acados/include/blasfeo/include", "#third_party/acados/include/hpipm/include", "#third_party/catch2/include", - "#third_party/libyuv/include", [x.INCLUDE_DIR for x in pkgs], ], LIBPATH=[ @@ -98,7 +98,6 @@ env = Environment( "#third_party", "#selfdrive/pandad", "#rednose/helpers", - f"#third_party/libyuv/{arch}/lib", f"#third_party/acados/{arch}/lib", [x.LIB_DIR for x in pkgs], ], diff --git a/pyproject.toml b/pyproject.toml index c3467342b62..6516c8cd5bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ dependencies = [ "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", + "libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv", "openssl3 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=openssl3", "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index 827c1ce5d0e..b02c4092408 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -2,11 +2,12 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', - 'yuv', 'pthread', 'z', 'm', 'zstd'] + 'pthread', 'z', 'm', 'zstd'] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] + libs += ['yuv'] if arch == "Darwin": # exclude v4l diff --git a/system/loggerd/encoder/ffmpeg_encoder.cc b/system/loggerd/encoder/ffmpeg_encoder.cc index 4d6be471821..275a2e481e5 100644 --- a/system/loggerd/encoder/ffmpeg_encoder.cc +++ b/system/loggerd/encoder/ffmpeg_encoder.cc @@ -9,7 +9,7 @@ #define __STDC_CONSTANT_MACROS -#include "third_party/libyuv/include/libyuv.h" +#include "libyuv.h" extern "C" { #include diff --git a/system/loggerd/encoder/v4l_encoder.cc b/system/loggerd/encoder/v4l_encoder.cc index 383fa2f0f55..cabd9fd997d 100644 --- a/system/loggerd/encoder/v4l_encoder.cc +++ b/system/loggerd/encoder/v4l_encoder.cc @@ -7,7 +7,6 @@ #include "common/util.h" #include "common/timing.h" -#include "third_party/libyuv/include/libyuv.h" #include "third_party/linux/include/msm_media_info.h" // has to be in this order diff --git a/third_party/libyuv/.gitignore b/third_party/libyuv/.gitignore deleted file mode 100644 index 1e943ae6c6d..00000000000 --- a/third_party/libyuv/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/libyuv/ -!*.a diff --git a/third_party/libyuv/Darwin/lib/libyuv.a b/third_party/libyuv/Darwin/lib/libyuv.a deleted file mode 100644 index b72979ef19c..00000000000 --- a/third_party/libyuv/Darwin/lib/libyuv.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:497e01c39e1629a89afa730341fe066c2e926966c5f050003e7fde2ce46d9da3 -size 863648 diff --git a/third_party/libyuv/LICENSE b/third_party/libyuv/LICENSE deleted file mode 100644 index c911747a6b5..00000000000 --- a/third_party/libyuv/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -Copyright 2011 The LibYuv Project Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of Google nor the names of its contributors may - be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third_party/libyuv/aarch64 b/third_party/libyuv/aarch64 deleted file mode 120000 index 062c65e8d99..00000000000 --- a/third_party/libyuv/aarch64 +++ /dev/null @@ -1 +0,0 @@ -larch64/ \ No newline at end of file diff --git a/third_party/libyuv/build.sh b/third_party/libyuv/build.sh deleted file mode 100755 index 11f88ab46cf..00000000000 --- a/third_party/libyuv/build.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/usr/bin/env bash -set -e - -export SOURCE_DATE_EPOCH=0 -export ZERO_AR_DATE=1 - -DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" - -ARCHNAME=$(uname -m) -if [ -f /TICI ]; then - ARCHNAME="larch64" -fi - -if [[ "$OSTYPE" == "darwin"* ]]; then - ARCHNAME="Darwin" -fi - -cd $DIR -if [ ! -d libyuv ]; then - git clone --single-branch https://chromium.googlesource.com/libyuv/libyuv -fi - -cd libyuv -git checkout 4a14cb2e81235ecd656e799aecaaf139db8ce4a2 - -# build -cmake . -make -j$(nproc) - -INSTALL_DIR="$DIR/$ARCHNAME" -rm -rf $INSTALL_DIR -mkdir -p $INSTALL_DIR - -rm -rf $DIR/include -mkdir -p $INSTALL_DIR/lib -cp $DIR/libyuv/libyuv.a $INSTALL_DIR/lib -cp -r $DIR/libyuv/include $DIR diff --git a/third_party/libyuv/include/libyuv.h b/third_party/libyuv/include/libyuv.h deleted file mode 100644 index aeffd5ef7a4..00000000000 --- a/third_party/libyuv/include/libyuv.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_H_ -#define INCLUDE_LIBYUV_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/compare.h" -#include "libyuv/convert.h" -#include "libyuv/convert_argb.h" -#include "libyuv/convert_from.h" -#include "libyuv/convert_from_argb.h" -#include "libyuv/cpu_id.h" -#include "libyuv/mjpeg_decoder.h" -#include "libyuv/planar_functions.h" -#include "libyuv/rotate.h" -#include "libyuv/rotate_argb.h" -#include "libyuv/row.h" -#include "libyuv/scale.h" -#include "libyuv/scale_argb.h" -#include "libyuv/scale_row.h" -#include "libyuv/version.h" -#include "libyuv/video_common.h" - -#endif // INCLUDE_LIBYUV_H_ diff --git a/third_party/libyuv/include/libyuv/basic_types.h b/third_party/libyuv/include/libyuv/basic_types.h deleted file mode 100644 index 5b760ee0d4d..00000000000 --- a/third_party/libyuv/include/libyuv/basic_types.h +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_BASIC_TYPES_H_ -#define INCLUDE_LIBYUV_BASIC_TYPES_H_ - -#include // for NULL, size_t - -#if defined(_MSC_VER) && (_MSC_VER < 1600) -#include // for uintptr_t on x86 -#else -#include // for uintptr_t -#endif - -#ifndef GG_LONGLONG -#ifndef INT_TYPES_DEFINED -#define INT_TYPES_DEFINED -#ifdef COMPILER_MSVC -typedef unsigned __int64 uint64; -typedef __int64 int64; -#ifndef INT64_C -#define INT64_C(x) x ## I64 -#endif -#ifndef UINT64_C -#define UINT64_C(x) x ## UI64 -#endif -#define INT64_F "I64" -#else // COMPILER_MSVC -#if defined(__LP64__) && !defined(__OpenBSD__) && !defined(__APPLE__) -typedef unsigned long uint64; // NOLINT -typedef long int64; // NOLINT -#ifndef INT64_C -#define INT64_C(x) x ## L -#endif -#ifndef UINT64_C -#define UINT64_C(x) x ## UL -#endif -#define INT64_F "l" -#else // defined(__LP64__) && !defined(__OpenBSD__) && !defined(__APPLE__) -typedef unsigned long long uint64; // NOLINT -typedef long long int64; // NOLINT -#ifndef INT64_C -#define INT64_C(x) x ## LL -#endif -#ifndef UINT64_C -#define UINT64_C(x) x ## ULL -#endif -#define INT64_F "ll" -#endif // __LP64__ -#endif // COMPILER_MSVC -typedef unsigned int uint32; -typedef int int32; -typedef unsigned short uint16; // NOLINT -typedef short int16; // NOLINT -typedef unsigned char uint8; -typedef signed char int8; -#endif // INT_TYPES_DEFINED -#endif // GG_LONGLONG - -// Detect compiler is for x86 or x64. -#if defined(__x86_64__) || defined(_M_X64) || \ - defined(__i386__) || defined(_M_IX86) -#define CPU_X86 1 -#endif -// Detect compiler is for ARM. -#if defined(__arm__) || defined(_M_ARM) -#define CPU_ARM 1 -#endif - -#ifndef ALIGNP -#ifdef __cplusplus -#define ALIGNP(p, t) \ - (reinterpret_cast(((reinterpret_cast(p) + \ - ((t) - 1)) & ~((t) - 1)))) -#else -#define ALIGNP(p, t) \ - ((uint8*)((((uintptr_t)(p) + ((t) - 1)) & ~((t) - 1)))) /* NOLINT */ -#endif -#endif - -#if !defined(LIBYUV_API) -#if defined(_WIN32) || defined(__CYGWIN__) -#if defined(LIBYUV_BUILDING_SHARED_LIBRARY) -#define LIBYUV_API __declspec(dllexport) -#elif defined(LIBYUV_USING_SHARED_LIBRARY) -#define LIBYUV_API __declspec(dllimport) -#else -#define LIBYUV_API -#endif // LIBYUV_BUILDING_SHARED_LIBRARY -#elif defined(__GNUC__) && (__GNUC__ >= 4) && !defined(__APPLE__) && \ - (defined(LIBYUV_BUILDING_SHARED_LIBRARY) || \ - defined(LIBYUV_USING_SHARED_LIBRARY)) -#define LIBYUV_API __attribute__ ((visibility ("default"))) -#else -#define LIBYUV_API -#endif // __GNUC__ -#endif // LIBYUV_API - -#define LIBYUV_BOOL int -#define LIBYUV_FALSE 0 -#define LIBYUV_TRUE 1 - -// Visual C x86 or GCC little endian. -#if defined(__x86_64__) || defined(_M_X64) || \ - defined(__i386__) || defined(_M_IX86) || \ - defined(__arm__) || defined(_M_ARM) || \ - (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) -#define LIBYUV_LITTLE_ENDIAN -#endif - -#endif // INCLUDE_LIBYUV_BASIC_TYPES_H_ diff --git a/third_party/libyuv/include/libyuv/compare.h b/third_party/libyuv/include/libyuv/compare.h deleted file mode 100644 index 550712de6e5..00000000000 --- a/third_party/libyuv/include/libyuv/compare.h +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_COMPARE_H_ -#define INCLUDE_LIBYUV_COMPARE_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Compute a hash for specified memory. Seed of 5381 recommended. -LIBYUV_API -uint32 HashDjb2(const uint8* src, uint64 count, uint32 seed); - -// Scan an opaque argb image and return fourcc based on alpha offset. -// Returns FOURCC_ARGB, FOURCC_BGRA, or 0 if unknown. -LIBYUV_API -uint32 ARGBDetect(const uint8* argb, int stride_argb, int width, int height); - -// Sum Square Error - used to compute Mean Square Error or PSNR. -LIBYUV_API -uint64 ComputeSumSquareError(const uint8* src_a, - const uint8* src_b, int count); - -LIBYUV_API -uint64 ComputeSumSquareErrorPlane(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height); - -static const int kMaxPsnr = 128; - -LIBYUV_API -double SumSquareErrorToPsnr(uint64 sse, uint64 count); - -LIBYUV_API -double CalcFramePsnr(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height); - -LIBYUV_API -double I420Psnr(const uint8* src_y_a, int stride_y_a, - const uint8* src_u_a, int stride_u_a, - const uint8* src_v_a, int stride_v_a, - const uint8* src_y_b, int stride_y_b, - const uint8* src_u_b, int stride_u_b, - const uint8* src_v_b, int stride_v_b, - int width, int height); - -LIBYUV_API -double CalcFrameSsim(const uint8* src_a, int stride_a, - const uint8* src_b, int stride_b, - int width, int height); - -LIBYUV_API -double I420Ssim(const uint8* src_y_a, int stride_y_a, - const uint8* src_u_a, int stride_u_a, - const uint8* src_v_a, int stride_v_a, - const uint8* src_y_b, int stride_y_b, - const uint8* src_u_b, int stride_u_b, - const uint8* src_v_b, int stride_v_b, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_COMPARE_H_ diff --git a/third_party/libyuv/include/libyuv/compare_row.h b/third_party/libyuv/include/libyuv/compare_row.h deleted file mode 100644 index 781cad3e65a..00000000000 --- a/third_party/libyuv/include/libyuv/compare_row.h +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_COMPARE_ROW_H_ -#define INCLUDE_LIBYUV_COMPARE_ROW_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif - -// Visual C 2012 required for AVX2. -#if defined(_M_IX86) && !defined(__clang__) && \ - defined(_MSC_VER) && _MSC_VER >= 1700 -#define VISUALC_HAS_AVX2 1 -#endif // VisualStudio >= 2012 - -// clang >= 3.4.0 required for AVX2. -#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) -#if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) -#define CLANG_HAS_AVX2 1 -#endif // clang >= 3.4 -#endif // __clang__ - -#if !defined(LIBYUV_DISABLE_X86) && \ - defined(_M_IX86) && (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) -#define HAS_HASHDJB2_AVX2 -#endif - -// The following are available for Visual C and GCC: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__x86_64__) || (defined(__i386__) || defined(_M_IX86))) -#define HAS_HASHDJB2_SSE41 -#define HAS_SUMSQUAREERROR_SSE2 -#endif - -// The following are available for Visual C and clangcl 32 bit: -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) && \ - (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) -#define HAS_HASHDJB2_AVX2 -#define HAS_SUMSQUAREERROR_AVX2 -#endif - -// The following are available for Neon: -#if !defined(LIBYUV_DISABLE_NEON) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_SUMSQUAREERROR_NEON -#endif - -uint32 SumSquareError_C(const uint8* src_a, const uint8* src_b, int count); -uint32 SumSquareError_SSE2(const uint8* src_a, const uint8* src_b, int count); -uint32 SumSquareError_AVX2(const uint8* src_a, const uint8* src_b, int count); -uint32 SumSquareError_NEON(const uint8* src_a, const uint8* src_b, int count); - -uint32 HashDjb2_C(const uint8* src, int count, uint32 seed); -uint32 HashDjb2_SSE41(const uint8* src, int count, uint32 seed); -uint32 HashDjb2_AVX2(const uint8* src, int count, uint32 seed); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_COMPARE_ROW_H_ diff --git a/third_party/libyuv/include/libyuv/convert.h b/third_party/libyuv/include/libyuv/convert.h deleted file mode 100644 index d44485847be..00000000000 --- a/third_party/libyuv/include/libyuv/convert.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_H_ -#define INCLUDE_LIBYUV_CONVERT_H_ - -#include "libyuv/basic_types.h" - -#include "libyuv/rotate.h" // For enum RotationMode. - -// TODO(fbarchard): fix WebRTC source to include following libyuv headers: -#include "libyuv/convert_argb.h" // For WebRTC I420ToARGB. b/620 -#include "libyuv/convert_from.h" // For WebRTC ConvertFromI420. b/620 -#include "libyuv/planar_functions.h" // For WebRTC I420Rect, CopyPlane. b/618 - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Convert I444 to I420. -LIBYUV_API -int I444ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert I422 to I420. -LIBYUV_API -int I422ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert I411 to I420. -LIBYUV_API -int I411ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Copy I420 to I420. -#define I420ToI420 I420Copy -LIBYUV_API -int I420Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert I400 (grey) to I420. -LIBYUV_API -int I400ToI420(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -#define J400ToJ420 I400ToI420 - -// Convert NV12 to I420. -LIBYUV_API -int NV12ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert NV21 to I420. -LIBYUV_API -int NV21ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_vu, int src_stride_vu, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert YUY2 to I420. -LIBYUV_API -int YUY2ToI420(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert UYVY to I420. -LIBYUV_API -int UYVYToI420(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert M420 to I420. -LIBYUV_API -int M420ToI420(const uint8* src_m420, int src_stride_m420, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert Android420 to I420. -LIBYUV_API -int Android420ToI420(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - int pixel_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// ARGB little endian (bgra in memory) to I420. -LIBYUV_API -int ARGBToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// BGRA little endian (argb in memory) to I420. -LIBYUV_API -int BGRAToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// ABGR little endian (rgba in memory) to I420. -LIBYUV_API -int ABGRToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGBA little endian (abgr in memory) to I420. -LIBYUV_API -int RGBAToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB little endian (bgr in memory) to I420. -LIBYUV_API -int RGB24ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB big endian (rgb in memory) to I420. -LIBYUV_API -int RAWToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB16 (RGBP fourcc) little endian to I420. -LIBYUV_API -int RGB565ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB15 (RGBO fourcc) little endian to I420. -LIBYUV_API -int ARGB1555ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// RGB12 (R444 fourcc) little endian to I420. -LIBYUV_API -int ARGB4444ToI420(const uint8* src_frame, int src_stride_frame, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -#ifdef HAVE_JPEG -// src_width/height provided by capture. -// dst_width/height for clipping determine final size. -LIBYUV_API -int MJPGToI420(const uint8* sample, size_t sample_size, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_width, int src_height, - int dst_width, int dst_height); - -// Query size of MJPG in pixels. -LIBYUV_API -int MJPGSize(const uint8* sample, size_t sample_size, - int* width, int* height); -#endif - -// Convert camera sample to I420 with cropping, rotation and vertical flip. -// "src_size" is needed to parse MJPG. -// "dst_stride_y" number of bytes in a row of the dst_y plane. -// Normally this would be the same as dst_width, with recommended alignment -// to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. The caller should -// allocate the I420 buffer according to rotation. -// "dst_stride_u" number of bytes in a row of the dst_u plane. -// Normally this would be the same as (dst_width + 1) / 2, with -// recommended alignment to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. -// "crop_x" and "crop_y" are starting position for cropping. -// To center, crop_x = (src_width - dst_width) / 2 -// crop_y = (src_height - dst_height) / 2 -// "src_width" / "src_height" is size of src_frame in pixels. -// "src_height" can be negative indicating a vertically flipped image source. -// "crop_width" / "crop_height" is the size to crop the src to. -// Must be less than or equal to src_width/src_height -// Cropping parameters are pre-rotation. -// "rotation" can be 0, 90, 180 or 270. -// "format" is a fourcc. ie 'I420', 'YUY2' -// Returns 0 for successful; -1 for invalid parameter. Non-zero for failure. -LIBYUV_API -int ConvertToI420(const uint8* src_frame, size_t src_size, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int crop_x, int crop_y, - int src_width, int src_height, - int crop_width, int crop_height, - enum RotationMode rotation, - uint32 format); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_H_ diff --git a/third_party/libyuv/include/libyuv/convert_argb.h b/third_party/libyuv/include/libyuv/convert_argb.h deleted file mode 100644 index dc03ac8d5dc..00000000000 --- a/third_party/libyuv/include/libyuv/convert_argb.h +++ /dev/null @@ -1,319 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_ARGB_H_ -#define INCLUDE_LIBYUV_CONVERT_ARGB_H_ - -#include "libyuv/basic_types.h" - -#include "libyuv/rotate.h" // For enum RotationMode. - -// TODO(fbarchard): This set of functions should exactly match convert.h -// TODO(fbarchard): Add tests. Create random content of right size and convert -// with C vs Opt and or to I420 and compare. -// TODO(fbarchard): Some of these functions lack parameter setting. - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Alias. -#define ARGBToARGB ARGBCopy - -// Copy ARGB to ARGB. -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I420 to ARGB. -LIBYUV_API -int I420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Duplicate prototype for function in convert_from.h for remoting. -LIBYUV_API -int I420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I422 to ARGB. -LIBYUV_API -int I422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I444 to ARGB. -LIBYUV_API -int I444ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J444 to ARGB. -LIBYUV_API -int J444ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I444 to ABGR. -LIBYUV_API -int I444ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert I411 to ARGB. -LIBYUV_API -int I411ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I420 with Alpha to preattenuated ARGB. -LIBYUV_API -int I420AlphaToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - const uint8* src_a, int src_stride_a, - uint8* dst_argb, int dst_stride_argb, - int width, int height, int attenuate); - -// Convert I420 with Alpha to preattenuated ABGR. -LIBYUV_API -int I420AlphaToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - const uint8* src_a, int src_stride_a, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height, int attenuate); - -// Convert I400 (grey) to ARGB. Reverse of ARGBToI400. -LIBYUV_API -int I400ToARGB(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J400 (jpeg grey) to ARGB. -LIBYUV_API -int J400ToARGB(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Alias. -#define YToARGB I400ToARGB - -// Convert NV12 to ARGB. -LIBYUV_API -int NV12ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert NV21 to ARGB. -LIBYUV_API -int NV21ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_vu, int src_stride_vu, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert M420 to ARGB. -LIBYUV_API -int M420ToARGB(const uint8* src_m420, int src_stride_m420, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert YUY2 to ARGB. -LIBYUV_API -int YUY2ToARGB(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert UYVY to ARGB. -LIBYUV_API -int UYVYToARGB(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J420 to ARGB. -LIBYUV_API -int J420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J422 to ARGB. -LIBYUV_API -int J422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert J420 to ABGR. -LIBYUV_API -int J420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert J422 to ABGR. -LIBYUV_API -int J422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert H420 to ARGB. -LIBYUV_API -int H420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert H422 to ARGB. -LIBYUV_API -int H422ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert H420 to ABGR. -LIBYUV_API -int H420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert H422 to ABGR. -LIBYUV_API -int H422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// BGRA little endian (argb in memory) to ARGB. -LIBYUV_API -int BGRAToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// ABGR little endian (rgba in memory) to ARGB. -LIBYUV_API -int ABGRToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGBA little endian (abgr in memory) to ARGB. -LIBYUV_API -int RGBAToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Deprecated function name. -#define BG24ToARGB RGB24ToARGB - -// RGB little endian (bgr in memory) to ARGB. -LIBYUV_API -int RGB24ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB big endian (rgb in memory) to ARGB. -LIBYUV_API -int RAWToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB16 (RGBP fourcc) little endian to ARGB. -LIBYUV_API -int RGB565ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB15 (RGBO fourcc) little endian to ARGB. -LIBYUV_API -int ARGB1555ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// RGB12 (R444 fourcc) little endian to ARGB. -LIBYUV_API -int ARGB4444ToARGB(const uint8* src_frame, int src_stride_frame, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -#ifdef HAVE_JPEG -// src_width/height provided by capture -// dst_width/height for clipping determine final size. -LIBYUV_API -int MJPGToARGB(const uint8* sample, size_t sample_size, - uint8* dst_argb, int dst_stride_argb, - int src_width, int src_height, - int dst_width, int dst_height); -#endif - -// Convert camera sample to ARGB with cropping, rotation and vertical flip. -// "src_size" is needed to parse MJPG. -// "dst_stride_argb" number of bytes in a row of the dst_argb plane. -// Normally this would be the same as dst_width, with recommended alignment -// to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. The caller should -// allocate the I420 buffer according to rotation. -// "dst_stride_u" number of bytes in a row of the dst_u plane. -// Normally this would be the same as (dst_width + 1) / 2, with -// recommended alignment to 16 bytes for better efficiency. -// If rotation of 90 or 270 is used, stride is affected. -// "crop_x" and "crop_y" are starting position for cropping. -// To center, crop_x = (src_width - dst_width) / 2 -// crop_y = (src_height - dst_height) / 2 -// "src_width" / "src_height" is size of src_frame in pixels. -// "src_height" can be negative indicating a vertically flipped image source. -// "crop_width" / "crop_height" is the size to crop the src to. -// Must be less than or equal to src_width/src_height -// Cropping parameters are pre-rotation. -// "rotation" can be 0, 90, 180 or 270. -// "format" is a fourcc. ie 'I420', 'YUY2' -// Returns 0 for successful; -1 for invalid parameter. Non-zero for failure. -LIBYUV_API -int ConvertToARGB(const uint8* src_frame, size_t src_size, - uint8* dst_argb, int dst_stride_argb, - int crop_x, int crop_y, - int src_width, int src_height, - int crop_width, int crop_height, - enum RotationMode rotation, - uint32 format); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_ARGB_H_ diff --git a/third_party/libyuv/include/libyuv/convert_from.h b/third_party/libyuv/include/libyuv/convert_from.h deleted file mode 100644 index 59c40474f1e..00000000000 --- a/third_party/libyuv/include/libyuv/convert_from.h +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_FROM_H_ -#define INCLUDE_LIBYUV_CONVERT_FROM_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/rotate.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// See Also convert.h for conversions from formats to I420. - -// I420Copy in convert to I420ToI420. - -LIBYUV_API -int I420ToI422(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -LIBYUV_API -int I420ToI444(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -LIBYUV_API -int I420ToI411(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Copy to I400. Source can be I420, I422, I444, I400, NV12 or NV21. -LIBYUV_API -int I400Copy(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -LIBYUV_API -int I420ToNV12(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -LIBYUV_API -int I420ToNV21(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height); - -LIBYUV_API -int I420ToYUY2(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToUYVY(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToARGB(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -LIBYUV_API -int I420ToBGRA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -LIBYUV_API -int I420ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -LIBYUV_API -int I420ToRGBA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height); - -LIBYUV_API -int I420ToRGB24(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToRAW(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToRGB565(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert I420 To RGB565 with 4x4 dither matrix (16 bytes). -// Values in dither matrix from 0 to 7 recommended. -// The order of the dither matrix is first byte is upper left. - -LIBYUV_API -int I420ToRGB565Dither(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - const uint8* dither4x4, int width, int height); - -LIBYUV_API -int I420ToARGB1555(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -LIBYUV_API -int I420ToARGB4444(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert I420 to specified format. -// "dst_sample_stride" is bytes in a row for the destination. Pass 0 if the -// buffer has contiguous rows. Can be negative. A multiple of 16 is optimal. -LIBYUV_API -int ConvertFromI420(const uint8* y, int y_stride, - const uint8* u, int u_stride, - const uint8* v, int v_stride, - uint8* dst_sample, int dst_sample_stride, - int width, int height, - uint32 format); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_FROM_H_ diff --git a/third_party/libyuv/include/libyuv/convert_from_argb.h b/third_party/libyuv/include/libyuv/convert_from_argb.h deleted file mode 100644 index 8d7f02f8c4d..00000000000 --- a/third_party/libyuv/include/libyuv/convert_from_argb.h +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CONVERT_FROM_ARGB_H_ -#define INCLUDE_LIBYUV_CONVERT_FROM_ARGB_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Copy ARGB to ARGB. -#define ARGBToARGB ARGBCopy -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert ARGB To BGRA. -LIBYUV_API -int ARGBToBGRA(const uint8* src_argb, int src_stride_argb, - uint8* dst_bgra, int dst_stride_bgra, - int width, int height); - -// Convert ARGB To ABGR. -LIBYUV_API -int ARGBToABGR(const uint8* src_argb, int src_stride_argb, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert ARGB To RGBA. -LIBYUV_API -int ARGBToRGBA(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height); - -// Convert ARGB To RGB24. -LIBYUV_API -int ARGBToRGB24(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb24, int dst_stride_rgb24, - int width, int height); - -// Convert ARGB To RAW. -LIBYUV_API -int ARGBToRAW(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb, int dst_stride_rgb, - int width, int height); - -// Convert ARGB To RGB565. -LIBYUV_API -int ARGBToRGB565(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb565, int dst_stride_rgb565, - int width, int height); - -// Convert ARGB To RGB565 with 4x4 dither matrix (16 bytes). -// Values in dither matrix from 0 to 7 recommended. -// The order of the dither matrix is first byte is upper left. -// TODO(fbarchard): Consider pointer to 2d array for dither4x4. -// const uint8(*dither)[4][4]; -LIBYUV_API -int ARGBToRGB565Dither(const uint8* src_argb, int src_stride_argb, - uint8* dst_rgb565, int dst_stride_rgb565, - const uint8* dither4x4, int width, int height); - -// Convert ARGB To ARGB1555. -LIBYUV_API -int ARGBToARGB1555(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb1555, int dst_stride_argb1555, - int width, int height); - -// Convert ARGB To ARGB4444. -LIBYUV_API -int ARGBToARGB4444(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb4444, int dst_stride_argb4444, - int width, int height); - -// Convert ARGB To I444. -LIBYUV_API -int ARGBToI444(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB To I422. -LIBYUV_API -int ARGBToI422(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB To I420. (also in convert.h) -LIBYUV_API -int ARGBToI420(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB to J420. (JPeg full range I420). -LIBYUV_API -int ARGBToJ420(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB to J422. -LIBYUV_API -int ARGBToJ422(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB To I411. -LIBYUV_API -int ARGBToI411(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert ARGB to J400. (JPeg full range). -LIBYUV_API -int ARGBToJ400(const uint8* src_argb, int src_stride_argb, - uint8* dst_yj, int dst_stride_yj, - int width, int height); - -// Convert ARGB to I400. -LIBYUV_API -int ARGBToI400(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Convert ARGB to G. (Reverse of J400toARGB, which replicates G back to ARGB) -LIBYUV_API -int ARGBToG(const uint8* src_argb, int src_stride_argb, - uint8* dst_g, int dst_stride_g, - int width, int height); - -// Convert ARGB To NV12. -LIBYUV_API -int ARGBToNV12(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -// Convert ARGB To NV21. -LIBYUV_API -int ARGBToNV21(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height); - -// Convert ARGB To NV21. -LIBYUV_API -int ARGBToNV21(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - uint8* dst_vu, int dst_stride_vu, - int width, int height); - -// Convert ARGB To YUY2. -LIBYUV_API -int ARGBToYUY2(const uint8* src_argb, int src_stride_argb, - uint8* dst_yuy2, int dst_stride_yuy2, - int width, int height); - -// Convert ARGB To UYVY. -LIBYUV_API -int ARGBToUYVY(const uint8* src_argb, int src_stride_argb, - uint8* dst_uyvy, int dst_stride_uyvy, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CONVERT_FROM_ARGB_H_ diff --git a/third_party/libyuv/include/libyuv/cpu_id.h b/third_party/libyuv/include/libyuv/cpu_id.h deleted file mode 100644 index 7c6c9aeb005..00000000000 --- a/third_party/libyuv/include/libyuv/cpu_id.h +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_CPU_ID_H_ -#define INCLUDE_LIBYUV_CPU_ID_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Internal flag to indicate cpuid requires initialization. -static const int kCpuInitialized = 0x1; - -// These flags are only valid on ARM processors. -static const int kCpuHasARM = 0x2; -static const int kCpuHasNEON = 0x4; -// 0x8 reserved for future ARM flag. - -// These flags are only valid on x86 processors. -static const int kCpuHasX86 = 0x10; -static const int kCpuHasSSE2 = 0x20; -static const int kCpuHasSSSE3 = 0x40; -static const int kCpuHasSSE41 = 0x80; -static const int kCpuHasSSE42 = 0x100; -static const int kCpuHasAVX = 0x200; -static const int kCpuHasAVX2 = 0x400; -static const int kCpuHasERMS = 0x800; -static const int kCpuHasFMA3 = 0x1000; -static const int kCpuHasAVX3 = 0x2000; -// 0x2000, 0x4000, 0x8000 reserved for future X86 flags. - -// These flags are only valid on MIPS processors. -static const int kCpuHasMIPS = 0x10000; -static const int kCpuHasDSPR2 = 0x20000; -static const int kCpuHasMSA = 0x40000; - -// Internal function used to auto-init. -LIBYUV_API -int InitCpuFlags(void); - -// Internal function for parsing /proc/cpuinfo. -LIBYUV_API -int ArmCpuCaps(const char* cpuinfo_name); - -// Detect CPU has SSE2 etc. -// Test_flag parameter should be one of kCpuHas constants above. -// returns non-zero if instruction set is detected -static __inline int TestCpuFlag(int test_flag) { - LIBYUV_API extern int cpu_info_; - return (!cpu_info_ ? InitCpuFlags() : cpu_info_) & test_flag; -} - -// For testing, allow CPU flags to be disabled. -// ie MaskCpuFlags(~kCpuHasSSSE3) to disable SSSE3. -// MaskCpuFlags(-1) to enable all cpu specific optimizations. -// MaskCpuFlags(1) to disable all cpu specific optimizations. -LIBYUV_API -void MaskCpuFlags(int enable_flags); - -// Low level cpuid for X86. Returns zeros on other CPUs. -// eax is the info type that you want. -// ecx is typically the cpu number, and should normally be zero. -LIBYUV_API -void CpuId(uint32 eax, uint32 ecx, uint32* cpu_info); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_CPU_ID_H_ diff --git a/third_party/libyuv/include/libyuv/macros_msa.h b/third_party/libyuv/include/libyuv/macros_msa.h deleted file mode 100644 index 92ed21c3853..00000000000 --- a/third_party/libyuv/include/libyuv/macros_msa.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2016 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_MACROS_MSA_H_ -#define INCLUDE_LIBYUV_MACROS_MSA_H_ - -#if !defined(LIBYUV_DISABLE_MSA) && defined(__mips_msa) -#include -#include - -#define LD_B(RTYPE, psrc) *((RTYPE*)(psrc)) /* NOLINT */ -#define LD_UB(...) LD_B(v16u8, __VA_ARGS__) - -#define ST_B(RTYPE, in, pdst) *((RTYPE*)(pdst)) = (in) /* NOLINT */ -#define ST_UB(...) ST_B(v16u8, __VA_ARGS__) - -/* Description : Load two vectors with 16 'byte' sized elements - Arguments : Inputs - psrc, stride - Outputs - out0, out1 - Return Type - as per RTYPE - Details : Load 16 byte elements in 'out0' from (psrc) - Load 16 byte elements in 'out1' from (psrc + stride) -*/ -#define LD_B2(RTYPE, psrc, stride, out0, out1) { \ - out0 = LD_B(RTYPE, (psrc)); \ - out1 = LD_B(RTYPE, (psrc) + stride); \ -} -#define LD_UB2(...) LD_B2(v16u8, __VA_ARGS__) - -#define LD_B4(RTYPE, psrc, stride, out0, out1, out2, out3) { \ - LD_B2(RTYPE, (psrc), stride, out0, out1); \ - LD_B2(RTYPE, (psrc) + 2 * stride , stride, out2, out3); \ -} -#define LD_UB4(...) LD_B4(v16u8, __VA_ARGS__) - -/* Description : Store two vectors with stride each having 16 'byte' sized - elements - Arguments : Inputs - in0, in1, pdst, stride - Details : Store 16 byte elements from 'in0' to (pdst) - Store 16 byte elements from 'in1' to (pdst + stride) -*/ -#define ST_B2(RTYPE, in0, in1, pdst, stride) { \ - ST_B(RTYPE, in0, (pdst)); \ - ST_B(RTYPE, in1, (pdst) + stride); \ -} -#define ST_UB2(...) ST_B2(v16u8, __VA_ARGS__) -# -#define ST_B4(RTYPE, in0, in1, in2, in3, pdst, stride) { \ - ST_B2(RTYPE, in0, in1, (pdst), stride); \ - ST_B2(RTYPE, in2, in3, (pdst) + 2 * stride, stride); \ -} -#define ST_UB4(...) ST_B4(v16u8, __VA_ARGS__) -# -/* Description : Shuffle byte vector elements as per mask vector - Arguments : Inputs - in0, in1, in2, in3, mask0, mask1 - Outputs - out0, out1 - Return Type - as per RTYPE - Details : Byte elements from 'in0' & 'in1' are copied selectively to - 'out0' as per control vector 'mask0' -*/ -#define VSHF_B2(RTYPE, in0, in1, in2, in3, mask0, mask1, out0, out1) { \ - out0 = (RTYPE) __msa_vshf_b((v16i8) mask0, (v16i8) in1, (v16i8) in0); \ - out1 = (RTYPE) __msa_vshf_b((v16i8) mask1, (v16i8) in3, (v16i8) in2); \ -} -#define VSHF_B2_UB(...) VSHF_B2(v16u8, __VA_ARGS__) - -#endif /* !defined(LIBYUV_DISABLE_MSA) && defined(__mips_msa) */ - -#endif // INCLUDE_LIBYUV_MACROS_MSA_H_ diff --git a/third_party/libyuv/include/libyuv/mjpeg_decoder.h b/third_party/libyuv/include/libyuv/mjpeg_decoder.h deleted file mode 100644 index 4975bae5b76..00000000000 --- a/third_party/libyuv/include/libyuv/mjpeg_decoder.h +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_MJPEG_DECODER_H_ -#define INCLUDE_LIBYUV_MJPEG_DECODER_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -// NOTE: For a simplified public API use convert.h MJPGToI420(). - -struct jpeg_common_struct; -struct jpeg_decompress_struct; -struct jpeg_source_mgr; - -namespace libyuv { - -#ifdef __cplusplus -extern "C" { -#endif - -LIBYUV_BOOL ValidateJpeg(const uint8* sample, size_t sample_size); - -#ifdef __cplusplus -} // extern "C" -#endif - -static const uint32 kUnknownDataSize = 0xFFFFFFFF; - -enum JpegSubsamplingType { - kJpegYuv420, - kJpegYuv422, - kJpegYuv411, - kJpegYuv444, - kJpegYuv400, - kJpegUnknown -}; - -struct Buffer { - const uint8* data; - int len; -}; - -struct BufferVector { - Buffer* buffers; - int len; - int pos; -}; - -struct SetJmpErrorMgr; - -// MJPEG ("Motion JPEG") is a pseudo-standard video codec where the frames are -// simply independent JPEG images with a fixed huffman table (which is omitted). -// It is rarely used in video transmission, but is common as a camera capture -// format, especially in Logitech devices. This class implements a decoder for -// MJPEG frames. -// -// See http://tools.ietf.org/html/rfc2435 -class LIBYUV_API MJpegDecoder { - public: - typedef void (*CallbackFunction)(void* opaque, - const uint8* const* data, - const int* strides, - int rows); - - static const int kColorSpaceUnknown; - static const int kColorSpaceGrayscale; - static const int kColorSpaceRgb; - static const int kColorSpaceYCbCr; - static const int kColorSpaceCMYK; - static const int kColorSpaceYCCK; - - MJpegDecoder(); - ~MJpegDecoder(); - - // Loads a new frame, reads its headers, and determines the uncompressed - // image format. - // Returns LIBYUV_TRUE if image looks valid and format is supported. - // If return value is LIBYUV_TRUE, then the values for all the following - // getters are populated. - // src_len is the size of the compressed mjpeg frame in bytes. - LIBYUV_BOOL LoadFrame(const uint8* src, size_t src_len); - - // Returns width of the last loaded frame in pixels. - int GetWidth(); - - // Returns height of the last loaded frame in pixels. - int GetHeight(); - - // Returns format of the last loaded frame. The return value is one of the - // kColorSpace* constants. - int GetColorSpace(); - - // Number of color components in the color space. - int GetNumComponents(); - - // Sample factors of the n-th component. - int GetHorizSampFactor(int component); - - int GetVertSampFactor(int component); - - int GetHorizSubSampFactor(int component); - - int GetVertSubSampFactor(int component); - - // Public for testability. - int GetImageScanlinesPerImcuRow(); - - // Public for testability. - int GetComponentScanlinesPerImcuRow(int component); - - // Width of a component in bytes. - int GetComponentWidth(int component); - - // Height of a component. - int GetComponentHeight(int component); - - // Width of a component in bytes with padding for DCTSIZE. Public for testing. - int GetComponentStride(int component); - - // Size of a component in bytes. - int GetComponentSize(int component); - - // Call this after LoadFrame() if you decide you don't want to decode it - // after all. - LIBYUV_BOOL UnloadFrame(); - - // Decodes the entire image into a one-buffer-per-color-component format. - // dst_width must match exactly. dst_height must be <= to image height; if - // less, the image is cropped. "planes" must have size equal to at least - // GetNumComponents() and they must point to non-overlapping buffers of size - // at least GetComponentSize(i). The pointers in planes are incremented - // to point to after the end of the written data. - // TODO(fbarchard): Add dst_x, dst_y to allow specific rect to be decoded. - LIBYUV_BOOL DecodeToBuffers(uint8** planes, int dst_width, int dst_height); - - // Decodes the entire image and passes the data via repeated calls to a - // callback function. Each call will get the data for a whole number of - // image scanlines. - // TODO(fbarchard): Add dst_x, dst_y to allow specific rect to be decoded. - LIBYUV_BOOL DecodeToCallback(CallbackFunction fn, void* opaque, - int dst_width, int dst_height); - - // The helper function which recognizes the jpeg sub-sampling type. - static JpegSubsamplingType JpegSubsamplingTypeHelper( - int* subsample_x, int* subsample_y, int number_of_components); - - private: - void AllocOutputBuffers(int num_outbufs); - void DestroyOutputBuffers(); - - LIBYUV_BOOL StartDecode(); - LIBYUV_BOOL FinishDecode(); - - void SetScanlinePointers(uint8** data); - LIBYUV_BOOL DecodeImcuRow(); - - int GetComponentScanlinePadding(int component); - - // A buffer holding the input data for a frame. - Buffer buf_; - BufferVector buf_vec_; - - jpeg_decompress_struct* decompress_struct_; - jpeg_source_mgr* source_mgr_; - SetJmpErrorMgr* error_mgr_; - - // LIBYUV_TRUE iff at least one component has scanline padding. (i.e., - // GetComponentScanlinePadding() != 0.) - LIBYUV_BOOL has_scanline_padding_; - - // Temporaries used to point to scanline outputs. - int num_outbufs_; // Outermost size of all arrays below. - uint8*** scanlines_; - int* scanlines_sizes_; - // Temporary buffer used for decoding when we can't decode directly to the - // output buffers. Large enough for just one iMCU row. - uint8** databuf_; - int* databuf_strides_; -}; - -} // namespace libyuv - -#endif // __cplusplus -#endif // INCLUDE_LIBYUV_MJPEG_DECODER_H_ diff --git a/third_party/libyuv/include/libyuv/planar_functions.h b/third_party/libyuv/include/libyuv/planar_functions.h deleted file mode 100644 index 1b57b29261e..00000000000 --- a/third_party/libyuv/include/libyuv/planar_functions.h +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_PLANAR_FUNCTIONS_H_ -#define INCLUDE_LIBYUV_PLANAR_FUNCTIONS_H_ - -#include "libyuv/basic_types.h" - -// TODO(fbarchard): Remove the following headers includes. -#include "libyuv/convert.h" -#include "libyuv/convert_argb.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Copy a plane of data. -LIBYUV_API -void CopyPlane(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -LIBYUV_API -void CopyPlane_16(const uint16* src_y, int src_stride_y, - uint16* dst_y, int dst_stride_y, - int width, int height); - -// Set a plane of data to a 32 bit value. -LIBYUV_API -void SetPlane(uint8* dst_y, int dst_stride_y, - int width, int height, - uint32 value); - -// Split interleaved UV plane into separate U and V planes. -LIBYUV_API -void SplitUVPlane(const uint8* src_uv, int src_stride_uv, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Merge separate U and V planes into one interleaved UV plane. -LIBYUV_API -void MergeUVPlane(const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -// Copy I400. Supports inverting. -LIBYUV_API -int I400ToI400(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -#define J400ToJ400 I400ToI400 - -// Copy I422 to I422. -#define I422ToI422 I422Copy -LIBYUV_API -int I422Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Copy I444 to I444. -#define I444ToI444 I444Copy -LIBYUV_API -int I444Copy(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert YUY2 to I422. -LIBYUV_API -int YUY2ToI422(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Convert UYVY to I422. -LIBYUV_API -int UYVYToI422(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -LIBYUV_API -int YUY2ToNV12(const uint8* src_yuy2, int src_stride_yuy2, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -LIBYUV_API -int UYVYToNV12(const uint8* src_uyvy, int src_stride_uyvy, - uint8* dst_y, int dst_stride_y, - uint8* dst_uv, int dst_stride_uv, - int width, int height); - -// Convert I420 to I400. (calls CopyPlane ignoring u/v). -LIBYUV_API -int I420ToI400(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Alias -#define J420ToJ400 I420ToI400 -#define I420ToI420Mirror I420Mirror - -// I420 mirror. -LIBYUV_API -int I420Mirror(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Alias -#define I400ToI400Mirror I400Mirror - -// I400 mirror. A single plane is mirrored horizontally. -// Pass negative height to achieve 180 degree rotation. -LIBYUV_API -int I400Mirror(const uint8* src_y, int src_stride_y, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Alias -#define ARGBToARGBMirror ARGBMirror - -// ARGB mirror. -LIBYUV_API -int ARGBMirror(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert NV12 to RGB565. -LIBYUV_API -int NV12ToRGB565(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_rgb565, int dst_stride_rgb565, - int width, int height); - -// I422ToARGB is in convert_argb.h -// Convert I422 to BGRA. -LIBYUV_API -int I422ToBGRA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_bgra, int dst_stride_bgra, - int width, int height); - -// Convert I422 to ABGR. -LIBYUV_API -int I422ToABGR(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_abgr, int dst_stride_abgr, - int width, int height); - -// Convert I422 to RGBA. -LIBYUV_API -int I422ToRGBA(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_rgba, int dst_stride_rgba, - int width, int height); - -// Alias -#define RGB24ToRAW RAWToRGB24 - -LIBYUV_API -int RAWToRGB24(const uint8* src_raw, int src_stride_raw, - uint8* dst_rgb24, int dst_stride_rgb24, - int width, int height); - -// Draw a rectangle into I420. -LIBYUV_API -int I420Rect(uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int x, int y, int width, int height, - int value_y, int value_u, int value_v); - -// Draw a rectangle into ARGB. -LIBYUV_API -int ARGBRect(uint8* dst_argb, int dst_stride_argb, - int x, int y, int width, int height, uint32 value); - -// Convert ARGB to gray scale ARGB. -LIBYUV_API -int ARGBGrayTo(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Make a rectangle of ARGB gray scale. -LIBYUV_API -int ARGBGray(uint8* dst_argb, int dst_stride_argb, - int x, int y, int width, int height); - -// Make a rectangle of ARGB Sepia tone. -LIBYUV_API -int ARGBSepia(uint8* dst_argb, int dst_stride_argb, - int x, int y, int width, int height); - -// Apply a matrix rotation to each ARGB pixel. -// matrix_argb is 4 signed ARGB values. -128 to 127 representing -2 to 2. -// The first 4 coefficients apply to B, G, R, A and produce B of the output. -// The next 4 coefficients apply to B, G, R, A and produce G of the output. -// The next 4 coefficients apply to B, G, R, A and produce R of the output. -// The last 4 coefficients apply to B, G, R, A and produce A of the output. -LIBYUV_API -int ARGBColorMatrix(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const int8* matrix_argb, - int width, int height); - -// Deprecated. Use ARGBColorMatrix instead. -// Apply a matrix rotation to each ARGB pixel. -// matrix_argb is 3 signed ARGB values. -128 to 127 representing -1 to 1. -// The first 4 coefficients apply to B, G, R, A and produce B of the output. -// The next 4 coefficients apply to B, G, R, A and produce G of the output. -// The last 4 coefficients apply to B, G, R, A and produce R of the output. -LIBYUV_API -int RGBColorMatrix(uint8* dst_argb, int dst_stride_argb, - const int8* matrix_rgb, - int x, int y, int width, int height); - -// Apply a color table each ARGB pixel. -// Table contains 256 ARGB values. -LIBYUV_API -int ARGBColorTable(uint8* dst_argb, int dst_stride_argb, - const uint8* table_argb, - int x, int y, int width, int height); - -// Apply a color table each ARGB pixel but preserve destination alpha. -// Table contains 256 ARGB values. -LIBYUV_API -int RGBColorTable(uint8* dst_argb, int dst_stride_argb, - const uint8* table_argb, - int x, int y, int width, int height); - -// Apply a luma/color table each ARGB pixel but preserve destination alpha. -// Table contains 32768 values indexed by [Y][C] where 7 it 7 bit luma from -// RGB (YJ style) and C is an 8 bit color component (R, G or B). -LIBYUV_API -int ARGBLumaColorTable(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const uint8* luma_rgb_table, - int width, int height); - -// Apply a 3 term polynomial to ARGB values. -// poly points to a 4x4 matrix. The first row is constants. The 2nd row is -// coefficients for b, g, r and a. The 3rd row is coefficients for b squared, -// g squared, r squared and a squared. The 4rd row is coefficients for b to -// the 3, g to the 3, r to the 3 and a to the 3. The values are summed and -// result clamped to 0 to 255. -// A polynomial approximation can be dirived using software such as 'R'. - -LIBYUV_API -int ARGBPolynomial(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - const float* poly, - int width, int height); - -// Convert plane of 16 bit shorts to half floats. -// Source values are multiplied by scale before storing as half float. -LIBYUV_API -int HalfFloatPlane(const uint16* src_y, int src_stride_y, - uint16* dst_y, int dst_stride_y, - float scale, - int width, int height); - -// Quantize a rectangle of ARGB. Alpha unaffected. -// scale is a 16 bit fractional fixed point scaler between 0 and 65535. -// interval_size should be a value between 1 and 255. -// interval_offset should be a value between 0 and 255. -LIBYUV_API -int ARGBQuantize(uint8* dst_argb, int dst_stride_argb, - int scale, int interval_size, int interval_offset, - int x, int y, int width, int height); - -// Copy ARGB to ARGB. -LIBYUV_API -int ARGBCopy(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Copy Alpha channel of ARGB to alpha of ARGB. -LIBYUV_API -int ARGBCopyAlpha(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Extract the alpha channel from ARGB. -LIBYUV_API -int ARGBExtractAlpha(const uint8* src_argb, int src_stride_argb, - uint8* dst_a, int dst_stride_a, - int width, int height); - -// Copy Y channel to Alpha of ARGB. -LIBYUV_API -int ARGBCopyYToAlpha(const uint8* src_y, int src_stride_y, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -typedef void (*ARGBBlendRow)(const uint8* src_argb0, const uint8* src_argb1, - uint8* dst_argb, int width); - -// Get function to Alpha Blend ARGB pixels and store to destination. -LIBYUV_API -ARGBBlendRow GetARGBBlend(); - -// Alpha Blend ARGB images and store to destination. -// Source is pre-multiplied by alpha using ARGBAttenuate. -// Alpha of destination is set to 255. -LIBYUV_API -int ARGBBlend(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Alpha Blend plane and store to destination. -// Source is not pre-multiplied by alpha. -LIBYUV_API -int BlendPlane(const uint8* src_y0, int src_stride_y0, - const uint8* src_y1, int src_stride_y1, - const uint8* alpha, int alpha_stride, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Alpha Blend YUV images and store to destination. -// Source is not pre-multiplied by alpha. -// Alpha is full width x height and subsampled to half size to apply to UV. -LIBYUV_API -int I420Blend(const uint8* src_y0, int src_stride_y0, - const uint8* src_u0, int src_stride_u0, - const uint8* src_v0, int src_stride_v0, - const uint8* src_y1, int src_stride_y1, - const uint8* src_u1, int src_stride_u1, - const uint8* src_v1, int src_stride_v1, - const uint8* alpha, int alpha_stride, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height); - -// Multiply ARGB image by ARGB image. Shifted down by 8. Saturates to 255. -LIBYUV_API -int ARGBMultiply(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Add ARGB image with ARGB image. Saturates to 255. -LIBYUV_API -int ARGBAdd(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Subtract ARGB image (argb1) from ARGB image (argb0). Saturates to 0. -LIBYUV_API -int ARGBSubtract(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert I422 to YUY2. -LIBYUV_API -int I422ToYUY2(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert I422 to UYVY. -LIBYUV_API -int I422ToUYVY(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_frame, int dst_stride_frame, - int width, int height); - -// Convert unattentuated ARGB to preattenuated ARGB. -LIBYUV_API -int ARGBAttenuate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Convert preattentuated ARGB to unattenuated ARGB. -LIBYUV_API -int ARGBUnattenuate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Internal function - do not call directly. -// Computes table of cumulative sum for image where the value is the sum -// of all values above and to the left of the entry. Used by ARGBBlur. -LIBYUV_API -int ARGBComputeCumulativeSum(const uint8* src_argb, int src_stride_argb, - int32* dst_cumsum, int dst_stride32_cumsum, - int width, int height); - -// Blur ARGB image. -// dst_cumsum table of width * (height + 1) * 16 bytes aligned to -// 16 byte boundary. -// dst_stride32_cumsum is number of ints in a row (width * 4). -// radius is number of pixels around the center. e.g. 1 = 3x3. 2=5x5. -// Blur is optimized for radius of 5 (11x11) or less. -LIBYUV_API -int ARGBBlur(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int32* dst_cumsum, int dst_stride32_cumsum, - int width, int height, int radius); - -// Multiply ARGB image by ARGB value. -LIBYUV_API -int ARGBShade(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height, uint32 value); - -// Interpolate between two images using specified amount of interpolation -// (0 to 255) and store to destination. -// 'interpolation' is specified as 8 bit fraction where 0 means 100% src0 -// and 255 means 1% src0 and 99% src1. -LIBYUV_API -int InterpolatePlane(const uint8* src0, int src_stride0, - const uint8* src1, int src_stride1, - uint8* dst, int dst_stride, - int width, int height, int interpolation); - -// Interpolate between two ARGB images using specified amount of interpolation -// Internally calls InterpolatePlane with width * 4 (bpp). -LIBYUV_API -int ARGBInterpolate(const uint8* src_argb0, int src_stride_argb0, - const uint8* src_argb1, int src_stride_argb1, - uint8* dst_argb, int dst_stride_argb, - int width, int height, int interpolation); - -// Interpolate between two YUV images using specified amount of interpolation -// Internally calls InterpolatePlane on each plane where the U and V planes -// are half width and half height. -LIBYUV_API -int I420Interpolate(const uint8* src0_y, int src0_stride_y, - const uint8* src0_u, int src0_stride_u, - const uint8* src0_v, int src0_stride_v, - const uint8* src1_y, int src1_stride_y, - const uint8* src1_u, int src1_stride_u, - const uint8* src1_v, int src1_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int width, int height, int interpolation); - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif -// The following are available on all x86 platforms: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) -#define HAS_ARGBAFFINEROW_SSE2 -#endif - -// Row function for copying pixels from a source with a slope to a row -// of destination. Useful for scaling, rotation, mirror, texture mapping. -LIBYUV_API -void ARGBAffineRow_C(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); -LIBYUV_API -void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); - -// Shuffle ARGB channel order. e.g. BGRA to ARGB. -// shuffler is 16 bytes and must be aligned. -LIBYUV_API -int ARGBShuffle(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_argb, int dst_stride_argb, - const uint8* shuffler, int width, int height); - -// Sobel ARGB effect with planar output. -LIBYUV_API -int ARGBSobelToPlane(const uint8* src_argb, int src_stride_argb, - uint8* dst_y, int dst_stride_y, - int width, int height); - -// Sobel ARGB effect. -LIBYUV_API -int ARGBSobel(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -// Sobel ARGB effect w/ Sobel X, Sobel, Sobel Y in ARGB. -LIBYUV_API -int ARGBSobelXY(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_PLANAR_FUNCTIONS_H_ diff --git a/third_party/libyuv/include/libyuv/rotate.h b/third_party/libyuv/include/libyuv/rotate.h deleted file mode 100644 index 8a2da9a5aad..00000000000 --- a/third_party/libyuv/include/libyuv/rotate.h +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROTATE_H_ -#define INCLUDE_LIBYUV_ROTATE_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Supported rotation. -typedef enum RotationMode { - kRotate0 = 0, // No rotation. - kRotate90 = 90, // Rotate 90 degrees clockwise. - kRotate180 = 180, // Rotate 180 degrees. - kRotate270 = 270, // Rotate 270 degrees clockwise. - - // Deprecated. - kRotateNone = 0, - kRotateClockwise = 90, - kRotateCounterClockwise = 270, -} RotationModeEnum; - -// Rotate I420 frame. -LIBYUV_API -int I420Rotate(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_width, int src_height, enum RotationMode mode); - -// Rotate NV12 input and store in I420. -LIBYUV_API -int NV12ToI420Rotate(const uint8* src_y, int src_stride_y, - const uint8* src_uv, int src_stride_uv, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int src_width, int src_height, enum RotationMode mode); - -// Rotate a plane by 0, 90, 180, or 270. -LIBYUV_API -int RotatePlane(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int src_width, int src_height, enum RotationMode mode); - -// Rotate planes by 90, 180, 270. Deprecated. -LIBYUV_API -void RotatePlane90(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void RotatePlane180(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void RotatePlane270(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void RotateUV90(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -// Rotations for when U and V are interleaved. -// These functions take one input pointer and -// split the data into two buffers while -// rotating them. Deprecated. -LIBYUV_API -void RotateUV180(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -LIBYUV_API -void RotateUV270(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -// The 90 and 270 functions are based on transposes. -// Doing a transpose with reversing the read/write -// order will result in a rotation by +- 90 degrees. -// Deprecated. -LIBYUV_API -void TransposePlane(const uint8* src, int src_stride, - uint8* dst, int dst_stride, - int width, int height); - -LIBYUV_API -void TransposeUV(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROTATE_H_ diff --git a/third_party/libyuv/include/libyuv/rotate_argb.h b/third_party/libyuv/include/libyuv/rotate_argb.h deleted file mode 100644 index 21fe7e1807c..00000000000 --- a/third_party/libyuv/include/libyuv/rotate_argb.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROTATE_ARGB_H_ -#define INCLUDE_LIBYUV_ROTATE_ARGB_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/rotate.h" // For RotationMode. - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Rotate ARGB frame -LIBYUV_API -int ARGBRotate(const uint8* src_argb, int src_stride_argb, - uint8* dst_argb, int dst_stride_argb, - int src_width, int src_height, enum RotationMode mode); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROTATE_ARGB_H_ diff --git a/third_party/libyuv/include/libyuv/rotate_row.h b/third_party/libyuv/include/libyuv/rotate_row.h deleted file mode 100644 index 6abd2016774..00000000000 --- a/third_party/libyuv/include/libyuv/rotate_row.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROTATE_ROW_H_ -#define INCLUDE_LIBYUV_ROTATE_ROW_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif -// The following are available for Visual C and clangcl 32 bit: -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) -#define HAS_TRANSPOSEWX8_SSSE3 -#define HAS_TRANSPOSEUVWX8_SSE2 -#endif - -// The following are available for GCC 32 or 64 bit but not NaCL for 64 bit: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__i386__) || (defined(__x86_64__) && !defined(__native_client__))) -#define HAS_TRANSPOSEWX8_SSSE3 -#endif - -// The following are available for 64 bit GCC but not NaCL: -#if !defined(LIBYUV_DISABLE_X86) && !defined(__native_client__) && \ - defined(__x86_64__) -#define HAS_TRANSPOSEWX8_FAST_SSSE3 -#define HAS_TRANSPOSEUVWX8_SSE2 -#endif - -#if !defined(LIBYUV_DISABLE_NEON) && !defined(__native_client__) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_TRANSPOSEWX8_NEON -#define HAS_TRANSPOSEUVWX8_NEON -#endif - -#if !defined(LIBYUV_DISABLE_MIPS) && !defined(__native_client__) && \ - defined(__mips__) && \ - defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_TRANSPOSEWX8_DSPR2 -#define HAS_TRANSPOSEUVWX8_DSPR2 -#endif // defined(__mips__) - -void TransposeWxH_C(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width, int height); - -void TransposeWx8_C(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_NEON(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); - -void TransposeWx8_Any_NEON(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Any_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Fast_Any_SSSE3(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); -void TransposeWx8_Any_DSPR2(const uint8* src, int src_stride, - uint8* dst, int dst_stride, int width); - -void TransposeUVWxH_C(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, - int width, int height); - -void TransposeUVWx8_C(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_SSE2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_NEON(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_DSPR2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); - -void TransposeUVWx8_Any_SSE2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_Any_NEON(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); -void TransposeUVWx8_Any_DSPR2(const uint8* src, int src_stride, - uint8* dst_a, int dst_stride_a, - uint8* dst_b, int dst_stride_b, int width); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROTATE_ROW_H_ diff --git a/third_party/libyuv/include/libyuv/row.h b/third_party/libyuv/include/libyuv/row.h deleted file mode 100644 index b810221ec7a..00000000000 --- a/third_party/libyuv/include/libyuv/row.h +++ /dev/null @@ -1,1963 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_ROW_H_ -#define INCLUDE_LIBYUV_ROW_H_ - -#include // For malloc. - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#define IS_ALIGNED(p, a) (!((uintptr_t)(p) & ((a) - 1))) - -#define align_buffer_64(var, size) \ - uint8* var##_mem = (uint8*)(malloc((size) + 63)); /* NOLINT */ \ - uint8* var = (uint8*)(((intptr_t)(var##_mem) + 63) & ~63) /* NOLINT */ - -#define free_aligned_buffer_64(var) \ - free(var##_mem); \ - var = 0 - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif -// True if compiling for SSSE3 as a requirement. -#if defined(__SSSE3__) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 3)) -#define LIBYUV_SSSE3_ONLY -#endif - -#if defined(__native_client__) -#define LIBYUV_DISABLE_NEON -#endif -// clang >= 3.5.0 required for Arm64. -#if defined(__clang__) && defined(__aarch64__) && !defined(LIBYUV_DISABLE_NEON) -#if (__clang_major__ < 3) || (__clang_major__ == 3 && (__clang_minor__ < 5)) -#define LIBYUV_DISABLE_NEON -#endif // clang >= 3.5 -#endif // __clang__ - -// GCC >= 4.7.0 required for AVX2. -#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) -#if (__GNUC__ > 4) || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 7)) -#define GCC_HAS_AVX2 1 -#endif // GNUC >= 4.7 -#endif // __GNUC__ - -// clang >= 3.4.0 required for AVX2. -#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) -#if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) -#define CLANG_HAS_AVX2 1 -#endif // clang >= 3.4 -#endif // __clang__ - -// Visual C 2012 required for AVX2. -#if defined(_M_IX86) && !defined(__clang__) && \ - defined(_MSC_VER) && _MSC_VER >= 1700 -#define VISUALC_HAS_AVX2 1 -#endif // VisualStudio >= 2012 - -// The following are available on all x86 platforms: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) -// Conversions: -#define HAS_ABGRTOUVROW_SSSE3 -#define HAS_ABGRTOYROW_SSSE3 -#define HAS_ARGB1555TOARGBROW_SSE2 -#define HAS_ARGB4444TOARGBROW_SSE2 -#define HAS_ARGBSETROW_X86 -#define HAS_ARGBSHUFFLEROW_SSE2 -#define HAS_ARGBSHUFFLEROW_SSSE3 -#define HAS_ARGBTOARGB1555ROW_SSE2 -#define HAS_ARGBTOARGB4444ROW_SSE2 -#define HAS_ARGBTORAWROW_SSSE3 -#define HAS_ARGBTORGB24ROW_SSSE3 -#define HAS_ARGBTORGB565DITHERROW_SSE2 -#define HAS_ARGBTORGB565ROW_SSE2 -#define HAS_ARGBTOUV444ROW_SSSE3 -#define HAS_ARGBTOUVJROW_SSSE3 -#define HAS_ARGBTOUVROW_SSSE3 -#define HAS_ARGBTOYJROW_SSSE3 -#define HAS_ARGBTOYROW_SSSE3 -#define HAS_ARGBEXTRACTALPHAROW_SSE2 -#define HAS_BGRATOUVROW_SSSE3 -#define HAS_BGRATOYROW_SSSE3 -#define HAS_COPYROW_ERMS -#define HAS_COPYROW_SSE2 -#define HAS_H422TOARGBROW_SSSE3 -#define HAS_I400TOARGBROW_SSE2 -#define HAS_I422TOARGB1555ROW_SSSE3 -#define HAS_I422TOARGB4444ROW_SSSE3 -#define HAS_I422TOARGBROW_SSSE3 -#define HAS_I422TORGB24ROW_SSSE3 -#define HAS_I422TORGB565ROW_SSSE3 -#define HAS_I422TORGBAROW_SSSE3 -#define HAS_I422TOUYVYROW_SSE2 -#define HAS_I422TOYUY2ROW_SSE2 -#define HAS_I444TOARGBROW_SSSE3 -#define HAS_J400TOARGBROW_SSE2 -#define HAS_J422TOARGBROW_SSSE3 -#define HAS_MERGEUVROW_SSE2 -#define HAS_MIRRORROW_SSSE3 -#define HAS_MIRRORUVROW_SSSE3 -#define HAS_NV12TOARGBROW_SSSE3 -#define HAS_NV12TORGB565ROW_SSSE3 -#define HAS_NV21TOARGBROW_SSSE3 -#define HAS_RAWTOARGBROW_SSSE3 -#define HAS_RAWTORGB24ROW_SSSE3 -#define HAS_RAWTOYROW_SSSE3 -#define HAS_RGB24TOARGBROW_SSSE3 -#define HAS_RGB24TOYROW_SSSE3 -#define HAS_RGB565TOARGBROW_SSE2 -#define HAS_RGBATOUVROW_SSSE3 -#define HAS_RGBATOYROW_SSSE3 -#define HAS_SETROW_ERMS -#define HAS_SETROW_X86 -#define HAS_SPLITUVROW_SSE2 -#define HAS_UYVYTOARGBROW_SSSE3 -#define HAS_UYVYTOUV422ROW_SSE2 -#define HAS_UYVYTOUVROW_SSE2 -#define HAS_UYVYTOYROW_SSE2 -#define HAS_YUY2TOARGBROW_SSSE3 -#define HAS_YUY2TOUV422ROW_SSE2 -#define HAS_YUY2TOUVROW_SSE2 -#define HAS_YUY2TOYROW_SSE2 - -// Effects: -#define HAS_ARGBADDROW_SSE2 -#define HAS_ARGBAFFINEROW_SSE2 -#define HAS_ARGBATTENUATEROW_SSSE3 -#define HAS_ARGBBLENDROW_SSSE3 -#define HAS_ARGBCOLORMATRIXROW_SSSE3 -#define HAS_ARGBCOLORTABLEROW_X86 -#define HAS_ARGBCOPYALPHAROW_SSE2 -#define HAS_ARGBCOPYYTOALPHAROW_SSE2 -#define HAS_ARGBGRAYROW_SSSE3 -#define HAS_ARGBLUMACOLORTABLEROW_SSSE3 -#define HAS_ARGBMIRRORROW_SSE2 -#define HAS_ARGBMULTIPLYROW_SSE2 -#define HAS_ARGBPOLYNOMIALROW_SSE2 -#define HAS_ARGBQUANTIZEROW_SSE2 -#define HAS_ARGBSEPIAROW_SSSE3 -#define HAS_ARGBSHADEROW_SSE2 -#define HAS_ARGBSUBTRACTROW_SSE2 -#define HAS_ARGBUNATTENUATEROW_SSE2 -#define HAS_BLENDPLANEROW_SSSE3 -#define HAS_COMPUTECUMULATIVESUMROW_SSE2 -#define HAS_CUMULATIVESUMTOAVERAGEROW_SSE2 -#define HAS_INTERPOLATEROW_SSSE3 -#define HAS_RGBCOLORTABLEROW_X86 -#define HAS_SOBELROW_SSE2 -#define HAS_SOBELTOPLANEROW_SSE2 -#define HAS_SOBELXROW_SSE2 -#define HAS_SOBELXYROW_SSE2 -#define HAS_SOBELYROW_SSE2 - -// The following functions fail on gcc/clang 32 bit with fpic and framepointer. -// caveat: clangcl uses row_win.cc which works. -#if defined(NDEBUG) || !(defined(_DEBUG) && defined(__i386__)) || \ - !defined(__i386__) || defined(_MSC_VER) -// TODO(fbarchard): fix build error on x86 debug -// https://code.google.com/p/libyuv/issues/detail?id=524 -#define HAS_I411TOARGBROW_SSSE3 -// TODO(fbarchard): fix build error on android_full_debug=1 -// https://code.google.com/p/libyuv/issues/detail?id=517 -#define HAS_I422ALPHATOARGBROW_SSSE3 -#endif -#endif - -// The following are available on all x86 platforms, but -// require VS2012, clang 3.4 or gcc 4.7. -// The code supports NaCL but requires a new compiler and validator. -#if !defined(LIBYUV_DISABLE_X86) && (defined(VISUALC_HAS_AVX2) || \ - defined(CLANG_HAS_AVX2) || defined(GCC_HAS_AVX2)) -#define HAS_ARGBCOPYALPHAROW_AVX2 -#define HAS_ARGBCOPYYTOALPHAROW_AVX2 -#define HAS_ARGBMIRRORROW_AVX2 -#define HAS_ARGBPOLYNOMIALROW_AVX2 -#define HAS_ARGBSHUFFLEROW_AVX2 -#define HAS_ARGBTORGB565DITHERROW_AVX2 -#define HAS_ARGBTOUVJROW_AVX2 -#define HAS_ARGBTOUVROW_AVX2 -#define HAS_ARGBTOYJROW_AVX2 -#define HAS_ARGBTOYROW_AVX2 -#define HAS_COPYROW_AVX -#define HAS_H422TOARGBROW_AVX2 -#define HAS_I400TOARGBROW_AVX2 -#if !(defined(_DEBUG) && defined(__i386__)) -// TODO(fbarchard): fix build error on android_full_debug=1 -// https://code.google.com/p/libyuv/issues/detail?id=517 -#define HAS_I422ALPHATOARGBROW_AVX2 -#endif -#define HAS_I411TOARGBROW_AVX2 -#define HAS_I422TOARGB1555ROW_AVX2 -#define HAS_I422TOARGB4444ROW_AVX2 -#define HAS_I422TOARGBROW_AVX2 -#define HAS_I422TORGB24ROW_AVX2 -#define HAS_I422TORGB565ROW_AVX2 -#define HAS_I422TORGBAROW_AVX2 -#define HAS_I444TOARGBROW_AVX2 -#define HAS_INTERPOLATEROW_AVX2 -#define HAS_J422TOARGBROW_AVX2 -#define HAS_MERGEUVROW_AVX2 -#define HAS_MIRRORROW_AVX2 -#define HAS_NV12TOARGBROW_AVX2 -#define HAS_NV12TORGB565ROW_AVX2 -#define HAS_NV21TOARGBROW_AVX2 -#define HAS_SPLITUVROW_AVX2 -#define HAS_UYVYTOARGBROW_AVX2 -#define HAS_UYVYTOUV422ROW_AVX2 -#define HAS_UYVYTOUVROW_AVX2 -#define HAS_UYVYTOYROW_AVX2 -#define HAS_YUY2TOARGBROW_AVX2 -#define HAS_YUY2TOUV422ROW_AVX2 -#define HAS_YUY2TOUVROW_AVX2 -#define HAS_YUY2TOYROW_AVX2 -#define HAS_HALFFLOATROW_AVX2 - -// Effects: -#define HAS_ARGBADDROW_AVX2 -#define HAS_ARGBATTENUATEROW_AVX2 -#define HAS_ARGBMULTIPLYROW_AVX2 -#define HAS_ARGBSUBTRACTROW_AVX2 -#define HAS_ARGBUNATTENUATEROW_AVX2 -#define HAS_BLENDPLANEROW_AVX2 -#endif - -// The following are available for AVX2 Visual C and clangcl 32 bit: -// TODO(fbarchard): Port to gcc. -#if !defined(LIBYUV_DISABLE_X86) && defined(_M_IX86) && \ - (defined(VISUALC_HAS_AVX2) || defined(CLANG_HAS_AVX2)) -#define HAS_ARGB1555TOARGBROW_AVX2 -#define HAS_ARGB4444TOARGBROW_AVX2 -#define HAS_ARGBTOARGB1555ROW_AVX2 -#define HAS_ARGBTOARGB4444ROW_AVX2 -#define HAS_ARGBTORGB565ROW_AVX2 -#define HAS_J400TOARGBROW_AVX2 -#define HAS_RGB565TOARGBROW_AVX2 -#endif - -// The following are also available on x64 Visual C. -#if !defined(LIBYUV_DISABLE_X86) && defined(_MSC_VER) && defined(_M_X64) && \ - (!defined(__clang__) || defined(__SSSE3__)) -#define HAS_I422ALPHATOARGBROW_SSSE3 -#define HAS_I422TOARGBROW_SSSE3 -#endif - -// The following are available on gcc x86 platforms: -// TODO(fbarchard): Port to Visual C. -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(__x86_64__) || (defined(__i386__) && !defined(_MSC_VER))) -#define HAS_HALFFLOATROW_SSE2 -#endif - -// The following are available on Neon platforms: -#if !defined(LIBYUV_DISABLE_NEON) && \ - (defined(__aarch64__) || defined(__ARM_NEON__) || defined(LIBYUV_NEON)) -#define HAS_ABGRTOUVROW_NEON -#define HAS_ABGRTOYROW_NEON -#define HAS_ARGB1555TOARGBROW_NEON -#define HAS_ARGB1555TOUVROW_NEON -#define HAS_ARGB1555TOYROW_NEON -#define HAS_ARGB4444TOARGBROW_NEON -#define HAS_ARGB4444TOUVROW_NEON -#define HAS_ARGB4444TOYROW_NEON -#define HAS_ARGBSETROW_NEON -#define HAS_ARGBTOARGB1555ROW_NEON -#define HAS_ARGBTOARGB4444ROW_NEON -#define HAS_ARGBTORAWROW_NEON -#define HAS_ARGBTORGB24ROW_NEON -#define HAS_ARGBTORGB565DITHERROW_NEON -#define HAS_ARGBTORGB565ROW_NEON -#define HAS_ARGBTOUV411ROW_NEON -#define HAS_ARGBTOUV444ROW_NEON -#define HAS_ARGBTOUVJROW_NEON -#define HAS_ARGBTOUVROW_NEON -#define HAS_ARGBTOYJROW_NEON -#define HAS_ARGBTOYROW_NEON -#define HAS_ARGBEXTRACTALPHAROW_NEON -#define HAS_BGRATOUVROW_NEON -#define HAS_BGRATOYROW_NEON -#define HAS_COPYROW_NEON -#define HAS_I400TOARGBROW_NEON -#define HAS_I411TOARGBROW_NEON -#define HAS_I422ALPHATOARGBROW_NEON -#define HAS_I422TOARGB1555ROW_NEON -#define HAS_I422TOARGB4444ROW_NEON -#define HAS_I422TOARGBROW_NEON -#define HAS_I422TORGB24ROW_NEON -#define HAS_I422TORGB565ROW_NEON -#define HAS_I422TORGBAROW_NEON -#define HAS_I422TOUYVYROW_NEON -#define HAS_I422TOYUY2ROW_NEON -#define HAS_I444TOARGBROW_NEON -#define HAS_J400TOARGBROW_NEON -#define HAS_MERGEUVROW_NEON -#define HAS_MIRRORROW_NEON -#define HAS_MIRRORUVROW_NEON -#define HAS_NV12TOARGBROW_NEON -#define HAS_NV12TORGB565ROW_NEON -#define HAS_NV21TOARGBROW_NEON -#define HAS_RAWTOARGBROW_NEON -#define HAS_RAWTORGB24ROW_NEON -#define HAS_RAWTOUVROW_NEON -#define HAS_RAWTOYROW_NEON -#define HAS_RGB24TOARGBROW_NEON -#define HAS_RGB24TOUVROW_NEON -#define HAS_RGB24TOYROW_NEON -#define HAS_RGB565TOARGBROW_NEON -#define HAS_RGB565TOUVROW_NEON -#define HAS_RGB565TOYROW_NEON -#define HAS_RGBATOUVROW_NEON -#define HAS_RGBATOYROW_NEON -#define HAS_SETROW_NEON -#define HAS_SPLITUVROW_NEON -#define HAS_UYVYTOARGBROW_NEON -#define HAS_UYVYTOUV422ROW_NEON -#define HAS_UYVYTOUVROW_NEON -#define HAS_UYVYTOYROW_NEON -#define HAS_YUY2TOARGBROW_NEON -#define HAS_YUY2TOUV422ROW_NEON -#define HAS_YUY2TOUVROW_NEON -#define HAS_YUY2TOYROW_NEON - -// Effects: -#define HAS_ARGBADDROW_NEON -#define HAS_ARGBATTENUATEROW_NEON -#define HAS_ARGBBLENDROW_NEON -#define HAS_ARGBCOLORMATRIXROW_NEON -#define HAS_ARGBGRAYROW_NEON -#define HAS_ARGBMIRRORROW_NEON -#define HAS_ARGBMULTIPLYROW_NEON -#define HAS_ARGBQUANTIZEROW_NEON -#define HAS_ARGBSEPIAROW_NEON -#define HAS_ARGBSHADEROW_NEON -#define HAS_ARGBSHUFFLEROW_NEON -#define HAS_ARGBSUBTRACTROW_NEON -#define HAS_INTERPOLATEROW_NEON -#define HAS_SOBELROW_NEON -#define HAS_SOBELTOPLANEROW_NEON -#define HAS_SOBELXROW_NEON -#define HAS_SOBELXYROW_NEON -#define HAS_SOBELYROW_NEON -#endif - -// The following are available on Mips platforms: -#if !defined(LIBYUV_DISABLE_MIPS) && defined(__mips__) && \ - (_MIPS_SIM == _MIPS_SIM_ABI32) && (__mips_isa_rev < 6) -#define HAS_COPYROW_MIPS -#if defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_I422TOARGBROW_DSPR2 -#define HAS_INTERPOLATEROW_DSPR2 -#define HAS_MIRRORROW_DSPR2 -#define HAS_MIRRORUVROW_DSPR2 -#define HAS_SPLITUVROW_DSPR2 -#endif -#endif - -#if !defined(LIBYUV_DISABLE_MSA) && defined(__mips_msa) -#define HAS_MIRRORROW_MSA -#define HAS_ARGBMIRRORROW_MSA -#endif - -#if defined(_MSC_VER) && !defined(__CLR_VER) && !defined(__clang__) -#if defined(VISUALC_HAS_AVX2) -#define SIMD_ALIGNED(var) __declspec(align(32)) var -#else -#define SIMD_ALIGNED(var) __declspec(align(16)) var -#endif -typedef __declspec(align(16)) int16 vec16[8]; -typedef __declspec(align(16)) int32 vec32[4]; -typedef __declspec(align(16)) int8 vec8[16]; -typedef __declspec(align(16)) uint16 uvec16[8]; -typedef __declspec(align(16)) uint32 uvec32[4]; -typedef __declspec(align(16)) uint8 uvec8[16]; -typedef __declspec(align(32)) int16 lvec16[16]; -typedef __declspec(align(32)) int32 lvec32[8]; -typedef __declspec(align(32)) int8 lvec8[32]; -typedef __declspec(align(32)) uint16 ulvec16[16]; -typedef __declspec(align(32)) uint32 ulvec32[8]; -typedef __declspec(align(32)) uint8 ulvec8[32]; -#elif !defined(__pnacl__) && (defined(__GNUC__) || defined(__clang__)) -// Caveat GCC 4.2 to 4.7 have a known issue using vectors with const. -#if defined(CLANG_HAS_AVX2) || defined(GCC_HAS_AVX2) -#define SIMD_ALIGNED(var) var __attribute__((aligned(32))) -#else -#define SIMD_ALIGNED(var) var __attribute__((aligned(16))) -#endif -typedef int16 __attribute__((vector_size(16))) vec16; -typedef int32 __attribute__((vector_size(16))) vec32; -typedef int8 __attribute__((vector_size(16))) vec8; -typedef uint16 __attribute__((vector_size(16))) uvec16; -typedef uint32 __attribute__((vector_size(16))) uvec32; -typedef uint8 __attribute__((vector_size(16))) uvec8; -typedef int16 __attribute__((vector_size(32))) lvec16; -typedef int32 __attribute__((vector_size(32))) lvec32; -typedef int8 __attribute__((vector_size(32))) lvec8; -typedef uint16 __attribute__((vector_size(32))) ulvec16; -typedef uint32 __attribute__((vector_size(32))) ulvec32; -typedef uint8 __attribute__((vector_size(32))) ulvec8; -#else -#define SIMD_ALIGNED(var) var -typedef int16 vec16[8]; -typedef int32 vec32[4]; -typedef int8 vec8[16]; -typedef uint16 uvec16[8]; -typedef uint32 uvec32[4]; -typedef uint8 uvec8[16]; -typedef int16 lvec16[16]; -typedef int32 lvec32[8]; -typedef int8 lvec8[32]; -typedef uint16 ulvec16[16]; -typedef uint32 ulvec32[8]; -typedef uint8 ulvec8[32]; -#endif - -#if defined(__aarch64__) -// This struct is for Arm64 color conversion. -struct YuvConstants { - uvec16 kUVToRB; - uvec16 kUVToRB2; - uvec16 kUVToG; - uvec16 kUVToG2; - vec16 kUVBiasBGR; - vec32 kYToRgb; -}; -#elif defined(__arm__) -// This struct is for ArmV7 color conversion. -struct YuvConstants { - uvec8 kUVToRB; - uvec8 kUVToG; - vec16 kUVBiasBGR; - vec32 kYToRgb; -}; -#else -// This struct is for Intel color conversion. -struct YuvConstants { - int8 kUVToB[32]; - int8 kUVToG[32]; - int8 kUVToR[32]; - int16 kUVBiasB[16]; - int16 kUVBiasG[16]; - int16 kUVBiasR[16]; - int16 kYToRgb[16]; -}; - -// Offsets into YuvConstants structure -#define KUVTOB 0 -#define KUVTOG 32 -#define KUVTOR 64 -#define KUVBIASB 96 -#define KUVBIASG 128 -#define KUVBIASR 160 -#define KYTORGB 192 -#endif - -// Conversion matrix for YUV to RGB -extern const struct YuvConstants SIMD_ALIGNED(kYuvI601Constants); // BT.601 -extern const struct YuvConstants SIMD_ALIGNED(kYuvJPEGConstants); // JPeg -extern const struct YuvConstants SIMD_ALIGNED(kYuvH709Constants); // BT.709 - -// Conversion matrix for YVU to BGR -extern const struct YuvConstants SIMD_ALIGNED(kYvuI601Constants); // BT.601 -extern const struct YuvConstants SIMD_ALIGNED(kYvuJPEGConstants); // JPeg -extern const struct YuvConstants SIMD_ALIGNED(kYvuH709Constants); // BT.709 - -#if defined(__APPLE__) || defined(__x86_64__) || defined(__llvm__) -#define OMITFP -#else -#define OMITFP __attribute__((optimize("omit-frame-pointer"))) -#endif - -// NaCL macros for GCC x86 and x64. -#if defined(__native_client__) -#define LABELALIGN ".p2align 5\n" -#else -#define LABELALIGN -#endif -#if defined(__native_client__) && defined(__x86_64__) -// r14 is used for MEMOP macros. -#define NACL_R14 "r14", -#define BUNDLELOCK ".bundle_lock\n" -#define BUNDLEUNLOCK ".bundle_unlock\n" -#define MEMACCESS(base) "%%nacl:(%%r15,%q" #base ")" -#define MEMACCESS2(offset, base) "%%nacl:" #offset "(%%r15,%q" #base ")" -#define MEMLEA(offset, base) #offset "(%q" #base ")" -#define MEMLEA3(offset, index, scale) \ - #offset "(,%q" #index "," #scale ")" -#define MEMLEA4(offset, base, index, scale) \ - #offset "(%q" #base ",%q" #index "," #scale ")" -#define MEMMOVESTRING(s, d) "%%nacl:(%q" #s "),%%nacl:(%q" #d "), %%r15" -#define MEMSTORESTRING(reg, d) "%%" #reg ",%%nacl:(%q" #d "), %%r15" -#define MEMOPREG(opcode, offset, base, index, scale, reg) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " (%%r15,%%r14),%%" #reg "\n" \ - BUNDLEUNLOCK -#define MEMOPMEM(opcode, reg, offset, base, index, scale) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " %%" #reg ",(%%r15,%%r14)\n" \ - BUNDLEUNLOCK -#define MEMOPARG(opcode, offset, base, index, scale, arg) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " (%%r15,%%r14),%" #arg "\n" \ - BUNDLEUNLOCK -#define VMEMOPREG(opcode, offset, base, index, scale, reg1, reg2) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #opcode " (%%r15,%%r14),%%" #reg1 ",%%" #reg2 "\n" \ - BUNDLEUNLOCK -#define VEXTOPMEM(op, sel, reg, offset, base, index, scale) \ - BUNDLELOCK \ - "lea " #offset "(%q" #base ",%q" #index "," #scale "),%%r14d\n" \ - #op " $" #sel ",%%" #reg ",(%%r15,%%r14)\n" \ - BUNDLEUNLOCK -#else // defined(__native_client__) && defined(__x86_64__) -#define NACL_R14 -#define BUNDLEALIGN -#define MEMACCESS(base) "(%" #base ")" -#define MEMACCESS2(offset, base) #offset "(%" #base ")" -#define MEMLEA(offset, base) #offset "(%" #base ")" -#define MEMLEA3(offset, index, scale) \ - #offset "(,%" #index "," #scale ")" -#define MEMLEA4(offset, base, index, scale) \ - #offset "(%" #base ",%" #index "," #scale ")" -#define MEMMOVESTRING(s, d) -#define MEMSTORESTRING(reg, d) -#define MEMOPREG(opcode, offset, base, index, scale, reg) \ - #opcode " " #offset "(%" #base ",%" #index "," #scale "),%%" #reg "\n" -#define MEMOPMEM(opcode, reg, offset, base, index, scale) \ - #opcode " %%" #reg ","#offset "(%" #base ",%" #index "," #scale ")\n" -#define MEMOPARG(opcode, offset, base, index, scale, arg) \ - #opcode " " #offset "(%" #base ",%" #index "," #scale "),%" #arg "\n" -#define VMEMOPREG(opcode, offset, base, index, scale, reg1, reg2) \ - #opcode " " #offset "(%" #base ",%" #index "," #scale "),%%" #reg1 ",%%" \ - #reg2 "\n" -#define VEXTOPMEM(op, sel, reg, offset, base, index, scale) \ - #op " $" #sel ",%%" #reg ","#offset "(%" #base ",%" #index "," #scale ")\n" -#endif // defined(__native_client__) && defined(__x86_64__) - -#if defined(__arm__) || defined(__aarch64__) -#undef MEMACCESS -#if defined(__native_client__) -#define MEMACCESS(base) ".p2align 3\nbic %" #base ", #0xc0000000\n" -#else -#define MEMACCESS(base) -#endif -#endif - -void I444ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_NEON(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb1555, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_NEON(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_NEON(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_NEON(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); - -void ARGBToYRow_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYRow_Any_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_Any_AVX2(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_SSSE3(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_SSSE3(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_SSSE3(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_SSSE3(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_SSSE3(const uint8* src_raw, uint8* dst_y, int width); -void ARGBToYRow_NEON(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_NEON(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToUV444Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUV411Row_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUVRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_NEON(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_NEON(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_NEON(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void RGB24ToUVRow_NEON(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width); -void RAWToUVRow_NEON(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width); -void RGB565ToUVRow_NEON(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width); -void ARGB1555ToUVRow_NEON(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width); -void ARGB4444ToUVRow_NEON(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToYRow_NEON(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_NEON(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_NEON(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_NEON(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_NEON(const uint8* src_raw, uint8* dst_y, int width); -void RGB565ToYRow_NEON(const uint8* src_rgb565, uint8* dst_y, int width); -void ARGB1555ToYRow_NEON(const uint8* src_argb1555, uint8* dst_y, int width); -void ARGB4444ToYRow_NEON(const uint8* src_argb4444, uint8* dst_y, int width); -void ARGBToYRow_C(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_C(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_C(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_C(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_C(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_C(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_C(const uint8* src_raw, uint8* dst_y, int width); -void RGB565ToYRow_C(const uint8* src_rgb565, uint8* dst_y, int width); -void ARGB1555ToYRow_C(const uint8* src_argb1555, uint8* dst_y, int width); -void ARGB4444ToYRow_C(const uint8* src_argb4444, uint8* dst_y, int width); -void ARGBToYRow_Any_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_Any_SSSE3(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_Any_SSSE3(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_Any_SSSE3(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_Any_SSSE3(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_Any_SSSE3(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_Any_SSSE3(const uint8* src_raw, uint8* dst_y, int width); -void ARGBToYRow_Any_NEON(const uint8* src_argb, uint8* dst_y, int width); -void ARGBToYJRow_Any_NEON(const uint8* src_argb, uint8* dst_y, int width); -void BGRAToYRow_Any_NEON(const uint8* src_bgra, uint8* dst_y, int width); -void ABGRToYRow_Any_NEON(const uint8* src_abgr, uint8* dst_y, int width); -void RGBAToYRow_Any_NEON(const uint8* src_rgba, uint8* dst_y, int width); -void RGB24ToYRow_Any_NEON(const uint8* src_rgb24, uint8* dst_y, int width); -void RAWToYRow_Any_NEON(const uint8* src_raw, uint8* dst_y, int width); -void RGB565ToYRow_Any_NEON(const uint8* src_rgb565, uint8* dst_y, int width); -void ARGB1555ToYRow_Any_NEON(const uint8* src_argb1555, uint8* dst_y, - int width); -void ARGB4444ToYRow_Any_NEON(const uint8* src_argb4444, uint8* dst_y, - int width); - -void ARGBToUVRow_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_SSSE3(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_SSSE3(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_SSSE3(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_Any_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_Any_AVX2(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_Any_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_Any_SSSE3(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_Any_SSSE3(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_Any_SSSE3(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_Any_SSSE3(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV444Row_Any_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUV411Row_Any_NEON(const uint8* src_argb, uint8* dst_u, uint8* dst_v, - int width); -void ARGBToUVRow_Any_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_Any_NEON(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_Any_NEON(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_Any_NEON(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_Any_NEON(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void RGB24ToUVRow_Any_NEON(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width); -void RAWToUVRow_Any_NEON(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width); -void RGB565ToUVRow_Any_NEON(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width); -void ARGB1555ToUVRow_Any_NEON(const uint8* src_argb1555, - int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width); -void ARGB4444ToUVRow_Any_NEON(const uint8* src_argb4444, - int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVRow_C(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUVJRow_C(const uint8* src_argb, int src_stride_argb, - uint8* dst_u, uint8* dst_v, int width); -void BGRAToUVRow_C(const uint8* src_bgra, int src_stride_bgra, - uint8* dst_u, uint8* dst_v, int width); -void ABGRToUVRow_C(const uint8* src_abgr, int src_stride_abgr, - uint8* dst_u, uint8* dst_v, int width); -void RGBAToUVRow_C(const uint8* src_rgba, int src_stride_rgba, - uint8* dst_u, uint8* dst_v, int width); -void RGB24ToUVRow_C(const uint8* src_rgb24, int src_stride_rgb24, - uint8* dst_u, uint8* dst_v, int width); -void RAWToUVRow_C(const uint8* src_raw, int src_stride_raw, - uint8* dst_u, uint8* dst_v, int width); -void RGB565ToUVRow_C(const uint8* src_rgb565, int src_stride_rgb565, - uint8* dst_u, uint8* dst_v, int width); -void ARGB1555ToUVRow_C(const uint8* src_argb1555, int src_stride_argb1555, - uint8* dst_u, uint8* dst_v, int width); -void ARGB4444ToUVRow_C(const uint8* src_argb4444, int src_stride_argb4444, - uint8* dst_u, uint8* dst_v, int width); - -void ARGBToUV444Row_SSSE3(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV444Row_Any_SSSE3(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); - -void ARGBToUV444Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); -void ARGBToUV411Row_C(const uint8* src_argb, - uint8* dst_u, uint8* dst_v, int width); - -void MirrorRow_AVX2(const uint8* src, uint8* dst, int width); -void MirrorRow_SSSE3(const uint8* src, uint8* dst, int width); -void MirrorRow_NEON(const uint8* src, uint8* dst, int width); -void MirrorRow_DSPR2(const uint8* src, uint8* dst, int width); -void MirrorRow_MSA(const uint8* src, uint8* dst, int width); -void MirrorRow_C(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_AVX2(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_SSSE3(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_SSE2(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_NEON(const uint8* src, uint8* dst, int width); -void MirrorRow_Any_MSA(const uint8* src, uint8* dst, int width); - -void MirrorUVRow_SSSE3(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void MirrorUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); - -void ARGBMirrorRow_AVX2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_SSE2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_NEON(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_MSA(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_C(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_AVX2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_SSE2(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_NEON(const uint8* src, uint8* dst, int width); -void ARGBMirrorRow_Any_MSA(const uint8* src, uint8* dst, int width); - -void SplitUVRow_C(const uint8* src_uv, uint8* dst_u, uint8* dst_v, int width); -void SplitUVRow_SSE2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_SSE2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_AVX2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_NEON(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); -void SplitUVRow_Any_DSPR2(const uint8* src_uv, uint8* dst_u, uint8* dst_v, - int width); - -void MergeUVRow_C(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_SSE2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_AVX2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_Any_SSE2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_Any_AVX2(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); -void MergeUVRow_Any_NEON(const uint8* src_u, const uint8* src_v, uint8* dst_uv, - int width); - -void CopyRow_SSE2(const uint8* src, uint8* dst, int count); -void CopyRow_AVX(const uint8* src, uint8* dst, int count); -void CopyRow_ERMS(const uint8* src, uint8* dst, int count); -void CopyRow_NEON(const uint8* src, uint8* dst, int count); -void CopyRow_MIPS(const uint8* src, uint8* dst, int count); -void CopyRow_C(const uint8* src, uint8* dst, int count); -void CopyRow_Any_SSE2(const uint8* src, uint8* dst, int count); -void CopyRow_Any_AVX(const uint8* src, uint8* dst, int count); -void CopyRow_Any_NEON(const uint8* src, uint8* dst, int count); - -void CopyRow_16_C(const uint16* src, uint16* dst, int count); - -void ARGBCopyAlphaRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBCopyAlphaRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBCopyAlphaRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBCopyAlphaRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBCopyAlphaRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - int width); - -void ARGBExtractAlphaRow_C(const uint8* src_argb, uint8* dst_a, int width); -void ARGBExtractAlphaRow_SSE2(const uint8* src_argb, uint8* dst_a, int width); -void ARGBExtractAlphaRow_NEON(const uint8* src_argb, uint8* dst_a, int width); -void ARGBExtractAlphaRow_Any_SSE2(const uint8* src_argb, uint8* dst_a, - int width); -void ARGBExtractAlphaRow_Any_NEON(const uint8* src_argb, uint8* dst_a, - int width); - -void ARGBCopyYToAlphaRow_C(const uint8* src_y, uint8* dst_argb, int width); -void ARGBCopyYToAlphaRow_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void ARGBCopyYToAlphaRow_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void ARGBCopyYToAlphaRow_Any_SSE2(const uint8* src_y, uint8* dst_argb, - int width); -void ARGBCopyYToAlphaRow_Any_AVX2(const uint8* src_y, uint8* dst_argb, - int width); - -void SetRow_C(uint8* dst, uint8 v8, int count); -void SetRow_X86(uint8* dst, uint8 v8, int count); -void SetRow_ERMS(uint8* dst, uint8 v8, int count); -void SetRow_NEON(uint8* dst, uint8 v8, int count); -void SetRow_Any_X86(uint8* dst, uint8 v8, int count); -void SetRow_Any_NEON(uint8* dst, uint8 v8, int count); - -void ARGBSetRow_C(uint8* dst_argb, uint32 v32, int count); -void ARGBSetRow_X86(uint8* dst_argb, uint32 v32, int count); -void ARGBSetRow_NEON(uint8* dst_argb, uint32 v32, int count); -void ARGBSetRow_Any_NEON(uint8* dst_argb, uint32 v32, int count); - -// ARGBShufflers for BGRAToARGB etc. -void ARGBShuffleRow_C(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_SSE2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_AVX2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_NEON(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_SSSE3(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); -void ARGBShuffleRow_Any_NEON(const uint8* src_argb, uint8* dst_argb, - const uint8* shuffler, int width); - -void RGB24ToARGBRow_SSSE3(const uint8* src_rgb24, uint8* dst_argb, int width); -void RAWToARGBRow_SSSE3(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_SSSE3(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_SSE2(const uint8* src_rgb565, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_SSE2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_SSE2(const uint8* src_argb4444, uint8* dst_argb, - int width); -void RGB565ToARGBRow_AVX2(const uint8* src_rgb565, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_AVX2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_AVX2(const uint8* src_argb4444, uint8* dst_argb, - int width); - -void RGB24ToARGBRow_NEON(const uint8* src_rgb24, uint8* dst_argb, int width); -void RAWToARGBRow_NEON(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_NEON(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_NEON(const uint8* src_rgb565, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_NEON(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_NEON(const uint8* src_argb4444, uint8* dst_argb, - int width); -void RGB24ToARGBRow_C(const uint8* src_rgb24, uint8* dst_argb, int width); -void RAWToARGBRow_C(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_C(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_C(const uint8* src_rgb, uint8* dst_argb, int width); -void ARGB1555ToARGBRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGB4444ToARGBRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void RGB24ToARGBRow_Any_SSSE3(const uint8* src_rgb24, uint8* dst_argb, - int width); -void RAWToARGBRow_Any_SSSE3(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_Any_SSSE3(const uint8* src_raw, uint8* dst_rgb24, int width); - -void RGB565ToARGBRow_Any_SSE2(const uint8* src_rgb565, uint8* dst_argb, - int width); -void ARGB1555ToARGBRow_Any_SSE2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_Any_SSE2(const uint8* src_argb4444, uint8* dst_argb, - int width); -void RGB565ToARGBRow_Any_AVX2(const uint8* src_rgb565, uint8* dst_argb, - int width); -void ARGB1555ToARGBRow_Any_AVX2(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_Any_AVX2(const uint8* src_argb4444, uint8* dst_argb, - int width); - -void RGB24ToARGBRow_Any_NEON(const uint8* src_rgb24, uint8* dst_argb, - int width); -void RAWToARGBRow_Any_NEON(const uint8* src_raw, uint8* dst_argb, int width); -void RAWToRGB24Row_Any_NEON(const uint8* src_raw, uint8* dst_rgb24, int width); -void RGB565ToARGBRow_Any_NEON(const uint8* src_rgb565, uint8* dst_argb, - int width); -void ARGB1555ToARGBRow_Any_NEON(const uint8* src_argb1555, uint8* dst_argb, - int width); -void ARGB4444ToARGBRow_Any_NEON(const uint8* src_argb4444, uint8* dst_argb, - int width); - -void ARGBToRGB24Row_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); - -void ARGBToRGB565DitherRow_C(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); -void ARGBToRGB565DitherRow_SSE2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); -void ARGBToRGB565DitherRow_AVX2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void ARGBToRGB565Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); - -void ARGBToRGB24Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565DitherRow_NEON(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void ARGBToRGBARow_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB24Row_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_C(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB4444Row_C(const uint8* src_argb, uint8* dst_rgb, int width); - -void J400ToARGBRow_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_NEON(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_C(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_Any_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_Any_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void J400ToARGBRow_Any_NEON(const uint8* src_y, uint8* dst_argb, int width); - -void I444ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_C(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_C(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_C(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_C(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb4444, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb565, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_SSSE3(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_SSSE3(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_AVX2(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_AVX2(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgb24, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I444ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_Any_SSSE3(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_Any_AVX2(const uint8* y_buf, - const uint8* u_buf, - const uint8* v_buf, - const uint8* a_buf, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_Any_SSSE3(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_Any_AVX2(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_Any_SSSE3(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_Any_AVX2(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_Any_SSSE3(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_Any_SSSE3(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_Any_AVX2(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_Any_AVX2(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_rgba, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_Any_SSSE3(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_Any_AVX2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); - -void I400ToARGBRow_C(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_NEON(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_Any_SSE2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_Any_AVX2(const uint8* src_y, uint8* dst_argb, int width); -void I400ToARGBRow_Any_NEON(const uint8* src_y, uint8* dst_argb, int width); - -// ARGB preattenuated alpha blend. -void ARGBBlendRow_SSSE3(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBBlendRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBBlendRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -// Unattenuated planar alpha blend. -void BlendPlaneRow_SSSE3(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_Any_SSSE3(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_AVX2(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_Any_AVX2(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); -void BlendPlaneRow_C(const uint8* src0, const uint8* src1, - const uint8* alpha, uint8* dst, int width); - -// ARGB multiply images. Same API as Blend, but these require -// pointer and width alignment for SSE2. -void ARGBMultiplyRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_Any_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_Any_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBMultiplyRow_Any_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -// ARGB add images. -void ARGBAddRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_Any_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_Any_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBAddRow_Any_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -// ARGB subtract images. Same API as Blend, but these require -// pointer and width alignment for SSE2. -void ARGBSubtractRow_C(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_Any_SSE2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_Any_AVX2(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); -void ARGBSubtractRow_Any_NEON(const uint8* src_argb, const uint8* src_argb1, - uint8* dst_argb, int width); - -void ARGBToRGB24Row_Any_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_Any_SSSE3(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToARGB4444Row_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, - int width); - -void ARGBToRGB565DitherRow_Any_SSE2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); -void ARGBToRGB565DitherRow_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void ARGBToRGB565Row_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToARGB4444Row_Any_AVX2(const uint8* src_argb, uint8* dst_rgb, - int width); - -void ARGBToRGB24Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRAWRow_Any_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToRGB565Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, int width); -void ARGBToARGB1555Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToARGB4444Row_Any_NEON(const uint8* src_argb, uint8* dst_rgb, - int width); -void ARGBToRGB565DitherRow_Any_NEON(const uint8* src_argb, uint8* dst_rgb, - const uint32 dither4, int width); - -void I444ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422AlphaToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - const uint8* src_a, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I411ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGBARow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB24Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB4444Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGB1555Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToRGB565Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV21ToARGBRow_Any_NEON(const uint8* src_y, - const uint8* src_vu, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void NV12ToRGB565Row_Any_NEON(const uint8* src_y, - const uint8* src_uv, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void YUY2ToARGBRow_Any_NEON(const uint8* src_yuy2, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void UYVYToARGBRow_Any_NEON(const uint8* src_uyvy, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_DSPR2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); -void I422ToARGBRow_DSPR2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_argb, - const struct YuvConstants* yuvconstants, - int width); - -void YUY2ToYRow_AVX2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_AVX2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_AVX2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_SSE2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_SSE2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_SSE2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_NEON(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_NEON(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_NEON(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_C(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_C(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_C(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_Any_AVX2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_Any_AVX2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_Any_AVX2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_Any_SSE2(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_Any_SSE2(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_Any_SSE2(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToYRow_Any_NEON(const uint8* src_yuy2, uint8* dst_y, int width); -void YUY2ToUVRow_Any_NEON(const uint8* src_yuy2, int stride_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void YUY2ToUV422Row_Any_NEON(const uint8* src_yuy2, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_AVX2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_SSE2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_SSE2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_SSE2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_AVX2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_NEON(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_NEON(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_NEON(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); - -void UYVYToYRow_C(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_C(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_C(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_Any_AVX2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_Any_AVX2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_Any_AVX2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_Any_SSE2(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_Any_SSE2(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_Any_SSE2(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToYRow_Any_NEON(const uint8* src_uyvy, uint8* dst_y, int width); -void UYVYToUVRow_Any_NEON(const uint8* src_uyvy, int stride_uyvy, - uint8* dst_u, uint8* dst_v, int width); -void UYVYToUV422Row_Any_NEON(const uint8* src_uyvy, - uint8* dst_u, uint8* dst_v, int width); - -void I422ToYUY2Row_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_C(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_Any_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_Any_SSE2(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); -void I422ToYUY2Row_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_yuy2, int width); -void I422ToUYVYRow_Any_NEON(const uint8* src_y, - const uint8* src_u, - const uint8* src_v, - uint8* dst_uyvy, int width); - -// Effects related row functions. -void ARGBAttenuateRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_NEON(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBAttenuateRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBAttenuateRow_Any_SSSE3(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBAttenuateRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBAttenuateRow_Any_NEON(const uint8* src_argb, uint8* dst_argb, - int width); - -// Inverse table for unattenuate, shared by C and SSE2. -extern const uint32 fixed_invtbl8[256]; -void ARGBUnattenuateRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBUnattenuateRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBUnattenuateRow_AVX2(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBUnattenuateRow_Any_SSE2(const uint8* src_argb, uint8* dst_argb, - int width); -void ARGBUnattenuateRow_Any_AVX2(const uint8* src_argb, uint8* dst_argb, - int width); - -void ARGBGrayRow_C(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBGrayRow_SSSE3(const uint8* src_argb, uint8* dst_argb, int width); -void ARGBGrayRow_NEON(const uint8* src_argb, uint8* dst_argb, int width); - -void ARGBSepiaRow_C(uint8* dst_argb, int width); -void ARGBSepiaRow_SSSE3(uint8* dst_argb, int width); -void ARGBSepiaRow_NEON(uint8* dst_argb, int width); - -void ARGBColorMatrixRow_C(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width); -void ARGBColorMatrixRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width); -void ARGBColorMatrixRow_NEON(const uint8* src_argb, uint8* dst_argb, - const int8* matrix_argb, int width); - -void ARGBColorTableRow_C(uint8* dst_argb, const uint8* table_argb, int width); -void ARGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width); - -void RGBColorTableRow_C(uint8* dst_argb, const uint8* table_argb, int width); -void RGBColorTableRow_X86(uint8* dst_argb, const uint8* table_argb, int width); - -void ARGBQuantizeRow_C(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width); -void ARGBQuantizeRow_SSE2(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width); -void ARGBQuantizeRow_NEON(uint8* dst_argb, int scale, int interval_size, - int interval_offset, int width); - -void ARGBShadeRow_C(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value); -void ARGBShadeRow_SSE2(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value); -void ARGBShadeRow_NEON(const uint8* src_argb, uint8* dst_argb, int width, - uint32 value); - -// Used for blur. -void CumulativeSumToAverageRow_SSE2(const int32* topleft, const int32* botleft, - int width, int area, uint8* dst, int count); -void ComputeCumulativeSumRow_SSE2(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width); - -void CumulativeSumToAverageRow_C(const int32* topleft, const int32* botleft, - int width, int area, uint8* dst, int count); -void ComputeCumulativeSumRow_C(const uint8* row, int32* cumsum, - const int32* previous_cumsum, int width); - -LIBYUV_API -void ARGBAffineRow_C(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); -LIBYUV_API -void ARGBAffineRow_SSE2(const uint8* src_argb, int src_argb_stride, - uint8* dst_argb, const float* uv_dudv, int width); - -// Used for I420Scale, ARGBScale, and ARGBInterpolate. -void InterpolateRow_C(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, - int width, int source_y_fraction); -void InterpolateRow_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_AVX2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_NEON(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_DSPR2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_NEON(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_AVX2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); -void InterpolateRow_Any_DSPR2(uint8* dst_ptr, const uint8* src_ptr, - ptrdiff_t src_stride_ptr, int width, - int source_y_fraction); - -void InterpolateRow_16_C(uint16* dst_ptr, const uint16* src_ptr, - ptrdiff_t src_stride_ptr, - int width, int source_y_fraction); - -// Sobel images. -void SobelXRow_C(const uint8* src_y0, const uint8* src_y1, const uint8* src_y2, - uint8* dst_sobelx, int width); -void SobelXRow_SSE2(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width); -void SobelXRow_NEON(const uint8* src_y0, const uint8* src_y1, - const uint8* src_y2, uint8* dst_sobelx, int width); -void SobelYRow_C(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width); -void SobelYRow_SSE2(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width); -void SobelYRow_NEON(const uint8* src_y0, const uint8* src_y1, - uint8* dst_sobely, int width); -void SobelRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelToPlaneRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelToPlaneRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelToPlaneRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelXYRow_C(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelXYRow_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelXYRow_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_Any_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelRow_Any_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelToPlaneRow_Any_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelToPlaneRow_Any_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_y, int width); -void SobelXYRow_Any_SSE2(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); -void SobelXYRow_Any_NEON(const uint8* src_sobelx, const uint8* src_sobely, - uint8* dst_argb, int width); - -void ARGBPolynomialRow_C(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width); -void ARGBPolynomialRow_SSE2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width); -void ARGBPolynomialRow_AVX2(const uint8* src_argb, - uint8* dst_argb, const float* poly, - int width); - -// Scale and convert to half float. -void HalfFloatRow_C(const uint16* src, uint16* dst, float scale, int width); -void HalfFloatRow_AVX2(const uint16* src, uint16* dst, float scale, int width); -void HalfFloatRow_Any_AVX2(const uint16* src, uint16* dst, float scale, - int width); -void HalfFloatRow_SSE2(const uint16* src, uint16* dst, float scale, int width); -void HalfFloatRow_Any_SSE2(const uint16* src, uint16* dst, float scale, - int width); - -void ARGBLumaColorTableRow_C(const uint8* src_argb, uint8* dst_argb, int width, - const uint8* luma, uint32 lumacoeff); -void ARGBLumaColorTableRow_SSSE3(const uint8* src_argb, uint8* dst_argb, - int width, - const uint8* luma, uint32 lumacoeff); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_ROW_H_ diff --git a/third_party/libyuv/include/libyuv/scale.h b/third_party/libyuv/include/libyuv/scale.h deleted file mode 100644 index ae14694598b..00000000000 --- a/third_party/libyuv/include/libyuv/scale.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_SCALE_H_ -#define INCLUDE_LIBYUV_SCALE_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -// Supported filtering. -typedef enum FilterMode { - kFilterNone = 0, // Point sample; Fastest. - kFilterLinear = 1, // Filter horizontally only. - kFilterBilinear = 2, // Faster than box, but lower quality scaling down. - kFilterBox = 3 // Highest quality. -} FilterModeEnum; - -// Scale a YUV plane. -LIBYUV_API -void ScalePlane(const uint8* src, int src_stride, - int src_width, int src_height, - uint8* dst, int dst_stride, - int dst_width, int dst_height, - enum FilterMode filtering); - -LIBYUV_API -void ScalePlane_16(const uint16* src, int src_stride, - int src_width, int src_height, - uint16* dst, int dst_stride, - int dst_width, int dst_height, - enum FilterMode filtering); - -// Scales a YUV 4:2:0 image from the src width and height to the -// dst width and height. -// If filtering is kFilterNone, a simple nearest-neighbor algorithm is -// used. This produces basic (blocky) quality at the fastest speed. -// If filtering is kFilterBilinear, interpolation is used to produce a better -// quality image, at the expense of speed. -// If filtering is kFilterBox, averaging is used to produce ever better -// quality image, at further expense of speed. -// Returns 0 if successful. - -LIBYUV_API -int I420Scale(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - int src_width, int src_height, - uint8* dst_y, int dst_stride_y, - uint8* dst_u, int dst_stride_u, - uint8* dst_v, int dst_stride_v, - int dst_width, int dst_height, - enum FilterMode filtering); - -LIBYUV_API -int I420Scale_16(const uint16* src_y, int src_stride_y, - const uint16* src_u, int src_stride_u, - const uint16* src_v, int src_stride_v, - int src_width, int src_height, - uint16* dst_y, int dst_stride_y, - uint16* dst_u, int dst_stride_u, - uint16* dst_v, int dst_stride_v, - int dst_width, int dst_height, - enum FilterMode filtering); - -#ifdef __cplusplus -// Legacy API. Deprecated. -LIBYUV_API -int Scale(const uint8* src_y, const uint8* src_u, const uint8* src_v, - int src_stride_y, int src_stride_u, int src_stride_v, - int src_width, int src_height, - uint8* dst_y, uint8* dst_u, uint8* dst_v, - int dst_stride_y, int dst_stride_u, int dst_stride_v, - int dst_width, int dst_height, - LIBYUV_BOOL interpolate); - -// Legacy API. Deprecated. -LIBYUV_API -int ScaleOffset(const uint8* src_i420, int src_width, int src_height, - uint8* dst_i420, int dst_width, int dst_height, int dst_yoffset, - LIBYUV_BOOL interpolate); - -// For testing, allow disabling of specialized scalers. -LIBYUV_API -void SetUseReferenceImpl(LIBYUV_BOOL use); -#endif // __cplusplus - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_SCALE_H_ diff --git a/third_party/libyuv/include/libyuv/scale_argb.h b/third_party/libyuv/include/libyuv/scale_argb.h deleted file mode 100644 index 35cd191c0f6..00000000000 --- a/third_party/libyuv/include/libyuv/scale_argb.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_SCALE_ARGB_H_ -#define INCLUDE_LIBYUV_SCALE_ARGB_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/scale.h" // For FilterMode - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -LIBYUV_API -int ARGBScale(const uint8* src_argb, int src_stride_argb, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - int dst_width, int dst_height, - enum FilterMode filtering); - -// Clipped scale takes destination rectangle coordinates for clip values. -LIBYUV_API -int ARGBScaleClip(const uint8* src_argb, int src_stride_argb, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - int dst_width, int dst_height, - int clip_x, int clip_y, int clip_width, int clip_height, - enum FilterMode filtering); - -// Scale with YUV conversion to ARGB and clipping. -LIBYUV_API -int YUVToARGBScaleClip(const uint8* src_y, int src_stride_y, - const uint8* src_u, int src_stride_u, - const uint8* src_v, int src_stride_v, - uint32 src_fourcc, - int src_width, int src_height, - uint8* dst_argb, int dst_stride_argb, - uint32 dst_fourcc, - int dst_width, int dst_height, - int clip_x, int clip_y, int clip_width, int clip_height, - enum FilterMode filtering); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_SCALE_ARGB_H_ diff --git a/third_party/libyuv/include/libyuv/scale_row.h b/third_party/libyuv/include/libyuv/scale_row.h deleted file mode 100644 index 791fbf7d053..00000000000 --- a/third_party/libyuv/include/libyuv/scale_row.h +++ /dev/null @@ -1,503 +0,0 @@ -/* - * Copyright 2013 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_SCALE_ROW_H_ -#define INCLUDE_LIBYUV_SCALE_ROW_H_ - -#include "libyuv/basic_types.h" -#include "libyuv/scale.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -#if defined(__pnacl__) || defined(__CLR_VER) || \ - (defined(__i386__) && !defined(__SSE2__)) -#define LIBYUV_DISABLE_X86 -#endif -// MemorySanitizer does not support assembly code yet. http://crbug.com/344505 -#if defined(__has_feature) -#if __has_feature(memory_sanitizer) -#define LIBYUV_DISABLE_X86 -#endif -#endif - -// GCC >= 4.7.0 required for AVX2. -#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__)) -#if (__GNUC__ > 4) || (__GNUC__ == 4 && (__GNUC_MINOR__ >= 7)) -#define GCC_HAS_AVX2 1 -#endif // GNUC >= 4.7 -#endif // __GNUC__ - -// clang >= 3.4.0 required for AVX2. -#if defined(__clang__) && (defined(__x86_64__) || defined(__i386__)) -#if (__clang_major__ > 3) || (__clang_major__ == 3 && (__clang_minor__ >= 4)) -#define CLANG_HAS_AVX2 1 -#endif // clang >= 3.4 -#endif // __clang__ - -// Visual C 2012 required for AVX2. -#if defined(_M_IX86) && !defined(__clang__) && \ - defined(_MSC_VER) && _MSC_VER >= 1700 -#define VISUALC_HAS_AVX2 1 -#endif // VisualStudio >= 2012 - -// The following are available on all x86 platforms: -#if !defined(LIBYUV_DISABLE_X86) && \ - (defined(_M_IX86) || defined(__x86_64__) || defined(__i386__)) -#define HAS_FIXEDDIV1_X86 -#define HAS_FIXEDDIV_X86 -#define HAS_SCALEARGBCOLS_SSE2 -#define HAS_SCALEARGBCOLSUP2_SSE2 -#define HAS_SCALEARGBFILTERCOLS_SSSE3 -#define HAS_SCALEARGBROWDOWN2_SSE2 -#define HAS_SCALEARGBROWDOWNEVEN_SSE2 -#define HAS_SCALECOLSUP2_SSE2 -#define HAS_SCALEFILTERCOLS_SSSE3 -#define HAS_SCALEROWDOWN2_SSSE3 -#define HAS_SCALEROWDOWN34_SSSE3 -#define HAS_SCALEROWDOWN38_SSSE3 -#define HAS_SCALEROWDOWN4_SSSE3 -#define HAS_SCALEADDROW_SSE2 -#endif - -// The following are available on all x86 platforms, but -// require VS2012, clang 3.4 or gcc 4.7. -// The code supports NaCL but requires a new compiler and validator. -#if !defined(LIBYUV_DISABLE_X86) && (defined(VISUALC_HAS_AVX2) || \ - defined(CLANG_HAS_AVX2) || defined(GCC_HAS_AVX2)) -#define HAS_SCALEADDROW_AVX2 -#define HAS_SCALEROWDOWN2_AVX2 -#define HAS_SCALEROWDOWN4_AVX2 -#endif - -// The following are available on Neon platforms: -#if !defined(LIBYUV_DISABLE_NEON) && !defined(__native_client__) && \ - (defined(__ARM_NEON__) || defined(LIBYUV_NEON) || defined(__aarch64__)) -#define HAS_SCALEARGBCOLS_NEON -#define HAS_SCALEARGBROWDOWN2_NEON -#define HAS_SCALEARGBROWDOWNEVEN_NEON -#define HAS_SCALEFILTERCOLS_NEON -#define HAS_SCALEROWDOWN2_NEON -#define HAS_SCALEROWDOWN34_NEON -#define HAS_SCALEROWDOWN38_NEON -#define HAS_SCALEROWDOWN4_NEON -#define HAS_SCALEARGBFILTERCOLS_NEON -#endif - -// The following are available on Mips platforms: -#if !defined(LIBYUV_DISABLE_MIPS) && !defined(__native_client__) && \ - defined(__mips__) && defined(__mips_dsp) && (__mips_dsp_rev >= 2) -#define HAS_SCALEROWDOWN2_DSPR2 -#define HAS_SCALEROWDOWN4_DSPR2 -#define HAS_SCALEROWDOWN34_DSPR2 -#define HAS_SCALEROWDOWN38_DSPR2 -#endif - -// Scale ARGB vertically with bilinear interpolation. -void ScalePlaneVertical(int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint8* src_argb, uint8* dst_argb, - int x, int y, int dy, - int bpp, enum FilterMode filtering); - -void ScalePlaneVertical_16(int src_height, - int dst_width, int dst_height, - int src_stride, int dst_stride, - const uint16* src_argb, uint16* dst_argb, - int x, int y, int dy, - int wpp, enum FilterMode filtering); - -// Simplify the filtering based on scale factors. -enum FilterMode ScaleFilterReduce(int src_width, int src_height, - int dst_width, int dst_height, - enum FilterMode filtering); - -// Divide num by div and return as 16.16 fixed point result. -int FixedDiv_C(int num, int div); -int FixedDiv_X86(int num, int div); -// Divide num - 1 by div - 1 and return as 16.16 fixed point result. -int FixedDiv1_C(int num, int div); -int FixedDiv1_X86(int num, int div); -#ifdef HAS_FIXEDDIV_X86 -#define FixedDiv FixedDiv_X86 -#define FixedDiv1 FixedDiv1_X86 -#else -#define FixedDiv FixedDiv_C -#define FixedDiv1 FixedDiv1_C -#endif - -// Compute slope values for stepping. -void ScaleSlope(int src_width, int src_height, - int dst_width, int dst_height, - enum FilterMode filtering, - int* x, int* y, int* dx, int* dy); - -void ScaleRowDown2_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown2Linear_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Linear_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown2Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_Odd_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown4_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown4Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown34_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown34_0_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_0_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* d, int dst_width); -void ScaleRowDown34_1_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_1_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* d, int dst_width); -void ScaleCols_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleCols_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx); -void ScaleColsUp2_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int, int); -void ScaleColsUp2_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int, int); -void ScaleFilterCols_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleFilterCols_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx); -void ScaleFilterCols64_C(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleFilterCols64_16_C(uint16* dst_ptr, const uint16* src_ptr, - int dst_width, int x, int dx); -void ScaleRowDown38_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown38_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst, int dst_width); -void ScaleRowDown38_3_Box_C(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_16_C(const uint16* src_ptr, - ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_C(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_16_C(const uint16* src_ptr, ptrdiff_t src_stride, - uint16* dst_ptr, int dst_width); -void ScaleAddRow_C(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_16_C(const uint16* src_ptr, uint32* dst_ptr, int src_width); -void ScaleARGBRowDown2_C(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Linear_C(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_C(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_C(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_C(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBCols_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBCols64_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBColsUp2_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int, int); -void ScaleARGBFilterCols_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols64_C(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); - -// Specialized scalers for x86. -void ScaleRowDown2_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleRowDown34_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Odd_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Linear_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown2Box_Odd_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_Any_AVX2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleRowDown34_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_Any_SSSE3(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_2_Box_Any_SSSE3(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleAddRow_SSE2(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_AVX2(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_Any_SSE2(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_Any_AVX2(const uint8* src_ptr, uint16* dst_ptr, int src_width); - -void ScaleFilterCols_SSSE3(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); -void ScaleColsUp2_SSE2(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); - - -// ARGB Column functions -void ScaleARGBCols_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols_SSSE3(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBColsUp2_SSE2(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBCols_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBFilterCols_Any_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); -void ScaleARGBCols_Any_NEON(uint8* dst_argb, const uint8* src_argb, - int dst_width, int x, int dx); - -// ARGB Row functions -void ScaleARGBRowDown2_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Linear_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleARGBRowDown2Linear_NEON(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleARGBRowDown2_Any_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Linear_Any_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_Any_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleARGBRowDown2Linear_Any_NEON(const uint8* src_argb, - ptrdiff_t src_stride, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDown2Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); - -void ScaleARGBRowDownEven_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_Any_SSE2(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_Any_SSE2(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEven_Any_NEON(const uint8* src_argb, ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); -void ScaleARGBRowDownEvenBox_Any_NEON(const uint8* src_argb, - ptrdiff_t src_stride, - int src_stepx, - uint8* dst_argb, int dst_width); - -// ScaleRowDown2Box also used by planar functions -// NEON downscalers with interpolation. - -// Note - not static due to reuse in convert for 444 to 420. -void ScaleRowDown2_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Linear_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); - -void ScaleRowDown4_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -// Down scale from 4 to 3 pixels. Use the neon multilane read/write -// to load up the every 4th pixel into a 4 different registers. -// Point samples 32 pixels to 24 pixels. -void ScaleRowDown34_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -// 32 -> 12 -void ScaleRowDown38_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x3 -> 12x1 -void ScaleRowDown38_3_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x2 -> 12x1 -void ScaleRowDown38_2_Box_NEON(const uint8* src_ptr, - ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleRowDown2_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Linear_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_Odd_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown4Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_0_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown34_1_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32 -> 12 -void ScaleRowDown38_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x3 -> 12x1 -void ScaleRowDown38_3_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -// 32x2 -> 12x1 -void ScaleRowDown38_2_Box_Any_NEON(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -void ScaleAddRow_NEON(const uint8* src_ptr, uint16* dst_ptr, int src_width); -void ScaleAddRow_Any_NEON(const uint8* src_ptr, uint16* dst_ptr, int src_width); - -void ScaleFilterCols_NEON(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); - -void ScaleFilterCols_Any_NEON(uint8* dst_ptr, const uint8* src_ptr, - int dst_width, int x, int dx); - -void ScaleRowDown2_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown2Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown4Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown34_0_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown34_1_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* d, int dst_width); -void ScaleRowDown38_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst, int dst_width); -void ScaleRowDown38_2_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); -void ScaleRowDown38_3_Box_DSPR2(const uint8* src_ptr, ptrdiff_t src_stride, - uint8* dst_ptr, int dst_width); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_SCALE_ROW_H_ diff --git a/third_party/libyuv/include/libyuv/version.h b/third_party/libyuv/include/libyuv/version.h deleted file mode 100644 index 3a8f6337ca2..00000000000 --- a/third_party/libyuv/include/libyuv/version.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2012 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef INCLUDE_LIBYUV_VERSION_H_ -#define INCLUDE_LIBYUV_VERSION_H_ - -#define LIBYUV_VERSION 1622 - -#endif // INCLUDE_LIBYUV_VERSION_H_ diff --git a/third_party/libyuv/include/libyuv/video_common.h b/third_party/libyuv/include/libyuv/video_common.h deleted file mode 100644 index cb425426a25..00000000000 --- a/third_party/libyuv/include/libyuv/video_common.h +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright 2011 The LibYuv Project Authors. All rights reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -// Common definitions for video, including fourcc and VideoFormat. - -#ifndef INCLUDE_LIBYUV_VIDEO_COMMON_H_ -#define INCLUDE_LIBYUV_VIDEO_COMMON_H_ - -#include "libyuv/basic_types.h" - -#ifdef __cplusplus -namespace libyuv { -extern "C" { -#endif - -////////////////////////////////////////////////////////////////////////////// -// Definition of FourCC codes -////////////////////////////////////////////////////////////////////////////// - -// Convert four characters to a FourCC code. -// Needs to be a macro otherwise the OS X compiler complains when the kFormat* -// constants are used in a switch. -#ifdef __cplusplus -#define FOURCC(a, b, c, d) ( \ - (static_cast(a)) | (static_cast(b) << 8) | \ - (static_cast(c) << 16) | (static_cast(d) << 24)) -#else -#define FOURCC(a, b, c, d) ( \ - ((uint32)(a)) | ((uint32)(b) << 8) | /* NOLINT */ \ - ((uint32)(c) << 16) | ((uint32)(d) << 24)) /* NOLINT */ -#endif - -// Some pages discussing FourCC codes: -// http://www.fourcc.org/yuv.php -// http://v4l2spec.bytesex.org/spec/book1.htm -// http://developer.apple.com/quicktime/icefloe/dispatch020.html -// http://msdn.microsoft.com/library/windows/desktop/dd206750.aspx#nv12 -// http://people.xiph.org/~xiphmont/containers/nut/nut4cc.txt - -// FourCC codes grouped according to implementation efficiency. -// Primary formats should convert in 1 efficient step. -// Secondary formats are converted in 2 steps. -// Auxilliary formats call primary converters. -enum FourCC { - // 9 Primary YUV formats: 5 planar, 2 biplanar, 2 packed. - FOURCC_I420 = FOURCC('I', '4', '2', '0'), - FOURCC_I422 = FOURCC('I', '4', '2', '2'), - FOURCC_I444 = FOURCC('I', '4', '4', '4'), - FOURCC_I411 = FOURCC('I', '4', '1', '1'), - FOURCC_I400 = FOURCC('I', '4', '0', '0'), - FOURCC_NV21 = FOURCC('N', 'V', '2', '1'), - FOURCC_NV12 = FOURCC('N', 'V', '1', '2'), - FOURCC_YUY2 = FOURCC('Y', 'U', 'Y', '2'), - FOURCC_UYVY = FOURCC('U', 'Y', 'V', 'Y'), - - // 2 Secondary YUV formats: row biplanar. - FOURCC_M420 = FOURCC('M', '4', '2', '0'), - FOURCC_Q420 = FOURCC('Q', '4', '2', '0'), // deprecated. - - // 9 Primary RGB formats: 4 32 bpp, 2 24 bpp, 3 16 bpp. - FOURCC_ARGB = FOURCC('A', 'R', 'G', 'B'), - FOURCC_BGRA = FOURCC('B', 'G', 'R', 'A'), - FOURCC_ABGR = FOURCC('A', 'B', 'G', 'R'), - FOURCC_24BG = FOURCC('2', '4', 'B', 'G'), - FOURCC_RAW = FOURCC('r', 'a', 'w', ' '), - FOURCC_RGBA = FOURCC('R', 'G', 'B', 'A'), - FOURCC_RGBP = FOURCC('R', 'G', 'B', 'P'), // rgb565 LE. - FOURCC_RGBO = FOURCC('R', 'G', 'B', 'O'), // argb1555 LE. - FOURCC_R444 = FOURCC('R', '4', '4', '4'), // argb4444 LE. - - // 4 Secondary RGB formats: 4 Bayer Patterns. deprecated. - FOURCC_RGGB = FOURCC('R', 'G', 'G', 'B'), - FOURCC_BGGR = FOURCC('B', 'G', 'G', 'R'), - FOURCC_GRBG = FOURCC('G', 'R', 'B', 'G'), - FOURCC_GBRG = FOURCC('G', 'B', 'R', 'G'), - - // 1 Primary Compressed YUV format. - FOURCC_MJPG = FOURCC('M', 'J', 'P', 'G'), - - // 5 Auxiliary YUV variations: 3 with U and V planes are swapped, 1 Alias. - FOURCC_YV12 = FOURCC('Y', 'V', '1', '2'), - FOURCC_YV16 = FOURCC('Y', 'V', '1', '6'), - FOURCC_YV24 = FOURCC('Y', 'V', '2', '4'), - FOURCC_YU12 = FOURCC('Y', 'U', '1', '2'), // Linux version of I420. - FOURCC_J420 = FOURCC('J', '4', '2', '0'), - FOURCC_J400 = FOURCC('J', '4', '0', '0'), // unofficial fourcc - FOURCC_H420 = FOURCC('H', '4', '2', '0'), // unofficial fourcc - - // 14 Auxiliary aliases. CanonicalFourCC() maps these to canonical fourcc. - FOURCC_IYUV = FOURCC('I', 'Y', 'U', 'V'), // Alias for I420. - FOURCC_YU16 = FOURCC('Y', 'U', '1', '6'), // Alias for I422. - FOURCC_YU24 = FOURCC('Y', 'U', '2', '4'), // Alias for I444. - FOURCC_YUYV = FOURCC('Y', 'U', 'Y', 'V'), // Alias for YUY2. - FOURCC_YUVS = FOURCC('y', 'u', 'v', 's'), // Alias for YUY2 on Mac. - FOURCC_HDYC = FOURCC('H', 'D', 'Y', 'C'), // Alias for UYVY. - FOURCC_2VUY = FOURCC('2', 'v', 'u', 'y'), // Alias for UYVY on Mac. - FOURCC_JPEG = FOURCC('J', 'P', 'E', 'G'), // Alias for MJPG. - FOURCC_DMB1 = FOURCC('d', 'm', 'b', '1'), // Alias for MJPG on Mac. - FOURCC_BA81 = FOURCC('B', 'A', '8', '1'), // Alias for BGGR. - FOURCC_RGB3 = FOURCC('R', 'G', 'B', '3'), // Alias for RAW. - FOURCC_BGR3 = FOURCC('B', 'G', 'R', '3'), // Alias for 24BG. - FOURCC_CM32 = FOURCC(0, 0, 0, 32), // Alias for BGRA kCMPixelFormat_32ARGB - FOURCC_CM24 = FOURCC(0, 0, 0, 24), // Alias for RAW kCMPixelFormat_24RGB - FOURCC_L555 = FOURCC('L', '5', '5', '5'), // Alias for RGBO. - FOURCC_L565 = FOURCC('L', '5', '6', '5'), // Alias for RGBP. - FOURCC_5551 = FOURCC('5', '5', '5', '1'), // Alias for RGBO. - - // 1 Auxiliary compressed YUV format set aside for capturer. - FOURCC_H264 = FOURCC('H', '2', '6', '4'), - - // Match any fourcc. - FOURCC_ANY = -1, -}; - -enum FourCCBpp { - // Canonical fourcc codes used in our code. - FOURCC_BPP_I420 = 12, - FOURCC_BPP_I422 = 16, - FOURCC_BPP_I444 = 24, - FOURCC_BPP_I411 = 12, - FOURCC_BPP_I400 = 8, - FOURCC_BPP_NV21 = 12, - FOURCC_BPP_NV12 = 12, - FOURCC_BPP_YUY2 = 16, - FOURCC_BPP_UYVY = 16, - FOURCC_BPP_M420 = 12, - FOURCC_BPP_Q420 = 12, - FOURCC_BPP_ARGB = 32, - FOURCC_BPP_BGRA = 32, - FOURCC_BPP_ABGR = 32, - FOURCC_BPP_RGBA = 32, - FOURCC_BPP_24BG = 24, - FOURCC_BPP_RAW = 24, - FOURCC_BPP_RGBP = 16, - FOURCC_BPP_RGBO = 16, - FOURCC_BPP_R444 = 16, - FOURCC_BPP_RGGB = 8, - FOURCC_BPP_BGGR = 8, - FOURCC_BPP_GRBG = 8, - FOURCC_BPP_GBRG = 8, - FOURCC_BPP_YV12 = 12, - FOURCC_BPP_YV16 = 16, - FOURCC_BPP_YV24 = 24, - FOURCC_BPP_YU12 = 12, - FOURCC_BPP_J420 = 12, - FOURCC_BPP_J400 = 8, - FOURCC_BPP_H420 = 12, - FOURCC_BPP_MJPG = 0, // 0 means unknown. - FOURCC_BPP_H264 = 0, - FOURCC_BPP_IYUV = 12, - FOURCC_BPP_YU16 = 16, - FOURCC_BPP_YU24 = 24, - FOURCC_BPP_YUYV = 16, - FOURCC_BPP_YUVS = 16, - FOURCC_BPP_HDYC = 16, - FOURCC_BPP_2VUY = 16, - FOURCC_BPP_JPEG = 1, - FOURCC_BPP_DMB1 = 1, - FOURCC_BPP_BA81 = 8, - FOURCC_BPP_RGB3 = 24, - FOURCC_BPP_BGR3 = 24, - FOURCC_BPP_CM32 = 32, - FOURCC_BPP_CM24 = 24, - - // Match any fourcc. - FOURCC_BPP_ANY = 0, // 0 means unknown. -}; - -// Converts fourcc aliases into canonical ones. -LIBYUV_API uint32 CanonicalFourCC(uint32 fourcc); - -#ifdef __cplusplus -} // extern "C" -} // namespace libyuv -#endif - -#endif // INCLUDE_LIBYUV_VIDEO_COMMON_H_ diff --git a/third_party/libyuv/larch64/lib/libyuv.a b/third_party/libyuv/larch64/lib/libyuv.a deleted file mode 100644 index 9c4a32bcdbe..00000000000 --- a/third_party/libyuv/larch64/lib/libyuv.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adafce26582e425164df7af36253ce58e3ed1dba9965650745c93bd96e42e976 -size 462482 diff --git a/third_party/libyuv/x86_64/lib/libyuv.a b/third_party/libyuv/x86_64/lib/libyuv.a deleted file mode 100644 index 391b1c87698..00000000000 --- a/third_party/libyuv/x86_64/lib/libyuv.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:00f9759c67c6fa21657fabde9e096478ea5809716989599f673f638f039431e5 -size 504790 diff --git a/tools/replay/framereader.cc b/tools/replay/framereader.cc index 13d4497a58f..a643f0d3a78 100644 --- a/tools/replay/framereader.cc +++ b/tools/replay/framereader.cc @@ -6,7 +6,7 @@ #include #include "common/util.h" -#include "third_party/libyuv/include/libyuv.h" +#include "libyuv.h" #include "tools/replay/py_downloader.h" #include "tools/replay/util.h" #include "system/hardware/hw.h" diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 8132cd16dc1..0b785bf4a2e 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -113,12 +113,24 @@ function install_python_deps() { source .venv/bin/activate } +function install_macos_deps() { + if ! command -v brew > /dev/null 2>&1; then + echo "homebrew not found, skipping macOS system dependency install" + return 0 + fi + + if ! command -v cmake > /dev/null 2>&1; then + brew install cmake + fi +} + # --- Main --- if [[ "$OSTYPE" == "linux-gnu"* ]]; then install_ubuntu_deps echo "[ ] installed system dependencies t=$SECONDS" elif [[ "$OSTYPE" == "darwin"* ]]; then + install_macos_deps if [[ $SHELL == "/bin/zsh" ]]; then RC_FILE="$HOME/.zshrc" elif [[ $SHELL == "/bin/bash" ]]; then diff --git a/uv.lock b/uv.lock index 63c8c4cffa9..f5c128fcb76 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "casadi" @@ -381,7 +381,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "execnet" @@ -395,7 +395,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "fonttools" @@ -442,7 +442,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "ghp-import" @@ -459,7 +459,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "google-crc32c" @@ -577,7 +577,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "libusb1" @@ -590,6 +590,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/6d/344a164d32d65d503ffe9201cd74cf13a020099dc446554d1e50b07f167b/libusb1-3.3.1-py3-none-win_amd64.whl", hash = "sha256:6e21b772d80d6487fbb55d3d2141218536db302da82f1983754e96c72781c102", size = 141080, upload-time = "2025-03-24T05:36:46.594Z" }, ] +[[package]] +name = "libyuv" +version = "1922.0" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } + [[package]] name = "markdown" version = "3.10.2" @@ -740,7 +745,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "numpy" @@ -801,6 +806,7 @@ dependencies = [ { name = "json-rpc" }, { name = "libjpeg" }, { name = "libusb1" }, + { name = "libyuv" }, { name = "ncurses" }, { name = "numpy" }, { name = "openssl3" }, @@ -883,6 +889,7 @@ requires-dist = [ { name = "json-rpc" }, { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases" }, { name = "libusb1" }, + { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases" }, { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, { name = "mkdocs", marker = "extra == 'docs'" }, @@ -928,7 +935,7 @@ provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "openssl3" version = "3.4.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "packaging" @@ -1299,7 +1306,7 @@ wheels = [ [[package]] name = "python3-dev" version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "pyyaml" @@ -1667,7 +1674,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "zstandard" @@ -1697,4 +1704,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#11895d3f6bc62a229e84c87505948f15f9018ce0" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } From 037497939778c3fff8d721b60b7c85ac7f995683 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 13:52:39 -0800 Subject: [PATCH 436/685] use vendored raylib from dependencies repo (#37489) --- SConstruct | 3 +- pyproject.toml | 2 +- scripts/lint/check_raylib_includes.sh | 10 - selfdrive/ui/SConscript | 9 +- selfdrive/ui/installer/installer.cc | 2 +- third_party/raylib/.gitignore | 4 - third_party/raylib/Darwin/libraylib.a | 3 - third_party/raylib/build.sh | 93 - third_party/raylib/include/raygui.h | 5759 ------------------------ third_party/raylib/include/raylib.h | 1766 -------- third_party/raylib/include/raymath.h | 2949 ------------ third_party/raylib/include/rlgl.h | 5262 ---------------------- third_party/raylib/larch64/libraylib.a | 3 - third_party/raylib/x86_64/libraylib.a | 3 - uv.lock | 40 +- 15 files changed, 26 insertions(+), 15882 deletions(-) delete mode 100755 scripts/lint/check_raylib_includes.sh delete mode 100644 third_party/raylib/.gitignore delete mode 100644 third_party/raylib/Darwin/libraylib.a delete mode 100755 third_party/raylib/build.sh delete mode 100644 third_party/raylib/include/raygui.h delete mode 100644 third_party/raylib/include/raylib.h delete mode 100644 third_party/raylib/include/raymath.h delete mode 100644 third_party/raylib/include/rlgl.h delete mode 100644 third_party/raylib/larch64/libraylib.a delete mode 100644 third_party/raylib/x86_64/libraylib.a diff --git a/SConstruct b/SConstruct index da70bc39247..7003abbf05a 100644 --- a/SConstruct +++ b/SConstruct @@ -48,9 +48,10 @@ if arch != "larch64": import ncurses import openssl3 import python3_dev + import raylib import zeromq import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd] + pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, raylib, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs diff --git a/pyproject.toml b/pyproject.toml index 6516c8cd5bc..d0020b21e7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ dependencies = [ "zstandard", # ui - "raylib > 5.5.0.3", + "raylib @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=raylib", "qrcode", "jeepney", ] diff --git a/scripts/lint/check_raylib_includes.sh b/scripts/lint/check_raylib_includes.sh deleted file mode 100755 index e3be73a4897..00000000000 --- a/scripts/lint/check_raylib_includes.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash - -FAIL=0 - -if grep -n '#include "third_party/raylib/include/raylib\.h"' $@ | grep -v '^system/ui/raylib/raylib\.h'; then - echo -e "Bad raylib include found! Use '#include \"system/ui/raylib/raylib.h\"' instead\n" - FAIL=1 -fi - -exit $FAIL diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 4d7448c62f8..bebfd0011fc 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -16,12 +16,17 @@ env.Command( action=f"python3 {generator}", ) +try: + import raylib +except ImportError: + raylib = None -if GetOption('extras') and arch == "larch64": +if GetOption('extras') and arch == "larch64" and raylib is not None: # build installers if arch != "Darwin": raylib_env = env.Clone() - raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] + raylib_env['CPPPATH'] += [raylib.INCLUDE_DIR] + raylib_env['LIBPATH'] += [raylib.LIB_DIR] raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') raylib_libs = common + ["raylib"] diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 75994541946..338bcad34e4 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -6,7 +6,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "system/hardware/hw.h" -#include "third_party/raylib/include/raylib.h" +#include "raylib.h" int freshClone(); int cachedFetch(const std::string &cache); diff --git a/third_party/raylib/.gitignore b/third_party/raylib/.gitignore deleted file mode 100644 index 6b1d3ad7482..00000000000 --- a/third_party/raylib/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/raylib_repo/ -/raylib_python_repo/ -/wheel/ -!*.a diff --git a/third_party/raylib/Darwin/libraylib.a b/third_party/raylib/Darwin/libraylib.a deleted file mode 100644 index dd2e9b33f12..00000000000 --- a/third_party/raylib/Darwin/libraylib.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:fd045c1d4bca5c9b2ad044ea730826ff6cedeef0b64451b123717b136f1cd702 -size 6392532 diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh deleted file mode 100755 index d20f9d33af1..00000000000 --- a/third_party/raylib/build.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env bash -set -e - -export SOURCE_DATE_EPOCH=0 -export ZERO_AR_DATE=1 - -SUDO="" - -# Use sudo if not root -if [[ ! $(id -u) -eq 0 ]]; then - if [[ -z $(which sudo) ]]; then - echo "Please install sudo or run as root" - exit 1 - fi - SUDO="sudo" -fi - -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" -cd $DIR - -RAYLIB_PLATFORM="PLATFORM_DESKTOP" - -ARCHNAME=$(uname -m) -if [ -f /TICI ]; then - ARCHNAME="larch64" - RAYLIB_PLATFORM="PLATFORM_COMMA" -elif [[ "$OSTYPE" == "linux"* ]]; then - # required dependencies on Linux PC - $SUDO apt install \ - libxcursor-dev \ - libxi-dev \ - libxinerama-dev \ - libxrandr-dev -fi - -if [[ "$OSTYPE" == "darwin"* ]]; then - ARCHNAME="Darwin" -fi - -INSTALL_DIR="$DIR/$ARCHNAME" -rm -rf $INSTALL_DIR -mkdir -p $INSTALL_DIR - -INSTALL_H_DIR="$DIR/include" -rm -rf $INSTALL_H_DIR -mkdir -p $INSTALL_H_DIR - -if [ ! -d raylib_repo ]; then - git clone -b master --no-tags https://github.com/commaai/raylib.git raylib_repo -fi - -cd raylib_repo - -COMMIT=${1:-3425bd9d1fb292ede4d80f97a1f4f258f614cffc} -git fetch origin $COMMIT -git reset --hard $COMMIT -git clean -xdff . - -cd src - -make -j$(nproc) PLATFORM=$RAYLIB_PLATFORM RAYLIB_RELEASE_PATH=$INSTALL_DIR -cp raylib.h raymath.h rlgl.h $INSTALL_H_DIR/ -echo "raylib development files installed/updated in $INSTALL_H_DIR" - -# this commit needs to be in line with raylib -set -x -RAYGUI_COMMIT="76b36b597edb70ffaf96f046076adc20d67e7827" -curl -fsSLo $INSTALL_H_DIR/raygui.h https://raw.githubusercontent.com/raysan5/raygui/$RAYGUI_COMMIT/src/raygui.h - -if [ -f /TICI ]; then - - # Building the python bindings - cd $DIR - - if [ ! -d raylib_python_repo ]; then - git clone -b master --no-tags https://github.com/commaai/raylib-python-cffi.git raylib_python_repo - fi - - cd raylib_python_repo - - BINDINGS_COMMIT="a0710d95af3c12fd7f4b639589be9a13dad93cb6" - git fetch origin $BINDINGS_COMMIT - git reset --hard $BINDINGS_COMMIT - git clean -xdff . - - RAYLIB_PLATFORM=$RAYLIB_PLATFORM RAYLIB_INCLUDE_PATH=$INSTALL_H_DIR RAYLIB_LIB_PATH=$INSTALL_DIR python setup.py bdist_wheel - cd $DIR - - rm -rf wheel - mkdir wheel - cp raylib_python_repo/dist/*.whl wheel/ - -fi diff --git a/third_party/raylib/include/raygui.h b/third_party/raylib/include/raygui.h deleted file mode 100644 index fe233a16cf6..00000000000 --- a/third_party/raylib/include/raygui.h +++ /dev/null @@ -1,5759 +0,0 @@ -/******************************************************************************************* -* -* raygui v4.5-dev - A simple and easy-to-use immediate-mode gui library -* -* DESCRIPTION: -* raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also -* available as a standalone library, as long as input and drawing functions are provided. -* -* FEATURES: -* - Immediate-mode gui, minimal retained data -* - +25 controls provided (basic and advanced) -* - Styling system for colors, font and metrics -* - Icons supported, embedded as a 1-bit icons pack -* - Standalone mode option (custom input/graphics backend) -* - Multiple support tools provided for raygui development -* -* POSSIBLE IMPROVEMENTS: -* - Better standalone mode API for easy plug of custom backends -* - Externalize required inputs, allow user easier customization -* -* LIMITATIONS: -* - No editable multi-line word-wraped text box supported -* - No auto-layout mechanism, up to the user to define controls position and size -* - Standalone mode requires library modification and some user work to plug another backend -* -* NOTES: -* - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for -* font atlas recs and glyphs, freeing that memory is (usually) up to the user, -* no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads -* by default any previously loaded font (texture, recs, glyphs). -* - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions -* -* CONTROLS PROVIDED: -* # Container/separators Controls -* - WindowBox --> StatusBar, Panel -* - GroupBox --> Line -* - Line -* - Panel --> StatusBar -* - ScrollPanel --> StatusBar -* - TabBar --> Button -* -* # Basic Controls -* - Label -* - LabelButton --> Label -* - Button -* - Toggle -* - ToggleGroup --> Toggle -* - ToggleSlider -* - CheckBox -* - ComboBox -* - DropdownBox -* - TextBox -* - ValueBox --> TextBox -* - Spinner --> Button, ValueBox -* - Slider -* - SliderBar --> Slider -* - ProgressBar -* - StatusBar -* - DummyRec -* - Grid -* -* # Advance Controls -* - ListView -* - ColorPicker --> ColorPanel, ColorBarHue -* - MessageBox --> Window, Label, Button -* - TextInputBox --> Window, Label, TextBox, Button -* -* It also provides a set of functions for styling the controls based on its properties (size, color). -* -* -* RAYGUI STYLE (guiStyle): -* raygui uses a global data array for all gui style properties (allocated on data segment by default), -* when a new style is loaded, it is loaded over the global style... but a default gui style could always be -* recovered with GuiLoadStyleDefault() function, that overwrites the current style to the default one -* -* The global style array size is fixed and depends on the number of controls and properties: -* -* static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; -* -* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB -* -* Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style -* used for all controls, when any of those base values is set, it is automatically populated to all -* controls, so, specific control values overwriting generic style should be set after base values. -* -* After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those -* properties are actually common to all controls and can not be overwritten individually (like BASE ones) -* Some of those properties are: TEXT_SIZE, TEXT_SPACING, LINE_COLOR, BACKGROUND_COLOR -* -* Custom control properties can be defined using the EXTENDED properties for each independent control. -* -* TOOL: rGuiStyler is a visual tool to customize raygui style: github.com/raysan5/rguistyler -* -* -* RAYGUI ICONS (guiIcons): -* raygui could use a global array containing icons data (allocated on data segment by default), -* a custom icons set could be loaded over this array using GuiLoadIcons(), but loaded icons set -* must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS will be loaded -* -* Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon -* requires 8 integers (16*16/32) to be stored in memory. -* -* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set. -* -* The global icons array size is fixed and depends on the number of icons and size: -* -* static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS]; -* -* guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB -* -* TOOL: rGuiIcons is a visual tool to customize/create raygui icons: github.com/raysan5/rguiicons -* -* RAYGUI LAYOUT: -* raygui currently does not provide an auto-layout mechanism like other libraries, -* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it. -* -* TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout -* -* CONFIGURATION: -* #define RAYGUI_IMPLEMENTATION -* Generates the implementation of the library into the included file. -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. -* -* #define RAYGUI_STANDALONE -* Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined -* internally in the library and input management and drawing functions must be provided by -* the user (check library implementation for further details). -* -* #define RAYGUI_NO_ICONS -* Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) -* -* #define RAYGUI_CUSTOM_ICONS -* Includes custom ricons.h header defining a set of custom icons, -* this file can be generated using rGuiIcons tool -* -* #define RAYGUI_DEBUG_RECS_BOUNDS -* Draw control bounds rectangles for debug -* -* #define RAYGUI_DEBUG_TEXT_BOUNDS -* Draw text bounds rectangles for debug -* -* VERSIONS HISTORY: -* 4.5-dev (Sep-2024) Current dev version... -* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes -* ADDED: GuiValueBoxFloat() -* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP -* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH -* ADDED: Multiple new icons -* REVIEWED: GuiTabBar(), close tab with mouse middle button -* REVIEWED: GuiScrollPanel(), scroll speed proportional to content -* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow -* REVIEWED: GuiTextBox(), cursor position initialization -* REVIEWED: GuiSliderPro(), control value change check -* REVIEWED: GuiGrid(), simplified implementation -* REVIEWED: GuiIconText(), increase buffer size and reviewed padding -* REVIEWED: GuiDrawText(), improved wrap mode drawing -* REVIEWED: GuiScrollBar(), minor tweaks -* REVIEWED: Functions descriptions, removed wrong return value reference -* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion -* -* 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() -* ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() -* ADDED: Multiple new icons, mostly compiler related -* ADDED: New DEFAULT properties: TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE -* ADDED: New enum values: GuiTextAlignment, GuiTextAlignmentVertical, GuiTextWrapMode -* ADDED: Support loading styles with custom font charset from external file -* REDESIGNED: GuiTextBox(), support mouse cursor positioning -* REDESIGNED: GuiDrawText(), support multiline and word-wrap modes (read only) -* REDESIGNED: GuiProgressBar() to be more visual, progress affects border color -* REDESIGNED: Global alpha consideration moved to GuiDrawRectangle() and GuiDrawText() -* REDESIGNED: GuiScrollPanel(), get parameters by reference and return result value -* REDESIGNED: GuiToggleGroup(), get parameters by reference and return result value -* REDESIGNED: GuiComboBox(), get parameters by reference and return result value -* REDESIGNED: GuiCheckBox(), get parameters by reference and return result value -* REDESIGNED: GuiSlider(), get parameters by reference and return result value -* REDESIGNED: GuiSliderBar(), get parameters by reference and return result value -* REDESIGNED: GuiProgressBar(), get parameters by reference and return result value -* REDESIGNED: GuiListView(), get parameters by reference and return result value -* REDESIGNED: GuiColorPicker(), get parameters by reference and return result value -* REDESIGNED: GuiColorPanel(), get parameters by reference and return result value -* REDESIGNED: GuiColorBarAlpha(), get parameters by reference and return result value -* REDESIGNED: GuiColorBarHue(), get parameters by reference and return result value -* REDESIGNED: GuiGrid(), get parameters by reference and return result value -* REDESIGNED: GuiGrid(), added extra parameter -* REDESIGNED: GuiListViewEx(), change parameters order -* REDESIGNED: All controls return result as int value -* REVIEWED: GuiScrollPanel() to avoid smallish scroll-bars -* REVIEWED: All examples and specially controls_test_suite -* RENAMED: gui_file_dialog module to gui_window_file_dialog -* UPDATED: All styles to include ISO-8859-15 charset (as much as possible) -* -* 3.6 (10-May-2023) ADDED: New icon: SAND_TIMER -* ADDED: GuiLoadStyleFromMemory() (binary only) -* REVIEWED: GuiScrollBar() horizontal movement key -* REVIEWED: GuiTextBox() crash on cursor movement -* REVIEWED: GuiTextBox(), additional inputs support -* REVIEWED: GuiLabelButton(), avoid text cut -* REVIEWED: GuiTextInputBox(), password input -* REVIEWED: Local GetCodepointNext(), aligned with raylib -* REDESIGNED: GuiSlider*()/GuiScrollBar() to support out-of-bounds -* -* 3.5 (20-Apr-2023) ADDED: GuiTabBar(), based on GuiToggle() -* ADDED: Helper functions to split text in separate lines -* ADDED: Multiple new icons, useful for code editing tools -* REMOVED: Unneeded icon editing functions -* REMOVED: GuiTextBoxMulti(), very limited and broken -* REMOVED: MeasureTextEx() dependency, logic directly implemented -* REMOVED: DrawTextEx() dependency, logic directly implemented -* REVIEWED: GuiScrollBar(), improve mouse-click behaviour -* REVIEWED: Library header info, more info, better organized -* REDESIGNED: GuiTextBox() to support cursor movement -* REDESIGNED: GuiDrawText() to divide drawing by lines -* -* 3.2 (22-May-2022) RENAMED: Some enum values, for unification, avoiding prefixes -* REMOVED: GuiScrollBar(), only internal -* REDESIGNED: GuiPanel() to support text parameter -* REDESIGNED: GuiScrollPanel() to support text parameter -* REDESIGNED: GuiColorPicker() to support text parameter -* REDESIGNED: GuiColorPanel() to support text parameter -* REDESIGNED: GuiColorBarAlpha() to support text parameter -* REDESIGNED: GuiColorBarHue() to support text parameter -* REDESIGNED: GuiTextInputBox() to support password -* -* 3.1 (12-Jan-2022) REVIEWED: Default style for consistency (aligned with rGuiLayout v2.5 tool) -* REVIEWED: GuiLoadStyle() to support compressed font atlas image data and unload previous textures -* REVIEWED: External icons usage logic -* REVIEWED: GuiLine() for centered alignment when including text -* RENAMED: Multiple controls properties definitions to prepend RAYGUI_ -* RENAMED: RICON_ references to RAYGUI_ICON_ for library consistency -* Projects updated and multiple tweaks -* -* 3.0 (04-Nov-2021) Integrated ricons data to avoid external file -* REDESIGNED: GuiTextBoxMulti() -* REMOVED: GuiImageButton*() -* Multiple minor tweaks and bugs corrected -* -* 2.9 (17-Mar-2021) REMOVED: Tooltip API -* 2.8 (03-May-2020) Centralized rectangles drawing to GuiDrawRectangle() -* 2.7 (20-Feb-2020) ADDED: Possible tooltips API -* 2.6 (09-Sep-2019) ADDED: GuiTextInputBox() -* REDESIGNED: GuiListView*(), GuiDropdownBox(), GuiSlider*(), GuiProgressBar(), GuiMessageBox() -* REVIEWED: GuiTextBox(), GuiSpinner(), GuiValueBox(), GuiLoadStyle() -* Replaced property INNER_PADDING by TEXT_PADDING, renamed some properties -* ADDED: 8 new custom styles ready to use -* Multiple minor tweaks and bugs corrected -* -* 2.5 (28-May-2019) Implemented extended GuiTextBox(), GuiValueBox(), GuiSpinner() -* 2.3 (29-Apr-2019) ADDED: rIcons auxiliar library and support for it, multiple controls reviewed -* Refactor all controls drawing mechanism to use control state -* 2.2 (05-Feb-2019) ADDED: GuiScrollBar(), GuiScrollPanel(), reviewed GuiListView(), removed Gui*Ex() controls -* 2.1 (26-Dec-2018) REDESIGNED: GuiCheckBox(), GuiComboBox(), GuiDropdownBox(), GuiToggleGroup() > Use combined text string -* REDESIGNED: Style system (breaking change) -* 2.0 (08-Nov-2018) ADDED: Support controls guiLock and custom fonts -* REVIEWED: GuiComboBox(), GuiListView()... -* 1.9 (09-Oct-2018) REVIEWED: GuiGrid(), GuiTextBox(), GuiTextBoxMulti(), GuiValueBox()... -* 1.8 (01-May-2018) Lot of rework and redesign to align with rGuiStyler and rGuiLayout -* 1.5 (21-Jun-2017) Working in an improved styles system -* 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) -* 1.3 (12-Jun-2017) Complete redesign of style system -* 1.1 (01-Jun-2017) Complete review of the library -* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria. -* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria. -* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. -* -* DEPENDENCIES: -* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing -* -* STANDALONE MODE: -* By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled -* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs. -* -* The following functions should be redefined for a custom backend: -* -* - Vector2 GetMousePosition(void); -* - float GetMouseWheelMove(void); -* - bool IsMouseButtonDown(int button); -* - bool IsMouseButtonPressed(int button); -* - bool IsMouseButtonReleased(int button); -* - bool IsKeyDown(int key); -* - bool IsKeyPressed(int key); -* - int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() -* -* - void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() -* - void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() -* -* - Font GetFontDefault(void); // -- GuiLoadStyleDefault() -* - Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle() -* - Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image -* - void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) -* - char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data -* - void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data -* - const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs -* - int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list -* - void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list -* - unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() -* -* CONTRIBUTORS: -* Ramon Santamaria: Supervision, review, redesign, update and maintenance -* Vlad Adrian: Complete rewrite of GuiTextBox() to support extended features (2019) -* Sergio Martinez: Review, testing (2015) and redesign of multiple controls (2018) -* Adria Arranz: Testing and implementation of additional controls (2018) -* Jordi Jorba: Testing and implementation of additional controls (2018) -* Albert Martos: Review and testing of the library (2015) -* Ian Eito: Review and testing of the library (2015) -* Kevin Gato: Initial implementation of basic components (2014) -* Daniel Nicolas: Initial implementation of basic components (2014) -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef RAYGUI_H -#define RAYGUI_H - -#define RAYGUI_VERSION_MAJOR 4 -#define RAYGUI_VERSION_MINOR 5 -#define RAYGUI_VERSION_PATCH 0 -#define RAYGUI_VERSION "4.5-dev" - -#if !defined(RAYGUI_STANDALONE) - #include "raylib.h" -#endif - -// Function specifiers in case library is build/used as a shared library (Windows) -// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll -#if defined(_WIN32) - #if defined(BUILD_LIBTYPE_SHARED) - #define RAYGUIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) - #elif defined(USE_LIBTYPE_SHARED) - #define RAYGUIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) - #endif -#endif - -// Function specifiers definition -#ifndef RAYGUIAPI - #define RAYGUIAPI // Functions defined as 'extern' by default (implicit specifiers) -#endif - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -// Allow custom memory allocators -#ifndef RAYGUI_MALLOC - #define RAYGUI_MALLOC(sz) malloc(sz) -#endif -#ifndef RAYGUI_CALLOC - #define RAYGUI_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RAYGUI_FREE - #define RAYGUI_FREE(p) free(p) -#endif - -// Simple log system to avoid printf() calls if required -// NOTE: Avoiding those calls, also avoids const strings memory usage -#define RAYGUI_SUPPORT_LOG_INFO -#if defined(RAYGUI_SUPPORT_LOG_INFO) - #define RAYGUI_LOG(...) printf(__VA_ARGS__) -#else - #define RAYGUI_LOG(...) -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -// NOTE: Some types are required for RAYGUI_STANDALONE usage -//---------------------------------------------------------------------------------- -#if defined(RAYGUI_STANDALONE) - #ifndef __cplusplus - // Boolean type - #ifndef true - typedef enum { false, true } bool; - #endif - #endif - - // Vector2 type - typedef struct Vector2 { - float x; - float y; - } Vector2; - - // Vector3 type // -- ConvertHSVtoRGB(), ConvertRGBtoHSV() - typedef struct Vector3 { - float x; - float y; - float z; - } Vector3; - - // Color type, RGBA (32bit) - typedef struct Color { - unsigned char r; - unsigned char g; - unsigned char b; - unsigned char a; - } Color; - - // Rectangle type - typedef struct Rectangle { - float x; - float y; - float width; - float height; - } Rectangle; - - // TODO: Texture2D type is very coupled to raylib, required by Font type - // It should be redesigned to be provided by user - typedef struct Texture2D { - unsigned int id; // OpenGL texture id - int width; // Texture base width - int height; // Texture base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) - } Texture2D; - - // Image, pixel data stored in CPU memory (RAM) - typedef struct Image { - void *data; // Image raw data - int width; // Image base width - int height; // Image base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) - } Image; - - // GlyphInfo, font characters glyphs info - typedef struct GlyphInfo { - int value; // Character value (Unicode) - int offsetX; // Character offset X when drawing - int offsetY; // Character offset Y when drawing - int advanceX; // Character advance position X - Image image; // Character image data - } GlyphInfo; - - // TODO: Font type is very coupled to raylib, mostly required by GuiLoadStyle() - // It should be redesigned to be provided by user - typedef struct Font { - int baseSize; // Base size (default chars height) - int glyphCount; // Number of glyph characters - int glyphPadding; // Padding around the glyph characters - Texture2D texture; // Texture atlas containing the glyphs - Rectangle *recs; // Rectangles in texture for the glyphs - GlyphInfo *glyphs; // Glyphs info data - } Font; -#endif - -// Style property -// NOTE: Used when exporting style as code for convenience -typedef struct GuiStyleProp { - unsigned short controlId; // Control identifier - unsigned short propertyId; // Property identifier - int propertyValue; // Property value -} GuiStyleProp; - -/* -// Controls text style -NOT USED- -// NOTE: Text style is defined by control -typedef struct GuiTextStyle { - unsigned int size; - int charSpacing; - int lineSpacing; - int alignmentH; - int alignmentV; - int padding; -} GuiTextStyle; -*/ - -// Gui control state -typedef enum { - STATE_NORMAL = 0, - STATE_FOCUSED, - STATE_PRESSED, - STATE_DISABLED -} GuiState; - -// Gui control text alignment -typedef enum { - TEXT_ALIGN_LEFT = 0, - TEXT_ALIGN_CENTER, - TEXT_ALIGN_RIGHT -} GuiTextAlignment; - -// Gui control text alignment vertical -// NOTE: Text vertical position inside the text bounds -typedef enum { - TEXT_ALIGN_TOP = 0, - TEXT_ALIGN_MIDDLE, - TEXT_ALIGN_BOTTOM -} GuiTextAlignmentVertical; - -// Gui control text wrap mode -// NOTE: Useful for multiline text -typedef enum { - TEXT_WRAP_NONE = 0, - TEXT_WRAP_CHAR, - TEXT_WRAP_WORD -} GuiTextWrapMode; - -// Gui controls -typedef enum { - // Default -> populates to all controls when set - DEFAULT = 0, - - // Basic controls - LABEL, // Used also for: LABELBUTTON - BUTTON, - TOGGLE, // Used also for: TOGGLEGROUP - SLIDER, // Used also for: SLIDERBAR, TOGGLESLIDER - PROGRESSBAR, - CHECKBOX, - COMBOBOX, - DROPDOWNBOX, - TEXTBOX, // Used also for: TEXTBOXMULTI - VALUEBOX, - SPINNER, // Uses: BUTTON, VALUEBOX - LISTVIEW, - COLORPICKER, - SCROLLBAR, - STATUSBAR -} GuiControl; - -// Gui base properties for every control -// NOTE: RAYGUI_MAX_PROPS_BASE properties (by default 16 properties) -typedef enum { - BORDER_COLOR_NORMAL = 0, // Control border color in STATE_NORMAL - BASE_COLOR_NORMAL, // Control base color in STATE_NORMAL - TEXT_COLOR_NORMAL, // Control text color in STATE_NORMAL - BORDER_COLOR_FOCUSED, // Control border color in STATE_FOCUSED - BASE_COLOR_FOCUSED, // Control base color in STATE_FOCUSED - TEXT_COLOR_FOCUSED, // Control text color in STATE_FOCUSED - BORDER_COLOR_PRESSED, // Control border color in STATE_PRESSED - BASE_COLOR_PRESSED, // Control base color in STATE_PRESSED - TEXT_COLOR_PRESSED, // Control text color in STATE_PRESSED - BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED - BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED - TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED - BORDER_WIDTH, // Control border size, 0 for no border - //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls - //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls - //TEXT_LINE_SPACING // Control text spacing between lines -> GLOBAL for all controls - TEXT_PADDING, // Control text padding, not considering border - TEXT_ALIGNMENT, // Control text horizontal alignment inside control text bound (after border and padding) - //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls -} GuiControlProperty; - -// TODO: Which text styling properties should be global or per-control? -// At this moment TEXT_PADDING and TEXT_ALIGNMENT is configured and saved per control while -// TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and -// should be configured by user as needed while defining the UI layout - -// Gui extended properties depend on control -// NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) -//---------------------------------------------------------------------------------- -// DEFAULT extended properties -// NOTE: Those properties are common to all controls or global -// WARNING: We only have 8 slots for those properties by default!!! -> New global control: TEXT? -typedef enum { - TEXT_SIZE = 16, // Text size (glyphs max height) - TEXT_SPACING, // Text spacing between glyphs - LINE_COLOR, // Line control color - BACKGROUND_COLOR, // Background color - TEXT_LINE_SPACING, // Text spacing between lines - TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) - TEXT_WRAP_MODE // Text wrap-mode inside text bounds - //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline - //TEXT_DECORATION_THICK // Text decoration line thickness -} GuiDefaultProperty; - -// Other possible text properties: -// TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change -// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... - -// Label -//typedef enum { } GuiLabelProperty; - -// Button/Spinner -//typedef enum { } GuiButtonProperty; - -// Toggle/ToggleGroup -typedef enum { - GROUP_PADDING = 16, // ToggleGroup separation between toggles -} GuiToggleProperty; - -// Slider/SliderBar -typedef enum { - SLIDER_WIDTH = 16, // Slider size of internal bar - SLIDER_PADDING // Slider/SliderBar internal bar padding -} GuiSliderProperty; - -// ProgressBar -typedef enum { - PROGRESS_PADDING = 16, // ProgressBar internal padding -} GuiProgressBarProperty; - -// ScrollBar -typedef enum { - ARROWS_SIZE = 16, // ScrollBar arrows size - ARROWS_VISIBLE, // ScrollBar arrows visible - SCROLL_SLIDER_PADDING, // ScrollBar slider internal padding - SCROLL_SLIDER_SIZE, // ScrollBar slider size - SCROLL_PADDING, // ScrollBar scroll padding from arrows - SCROLL_SPEED, // ScrollBar scrolling speed -} GuiScrollBarProperty; - -// CheckBox -typedef enum { - CHECK_PADDING = 16 // CheckBox internal check padding -} GuiCheckBoxProperty; - -// ComboBox -typedef enum { - COMBO_BUTTON_WIDTH = 16, // ComboBox right button width - COMBO_BUTTON_SPACING // ComboBox button separation -} GuiComboBoxProperty; - -// DropdownBox -typedef enum { - ARROW_PADDING = 16, // DropdownBox arrow separation from border and items - DROPDOWN_ITEMS_SPACING, // DropdownBox items separation - DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden - DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) -} GuiDropdownBoxProperty; - -// TextBox/TextBoxMulti/ValueBox/Spinner -typedef enum { - TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable -} GuiTextBoxProperty; - -// Spinner -typedef enum { - SPIN_BUTTON_WIDTH = 16, // Spinner left/right buttons width - SPIN_BUTTON_SPACING, // Spinner buttons separation -} GuiSpinnerProperty; - -// ListView -typedef enum { - LIST_ITEMS_HEIGHT = 16, // ListView items height - LIST_ITEMS_SPACING, // ListView items separation - SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) - SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) - LIST_ITEMS_BORDER_WIDTH // ListView items border width -} GuiListViewProperty; - -// ColorPicker -typedef enum { - COLOR_SELECTOR_SIZE = 16, - HUEBAR_WIDTH, // ColorPicker right hue bar width - HUEBAR_PADDING, // ColorPicker right hue bar separation from panel - HUEBAR_SELECTOR_HEIGHT, // ColorPicker right hue bar selector height - HUEBAR_SELECTOR_OVERFLOW // ColorPicker right hue bar selector overflow -} GuiColorPickerProperty; - -#define SCROLLBAR_LEFT_SIDE 0 -#define SCROLLBAR_RIGHT_SIDE 1 - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -// ... - -//---------------------------------------------------------------------------------- -// Module Functions Declaration -//---------------------------------------------------------------------------------- - -#if defined(__cplusplus) -extern "C" { // Prevents name mangling of functions -#endif - -// Global gui state control functions -RAYGUIAPI void GuiEnable(void); // Enable gui controls (global state) -RAYGUIAPI void GuiDisable(void); // Disable gui controls (global state) -RAYGUIAPI void GuiLock(void); // Lock gui controls (global state) -RAYGUIAPI void GuiUnlock(void); // Unlock gui controls (global state) -RAYGUIAPI bool GuiIsLocked(void); // Check if gui is locked (global state) -RAYGUIAPI void GuiSetAlpha(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f -RAYGUIAPI void GuiSetState(int state); // Set gui state (global state) -RAYGUIAPI int GuiGetState(void); // Get gui state (global state) - -// Font set/get functions -RAYGUIAPI void GuiSetFont(Font font); // Set gui custom font (global state) -RAYGUIAPI Font GuiGetFont(void); // Get gui custom font (global state) - -// Style set/get functions -RAYGUIAPI void GuiSetStyle(int control, int property, int value); // Set one style property -RAYGUIAPI int GuiGetStyle(int control, int property); // Get one style property - -// Styles loading functions -RAYGUIAPI void GuiLoadStyle(const char *fileName); // Load style file over global style variable (.rgs) -RAYGUIAPI void GuiLoadStyleDefault(void); // Load style default over global style - -// Tooltips management functions -RAYGUIAPI void GuiEnableTooltip(void); // Enable gui tooltips (global state) -RAYGUIAPI void GuiDisableTooltip(void); // Disable gui tooltips (global state) -RAYGUIAPI void GuiSetTooltip(const char *tooltip); // Set tooltip string - -// Icons functionality -RAYGUIAPI const char *GuiIconText(int iconId, const char *text); // Get text with icon id prepended (if supported) -#if !defined(RAYGUI_NO_ICONS) -RAYGUIAPI void GuiSetIconScale(int scale); // Set default icon drawing size -RAYGUIAPI unsigned int *GuiGetIcons(void); // Get raygui icons data pointer -RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load raygui icons file (.rgi) into internal icons data -RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position -#endif - -// Controls -//---------------------------------------------------------------------------------------------------------- -// Container/separator controls, useful for controls organization -RAYGUIAPI int GuiWindowBox(Rectangle bounds, const char *title); // Window Box control, shows a window that can be closed -RAYGUIAPI int GuiGroupBox(Rectangle bounds, const char *text); // Group Box control with text name -RAYGUIAPI int GuiLine(Rectangle bounds, const char *text); // Line separator control, could contain text -RAYGUIAPI int GuiPanel(Rectangle bounds, const char *text); // Panel control, useful to group controls -RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *active); // Tab Bar control, returns TAB to be closed or -1 -RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control - -// Basic controls set -RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control -RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked -RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked -RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control -RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control -RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control -RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active -RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control - -RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control -RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control -RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers -RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values -RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text - -RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control -RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control -RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control -RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text -RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders -RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control - -// Advance controls set -RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control -RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters -RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message -RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret -RAYGUIAPI int GuiColorPicker(Rectangle bounds, const char *text, Color *color); // Color Picker control (multiple color controls) -RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); // Color Panel control -RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control -RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control -RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) -RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() -//---------------------------------------------------------------------------------------------------------- - -#if !defined(RAYGUI_NO_ICONS) - -#if !defined(RAYGUI_CUSTOM_ICONS) -//---------------------------------------------------------------------------------- -// Icons enumeration -//---------------------------------------------------------------------------------- -typedef enum { - ICON_NONE = 0, - ICON_FOLDER_FILE_OPEN = 1, - ICON_FILE_SAVE_CLASSIC = 2, - ICON_FOLDER_OPEN = 3, - ICON_FOLDER_SAVE = 4, - ICON_FILE_OPEN = 5, - ICON_FILE_SAVE = 6, - ICON_FILE_EXPORT = 7, - ICON_FILE_ADD = 8, - ICON_FILE_DELETE = 9, - ICON_FILETYPE_TEXT = 10, - ICON_FILETYPE_AUDIO = 11, - ICON_FILETYPE_IMAGE = 12, - ICON_FILETYPE_PLAY = 13, - ICON_FILETYPE_VIDEO = 14, - ICON_FILETYPE_INFO = 15, - ICON_FILE_COPY = 16, - ICON_FILE_CUT = 17, - ICON_FILE_PASTE = 18, - ICON_CURSOR_HAND = 19, - ICON_CURSOR_POINTER = 20, - ICON_CURSOR_CLASSIC = 21, - ICON_PENCIL = 22, - ICON_PENCIL_BIG = 23, - ICON_BRUSH_CLASSIC = 24, - ICON_BRUSH_PAINTER = 25, - ICON_WATER_DROP = 26, - ICON_COLOR_PICKER = 27, - ICON_RUBBER = 28, - ICON_COLOR_BUCKET = 29, - ICON_TEXT_T = 30, - ICON_TEXT_A = 31, - ICON_SCALE = 32, - ICON_RESIZE = 33, - ICON_FILTER_POINT = 34, - ICON_FILTER_BILINEAR = 35, - ICON_CROP = 36, - ICON_CROP_ALPHA = 37, - ICON_SQUARE_TOGGLE = 38, - ICON_SYMMETRY = 39, - ICON_SYMMETRY_HORIZONTAL = 40, - ICON_SYMMETRY_VERTICAL = 41, - ICON_LENS = 42, - ICON_LENS_BIG = 43, - ICON_EYE_ON = 44, - ICON_EYE_OFF = 45, - ICON_FILTER_TOP = 46, - ICON_FILTER = 47, - ICON_TARGET_POINT = 48, - ICON_TARGET_SMALL = 49, - ICON_TARGET_BIG = 50, - ICON_TARGET_MOVE = 51, - ICON_CURSOR_MOVE = 52, - ICON_CURSOR_SCALE = 53, - ICON_CURSOR_SCALE_RIGHT = 54, - ICON_CURSOR_SCALE_LEFT = 55, - ICON_UNDO = 56, - ICON_REDO = 57, - ICON_REREDO = 58, - ICON_MUTATE = 59, - ICON_ROTATE = 60, - ICON_REPEAT = 61, - ICON_SHUFFLE = 62, - ICON_EMPTYBOX = 63, - ICON_TARGET = 64, - ICON_TARGET_SMALL_FILL = 65, - ICON_TARGET_BIG_FILL = 66, - ICON_TARGET_MOVE_FILL = 67, - ICON_CURSOR_MOVE_FILL = 68, - ICON_CURSOR_SCALE_FILL = 69, - ICON_CURSOR_SCALE_RIGHT_FILL = 70, - ICON_CURSOR_SCALE_LEFT_FILL = 71, - ICON_UNDO_FILL = 72, - ICON_REDO_FILL = 73, - ICON_REREDO_FILL = 74, - ICON_MUTATE_FILL = 75, - ICON_ROTATE_FILL = 76, - ICON_REPEAT_FILL = 77, - ICON_SHUFFLE_FILL = 78, - ICON_EMPTYBOX_SMALL = 79, - ICON_BOX = 80, - ICON_BOX_TOP = 81, - ICON_BOX_TOP_RIGHT = 82, - ICON_BOX_RIGHT = 83, - ICON_BOX_BOTTOM_RIGHT = 84, - ICON_BOX_BOTTOM = 85, - ICON_BOX_BOTTOM_LEFT = 86, - ICON_BOX_LEFT = 87, - ICON_BOX_TOP_LEFT = 88, - ICON_BOX_CENTER = 89, - ICON_BOX_CIRCLE_MASK = 90, - ICON_POT = 91, - ICON_ALPHA_MULTIPLY = 92, - ICON_ALPHA_CLEAR = 93, - ICON_DITHERING = 94, - ICON_MIPMAPS = 95, - ICON_BOX_GRID = 96, - ICON_GRID = 97, - ICON_BOX_CORNERS_SMALL = 98, - ICON_BOX_CORNERS_BIG = 99, - ICON_FOUR_BOXES = 100, - ICON_GRID_FILL = 101, - ICON_BOX_MULTISIZE = 102, - ICON_ZOOM_SMALL = 103, - ICON_ZOOM_MEDIUM = 104, - ICON_ZOOM_BIG = 105, - ICON_ZOOM_ALL = 106, - ICON_ZOOM_CENTER = 107, - ICON_BOX_DOTS_SMALL = 108, - ICON_BOX_DOTS_BIG = 109, - ICON_BOX_CONCENTRIC = 110, - ICON_BOX_GRID_BIG = 111, - ICON_OK_TICK = 112, - ICON_CROSS = 113, - ICON_ARROW_LEFT = 114, - ICON_ARROW_RIGHT = 115, - ICON_ARROW_DOWN = 116, - ICON_ARROW_UP = 117, - ICON_ARROW_LEFT_FILL = 118, - ICON_ARROW_RIGHT_FILL = 119, - ICON_ARROW_DOWN_FILL = 120, - ICON_ARROW_UP_FILL = 121, - ICON_AUDIO = 122, - ICON_FX = 123, - ICON_WAVE = 124, - ICON_WAVE_SINUS = 125, - ICON_WAVE_SQUARE = 126, - ICON_WAVE_TRIANGULAR = 127, - ICON_CROSS_SMALL = 128, - ICON_PLAYER_PREVIOUS = 129, - ICON_PLAYER_PLAY_BACK = 130, - ICON_PLAYER_PLAY = 131, - ICON_PLAYER_PAUSE = 132, - ICON_PLAYER_STOP = 133, - ICON_PLAYER_NEXT = 134, - ICON_PLAYER_RECORD = 135, - ICON_MAGNET = 136, - ICON_LOCK_CLOSE = 137, - ICON_LOCK_OPEN = 138, - ICON_CLOCK = 139, - ICON_TOOLS = 140, - ICON_GEAR = 141, - ICON_GEAR_BIG = 142, - ICON_BIN = 143, - ICON_HAND_POINTER = 144, - ICON_LASER = 145, - ICON_COIN = 146, - ICON_EXPLOSION = 147, - ICON_1UP = 148, - ICON_PLAYER = 149, - ICON_PLAYER_JUMP = 150, - ICON_KEY = 151, - ICON_DEMON = 152, - ICON_TEXT_POPUP = 153, - ICON_GEAR_EX = 154, - ICON_CRACK = 155, - ICON_CRACK_POINTS = 156, - ICON_STAR = 157, - ICON_DOOR = 158, - ICON_EXIT = 159, - ICON_MODE_2D = 160, - ICON_MODE_3D = 161, - ICON_CUBE = 162, - ICON_CUBE_FACE_TOP = 163, - ICON_CUBE_FACE_LEFT = 164, - ICON_CUBE_FACE_FRONT = 165, - ICON_CUBE_FACE_BOTTOM = 166, - ICON_CUBE_FACE_RIGHT = 167, - ICON_CUBE_FACE_BACK = 168, - ICON_CAMERA = 169, - ICON_SPECIAL = 170, - ICON_LINK_NET = 171, - ICON_LINK_BOXES = 172, - ICON_LINK_MULTI = 173, - ICON_LINK = 174, - ICON_LINK_BROKE = 175, - ICON_TEXT_NOTES = 176, - ICON_NOTEBOOK = 177, - ICON_SUITCASE = 178, - ICON_SUITCASE_ZIP = 179, - ICON_MAILBOX = 180, - ICON_MONITOR = 181, - ICON_PRINTER = 182, - ICON_PHOTO_CAMERA = 183, - ICON_PHOTO_CAMERA_FLASH = 184, - ICON_HOUSE = 185, - ICON_HEART = 186, - ICON_CORNER = 187, - ICON_VERTICAL_BARS = 188, - ICON_VERTICAL_BARS_FILL = 189, - ICON_LIFE_BARS = 190, - ICON_INFO = 191, - ICON_CROSSLINE = 192, - ICON_HELP = 193, - ICON_FILETYPE_ALPHA = 194, - ICON_FILETYPE_HOME = 195, - ICON_LAYERS_VISIBLE = 196, - ICON_LAYERS = 197, - ICON_WINDOW = 198, - ICON_HIDPI = 199, - ICON_FILETYPE_BINARY = 200, - ICON_HEX = 201, - ICON_SHIELD = 202, - ICON_FILE_NEW = 203, - ICON_FOLDER_ADD = 204, - ICON_ALARM = 205, - ICON_CPU = 206, - ICON_ROM = 207, - ICON_STEP_OVER = 208, - ICON_STEP_INTO = 209, - ICON_STEP_OUT = 210, - ICON_RESTART = 211, - ICON_BREAKPOINT_ON = 212, - ICON_BREAKPOINT_OFF = 213, - ICON_BURGER_MENU = 214, - ICON_CASE_SENSITIVE = 215, - ICON_REG_EXP = 216, - ICON_FOLDER = 217, - ICON_FILE = 218, - ICON_SAND_TIMER = 219, - ICON_WARNING = 220, - ICON_HELP_BOX = 221, - ICON_INFO_BOX = 222, - ICON_PRIORITY = 223, - ICON_LAYERS_ISO = 224, - ICON_LAYERS2 = 225, - ICON_MLAYERS = 226, - ICON_MAPS = 227, - ICON_HOT = 228, - ICON_229 = 229, - ICON_230 = 230, - ICON_231 = 231, - ICON_232 = 232, - ICON_233 = 233, - ICON_234 = 234, - ICON_235 = 235, - ICON_236 = 236, - ICON_237 = 237, - ICON_238 = 238, - ICON_239 = 239, - ICON_240 = 240, - ICON_241 = 241, - ICON_242 = 242, - ICON_243 = 243, - ICON_244 = 244, - ICON_245 = 245, - ICON_246 = 246, - ICON_247 = 247, - ICON_248 = 248, - ICON_249 = 249, - ICON_250 = 250, - ICON_251 = 251, - ICON_252 = 252, - ICON_253 = 253, - ICON_254 = 254, - ICON_255 = 255, -} GuiIconName; -#endif - -#endif - -#if defined(__cplusplus) -} // Prevents name mangling of functions -#endif - -#endif // RAYGUI_H - -/*********************************************************************************** -* -* RAYGUI IMPLEMENTATION -* -************************************************************************************/ - -#if defined(RAYGUI_IMPLEMENTATION) - -#include // required for: isspace() [GuiTextBox()] -#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] -#include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] -#include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() -#include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] -#include // Required for: roundf() [GuiColorPicker()] - -#ifdef __cplusplus - #define RAYGUI_CLITERAL(name) name -#else - #define RAYGUI_CLITERAL(name) (name) -#endif - -// Check if two rectangles are equal, used to validate a slider bounds as an id -#ifndef CHECK_BOUNDS_ID - #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) -#endif - -#if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) - -// Embedded icons, no external file provided -#define RAYGUI_ICON_SIZE 16 // Size of icons in pixels (squared) -#define RAYGUI_ICON_MAX_ICONS 256 // Maximum number of icons -#define RAYGUI_ICON_MAX_NAME_LENGTH 32 // Maximum length of icon name id - -// Icons data is defined by bit array (every bit represents one pixel) -// Those arrays are stored as unsigned int data arrays, so, -// every array element defines 32 pixels (bits) of information -// One icon is defined by 8 int, (8 int * 32 bit = 256 bit = 16*16 pixels) -// NOTE: Number of elemens depend on RAYGUI_ICON_SIZE (by default 16x16 pixels) -#define RAYGUI_ICON_DATA_ELEMENTS (RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32) - -//---------------------------------------------------------------------------------- -// Icons data for all gui possible icons (allocated on data segment by default) -// -// NOTE 1: Every icon is codified in binary form, using 1 bit per pixel, so, -// every 16x16 icon requires 8 integers (16*16/32) to be stored -// -// NOTE 2: A different icon set could be loaded over this array using GuiLoadIcons(), -// but loaded icons set must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS -// -// guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB -//---------------------------------------------------------------------------------- -static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = { - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_NONE - 0x3ff80000, 0x2f082008, 0x2042207e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x00007ffe, // ICON_FOLDER_FILE_OPEN - 0x3ffe0000, 0x44226422, 0x400247e2, 0x5ffa4002, 0x57ea500a, 0x500a500a, 0x40025ffa, 0x00007ffe, // ICON_FILE_SAVE_CLASSIC - 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024002, 0x44424282, 0x793e4102, 0x00000100, // ICON_FOLDER_OPEN - 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024102, 0x44424102, 0x793e4282, 0x00000000, // ICON_FOLDER_SAVE - 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x24442284, 0x21042104, 0x20042104, 0x00003ffc, // ICON_FILE_OPEN - 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x21042104, 0x22842444, 0x20042104, 0x00003ffc, // ICON_FILE_SAVE - 0x3ff00000, 0x201c2010, 0x00042004, 0x20041004, 0x20844784, 0x00841384, 0x20042784, 0x00003ffc, // ICON_FILE_EXPORT - 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x22042204, 0x22042f84, 0x20042204, 0x00003ffc, // ICON_FILE_ADD - 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x25042884, 0x25042204, 0x20042884, 0x00003ffc, // ICON_FILE_DELETE - 0x3ff00000, 0x201c2010, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_FILETYPE_TEXT - 0x3ff00000, 0x201c2010, 0x27042004, 0x244424c4, 0x26442444, 0x20642664, 0x20042004, 0x00003ffc, // ICON_FILETYPE_AUDIO - 0x3ff00000, 0x201c2010, 0x26042604, 0x20042004, 0x35442884, 0x2414222c, 0x20042004, 0x00003ffc, // ICON_FILETYPE_IMAGE - 0x3ff00000, 0x201c2010, 0x20c42004, 0x22442144, 0x22442444, 0x20c42144, 0x20042004, 0x00003ffc, // ICON_FILETYPE_PLAY - 0x3ff00000, 0x3ffc2ff0, 0x3f3c2ff4, 0x3dbc2eb4, 0x3dbc2bb4, 0x3f3c2eb4, 0x3ffc2ff4, 0x00002ff4, // ICON_FILETYPE_VIDEO - 0x3ff00000, 0x201c2010, 0x21842184, 0x21842004, 0x21842184, 0x21842184, 0x20042184, 0x00003ffc, // ICON_FILETYPE_INFO - 0x0ff00000, 0x381c0810, 0x28042804, 0x28042804, 0x28042804, 0x28042804, 0x20102ffc, 0x00003ff0, // ICON_FILE_COPY - 0x00000000, 0x701c0000, 0x079c1e14, 0x55a000f0, 0x079c00f0, 0x701c1e14, 0x00000000, 0x00000000, // ICON_FILE_CUT - 0x01c00000, 0x13e41bec, 0x3f841004, 0x204420c4, 0x20442044, 0x20442044, 0x207c2044, 0x00003fc0, // ICON_FILE_PASTE - 0x00000000, 0x3aa00fe0, 0x2abc2aa0, 0x2aa42aa4, 0x20042aa4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_CURSOR_HAND - 0x00000000, 0x003c000c, 0x030800c8, 0x30100c10, 0x10202020, 0x04400840, 0x01800280, 0x00000000, // ICON_CURSOR_POINTER - 0x00000000, 0x00180000, 0x01f00078, 0x03e007f0, 0x07c003e0, 0x04000e40, 0x00000000, 0x00000000, // ICON_CURSOR_CLASSIC - 0x00000000, 0x04000000, 0x11000a00, 0x04400a80, 0x01100220, 0x00580088, 0x00000038, 0x00000000, // ICON_PENCIL - 0x04000000, 0x15000a00, 0x50402880, 0x14102820, 0x05040a08, 0x015c028c, 0x007c00bc, 0x00000000, // ICON_PENCIL_BIG - 0x01c00000, 0x01400140, 0x01400140, 0x0ff80140, 0x0ff80808, 0x0aa80808, 0x0aa80aa8, 0x00000ff8, // ICON_BRUSH_CLASSIC - 0x1ffc0000, 0x5ffc7ffe, 0x40004000, 0x00807f80, 0x01c001c0, 0x01c001c0, 0x01c001c0, 0x00000080, // ICON_BRUSH_PAINTER - 0x00000000, 0x00800000, 0x01c00080, 0x03e001c0, 0x07f003e0, 0x036006f0, 0x000001c0, 0x00000000, // ICON_WATER_DROP - 0x00000000, 0x3e003800, 0x1f803f80, 0x0c201e40, 0x02080c10, 0x00840104, 0x00380044, 0x00000000, // ICON_COLOR_PICKER - 0x00000000, 0x07800300, 0x1fe00fc0, 0x3f883fd0, 0x0e021f04, 0x02040402, 0x00f00108, 0x00000000, // ICON_RUBBER - 0x00c00000, 0x02800140, 0x08200440, 0x20081010, 0x2ffe3004, 0x03f807fc, 0x00e001f0, 0x00000040, // ICON_COLOR_BUCKET - 0x00000000, 0x21843ffc, 0x01800180, 0x01800180, 0x01800180, 0x01800180, 0x03c00180, 0x00000000, // ICON_TEXT_T - 0x00800000, 0x01400180, 0x06200340, 0x0c100620, 0x1ff80c10, 0x380c1808, 0x70067004, 0x0000f80f, // ICON_TEXT_A - 0x78000000, 0x50004000, 0x00004800, 0x03c003c0, 0x03c003c0, 0x00100000, 0x0002000a, 0x0000000e, // ICON_SCALE - 0x75560000, 0x5e004002, 0x54001002, 0x41001202, 0x408200fe, 0x40820082, 0x40820082, 0x00006afe, // ICON_RESIZE - 0x00000000, 0x3f003f00, 0x3f003f00, 0x3f003f00, 0x00400080, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_POINT - 0x6d800000, 0x00004080, 0x40804080, 0x40800000, 0x00406d80, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_BILINEAR - 0x40080000, 0x1ffe2008, 0x14081008, 0x11081208, 0x10481088, 0x10081028, 0x10047ff8, 0x00001002, // ICON_CROP - 0x00100000, 0x3ffc0010, 0x2ab03550, 0x22b02550, 0x20b02150, 0x20302050, 0x2000fff0, 0x00002000, // ICON_CROP_ALPHA - 0x40000000, 0x1ff82000, 0x04082808, 0x01082208, 0x00482088, 0x00182028, 0x35542008, 0x00000002, // ICON_SQUARE_TOGGLE - 0x00000000, 0x02800280, 0x06c006c0, 0x0ea00ee0, 0x1e901eb0, 0x3e883e98, 0x7efc7e8c, 0x00000000, // ICON_SYMMETRY - 0x01000000, 0x05600100, 0x1d480d50, 0x7d423d44, 0x3d447d42, 0x0d501d48, 0x01000560, 0x00000100, // ICON_SYMMETRY_HORIZONTAL - 0x01800000, 0x04200240, 0x10080810, 0x00001ff8, 0x00007ffe, 0x0ff01ff8, 0x03c007e0, 0x00000180, // ICON_SYMMETRY_VERTICAL - 0x00000000, 0x010800f0, 0x02040204, 0x02040204, 0x07f00308, 0x1c000e00, 0x30003800, 0x00000000, // ICON_LENS - 0x00000000, 0x061803f0, 0x08240c0c, 0x08040814, 0x0c0c0804, 0x23f01618, 0x18002400, 0x00000000, // ICON_LENS_BIG - 0x00000000, 0x00000000, 0x1c7007c0, 0x638e3398, 0x1c703398, 0x000007c0, 0x00000000, 0x00000000, // ICON_EYE_ON - 0x00000000, 0x10002000, 0x04700fc0, 0x610e3218, 0x1c703098, 0x001007a0, 0x00000008, 0x00000000, // ICON_EYE_OFF - 0x00000000, 0x00007ffc, 0x40047ffc, 0x10102008, 0x04400820, 0x02800280, 0x02800280, 0x00000100, // ICON_FILTER_TOP - 0x00000000, 0x40027ffe, 0x10082004, 0x04200810, 0x02400240, 0x02400240, 0x01400240, 0x000000c0, // ICON_FILTER - 0x00800000, 0x00800080, 0x00000080, 0x3c9e0000, 0x00000000, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_POINT - 0x00800000, 0x00800080, 0x00800080, 0x3f7e01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL - 0x00800000, 0x00800080, 0x03e00080, 0x3e3e0220, 0x03e00220, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG - 0x01000000, 0x04400280, 0x01000100, 0x43842008, 0x43849ab2, 0x01002008, 0x04400100, 0x01000280, // ICON_TARGET_MOVE - 0x01000000, 0x04400280, 0x01000100, 0x41042108, 0x41049ff2, 0x01002108, 0x04400100, 0x01000280, // ICON_CURSOR_MOVE - 0x781e0000, 0x500a4002, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x4002500a, 0x0000781e, // ICON_CURSOR_SCALE - 0x00000000, 0x20003c00, 0x24002800, 0x01000200, 0x00400080, 0x00140024, 0x003c0004, 0x00000000, // ICON_CURSOR_SCALE_RIGHT - 0x00000000, 0x0004003c, 0x00240014, 0x00800040, 0x02000100, 0x28002400, 0x3c002000, 0x00000000, // ICON_CURSOR_SCALE_LEFT - 0x00000000, 0x00100020, 0x10101fc8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO - 0x00000000, 0x08000400, 0x080813f8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO - 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3f902020, 0x00400020, 0x00000000, // ICON_REREDO - 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3fc82010, 0x00200010, 0x00000000, // ICON_MUTATE - 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18101020, 0x00100fc8, 0x00000020, // ICON_ROTATE - 0x00000000, 0x04000200, 0x240429fc, 0x20042204, 0x20442004, 0x3f942024, 0x00400020, 0x00000000, // ICON_REPEAT - 0x00000000, 0x20001000, 0x22104c0e, 0x00801120, 0x11200040, 0x4c0e2210, 0x10002000, 0x00000000, // ICON_SHUFFLE - 0x7ffe0000, 0x50024002, 0x44024802, 0x41024202, 0x40424082, 0x40124022, 0x4002400a, 0x00007ffe, // ICON_EMPTYBOX - 0x00800000, 0x03e00080, 0x08080490, 0x3c9e0808, 0x08080808, 0x03e00490, 0x00800080, 0x00000000, // ICON_TARGET - 0x00800000, 0x00800080, 0x00800080, 0x3ffe01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL_FILL - 0x00800000, 0x00800080, 0x03e00080, 0x3ffe03e0, 0x03e003e0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG_FILL - 0x01000000, 0x07c00380, 0x01000100, 0x638c2008, 0x638cfbbe, 0x01002008, 0x07c00100, 0x01000380, // ICON_TARGET_MOVE_FILL - 0x01000000, 0x07c00380, 0x01000100, 0x610c2108, 0x610cfffe, 0x01002108, 0x07c00100, 0x01000380, // ICON_CURSOR_MOVE_FILL - 0x781e0000, 0x6006700e, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x700e6006, 0x0000781e, // ICON_CURSOR_SCALE_FILL - 0x00000000, 0x38003c00, 0x24003000, 0x01000200, 0x00400080, 0x000c0024, 0x003c001c, 0x00000000, // ICON_CURSOR_SCALE_RIGHT_FILL - 0x00000000, 0x001c003c, 0x0024000c, 0x00800040, 0x02000100, 0x30002400, 0x3c003800, 0x00000000, // ICON_CURSOR_SCALE_LEFT_FILL - 0x00000000, 0x00300020, 0x10301ff8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO_FILL - 0x00000000, 0x0c000400, 0x0c081ff8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO_FILL - 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3ff02060, 0x00400060, 0x00000000, // ICON_REREDO_FILL - 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3ff82030, 0x00200030, 0x00000000, // ICON_MUTATE_FILL - 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18301020, 0x00300ff8, 0x00000020, // ICON_ROTATE_FILL - 0x00000000, 0x06000200, 0x26042ffc, 0x20042204, 0x20442004, 0x3ff42064, 0x00400060, 0x00000000, // ICON_REPEAT_FILL - 0x00000000, 0x30001000, 0x32107c0e, 0x00801120, 0x11200040, 0x7c0e3210, 0x10003000, 0x00000000, // ICON_SHUFFLE_FILL - 0x00000000, 0x30043ffc, 0x24042804, 0x21042204, 0x20442084, 0x20142024, 0x3ffc200c, 0x00000000, // ICON_EMPTYBOX_SMALL - 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX - 0x00000000, 0x23c43ffc, 0x23c423c4, 0x200423c4, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP - 0x00000000, 0x3e043ffc, 0x3e043e04, 0x20043e04, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_RIGHT - 0x00000000, 0x20043ffc, 0x20042004, 0x3e043e04, 0x3e043e04, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_RIGHT - 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x3e042004, 0x3e043e04, 0x3ffc3e04, 0x00000000, // ICON_BOX_BOTTOM_RIGHT - 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x23c42004, 0x23c423c4, 0x3ffc23c4, 0x00000000, // ICON_BOX_BOTTOM - 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x207c2004, 0x207c207c, 0x3ffc207c, 0x00000000, // ICON_BOX_BOTTOM_LEFT - 0x00000000, 0x20043ffc, 0x20042004, 0x207c207c, 0x207c207c, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_LEFT - 0x00000000, 0x207c3ffc, 0x207c207c, 0x2004207c, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_LEFT - 0x00000000, 0x20043ffc, 0x20042004, 0x23c423c4, 0x23c423c4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_CENTER - 0x7ffe0000, 0x40024002, 0x47e24182, 0x4ff247e2, 0x47e24ff2, 0x418247e2, 0x40024002, 0x00007ffe, // ICON_BOX_CIRCLE_MASK - 0x7fff0000, 0x40014001, 0x40014001, 0x49555ddd, 0x4945495d, 0x400149c5, 0x40014001, 0x00007fff, // ICON_POT - 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x404e40ce, 0x48125432, 0x4006540e, 0x00007ffe, // ICON_ALPHA_MULTIPLY - 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x5c4e40ce, 0x44124432, 0x40065c0e, 0x00007ffe, // ICON_ALPHA_CLEAR - 0x7ffe0000, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x00007ffe, // ICON_DITHERING - 0x07fe0000, 0x1ffa0002, 0x7fea000a, 0x402a402a, 0x5b2a512a, 0x5128552a, 0x40205128, 0x00007fe0, // ICON_MIPMAPS - 0x00000000, 0x1ff80000, 0x12481248, 0x12481ff8, 0x1ff81248, 0x12481248, 0x00001ff8, 0x00000000, // ICON_BOX_GRID - 0x12480000, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x00001248, // ICON_GRID - 0x00000000, 0x1c380000, 0x1c3817e8, 0x08100810, 0x08100810, 0x17e81c38, 0x00001c38, 0x00000000, // ICON_BOX_CORNERS_SMALL - 0x700e0000, 0x700e5ffa, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x5ffa700e, 0x0000700e, // ICON_BOX_CORNERS_BIG - 0x3f7e0000, 0x21422142, 0x21422142, 0x00003f7e, 0x21423f7e, 0x21422142, 0x3f7e2142, 0x00000000, // ICON_FOUR_BOXES - 0x00000000, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x00000000, // ICON_GRID_FILL - 0x7ffe0000, 0x7ffe7ffe, 0x77fe7000, 0x77fe77fe, 0x777e7700, 0x777e777e, 0x777e777e, 0x0000777e, // ICON_BOX_MULTISIZE - 0x781e0000, 0x40024002, 0x00004002, 0x01800000, 0x00000180, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_SMALL - 0x781e0000, 0x40024002, 0x00004002, 0x03c003c0, 0x03c003c0, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_MEDIUM - 0x781e0000, 0x40024002, 0x07e04002, 0x07e007e0, 0x07e007e0, 0x400207e0, 0x40024002, 0x0000781e, // ICON_ZOOM_BIG - 0x781e0000, 0x5ffa4002, 0x1ff85ffa, 0x1ff81ff8, 0x1ff81ff8, 0x5ffa1ff8, 0x40025ffa, 0x0000781e, // ICON_ZOOM_ALL - 0x00000000, 0x2004381c, 0x00002004, 0x00000000, 0x00000000, 0x20040000, 0x381c2004, 0x00000000, // ICON_ZOOM_CENTER - 0x00000000, 0x1db80000, 0x10081008, 0x10080000, 0x00001008, 0x10081008, 0x00001db8, 0x00000000, // ICON_BOX_DOTS_SMALL - 0x35560000, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x35562002, 0x00000000, // ICON_BOX_DOTS_BIG - 0x7ffe0000, 0x40024002, 0x48124ff2, 0x49924812, 0x48124992, 0x4ff24812, 0x40024002, 0x00007ffe, // ICON_BOX_CONCENTRIC - 0x00000000, 0x10841ffc, 0x10841084, 0x1ffc1084, 0x10841084, 0x10841084, 0x00001ffc, 0x00000000, // ICON_BOX_GRID_BIG - 0x00000000, 0x00000000, 0x10000000, 0x04000800, 0x01040200, 0x00500088, 0x00000020, 0x00000000, // ICON_OK_TICK - 0x00000000, 0x10080000, 0x04200810, 0x01800240, 0x02400180, 0x08100420, 0x00001008, 0x00000000, // ICON_CROSS - 0x00000000, 0x02000000, 0x00800100, 0x00200040, 0x00200010, 0x00800040, 0x02000100, 0x00000000, // ICON_ARROW_LEFT - 0x00000000, 0x00400000, 0x01000080, 0x04000200, 0x04000800, 0x01000200, 0x00400080, 0x00000000, // ICON_ARROW_RIGHT - 0x00000000, 0x00000000, 0x00000000, 0x08081004, 0x02200410, 0x00800140, 0x00000000, 0x00000000, // ICON_ARROW_DOWN - 0x00000000, 0x00000000, 0x01400080, 0x04100220, 0x10040808, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP - 0x00000000, 0x02000000, 0x03800300, 0x03e003c0, 0x03e003f0, 0x038003c0, 0x02000300, 0x00000000, // ICON_ARROW_LEFT_FILL - 0x00000000, 0x00400000, 0x01c000c0, 0x07c003c0, 0x07c00fc0, 0x01c003c0, 0x004000c0, 0x00000000, // ICON_ARROW_RIGHT_FILL - 0x00000000, 0x00000000, 0x00000000, 0x0ff81ffc, 0x03e007f0, 0x008001c0, 0x00000000, 0x00000000, // ICON_ARROW_DOWN_FILL - 0x00000000, 0x00000000, 0x01c00080, 0x07f003e0, 0x1ffc0ff8, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP_FILL - 0x00000000, 0x18a008c0, 0x32881290, 0x24822686, 0x26862482, 0x12903288, 0x08c018a0, 0x00000000, // ICON_AUDIO - 0x00000000, 0x04800780, 0x004000c0, 0x662000f0, 0x08103c30, 0x130a0e18, 0x0000318e, 0x00000000, // ICON_FX - 0x00000000, 0x00800000, 0x08880888, 0x2aaa0a8a, 0x0a8a2aaa, 0x08880888, 0x00000080, 0x00000000, // ICON_WAVE - 0x00000000, 0x00600000, 0x01080090, 0x02040108, 0x42044204, 0x24022402, 0x00001800, 0x00000000, // ICON_WAVE_SINUS - 0x00000000, 0x07f80000, 0x04080408, 0x04080408, 0x04080408, 0x7c0e0408, 0x00000000, 0x00000000, // ICON_WAVE_SQUARE - 0x00000000, 0x00000000, 0x00a00040, 0x22084110, 0x08021404, 0x00000000, 0x00000000, 0x00000000, // ICON_WAVE_TRIANGULAR - 0x00000000, 0x00000000, 0x04200000, 0x01800240, 0x02400180, 0x00000420, 0x00000000, 0x00000000, // ICON_CROSS_SMALL - 0x00000000, 0x18380000, 0x12281428, 0x10a81128, 0x112810a8, 0x14281228, 0x00001838, 0x00000000, // ICON_PLAYER_PREVIOUS - 0x00000000, 0x18000000, 0x11801600, 0x10181060, 0x10601018, 0x16001180, 0x00001800, 0x00000000, // ICON_PLAYER_PLAY_BACK - 0x00000000, 0x00180000, 0x01880068, 0x18080608, 0x06081808, 0x00680188, 0x00000018, 0x00000000, // ICON_PLAYER_PLAY - 0x00000000, 0x1e780000, 0x12481248, 0x12481248, 0x12481248, 0x12481248, 0x00001e78, 0x00000000, // ICON_PLAYER_PAUSE - 0x00000000, 0x1ff80000, 0x10081008, 0x10081008, 0x10081008, 0x10081008, 0x00001ff8, 0x00000000, // ICON_PLAYER_STOP - 0x00000000, 0x1c180000, 0x14481428, 0x15081488, 0x14881508, 0x14281448, 0x00001c18, 0x00000000, // ICON_PLAYER_NEXT - 0x00000000, 0x03c00000, 0x08100420, 0x10081008, 0x10081008, 0x04200810, 0x000003c0, 0x00000000, // ICON_PLAYER_RECORD - 0x00000000, 0x0c3007e0, 0x13c81818, 0x14281668, 0x14281428, 0x1c381c38, 0x08102244, 0x00000000, // ICON_MAGNET - 0x07c00000, 0x08200820, 0x3ff80820, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_CLOSE - 0x07c00000, 0x08000800, 0x3ff80800, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_OPEN - 0x01c00000, 0x0c180770, 0x3086188c, 0x60832082, 0x60034781, 0x30062002, 0x0c18180c, 0x01c00770, // ICON_CLOCK - 0x0a200000, 0x1b201b20, 0x04200e20, 0x04200420, 0x04700420, 0x0e700e70, 0x0e700e70, 0x04200e70, // ICON_TOOLS - 0x01800000, 0x3bdc318c, 0x0ff01ff8, 0x7c3e1e78, 0x1e787c3e, 0x1ff80ff0, 0x318c3bdc, 0x00000180, // ICON_GEAR - 0x01800000, 0x3ffc318c, 0x1c381ff8, 0x781e1818, 0x1818781e, 0x1ff81c38, 0x318c3ffc, 0x00000180, // ICON_GEAR_BIG - 0x00000000, 0x08080ff8, 0x08081ffc, 0x0aa80aa8, 0x0aa80aa8, 0x0aa80aa8, 0x08080aa8, 0x00000ff8, // ICON_BIN - 0x00000000, 0x00000000, 0x20043ffc, 0x08043f84, 0x04040f84, 0x04040784, 0x000007fc, 0x00000000, // ICON_HAND_POINTER - 0x00000000, 0x24400400, 0x00001480, 0x6efe0e00, 0x00000e00, 0x24401480, 0x00000400, 0x00000000, // ICON_LASER - 0x00000000, 0x03c00000, 0x08300460, 0x11181118, 0x11181118, 0x04600830, 0x000003c0, 0x00000000, // ICON_COIN - 0x00000000, 0x10880080, 0x06c00810, 0x366c07e0, 0x07e00240, 0x00001768, 0x04200240, 0x00000000, // ICON_EXPLOSION - 0x00000000, 0x3d280000, 0x2528252c, 0x3d282528, 0x05280528, 0x05e80528, 0x00000000, 0x00000000, // ICON_1UP - 0x01800000, 0x03c003c0, 0x018003c0, 0x0ff007e0, 0x0bd00bd0, 0x0a500bd0, 0x02400240, 0x02400240, // ICON_PLAYER - 0x01800000, 0x03c003c0, 0x118013c0, 0x03c81ff8, 0x07c003c8, 0x04400440, 0x0c080478, 0x00000000, // ICON_PLAYER_JUMP - 0x3ff80000, 0x30183ff8, 0x30183018, 0x3ff83ff8, 0x03000300, 0x03c003c0, 0x03e00300, 0x000003e0, // ICON_KEY - 0x3ff80000, 0x3ff83ff8, 0x33983ff8, 0x3ff83398, 0x3ff83ff8, 0x00000540, 0x0fe00aa0, 0x00000fe0, // ICON_DEMON - 0x00000000, 0x0ff00000, 0x20041008, 0x25442004, 0x10082004, 0x06000bf0, 0x00000300, 0x00000000, // ICON_TEXT_POPUP - 0x00000000, 0x11440000, 0x07f00be8, 0x1c1c0e38, 0x1c1c0c18, 0x07f00e38, 0x11440be8, 0x00000000, // ICON_GEAR_EX - 0x00000000, 0x20080000, 0x0c601010, 0x07c00fe0, 0x07c007c0, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK - 0x00000000, 0x20080000, 0x0c601010, 0x04400fe0, 0x04405554, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK_POINTS - 0x00000000, 0x00800080, 0x01c001c0, 0x1ffc3ffe, 0x03e007f0, 0x07f003e0, 0x0c180770, 0x00000808, // ICON_STAR - 0x0ff00000, 0x08180810, 0x08100818, 0x0a100810, 0x08180810, 0x08100818, 0x08100810, 0x00001ff8, // ICON_DOOR - 0x0ff00000, 0x08100810, 0x08100810, 0x10100010, 0x4f902010, 0x10102010, 0x08100010, 0x00000ff0, // ICON_EXIT - 0x00040000, 0x001f000e, 0x0ef40004, 0x12f41284, 0x0ef41214, 0x10040004, 0x7ffc3004, 0x10003000, // ICON_MODE_2D - 0x78040000, 0x501f600e, 0x0ef44004, 0x12f41284, 0x0ef41284, 0x10140004, 0x7ffc300c, 0x10003000, // ICON_MODE_3D - 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE - 0x7fe00000, 0x5ff87ff0, 0x47fe4ffc, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_TOP - 0x7fe00000, 0x50386030, 0x47c2483c, 0x443e443e, 0x443e443e, 0x241e75fe, 0x0c06140e, 0x000007fe, // ICON_CUBE_FACE_LEFT - 0x7fe00000, 0x50286030, 0x47fe4804, 0x47fe47fe, 0x47fe47fe, 0x27fe77fe, 0x0ffe17fe, 0x000007fe, // ICON_CUBE_FACE_FRONT - 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x3bf27be2, 0x0bfe1bfa, 0x000007fe, // ICON_CUBE_FACE_BOTTOM - 0x7fe00000, 0x70286030, 0x7ffe7804, 0x7c227c02, 0x7c227c22, 0x3c127de2, 0x0c061c0a, 0x000007fe, // ICON_CUBE_FACE_RIGHT - 0x7fe00000, 0x6fe85ff0, 0x781e77e4, 0x7be27be2, 0x7be27be2, 0x24127be2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_BACK - 0x00000000, 0x2a0233fe, 0x22022602, 0x22022202, 0x2a022602, 0x00a033fe, 0x02080110, 0x00000000, // ICON_CAMERA - 0x00000000, 0x200c3ffc, 0x000c000c, 0x3ffc000c, 0x30003000, 0x30003000, 0x3ffc3004, 0x00000000, // ICON_SPECIAL - 0x00000000, 0x0022003e, 0x012201e2, 0x0100013e, 0x01000100, 0x79000100, 0x4f004900, 0x00007800, // ICON_LINK_NET - 0x00000000, 0x44007c00, 0x45004600, 0x00627cbe, 0x00620022, 0x45007cbe, 0x44004600, 0x00007c00, // ICON_LINK_BOXES - 0x00000000, 0x0044007c, 0x0010007c, 0x3f100010, 0x3f1021f0, 0x3f100010, 0x3f0021f0, 0x00000000, // ICON_LINK_MULTI - 0x00000000, 0x0044007c, 0x00440044, 0x0010007c, 0x00100010, 0x44107c10, 0x440047f0, 0x00007c00, // ICON_LINK - 0x00000000, 0x0044007c, 0x00440044, 0x0000007c, 0x00000010, 0x44007c10, 0x44004550, 0x00007c00, // ICON_LINK_BROKE - 0x02a00000, 0x22a43ffc, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_TEXT_NOTES - 0x3ffc0000, 0x20042004, 0x245e27c4, 0x27c42444, 0x2004201e, 0x201e2004, 0x20042004, 0x00003ffc, // ICON_NOTEBOOK - 0x00000000, 0x07e00000, 0x04200420, 0x24243ffc, 0x24242424, 0x24242424, 0x3ffc2424, 0x00000000, // ICON_SUITCASE - 0x00000000, 0x0fe00000, 0x08200820, 0x40047ffc, 0x7ffc5554, 0x40045554, 0x7ffc4004, 0x00000000, // ICON_SUITCASE_ZIP - 0x00000000, 0x20043ffc, 0x3ffc2004, 0x13c81008, 0x100813c8, 0x10081008, 0x1ff81008, 0x00000000, // ICON_MAILBOX - 0x00000000, 0x40027ffe, 0x5ffa5ffa, 0x5ffa5ffa, 0x40025ffa, 0x03c07ffe, 0x1ff81ff8, 0x00000000, // ICON_MONITOR - 0x0ff00000, 0x6bfe7ffe, 0x7ffe7ffe, 0x68167ffe, 0x08106816, 0x08100810, 0x0ff00810, 0x00000000, // ICON_PRINTER - 0x3ff80000, 0xfffe2008, 0x870a8002, 0x904a888a, 0x904a904a, 0x870a888a, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA - 0x0fc00000, 0xfcfe0cd8, 0x8002fffe, 0x84428382, 0x84428442, 0x80028382, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA_FLASH - 0x00000000, 0x02400180, 0x08100420, 0x20041008, 0x23c42004, 0x22442244, 0x3ffc2244, 0x00000000, // ICON_HOUSE - 0x00000000, 0x1c700000, 0x3ff83ef8, 0x3ff83ff8, 0x0fe01ff0, 0x038007c0, 0x00000100, 0x00000000, // ICON_HEART - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0xe000c000, // ICON_CORNER - 0x00000000, 0x14001c00, 0x15c01400, 0x15401540, 0x155c1540, 0x15541554, 0x1ddc1554, 0x00000000, // ICON_VERTICAL_BARS - 0x00000000, 0x03000300, 0x1b001b00, 0x1b601b60, 0x1b6c1b60, 0x1b6c1b6c, 0x1b6c1b6c, 0x00000000, // ICON_VERTICAL_BARS_FILL - 0x00000000, 0x00000000, 0x403e7ffe, 0x7ffe403e, 0x7ffe0000, 0x43fe43fe, 0x00007ffe, 0x00000000, // ICON_LIFE_BARS - 0x7ffc0000, 0x43844004, 0x43844284, 0x43844004, 0x42844284, 0x42844284, 0x40044384, 0x00007ffc, // ICON_INFO - 0x40008000, 0x10002000, 0x04000800, 0x01000200, 0x00400080, 0x00100020, 0x00040008, 0x00010002, // ICON_CROSSLINE - 0x00000000, 0x1ff01ff0, 0x18301830, 0x1f001830, 0x03001f00, 0x00000300, 0x03000300, 0x00000000, // ICON_HELP - 0x3ff00000, 0x2abc3550, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x00003ffc, // ICON_FILETYPE_ALPHA - 0x3ff00000, 0x201c2010, 0x22442184, 0x28142424, 0x29942814, 0x2ff42994, 0x20042004, 0x00003ffc, // ICON_FILETYPE_HOME - 0x07fe0000, 0x04020402, 0x7fe20402, 0x44224422, 0x44224422, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS_VISIBLE - 0x07fe0000, 0x04020402, 0x7c020402, 0x44024402, 0x44024402, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS - 0x00000000, 0x40027ffe, 0x7ffe4002, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_WINDOW - 0x09100000, 0x09f00910, 0x09100910, 0x00000910, 0x24a2779e, 0x27a224a2, 0x709e20a2, 0x00000000, // ICON_HIDPI - 0x3ff00000, 0x201c2010, 0x2a842e84, 0x2e842a84, 0x2ba42004, 0x2aa42aa4, 0x20042ba4, 0x00003ffc, // ICON_FILETYPE_BINARY - 0x00000000, 0x00000000, 0x00120012, 0x4a5e4bd2, 0x485233d2, 0x00004bd2, 0x00000000, 0x00000000, // ICON_HEX - 0x01800000, 0x381c0660, 0x23c42004, 0x23c42044, 0x13c82204, 0x08101008, 0x02400420, 0x00000180, // ICON_SHIELD - 0x007e0000, 0x20023fc2, 0x40227fe2, 0x400a403a, 0x400a400a, 0x400a400a, 0x4008400e, 0x00007ff8, // ICON_FILE_NEW - 0x00000000, 0x0042007e, 0x40027fc2, 0x44024002, 0x5f024402, 0x44024402, 0x7ffe4002, 0x00000000, // ICON_FOLDER_ADD - 0x44220000, 0x12482244, 0xf3cf0000, 0x14280420, 0x48122424, 0x08100810, 0x1ff81008, 0x03c00420, // ICON_ALARM - 0x0aa00000, 0x1ff80aa0, 0x1068700e, 0x1008706e, 0x1008700e, 0x1008700e, 0x0aa01ff8, 0x00000aa0, // ICON_CPU - 0x07e00000, 0x04201db8, 0x04a01c38, 0x04a01d38, 0x04a01d38, 0x04a01d38, 0x04201d38, 0x000007e0, // ICON_ROM - 0x00000000, 0x03c00000, 0x3c382ff0, 0x3c04380c, 0x01800000, 0x03c003c0, 0x00000180, 0x00000000, // ICON_STEP_OVER - 0x01800000, 0x01800180, 0x01800180, 0x03c007e0, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_INTO - 0x01800000, 0x07e003c0, 0x01800180, 0x01800180, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_OUT - 0x00000000, 0x0ff003c0, 0x181c1c34, 0x303c301c, 0x30003000, 0x1c301800, 0x03c00ff0, 0x00000000, // ICON_RESTART - 0x00000000, 0x00000000, 0x07e003c0, 0x0ff00ff0, 0x0ff00ff0, 0x03c007e0, 0x00000000, 0x00000000, // ICON_BREAKPOINT_ON - 0x00000000, 0x00000000, 0x042003c0, 0x08100810, 0x08100810, 0x03c00420, 0x00000000, 0x00000000, // ICON_BREAKPOINT_OFF - 0x00000000, 0x00000000, 0x1ff81ff8, 0x1ff80000, 0x00001ff8, 0x1ff81ff8, 0x00000000, 0x00000000, // ICON_BURGER_MENU - 0x00000000, 0x00000000, 0x00880070, 0x0c880088, 0x1e8810f8, 0x3e881288, 0x00000000, 0x00000000, // ICON_CASE_SENSITIVE - 0x00000000, 0x02000000, 0x07000a80, 0x07001fc0, 0x02000a80, 0x00300030, 0x00000000, 0x00000000, // ICON_REG_EXP - 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER - 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE - 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER - 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING - 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX - 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX - 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY - 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO - 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 - 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS - 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS - 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_231 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_232 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_233 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_237 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_238 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_239 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_240 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_241 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_242 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_243 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_244 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_245 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_246 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_247 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_248 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_249 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_253 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_254 - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 -}; - -// NOTE: A pointer to current icons array should be defined -static unsigned int *guiIconsPtr = guiIcons; - -#endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS - -#ifndef RAYGUI_ICON_SIZE - #define RAYGUI_ICON_SIZE 0 -#endif - -// WARNING: Those values define the total size of the style data array, -// if changed, previous saved styles could become incompatible -#define RAYGUI_MAX_CONTROLS 16 // Maximum number of controls -#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of base properties -#define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -// Gui control property style color element -typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -static GuiState guiState = STATE_NORMAL; // Gui global state, if !STATE_NORMAL, forces defined state - -static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib) -static bool guiLocked = false; // Gui lock state (no inputs processed) -static float guiAlpha = 1.0f; // Gui controls transparency - -static unsigned int guiIconScale = 1; // Gui icon default scale (if icons enabled) - -static bool guiTooltip = false; // Tooltip enabled/disabled -static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) - -static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) -static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier - -static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() -//static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking -static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down -static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement - -//---------------------------------------------------------------------------------- -// Style data array for all gui style properties (allocated on data segment by default) -// -// NOTE 1: First set of BASE properties are generic to all controls but could be individually -// overwritten per control, first set of EXTENDED properties are generic to all controls and -// can not be overwritten individually but custom EXTENDED properties can be used by control -// -// NOTE 2: A new style set could be loaded over this array using GuiLoadStyle(), -// but default gui style could always be recovered with GuiLoadStyleDefault() -// -// guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB -//---------------------------------------------------------------------------------- -static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 }; - -static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization - -//---------------------------------------------------------------------------------- -// Standalone Mode Functions Declaration -// -// NOTE: raygui depend on some raylib input and drawing functions -// To use raygui as standalone library, below functions must be defined by the user -//---------------------------------------------------------------------------------- -#if defined(RAYGUI_STANDALONE) - -#define KEY_RIGHT 262 -#define KEY_LEFT 263 -#define KEY_DOWN 264 -#define KEY_UP 265 -#define KEY_BACKSPACE 259 -#define KEY_ENTER 257 - -#define MOUSE_LEFT_BUTTON 0 - -// Input required functions -//------------------------------------------------------------------------------- -static Vector2 GetMousePosition(void); -static float GetMouseWheelMove(void); -static bool IsMouseButtonDown(int button); -static bool IsMouseButtonPressed(int button); -static bool IsMouseButtonReleased(int button); - -static bool IsKeyDown(int key); -static bool IsKeyPressed(int key); -static int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() -//------------------------------------------------------------------------------- - -// Drawing required functions -//------------------------------------------------------------------------------- -static void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() -static void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() -//------------------------------------------------------------------------------- - -// Text required functions -//------------------------------------------------------------------------------- -static Font GetFontDefault(void); // -- GuiLoadStyleDefault() -static Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle(), load font - -static Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image -static void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) - -static char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data -static void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data - -static const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs - -static int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list -static void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list - -static unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() -//------------------------------------------------------------------------------- - -// raylib functions already implemented in raygui -//------------------------------------------------------------------------------- -static Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value -static int ColorToInt(Color color); // Returns hexadecimal value for a Color -static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle -static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' -static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings -static int TextToInteger(const char *text); // Get integer value from text -static float TextToFloat(const char *text); // Get float value from text - -static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text -static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) - -static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); // Draw rectangle vertical gradient -//------------------------------------------------------------------------------- - -#endif // RAYGUI_STANDALONE - -//---------------------------------------------------------------------------------- -// Module specific Functions Declaration -//---------------------------------------------------------------------------------- -static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) - -static int GetTextWidth(const char *text); // Gui get text width using gui font and style -static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds -static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor - -static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint); // Gui draw text using default font -static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color); // Gui draw rectangle using default raygui style - -static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow); // Split controls text into multiple strings -static Vector3 ConvertHSVtoRGB(Vector3 hsv); // Convert color data from HSV to RGB -static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color data from RGB to HSV - -static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll bar control, used by GuiScrollPanel() -static void GuiTooltip(Rectangle controlRec); // Draw tooltip using control rec position - -static Color GuiFade(Color color, float alpha); // Fade color by an alpha factor - -//---------------------------------------------------------------------------------- -// Gui Setup Functions Definition -//---------------------------------------------------------------------------------- -// Enable gui global state -// NOTE: We check for STATE_DISABLED to avoid messing custom global state setups -void GuiEnable(void) { if (guiState == STATE_DISABLED) guiState = STATE_NORMAL; } - -// Disable gui global state -// NOTE: We check for STATE_NORMAL to avoid messing custom global state setups -void GuiDisable(void) { if (guiState == STATE_NORMAL) guiState = STATE_DISABLED; } - -// Lock gui global state -void GuiLock(void) { guiLocked = true; } - -// Unlock gui global state -void GuiUnlock(void) { guiLocked = false; } - -// Check if gui is locked (global state) -bool GuiIsLocked(void) { return guiLocked; } - -// Set gui controls alpha global state -void GuiSetAlpha(float alpha) -{ - if (alpha < 0.0f) alpha = 0.0f; - else if (alpha > 1.0f) alpha = 1.0f; - - guiAlpha = alpha; -} - -// Set gui state (global state) -void GuiSetState(int state) { guiState = (GuiState)state; } - -// Get gui state (global state) -int GuiGetState(void) { return guiState; } - -// Set custom gui font -// NOTE: Font loading/unloading is external to raygui -void GuiSetFont(Font font) -{ - if (font.texture.id > 0) - { - // NOTE: If we try to setup a font but default style has not been - // lazily loaded before, it will be overwritten, so we need to force - // default style loading first - if (!guiStyleLoaded) GuiLoadStyleDefault(); - - guiFont = font; - } -} - -// Get custom gui font -Font GuiGetFont(void) -{ - return guiFont; -} - -// Set control style property value -void GuiSetStyle(int control, int property, int value) -{ - if (!guiStyleLoaded) GuiLoadStyleDefault(); - guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; - - // Default properties are propagated to all controls - if ((control == 0) && (property < RAYGUI_MAX_PROPS_BASE)) - { - for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) guiStyle[i*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; - } -} - -// Get control style property value -int GuiGetStyle(int control, int property) -{ - if (!guiStyleLoaded) GuiLoadStyleDefault(); - return guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property]; -} - -//---------------------------------------------------------------------------------- -// Gui Controls Functions Definition -//---------------------------------------------------------------------------------- - -// Window Box control -int GuiWindowBox(Rectangle bounds, const char *title) -{ - // Window title bar height (including borders) - // NOTE: This define is also used by GuiMessageBox() and GuiTextInputBox() - #if !defined(RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT) - #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 - #endif - - int result = 0; - //GuiState state = guiState; - - int statusBarHeight = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT; - - Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; - if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; - - Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; - Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, - statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; - - // Update control - //-------------------------------------------------------------------- - // NOTE: Logic is directly managed by button - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiStatusBar(statusBar, title); // Draw window header as status bar - GuiPanel(windowPanel, NULL); // Draw window base - - // Draw window close button - int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); - int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, 1); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); -#if defined(RAYGUI_NO_ICONS) - result = GuiButton(closeButtonRec, "x"); -#else - result = GuiButton(closeButtonRec, GuiIconText(ICON_CROSS_SMALL, NULL)); -#endif - GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); - //-------------------------------------------------------------------- - - return result; // Window close button clicked: result = 1 -} - -// Group Box control with text name -int GuiGroupBox(Rectangle bounds, const char *text) -{ - #if !defined(RAYGUI_GROUPBOX_LINE_THICK) - #define RAYGUI_GROUPBOX_LINE_THICK 1 - #endif - - int result = 0; - GuiState state = guiState; - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, RAYGUI_GROUPBOX_LINE_THICK }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); - - GuiLine(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y - GuiGetStyle(DEFAULT, TEXT_SIZE)/2, bounds.width, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) }, text); - //-------------------------------------------------------------------- - - return result; -} - -// Line control -int GuiLine(Rectangle bounds, const char *text) -{ - #if !defined(RAYGUI_LINE_ORIGIN_SIZE) - #define RAYGUI_LINE_MARGIN_TEXT 12 - #endif - #if !defined(RAYGUI_LINE_TEXT_PADDING) - #define RAYGUI_LINE_TEXT_PADDING 4 - #endif - - int result = 0; - GuiState state = guiState; - - Color color = GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)); - - // Draw control - //-------------------------------------------------------------------- - if (text == NULL) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, bounds.width, 1 }, 0, BLANK, color); - else - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = bounds.height; - textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; - textBounds.y = bounds.y; - - // Draw line with embedded text label: "--- text --------------" - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); - GuiDrawText(text, textBounds, TEXT_ALIGN_LEFT, color); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + 12 + textBounds.width + 4, bounds.y + bounds.height/2, bounds.width - textBounds.width - RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); - } - //-------------------------------------------------------------------- - - return result; -} - -// Panel control -int GuiPanel(Rectangle bounds, const char *text) -{ - #if !defined(RAYGUI_PANEL_BORDER_WIDTH) - #define RAYGUI_PANEL_BORDER_WIDTH 1 - #endif - - int result = 0; - GuiState state = guiState; - - // Text will be drawn as a header bar (if provided) - Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; - if ((text != NULL) && (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f)) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; - - if (text != NULL) - { - // Move panel bounds after the header bar - bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; - bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; - } - - // Draw control - //-------------------------------------------------------------------- - if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar - - GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED: (int)LINE_COLOR)), - GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR))); - //-------------------------------------------------------------------- - - return result; -} - -// Tab Bar control -// NOTE: Using GuiToggle() for the TABS -int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) -{ - #define RAYGUI_TABBAR_ITEM_WIDTH 160 - - int result = -1; - //GuiState state = guiState; - - Rectangle tabBounds = { bounds.x, bounds.y, RAYGUI_TABBAR_ITEM_WIDTH, bounds.height }; - - if (*active < 0) *active = 0; - else if (*active > count - 1) *active = count - 1; - - int offsetX = 0; // Required in case tabs go out of screen - offsetX = (*active + 2)*RAYGUI_TABBAR_ITEM_WIDTH - GetScreenWidth(); - if (offsetX < 0) offsetX = 0; - - bool toggle = false; // Required for individual toggles - - // Draw control - //-------------------------------------------------------------------- - for (int i = 0; i < count; i++) - { - tabBounds.x = bounds.x + (RAYGUI_TABBAR_ITEM_WIDTH + 4)*i - offsetX; - - if (tabBounds.x < GetScreenWidth()) - { - // Draw tabs as toggle controls - int textAlignment = GuiGetStyle(TOGGLE, TEXT_ALIGNMENT); - int textPadding = GuiGetStyle(TOGGLE, TEXT_PADDING); - GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(TOGGLE, TEXT_PADDING, 8); - - if (i == (*active)) - { - toggle = true; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); - } - else - { - toggle = false; - GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); - if (toggle) *active = i; - } - - // Close tab with middle mouse button pressed - if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; - - GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); - GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); - - // Draw tab close button - // NOTE: Only draw close button for current tab: if (CheckCollisionPointRec(mousePosition, tabBounds)) - int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); - int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, 1); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); -#if defined(RAYGUI_NO_ICONS) - if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, "x")) result = i; -#else - if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, GuiIconText(ICON_CROSS_SMALL, NULL))) result = i; -#endif - GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); - } - } - - // Draw tab-bar bottom line - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, 1 }, 0, BLANK, GetColor(GuiGetStyle(TOGGLE, BORDER_COLOR_NORMAL))); - //-------------------------------------------------------------------- - - return result; // Return as result the current TAB closing requested -} - -// Scroll Panel control -int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view) -{ - #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 - #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 - #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 - - int result = 0; - GuiState state = guiState; - - Rectangle temp = { 0 }; - if (view == NULL) view = &temp; - - Vector2 scrollPos = { 0.0f, 0.0f }; - if (scroll != NULL) scrollPos = *scroll; - - // Text will be drawn as a header bar (if provided) - Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; - if (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; - - if (text != NULL) - { - // Move panel bounds after the header bar - bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; - bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + 1; - } - - bool hasHorizontalScrollBar = (content.width > bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; - bool hasVerticalScrollBar = (content.height > bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; - - // Recheck to account for the other scrollbar being visible - if (!hasHorizontalScrollBar) hasHorizontalScrollBar = (hasVerticalScrollBar && (content.width > (bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; - if (!hasVerticalScrollBar) hasVerticalScrollBar = (hasHorizontalScrollBar && (content.height > (bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; - - int horizontalScrollBarWidth = hasHorizontalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; - int verticalScrollBarWidth = hasVerticalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; - Rectangle horizontalScrollBar = { - (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)horizontalScrollBarWidth - }; - Rectangle verticalScrollBar = { - (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), - (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), - (float)verticalScrollBarWidth, - (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - }; - - // Make sure scroll bars have a minimum width/height - if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; - if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; - - // Calculate view area (area without the scrollbars) - *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? - RAYGUI_CLITERAL(Rectangle){ bounds.x + verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth } : - RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth }; - - // Clip view area to the actual content size - if (view->width > content.width) view->width = content.width; - if (view->height > content.height) view->height = content.height; - - float horizontalMin = hasHorizontalScrollBar? ((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH); - float horizontalMax = hasHorizontalScrollBar? content.width - bounds.width + (float)verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH) - (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)verticalScrollBarWidth : 0) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); - float verticalMin = hasVerticalScrollBar? 0.0f : -1.0f; - float verticalMax = hasVerticalScrollBar? content.height - bounds.height + (float)horizontalScrollBarWidth + (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - // Check button state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - -#if defined(SUPPORT_SCROLLBAR_KEY_INPUT) - if (hasHorizontalScrollBar) - { - if (IsKeyDown(KEY_RIGHT)) scrollPos.x -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - if (IsKeyDown(KEY_LEFT)) scrollPos.x += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - } - - if (hasVerticalScrollBar) - { - if (IsKeyDown(KEY_DOWN)) scrollPos.y -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - if (IsKeyDown(KEY_UP)) scrollPos.y += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - } -#endif - float wheelMove = GetMouseWheelMove(); - - // Set scrolling speed with mouse wheel based on ratio between bounds and content - Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; - if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; - if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; - - // Horizontal and vertical scrolling with mouse wheel - if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; - else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll - } - } - - // Normalize scroll values - if (scrollPos.x > -horizontalMin) scrollPos.x = -horizontalMin; - if (scrollPos.x < -horizontalMax) scrollPos.x = -horizontalMax; - if (scrollPos.y > -verticalMin) scrollPos.y = -verticalMin; - if (scrollPos.y < -verticalMax) scrollPos.y = -verticalMax; - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar - - GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background - - // Save size of the scrollbar slider - const int slider = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); - - // Draw horizontal scrollbar if visible - if (hasHorizontalScrollBar) - { - // Change scrollbar slider size to show the diff in size between the content width and the widget width - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth)/(int)content.width)*((int)bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth))); - scrollPos.x = (float)-GuiScrollBar(horizontalScrollBar, (int)-scrollPos.x, (int)horizontalMin, (int)horizontalMax); - } - else scrollPos.x = 0.0f; - - // Draw vertical scrollbar if visible - if (hasVerticalScrollBar) - { - // Change scrollbar slider size to show the diff in size between the content height and the widget height - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth)/(int)content.height)*((int)bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth))); - scrollPos.y = (float)-GuiScrollBar(verticalScrollBar, (int)-scrollPos.y, (int)verticalMin, (int)verticalMax); - } - else scrollPos.y = 0.0f; - - // Draw detail corner rectangle if both scroll bars are visible - if (hasHorizontalScrollBar && hasVerticalScrollBar) - { - Rectangle corner = { (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) + 2) : (horizontalScrollBar.x + horizontalScrollBar.width + 2), verticalScrollBar.y + verticalScrollBar.height + 2, (float)horizontalScrollBarWidth - 4, (float)verticalScrollBarWidth - 4 }; - GuiDrawRectangle(corner, 0, BLANK, GetColor(GuiGetStyle(LISTVIEW, TEXT + (state*3)))); - } - - // Draw scrollbar lines depending on current state - GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); - - // Set scrollbar slider size back to the way it was before - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); - //-------------------------------------------------------------------- - - if (scroll != NULL) *scroll = scrollPos; - - return result; -} - -// Label control -int GuiLabel(Rectangle bounds, const char *text) -{ - int result = 0; - GuiState state = guiState; - - // Update control - //-------------------------------------------------------------------- - //... - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Button control, returns true when clicked -int GuiButton(Rectangle bounds, const char *text) -{ - int result = 0; - GuiState state = guiState; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check button state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(BUTTON, BORDER_WIDTH), GetColor(GuiGetStyle(BUTTON, BORDER + (state*3))), GetColor(GuiGetStyle(BUTTON, BASE + (state*3)))); - GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), GetColor(GuiGetStyle(BUTTON, TEXT + (state*3)))); - - if (state == STATE_FOCUSED) GuiTooltip(bounds); - //------------------------------------------------------------------ - - return result; // Button pressed: result = 1 -} - -// Label button control -int GuiLabelButton(Rectangle bounds, const char *text) -{ - GuiState state = guiState; - bool pressed = false; - - // NOTE: We force bounds.width to be all text - float textWidth = (float)GetTextWidth(text); - if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check checkbox state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return pressed; -} - -// Toggle Button control -int GuiToggle(Rectangle bounds, const char *text, bool *active) -{ - int result = 0; - GuiState state = guiState; - - bool temp = false; - if (active == NULL) active = &temp; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check toggle button state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) - { - state = STATE_NORMAL; - *active = !(*active); - } - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state == STATE_NORMAL) - { - GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, ((*active)? BORDER_COLOR_PRESSED : (BORDER + state*3)))), GetColor(GuiGetStyle(TOGGLE, ((*active)? BASE_COLOR_PRESSED : (BASE + state*3))))); - GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, ((*active)? TEXT_COLOR_PRESSED : (TEXT + state*3))))); - } - else - { - GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + state*3)), GetColor(GuiGetStyle(TOGGLE, BASE + state*3))); - GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, TEXT + state*3))); - } - - if (state == STATE_FOCUSED) GuiTooltip(bounds); - //-------------------------------------------------------------------- - - return result; -} - -// Toggle Group control -int GuiToggleGroup(Rectangle bounds, const char *text, int *active) -{ - #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) - #define RAYGUI_TOGGLEGROUP_MAX_ITEMS 32 - #endif - - int result = 0; - float initBoundsX = bounds.x; - - int temp = 0; - if (active == NULL) active = &temp; - - bool toggle = false; // Required for individual toggles - - // Get substrings items from text (items pointers) - int rows[RAYGUI_TOGGLEGROUP_MAX_ITEMS] = { 0 }; - int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, rows); - - int prevRow = rows[0]; - - for (int i = 0; i < itemCount; i++) - { - if (prevRow != rows[i]) - { - bounds.x = initBoundsX; - bounds.y += (bounds.height + GuiGetStyle(TOGGLE, GROUP_PADDING)); - prevRow = rows[i]; - } - - if (i == (*active)) - { - toggle = true; - GuiToggle(bounds, items[i], &toggle); - } - else - { - toggle = false; - GuiToggle(bounds, items[i], &toggle); - if (toggle) *active = i; - } - - bounds.x += (bounds.width + GuiGetStyle(TOGGLE, GROUP_PADDING)); - } - - return result; -} - -// Toggle Slider control extended -int GuiToggleSlider(Rectangle bounds, const char *text, int *active) -{ - int result = 0; - GuiState state = guiState; - - int temp = 0; - if (active == NULL) active = &temp; - - //bool toggle = false; // Required for individual toggles - - // Get substrings items from text (items pointers) - int itemCount = 0; - const char **items = NULL; - - if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); - - Rectangle slider = { - 0, // Calculated later depending on the active toggle - bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), - (bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - (itemCount + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING))/itemCount, - bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - (*active)++; - result = 1; - } - else state = STATE_FOCUSED; - } - - if ((*active) && (state != STATE_FOCUSED)) state = STATE_PRESSED; - } - - if (*active >= itemCount) *active = 0; - slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH) + (*active + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING) + (*active)*slider.width; - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + (state*3))), - GetColor(GuiGetStyle(TOGGLE, BASE_COLOR_NORMAL))); - - // Draw internal slider - if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); - else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_FOCUSED))); - else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); - - // Draw text in slider - if (text != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(text); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = slider.x + slider.width/2 - textBounds.width/2; - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(items[*active], textBounds, GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TOGGLE, TEXT + (state*3))), guiAlpha)); - } - //-------------------------------------------------------------------- - - return result; -} - -// Check Box control, returns 1 when state changed -int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) -{ - int result = 0; - GuiState state = guiState; - - bool temp = false; - if (checked == NULL) checked = &temp; - - Rectangle textBounds = { 0 }; - - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(CHECKBOX, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - Rectangle totalBounds = { - (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT)? textBounds.x : bounds.x, - bounds.y, - bounds.width + textBounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING), - bounds.height, - }; - - // Check checkbox state - if (CheckCollisionPointRec(mousePoint, totalBounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) - { - *checked = !(*checked); - result = 1; - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(CHECKBOX, BORDER_WIDTH), GetColor(GuiGetStyle(CHECKBOX, BORDER + (state*3))), BLANK); - - if (*checked) - { - Rectangle check = { bounds.x + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), - bounds.y + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), - bounds.width - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)), - bounds.height - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)) }; - GuiDrawRectangle(check, 0, BLANK, GetColor(GuiGetStyle(CHECKBOX, TEXT + state*3))); - } - - GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Combo Box control -int GuiComboBox(Rectangle bounds, const char *text, int *active) -{ - int result = 0; - GuiState state = guiState; - - int temp = 0; - if (active == NULL) active = &temp; - - bounds.width -= (GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH) + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING)); - - Rectangle selector = { (float)bounds.x + bounds.width + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING), - (float)bounds.y, (float)GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH), (float)bounds.height }; - - // Get substrings items from text (items pointers, lengths and count) - int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, NULL); - - if (*active < 0) *active = 0; - else if (*active > (itemCount - 1)) *active = itemCount - 1; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - if (CheckCollisionPointRec(mousePoint, bounds) || - CheckCollisionPointRec(mousePoint, selector)) - { - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - *active += 1; - if (*active >= itemCount) *active = 0; // Cyclic combobox - } - - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - // Draw combo box main - GuiDrawRectangle(bounds, GuiGetStyle(COMBOBOX, BORDER_WIDTH), GetColor(GuiGetStyle(COMBOBOX, BORDER + (state*3))), GetColor(GuiGetStyle(COMBOBOX, BASE + (state*3)))); - GuiDrawText(items[*active], GetTextBounds(COMBOBOX, bounds), GuiGetStyle(COMBOBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(COMBOBOX, TEXT + (state*3)))); - - // Draw selector using a custom button - // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values - int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); - int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, 1); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - GuiButton(selector, TextFormat("%i/%i", *active + 1, itemCount)); - - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); - GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); - //-------------------------------------------------------------------- - - return result; -} - -// Dropdown Box control -// NOTE: Returns mouse click -int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode) -{ - int result = 0; - GuiState state = guiState; - - int temp = 0; - if (active == NULL) active = &temp; - - int itemSelected = *active; - int itemFocused = -1; - - int direction = 0; // Dropdown box open direction: down (default) - if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up - - // Get substrings items from text (items pointers, lengths and count) - int itemCount = 0; - const char **items = GuiTextSplit(text, ';', &itemCount, NULL); - - Rectangle boundsOpen = bounds; - boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); - - Rectangle itemBounds = bounds; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - if (editMode) - { - state = STATE_PRESSED; - - // Check if mouse has been pressed or released outside limits - if (!CheckCollisionPointRec(mousePoint, boundsOpen)) - { - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; - } - - // Check if already selected item has been pressed again - if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; - - // Check focused and selected item - for (int i = 0; i < itemCount; i++) - { - // Update item rectangle y position for next item - if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - - if (CheckCollisionPointRec(mousePoint, itemBounds)) - { - itemFocused = i; - if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) - { - itemSelected = i; - result = 1; // Item selected - } - break; - } - } - - itemBounds = bounds; - } - else - { - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - result = 1; - state = STATE_PRESSED; - } - else state = STATE_FOCUSED; - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (editMode) GuiPanel(boundsOpen, NULL); - - GuiDrawRectangle(bounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3))); - GuiDrawText(items[itemSelected], GetTextBounds(DROPDOWNBOX, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3))); - - if (editMode) - { - // Draw visible items - for (int i = 0; i < itemCount; i++) - { - // Update item rectangle y position for next item - if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); - - if (i == itemSelected) - { - GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED))); - GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED))); - } - else if (i == itemFocused) - { - GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED))); - GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED))); - } - else GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL))); - } - } - - if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) - { - // Draw arrows (using icon if available) -#if defined(RAYGUI_NO_ICONS) - GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); -#else - GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL -#endif - } - //-------------------------------------------------------------------- - - *active = itemSelected; - - // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item... - return result; // Mouse click: result = 1 -} - -// Text Box control -// NOTE: Returns true on ENTER pressed (useful for data validation) -int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) -{ - #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement - #endif - #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) - #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement - #endif - - int result = 0; - GuiState state = guiState; - - bool multiline = false; // TODO: Consider multiline text input - int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); - - Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); - int textLength = (int)strlen(text); // Get current text length - int thisCursorIndex = textBoxCursorIndex; - if (thisCursorIndex > textLength) thisCursorIndex = textLength; - int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); - int textIndexOffset = 0; // Text index offset to start drawing in the box - - // Cursor rectangle - // NOTE: Position X value should be updated - Rectangle cursor = { - textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), - textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), - 2, - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 - }; - - if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; - if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); - - // Mouse cursor rectangle - // NOTE: Initialized outside of screen - Rectangle mouseCursor = cursor; - mouseCursor.x = -1; - mouseCursor.width = 1; - - // Auto-cursor movement logic - // NOTE: Cursor moves automatically when key down after some time - if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; - else - { - autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter - autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter - } - - // Blink-cursor frame counter - //if (!autoCursorMode) blinkCursorFrameCounter++; - //else blinkCursorFrameCounter = 0; - - // Update control - //-------------------------------------------------------------------- - // WARNING: Text editing is only supported under certain conditions: - if ((state != STATE_DISABLED) && // Control not disabled - !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode - !guiLocked && // Gui not locked - !guiControlExclusiveMode && // No gui slider on dragging - (wrapMode == TEXT_WRAP_NONE)) // No wrap mode - { - Vector2 mousePosition = GetMousePosition(); - - if (editMode) - { - state = STATE_PRESSED; - - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - - // If text does not fit in the textbox and current cursor position is out of bounds, - // we add an index offset to text for drawing only what requires depending on cursor - while (textWidth >= textBounds.width) - { - int nextCodepointSize = 0; - GetCodepointNext(text + textIndexOffset, &nextCodepointSize); - - textIndexOffset += nextCodepointSize; - - textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); - } - - int codepoint = GetCharPressed(); // Get Unicode codepoint - if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; - - // Encode codepoint as UTF-8 - int codepointSize = 0; - const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); - - // Add codepoint to text, at current cursor position - // NOTE: Make sure we do not overflow buffer size - if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) - { - // Move forward data from cursor position - for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; - - // Add new codepoint in current cursor position - for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; - - textBoxCursorIndex += codepointSize; - textLength += codepointSize; - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - - // Move cursor to start - if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; - - // Move cursor to end - if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; - - // Delete codepoint from text, after current cursor position - if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) - { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - - // Move backward text from cursor position - for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; - - textLength -= codepointSize; - if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - } - - // Delete related codepoints from text, before current cursor position - if ((textLength > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) - { - int i = textBoxCursorIndex - 1; - int accCodepointSize = 0; - - // Move cursor to the end of word if on space already - while ((i > 0) && isspace(text[i])) - { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; - accCodepointSize += prevCodepointSize; - } - - // Move cursor to the start of the word - while ((i > 0) && !isspace(text[i])) - { - int prevCodepointSize = 0; - GetCodepointPrevious(text + i, &prevCodepointSize); - i -= prevCodepointSize; - accCodepointSize += prevCodepointSize; - } - - // Move forward text from cursor position - for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize]; - - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - textBoxCursorIndex -= accCodepointSize; - textLength -= accCodepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) - { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int prevCodepointSize = 0; - - // Prevent cursor index from decrementing past 0 - if (textBoxCursorIndex > 0) - { - GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - - // Move backward text from cursor position - for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; - - textBoxCursorIndex -= codepointSize; - textLength -= codepointSize; - } - - // Make sure text last character is EOL - text[textLength] = '\0'; - } - } - - // Move cursor position with keys - if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) - { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int prevCodepointSize = 0; - if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); - - if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; - } - } - else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) - { - autoCursorDelayCounter++; - - if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames - { - int nextCodepointSize = 0; - GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); - - if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; - } - } - - // Move cursor position with mouse - if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text - { - float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; - int codepointIndex = 0; - float glyphWidth = 0.0f; - float widthToMouseX = 0; - int mouseCursorIndex = 0; - - for (int i = textIndexOffset; i < textLength; i++) - { - codepoint = GetCodepointNext(&text[i], &codepointSize); - codepointIndex = GetGlyphIndex(guiFont, codepoint); - - if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); - else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); - - if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) - { - mouseCursor.x = textBounds.x + widthToMouseX; - mouseCursorIndex = i; - break; - } - - widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - - // Check if mouse cursor is at the last position - int textEndWidth = GetTextWidth(text + textIndexOffset); - if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) - { - mouseCursor.x = textBounds.x + textEndWidth; - mouseCursorIndex = textLength; - } - - // Place cursor at required index on mouse click - if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - cursor.x = mouseCursor.x; - textBoxCursorIndex = mouseCursorIndex; - } - } - else mouseCursor.x = -1; - - // Recalculate cursor position.y depending on textBoxCursorIndex - cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); - //if (multiline) cursor.y = GetTextLines() - - // Finish text editing on ENTER or mouse click outside bounds - if ((!multiline && IsKeyPressed(KEY_ENTER)) || - (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) - { - textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index - result = 1; - } - } - else - { - if (CheckCollisionPointRec(mousePosition, bounds)) - { - state = STATE_FOCUSED; - - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text - result = 1; - } - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state == STATE_PRESSED) - { - GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); - } - else if (state == STATE_DISABLED) - { - GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); - } - else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); - - // Draw text considering index offset if required - // NOTE: Text index offset depends on cursor position - GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); - - // Draw cursor - if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) - { - //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) - GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); - - // Draw mouse position cursor (if required) - if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); - } - else if (state == STATE_FOCUSED) GuiTooltip(bounds); - //-------------------------------------------------------------------- - - return result; // Mouse button pressed: result = 1 -} - -/* -// Text Box control with multiple lines and word-wrap -// NOTE: This text-box is readonly, no editing supported by default -bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) -{ - bool pressed = false; - - GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); - GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // WARNING: If wrap mode enabled, text editing is not supported - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); - - // TODO: Implement methods to calculate cursor position properly - pressed = GuiTextBox(bounds, text, textSize, editMode); - - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); - GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); - GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); - - return pressed; -} -*/ - -// Spinner control, returns selected value -int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) -{ - int result = 1; - GuiState state = guiState; - - int tempValue = *value; - - Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, - bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; - Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; - - Rectangle textBounds = { 0 }; - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check spinner state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - } - } - -#if defined(RAYGUI_NO_ICONS) - if (GuiButton(leftButtonBound, "<")) tempValue--; - if (GuiButton(rightButtonBound, ">")) tempValue++; -#else - if (GuiButton(leftButtonBound, GuiIconText(ICON_ARROW_LEFT_FILL, NULL))) tempValue--; - if (GuiButton(rightButtonBound, GuiIconText(ICON_ARROW_RIGHT_FILL, NULL))) tempValue++; -#endif - - if (!editMode) - { - if (tempValue < minValue) tempValue = minValue; - if (tempValue > maxValue) tempValue = maxValue; - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); - - // Draw value selector custom buttons - // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values - int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); - int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); - GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); - - // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - *value = tempValue; - return result; -} - -// Value Box control, updates input text with numbers -// NOTE: Requires static variables: frameCounter -int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) -{ - #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) - #define RAYGUI_VALUEBOX_MAX_CHARS 32 - #endif - - int result = 0; - GuiState state = guiState; - - char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - sprintf(textValue, "%i", *value); - - Rectangle textBounds = { 0 }; - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - bool valueHasChanged = false; - - if (editMode) - { - state = STATE_PRESSED; - - int keyCount = (int)strlen(textValue); - - // Only allow keys in range [48..57] - if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) - { - if (GetTextWidth(textValue) < bounds.width) - { - int key = GetCharPressed(); - if ((key >= 48) && (key <= 57)) - { - textValue[keyCount] = (char)key; - keyCount++; - valueHasChanged = true; - } - } - } - - // Delete text - if (keyCount > 0) - { - if (IsKeyPressed(KEY_BACKSPACE)) - { - keyCount--; - textValue[keyCount] = '\0'; - valueHasChanged = true; - } - } - - if (valueHasChanged) *value = TextToInteger(textValue); - - // NOTE: We are not clamp values until user input finishes - //if (*value > maxValue) *value = maxValue; - //else if (*value < minValue) *value = minValue; - - if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) - { - if (*value > maxValue) *value = maxValue; - else if (*value < minValue) *value = minValue; - - result = 1; - } - } - else - { - if (*value > maxValue) *value = maxValue; - else if (*value < minValue) *value = minValue; - - if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - Color baseColor = BLANK; - if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); - else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); - - GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); - GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); - - // Draw cursor - if (editMode) - { - // NOTE: ValueBox internal text is always centered - Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; - GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); - } - - // Draw text label if provided - GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Floating point Value Box control, updates input val_str with numbers -// NOTE: Requires static variables: frameCounter -int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) -{ - #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) - #define RAYGUI_VALUEBOX_MAX_CHARS 32 - #endif - - int result = 0; - GuiState state = guiState; - - //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; - //sprintf(textValue, "%2.2f", *value); - - Rectangle textBounds = {0}; - if (text != NULL) - { - textBounds.width = (float)GetTextWidth(text) + 2; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - bool valueHasChanged = false; - - if (editMode) - { - state = STATE_PRESSED; - - int keyCount = (int)strlen(textValue); - - // Only allow keys in range [48..57] - if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) - { - if (GetTextWidth(textValue) < bounds.width) - { - int key = GetCharPressed(); - if (((key >= 48) && (key <= 57)) || - (key == '.') || - ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position - ((keyCount == 0) && (key == '-'))) - { - textValue[keyCount] = (char)key; - keyCount++; - - valueHasChanged = true; - } - } - } - - // Pressed backspace - if (IsKeyPressed(KEY_BACKSPACE)) - { - if (keyCount > 0) - { - keyCount--; - textValue[keyCount] = '\0'; - valueHasChanged = true; - } - } - - if (valueHasChanged) *value = TextToFloat(textValue); - - if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; - } - else - { - if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; - } - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - Color baseColor = BLANK; - if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); - else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); - - GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); - GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); - - // Draw cursor - if (editMode) - { - // NOTE: ValueBox internal text is always centered - Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, - bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, - bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; - GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); - } - - // Draw text label if provided - GuiDrawText(text, textBounds, - (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, - GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Slider control with pro parameters -// NOTE: Other GuiSlider*() controls use this one -int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) -{ - int result = 0; - GuiState state = guiState; - - float temp = (maxValue - minValue)/2.0f; - if (value == NULL) value = &temp; - float oldValue = *value; - - Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), - 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - state = STATE_PRESSED; - // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts - - if (!CheckCollisionPointRec(mousePoint, slider)) - { - // Get equivalent value and slider position from mousePosition.x - *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; - } - } - else state = STATE_FOCUSED; - } - - if (*value > maxValue) *value = maxValue; - else if (*value < minValue) *value = minValue; - } - - // Control value change check - if (oldValue == *value) result = 0; - else result = 1; - - // Slider bar limits check - float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); - if (sliderWidth > 0) // Slider - { - slider.x += sliderValue; - slider.width = (float)sliderWidth; - if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); - else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); - } - else if (sliderWidth == 0) // SliderBar - { - slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); - slider.width = sliderValue; - if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); - - // Draw slider internal bar (depends on state) - if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); - else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); - else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); - - // Draw left/right text if provided - if (textLeft != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); - } - - if (textRight != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); - } - //-------------------------------------------------------------------- - - return result; -} - -// Slider control extended, returns selected value and has text -int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); -} - -// Slider Bar control extended, returns selected value -int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); -} - -// Progress Bar control extended, shows current progress value -int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) -{ - int result = 0; - GuiState state = guiState; - - float temp = (maxValue - minValue)/2.0f; - if (value == NULL) value = &temp; - - // Progress bar - Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), - bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, - bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; - - // Update control - //-------------------------------------------------------------------- - if (*value > maxValue) *value = maxValue; - - // WARNING: Working with floats could lead to rounding issues - if ((state != STATE_DISABLED)) progress.width = (float)(*value/(maxValue - minValue))*bounds.width - ((*value >= maxValue)? (float)(2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)) : 0.0f); - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state == STATE_DISABLED) - { - GuiDrawRectangle(bounds, GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(PROGRESSBAR, BORDER + (state*3))), BLANK); - } - else - { - if (*value > minValue) - { - // Draw progress bar with colored border, more visual - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); - } - else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - - if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + 1, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); - else - { - // Draw borders not yet reached by value - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y + bounds.height - 1, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); - } - - // Draw slider internal progress bar (depends on state) - GuiDrawRectangle(progress, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BASE_COLOR_PRESSED))); - } - - // Draw left/right text if provided - if (textLeft != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textLeft); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); - } - - if (textRight != NULL) - { - Rectangle textBounds = { 0 }; - textBounds.width = (float)GetTextWidth(textRight); - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); - textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - - GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); - } - //-------------------------------------------------------------------- - - return result; -} - -// Status Bar control -int GuiStatusBar(Rectangle bounds, const char *text) -{ - int result = 0; - GuiState state = guiState; - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(STATUSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(STATUSBAR, BORDER + (state*3))), GetColor(GuiGetStyle(STATUSBAR, BASE + (state*3)))); - GuiDrawText(text, GetTextBounds(STATUSBAR, bounds), GuiGetStyle(STATUSBAR, TEXT_ALIGNMENT), GetColor(GuiGetStyle(STATUSBAR, TEXT + (state*3)))); - //-------------------------------------------------------------------- - - return result; -} - -// Dummy rectangle control, intended for placeholding -int GuiDummyRec(Rectangle bounds, const char *text) -{ - int result = 0; - GuiState state = guiState; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check button state - if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); - GuiDrawText(text, GetTextBounds(DEFAULT, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(BUTTON, (state != STATE_DISABLED)? TEXT_COLOR_NORMAL : TEXT_COLOR_DISABLED))); - //------------------------------------------------------------------ - - return result; -} - -// List View control -int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active) -{ - int result = 0; - int itemCount = 0; - const char **items = NULL; - - if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); - - result = GuiListViewEx(bounds, items, itemCount, scrollIndex, active, NULL); - - return result; -} - -// List View control with extended parameters -int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus) -{ - int result = 0; - GuiState state = guiState; - - int itemFocused = (focus == NULL)? -1 : *focus; - int itemSelected = (active == NULL)? -1 : *active; - - // Check if we need a scroll bar - bool useScrollBar = false; - if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING))*count > bounds.height) useScrollBar = true; - - // Define base item rectangle [0] - Rectangle itemBounds = { 0 }; - itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING); - itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); - itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); - itemBounds.height = (float)GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); - if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); - - // Get items on the list - int visibleItems = (int)bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); - if (visibleItems > count) visibleItems = count; - - int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; - if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; - int endIndex = startIndex + visibleItems; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - Vector2 mousePoint = GetMousePosition(); - - // Check mouse inside list view - if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - - // Check focused and selected item - for (int i = 0; i < visibleItems; i++) - { - if (CheckCollisionPointRec(mousePoint, itemBounds)) - { - itemFocused = startIndex + i; - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - if (itemSelected == (startIndex + i)) itemSelected = -1; - else itemSelected = startIndex + i; - } - break; - } - - // Update item rectangle y position for next item - itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); - } - - if (useScrollBar) - { - int wheelMove = (int)GetMouseWheelMove(); - startIndex -= wheelMove; - - if (startIndex < 0) startIndex = 0; - else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; - - endIndex = startIndex + visibleItems; - if (endIndex > count) endIndex = count; - } - } - else itemFocused = -1; - - // Reset item rectangle y to [0] - itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background - - // Draw visible items - for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) - { - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); - - if (state == STATE_DISABLED) - { - if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); - - GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); - } - else - { - if (((startIndex + i) == itemSelected) && (active != NULL)) - { - // Draw item selected - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); - GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); - } - else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! - { - // Draw item focused - GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); - GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); - } - else - { - // Draw item normal - GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); - } - } - - // Update item rectangle y position for next item - itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); - } - - if (useScrollBar) - { - Rectangle scrollBarBounds = { - bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), - bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), - bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - }; - - // Calculate percentage of visible items and apply same percentage to scrollbar - float percentVisible = (float)(endIndex - startIndex)/count; - float sliderSize = bounds.height*percentVisible; - - int prevSliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); // Save default slider size - int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)sliderSize); // Change slider size - GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed - - startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); - - GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, prevSliderSize); // Reset slider size to default - } - //-------------------------------------------------------------------- - - if (active != NULL) *active = itemSelected; - if (focus != NULL) *focus = itemFocused; - if (scrollIndex != NULL) *scrollIndex = startIndex; - - return result; -} - -// Color Panel control - Color (RGBA) variant. -int GuiColorPanel(Rectangle bounds, const char *text, Color *color) -{ - int result = 0; - - Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; - Vector3 hsv = ConvertRGBtoHSV(vcolor); - Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. - - GuiColorPanelHSV(bounds, text, &hsv); - - // Check if the hsv was changed, only then change the color. - // This is required, because the Color->HSV->Color conversion has precision errors. - // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. - // Otherwise GuiColorPanel would often modify it's color without user input. - // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. - if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) - { - Vector3 rgb = ConvertHSVtoRGB(hsv); - - // NOTE: Vector3ToColor() only available on raylib 1.8.1 - *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), - (unsigned char)(255.0f*rgb.y), - (unsigned char)(255.0f*rgb.z), - color->a }; - } - return result; -} - -// Color Bar Alpha control -// NOTE: Returns alpha value normalized [0..1] -int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) -{ - #if !defined(RAYGUI_COLORBARALPHA_CHECKED_SIZE) - #define RAYGUI_COLORBARALPHA_CHECKED_SIZE 10 - #endif - - int result = 0; - GuiState state = guiState; - Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - state = STATE_PRESSED; - - *alpha = (mousePoint.x - bounds.x)/bounds.width; - if (*alpha <= 0.0f) *alpha = 0.0f; - if (*alpha >= 1.0f) *alpha = 1.0f; - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts - - *alpha = (mousePoint.x - bounds.x)/bounds.width; - if (*alpha <= 0.0f) *alpha = 0.0f; - if (*alpha >= 1.0f) *alpha = 1.0f; - //selector.x = bounds.x + (int)(((alpha - 0)/(100 - 0))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))) - selector.width/2; - } - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - - // Draw alpha bar: checked background - if (state != STATE_DISABLED) - { - int checksX = (int)bounds.width/RAYGUI_COLORBARALPHA_CHECKED_SIZE; - int checksY = (int)bounds.height/RAYGUI_COLORBARALPHA_CHECKED_SIZE; - - for (int x = 0; x < checksX; x++) - { - for (int y = 0; y < checksY; y++) - { - Rectangle check = { bounds.x + x*RAYGUI_COLORBARALPHA_CHECKED_SIZE, bounds.y + y*RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE }; - GuiDrawRectangle(check, 0, BLANK, ((x + y)%2)? Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.4f) : Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.4f)); - } - } - - DrawRectangleGradientEx(bounds, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha)); - } - else DrawRectangleGradientEx(bounds, Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); - - GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); - - // Draw alpha bar: selector - GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); - //-------------------------------------------------------------------- - - return result; -} - -// Color Bar Hue control -// Returns hue value normalized [0..1] -// NOTE: Other similar bars (for reference): -// Color GuiColorBarSat() [WHITE->color] -// Color GuiColorBarValue() [BLACK->color], HSV/HSL -// float GuiColorBarLuminance() [BLACK->WHITE] -int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) -{ - int result = 0; - GuiState state = guiState; - Rectangle selector = { (float)bounds.x - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)bounds.y + (*hue)/360.0f*bounds.height - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2, (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT) }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - state = STATE_PRESSED; - - *hue = (mousePoint.y - bounds.y)*360/bounds.height; - if (*hue <= 0.0f) *hue = 0.0f; - if (*hue >= 359.0f) *hue = 359.0f; - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts - - *hue = (mousePoint.y - bounds.y)*360/bounds.height; - if (*hue <= 0.0f) *hue = 0.0f; - if (*hue >= 359.0f) *hue = 359.0f; - - } - else state = STATE_FOCUSED; - - /*if (IsKeyDown(KEY_UP)) - { - hue -= 2.0f; - if (hue <= 0.0f) hue = 0.0f; - } - else if (IsKeyDown(KEY_DOWN)) - { - hue += 2.0f; - if (hue >= 360.0f) hue = 360.0f; - }*/ - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state != STATE_DISABLED) - { - // Draw hue bar:color bars - // TODO: Use directly DrawRectangleGradientEx(bounds, color1, color2, color2, color1); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + bounds.height/6), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 2*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 3*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 4*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha)); - DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 5*(bounds.height/6)), (int)bounds.width, (int)(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha)); - } - else DrawRectangleGradientV((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); - - GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); - - // Draw hue bar: selector - GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); - //-------------------------------------------------------------------- - - return result; -} - -// Color Picker control -// NOTE: It's divided in multiple controls: -// Color GuiColorPanel(Rectangle bounds, Color color) -// float GuiColorBarAlpha(Rectangle bounds, float alpha) -// float GuiColorBarHue(Rectangle bounds, float value) -// NOTE: bounds define GuiColorPanel() size -// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead -int GuiColorPicker(Rectangle bounds, const char *text, Color *color) -{ - int result = 0; - - Color temp = { 200, 0, 0, 255 }; - if (color == NULL) color = &temp; - - GuiColorPanel(bounds, NULL, color); - - Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; - //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; - - // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. - Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); - - GuiColorBarHue(boundsHue, NULL, &hsv.x); - - //color.a = (unsigned char)(GuiColorBarAlpha(boundsAlpha, (float)color.a/255.0f)*255.0f); - Vector3 rgb = ConvertHSVtoRGB(hsv); - - *color = RAYGUI_CLITERAL(Color){ (unsigned char)roundf(rgb.x*255.0f), (unsigned char)roundf(rgb.y*255.0f), (unsigned char)roundf(rgb.z*255.0f), (*color).a }; - - return result; -} - -// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering. -// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB. -// NOTE: It's divided in multiple controls: -// int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) -// int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) -// float GuiColorBarHue(Rectangle bounds, float value) -// NOTE: bounds define GuiColorPanelHSV() size -int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) -{ - int result = 0; - - Vector3 tempHsv = { 0 }; - - if (colorHsv == NULL) - { - const Vector3 tempColor = { 200.0f/255.0f, 0.0f, 0.0f }; - tempHsv = ConvertRGBtoHSV(tempColor); - colorHsv = &tempHsv; - } - - GuiColorPanelHSV(bounds, NULL, colorHsv); - - const Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; - - GuiColorBarHue(boundsHue, NULL, &colorHsv->x); - - return result; -} - -// Color Panel control - HSV variant -int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) -{ - int result = 0; - GuiState state = guiState; - Vector2 pickerSelector = { 0 }; - - const Color colWhite = { 255, 255, 255, 255 }; - const Color colBlack = { 0, 0, 0, 255 }; - - pickerSelector.x = bounds.x + (float)colorHsv->y*bounds.width; // HSV: Saturation - pickerSelector.y = bounds.y + (1.0f - (float)colorHsv->z)*bounds.height; // HSV: Value - - Vector3 maxHue = { colorHsv->x, 1.0f, 1.0f }; - Vector3 rgbHue = ConvertHSVtoRGB(maxHue); - Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), - (unsigned char)(255.0f*rgbHue.y), - (unsigned char)(255.0f*rgbHue.z), 255 }; - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - pickerSelector = mousePoint; - - if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; - if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; - if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; - if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; - - // Calculate color from picker - Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; - - colorPick.x /= (float)bounds.width; // Get normalized value on x - colorPick.y /= (float)bounds.height; // Get normalized value on y - - colorHsv->y = colorPick.x; - colorHsv->z = 1.0f - colorPick.y; - - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds)) - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) - { - state = STATE_PRESSED; - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; - pickerSelector = mousePoint; - - // Calculate color from picker - Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; - - colorPick.x /= (float)bounds.width; // Get normalized value on x - colorPick.y /= (float)bounds.height; // Get normalized value on y - - colorHsv->y = colorPick.x; - colorHsv->z = 1.0f - colorPick.y; - } - else state = STATE_FOCUSED; - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state != STATE_DISABLED) - { - DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); - DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); - - // Draw color picker: selector - Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; - GuiDrawRectangle(selector, 0, BLANK, colWhite); - } - else - { - DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); - } - - GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); - //-------------------------------------------------------------------- - - return result; -} - -// Message Box control -int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons) -{ - #if !defined(RAYGUI_MESSAGEBOX_BUTTON_HEIGHT) - #define RAYGUI_MESSAGEBOX_BUTTON_HEIGHT 24 - #endif - #if !defined(RAYGUI_MESSAGEBOX_BUTTON_PADDING) - #define RAYGUI_MESSAGEBOX_BUTTON_PADDING 12 - #endif - - int result = -1; // Returns clicked button from buttons list, 0 refers to closed window button - - int buttonCount = 0; - const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); - Rectangle buttonBounds = { 0 }; - buttonBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; - buttonBounds.y = bounds.y + bounds.height - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT - RAYGUI_MESSAGEBOX_BUTTON_PADDING; - buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; - buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; - - //int textWidth = GetTextWidth(message) + 2; - - Rectangle textBounds = { 0 }; - textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; - textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; - textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; - textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; - - // Draw control - //-------------------------------------------------------------------- - if (GuiWindowBox(bounds, title)) result = 0; - - int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(textBounds, message); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); - - prevTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - for (int i = 0; i < buttonCount; i++) - { - if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; - buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); - } - - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevTextAlignment); - //-------------------------------------------------------------------- - - return result; -} - -// Text Input Box control, ask for text -int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive) -{ - #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT) - #define RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT 24 - #endif - #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_PADDING) - #define RAYGUI_TEXTINPUTBOX_BUTTON_PADDING 12 - #endif - #if !defined(RAYGUI_TEXTINPUTBOX_HEIGHT) - #define RAYGUI_TEXTINPUTBOX_HEIGHT 26 - #endif - - // Used to enable text edit mode - // WARNING: No more than one GuiTextInputBox() should be open at the same time - static bool textEditMode = false; - - int result = -1; - - int buttonCount = 0; - const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); - Rectangle buttonBounds = { 0 }; - buttonBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - buttonBounds.y = bounds.y + bounds.height - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - buttonBounds.width = (bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; - buttonBounds.height = RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT; - - int messageInputHeight = (int)bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - 2*RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - - Rectangle textBounds = { 0 }; - if (message != NULL) - { - int textSize = GetTextWidth(message) + 2; - - textBounds.x = bounds.x + bounds.width/2 - textSize/2; - textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; - textBounds.width = (float)textSize; - textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - } - - Rectangle textBoxBounds = { 0 }; - textBoxBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - textBoxBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - RAYGUI_TEXTINPUTBOX_HEIGHT/2; - if (message == NULL) textBoxBounds.y = bounds.y + 24 + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; - else textBoxBounds.y += (messageInputHeight/2 + messageInputHeight/4); - textBoxBounds.width = bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*2; - textBoxBounds.height = RAYGUI_TEXTINPUTBOX_HEIGHT; - - // Draw control - //-------------------------------------------------------------------- - if (GuiWindowBox(bounds, title)) result = 0; - - // Draw message if available - if (message != NULL) - { - int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(textBounds, message); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); - } - - if (secretViewActive != NULL) - { - static char stars[] = "****************"; - if (GuiTextBox(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x, textBoxBounds.y, textBoxBounds.width - 4 - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.height }, - ((*secretViewActive == 1) || textEditMode)? text : stars, textMaxSize, textEditMode)) textEditMode = !textEditMode; - - GuiToggle(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x + textBoxBounds.width - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.y, RAYGUI_TEXTINPUTBOX_HEIGHT, RAYGUI_TEXTINPUTBOX_HEIGHT }, (*secretViewActive == 1)? "#44#" : "#45#", secretViewActive); - } - else - { - if (GuiTextBox(textBoxBounds, text, textMaxSize, textEditMode)) textEditMode = !textEditMode; - } - - int prevBtnTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - for (int i = 0; i < buttonCount; i++) - { - if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; - buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); - } - - if (result >= 0) textEditMode = false; - - GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevBtnTextAlignment); - //-------------------------------------------------------------------- - - return result; // Result is the pressed button index -} - -// Grid control -// NOTE: Returns grid mouse-hover selected cell -// About drawing lines at subpixel spacing, simple put, not easy solution: -// https://stackoverflow.com/questions/4435450/2d-opengl-drawing-lines-that-dont-exactly-fit-pixel-raster -int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell) -{ - // Grid lines alpha amount - #if !defined(RAYGUI_GRID_ALPHA) - #define RAYGUI_GRID_ALPHA 0.15f - #endif - - int result = 0; - GuiState state = guiState; - - Vector2 mousePoint = GetMousePosition(); - Vector2 currentMouseCell = { -1, -1 }; - - float spaceWidth = spacing/(float)subdivs; - int linesV = (int)(bounds.width/spaceWidth) + 1; - int linesH = (int)(bounds.height/spaceWidth) + 1; - - int color = GuiGetStyle(DEFAULT, LINE_COLOR); - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) - { - if (CheckCollisionPointRec(mousePoint, bounds)) - { - // NOTE: Cell values must be the upper left of the cell the mouse is in - currentMouseCell.x = floorf((mousePoint.x - bounds.x)/spacing); - currentMouseCell.y = floorf((mousePoint.y - bounds.y)/spacing); - } - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - if (state == STATE_DISABLED) color = GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED); - - if (subdivs > 0) - { - // Draw vertical grid lines - for (int i = 0; i < linesV; i++) - { - Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; - GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); - } - - // Draw horizontal grid lines - for (int i = 0; i < linesH; i++) - { - Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; - GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); - } - } - - if (mouseCell != NULL) *mouseCell = currentMouseCell; - return result; -} - -//---------------------------------------------------------------------------------- -// Tooltip management functions -// NOTE: Tooltips requires some global variables: tooltipPtr -//---------------------------------------------------------------------------------- -// Enable gui tooltips (global state) -void GuiEnableTooltip(void) { guiTooltip = true; } - -// Disable gui tooltips (global state) -void GuiDisableTooltip(void) { guiTooltip = false; } - -// Set tooltip string -void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } - -//---------------------------------------------------------------------------------- -// Styles loading functions -//---------------------------------------------------------------------------------- - -// Load raygui style file (.rgs) -// NOTE: By default a binary file is expected, that file could contain a custom font, -// in that case, custom font image atlas is GRAY+ALPHA and pixel data can be compressed (DEFLATE) -void GuiLoadStyle(const char *fileName) -{ - #define MAX_LINE_BUFFER_SIZE 256 - - bool tryBinary = false; - if (!guiStyleLoaded) GuiLoadStyleDefault(); - - // Try reading the files as text file first - FILE *rgsFile = fopen(fileName, "rt"); - - if (rgsFile != NULL) - { - char buffer[MAX_LINE_BUFFER_SIZE] = { 0 }; - fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); - - if (buffer[0] == '#') - { - int controlId = 0; - int propertyId = 0; - unsigned int propertyValue = 0; - - while (!feof(rgsFile)) - { - switch (buffer[0]) - { - case 'p': - { - // Style property: p - - sscanf(buffer, "p %d %d 0x%x", &controlId, &propertyId, &propertyValue); - GuiSetStyle(controlId, propertyId, (int)propertyValue); - - } break; - case 'f': - { - // Style font: f - - int fontSize = 0; - char charmapFileName[256] = { 0 }; - char fontFileName[256] = { 0 }; - sscanf(buffer, "f %d %s %[^\r\n]s", &fontSize, charmapFileName, fontFileName); - - Font font = { 0 }; - int *codepoints = NULL; - int codepointCount = 0; - - if (charmapFileName[0] != '0') - { - // Load text data from file - // NOTE: Expected an UTF-8 array of codepoints, no separation - char *textData = LoadFileText(TextFormat("%s/%s", GetDirectoryPath(fileName), charmapFileName)); - codepoints = LoadCodepoints(textData, &codepointCount); - UnloadFileText(textData); - } - - if (fontFileName[0] != '\0') - { - // In case a font is already loaded and it is not default internal font, unload it - if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); - - if (codepointCount > 0) font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, codepoints, codepointCount); - else font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, NULL, 0); // Default to 95 standard codepoints - } - - // If font texture not properly loaded, revert to default font and size/spacing - if (font.texture.id == 0) - { - font = GetFontDefault(); - GuiSetStyle(DEFAULT, TEXT_SIZE, 10); - GuiSetStyle(DEFAULT, TEXT_SPACING, 1); - } - - UnloadCodepoints(codepoints); - - if ((font.texture.id > 0) && (font.glyphCount > 0)) GuiSetFont(font); - - } break; - default: break; - } - - fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); - } - } - else tryBinary = true; - - fclose(rgsFile); - } - - if (tryBinary) - { - rgsFile = fopen(fileName, "rb"); - - if (rgsFile != NULL) - { - fseek(rgsFile, 0, SEEK_END); - int fileDataSize = ftell(rgsFile); - fseek(rgsFile, 0, SEEK_SET); - - if (fileDataSize > 0) - { - unsigned char *fileData = (unsigned char *)RAYGUI_MALLOC(fileDataSize*sizeof(unsigned char)); - fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); - - GuiLoadStyleFromMemory(fileData, fileDataSize); - - RAYGUI_FREE(fileData); - } - - fclose(rgsFile); - } - } -} - -// Load style default over global style -void GuiLoadStyleDefault(void) -{ - // We set this variable first to avoid cyclic function calls - // when calling GuiSetStyle() and GuiGetStyle() - guiStyleLoaded = true; - - // Initialize default LIGHT style property values - // WARNING: Default value are applied to all controls on set but - // they can be overwritten later on for every custom control - GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, 0x838383ff); - GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, 0xc9c9c9ff); - GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, 0x686868ff); - GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, 0x5bb2d9ff); - GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, 0xc9effeff); - GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, 0x6c9bbcff); - GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, 0x0492c7ff); - GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, 0x97e8ffff); - GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, 0x368bafff); - GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, 0xb5c1c2ff); - GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, 0xe6e9e9ff); - GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, 0xaeb7b8ff); - GuiSetStyle(DEFAULT, BORDER_WIDTH, 1); - GuiSetStyle(DEFAULT, TEXT_PADDING, 0); - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - - // Initialize default extended property values - // NOTE: By default, extended property values are initialized to 0 - GuiSetStyle(DEFAULT, TEXT_SIZE, 10); // DEFAULT, shared by all controls - GuiSetStyle(DEFAULT, TEXT_SPACING, 1); // DEFAULT, shared by all controls - GuiSetStyle(DEFAULT, LINE_COLOR, 0x90abb5ff); // DEFAULT specific property - GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0xf5f5f5ff); // DEFAULT specific property - GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, 15); // DEFAULT, 15 pixels between lines - GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); // DEFAULT, text aligned vertically to middle of text-bounds - - // Initialize control-specific property values - // NOTE: Those properties are in default list but require specific values by control type - GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(BUTTON, BORDER_WIDTH, 2); - GuiSetStyle(SLIDER, TEXT_PADDING, 4); - GuiSetStyle(PROGRESSBAR, TEXT_PADDING, 4); - GuiSetStyle(CHECKBOX, TEXT_PADDING, 4); - GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, TEXT_ALIGN_RIGHT); - GuiSetStyle(DROPDOWNBOX, TEXT_PADDING, 0); - GuiSetStyle(DROPDOWNBOX, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiSetStyle(TEXTBOX, TEXT_PADDING, 4); - GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); - GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(SPINNER, TEXT_PADDING, 0); - GuiSetStyle(SPINNER, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); - GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); - - // Initialize extended property values - // NOTE: By default, extended property values are initialized to 0 - GuiSetStyle(TOGGLE, GROUP_PADDING, 2); - GuiSetStyle(SLIDER, SLIDER_WIDTH, 16); - GuiSetStyle(SLIDER, SLIDER_PADDING, 1); - GuiSetStyle(PROGRESSBAR, PROGRESS_PADDING, 1); - GuiSetStyle(CHECKBOX, CHECK_PADDING, 1); - GuiSetStyle(COMBOBOX, COMBO_BUTTON_WIDTH, 32); - GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); - GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); - GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); - GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 24); - GuiSetStyle(SPINNER, SPIN_BUTTON_SPACING, 2); - GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); - GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); - GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, 0); - GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, 16); - GuiSetStyle(SCROLLBAR, SCROLL_PADDING, 0); - GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); - GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); - GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); - GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); - GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); - GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); - GuiSetStyle(COLORPICKER, HUEBAR_WIDTH, 16); - GuiSetStyle(COLORPICKER, HUEBAR_PADDING, 8); - GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT, 8); - GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW, 2); - - if (guiFont.texture.id != GetFontDefault().texture.id) - { - // Unload previous font texture - UnloadTexture(guiFont.texture); - RL_FREE(guiFont.recs); - RL_FREE(guiFont.glyphs); - guiFont.recs = NULL; - guiFont.glyphs = NULL; - - // Setup default raylib font - guiFont = GetFontDefault(); - - // NOTE: Default raylib font character 95 is a white square - Rectangle whiteChar = guiFont.recs[95]; - - // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering - SetShapesTexture(guiFont.texture, RAYGUI_CLITERAL(Rectangle){ whiteChar.x + 1, whiteChar.y + 1, whiteChar.width - 2, whiteChar.height - 2 }); - } -} - -// Get text with icon id prepended -// NOTE: Useful to add icons by name id (enum) instead of -// a number that can change between ricon versions -const char *GuiIconText(int iconId, const char *text) -{ -#if defined(RAYGUI_NO_ICONS) - return NULL; -#else - static char buffer[1024] = { 0 }; - static char iconBuffer[16] = { 0 }; - - if (text != NULL) - { - memset(buffer, 0, 1024); - sprintf(buffer, "#%03i#", iconId); - - for (int i = 5; i < 1024; i++) - { - buffer[i] = text[i - 5]; - if (text[i - 5] == '\0') break; - } - - return buffer; - } - else - { - sprintf(iconBuffer, "#%03i#", iconId); - - return iconBuffer; - } -#endif -} - -#if !defined(RAYGUI_NO_ICONS) -// Get full icons data pointer -unsigned int *GuiGetIcons(void) { return guiIconsPtr; } - -// Load raygui icons file (.rgi) -// NOTE: In case nameIds are required, they can be requested with loadIconsName, -// they are returned as a guiIconsName[iconCount][RAYGUI_ICON_MAX_NAME_LENGTH], -// WARNING: guiIconsName[]][] memory should be manually freed! -char **GuiLoadIcons(const char *fileName, bool loadIconsName) -{ - // Style File Structure (.rgi) - // ------------------------------------------------------ - // Offset | Size | Type | Description - // ------------------------------------------------------ - // 0 | 4 | char | Signature: "rGI " - // 4 | 2 | short | Version: 100 - // 6 | 2 | short | reserved - - // 8 | 2 | short | Num icons (N) - // 10 | 2 | short | Icons size (Options: 16, 32, 64) (S) - - // Icons name id (32 bytes per name id) - // foreach (icon) - // { - // 12+32*i | 32 | char | Icon NameId - // } - - // Icons data: One bit per pixel, stored as unsigned int array (depends on icon size) - // S*S pixels/32bit per unsigned int = K unsigned int per icon - // foreach (icon) - // { - // ... | K | unsigned int | Icon Data - // } - - FILE *rgiFile = fopen(fileName, "rb"); - - char **guiIconsName = NULL; - - if (rgiFile != NULL) - { - char signature[5] = { 0 }; - short version = 0; - short reserved = 0; - short iconCount = 0; - short iconSize = 0; - - fread(signature, 1, 4, rgiFile); - fread(&version, sizeof(short), 1, rgiFile); - fread(&reserved, sizeof(short), 1, rgiFile); - fread(&iconCount, sizeof(short), 1, rgiFile); - fread(&iconSize, sizeof(short), 1, rgiFile); - - if ((signature[0] == 'r') && - (signature[1] == 'G') && - (signature[2] == 'I') && - (signature[3] == ' ')) - { - if (loadIconsName) - { - guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); - for (int i = 0; i < iconCount; i++) - { - guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); - fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); - } - } - else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); - - // Read icons data directly over internal icons array - fread(guiIconsPtr, sizeof(unsigned int), iconCount*(iconSize*iconSize/32), rgiFile); - } - - fclose(rgiFile); - } - - return guiIconsName; -} - -// Draw selected icon using rectangles pixel-by-pixel -void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) -{ - #define BIT_CHECK(a,b) ((a) & (1u<<(b))) - - for (int i = 0, y = 0; i < RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32; i++) - { - for (int k = 0; k < 32; k++) - { - if (BIT_CHECK(guiIconsPtr[iconId*RAYGUI_ICON_DATA_ELEMENTS + i], k)) - { - #if !defined(RAYGUI_STANDALONE) - GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ (float)posX + (k%RAYGUI_ICON_SIZE)*pixelSize, (float)posY + y*pixelSize, (float)pixelSize, (float)pixelSize }, 0, BLANK, color); - #endif - } - - if ((k == 15) || (k == 31)) y++; - } - } -} - -// Set icon drawing size -void GuiSetIconScale(int scale) -{ - if (scale >= 1) guiIconScale = scale; -} - -#endif // !RAYGUI_NO_ICONS - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- - -// Load style from memory -// WARNING: Binary files only -static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) -{ - unsigned char *fileDataPtr = (unsigned char *)fileData; - - char signature[5] = { 0 }; - short version = 0; - short reserved = 0; - int propertyCount = 0; - - memcpy(signature, fileDataPtr, 4); - memcpy(&version, fileDataPtr + 4, sizeof(short)); - memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); - memcpy(&propertyCount, fileDataPtr + 4 + 2 + 2, sizeof(int)); - fileDataPtr += 12; - - if ((signature[0] == 'r') && - (signature[1] == 'G') && - (signature[2] == 'S') && - (signature[3] == ' ')) - { - short controlId = 0; - short propertyId = 0; - unsigned int propertyValue = 0; - - for (int i = 0; i < propertyCount; i++) - { - memcpy(&controlId, fileDataPtr, sizeof(short)); - memcpy(&propertyId, fileDataPtr + 2, sizeof(short)); - memcpy(&propertyValue, fileDataPtr + 2 + 2, sizeof(unsigned int)); - fileDataPtr += 8; - - if (controlId == 0) // DEFAULT control - { - // If a DEFAULT property is loaded, it is propagated to all controls - // NOTE: All DEFAULT properties should be defined first in the file - GuiSetStyle(0, (int)propertyId, propertyValue); - - if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int j = 1; j < RAYGUI_MAX_CONTROLS; j++) GuiSetStyle(j, (int)propertyId, propertyValue); - } - else GuiSetStyle((int)controlId, (int)propertyId, propertyValue); - } - - // Font loading is highly dependant on raylib API to load font data and image - -#if !defined(RAYGUI_STANDALONE) - // Load custom font if available - int fontDataSize = 0; - memcpy(&fontDataSize, fileDataPtr, sizeof(int)); - fileDataPtr += 4; - - if (fontDataSize > 0) - { - Font font = { 0 }; - int fontType = 0; // 0-Normal, 1-SDF - - memcpy(&font.baseSize, fileDataPtr, sizeof(int)); - memcpy(&font.glyphCount, fileDataPtr + 4, sizeof(int)); - memcpy(&fontType, fileDataPtr + 4 + 4, sizeof(int)); - fileDataPtr += 12; - - // Load font white rectangle - Rectangle fontWhiteRec = { 0 }; - memcpy(&fontWhiteRec, fileDataPtr, sizeof(Rectangle)); - fileDataPtr += 16; - - // Load font image parameters - int fontImageUncompSize = 0; - int fontImageCompSize = 0; - memcpy(&fontImageUncompSize, fileDataPtr, sizeof(int)); - memcpy(&fontImageCompSize, fileDataPtr + 4, sizeof(int)); - fileDataPtr += 8; - - Image imFont = { 0 }; - imFont.mipmaps = 1; - memcpy(&imFont.width, fileDataPtr, sizeof(int)); - memcpy(&imFont.height, fileDataPtr + 4, sizeof(int)); - memcpy(&imFont.format, fileDataPtr + 4 + 4, sizeof(int)); - fileDataPtr += 12; - - if ((fontImageCompSize > 0) && (fontImageCompSize != fontImageUncompSize)) - { - // Compressed font atlas image data (DEFLATE), it requires DecompressData() - int dataUncompSize = 0; - unsigned char *compData = (unsigned char *)RAYGUI_MALLOC(fontImageCompSize); - memcpy(compData, fileDataPtr, fontImageCompSize); - fileDataPtr += fontImageCompSize; - - imFont.data = DecompressData(compData, fontImageCompSize, &dataUncompSize); - - // Security check, dataUncompSize must match the provided fontImageUncompSize - if (dataUncompSize != fontImageUncompSize) RAYGUI_LOG("WARNING: Uncompressed font atlas image data could be corrupted"); - - RAYGUI_FREE(compData); - } - else - { - // Font atlas image data is not compressed - imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageUncompSize); - memcpy(imFont.data, fileDataPtr, fontImageUncompSize); - fileDataPtr += fontImageUncompSize; - } - - if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); - font.texture = LoadTextureFromImage(imFont); - - RAYGUI_FREE(imFont.data); - - // Validate font atlas texture was loaded correctly - if (font.texture.id != 0) - { - // Load font recs data - int recsDataSize = font.glyphCount*sizeof(Rectangle); - int recsDataCompressedSize = 0; - - // WARNING: Version 400 adds the compression size parameter - if (version >= 400) - { - // RGS files version 400 support compressed recs data - memcpy(&recsDataCompressedSize, fileDataPtr, sizeof(int)); - fileDataPtr += sizeof(int); - } - - if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) - { - // Recs data is compressed, uncompress it - unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_MALLOC(recsDataCompressedSize); - - memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); - fileDataPtr += recsDataCompressedSize; - - int recsDataUncompSize = 0; - font.recs = (Rectangle *)DecompressData(recsDataCompressed, recsDataCompressedSize, &recsDataUncompSize); - - // Security check, data uncompressed size must match the expected original data size - if (recsDataUncompSize != recsDataSize) RAYGUI_LOG("WARNING: Uncompressed font recs data could be corrupted"); - - RAYGUI_FREE(recsDataCompressed); - } - else - { - // Recs data is uncompressed - font.recs = (Rectangle *)RAYGUI_CALLOC(font.glyphCount, sizeof(Rectangle)); - for (int i = 0; i < font.glyphCount; i++) - { - memcpy(&font.recs[i], fileDataPtr, sizeof(Rectangle)); - fileDataPtr += sizeof(Rectangle); - } - } - - // Load font glyphs info data - int glyphsDataSize = font.glyphCount*16; // 16 bytes data per glyph - int glyphsDataCompressedSize = 0; - - // WARNING: Version 400 adds the compression size parameter - if (version >= 400) - { - // RGS files version 400 support compressed glyphs data - memcpy(&glyphsDataCompressedSize, fileDataPtr, sizeof(int)); - fileDataPtr += sizeof(int); - } - - // Allocate required glyphs space to fill with data - font.glyphs = (GlyphInfo *)RAYGUI_CALLOC(font.glyphCount, sizeof(GlyphInfo)); - - if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) - { - // Glyphs data is compressed, uncompress it - unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_MALLOC(glyphsDataCompressedSize); - - memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); - fileDataPtr += glyphsDataCompressedSize; - - int glyphsDataUncompSize = 0; - unsigned char *glyphsDataUncomp = DecompressData(glypsDataCompressed, glyphsDataCompressedSize, &glyphsDataUncompSize); - - // Security check, data uncompressed size must match the expected original data size - if (glyphsDataUncompSize != glyphsDataSize) RAYGUI_LOG("WARNING: Uncompressed font glyphs data could be corrupted"); - - unsigned char *glyphsDataUncompPtr = glyphsDataUncomp; - - for (int i = 0; i < font.glyphCount; i++) - { - memcpy(&font.glyphs[i].value, glyphsDataUncompPtr, sizeof(int)); - memcpy(&font.glyphs[i].offsetX, glyphsDataUncompPtr + 4, sizeof(int)); - memcpy(&font.glyphs[i].offsetY, glyphsDataUncompPtr + 8, sizeof(int)); - memcpy(&font.glyphs[i].advanceX, glyphsDataUncompPtr + 12, sizeof(int)); - glyphsDataUncompPtr += 16; - } - - RAYGUI_FREE(glypsDataCompressed); - RAYGUI_FREE(glyphsDataUncomp); - } - else - { - // Glyphs data is uncompressed - for (int i = 0; i < font.glyphCount; i++) - { - memcpy(&font.glyphs[i].value, fileDataPtr, sizeof(int)); - memcpy(&font.glyphs[i].offsetX, fileDataPtr + 4, sizeof(int)); - memcpy(&font.glyphs[i].offsetY, fileDataPtr + 8, sizeof(int)); - memcpy(&font.glyphs[i].advanceX, fileDataPtr + 12, sizeof(int)); - fileDataPtr += 16; - } - } - } - else font = GetFontDefault(); // Fallback in case of errors loading font atlas texture - - GuiSetFont(font); - - // Set font texture source rectangle to be used as white texture to draw shapes - // NOTE: It makes possible to draw shapes and text (full UI) in a single draw call - if ((fontWhiteRec.x > 0) && - (fontWhiteRec.y > 0) && - (fontWhiteRec.width > 0) && - (fontWhiteRec.height > 0)) SetShapesTexture(font.texture, fontWhiteRec); - } -#endif - } -} - -// Gui get text width considering icon -static int GetTextWidth(const char *text) -{ - #if !defined(ICON_TEXT_PADDING) - #define ICON_TEXT_PADDING 4 - #endif - - Vector2 textSize = { 0 }; - int textIconOffset = 0; - - if ((text != NULL) && (text[0] != '\0')) - { - if (text[0] == '#') - { - for (int i = 1; (i < 5) && (text[i] != '\0'); i++) - { - if (text[i] == '#') - { - textIconOffset = i; - break; - } - } - } - - text += textIconOffset; - - // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly - float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); - - // Custom MeasureText() implementation - if ((guiFont.texture.id > 0) && (text != NULL)) - { - // Get size in bytes of text, considering end of line and line break - int size = 0; - for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) - { - if ((text[i] != '\0') && (text[i] != '\n')) size++; - else break; - } - - float scaleFactor = fontSize/(float)guiFont.baseSize; - textSize.y = (float)guiFont.baseSize*scaleFactor; - float glyphWidth = 0.0f; - - for (int i = 0, codepointSize = 0; i < size; i += codepointSize) - { - int codepoint = GetCodepointNext(&text[i], &codepointSize); - int codepointIndex = GetGlyphIndex(guiFont, codepoint); - - if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); - else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); - - textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - } - - if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); - } - - return (int)textSize.x; -} - -// Get text bounds considering control bounds -static Rectangle GetTextBounds(int control, Rectangle bounds) -{ - Rectangle textBounds = bounds; - - textBounds.x = bounds.x + GuiGetStyle(control, BORDER_WIDTH); - textBounds.y = bounds.y + GuiGetStyle(control, BORDER_WIDTH) + GuiGetStyle(control, TEXT_PADDING); - textBounds.width = bounds.width - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); - textBounds.height = bounds.height - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); // NOTE: Text is processed line per line! - - // Depending on control, TEXT_PADDING and TEXT_ALIGNMENT properties could affect the text-bounds - switch (control) - { - case COMBOBOX: - case DROPDOWNBOX: - case LISTVIEW: - // TODO: Special cases (no label): COMBOBOX, DROPDOWNBOX, LISTVIEW - case SLIDER: - case CHECKBOX: - case VALUEBOX: - case SPINNER: - // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER - default: - { - // TODO: WARNING: TEXT_ALIGNMENT is already considered in GuiDrawText() - if (GuiGetStyle(control, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT) textBounds.x -= GuiGetStyle(control, TEXT_PADDING); - else textBounds.x += GuiGetStyle(control, TEXT_PADDING); - } - break; - } - - return textBounds; -} - -// Get text icon if provided and move text cursor -// NOTE: We support up to 999 values for iconId -static const char *GetTextIcon(const char *text, int *iconId) -{ -#if !defined(RAYGUI_NO_ICONS) - *iconId = -1; - if (text[0] == '#') // Maybe we have an icon! - { - char iconValue[4] = { 0 }; // Maximum length for icon value: 3 digits + '\0' - - int pos = 1; - while ((pos < 4) && (text[pos] >= '0') && (text[pos] <= '9')) - { - iconValue[pos - 1] = text[pos]; - pos++; - } - - if (text[pos] == '#') - { - *iconId = TextToInteger(iconValue); - - // Move text pointer after icon - // WARNING: If only icon provided, it could point to EOL character: '\0' - if (*iconId >= 0) text += (pos + 1); - } - } -#endif - - return text; -} - -// Get text divided into lines (by line-breaks '\n') -const char **GetTextLines(const char *text, int *count) -{ - #define RAYGUI_MAX_TEXT_LINES 128 - - static const char *lines[RAYGUI_MAX_TEXT_LINES] = { 0 }; - for (int i = 0; i < RAYGUI_MAX_TEXT_LINES; i++) lines[i] = NULL; // Init NULL pointers to substrings - - int textSize = (int)strlen(text); - - lines[0] = text; - int len = 0; - *count = 1; - //int lineSize = 0; // Stores current line size, not returned - - for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) - { - if (text[i] == '\n') - { - //lineSize = len; - k++; - lines[k] = &text[i + 1]; // WARNING: next value is valid? - len = 0; - *count += 1; - } - else len++; - } - - //lines[*count - 1].size = len; - - return lines; -} - -// Get text width to next space for provided string -static float GetNextSpaceWidth(const char *text, int *nextSpaceIndex) -{ - float width = 0; - int codepointByteCount = 0; - int codepoint = 0; - int index = 0; - float glyphWidth = 0; - float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; - - for (int i = 0; text[i] != '\0'; i++) - { - if (text[i] != ' ') - { - codepoint = GetCodepoint(&text[i], &codepointByteCount); - index = GetGlyphIndex(guiFont, codepoint); - glyphWidth = (guiFont.glyphs[index].advanceX == 0)? guiFont.recs[index].width*scaleFactor : guiFont.glyphs[index].advanceX*scaleFactor; - width += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - else - { - *nextSpaceIndex = i; - break; - } - } - - return width; -} - -// Gui draw text using default font -static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint) -{ - #define TEXT_VALIGN_PIXEL_OFFSET(h) ((int)h%2) // Vertical alignment for pixel perfect - - #if !defined(ICON_TEXT_PADDING) - #define ICON_TEXT_PADDING 4 - #endif - - if ((text == NULL) || (text[0] == '\0')) return; // Security check - - // PROCEDURE: - // - Text is processed line per line - // - For every line, horizontal alignment is defined - // - For all text, vertical alignment is defined (multiline text only) - // - For every line, wordwrap mode is checked (useful for GuitextBox(), read-only) - - // Get text lines (using '\n' as delimiter) to be processed individually - // WARNING: We can't use GuiTextSplit() function because it can be already used - // before the GuiDrawText() call and its buffer is static, it would be overriden :( - int lineCount = 0; - const char **lines = GetTextLines(text, &lineCount); - - // Text style variables - //int alignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT); - int alignmentVertical = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL); - int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); // Wrap-mode only available in read-only mode, no for text editing - - // TODO: WARNING: This totalHeight is not valid for vertical alignment in case of word-wrap - float totalHeight = (float)(lineCount*GuiGetStyle(DEFAULT, TEXT_SIZE) + (lineCount - 1)*GuiGetStyle(DEFAULT, TEXT_SIZE)/2); - float posOffsetY = 0.0f; - - for (int i = 0; i < lineCount; i++) - { - int iconId = 0; - lines[i] = GetTextIcon(lines[i], &iconId); // Check text for icon and move cursor - - // Get text position depending on alignment and iconId - //--------------------------------------------------------------------------------- - Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; - float textBoundsWidthOffset = 0.0f; - - // NOTE: We get text size after icon has been processed - // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? - int textSizeX = GetTextWidth(lines[i]); - - // If text requires an icon, add size to measure - if (iconId >= 0) - { - textSizeX += RAYGUI_ICON_SIZE*guiIconScale; - - // WARNING: If only icon provided, text could be pointing to EOF character: '\0' -#if !defined(RAYGUI_NO_ICONS) - if ((lines[i] != NULL) && (lines[i][0] != '\0')) textSizeX += ICON_TEXT_PADDING; -#endif - } - - // Check guiTextAlign global variables - switch (alignment) - { - case TEXT_ALIGN_LEFT: textBoundsPosition.x = textBounds.x; break; - case TEXT_ALIGN_CENTER: textBoundsPosition.x = textBounds.x + textBounds.width/2 - textSizeX/2; break; - case TEXT_ALIGN_RIGHT: textBoundsPosition.x = textBounds.x + textBounds.width - textSizeX; break; - default: break; - } - - if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; - - switch (alignmentVertical) - { - // Only valid in case of wordWrap = 0; - case TEXT_ALIGN_TOP: textBoundsPosition.y = textBounds.y + posOffsetY; break; - case TEXT_ALIGN_MIDDLE: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height/2 - totalHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; - case TEXT_ALIGN_BOTTOM: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height - totalHeight + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; - default: break; - } - - // NOTE: Make sure we get pixel-perfect coordinates, - // In case of decimals we got weird text positioning - textBoundsPosition.x = (float)((int)textBoundsPosition.x); - textBoundsPosition.y = (float)((int)textBoundsPosition.y); - //--------------------------------------------------------------------------------- - - // Draw text (with icon if available) - //--------------------------------------------------------------------------------- -#if !defined(RAYGUI_NO_ICONS) - if (iconId >= 0) - { - // NOTE: We consider icon height, probably different than text size - GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); - textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); - textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); - } -#endif - // Get size in bytes of text, - // considering end of line and line break - int lineSize = 0; - for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } - float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; - - int lastSpaceIndex = 0; - bool tempWrapCharMode = false; - - int textOffsetY = 0; - float textOffsetX = 0.0f; - float glyphWidth = 0; - - int ellipsisWidth = GetTextWidth("..."); - bool textOverflow = false; - for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) - { - int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); - int index = GetGlyphIndex(guiFont, codepoint); - - // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) - // but we need to draw all of the bad bytes using the '?' symbol moving one byte - if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size - - // Get glyph width to check if it goes out of bounds - if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); - else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; - - // Wrap mode text measuring, to validate if - // it can be drawn or a new line is required - if (wrapMode == TEXT_WRAP_CHAR) - { - // Jump to next line if current character reach end of the box limits - if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) - { - textOffsetX = 0.0f; - textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); - - if (tempWrapCharMode) // Wrap at char level when too long words - { - wrapMode = TEXT_WRAP_WORD; - tempWrapCharMode = false; - } - } - } - else if (wrapMode == TEXT_WRAP_WORD) - { - if (codepoint == 32) lastSpaceIndex = c; - - // Get width to next space in line - int nextSpaceIndex = 0; - float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); - - int nextSpaceIndex2 = 0; - float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); - - if (nextWordSize > textBounds.width - textBoundsWidthOffset) - { - // Considering the case the next word is longer than bounds - tempWrapCharMode = true; - wrapMode = TEXT_WRAP_CHAR; - } - else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) - { - textOffsetX = 0.0f; - textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); - } - } - - if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint - else - { - // TODO: There are multiple types of spaces in Unicode, - // maybe it's a good idea to add support for more: http://jkorpela.fi/chars/spaces.html - if ((codepoint != ' ') && (codepoint != '\t')) // Do not draw codepoints with no glyph - { - if (wrapMode == TEXT_WRAP_NONE) - { - // Draw only required text glyphs fitting the textBounds.width - if (textSizeX > textBounds.width) - { - if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) - { - DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - else if (!textOverflow) - { - textOverflow = true; - - for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) - { - DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - } - } - else - { - DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - } - else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) - { - // Draw only glyphs inside the bounds - if ((textBoundsPosition.y + textOffsetY) <= (textBounds.y + textBounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) - { - DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); - } - } - } - - if (guiFont.glyphs[index].advanceX == 0) textOffsetX += ((float)guiFont.recs[index].width*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - else textOffsetX += ((float)guiFont.glyphs[index].advanceX*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - } - } - - if (wrapMode == TEXT_WRAP_NONE) posOffsetY += (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); - else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) posOffsetY += (textOffsetY + (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING)); - //--------------------------------------------------------------------------------- - } - -#if defined(RAYGUI_DEBUG_TEXT_BOUNDS) - GuiDrawRectangle(textBounds, 0, WHITE, Fade(BLUE, 0.4f)); -#endif -} - -// Gui draw rectangle using default raygui plain style with borders -static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color) -{ - if (color.a > 0) - { - // Draw rectangle filled with color - DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, GuiFade(color, guiAlpha)); - } - - if (borderWidth > 0) - { - // Draw rectangle border lines with color - DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); - DrawRectangle((int)rec.x, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); - DrawRectangle((int)rec.x + (int)rec.width - borderWidth, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); - DrawRectangle((int)rec.x, (int)rec.y + (int)rec.height - borderWidth, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); - } - -#if defined(RAYGUI_DEBUG_RECS_BOUNDS) - DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, Fade(RED, 0.4f)); -#endif -} - -// Draw tooltip using control bounds -static void GuiTooltip(Rectangle controlRec) -{ - if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) - { - Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); - - if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); - - GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, NULL); - - int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); - int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); - GuiSetStyle(LABEL, TEXT_PADDING, 0); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); - GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, guiTooltipPtr); - GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); - GuiSetStyle(LABEL, TEXT_PADDING, textPadding); - } -} - -// Split controls text into multiple strings -// Also check for multiple columns (required by GuiToggleGroup()) -static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow) -{ - // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) - // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, - // all used memory is static... it has some limitations: - // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS - // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE - // NOTE: Those definitions could be externally provided if required - - // TODO: HACK: GuiTextSplit() - Review how textRows are returned to user - // textRow is an externally provided array of integers that stores row number for every splitted string - - #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) - #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 - #endif - #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) - #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 - #endif - - static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; // String pointers array (points to buffer data) - static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; // Buffer data (text input copy with '\0' added) - memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); - - result[0] = buffer; - int counter = 1; - - if (textRow != NULL) textRow[0] = 0; - - // Count how many substrings we have on text and point to every one - for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) - { - buffer[i] = text[i]; - if (buffer[i] == '\0') break; - else if ((buffer[i] == delimiter) || (buffer[i] == '\n')) - { - result[counter] = buffer + i + 1; - - if (textRow != NULL) - { - if (buffer[i] == '\n') textRow[counter] = textRow[counter - 1] + 1; - else textRow[counter] = textRow[counter - 1]; - } - - buffer[i] = '\0'; // Set an end of string at this point - - counter++; - if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; - } - } - - *count = counter; - - return result; -} - -// Convert color data from RGB to HSV -// NOTE: Color data should be passed normalized -static Vector3 ConvertRGBtoHSV(Vector3 rgb) -{ - Vector3 hsv = { 0 }; - float min = 0.0f; - float max = 0.0f; - float delta = 0.0f; - - min = (rgb.x < rgb.y)? rgb.x : rgb.y; - min = (min < rgb.z)? min : rgb.z; - - max = (rgb.x > rgb.y)? rgb.x : rgb.y; - max = (max > rgb.z)? max : rgb.z; - - hsv.z = max; // Value - delta = max - min; - - if (delta < 0.00001f) - { - hsv.y = 0.0f; - hsv.x = 0.0f; // Undefined, maybe NAN? - return hsv; - } - - if (max > 0.0f) - { - // NOTE: If max is 0, this divide would cause a crash - hsv.y = (delta/max); // Saturation - } - else - { - // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined - hsv.y = 0.0f; - hsv.x = 0.0f; // Undefined, maybe NAN? - return hsv; - } - - // NOTE: Comparing float values could not work properly - if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta - else - { - if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow - else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan - } - - hsv.x *= 60.0f; // Convert to degrees - - if (hsv.x < 0.0f) hsv.x += 360.0f; - - return hsv; -} - -// Convert color data from HSV to RGB -// NOTE: Color data should be passed normalized -static Vector3 ConvertHSVtoRGB(Vector3 hsv) -{ - Vector3 rgb = { 0 }; - float hh = 0.0f, p = 0.0f, q = 0.0f, t = 0.0f, ff = 0.0f; - long i = 0; - - // NOTE: Comparing float values could not work properly - if (hsv.y <= 0.0f) - { - rgb.x = hsv.z; - rgb.y = hsv.z; - rgb.z = hsv.z; - return rgb; - } - - hh = hsv.x; - if (hh >= 360.0f) hh = 0.0f; - hh /= 60.0f; - - i = (long)hh; - ff = hh - i; - p = hsv.z*(1.0f - hsv.y); - q = hsv.z*(1.0f - (hsv.y*ff)); - t = hsv.z*(1.0f - (hsv.y*(1.0f - ff))); - - switch (i) - { - case 0: - { - rgb.x = hsv.z; - rgb.y = t; - rgb.z = p; - } break; - case 1: - { - rgb.x = q; - rgb.y = hsv.z; - rgb.z = p; - } break; - case 2: - { - rgb.x = p; - rgb.y = hsv.z; - rgb.z = t; - } break; - case 3: - { - rgb.x = p; - rgb.y = q; - rgb.z = hsv.z; - } break; - case 4: - { - rgb.x = t; - rgb.y = p; - rgb.z = hsv.z; - } break; - case 5: - default: - { - rgb.x = hsv.z; - rgb.y = p; - rgb.z = q; - } break; - } - - return rgb; -} - -// Scroll bar control (used by GuiScrollPanel()) -static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) -{ - GuiState state = guiState; - - // Is the scrollbar horizontal or vertical? - bool isVertical = (bounds.width > bounds.height)? false : true; - - // The size (width or height depending on scrollbar type) of the spinner buttons - const int spinnerSize = GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)? - (isVertical? (int)bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) : - (int)bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)) : 0; - - // Arrow buttons [<] [>] [∧] [∨] - Rectangle arrowUpLeft = { 0 }; - Rectangle arrowDownRight = { 0 }; - - // Actual area of the scrollbar excluding the arrow buttons - Rectangle scrollbar = { 0 }; - - // Slider bar that moves --[///]----- - Rectangle slider = { 0 }; - - // Normalize value - if (value > maxValue) value = maxValue; - if (value < minValue) value = minValue; - - int valueRange = maxValue - minValue; - if (valueRange <= 0) valueRange = 1; - - int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); - if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size - - // Calculate rectangles for all of the components - arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ - (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), - (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), - (float)spinnerSize, (float)spinnerSize }; - - if (isVertical) - { - arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; - scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; - - // Make sure the slider won't get outside of the scrollbar - sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; - slider = RAYGUI_CLITERAL(Rectangle){ - bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), - scrollbar.y + (int)(((float)(value - minValue)/valueRange)*(scrollbar.height - sliderSize)), - bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), - (float)sliderSize }; - } - else // horizontal - { - arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; - scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)) }; - - // Make sure the slider won't get outside of the scrollbar - sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; - slider = RAYGUI_CLITERAL(Rectangle){ - scrollbar.x + (int)(((float)(value - minValue)/valueRange)*(scrollbar.width - sliderSize)), - bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), - (float)sliderSize, - bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; - } - - // Update control - //-------------------------------------------------------------------- - if ((state != STATE_DISABLED) && !guiLocked) - { - Vector2 mousePoint = GetMousePosition(); - - if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds - { - if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && - !CheckCollisionPointRec(mousePoint, arrowUpLeft) && - !CheckCollisionPointRec(mousePoint, arrowDownRight)) - { - if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) - { - state = STATE_PRESSED; - - if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); - else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); - } - } - else - { - guiControlExclusiveMode = false; - guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; - } - } - else if (CheckCollisionPointRec(mousePoint, bounds)) - { - state = STATE_FOCUSED; - - // Handle mouse wheel - int wheel = (int)GetMouseWheelMove(); - if (wheel != 0) value += wheel; - - // Handle mouse button down - if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) - { - guiControlExclusiveMode = true; - guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts - - // Check arrows click - if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); - else if (!CheckCollisionPointRec(mousePoint, slider)) - { - // If click on scrollbar position but not on slider, place slider directly on that position - if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); - else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); - } - - state = STATE_PRESSED; - } - - // Keyboard control on mouse hover scrollbar - /* - if (isVertical) - { - if (IsKeyDown(KEY_DOWN)) value += 5; - else if (IsKeyDown(KEY_UP)) value -= 5; - } - else - { - if (IsKeyDown(KEY_RIGHT)) value += 5; - else if (IsKeyDown(KEY_LEFT)) value -= 5; - } - */ - } - - // Normalize value - if (value > maxValue) value = maxValue; - if (value < minValue) value = minValue; - } - //-------------------------------------------------------------------- - - // Draw control - //-------------------------------------------------------------------- - GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED))); // Draw the background - - GuiDrawRectangle(scrollbar, 0, BLANK, GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL))); // Draw the scrollbar active area background - GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BORDER + state*3))); // Draw the slider bar - - // Draw arrows (using icon if available) - if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) - { -#if defined(RAYGUI_NO_ICONS) - GuiDrawText(isVertical? "^" : "<", - RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); - GuiDrawText(isVertical? "v" : ">", - RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); -#else - GuiDrawText(isVertical? "#121#" : "#118#", - RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_UP_FILL / ICON_ARROW_LEFT_FILL - GuiDrawText(isVertical? "#120#" : "#119#", - RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, - TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_DOWN_FILL / ICON_ARROW_RIGHT_FILL -#endif - } - //-------------------------------------------------------------------- - - return value; -} - -// Color fade-in or fade-out, alpha goes from 0.0f to 1.0f -// WARNING: It multiplies current alpha by alpha scale factor -static Color GuiFade(Color color, float alpha) -{ - if (alpha < 0.0f) alpha = 0.0f; - else if (alpha > 1.0f) alpha = 1.0f; - - Color result = { color.r, color.g, color.b, (unsigned char)(color.a*alpha) }; - - return result; -} - -#if defined(RAYGUI_STANDALONE) -// Returns a Color struct from hexadecimal value -static Color GetColor(int hexValue) -{ - Color color; - - color.r = (unsigned char)(hexValue >> 24) & 0xFF; - color.g = (unsigned char)(hexValue >> 16) & 0xFF; - color.b = (unsigned char)(hexValue >> 8) & 0xFF; - color.a = (unsigned char)hexValue & 0xFF; - - return color; -} - -// Returns hexadecimal value for a Color -static int ColorToInt(Color color) -{ - return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); -} - -// Check if point is inside rectangle -static bool CheckCollisionPointRec(Vector2 point, Rectangle rec) -{ - bool collision = false; - - if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && - (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; - - return collision; -} - -// Formatting of text with variables to 'embed' -static const char *TextFormat(const char *text, ...) -{ - #if !defined(RAYGUI_TEXTFORMAT_MAX_SIZE) - #define RAYGUI_TEXTFORMAT_MAX_SIZE 256 - #endif - - static char buffer[RAYGUI_TEXTFORMAT_MAX_SIZE]; - - va_list args; - va_start(args, text); - vsprintf(buffer, text, args); - va_end(args); - - return buffer; -} - -// Draw rectangle with vertical gradient fill color -// NOTE: This function is only used by GuiColorPicker() -static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) -{ - Rectangle bounds = { (float)posX, (float)posY, (float)width, (float)height }; - DrawRectangleGradientEx(bounds, color1, color2, color2, color1); -} - -// Split string into multiple strings -const char **TextSplit(const char *text, char delimiter, int *count) -{ - // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) - // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, - // all used memory is static... it has some limitations: - // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS - // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE - - #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) - #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 - #endif - #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) - #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 - #endif - - static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; - static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; - memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); - - result[0] = buffer; - int counter = 0; - - if (text != NULL) - { - counter = 1; - - // Count how many substrings we have on text and point to every one - for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) - { - buffer[i] = text[i]; - if (buffer[i] == '\0') break; - else if (buffer[i] == delimiter) - { - buffer[i] = '\0'; // Set an end of string at this point - result[counter] = buffer + i + 1; - counter++; - - if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; - } - } - } - - *count = counter; - return result; -} - -// Get integer value from text -// NOTE: This function replaces atoi() [stdlib.h] -static int TextToInteger(const char *text) -{ - int value = 0; - int sign = 1; - - if ((text[0] == '+') || (text[0] == '-')) - { - if (text[0] == '-') sign = -1; - text++; - } - - for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0'); - - return value*sign; -} - -// Get float value from text -// NOTE: This function replaces atof() [stdlib.h] -// WARNING: Only '.' character is understood as decimal point -static float TextToFloat(const char *text) -{ - float value = 0.0f; - float sign = 1.0f; - - if ((text[0] == '+') || (text[0] == '-')) - { - if (text[0] == '-') sign = -1.0f; - text++; - } - - int i = 0; - for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); - - if (text[i++] != '.') value *= sign; - else - { - float divisor = 10.0f; - for (; ((text[i] >= '0') && (text[i] <= '9')); i++) - { - value += ((float)(text[i] - '0'))/divisor; - divisor = divisor*10.0f; - } - } - - return value; -} - -// Encode codepoint into UTF-8 text (char array size returned as parameter) -static const char *CodepointToUTF8(int codepoint, int *byteSize) -{ - static char utf8[6] = { 0 }; - int size = 0; - - if (codepoint <= 0x7f) - { - utf8[0] = (char)codepoint; - size = 1; - } - else if (codepoint <= 0x7ff) - { - utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); - utf8[1] = (char)((codepoint & 0x3f) | 0x80); - size = 2; - } - else if (codepoint <= 0xffff) - { - utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); - utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); - utf8[2] = (char)((codepoint & 0x3f) | 0x80); - size = 3; - } - else if (codepoint <= 0x10ffff) - { - utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); - utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); - utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); - utf8[3] = (char)((codepoint & 0x3f) | 0x80); - size = 4; - } - - *byteSize = size; - - return utf8; -} - -// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found -// When a invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned -// Total number of bytes processed are returned as a parameter -// NOTE: the standard says U+FFFD should be returned in case of errors -// but that character is not supported by the default font in raylib -static int GetCodepointNext(const char *text, int *codepointSize) -{ - const char *ptr = text; - int codepoint = 0x3f; // Codepoint (defaults to '?') - *codepointSize = 1; - - // Get current codepoint and bytes processed - if (0xf0 == (0xf8 & ptr[0])) - { - // 4 byte UTF-8 codepoint - if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks - codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); - *codepointSize = 4; - } - else if (0xe0 == (0xf0 & ptr[0])) - { - // 3 byte UTF-8 codepoint */ - if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks - codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); - *codepointSize = 3; - } - else if (0xc0 == (0xe0 & ptr[0])) - { - // 2 byte UTF-8 codepoint - if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks - codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); - *codepointSize = 2; - } - else if (0x00 == (0x80 & ptr[0])) - { - // 1 byte UTF-8 codepoint - codepoint = ptr[0]; - *codepointSize = 1; - } - - return codepoint; -} -#endif // RAYGUI_STANDALONE - -#endif // RAYGUI_IMPLEMENTATION diff --git a/third_party/raylib/include/raylib.h b/third_party/raylib/include/raylib.h deleted file mode 100644 index ea973ff1bac..00000000000 --- a/third_party/raylib/include/raylib.h +++ /dev/null @@ -1,1766 +0,0 @@ -/********************************************************************************************** -* -* raylib v5.6-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) -* -* FEATURES: -* - NO external dependencies, all required libraries included with raylib -* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, -* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5. -* - Written in plain C code (C99) in PascalCase/camelCase notation -* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) -* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] -* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) -* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) -* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! -* - Flexible Materials system, supporting classic maps and PBR maps -* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) -* - Shaders support, including Model shaders and Postprocessing shaders -* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] -* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) -* - VR stereo rendering with configurable HMD device parameters -* - Bindings to multiple programming languages available! -* -* NOTES: -* - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] -* - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) -* - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) -* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) -* -* DEPENDENCIES (included): -* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input -* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input -* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading -* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management -* -* OPTIONAL DEPENDENCIES (included): -* [rcore] msf_gif (Miles Fogle) for GIF recording -* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm -* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm -* [rcore] rprand (Ramon Snatamaria) for pseudo-random numbers generation -* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage -* [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) -* [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) -* [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms -* [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation -* [rtext] stb_truetype (Sean Barret) for ttf fonts loading -* [rtext] stb_rect_pack (Sean Barret) for rectangles packing -* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation -* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) -* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) -* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) -* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) -* [raudio] dr_wav (David Reid) for WAV audio file loading -* [raudio] dr_flac (David Reid) for FLAC audio file loading -* [raudio] dr_mp3 (David Reid) for MP3 audio file loading -* [raudio] stb_vorbis (Sean Barret) for OGG audio loading -* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading -* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading -* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage -* -* -* LICENSE: zlib/libpng -* -* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, -* BSD-like license that allows static linking with closed source software: -* -* Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef RAYLIB_H -#define RAYLIB_H - -#include // Required for: va_list - Only used by TraceLogCallback - -#define RAYLIB_VERSION_MAJOR 5 -#define RAYLIB_VERSION_MINOR 6 -#define RAYLIB_VERSION_PATCH 0 -#define RAYLIB_VERSION "5.6-dev" - -// Function specifiers in case library is build/used as a shared library -// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll -// NOTE: visibility("default") attribute makes symbols "visible" when compiled with -fvisibility=hidden -#if defined(_WIN32) - #if defined(__TINYC__) - #define __declspec(x) __attribute__((x)) - #endif - #if defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) - #elif defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) - #endif -#else - #if defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __attribute__((visibility("default"))) // We are building as a Unix shared library (.so/.dylib) - #endif -#endif - -#ifndef RLAPI - #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) -#endif - -//---------------------------------------------------------------------------------- -// Some basic Defines -//---------------------------------------------------------------------------------- -#ifndef PI - #define PI 3.14159265358979323846f -#endif -#ifndef DEG2RAD - #define DEG2RAD (PI/180.0f) -#endif -#ifndef RAD2DEG - #define RAD2DEG (180.0f/PI) -#endif - -// Allow custom memory allocators -// NOTE: Require recompiling raylib sources -#ifndef RL_MALLOC - #define RL_MALLOC(sz) malloc(sz) -#endif -#ifndef RL_CALLOC - #define RL_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RL_REALLOC - #define RL_REALLOC(ptr,sz) realloc(ptr,sz) -#endif -#ifndef RL_FREE - #define RL_FREE(ptr) free(ptr) -#endif - -// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) -// Plain structures in C++ (without constructors) can be initialized with { } -// This is called aggregate initialization (C++11 feature) -#if defined(__cplusplus) - #define CLITERAL(type) type -#else - #define CLITERAL(type) (type) -#endif - -// Some compilers (mostly macos clang) default to C++98, -// where aggregate initialization can't be used -// So, give a more clear error stating how to fix this -#if !defined(_MSC_VER) && (defined(__cplusplus) && __cplusplus < 201103L) - #error "C++11 or later is required. Add -std=c++11" -#endif - -// NOTE: We set some defines with some data types declared by raylib -// Other modules (raymath, rlgl) also require some of those types, so, -// to be able to use those other modules as standalone (not depending on raylib) -// this defines are very useful for internal check and avoid type (re)definitions -#define RL_COLOR_TYPE -#define RL_RECTANGLE_TYPE -#define RL_VECTOR2_TYPE -#define RL_VECTOR3_TYPE -#define RL_VECTOR4_TYPE -#define RL_QUATERNION_TYPE -#define RL_MATRIX_TYPE - -// Some Basic Colors -// NOTE: Custom raylib color palette for amazing visuals on WHITE background -#define _rl_LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray -#define _rl_GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray -#define _rl_DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray -#define _rl_YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow -#define _rl_GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold -#define _rl_ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange -#define _rl_PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink -#define _rl_RED CLITERAL(Color){ 230, 41, 55, 255 } // Red -#define _rl_MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon -#define _rl_GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green -#define _rl_LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime -#define _rl_DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green -#define _rl_SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue -#define _rl_BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue -#define _rl_DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue -#define _rl_PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple -#define _rl_VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet -#define _rl_DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple -#define _rl_BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige -#define _rl_BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown -#define _rl_DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown - -#define _rl_WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White -#define _rl_BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black -#define _rl_BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) -#define _rl_MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta -#define _rl_RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) - -#ifndef OPENPILOT_RAYLIB - #define LIGHTGRAY _rl_LIGHTGRAY - #define GRAY _rl_GRAY - #define DARKGRAY _rl_DARKGRAY - #define YELLOW _rl_YELLOW - #define GOLD _rl_GOLD - #define ORANGE _rl_ORANGE - #define PINK _rl_PINK - #define RED _rl_RED - #define MAROON _rl_MAROON - #define GREEN _rl_GREEN - #define LIME _rl_LIME - #define DARKGREEN _rl_DARKGREEN - #define SKYBLUE _rl_SKYBLUE - #define BLUE _rl_BLUE - #define DARKBLUE _rl_DARKBLUE - #define PURPLE _rl_PURPLE - #define VIOLET _rl_VIOLET - #define DARKPURPLE _rl_DARKBLUE - #define BEIGE _rl_BEIGE - #define BROWN _rl_BROWN - #define DARKBROWN _rl_DARKBROWN - - #define WHITE _rl_WHITE - #define BLACK _rl_BLACK - #define BLANK _rl_BLANK - #define MAGENTA _rl_MAGENTA - #define RAYWHITE _rl_RAYWHITE -#else - #define RAYLIB_LIGHTGRAY _rl_LIGHTGRAY - #define RAYLIB_GRAY _rl_GRAY - #define RAYLIB_DARKGRAY _rl_DARKGRAY - #define RAYLIB_YELLOW _rl_YELLOW - #define RAYLIB_GOLD _rl_GOLD - #define RAYLIB_ORANGE _rl_ORANGE - #define RAYLIB_PINK _rl_PINK - #define RAYLIB_RED _rl_RED - #define RAYLIB_MAROON _rl_MAROON - #define RAYLIB_GREEN _rl_GREEN - #define RAYLIB_LIME _rl_LIME - #define RAYLIB_DARKGREEN _rl_DARKGREEN - #define RAYLIB_SKYBLUE _rl_SKYBLUE - #define RAYLIB_BLUE _rl_BLUE - #define RAYLIB_DARKBLUE _rl_DARKBLUE - #define RAYLIB_PURPLE _rl_PURPLE - #define RAYLIB_VIOLET _rl_VIOLET - #define RAYLIB_DARKPURPLE _rl_DARKBLUE - #define RAYLIB_BEIGE _rl_BEIGE - #define RAYLIB_BROWN _rl_BROWN - #define RAYLIB_DARKBROWN _rl_DARKBROWN - - #define RAYLIB_WHITE _rl_WHITE - #define RAYLIB_BLACK _rl_BLACK - #define RAYLIB_BLANK _rl_BLANK - #define RAYLIB_MAGENTA _rl_MAGENTA - #define RAYLIB_RAYWHITE _rl_RAYWHITE -#endif - -//---------------------------------------------------------------------------------- -// Structures Definition -//---------------------------------------------------------------------------------- -// Boolean type -#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) - #include -#elif !defined(__cplusplus) && !defined(bool) - typedef enum bool { false = 0, true = !false } bool; - #define RL_BOOL_TYPE -#endif - -// Vector2, 2 components -typedef struct Vector2 { - float x; // Vector x component - float y; // Vector y component -} Vector2; - -// Vector3, 3 components -typedef struct Vector3 { - float x; // Vector x component - float y; // Vector y component - float z; // Vector z component -} Vector3; - -// Vector4, 4 components -typedef struct Vector4 { - float x; // Vector x component - float y; // Vector y component - float z; // Vector z component - float w; // Vector w component -} Vector4; - -// Quaternion, 4 components (Vector4 alias) -typedef Vector4 Quaternion; - -// Matrix, 4x4 components, column major, OpenGL style, right-handed -typedef struct Matrix { - float m0, m4, m8, m12; // Matrix first row (4 components) - float m1, m5, m9, m13; // Matrix second row (4 components) - float m2, m6, m10, m14; // Matrix third row (4 components) - float m3, m7, m11, m15; // Matrix fourth row (4 components) -} Matrix; - -// Color, 4 components, R8G8B8A8 (32bit) -typedef struct Color { - unsigned char r; // Color red value - unsigned char g; // Color green value - unsigned char b; // Color blue value - unsigned char a; // Color alpha value -} Color; - -// Rectangle, 4 components -typedef struct Rectangle { - float x; // Rectangle top-left corner position x - float y; // Rectangle top-left corner position y - float width; // Rectangle width - float height; // Rectangle height -} Rectangle; - -// Image, pixel data stored in CPU memory (RAM) -typedef struct Image { - void *data; // Image raw data - int width; // Image base width - int height; // Image base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) -} Image; - -// Texture, tex data stored in GPU memory (VRAM) -typedef struct Texture { - unsigned int id; // OpenGL texture id - int width; // Texture base width - int height; // Texture base height - int mipmaps; // Mipmap levels, 1 by default - int format; // Data format (PixelFormat type) -} Texture; - -// Texture2D, same as Texture -typedef Texture Texture2D; - -// TextureCubemap, same as Texture -typedef Texture TextureCubemap; - -// RenderTexture, fbo for texture rendering -typedef struct RenderTexture { - unsigned int id; // OpenGL framebuffer object id - Texture texture; // Color buffer attachment texture - Texture depth; // Depth buffer attachment texture -} RenderTexture; - -// RenderTexture2D, same as RenderTexture -typedef RenderTexture RenderTexture2D; - -// NPatchInfo, n-patch layout info -typedef struct NPatchInfo { - Rectangle source; // Texture source rectangle - int left; // Left border offset - int top; // Top border offset - int right; // Right border offset - int bottom; // Bottom border offset - int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 -} NPatchInfo; - -// GlyphInfo, font characters glyphs info -typedef struct GlyphInfo { - int value; // Character value (Unicode) - int offsetX; // Character offset X when drawing - int offsetY; // Character offset Y when drawing - int advanceX; // Character advance position X - Image image; // Character image data -} GlyphInfo; - -// Font, font texture and GlyphInfo array data -typedef struct Font { - int baseSize; // Base size (default chars height) - int glyphCount; // Number of glyph characters - int glyphPadding; // Padding around the glyph characters - Texture2D texture; // Texture atlas containing the glyphs - Rectangle *recs; // Rectangles in texture for the glyphs - GlyphInfo *glyphs; // Glyphs info data -} Font; - -// Camera, defines position/orientation in 3d space -typedef struct Camera3D { - Vector3 position; // Camera position - Vector3 target; // Camera target it looks-at - Vector3 up; // Camera up vector (rotation over its axis) - float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane width in orthographic - int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC -} Camera3D; - -typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D - -// Camera2D, defines position/orientation in 2d space -typedef struct Camera2D { - Vector2 offset; // Camera offset (displacement from target) - Vector2 target; // Camera target (rotation and zoom origin) - float rotation; // Camera rotation in degrees - float zoom; // Camera zoom (scaling), should be 1.0f by default -} Camera2D; - -// Mesh, vertex data and vao/vbo -typedef struct Mesh { - int vertexCount; // Number of vertices stored in arrays - int triangleCount; // Number of triangles stored (indexed or not) - - // Vertex attributes data - float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) - float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - float *texcoords2; // Vertex texture second coordinates (UV - 2 components per vertex) (shader-location = 5) - float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) - float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) - unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) - unsigned short *indices; // Vertex indices (in case vertex data comes indexed) - - // Animation vertex data - float *animVertices; // Animated vertex positions (after bones transformations) - float *animNormals; // Animated normals (after bones transformations) - unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) - float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) - Matrix *boneMatrices; // Bones animated transformation matrices - int boneCount; // Number of bones - - // OpenGL identifiers - unsigned int vaoId; // OpenGL Vertex Array Object id - unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) -} Mesh; - -// Shader -typedef struct Shader { - unsigned int id; // Shader program id - int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) -} Shader; - -// MaterialMap -typedef struct MaterialMap { - Texture2D texture; // Material map texture - Color color; // Material map color - float value; // Material map value -} MaterialMap; - -// Material, includes shader and maps -typedef struct Material { - Shader shader; // Material shader - MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) - float params[4]; // Material generic parameters (if required) -} Material; - -// Transform, vertex transformation data -typedef struct Transform { - Vector3 translation; // Translation - Quaternion rotation; // Rotation - Vector3 scale; // Scale -} Transform; - -// Bone, skeletal animation bone -typedef struct BoneInfo { - char name[32]; // Bone name - int parent; // Bone parent -} BoneInfo; - -// Model, meshes, materials and animation data -typedef struct Model { - Matrix transform; // Local transform matrix - - int meshCount; // Number of meshes - int materialCount; // Number of materials - Mesh *meshes; // Meshes array - Material *materials; // Materials array - int *meshMaterial; // Mesh material number - - // Animation data - int boneCount; // Number of bones - BoneInfo *bones; // Bones information (skeleton) - Transform *bindPose; // Bones base transformation (pose) -} Model; - -// ModelAnimation -typedef struct ModelAnimation { - int boneCount; // Number of bones - int frameCount; // Number of animation frames - BoneInfo *bones; // Bones information (skeleton) - Transform **framePoses; // Poses array by frame - char name[32]; // Animation name -} ModelAnimation; - -// Ray, ray for raycasting -typedef struct Ray { - Vector3 position; // Ray position (origin) - Vector3 direction; // Ray direction (normalized) -} Ray; - -// RayCollision, ray hit information -typedef struct RayCollision { - bool hit; // Did the ray hit something? - float distance; // Distance to the nearest hit - Vector3 point; // Point of the nearest hit - Vector3 normal; // Surface normal of hit -} RayCollision; - -// BoundingBox -typedef struct BoundingBox { - Vector3 min; // Minimum vertex box-corner - Vector3 max; // Maximum vertex box-corner -} BoundingBox; - -// Wave, audio wave data -typedef struct Wave { - unsigned int frameCount; // Total number of frames (considering channels) - unsigned int sampleRate; // Frequency (samples per second) - unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) - unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) - void *data; // Buffer data pointer -} Wave; - -// Opaque structs declaration -// NOTE: Actual structs are defined internally in raudio module -typedef struct rAudioBuffer rAudioBuffer; -typedef struct rAudioProcessor rAudioProcessor; - -// AudioStream, custom audio stream -typedef struct AudioStream { - rAudioBuffer *buffer; // Pointer to internal data used by the audio system - rAudioProcessor *processor; // Pointer to internal data processor, useful for audio effects - - unsigned int sampleRate; // Frequency (samples per second) - unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) - unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) -} AudioStream; - -// Sound -typedef struct Sound { - AudioStream stream; // Audio stream - unsigned int frameCount; // Total number of frames (considering channels) -} Sound; - -// Music, audio stream, anything longer than ~10 seconds should be streamed -typedef struct Music { - AudioStream stream; // Audio stream - unsigned int frameCount; // Total number of frames (considering channels) - bool looping; // Music looping enable - - int ctxType; // Type of music context (audio filetype) - void *ctxData; // Audio context data, depends on type -} Music; - -// VrDeviceInfo, Head-Mounted-Display device parameters -typedef struct VrDeviceInfo { - int hResolution; // Horizontal resolution in pixels - int vResolution; // Vertical resolution in pixels - float hScreenSize; // Horizontal size in meters - float vScreenSize; // Vertical size in meters - float eyeToScreenDistance; // Distance between eye and display in meters - float lensSeparationDistance; // Lens separation distance in meters - float interpupillaryDistance; // IPD (distance between pupils) in meters - float lensDistortionValues[4]; // Lens distortion constant parameters - float chromaAbCorrection[4]; // Chromatic aberration correction parameters -} VrDeviceInfo; - -// VrStereoConfig, VR stereo rendering configuration for simulator -typedef struct VrStereoConfig { - Matrix projection[2]; // VR projection matrices (per eye) - Matrix viewOffset[2]; // VR view offset matrices (per eye) - float leftLensCenter[2]; // VR left lens center - float rightLensCenter[2]; // VR right lens center - float leftScreenCenter[2]; // VR left screen center - float rightScreenCenter[2]; // VR right screen center - float scale[2]; // VR distortion scale - float scaleIn[2]; // VR distortion scale in -} VrStereoConfig; - -// File path list -typedef struct FilePathList { - unsigned int capacity; // Filepaths max entries - unsigned int count; // Filepaths entries count - char **paths; // Filepaths entries -} FilePathList; - -// Automation event -typedef struct AutomationEvent { - unsigned int frame; // Event frame - unsigned int type; // Event type (AutomationEventType) - int params[4]; // Event parameters (if required) -} AutomationEvent; - -// Automation event list -typedef struct AutomationEventList { - unsigned int capacity; // Events max entries (MAX_AUTOMATION_EVENTS) - unsigned int count; // Events entries count - AutomationEvent *events; // Events entries -} AutomationEventList; - -//---------------------------------------------------------------------------------- -// Enumerators Definition -//---------------------------------------------------------------------------------- -// System/Window config flags -// NOTE: Every bit registers one state (use it with bit masks) -// By default all flags are set to 0 -typedef enum { - FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU - FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen - FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window - FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) - FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window - FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) - FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) - FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused - FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top - FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized - FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer - FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI - FLAG_WINDOW_MOUSE_PASSTHROUGH = 0x00004000, // Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED - FLAG_BORDERLESS_WINDOWED_MODE = 0x00008000, // Set to run program in borderless windowed mode - FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X - FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) -} ConfigFlags; - -// Trace log level -// NOTE: Organized by priority level -typedef enum { - LOG_ALL = 0, // Display all logs - LOG_TRACE, // Trace logging, intended for internal use only - LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds - LOG_INFO, // Info logging, used for program execution info - LOG_WARNING, // Warning logging, used on recoverable failures - LOG_ERROR, // Error logging, used on unrecoverable failures - LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) - LOG_NONE // Disable logging -} TraceLogLevel; - -// Keyboard keys (US keyboard layout) -// NOTE: Use GetKeyPressed() to allow redefining -// required keys for alternative layouts -typedef enum { - KEY_NULL = 0, // Key: NULL, used for no key pressed - // Alphanumeric keys - KEY_APOSTROPHE = 39, // Key: ' - KEY_COMMA = 44, // Key: , - KEY_MINUS = 45, // Key: - - KEY_PERIOD = 46, // Key: . - KEY_SLASH = 47, // Key: / - KEY_ZERO = 48, // Key: 0 - KEY_ONE = 49, // Key: 1 - KEY_TWO = 50, // Key: 2 - KEY_THREE = 51, // Key: 3 - KEY_FOUR = 52, // Key: 4 - KEY_FIVE = 53, // Key: 5 - KEY_SIX = 54, // Key: 6 - KEY_SEVEN = 55, // Key: 7 - KEY_EIGHT = 56, // Key: 8 - KEY_NINE = 57, // Key: 9 - KEY_SEMICOLON = 59, // Key: ; - KEY_EQUAL = 61, // Key: = - KEY_A = 65, // Key: A | a - KEY_B = 66, // Key: B | b - KEY_C = 67, // Key: C | c - KEY_D = 68, // Key: D | d - KEY_E = 69, // Key: E | e - KEY_F = 70, // Key: F | f - KEY_G = 71, // Key: G | g - KEY_H = 72, // Key: H | h - KEY_I = 73, // Key: I | i - KEY_J = 74, // Key: J | j - KEY_K = 75, // Key: K | k - KEY_L = 76, // Key: L | l - KEY_M = 77, // Key: M | m - KEY_N = 78, // Key: N | n - KEY_O = 79, // Key: O | o - KEY_P = 80, // Key: P | p - KEY_Q = 81, // Key: Q | q - KEY_R = 82, // Key: R | r - KEY_S = 83, // Key: S | s - KEY_T = 84, // Key: T | t - KEY_U = 85, // Key: U | u - KEY_V = 86, // Key: V | v - KEY_W = 87, // Key: W | w - KEY_X = 88, // Key: X | x - KEY_Y = 89, // Key: Y | y - KEY_Z = 90, // Key: Z | z - KEY_LEFT_BRACKET = 91, // Key: [ - KEY_BACKSLASH = 92, // Key: '\' - KEY_RIGHT_BRACKET = 93, // Key: ] - KEY_GRAVE = 96, // Key: ` - // Function keys - KEY_SPACE = 32, // Key: Space - KEY_ESCAPE = 256, // Key: Esc - KEY_ENTER = 257, // Key: Enter - KEY_TAB = 258, // Key: Tab - KEY_BACKSPACE = 259, // Key: Backspace - KEY_INSERT = 260, // Key: Ins - KEY_DELETE = 261, // Key: Del - KEY_RIGHT = 262, // Key: Cursor right - KEY_LEFT = 263, // Key: Cursor left - KEY_DOWN = 264, // Key: Cursor down - KEY_UP = 265, // Key: Cursor up - KEY_PAGE_UP = 266, // Key: Page up - KEY_PAGE_DOWN = 267, // Key: Page down - KEY_HOME = 268, // Key: Home - KEY_END = 269, // Key: End - KEY_CAPS_LOCK = 280, // Key: Caps lock - KEY_SCROLL_LOCK = 281, // Key: Scroll down - KEY_NUM_LOCK = 282, // Key: Num lock - KEY_PRINT_SCREEN = 283, // Key: Print screen - KEY_PAUSE = 284, // Key: Pause - KEY_F1 = 290, // Key: F1 - KEY_F2 = 291, // Key: F2 - KEY_F3 = 292, // Key: F3 - KEY_F4 = 293, // Key: F4 - KEY_F5 = 294, // Key: F5 - KEY_F6 = 295, // Key: F6 - KEY_F7 = 296, // Key: F7 - KEY_F8 = 297, // Key: F8 - KEY_F9 = 298, // Key: F9 - KEY_F10 = 299, // Key: F10 - KEY_F11 = 300, // Key: F11 - KEY_F12 = 301, // Key: F12 - KEY_LEFT_SHIFT = 340, // Key: Shift left - KEY_LEFT_CONTROL = 341, // Key: Control left - KEY_LEFT_ALT = 342, // Key: Alt left - KEY_LEFT_SUPER = 343, // Key: Super left - KEY_RIGHT_SHIFT = 344, // Key: Shift right - KEY_RIGHT_CONTROL = 345, // Key: Control right - KEY_RIGHT_ALT = 346, // Key: Alt right - KEY_RIGHT_SUPER = 347, // Key: Super right - KEY_KB_MENU = 348, // Key: KB menu - // Keypad keys - KEY_KP_0 = 320, // Key: Keypad 0 - KEY_KP_1 = 321, // Key: Keypad 1 - KEY_KP_2 = 322, // Key: Keypad 2 - KEY_KP_3 = 323, // Key: Keypad 3 - KEY_KP_4 = 324, // Key: Keypad 4 - KEY_KP_5 = 325, // Key: Keypad 5 - KEY_KP_6 = 326, // Key: Keypad 6 - KEY_KP_7 = 327, // Key: Keypad 7 - KEY_KP_8 = 328, // Key: Keypad 8 - KEY_KP_9 = 329, // Key: Keypad 9 - KEY_KP_DECIMAL = 330, // Key: Keypad . - KEY_KP_DIVIDE = 331, // Key: Keypad / - KEY_KP_MULTIPLY = 332, // Key: Keypad * - KEY_KP_SUBTRACT = 333, // Key: Keypad - - KEY_KP_ADD = 334, // Key: Keypad + - KEY_KP_ENTER = 335, // Key: Keypad Enter - KEY_KP_EQUAL = 336, // Key: Keypad = - // Android key buttons - KEY_BACK = 4, // Key: Android back button - KEY_MENU = 5, // Key: Android menu button - KEY_VOLUME_UP = 24, // Key: Android volume up button - KEY_VOLUME_DOWN = 25 // Key: Android volume down button -} KeyboardKey; - -// Add backwards compatibility support for deprecated names -#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT -#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT -#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE - -// Mouse buttons -typedef enum { - MOUSE_BUTTON_LEFT = 0, // Mouse button left - MOUSE_BUTTON_RIGHT = 1, // Mouse button right - MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) - MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) - MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) - MOUSE_BUTTON_FORWARD = 5, // Mouse button forward (advanced mouse device) - MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) -} MouseButton; - -// Mouse cursor -typedef enum { - MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape - MOUSE_CURSOR_ARROW = 1, // Arrow shape - MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape - MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape - MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor - MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape - MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape - MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape - MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape - MOUSE_CURSOR_RESIZE_ALL = 9, // The omnidirectional resize/move cursor shape - MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape -} MouseCursor; - -// Gamepad buttons -typedef enum { - GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, just for error checking - GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button - GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button - GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button - GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button - GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) - GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Circle, Xbox: B) - GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) - GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Square, Xbox: X) - GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button - GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button - GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (first), it could be a trailing button - GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button - GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) - GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) - GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) - GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left - GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right -} GamepadButton; - -// Gamepad axis -typedef enum { - GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis - GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis - GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis - GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis - GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] - GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] -} GamepadAxis; - -// Material map index -typedef enum { - MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) - MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) - MATERIAL_MAP_NORMAL, // Normal material - MATERIAL_MAP_ROUGHNESS, // Roughness material - MATERIAL_MAP_OCCLUSION, // Ambient occlusion material - MATERIAL_MAP_EMISSION, // Emission material - MATERIAL_MAP_HEIGHT, // Heightmap material - MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) - MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) - MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) - MATERIAL_MAP_BRDF // Brdf material -} MaterialMapIndex; - -#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO -#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS - -// Shader location index -typedef enum { - SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position - SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 - SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 - SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal - SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent - SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color - SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection - SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) - SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection - SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) - SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal - SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view - SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color - SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color - SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color - SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) - SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) - SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal - SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness - SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion - SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission - SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height - SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap - SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance - SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter - SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf - SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds - SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights - SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices -} ShaderLocationIndex; - -#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO -#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS - -// Shader uniform data type -typedef enum { - SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float - SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) - SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) - SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) - SHADER_UNIFORM_INT, // Shader uniform type: int - SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) - SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) - SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) - SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d -} ShaderUniformDataType; - -// Shader attribute data types -typedef enum { - SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float - SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) - SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) - SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) -} ShaderAttributeDataType; - -// Pixel formats -// NOTE: Support depends on OpenGL version and platform -typedef enum { - PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) - PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp - PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp - PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) - PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) - PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp - PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) - PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) - PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) - PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) - PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) - PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) - PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) - PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) - PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp - PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp - PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp - PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp - PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp - PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp -} PixelFormat; - -// Texture parameters: filter mode -// NOTE 1: Filtering considers mipmaps if available in the texture -// NOTE 2: Filter is accordingly set for minification and magnification -typedef enum { - TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation - TEXTURE_FILTER_BILINEAR, // Linear filtering - TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) - TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x - TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x - TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x -} TextureFilter; - -// Texture parameters: wrap mode -typedef enum { - TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode - TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode - TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode - TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode -} TextureWrap; - -// Cubemap layouts -typedef enum { - CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type - CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces - CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces - CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces - CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces -} CubemapLayout; - -// Font type, defines generation method -typedef enum { - FONT_DEFAULT = 0, // Default font generation, anti-aliased - FONT_BITMAP, // Bitmap font generation, no anti-aliasing - FONT_SDF // SDF font generation, requires external shader -} FontType; - -// Color blending modes (pre-defined) -typedef enum { - BLEND_ALPHA = 0, // Blend textures considering alpha (default) - BLEND_ADDITIVE, // Blend textures adding colors - BLEND_MULTIPLIED, // Blend textures multiplying colors - BLEND_ADD_COLORS, // Blend textures adding colors (alternative) - BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) - BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha - BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) - BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) -} BlendMode; - -// Gesture -// NOTE: Provided as bit-wise flags to enable only desired gestures -typedef enum { - GESTURE_NONE = 0, // No gesture - GESTURE_TAP = 1, // Tap gesture - GESTURE_DOUBLETAP = 2, // Double tap gesture - GESTURE_HOLD = 4, // Hold gesture - GESTURE_DRAG = 8, // Drag gesture - GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture - GESTURE_SWIPE_LEFT = 32, // Swipe left gesture - GESTURE_SWIPE_UP = 64, // Swipe up gesture - GESTURE_SWIPE_DOWN = 128, // Swipe down gesture - GESTURE_PINCH_IN = 256, // Pinch in gesture - GESTURE_PINCH_OUT = 512 // Pinch out gesture -} Gesture; - -// Camera system modes -typedef enum { - CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) - CAMERA_FREE, // Camera free mode - CAMERA_ORBITAL, // Camera orbital, around target, zoom supported - CAMERA_FIRST_PERSON, // Camera first person - CAMERA_THIRD_PERSON // Camera third person -} CameraMode; - -// Camera projection -typedef enum { - CAMERA_PERSPECTIVE = 0, // Perspective projection - CAMERA_ORTHOGRAPHIC // Orthographic projection -} CameraProjection; - -// N-patch layout -typedef enum { - NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles - NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles - NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles -} NPatchLayout; - -// Callbacks to hook some internal functions -// WARNING: These callbacks are intended for advanced users -typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages -typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data -typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, int dataSize); // FileIO: Save binary data -typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data -typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data - -//------------------------------------------------------------------------------------ -// Global Variables Definition -//------------------------------------------------------------------------------------ -// It's lonely here... - -//------------------------------------------------------------------------------------ -// Window and Graphics Device Functions (Module: core) -//------------------------------------------------------------------------------------ - -#if defined(__cplusplus) -extern "C" { // Prevents name mangling of functions -#endif - -// Window-related functions -RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context -RLAPI void CloseWindow(void); // Close window and unload OpenGL context -RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) -RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully -RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen -RLAPI bool IsWindowHidden(void); // Check if window is currently hidden -RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized -RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized -RLAPI bool IsWindowFocused(void); // Check if window is currently focused -RLAPI bool IsWindowResized(void); // Check if window has been resized last frame -RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled -RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags -RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags -RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution -RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution -RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable -RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable -RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized -RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) -RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) -RLAPI void SetWindowTitle(const char *title); // Set title for window -RLAPI void SetWindowPosition(int x, int y); // Set window position on screen -RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window -RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) -RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) -RLAPI void SetWindowSize(int width, int height); // Set window dimensions -RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] -RLAPI void SetWindowFocused(void); // Set window focused -RLAPI void *GetWindowHandle(void); // Get native window handle -RLAPI int GetScreenWidth(void); // Get current screen width -RLAPI int GetScreenHeight(void); // Get current screen height -RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) -RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) -RLAPI int GetMonitorCount(void); // Get number of connected monitors -RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed -RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position -RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) -RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) -RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres -RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres -RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate -RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor -RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor -RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor -RLAPI void SetClipboardText(const char *text); // Set clipboard text content -RLAPI const char *GetClipboardText(void); // Get clipboard text content -RLAPI Image GetClipboardImage(void); // Get clipboard image content -RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling -RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling - -// Cursor-related functions -RLAPI void ShowCursor(void); // Shows cursor -RLAPI void HideCursor(void); // Hides cursor -RLAPI bool IsCursorHidden(void); // Check if cursor is not visible -RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) -RLAPI void DisableCursor(void); // Disables cursor (lock cursor) -RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen - -// Drawing-related functions -RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) -RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing -RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) -RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) -RLAPI void EndMode2D(void); // Ends 2D mode with custom camera -RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) -RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode -RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture -RLAPI void EndTextureMode(void); // Ends drawing to render texture -RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing -RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) -RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) -RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) -RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) -RLAPI void EndScissorMode(void); // End scissor mode -RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) -RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) - -// VR stereo config functions for VR simulator -RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters -RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config - -// Shader management functions -// NOTE: Shader functionality is not available on OpenGL 1.1 -RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations -RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations -RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU) -RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location -RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location -RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value -RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector -RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) -RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) -RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) - -// Screen-space-related functions -#define GetMouseRay GetScreenToWorldRay // Compatibility hack for previous raylib versions -RLAPI Ray GetScreenToWorldRay(Vector2 position, Camera camera); // Get a ray trace from screen position (i.e mouse) -RLAPI Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height); // Get a ray trace from screen position (i.e mouse) in a viewport -RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position -RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get size position for a 3d world space position -RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get the screen space position for a 2d camera world space position -RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen space position -RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) -RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix - -// Timing-related functions -RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) -RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) -RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() -RLAPI int GetFPS(void); // Get current FPS - -// Custom frame control functions -// NOTE: Those functions are intended for advanced users that want full control over the frame processing -// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() -// To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL -RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) -RLAPI void PollInputEvents(void); // Register all input events -RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) - -// Random values generation functions -RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator -RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) -RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated -RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence - -// Misc. functions -RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) -RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) -RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) - -// NOTE: Following functions implemented in module [utils] -//------------------------------------------------------------------ -RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) -RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level -RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator -RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator -RLAPI void MemFree(void *ptr); // Internal memory free - -// Set custom callbacks -// WARNING: Callbacks setup is intended for advanced users -RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log -RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader -RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver -RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader -RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver - -// Files management functions -RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) -RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() -RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write), returns true on success -RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success -RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string -RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() -RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success -//------------------------------------------------------------------ - -// File system functions -RLAPI bool FileExists(const char *fileName); // Check if file exists -RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists -RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (including point: .png, .wav) -RLAPI int GetFileLength(const char *fileName); // Get file length in bytes (NOTE: GetFileSize() conflicts with windows.h) -RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') -RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string -RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) -RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) -RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) -RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) -RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) -RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success -RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success -RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory -RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS -RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths -RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result -RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths -RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window -RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths -RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths -RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) - -// Compression/Encoding functionality -RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() -RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() -RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree() -RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() -RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code -RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) -RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) - - -// Automation events functionality -RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS -RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file -RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file -RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to -RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording -RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) -RLAPI void StopAutomationEventRecording(void); // Stop recording automation events -RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event - -//------------------------------------------------------------------------------------ -// Input Handling Functions (Module: core) -//------------------------------------------------------------------------------------ - -// Input-related functions: keyboard -RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once -RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again -RLAPI bool IsKeyDown(int key); // Check if a key is being pressed -RLAPI bool IsKeyReleased(int key); // Check if a key has been released once -RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed -RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty -RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty -RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) - -// Input-related functions: gamepads -RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available -RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id -RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once -RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed -RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once -RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed -RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed -RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad -RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis -RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) -RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) - -// Input-related functions: mouse -RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once -RLAPI bool IsMouseButtonDown(int button); // Check if a mouse button is being pressed -RLAPI bool IsMouseButtonReleased(int button); // Check if a mouse button has been released once -RLAPI bool IsMouseButtonUp(int button); // Check if a mouse button is NOT being pressed -RLAPI int GetMouseX(void); // Get mouse position X -RLAPI int GetMouseY(void); // Get mouse position Y -RLAPI Vector2 GetMousePosition(void); // Get mouse position XY -RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames -RLAPI void SetMousePosition(int x, int y); // Set mouse position XY -RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset -RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling -RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement for X or Y, whichever is larger -RLAPI Vector2 GetMouseWheelMoveV(void); // Get mouse wheel movement for both X and Y -RLAPI void SetMouseCursor(int cursor); // Set mouse cursor - -// Input-related functions: touch -RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) -RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) -RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) -RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index -RLAPI int GetTouchPointCount(void); // Get number of touch points - -//------------------------------------------------------------------------------------ -// Gestures and Touch Handling Functions (Module: rgestures) -//------------------------------------------------------------------------------------ -RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags -RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected -RLAPI int GetGestureDetected(void); // Get latest detected gesture -RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds -RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector -RLAPI float GetGestureDragAngle(void); // Get gesture drag angle -RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta -RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle - -//------------------------------------------------------------------------------------ -// Camera System Functions (Module: rcamera) -//------------------------------------------------------------------------------------ -RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode -RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation - -//------------------------------------------------------------------------------------ -// Basic Shapes Drawing Functions (Module: shapes) -//------------------------------------------------------------------------------------ -// Set texture and rectangle to be used on shapes drawing -// NOTE: It can be useful when using basic shapes and one single font, -// defining a font char white rectangle would allow drawing everything in a single draw call -RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing -RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing -RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing - -// Basic shapes drawing functions -RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] -RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] -RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line -RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) -RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) -RLAPI void DrawLineStrip(const Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) -RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation -RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle -RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle -RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline -RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle -RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) -RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline -RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) -RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse -RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline -RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring -RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline -RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle -RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) -RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle -RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters -RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle -RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle -RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors -RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline -RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters -RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges -RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle lines with rounded edges -RLAPI void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline -RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) -RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) -RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) -RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points -RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) -RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides -RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters - -// Splines drawing functions -RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points -RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points -RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points -RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] -RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] -RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points -RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points -RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points -RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point -RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points - -// Spline segment point evaluation functions, for a given t [0.0f .. 1.0f] -RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear -RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline -RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom -RLAPI Vector2 GetSplinePointBezierQuad(Vector2 p1, Vector2 c2, Vector2 p3, float t); // Get (evaluate) spline point: Quadratic Bezier -RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float t); // Get (evaluate) spline point: Cubic Bezier - -// Basic shapes collision detection functions -RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles -RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles -RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle -RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] -RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle -RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle -RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle -RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] -RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices -RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference -RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision - -//------------------------------------------------------------------------------------ -// Texture Loading and Drawing Functions (Module: textures) -//------------------------------------------------------------------------------------ - -// Image loading functions -// NOTE: These functions do not require GPU access -RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) -RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data -RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) -RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer -RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' -RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data -RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) -RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) -RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) -RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success -RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer -RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success - -// Image generation functions -RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color -RLAPI Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end); // Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient -RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient -RLAPI Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer); // Generate image: square gradient -RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked -RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise -RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise -RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells -RLAPI Image GenImageText(int width, int height, const char *text); // Generate image: grayscale image from text data - -// Image manipulation functions -RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) -RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece -RLAPI Image ImageFromChannel(Image image, int selectedChannel); // Create an image from a selected channel of another image (GRAYSCALE) -RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) -RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) -RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format -RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) -RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle -RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value -RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color -RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image -RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel -RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation -RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image -RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) -RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) -RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color -RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image -RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) -RLAPI void ImageFlipVertical(Image *image); // Flip image vertically -RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally -RLAPI void ImageRotate(Image *image, int degrees); // Rotate image by input angle in degrees (-359 to 359) -RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg -RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg -RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint -RLAPI void ImageColorInvert(Image *image); // Modify image color: invert -RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale -RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) -RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) -RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color -RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) -RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) -RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() -RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() -RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle -RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position - -// Image drawing functions -// NOTE: Image software-rendering functions (CPU) -RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color -RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image -RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) -RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image -RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) -RLAPI void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color); // Draw a line defining thickness within an image -RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw a filled circle within an image -RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw a filled circle within an image (Vector version) -RLAPI void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle outline within an image -RLAPI void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color); // Draw circle outline within an image (Vector version) -RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image -RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) -RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image -RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image -RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image -RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image -RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image -RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) -RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image -RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) -RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) -RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) - -// Texture loading functions -// NOTE: These functions require GPU access -RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) -RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data -RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported -RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) -RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU) -RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) -RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) -RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) -RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data -RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data - -// Texture configuration functions -RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture -RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode -RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode - -// Texture drawing functions -RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D -RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 -RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters -RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle -RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters -RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely - -// Color/pixel related functions -RLAPI bool ColorIsEqual(Color col1, Color col2); // Check if two colors are equal -RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f -RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color (0xRRGGBBAA) -RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] -RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] -RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] -RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] -RLAPI Color ColorTint(Color color, Color tint); // Get color multiplied with another color -RLAPI Color ColorBrightness(Color color, float factor); // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f -RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f -RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f -RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint -RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] -RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value -RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format -RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer -RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format - -//------------------------------------------------------------------------------------ -// Font Loading and Text Drawing Functions (Module: text) -//------------------------------------------------------------------------------------ - -// Font loading/unloading functions -RLAPI Font GetFontDefault(void); // Get the default Font -RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) -RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height -RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) -RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' -RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked) -RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount, int type); // Load font data for further use -RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info -RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) -RLAPI void UnloadFont(Font font); // Unload font from GPU memory (VRAM) -RLAPI bool ExportFontAsCode(Font font, const char *fileName); // Export font as code file, returns true on success - -// Text drawing functions -RLAPI void DrawFPS(int posX, int posY); // Draw current FPS -RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) -RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters -RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) -RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) -RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint) - -// Text font info functions -RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks -RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font -RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font -RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found -RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found -RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found - -// Text codepoints management functions (unicode characters) -RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array -RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array -RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter -RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory -RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string -RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure -RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) - -// Text strings management functions (no UTF-8 strings, only byte chars) -// NOTE: Some strings allocate memory internally for returned strings, just be careful! -RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied -RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal -RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending -RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) -RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string -RLAPI char *TextReplace(const char *text, const char *replace, const char *by); // Replace text string (WARNING: memory must be freed!) -RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) -RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter -RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings -RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! -RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string -RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string -RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string -RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string -RLAPI const char *TextToSnake(const char *text); // Get Snake case notation version of provided string -RLAPI const char *TextToCamel(const char *text); // Get Camel case notation version of provided string - -RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) -RLAPI float TextToFloat(const char *text); // Get float value from text (negative values not supported) - -//------------------------------------------------------------------------------------ -// Basic 3d Shapes Drawing Functions (Module: models) -//------------------------------------------------------------------------------------ - -// Basic geometric 3D shapes drawing functions -RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space -RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line -RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space -RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) -RLAPI void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points -RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube -RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) -RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires -RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) -RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere -RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters -RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires -RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone -RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos -RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires -RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos -RLAPI void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw a capsule with the center of its sphere caps at startPos and endPos -RLAPI void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw capsule wireframe with the center of its sphere caps at startPos and endPos -RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ -RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line -RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) - -//------------------------------------------------------------------------------------ -// Model 3d Loading and Drawing Functions (Module: models) -//------------------------------------------------------------------------------------ - -// Model management functions -RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) -RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) -RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs) -RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) -RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) - -// Model drawing functions -RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) -RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters -RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) -RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters -RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points -RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters -RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) -RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture -RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source -RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation - -// Mesh management functions -RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids -RLAPI void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index -RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU -RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform -RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms -RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits -RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents -RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success -RLAPI bool ExportMeshAsCode(Mesh mesh, const char *fileName); // Export mesh as code file (.h) defining multiple arrays of vertex attributes - -// Mesh generation functions -RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh -RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) -RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh -RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) -RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) -RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh -RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh -RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh -RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh -RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data -RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data - -// Material loading/unloading functions -RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file -RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) -RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU) -RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) -RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) -RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh - -// Model animations loading/unloading functions -RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file -RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) -RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) -RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data -RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data -RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match - -// Collision detection functions -RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres -RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes -RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere -RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere -RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box -RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh -RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle -RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad - -//------------------------------------------------------------------------------------ -// Audio Loading and Playing Functions (Module: audio) -//------------------------------------------------------------------------------------ -typedef void (*AudioCallback)(void *bufferData, unsigned int frames); - -// Audio device management functions -RLAPI void InitAudioDevice(void); // Initialize audio device and context -RLAPI void CloseAudioDevice(void); // Close the audio device and context -RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully -RLAPI void SetMasterVolume(float volume); // Set master volume (listener) -RLAPI float GetMasterVolume(void); // Get master volume (listener) - -// Wave/Sound loading/unloading functions -RLAPI Wave LoadWave(const char *fileName); // Load wave data from file -RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' -RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters) -RLAPI Sound LoadSound(const char *fileName); // Load sound from file -RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data -RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data -RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) -RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data -RLAPI void UnloadWave(Wave wave); // Unload wave data -RLAPI void UnloadSound(Sound sound); // Unload sound -RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) -RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success -RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success - -// Wave/Sound management functions -RLAPI void PlaySound(Sound sound); // Play a sound -RLAPI void StopSound(Sound sound); // Stop playing a sound -RLAPI void PauseSound(Sound sound); // Pause a sound -RLAPI void ResumeSound(Sound sound); // Resume a paused sound -RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing -RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) -RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) -RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (0.5 is center) -RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave -RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range -RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format -RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a 32bit float data array -RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() - -// Music management functions -RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file -RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data -RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized) -RLAPI void UnloadMusicStream(Music music); // Unload music stream -RLAPI void PlayMusicStream(Music music); // Start music playing -RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing -RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming -RLAPI void StopMusicStream(Music music); // Stop music playing -RLAPI void PauseMusicStream(Music music); // Pause music playing -RLAPI void ResumeMusicStream(Music music); // Resume playing paused music -RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) -RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) -RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) -RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (0.5 is center) -RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) -RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) - -// AudioStream management functions -RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) -RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized) -RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory -RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data -RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill -RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream -RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream -RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream -RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing -RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream -RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) -RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) -RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan for audio stream (0.5 is centered) -RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams -RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data - -RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives the samples as 'float' -RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream - -RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' -RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline - -#if defined(__cplusplus) -} -#endif - -#endif // RAYLIB_H diff --git a/third_party/raylib/include/raymath.h b/third_party/raylib/include/raymath.h deleted file mode 100644 index 5b5e4c74ff6..00000000000 --- a/third_party/raylib/include/raymath.h +++ /dev/null @@ -1,2949 +0,0 @@ -/********************************************************************************************** -* -* raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions -* -* CONVENTIONS: -* - Matrix structure is defined as row-major (memory layout) but parameters naming AND all -* math operations performed by the library consider the structure as it was column-major -* It is like transposed versions of the matrices are used for all the maths -* It benefits some functions making them cache-friendly and also avoids matrix -* transpositions sometimes required by OpenGL -* Example: In memory order, row0 is [m0 m4 m8 m12] but in semantic math row0 is [m0 m1 m2 m3] -* - Functions are always self-contained, no function use another raymath function inside, -* required code is directly re-implemented inside -* - Functions input parameters are always received by value (2 unavoidable exceptions) -* - Functions use always a "result" variable for return (except C++ operators) -* - Functions are always defined inline -* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) -* - No compound literals used to make sure libray is compatible with C++ -* -* CONFIGURATION: -* #define RAYMATH_IMPLEMENTATION -* Generates the implementation of the library into the included file. -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation. -* -* #define RAYMATH_STATIC_INLINE -* Define static inline functions code, so #include header suffices for use. -* This may use up lots of memory. -* -* #define RAYMATH_DISABLE_CPP_OPERATORS -* Disables C++ operator overloads for raymath types. -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef RAYMATH_H -#define RAYMATH_H - -#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) - #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" -#endif - -// Function specifiers definition -#if defined(RAYMATH_IMPLEMENTATION) - #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) - #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll) - #elif defined(BUILD_LIBTYPE_SHARED) - #define RMAPI __attribute__((visibility("default"))) // We are building raylib as a Unix shared library (.so/.dylib) - #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) - #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) - #else - #define RMAPI extern inline // Provide external definition - #endif -#elif defined(RAYMATH_STATIC_INLINE) - #define RMAPI static inline // Functions may be inlined, no external out-of-line definition -#else - #if defined(__TINYC__) - #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) - #else - #define RMAPI inline // Functions may be inlined or external definition used - #endif -#endif - - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#ifndef PI - #define PI 3.14159265358979323846f -#endif - -#ifndef EPSILON - #define EPSILON 0.000001f -#endif - -#ifndef DEG2RAD - #define DEG2RAD (PI/180.0f) -#endif - -#ifndef RAD2DEG - #define RAD2DEG (180.0f/PI) -#endif - -// Get float vector for Matrix -#ifndef MatrixToFloat - #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) -#endif - -// Get float vector for Vector3 -#ifndef Vector3ToFloat - #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -#if !defined(RL_VECTOR2_TYPE) -// Vector2 type -typedef struct Vector2 { - float x; - float y; -} Vector2; -#define RL_VECTOR2_TYPE -#endif - -#if !defined(RL_VECTOR3_TYPE) -// Vector3 type -typedef struct Vector3 { - float x; - float y; - float z; -} Vector3; -#define RL_VECTOR3_TYPE -#endif - -#if !defined(RL_VECTOR4_TYPE) -// Vector4 type -typedef struct Vector4 { - float x; - float y; - float z; - float w; -} Vector4; -#define RL_VECTOR4_TYPE -#endif - -#if !defined(RL_QUATERNION_TYPE) -// Quaternion type -typedef Vector4 Quaternion; -#define RL_QUATERNION_TYPE -#endif - -#if !defined(RL_MATRIX_TYPE) -// Matrix type (OpenGL style 4x4 - right handed, column major) -typedef struct Matrix { - float m0, m4, m8, m12; // Matrix first row (4 components) - float m1, m5, m9, m13; // Matrix second row (4 components) - float m2, m6, m10, m14; // Matrix third row (4 components) - float m3, m7, m11, m15; // Matrix fourth row (4 components) -} Matrix; -#define RL_MATRIX_TYPE -#endif - -// NOTE: Helper types to be used instead of array return types for *ToFloat functions -typedef struct float3 { - float v[3]; -} float3; - -typedef struct float16 { - float v[16]; -} float16; - -#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Utils math -//---------------------------------------------------------------------------------- - -// Clamp float value -RMAPI float Clamp(float value, float min, float max) -{ - float result = (value < min)? min : value; - - if (result > max) result = max; - - return result; -} - -// Calculate linear interpolation between two floats -RMAPI float Lerp(float start, float end, float amount) -{ - float result = start + amount*(end - start); - - return result; -} - -// Normalize input value within input range -RMAPI float Normalize(float value, float start, float end) -{ - float result = (value - start)/(end - start); - - return result; -} - -// Remap input value within input range to output range -RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) -{ - float result = (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; - - return result; -} - -// Wrap input value from min to max -RMAPI float Wrap(float value, float min, float max) -{ - float result = value - (max - min)*floorf((value - min)/(max - min)); - - return result; -} - -// Check whether two given floats are almost equal -RMAPI int FloatEquals(float x, float y) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); - - return result; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vector2 math -//---------------------------------------------------------------------------------- - -// Vector with components value 0.0f -RMAPI Vector2 Vector2Zero(void) -{ - Vector2 result = { 0.0f, 0.0f }; - - return result; -} - -// Vector with components value 1.0f -RMAPI Vector2 Vector2One(void) -{ - Vector2 result = { 1.0f, 1.0f }; - - return result; -} - -// Add two vectors (v1 + v2) -RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x + v2.x, v1.y + v2.y }; - - return result; -} - -// Add vector and float value -RMAPI Vector2 Vector2AddValue(Vector2 v, float add) -{ - Vector2 result = { v.x + add, v.y + add }; - - return result; -} - -// Subtract two vectors (v1 - v2) -RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x - v2.x, v1.y - v2.y }; - - return result; -} - -// Subtract vector by float value -RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) -{ - Vector2 result = { v.x - sub, v.y - sub }; - - return result; -} - -// Calculate vector length -RMAPI float Vector2Length(Vector2 v) -{ - float result = sqrtf((v.x*v.x) + (v.y*v.y)); - - return result; -} - -// Calculate vector square length -RMAPI float Vector2LengthSqr(Vector2 v) -{ - float result = (v.x*v.x) + (v.y*v.y); - - return result; -} - -// Calculate two vectors dot product -RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) -{ - float result = (v1.x*v2.x + v1.y*v2.y); - - return result; -} - -// Calculate two vectors cross product -RMAPI float Vector2CrossProduct(Vector2 v1, Vector2 v2) -{ - float result = (v1.x*v2.y - v1.y*v2.x); - - return result; -} - -// Calculate distance between two vectors -RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) -{ - float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); - - return result; -} - -// Calculate square distance between two vectors -RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) -{ - float result = ((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); - - return result; -} - -// Calculate angle between two vectors -// NOTE: Angle is calculated from origin point (0, 0) -RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) -{ - float result = 0.0f; - - float dot = v1.x*v2.x + v1.y*v2.y; - float det = v1.x*v2.y - v1.y*v2.x; - - result = atan2f(det, dot); - - return result; -} - -// Calculate angle defined by a two vectors line -// NOTE: Parameters need to be normalized -// Current implementation should be aligned with glm::angle -RMAPI float Vector2LineAngle(Vector2 start, Vector2 end) -{ - float result = 0.0f; - - // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior - result = -atan2f(end.y - start.y, end.x - start.x); - - return result; -} - -// Scale vector (multiply by value) -RMAPI Vector2 Vector2Scale(Vector2 v, float scale) -{ - Vector2 result = { v.x*scale, v.y*scale }; - - return result; -} - -// Multiply vector by vector -RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x*v2.x, v1.y*v2.y }; - - return result; -} - -// Negate vector -RMAPI Vector2 Vector2Negate(Vector2 v) -{ - Vector2 result = { -v.x, -v.y }; - - return result; -} - -// Divide vector by vector -RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) -{ - Vector2 result = { v1.x/v2.x, v1.y/v2.y }; - - return result; -} - -// Normalize provided vector -RMAPI Vector2 Vector2Normalize(Vector2 v) -{ - Vector2 result = { 0 }; - float length = sqrtf((v.x*v.x) + (v.y*v.y)); - - if (length > 0) - { - float ilength = 1.0f/length; - result.x = v.x*ilength; - result.y = v.y*ilength; - } - - return result; -} - -// Transforms a Vector2 by a given Matrix -RMAPI Vector2 Vector2Transform(Vector2 v, Matrix mat) -{ - Vector2 result = { 0 }; - - float x = v.x; - float y = v.y; - float z = 0; - - result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; - result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; - - return result; -} - -// Calculate linear interpolation between two vectors -RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) -{ - Vector2 result = { 0 }; - - result.x = v1.x + amount*(v2.x - v1.x); - result.y = v1.y + amount*(v2.y - v1.y); - - return result; -} - -// Calculate reflected vector to normal -RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) -{ - Vector2 result = { 0 }; - - float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product - - result.x = v.x - (2.0f*normal.x)*dotProduct; - result.y = v.y - (2.0f*normal.y)*dotProduct; - - return result; -} - -// Get min value for each pair of components -RMAPI Vector2 Vector2Min(Vector2 v1, Vector2 v2) -{ - Vector2 result = { 0 }; - - result.x = fminf(v1.x, v2.x); - result.y = fminf(v1.y, v2.y); - - return result; -} - -// Get max value for each pair of components -RMAPI Vector2 Vector2Max(Vector2 v1, Vector2 v2) -{ - Vector2 result = { 0 }; - - result.x = fmaxf(v1.x, v2.x); - result.y = fmaxf(v1.y, v2.y); - - return result; -} - -// Rotate vector by angle -RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) -{ - Vector2 result = { 0 }; - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.x = v.x*cosres - v.y*sinres; - result.y = v.x*sinres + v.y*cosres; - - return result; -} - -// Move Vector towards target -RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) -{ - Vector2 result = { 0 }; - - float dx = target.x - v.x; - float dy = target.y - v.y; - float value = (dx*dx) + (dy*dy); - - if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; - - float dist = sqrtf(value); - - result.x = v.x + dx/dist*maxDistance; - result.y = v.y + dy/dist*maxDistance; - - return result; -} - -// Invert the given vector -RMAPI Vector2 Vector2Invert(Vector2 v) -{ - Vector2 result = { 1.0f/v.x, 1.0f/v.y }; - - return result; -} - -// Clamp the components of the vector between -// min and max values specified by the given vectors -RMAPI Vector2 Vector2Clamp(Vector2 v, Vector2 min, Vector2 max) -{ - Vector2 result = { 0 }; - - result.x = fminf(max.x, fmaxf(min.x, v.x)); - result.y = fminf(max.y, fmaxf(min.y, v.y)); - - return result; -} - -// Clamp the magnitude of the vector between two min and max values -RMAPI Vector2 Vector2ClampValue(Vector2 v, float min, float max) -{ - Vector2 result = v; - - float length = (v.x*v.x) + (v.y*v.y); - if (length > 0.0f) - { - length = sqrtf(length); - - float scale = 1; // By default, 1 as the neutral element. - if (length < min) - { - scale = min/length; - } - else if (length > max) - { - scale = max/length; - } - - result.x = v.x*scale; - result.y = v.y*scale; - } - - return result; -} - -// Check whether two given vectors are almost equal -RMAPI int Vector2Equals(Vector2 p, Vector2 q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))); - - return result; -} - -// Compute the direction of a refracted ray -// v: normalized direction of the incoming ray -// n: normalized normal vector of the interface of two optical media -// r: ratio of the refractive index of the medium from where the ray comes -// to the refractive index of the medium on the other side of the surface -RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) -{ - Vector2 result = { 0 }; - - float dot = v.x*n.x + v.y*n.y; - float d = 1.0f - r*r*(1.0f - dot*dot); - - if (d >= 0.0f) - { - d = sqrtf(d); - v.x = r*v.x - (r*dot + d)*n.x; - v.y = r*v.y - (r*dot + d)*n.y; - - result = v; - } - - return result; -} - - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vector3 math -//---------------------------------------------------------------------------------- - -// Vector with components value 0.0f -RMAPI Vector3 Vector3Zero(void) -{ - Vector3 result = { 0.0f, 0.0f, 0.0f }; - - return result; -} - -// Vector with components value 1.0f -RMAPI Vector3 Vector3One(void) -{ - Vector3 result = { 1.0f, 1.0f, 1.0f }; - - return result; -} - -// Add two vectors -RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; - - return result; -} - -// Add vector and float value -RMAPI Vector3 Vector3AddValue(Vector3 v, float add) -{ - Vector3 result = { v.x + add, v.y + add, v.z + add }; - - return result; -} - -// Subtract two vectors -RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; - - return result; -} - -// Subtract vector by float value -RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) -{ - Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; - - return result; -} - -// Multiply vector by scalar -RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) -{ - Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; - - return result; -} - -// Multiply vector by vector -RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; - - return result; -} - -// Calculate two vectors cross product -RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; - - return result; -} - -// Calculate one vector perpendicular vector -RMAPI Vector3 Vector3Perpendicular(Vector3 v) -{ - Vector3 result = { 0 }; - - float min = fabsf(v.x); - Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; - - if (fabsf(v.y) < min) - { - min = fabsf(v.y); - Vector3 tmp = {0.0f, 1.0f, 0.0f}; - cardinalAxis = tmp; - } - - if (fabsf(v.z) < min) - { - Vector3 tmp = {0.0f, 0.0f, 1.0f}; - cardinalAxis = tmp; - } - - // Cross product between vectors - result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; - result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; - result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; - - return result; -} - -// Calculate vector length -RMAPI float Vector3Length(const Vector3 v) -{ - float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - - return result; -} - -// Calculate vector square length -RMAPI float Vector3LengthSqr(const Vector3 v) -{ - float result = v.x*v.x + v.y*v.y + v.z*v.z; - - return result; -} - -// Calculate two vectors dot product -RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) -{ - float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - - return result; -} - -// Calculate distance between two vectors -RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) -{ - float result = 0.0f; - - float dx = v2.x - v1.x; - float dy = v2.y - v1.y; - float dz = v2.z - v1.z; - result = sqrtf(dx*dx + dy*dy + dz*dz); - - return result; -} - -// Calculate square distance between two vectors -RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) -{ - float result = 0.0f; - - float dx = v2.x - v1.x; - float dy = v2.y - v1.y; - float dz = v2.z - v1.z; - result = dx*dx + dy*dy + dz*dz; - - return result; -} - -// Calculate angle between two vectors -RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) -{ - float result = 0.0f; - - Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; - float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); - float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - result = atan2f(len, dot); - - return result; -} - -// Negate provided vector (invert direction) -RMAPI Vector3 Vector3Negate(Vector3 v) -{ - Vector3 result = { -v.x, -v.y, -v.z }; - - return result; -} - -// Divide vector by vector -RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) -{ - Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; - - return result; -} - -// Normalize provided vector -RMAPI Vector3 Vector3Normalize(Vector3 v) -{ - Vector3 result = v; - - float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length != 0.0f) - { - float ilength = 1.0f/length; - - result.x *= ilength; - result.y *= ilength; - result.z *= ilength; - } - - return result; -} - -//Calculate the projection of the vector v1 on to v2 -RMAPI Vector3 Vector3Project(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); - - float mag = v1dv2/v2dv2; - - result.x = v2.x*mag; - result.y = v2.y*mag; - result.z = v2.z*mag; - - return result; -} - -//Calculate the rejection of the vector v1 on to v2 -RMAPI Vector3 Vector3Reject(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); - float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); - - float mag = v1dv2/v2dv2; - - result.x = v1.x - (v2.x*mag); - result.y = v1.y - (v2.y*mag); - result.z = v1.z - (v2.z*mag); - - return result; -} - -// Orthonormalize provided vectors -// Makes vectors normalized and orthogonal to each other -// Gram-Schmidt function implementation -RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) -{ - float length = 0.0f; - float ilength = 0.0f; - - // Vector3Normalize(*v1); - Vector3 v = *v1; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - v1->x *= ilength; - v1->y *= ilength; - v1->z *= ilength; - - // Vector3CrossProduct(*v1, *v2) - Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; - - // Vector3Normalize(vn1); - v = vn1; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - vn1.x *= ilength; - vn1.y *= ilength; - vn1.z *= ilength; - - // Vector3CrossProduct(vn1, *v1) - Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; - - *v2 = vn2; -} - -// Transforms a Vector3 by a given Matrix -RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) -{ - Vector3 result = { 0 }; - - float x = v.x; - float y = v.y; - float z = v.z; - - result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; - result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; - result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; - - return result; -} - -// Transform a vector by quaternion rotation -RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) -{ - Vector3 result = { 0 }; - - result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); - result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); - result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); - - return result; -} - -// Rotates a vector around an axis -RMAPI Vector3 Vector3RotateByAxisAngle(Vector3 v, Vector3 axis, float angle) -{ - // Using Euler-Rodrigues Formula - // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula - - Vector3 result = v; - - // Vector3Normalize(axis); - float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - axis.x *= ilength; - axis.y *= ilength; - axis.z *= ilength; - - angle /= 2.0f; - float a = sinf(angle); - float b = axis.x*a; - float c = axis.y*a; - float d = axis.z*a; - a = cosf(angle); - Vector3 w = { b, c, d }; - - // Vector3CrossProduct(w, v) - Vector3 wv = { w.y*v.z - w.z*v.y, w.z*v.x - w.x*v.z, w.x*v.y - w.y*v.x }; - - // Vector3CrossProduct(w, wv) - Vector3 wwv = { w.y*wv.z - w.z*wv.y, w.z*wv.x - w.x*wv.z, w.x*wv.y - w.y*wv.x }; - - // Vector3Scale(wv, 2*a) - a *= 2; - wv.x *= a; - wv.y *= a; - wv.z *= a; - - // Vector3Scale(wwv, 2) - wwv.x *= 2; - wwv.y *= 2; - wwv.z *= 2; - - result.x += wv.x; - result.y += wv.y; - result.z += wv.z; - - result.x += wwv.x; - result.y += wwv.y; - result.z += wwv.z; - - return result; -} - -// Move Vector towards target -RMAPI Vector3 Vector3MoveTowards(Vector3 v, Vector3 target, float maxDistance) -{ - Vector3 result = { 0 }; - - float dx = target.x - v.x; - float dy = target.y - v.y; - float dz = target.z - v.z; - float value = (dx*dx) + (dy*dy) + (dz*dz); - - if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; - - float dist = sqrtf(value); - - result.x = v.x + dx/dist*maxDistance; - result.y = v.y + dy/dist*maxDistance; - result.z = v.z + dz/dist*maxDistance; - - return result; -} - -// Calculate linear interpolation between two vectors -RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) -{ - Vector3 result = { 0 }; - - result.x = v1.x + amount*(v2.x - v1.x); - result.y = v1.y + amount*(v2.y - v1.y); - result.z = v1.z + amount*(v2.z - v1.z); - - return result; -} - -// Calculate cubic hermite interpolation between two vectors and their tangents -// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic -RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount) -{ - Vector3 result = { 0 }; - - float amountPow2 = amount*amount; - float amountPow3 = amount*amount*amount; - - result.x = (2*amountPow3 - 3*amountPow2 + 1)*v1.x + (amountPow3 - 2*amountPow2 + amount)*tangent1.x + (-2*amountPow3 + 3*amountPow2)*v2.x + (amountPow3 - amountPow2)*tangent2.x; - result.y = (2*amountPow3 - 3*amountPow2 + 1)*v1.y + (amountPow3 - 2*amountPow2 + amount)*tangent1.y + (-2*amountPow3 + 3*amountPow2)*v2.y + (amountPow3 - amountPow2)*tangent2.y; - result.z = (2*amountPow3 - 3*amountPow2 + 1)*v1.z + (amountPow3 - 2*amountPow2 + amount)*tangent1.z + (-2*amountPow3 + 3*amountPow2)*v2.z + (amountPow3 - amountPow2)*tangent2.z; - - return result; -} - -// Calculate reflected vector to normal -RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) -{ - Vector3 result = { 0 }; - - // I is the original vector - // N is the normal of the incident plane - // R = I - (2*N*(DotProduct[I, N])) - - float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); - - result.x = v.x - (2.0f*normal.x)*dotProduct; - result.y = v.y - (2.0f*normal.y)*dotProduct; - result.z = v.z - (2.0f*normal.z)*dotProduct; - - return result; -} - -// Get min value for each pair of components -RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - result.x = fminf(v1.x, v2.x); - result.y = fminf(v1.y, v2.y); - result.z = fminf(v1.z, v2.z); - - return result; -} - -// Get max value for each pair of components -RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) -{ - Vector3 result = { 0 }; - - result.x = fmaxf(v1.x, v2.x); - result.y = fmaxf(v1.y, v2.y); - result.z = fmaxf(v1.z, v2.z); - - return result; -} - -// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) -// NOTE: Assumes P is on the plane of the triangle -RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) -{ - Vector3 result = { 0 }; - - Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) - Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) - Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) - float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) - float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) - float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) - float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) - float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) - - float denom = d00*d11 - d01*d01; - - result.y = (d11*d20 - d01*d21)/denom; - result.z = (d00*d21 - d01*d20)/denom; - result.x = 1.0f - (result.z + result.y); - - return result; -} - -// Projects a Vector3 from screen space into object space -// NOTE: We are avoiding calling other raymath functions despite available -RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) -{ - Vector3 result = { 0 }; - - // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it - Matrix matViewProj = { // MatrixMultiply(view, projection); - view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, - view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, - view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, - view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, - view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, - view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, - view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, - view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, - view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, - view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, - view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, - view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, - view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, - view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, - view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, - view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; - - // Calculate inverted matrix -> MatrixInvert(matViewProj); - // Cache the matrix values (speed optimization) - float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; - float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; - float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; - float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; - - float b00 = a00*a11 - a01*a10; - float b01 = a00*a12 - a02*a10; - float b02 = a00*a13 - a03*a10; - float b03 = a01*a12 - a02*a11; - float b04 = a01*a13 - a03*a11; - float b05 = a02*a13 - a03*a12; - float b06 = a20*a31 - a21*a30; - float b07 = a20*a32 - a22*a30; - float b08 = a20*a33 - a23*a30; - float b09 = a21*a32 - a22*a31; - float b10 = a21*a33 - a23*a31; - float b11 = a22*a33 - a23*a32; - - // Calculate the invert determinant (inlined to avoid double-caching) - float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); - - Matrix matViewProjInv = { - (a11*b11 - a12*b10 + a13*b09)*invDet, - (-a01*b11 + a02*b10 - a03*b09)*invDet, - (a31*b05 - a32*b04 + a33*b03)*invDet, - (-a21*b05 + a22*b04 - a23*b03)*invDet, - (-a10*b11 + a12*b08 - a13*b07)*invDet, - (a00*b11 - a02*b08 + a03*b07)*invDet, - (-a30*b05 + a32*b02 - a33*b01)*invDet, - (a20*b05 - a22*b02 + a23*b01)*invDet, - (a10*b10 - a11*b08 + a13*b06)*invDet, - (-a00*b10 + a01*b08 - a03*b06)*invDet, - (a30*b04 - a31*b02 + a33*b00)*invDet, - (-a20*b04 + a21*b02 - a23*b00)*invDet, - (-a10*b09 + a11*b07 - a12*b06)*invDet, - (a00*b09 - a01*b07 + a02*b06)*invDet, - (-a30*b03 + a31*b01 - a32*b00)*invDet, - (a20*b03 - a21*b01 + a22*b00)*invDet }; - - // Create quaternion from source point - Quaternion quat = { source.x, source.y, source.z, 1.0f }; - - // Multiply quat point by unprojecte matrix - Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) - matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, - matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, - matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, - matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; - - // Normalized world points in vectors - result.x = qtransformed.x/qtransformed.w; - result.y = qtransformed.y/qtransformed.w; - result.z = qtransformed.z/qtransformed.w; - - return result; -} - -// Get Vector3 as float array -RMAPI float3 Vector3ToFloatV(Vector3 v) -{ - float3 buffer = { 0 }; - - buffer.v[0] = v.x; - buffer.v[1] = v.y; - buffer.v[2] = v.z; - - return buffer; -} - -// Invert the given vector -RMAPI Vector3 Vector3Invert(Vector3 v) -{ - Vector3 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z }; - - return result; -} - -// Clamp the components of the vector between -// min and max values specified by the given vectors -RMAPI Vector3 Vector3Clamp(Vector3 v, Vector3 min, Vector3 max) -{ - Vector3 result = { 0 }; - - result.x = fminf(max.x, fmaxf(min.x, v.x)); - result.y = fminf(max.y, fmaxf(min.y, v.y)); - result.z = fminf(max.z, fmaxf(min.z, v.z)); - - return result; -} - -// Clamp the magnitude of the vector between two values -RMAPI Vector3 Vector3ClampValue(Vector3 v, float min, float max) -{ - Vector3 result = v; - - float length = (v.x*v.x) + (v.y*v.y) + (v.z*v.z); - if (length > 0.0f) - { - length = sqrtf(length); - - float scale = 1; // By default, 1 as the neutral element. - if (length < min) - { - scale = min/length; - } - else if (length > max) - { - scale = max/length; - } - - result.x = v.x*scale; - result.y = v.y*scale; - result.z = v.z*scale; - } - - return result; -} - -// Check whether two given vectors are almost equal -RMAPI int Vector3Equals(Vector3 p, Vector3 q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))); - - return result; -} - -// Compute the direction of a refracted ray -// v: normalized direction of the incoming ray -// n: normalized normal vector of the interface of two optical media -// r: ratio of the refractive index of the medium from where the ray comes -// to the refractive index of the medium on the other side of the surface -RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) -{ - Vector3 result = { 0 }; - - float dot = v.x*n.x + v.y*n.y + v.z*n.z; - float d = 1.0f - r*r*(1.0f - dot*dot); - - if (d >= 0.0f) - { - d = sqrtf(d); - v.x = r*v.x - (r*dot + d)*n.x; - v.y = r*v.y - (r*dot + d)*n.y; - v.z = r*v.z - (r*dot + d)*n.z; - - result = v; - } - - return result; -} - - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vector4 math -//---------------------------------------------------------------------------------- - -RMAPI Vector4 Vector4Zero(void) -{ - Vector4 result = { 0.0f, 0.0f, 0.0f, 0.0f }; - return result; -} - -RMAPI Vector4 Vector4One(void) -{ - Vector4 result = { 1.0f, 1.0f, 1.0f, 1.0f }; - return result; -} - -RMAPI Vector4 Vector4Add(Vector4 v1, Vector4 v2) -{ - Vector4 result = { - v1.x + v2.x, - v1.y + v2.y, - v1.z + v2.z, - v1.w + v2.w - }; - return result; -} - -RMAPI Vector4 Vector4AddValue(Vector4 v, float add) -{ - Vector4 result = { - v.x + add, - v.y + add, - v.z + add, - v.w + add - }; - return result; -} - -RMAPI Vector4 Vector4Subtract(Vector4 v1, Vector4 v2) -{ - Vector4 result = { - v1.x - v2.x, - v1.y - v2.y, - v1.z - v2.z, - v1.w - v2.w - }; - return result; -} - -RMAPI Vector4 Vector4SubtractValue(Vector4 v, float add) -{ - Vector4 result = { - v.x - add, - v.y - add, - v.z - add, - v.w - add - }; - return result; -} - -RMAPI float Vector4Length(Vector4 v) -{ - float result = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); - return result; -} - -RMAPI float Vector4LengthSqr(Vector4 v) -{ - float result = (v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w); - return result; -} - -RMAPI float Vector4DotProduct(Vector4 v1, Vector4 v2) -{ - float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w); - return result; -} - -// Calculate distance between two vectors -RMAPI float Vector4Distance(Vector4 v1, Vector4 v2) -{ - float result = sqrtf( - (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + - (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w)); - return result; -} - -// Calculate square distance between two vectors -RMAPI float Vector4DistanceSqr(Vector4 v1, Vector4 v2) -{ - float result = - (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + - (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w); - - return result; -} - -RMAPI Vector4 Vector4Scale(Vector4 v, float scale) -{ - Vector4 result = { v.x*scale, v.y*scale, v.z*scale, v.w*scale }; - return result; -} - -// Multiply vector by vector -RMAPI Vector4 Vector4Multiply(Vector4 v1, Vector4 v2) -{ - Vector4 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z, v1.w*v2.w }; - return result; -} - -// Negate vector -RMAPI Vector4 Vector4Negate(Vector4 v) -{ - Vector4 result = { -v.x, -v.y, -v.z, -v.w }; - return result; -} - -// Divide vector by vector -RMAPI Vector4 Vector4Divide(Vector4 v1, Vector4 v2) -{ - Vector4 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z, v1.w/v2.w }; - return result; -} - -// Normalize provided vector -RMAPI Vector4 Vector4Normalize(Vector4 v) -{ - Vector4 result = { 0 }; - float length = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); - - if (length > 0) - { - float ilength = 1.0f/length; - result.x = v.x*ilength; - result.y = v.y*ilength; - result.z = v.z*ilength; - result.w = v.w*ilength; - } - - return result; -} - -// Get min value for each pair of components -RMAPI Vector4 Vector4Min(Vector4 v1, Vector4 v2) -{ - Vector4 result = { 0 }; - - result.x = fminf(v1.x, v2.x); - result.y = fminf(v1.y, v2.y); - result.z = fminf(v1.z, v2.z); - result.w = fminf(v1.w, v2.w); - - return result; -} - -// Get max value for each pair of components -RMAPI Vector4 Vector4Max(Vector4 v1, Vector4 v2) -{ - Vector4 result = { 0 }; - - result.x = fmaxf(v1.x, v2.x); - result.y = fmaxf(v1.y, v2.y); - result.z = fmaxf(v1.z, v2.z); - result.w = fmaxf(v1.w, v2.w); - - return result; -} - -// Calculate linear interpolation between two vectors -RMAPI Vector4 Vector4Lerp(Vector4 v1, Vector4 v2, float amount) -{ - Vector4 result = { 0 }; - - result.x = v1.x + amount*(v2.x - v1.x); - result.y = v1.y + amount*(v2.y - v1.y); - result.z = v1.z + amount*(v2.z - v1.z); - result.w = v1.w + amount*(v2.w - v1.w); - - return result; -} - -// Move Vector towards target -RMAPI Vector4 Vector4MoveTowards(Vector4 v, Vector4 target, float maxDistance) -{ - Vector4 result = { 0 }; - - float dx = target.x - v.x; - float dy = target.y - v.y; - float dz = target.z - v.z; - float dw = target.w - v.w; - float value = (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw); - - if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; - - float dist = sqrtf(value); - - result.x = v.x + dx/dist*maxDistance; - result.y = v.y + dy/dist*maxDistance; - result.z = v.z + dz/dist*maxDistance; - result.w = v.w + dw/dist*maxDistance; - - return result; -} - -// Invert the given vector -RMAPI Vector4 Vector4Invert(Vector4 v) -{ - Vector4 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z, 1.0f/v.w }; - return result; -} - -// Check whether two given vectors are almost equal -RMAPI int Vector4Equals(Vector4 p, Vector4 q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && - ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w))))); - return result; -} - - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Matrix math -//---------------------------------------------------------------------------------- - -// Compute matrix determinant -RMAPI float MatrixDeterminant(Matrix mat) -{ - float result = 0.0f; - - // Cache the matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; - - result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + - a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + - a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + - a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + - a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + - a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; - - return result; -} - -// Get the trace of the matrix (sum of the values along the diagonal) -RMAPI float MatrixTrace(Matrix mat) -{ - float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); - - return result; -} - -// Transposes provided matrix -RMAPI Matrix MatrixTranspose(Matrix mat) -{ - Matrix result = { 0 }; - - result.m0 = mat.m0; - result.m1 = mat.m4; - result.m2 = mat.m8; - result.m3 = mat.m12; - result.m4 = mat.m1; - result.m5 = mat.m5; - result.m6 = mat.m9; - result.m7 = mat.m13; - result.m8 = mat.m2; - result.m9 = mat.m6; - result.m10 = mat.m10; - result.m11 = mat.m14; - result.m12 = mat.m3; - result.m13 = mat.m7; - result.m14 = mat.m11; - result.m15 = mat.m15; - - return result; -} - -// Invert provided matrix -RMAPI Matrix MatrixInvert(Matrix mat) -{ - Matrix result = { 0 }; - - // Cache the matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; - - float b00 = a00*a11 - a01*a10; - float b01 = a00*a12 - a02*a10; - float b02 = a00*a13 - a03*a10; - float b03 = a01*a12 - a02*a11; - float b04 = a01*a13 - a03*a11; - float b05 = a02*a13 - a03*a12; - float b06 = a20*a31 - a21*a30; - float b07 = a20*a32 - a22*a30; - float b08 = a20*a33 - a23*a30; - float b09 = a21*a32 - a22*a31; - float b10 = a21*a33 - a23*a31; - float b11 = a22*a33 - a23*a32; - - // Calculate the invert determinant (inlined to avoid double-caching) - float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); - - result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; - result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; - result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; - result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; - result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; - result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; - result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; - result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; - result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; - result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; - result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; - result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; - result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; - result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; - result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; - result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; - - return result; -} - -// Get identity matrix -RMAPI Matrix MatrixIdentity(void) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Add two matrices -RMAPI Matrix MatrixAdd(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0 + right.m0; - result.m1 = left.m1 + right.m1; - result.m2 = left.m2 + right.m2; - result.m3 = left.m3 + right.m3; - result.m4 = left.m4 + right.m4; - result.m5 = left.m5 + right.m5; - result.m6 = left.m6 + right.m6; - result.m7 = left.m7 + right.m7; - result.m8 = left.m8 + right.m8; - result.m9 = left.m9 + right.m9; - result.m10 = left.m10 + right.m10; - result.m11 = left.m11 + right.m11; - result.m12 = left.m12 + right.m12; - result.m13 = left.m13 + right.m13; - result.m14 = left.m14 + right.m14; - result.m15 = left.m15 + right.m15; - - return result; -} - -// Subtract two matrices (left - right) -RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0 - right.m0; - result.m1 = left.m1 - right.m1; - result.m2 = left.m2 - right.m2; - result.m3 = left.m3 - right.m3; - result.m4 = left.m4 - right.m4; - result.m5 = left.m5 - right.m5; - result.m6 = left.m6 - right.m6; - result.m7 = left.m7 - right.m7; - result.m8 = left.m8 - right.m8; - result.m9 = left.m9 - right.m9; - result.m10 = left.m10 - right.m10; - result.m11 = left.m11 - right.m11; - result.m12 = left.m12 - right.m12; - result.m13 = left.m13 - right.m13; - result.m14 = left.m14 - right.m14; - result.m15 = left.m15 - right.m15; - - return result; -} - -// Get two matrix multiplication -// NOTE: When multiplying matrices... the order matters! -RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; - result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; - result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; - result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; - result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; - result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; - result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; - result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; - result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; - result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; - result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; - result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; - result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; - result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; - result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; - result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; - - return result; -} - -// Get translation matrix -RMAPI Matrix MatrixTranslate(float x, float y, float z) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, x, - 0.0f, 1.0f, 0.0f, y, - 0.0f, 0.0f, 1.0f, z, - 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Create rotation matrix from axis and angle -// NOTE: Angle should be provided in radians -RMAPI Matrix MatrixRotate(Vector3 axis, float angle) -{ - Matrix result = { 0 }; - - float x = axis.x, y = axis.y, z = axis.z; - - float lengthSquared = x*x + y*y + z*z; - - if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) - { - float ilength = 1.0f/sqrtf(lengthSquared); - x *= ilength; - y *= ilength; - z *= ilength; - } - - float sinres = sinf(angle); - float cosres = cosf(angle); - float t = 1.0f - cosres; - - result.m0 = x*x*t + cosres; - result.m1 = y*x*t + z*sinres; - result.m2 = z*x*t - y*sinres; - result.m3 = 0.0f; - - result.m4 = x*y*t - z*sinres; - result.m5 = y*y*t + cosres; - result.m6 = z*y*t + x*sinres; - result.m7 = 0.0f; - - result.m8 = x*z*t + y*sinres; - result.m9 = y*z*t - x*sinres; - result.m10 = z*z*t + cosres; - result.m11 = 0.0f; - - result.m12 = 0.0f; - result.m13 = 0.0f; - result.m14 = 0.0f; - result.m15 = 1.0f; - - return result; -} - -// Get x-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateX(float angle) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.m5 = cosres; - result.m6 = sinres; - result.m9 = -sinres; - result.m10 = cosres; - - return result; -} - -// Get y-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateY(float angle) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.m0 = cosres; - result.m2 = -sinres; - result.m8 = sinres; - result.m10 = cosres; - - return result; -} - -// Get z-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateZ(float angle) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float cosres = cosf(angle); - float sinres = sinf(angle); - - result.m0 = cosres; - result.m1 = sinres; - result.m4 = -sinres; - result.m5 = cosres; - - return result; -} - - -// Get xyz-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateXYZ(Vector3 angle) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float cosz = cosf(-angle.z); - float sinz = sinf(-angle.z); - float cosy = cosf(-angle.y); - float siny = sinf(-angle.y); - float cosx = cosf(-angle.x); - float sinx = sinf(-angle.x); - - result.m0 = cosz*cosy; - result.m1 = (cosz*siny*sinx) - (sinz*cosx); - result.m2 = (cosz*siny*cosx) + (sinz*sinx); - - result.m4 = sinz*cosy; - result.m5 = (sinz*siny*sinx) + (cosz*cosx); - result.m6 = (sinz*siny*cosx) - (cosz*sinx); - - result.m8 = -siny; - result.m9 = cosy*sinx; - result.m10= cosy*cosx; - - return result; -} - -// Get zyx-rotation matrix -// NOTE: Angle must be provided in radians -RMAPI Matrix MatrixRotateZYX(Vector3 angle) -{ - Matrix result = { 0 }; - - float cz = cosf(angle.z); - float sz = sinf(angle.z); - float cy = cosf(angle.y); - float sy = sinf(angle.y); - float cx = cosf(angle.x); - float sx = sinf(angle.x); - - result.m0 = cz*cy; - result.m4 = cz*sy*sx - cx*sz; - result.m8 = sz*sx + cz*cx*sy; - result.m12 = 0; - - result.m1 = cy*sz; - result.m5 = cz*cx + sz*sy*sx; - result.m9 = cx*sz*sy - cz*sx; - result.m13 = 0; - - result.m2 = -sy; - result.m6 = cy*sx; - result.m10 = cy*cx; - result.m14 = 0; - - result.m3 = 0; - result.m7 = 0; - result.m11 = 0; - result.m15 = 1; - - return result; -} - -// Get scaling matrix -RMAPI Matrix MatrixScale(float x, float y, float z) -{ - Matrix result = { x, 0.0f, 0.0f, 0.0f, - 0.0f, y, 0.0f, 0.0f, - 0.0f, 0.0f, z, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Get perspective projection matrix -RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) -{ - Matrix result = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(farPlane - nearPlane); - - result.m0 = ((float)nearPlane*2.0f)/rl; - result.m1 = 0.0f; - result.m2 = 0.0f; - result.m3 = 0.0f; - - result.m4 = 0.0f; - result.m5 = ((float)nearPlane*2.0f)/tb; - result.m6 = 0.0f; - result.m7 = 0.0f; - - result.m8 = ((float)right + (float)left)/rl; - result.m9 = ((float)top + (float)bottom)/tb; - result.m10 = -((float)farPlane + (float)nearPlane)/fn; - result.m11 = -1.0f; - - result.m12 = 0.0f; - result.m13 = 0.0f; - result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; - result.m15 = 0.0f; - - return result; -} - -// Get perspective projection matrix -// NOTE: Fovy angle must be provided in radians -RMAPI Matrix MatrixPerspective(double fovY, double aspect, double nearPlane, double farPlane) -{ - Matrix result = { 0 }; - - double top = nearPlane*tan(fovY*0.5); - double bottom = -top; - double right = top*aspect; - double left = -right; - - // MatrixFrustum(-right, right, -top, top, near, far); - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(farPlane - nearPlane); - - result.m0 = ((float)nearPlane*2.0f)/rl; - result.m5 = ((float)nearPlane*2.0f)/tb; - result.m8 = ((float)right + (float)left)/rl; - result.m9 = ((float)top + (float)bottom)/tb; - result.m10 = -((float)farPlane + (float)nearPlane)/fn; - result.m11 = -1.0f; - result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; - - return result; -} - -// Get orthographic projection matrix -RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane) -{ - Matrix result = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(farPlane - nearPlane); - - result.m0 = 2.0f/rl; - result.m1 = 0.0f; - result.m2 = 0.0f; - result.m3 = 0.0f; - result.m4 = 0.0f; - result.m5 = 2.0f/tb; - result.m6 = 0.0f; - result.m7 = 0.0f; - result.m8 = 0.0f; - result.m9 = 0.0f; - result.m10 = -2.0f/fn; - result.m11 = 0.0f; - result.m12 = -((float)left + (float)right)/rl; - result.m13 = -((float)top + (float)bottom)/tb; - result.m14 = -((float)farPlane + (float)nearPlane)/fn; - result.m15 = 1.0f; - - return result; -} - -// Get camera look-at matrix (view matrix) -RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) -{ - Matrix result = { 0 }; - - float length = 0.0f; - float ilength = 0.0f; - - // Vector3Subtract(eye, target) - Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; - - // Vector3Normalize(vz) - Vector3 v = vz; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - vz.x *= ilength; - vz.y *= ilength; - vz.z *= ilength; - - // Vector3CrossProduct(up, vz) - Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; - - // Vector3Normalize(x) - v = vx; - length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - vx.x *= ilength; - vx.y *= ilength; - vx.z *= ilength; - - // Vector3CrossProduct(vz, vx) - Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; - - result.m0 = vx.x; - result.m1 = vy.x; - result.m2 = vz.x; - result.m3 = 0.0f; - result.m4 = vx.y; - result.m5 = vy.y; - result.m6 = vz.y; - result.m7 = 0.0f; - result.m8 = vx.z; - result.m9 = vy.z; - result.m10 = vz.z; - result.m11 = 0.0f; - result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) - result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) - result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) - result.m15 = 1.0f; - - return result; -} - -// Get float array of matrix data -RMAPI float16 MatrixToFloatV(Matrix mat) -{ - float16 result = { 0 }; - - result.v[0] = mat.m0; - result.v[1] = mat.m1; - result.v[2] = mat.m2; - result.v[3] = mat.m3; - result.v[4] = mat.m4; - result.v[5] = mat.m5; - result.v[6] = mat.m6; - result.v[7] = mat.m7; - result.v[8] = mat.m8; - result.v[9] = mat.m9; - result.v[10] = mat.m10; - result.v[11] = mat.m11; - result.v[12] = mat.m12; - result.v[13] = mat.m13; - result.v[14] = mat.m14; - result.v[15] = mat.m15; - - return result; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Quaternion math -//---------------------------------------------------------------------------------- - -// Add two quaternions -RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) -{ - Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; - - return result; -} - -// Add quaternion and float value -RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) -{ - Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; - - return result; -} - -// Subtract two quaternions -RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) -{ - Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; - - return result; -} - -// Subtract quaternion and float value -RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) -{ - Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; - - return result; -} - -// Get identity quaternion -RMAPI Quaternion QuaternionIdentity(void) -{ - Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; - - return result; -} - -// Computes the length of a quaternion -RMAPI float QuaternionLength(Quaternion q) -{ - float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - - return result; -} - -// Normalize provided quaternion -RMAPI Quaternion QuaternionNormalize(Quaternion q) -{ - Quaternion result = { 0 }; - - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - - return result; -} - -// Invert provided quaternion -RMAPI Quaternion QuaternionInvert(Quaternion q) -{ - Quaternion result = q; - - float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; - - if (lengthSq != 0.0f) - { - float invLength = 1.0f/lengthSq; - - result.x *= -invLength; - result.y *= -invLength; - result.z *= -invLength; - result.w *= invLength; - } - - return result; -} - -// Calculate two quaternion multiplication -RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) -{ - Quaternion result = { 0 }; - - float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; - float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; - - result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; - result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; - result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; - result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; - - return result; -} - -// Scale quaternion by float value -RMAPI Quaternion QuaternionScale(Quaternion q, float mul) -{ - Quaternion result = { 0 }; - - result.x = q.x*mul; - result.y = q.y*mul; - result.z = q.z*mul; - result.w = q.w*mul; - - return result; -} - -// Divide two quaternions -RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) -{ - Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; - - return result; -} - -// Calculate linear interpolation between two quaternions -RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) -{ - Quaternion result = { 0 }; - - result.x = q1.x + amount*(q2.x - q1.x); - result.y = q1.y + amount*(q2.y - q1.y); - result.z = q1.z + amount*(q2.z - q1.z); - result.w = q1.w + amount*(q2.w - q1.w); - - return result; -} - -// Calculate slerp-optimized interpolation between two quaternions -RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) -{ - Quaternion result = { 0 }; - - // QuaternionLerp(q1, q2, amount) - result.x = q1.x + amount*(q2.x - q1.x); - result.y = q1.y + amount*(q2.y - q1.y); - result.z = q1.z + amount*(q2.z - q1.z); - result.w = q1.w + amount*(q2.w - q1.w); - - // QuaternionNormalize(q); - Quaternion q = result; - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - - return result; -} - -// Calculates spherical linear interpolation between two quaternions -RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) -{ - Quaternion result = { 0 }; - -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; - - if (cosHalfTheta < 0) - { - q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; - cosHalfTheta = -cosHalfTheta; - } - - if (fabsf(cosHalfTheta) >= 1.0f) result = q1; - else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); - else - { - float halfTheta = acosf(cosHalfTheta); - float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); - - if (fabsf(sinHalfTheta) < EPSILON) - { - result.x = (q1.x*0.5f + q2.x*0.5f); - result.y = (q1.y*0.5f + q2.y*0.5f); - result.z = (q1.z*0.5f + q2.z*0.5f); - result.w = (q1.w*0.5f + q2.w*0.5f); - } - else - { - float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; - float ratioB = sinf(amount*halfTheta)/sinHalfTheta; - - result.x = (q1.x*ratioA + q2.x*ratioB); - result.y = (q1.y*ratioA + q2.y*ratioB); - result.z = (q1.z*ratioA + q2.z*ratioB); - result.w = (q1.w*ratioA + q2.w*ratioB); - } - } - - return result; -} - -// Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm -// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic -RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t) -{ - float t2 = t*t; - float t3 = t2*t; - float h00 = 2*t3 - 3*t2 + 1; - float h10 = t3 - 2*t2 + t; - float h01 = -2*t3 + 3*t2; - float h11 = t3 - t2; - - Quaternion p0 = QuaternionScale(q1, h00); - Quaternion m0 = QuaternionScale(outTangent1, h10); - Quaternion p1 = QuaternionScale(q2, h01); - Quaternion m1 = QuaternionScale(inTangent2, h11); - - Quaternion result = { 0 }; - - result = QuaternionAdd(p0, m0); - result = QuaternionAdd(result, p1); - result = QuaternionAdd(result, m1); - result = QuaternionNormalize(result); - - return result; -} - -// Calculate quaternion based on the rotation from one vector to another -RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) -{ - Quaternion result = { 0 }; - - float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) - Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) - - result.x = cross.x; - result.y = cross.y; - result.z = cross.z; - result.w = 1.0f + cos2Theta; - - // QuaternionNormalize(q); - // NOTE: Normalize to essentially nlerp the original and identity to 0.5 - Quaternion q = result; - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - - return result; -} - -// Get a quaternion for a given rotation matrix -RMAPI Quaternion QuaternionFromMatrix(Matrix mat) -{ - Quaternion result = { 0 }; - - float fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10; - float fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10; - float fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10; - float fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5; - - int biggestIndex = 0; - float fourBiggestSquaredMinus1 = fourWSquaredMinus1; - if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourXSquaredMinus1; - biggestIndex = 1; - } - - if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourYSquaredMinus1; - biggestIndex = 2; - } - - if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) - { - fourBiggestSquaredMinus1 = fourZSquaredMinus1; - biggestIndex = 3; - } - - float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f)*0.5f; - float mult = 0.25f/biggestVal; - - switch (biggestIndex) - { - case 0: - result.w = biggestVal; - result.x = (mat.m6 - mat.m9)*mult; - result.y = (mat.m8 - mat.m2)*mult; - result.z = (mat.m1 - mat.m4)*mult; - break; - case 1: - result.x = biggestVal; - result.w = (mat.m6 - mat.m9)*mult; - result.y = (mat.m1 + mat.m4)*mult; - result.z = (mat.m8 + mat.m2)*mult; - break; - case 2: - result.y = biggestVal; - result.w = (mat.m8 - mat.m2)*mult; - result.x = (mat.m1 + mat.m4)*mult; - result.z = (mat.m6 + mat.m9)*mult; - break; - case 3: - result.z = biggestVal; - result.w = (mat.m1 - mat.m4)*mult; - result.x = (mat.m8 + mat.m2)*mult; - result.y = (mat.m6 + mat.m9)*mult; - break; - } - - return result; -} - -// Get a matrix for a given quaternion -RMAPI Matrix QuaternionToMatrix(Quaternion q) -{ - Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() - - float a2 = q.x*q.x; - float b2 = q.y*q.y; - float c2 = q.z*q.z; - float ac = q.x*q.z; - float ab = q.x*q.y; - float bc = q.y*q.z; - float ad = q.w*q.x; - float bd = q.w*q.y; - float cd = q.w*q.z; - - result.m0 = 1 - 2*(b2 + c2); - result.m1 = 2*(ab + cd); - result.m2 = 2*(ac - bd); - - result.m4 = 2*(ab - cd); - result.m5 = 1 - 2*(a2 + c2); - result.m6 = 2*(bc + ad); - - result.m8 = 2*(ac + bd); - result.m9 = 2*(bc - ad); - result.m10 = 1 - 2*(a2 + b2); - - return result; -} - -// Get rotation quaternion for an angle and axis -// NOTE: Angle must be provided in radians -RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) -{ - Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; - - float axisLength = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); - - if (axisLength != 0.0f) - { - angle *= 0.5f; - - float length = 0.0f; - float ilength = 0.0f; - - // Vector3Normalize(axis) - length = axisLength; - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - axis.x *= ilength; - axis.y *= ilength; - axis.z *= ilength; - - float sinres = sinf(angle); - float cosres = cosf(angle); - - result.x = axis.x*sinres; - result.y = axis.y*sinres; - result.z = axis.z*sinres; - result.w = cosres; - - // QuaternionNormalize(q); - Quaternion q = result; - length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - ilength = 1.0f/length; - result.x = q.x*ilength; - result.y = q.y*ilength; - result.z = q.z*ilength; - result.w = q.w*ilength; - } - - return result; -} - -// Get the rotation angle and axis for a given quaternion -RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) -{ - if (fabsf(q.w) > 1.0f) - { - // QuaternionNormalize(q); - float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); - if (length == 0.0f) length = 1.0f; - float ilength = 1.0f/length; - - q.x = q.x*ilength; - q.y = q.y*ilength; - q.z = q.z*ilength; - q.w = q.w*ilength; - } - - Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; - float resAngle = 2.0f*acosf(q.w); - float den = sqrtf(1.0f - q.w*q.w); - - if (den > EPSILON) - { - resAxis.x = q.x/den; - resAxis.y = q.y/den; - resAxis.z = q.z/den; - } - else - { - // This occurs when the angle is zero. - // Not a problem: just set an arbitrary normalized axis. - resAxis.x = 1.0f; - } - - *outAxis = resAxis; - *outAngle = resAngle; -} - -// Get the quaternion equivalent to Euler angles -// NOTE: Rotation order is ZYX -RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) -{ - Quaternion result = { 0 }; - - float x0 = cosf(pitch*0.5f); - float x1 = sinf(pitch*0.5f); - float y0 = cosf(yaw*0.5f); - float y1 = sinf(yaw*0.5f); - float z0 = cosf(roll*0.5f); - float z1 = sinf(roll*0.5f); - - result.x = x1*y0*z0 - x0*y1*z1; - result.y = x0*y1*z0 + x1*y0*z1; - result.z = x0*y0*z1 - x1*y1*z0; - result.w = x0*y0*z0 + x1*y1*z1; - - return result; -} - -// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) -// NOTE: Angles are returned in a Vector3 struct in radians -RMAPI Vector3 QuaternionToEuler(Quaternion q) -{ - Vector3 result = { 0 }; - - // Roll (x-axis rotation) - float x0 = 2.0f*(q.w*q.x + q.y*q.z); - float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); - result.x = atan2f(x0, x1); - - // Pitch (y-axis rotation) - float y0 = 2.0f*(q.w*q.y - q.z*q.x); - y0 = y0 > 1.0f ? 1.0f : y0; - y0 = y0 < -1.0f ? -1.0f : y0; - result.y = asinf(y0); - - // Yaw (z-axis rotation) - float z0 = 2.0f*(q.w*q.z + q.x*q.y); - float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); - result.z = atan2f(z0, z1); - - return result; -} - -// Transform a quaternion given a transformation matrix -RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) -{ - Quaternion result = { 0 }; - - result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; - result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; - result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; - result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; - - return result; -} - -// Check whether two given quaternions are almost equal -RMAPI int QuaternionEquals(Quaternion p, Quaternion q) -{ -#if !defined(EPSILON) - #define EPSILON 0.000001f -#endif - - int result = (((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && - ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))) || - (((fabsf(p.x + q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && - ((fabsf(p.y + q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && - ((fabsf(p.z + q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && - ((fabsf(p.w + q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))); - - return result; -} - -// Decompose a transformation matrix into its rotational, translational and scaling components -RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) -{ - // Extract translation. - translation->x = mat.m12; - translation->y = mat.m13; - translation->z = mat.m14; - - // Extract upper-left for determinant computation - const float a = mat.m0; - const float b = mat.m4; - const float c = mat.m8; - const float d = mat.m1; - const float e = mat.m5; - const float f = mat.m9; - const float g = mat.m2; - const float h = mat.m6; - const float i = mat.m10; - const float A = e*i - f*h; - const float B = f*g - d*i; - const float C = d*h - e*g; - - // Extract scale - const float det = a*A + b*B + c*C; - Vector3 abc = { a, b, c }; - Vector3 def = { d, e, f }; - Vector3 ghi = { g, h, i }; - - float scalex = Vector3Length(abc); - float scaley = Vector3Length(def); - float scalez = Vector3Length(ghi); - Vector3 s = { scalex, scaley, scalez }; - - if (det < 0) s = Vector3Negate(s); - - *scale = s; - - // Remove scale from the matrix if it is not close to zero - Matrix clone = mat; - if (!FloatEquals(det, 0)) - { - clone.m0 /= s.x; - clone.m4 /= s.x; - clone.m8 /= s.x; - clone.m1 /= s.y; - clone.m5 /= s.y; - clone.m9 /= s.y; - clone.m2 /= s.z; - clone.m6 /= s.z; - clone.m10 /= s.z; - - // Extract rotation - *rotation = QuaternionFromMatrix(clone); - } - else - { - // Set to identity if close to zero - *rotation = QuaternionIdentity(); - } -} - -#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) - -// Optional C++ math operators -//------------------------------------------------------------------------------- - -// Vector2 operators -static constexpr Vector2 Vector2Zeros = { 0, 0 }; -static constexpr Vector2 Vector2Ones = { 1, 1 }; -static constexpr Vector2 Vector2UnitX = { 1, 0 }; -static constexpr Vector2 Vector2UnitY = { 0, 1 }; - -inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Add(lhs, rhs); -} - -inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Add(lhs, rhs); - return lhs; -} - -inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Subtract(lhs, rhs); -} - -inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Subtract(lhs, rhs); - return lhs; -} - -inline Vector2 operator * (const Vector2& lhs, const float& rhs) -{ - return Vector2Scale(lhs, rhs); -} - -inline const Vector2& operator *= (Vector2& lhs, const float& rhs) -{ - lhs = Vector2Scale(lhs, rhs); - return lhs; -} - -inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Multiply(lhs, rhs); -} - -inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Multiply(lhs, rhs); - return lhs; -} - -inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) -{ - return Vector2Transform(lhs, rhs); -} - -inline const Vector2& operator *= (Vector2& lhs, const Matrix& rhs) -{ - lhs = Vector2Transform(lhs, rhs); - return lhs; -} - -inline Vector2 operator / (const Vector2& lhs, const float& rhs) -{ - return Vector2Scale(lhs, 1.0f / rhs); -} - -inline const Vector2& operator /= (Vector2& lhs, const float& rhs) -{ - lhs = Vector2Scale(lhs, 1.0f / rhs); - return lhs; -} - -inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) -{ - return Vector2Divide(lhs, rhs); -} - -inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) -{ - lhs = Vector2Divide(lhs, rhs); - return lhs; -} - -inline bool operator == (const Vector2& lhs, const Vector2& rhs) -{ - return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); -} - -inline bool operator != (const Vector2& lhs, const Vector2& rhs) -{ - return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); -} - -// Vector3 operators -static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; -static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; -static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; -static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; -static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; - -inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Add(lhs, rhs); -} - -inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Add(lhs, rhs); - return lhs; -} - -inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Subtract(lhs, rhs); -} - -inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Subtract(lhs, rhs); - return lhs; -} - -inline Vector3 operator * (const Vector3& lhs, const float& rhs) -{ - return Vector3Scale(lhs, rhs); -} - -inline const Vector3& operator *= (Vector3& lhs, const float& rhs) -{ - lhs = Vector3Scale(lhs, rhs); - return lhs; -} - -inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Multiply(lhs, rhs); -} - -inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Multiply(lhs, rhs); - return lhs; -} - -inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) -{ - return Vector3Transform(lhs, rhs); -} - -inline const Vector3& operator *= (Vector3& lhs, const Matrix& rhs) -{ - lhs = Vector3Transform(lhs, rhs); - return lhs; -} - -inline Vector3 operator / (const Vector3& lhs, const float& rhs) -{ - return Vector3Scale(lhs, 1.0f / rhs); -} - -inline const Vector3& operator /= (Vector3& lhs, const float& rhs) -{ - lhs = Vector3Scale(lhs, 1.0f / rhs); - return lhs; -} - -inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) -{ - return Vector3Divide(lhs, rhs); -} - -inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) -{ - lhs = Vector3Divide(lhs, rhs); - return lhs; -} - -inline bool operator == (const Vector3& lhs, const Vector3& rhs) -{ - return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); -} - -inline bool operator != (const Vector3& lhs, const Vector3& rhs) -{ - return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); -} - -// Vector4 operators -static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; -static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; -static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; -static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; -static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; -static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; - -inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Add(lhs, rhs); -} - -inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Add(lhs, rhs); - return lhs; -} - -inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Subtract(lhs, rhs); -} - -inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Subtract(lhs, rhs); - return lhs; -} - -inline Vector4 operator * (const Vector4& lhs, const float& rhs) -{ - return Vector4Scale(lhs, rhs); -} - -inline const Vector4& operator *= (Vector4& lhs, const float& rhs) -{ - lhs = Vector4Scale(lhs, rhs); - return lhs; -} - -inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Multiply(lhs, rhs); -} - -inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Multiply(lhs, rhs); - return lhs; -} - -inline Vector4 operator / (const Vector4& lhs, const float& rhs) -{ - return Vector4Scale(lhs, 1.0f / rhs); -} - -inline const Vector4& operator /= (Vector4& lhs, const float& rhs) -{ - lhs = Vector4Scale(lhs, 1.0f / rhs); - return lhs; -} - -inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) -{ - return Vector4Divide(lhs, rhs); -} - -inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) -{ - lhs = Vector4Divide(lhs, rhs); - return lhs; -} - -inline bool operator == (const Vector4& lhs, const Vector4& rhs) -{ - return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); -} - -inline bool operator != (const Vector4& lhs, const Vector4& rhs) -{ - return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); -} - -// Quaternion operators -static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; -static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; -static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; - -inline Quaternion operator + (const Quaternion& lhs, const float& rhs) -{ - return QuaternionAddValue(lhs, rhs); -} - -inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) -{ - lhs = QuaternionAddValue(lhs, rhs); - return lhs; -} - -inline Quaternion operator - (const Quaternion& lhs, const float& rhs) -{ - return QuaternionSubtractValue(lhs, rhs); -} - -inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) -{ - lhs = QuaternionSubtractValue(lhs, rhs); - return lhs; -} - -inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) -{ - return QuaternionTransform(lhs, rhs); -} - -inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) -{ - lhs = QuaternionTransform(lhs, rhs); - return lhs; -} - -// Matrix operators -inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) -{ - return MatrixAdd(lhs, rhs); -} - -inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) -{ - lhs = MatrixAdd(lhs, rhs); - return lhs; -} - -inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) -{ - return MatrixSubtract(lhs, rhs); -} - -inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) -{ - lhs = MatrixSubtract(lhs, rhs); - return lhs; -} - -inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) -{ - return MatrixMultiply(lhs, rhs); -} - -inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) -{ - lhs = MatrixMultiply(lhs, rhs); - return lhs; -} -//------------------------------------------------------------------------------- -#endif // C++ operators - -#endif // RAYMATH_H diff --git a/third_party/raylib/include/rlgl.h b/third_party/raylib/include/rlgl.h deleted file mode 100644 index 92971df6277..00000000000 --- a/third_party/raylib/include/rlgl.h +++ /dev/null @@ -1,5262 +0,0 @@ -/********************************************************************************************** -* -* rlgl v5.0 - A multi-OpenGL abstraction layer with an immediate-mode style API -* -* DESCRIPTION: -* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0, ES 3.0) -* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) -* -* ADDITIONAL NOTES: -* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffer are -* initialized on rlglInit() to accumulate vertex data -* -* When an internal state change is required all the stored vertex data is renderer in batch, -* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch -* -* Some resources are also loaded for convenience, here the complete list: -* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data -* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 -* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) -* -* Internal buffer (and resources) must be manually unloaded calling rlglClose() -* -* CONFIGURATION: -* #define GRAPHICS_API_OPENGL_11 -* #define GRAPHICS_API_OPENGL_21 -* #define GRAPHICS_API_OPENGL_33 -* #define GRAPHICS_API_OPENGL_43 -* #define GRAPHICS_API_OPENGL_ES2 -* #define GRAPHICS_API_OPENGL_ES3 -* Use selected OpenGL graphics backend, should be supported by platform -* Those preprocessor defines are only used on rlgl module, if OpenGL version is -* required by any other module, use rlGetVersion() to check it -* -* #define RLGL_IMPLEMENTATION -* Generates the implementation of the library into the included file -* If not defined, the library is in header only mode and can be included in other headers -* or source files without problems. But only ONE file should hold the implementation -* -* #define RLGL_RENDER_TEXTURES_HINT -* Enable framebuffer objects (fbo) support (enabled by default) -* Some GPUs could not support them despite the OpenGL version -* -* #define RLGL_SHOW_GL_DETAILS_INFO -* Show OpenGL extensions and capabilities detailed logs on init -* -* #define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT -* Enable debug context (only available on OpenGL 4.3) -* -* rlgl capabilities could be customized just defining some internal -* values before library inclusion (default values listed): -* -* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits -* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) -* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) -* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) -* -* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack -* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported -* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance -* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance -* -* When loading a shader, the following vertex attributes and uniform -* location names are tried to be set automatically: -* -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS -* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) -* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) -* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) -* -* DEPENDENCIES: -* - OpenGL libraries (depending on platform and OpenGL version selected) -* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) -* -* -* LICENSE: zlib/libpng -* -* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) -* -* This software is provided "as-is", without any express or implied warranty. In no event -* will the authors be held liable for any damages arising from the use of this software. -* -* Permission is granted to anyone to use this software for any purpose, including commercial -* applications, and to alter it and redistribute it freely, subject to the following restrictions: -* -* 1. The origin of this software must not be misrepresented; you must not claim that you -* wrote the original software. If you use this software in a product, an acknowledgment -* in the product documentation would be appreciated but is not required. -* -* 2. Altered source versions must be plainly marked as such, and must not be misrepresented -* as being the original software. -* -* 3. This notice may not be removed or altered from any source distribution. -* -**********************************************************************************************/ - -#ifndef RLGL_H -#define RLGL_H - -#define RLGL_VERSION "5.0" - -// Function specifiers in case library is build/used as a shared library -// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll -// NOTE: visibility(default) attribute makes symbols "visible" when compiled with -fvisibility=hidden -#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) -#elif defined(BUILD_LIBTYPE_SHARED) - #define RLAPI __attribute__((visibility("default"))) // We are building the library as a Unix shared library (.so/.dylib) -#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) - #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) -#endif - -// Function specifiers definition -#ifndef RLAPI - #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) -#endif - -// Support TRACELOG macros -#ifndef TRACELOG - #define TRACELOG(level, ...) (void)0 - #define TRACELOGD(...) (void)0 -#endif - -// Allow custom memory allocators -#ifndef RL_MALLOC - #define RL_MALLOC(sz) malloc(sz) -#endif -#ifndef RL_CALLOC - #define RL_CALLOC(n,sz) calloc(n,sz) -#endif -#ifndef RL_REALLOC - #define RL_REALLOC(n,sz) realloc(n,sz) -#endif -#ifndef RL_FREE - #define RL_FREE(p) free(p) -#endif - -// Security check in case no GRAPHICS_API_OPENGL_* defined -#if !defined(GRAPHICS_API_OPENGL_11) && \ - !defined(GRAPHICS_API_OPENGL_21) && \ - !defined(GRAPHICS_API_OPENGL_33) && \ - !defined(GRAPHICS_API_OPENGL_43) && \ - !defined(GRAPHICS_API_OPENGL_ES2) && \ - !defined(GRAPHICS_API_OPENGL_ES3) - #define GRAPHICS_API_OPENGL_33 -#endif - -// Security check in case multiple GRAPHICS_API_OPENGL_* defined -#if defined(GRAPHICS_API_OPENGL_11) - #if defined(GRAPHICS_API_OPENGL_21) - #undef GRAPHICS_API_OPENGL_21 - #endif - #if defined(GRAPHICS_API_OPENGL_33) - #undef GRAPHICS_API_OPENGL_33 - #endif - #if defined(GRAPHICS_API_OPENGL_43) - #undef GRAPHICS_API_OPENGL_43 - #endif - #if defined(GRAPHICS_API_OPENGL_ES2) - #undef GRAPHICS_API_OPENGL_ES2 - #endif -#endif - -// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality -// WARNING: Specific parts are checked with #if defines -#if defined(GRAPHICS_API_OPENGL_21) - #define GRAPHICS_API_OPENGL_33 -#endif - -// OpenGL 4.3 uses OpenGL 3.3 Core functionality -#if defined(GRAPHICS_API_OPENGL_43) - #define GRAPHICS_API_OPENGL_33 -#endif - -// OpenGL ES 3.0 uses OpenGL ES 2.0 functionality (and more) -#if defined(GRAPHICS_API_OPENGL_ES3) - #define GRAPHICS_API_OPENGL_ES2 -#endif - -// Support framebuffer objects by default -// NOTE: Some driver implementation do not support it, despite they should -#define RLGL_RENDER_TEXTURES_HINT - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- - -// Default internal render batch elements limits -#ifndef RL_DEFAULT_BATCH_BUFFER_ELEMENTS - #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // This is the maximum amount of elements (quads) per batch - // NOTE: Be careful with text, every letter maps to a quad - #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 - #endif - #if defined(GRAPHICS_API_OPENGL_ES2) - // We reduce memory sizes for embedded systems (RPI and HTML5) - // NOTE: On HTML5 (emscripten) this is allocated on heap, - // by default it's only 16MB!...just take care... - #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 2048 - #endif -#endif -#ifndef RL_DEFAULT_BATCH_BUFFERS - #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) -#endif -#ifndef RL_DEFAULT_BATCH_DRAWCALLS - #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) -#endif -#ifndef RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS - #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) -#endif - -// Internal Matrix stack -#ifndef RL_MAX_MATRIX_STACK_SIZE - #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack -#endif - -// Shader limits -#ifndef RL_MAX_SHADER_LOCATIONS - #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported -#endif - -// Projection matrix culling -#ifndef RL_CULL_DISTANCE_NEAR - #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance -#endif -#ifndef RL_CULL_DISTANCE_FAR - #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance -#endif - -// Texture parameters (equivalent to OpenGL defines) -#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S -#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T -#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER -#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER - -#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST -#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR -#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST -#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR -#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST -#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR -#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) -#define RL_TEXTURE_MIPMAP_BIAS_RATIO 0x4000 // Texture mipmap bias, percentage ratio (custom identifier) - -#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT -#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE -#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT -#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT - -// Matrix modes (equivalent to OpenGL) -#define RL_MODELVIEW 0x1700 // GL_MODELVIEW -#define RL_PROJECTION 0x1701 // GL_PROJECTION -#define RL_TEXTURE 0x1702 // GL_TEXTURE - -// Primitive assembly draw modes -#define RL_LINES 0x0001 // GL_LINES -#define RL_TRIANGLES 0x0004 // GL_TRIANGLES -#define RL_QUADS 0x0007 // GL_QUADS - -// GL equivalent data types -#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE -#define RL_FLOAT 0x1406 // GL_FLOAT - -// GL buffer usage hint -#define RL_STREAM_DRAW 0x88E0 // GL_STREAM_DRAW -#define RL_STREAM_READ 0x88E1 // GL_STREAM_READ -#define RL_STREAM_COPY 0x88E2 // GL_STREAM_COPY -#define RL_STATIC_DRAW 0x88E4 // GL_STATIC_DRAW -#define RL_STATIC_READ 0x88E5 // GL_STATIC_READ -#define RL_STATIC_COPY 0x88E6 // GL_STATIC_COPY -#define RL_DYNAMIC_DRAW 0x88E8 // GL_DYNAMIC_DRAW -#define RL_DYNAMIC_READ 0x88E9 // GL_DYNAMIC_READ -#define RL_DYNAMIC_COPY 0x88EA // GL_DYNAMIC_COPY - -// GL Shader type -#define RL_FRAGMENT_SHADER 0x8B30 // GL_FRAGMENT_SHADER -#define RL_VERTEX_SHADER 0x8B31 // GL_VERTEX_SHADER -#define RL_COMPUTE_SHADER 0x91B9 // GL_COMPUTE_SHADER - -// GL blending factors -#define RL_ZERO 0 // GL_ZERO -#define RL_ONE 1 // GL_ONE -#define RL_SRC_COLOR 0x0300 // GL_SRC_COLOR -#define RL_ONE_MINUS_SRC_COLOR 0x0301 // GL_ONE_MINUS_SRC_COLOR -#define RL_SRC_ALPHA 0x0302 // GL_SRC_ALPHA -#define RL_ONE_MINUS_SRC_ALPHA 0x0303 // GL_ONE_MINUS_SRC_ALPHA -#define RL_DST_ALPHA 0x0304 // GL_DST_ALPHA -#define RL_ONE_MINUS_DST_ALPHA 0x0305 // GL_ONE_MINUS_DST_ALPHA -#define RL_DST_COLOR 0x0306 // GL_DST_COLOR -#define RL_ONE_MINUS_DST_COLOR 0x0307 // GL_ONE_MINUS_DST_COLOR -#define RL_SRC_ALPHA_SATURATE 0x0308 // GL_SRC_ALPHA_SATURATE -#define RL_CONSTANT_COLOR 0x8001 // GL_CONSTANT_COLOR -#define RL_ONE_MINUS_CONSTANT_COLOR 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR -#define RL_CONSTANT_ALPHA 0x8003 // GL_CONSTANT_ALPHA -#define RL_ONE_MINUS_CONSTANT_ALPHA 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA - -// GL blending functions/equations -#define RL_FUNC_ADD 0x8006 // GL_FUNC_ADD -#define RL_MIN 0x8007 // GL_MIN -#define RL_MAX 0x8008 // GL_MAX -#define RL_FUNC_SUBTRACT 0x800A // GL_FUNC_SUBTRACT -#define RL_FUNC_REVERSE_SUBTRACT 0x800B // GL_FUNC_REVERSE_SUBTRACT -#define RL_BLEND_EQUATION 0x8009 // GL_BLEND_EQUATION -#define RL_BLEND_EQUATION_RGB 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) -#define RL_BLEND_EQUATION_ALPHA 0x883D // GL_BLEND_EQUATION_ALPHA -#define RL_BLEND_DST_RGB 0x80C8 // GL_BLEND_DST_RGB -#define RL_BLEND_SRC_RGB 0x80C9 // GL_BLEND_SRC_RGB -#define RL_BLEND_DST_ALPHA 0x80CA // GL_BLEND_DST_ALPHA -#define RL_BLEND_SRC_ALPHA 0x80CB // GL_BLEND_SRC_ALPHA -#define RL_BLEND_COLOR 0x8005 // GL_BLEND_COLOR - -#define RL_READ_FRAMEBUFFER 0x8CA8 // GL_READ_FRAMEBUFFER -#define RL_DRAW_FRAMEBUFFER 0x8CA9 // GL_DRAW_FRAMEBUFFER - -// Default shader vertex attribute locations -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 -#endif - #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT -#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 -#endif -#ifdef RL_SUPPORT_MESH_GPU_SKINNING -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS - #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 -#endif -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) - #include -#elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) - // Boolean type -typedef enum bool { false = 0, true = !false } bool; -#endif - -#if !defined(RL_MATRIX_TYPE) -// Matrix, 4x4 components, column major, OpenGL style, right handed -typedef struct Matrix { - float m0, m4, m8, m12; // Matrix first row (4 components) - float m1, m5, m9, m13; // Matrix second row (4 components) - float m2, m6, m10, m14; // Matrix third row (4 components) - float m3, m7, m11, m15; // Matrix fourth row (4 components) -} Matrix; -#define RL_MATRIX_TYPE -#endif - -// Dynamic vertex buffers (position + texcoords + colors + indices arrays) -typedef struct rlVertexBuffer { - int elementCount; // Number of elements in the buffer (QUADS) - - float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) - float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) - float *normals; // Vertex normal (XYZ - 3 components per vertex) (shader-location = 2) - unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) - unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) -#endif - unsigned int vaoId; // OpenGL Vertex Array Object id - unsigned int vboId[5]; // OpenGL Vertex Buffer Objects id (5 types of vertex data) -} rlVertexBuffer; - -// Draw call type -// NOTE: Only texture changes register a new draw, other state-change-related elements are not -// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any -// of those state-change happens (this is done in core module) -typedef struct rlDrawCall { - int mode; // Drawing mode: LINES, TRIANGLES, QUADS - int vertexCount; // Number of vertex of the draw - int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) - //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId - //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShaderId - unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes - - //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default - //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default -} rlDrawCall; - -// rlRenderBatch type -typedef struct rlRenderBatch { - int bufferCount; // Number of vertex buffers (multi-buffering support) - int currentBuffer; // Current buffer tracking in case of multi-buffering - rlVertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data - - rlDrawCall *draws; // Draw calls array, depends on textureId - int drawCounter; // Draw calls counter - float currentDepth; // Current depth value for next draw -} rlRenderBatch; - -// OpenGL version -typedef enum { - RL_OPENGL_11 = 1, // OpenGL 1.1 - RL_OPENGL_21, // OpenGL 2.1 (GLSL 120) - RL_OPENGL_33, // OpenGL 3.3 (GLSL 330) - RL_OPENGL_43, // OpenGL 4.3 (using GLSL 330) - RL_OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) - RL_OPENGL_ES_30 // OpenGL ES 3.0 (GLSL 300 es) -} rlGlVersion; - -// Trace log level -// NOTE: Organized by priority level -typedef enum { - RL_LOG_ALL = 0, // Display all logs - RL_LOG_TRACE, // Trace logging, intended for internal use only - RL_LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds - RL_LOG_INFO, // Info logging, used for program execution info - RL_LOG_WARNING, // Warning logging, used on recoverable failures - RL_LOG_ERROR, // Error logging, used on unrecoverable failures - RL_LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) - RL_LOG_NONE // Disable logging -} rlTraceLogLevel; - -// Texture pixel formats -// NOTE: Support depends on OpenGL version -typedef enum { - RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) - RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) - RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp - RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp - RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) - RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) - RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp - RL_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) - RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) - RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) - RL_PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) - RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) - RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) - RL_PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) - RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) - RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp - RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp - RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp -} rlPixelFormat; - -// Texture parameters: filter mode -// NOTE 1: Filtering considers mipmaps if available in the texture -// NOTE 2: Filter is accordingly set for minification and magnification -typedef enum { - RL_TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation - RL_TEXTURE_FILTER_BILINEAR, // Linear filtering - RL_TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) - RL_TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x - RL_TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x - RL_TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x -} rlTextureFilter; - -// Color blending modes (pre-defined) -typedef enum { - RL_BLEND_ALPHA = 0, // Blend textures considering alpha (default) - RL_BLEND_ADDITIVE, // Blend textures adding colors - RL_BLEND_MULTIPLIED, // Blend textures multiplying colors - RL_BLEND_ADD_COLORS, // Blend textures adding colors (alternative) - RL_BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) - RL_BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha - RL_BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) - RL_BLEND_CUSTOM_SEPARATE // Blend textures using custom src/dst factors (use rlSetBlendFactorsSeparate()) -} rlBlendMode; - -// Shader location point type -typedef enum { - RL_SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position - RL_SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 - RL_SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 - RL_SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal - RL_SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent - RL_SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color - RL_SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection - RL_SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) - RL_SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection - RL_SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) - RL_SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal - RL_SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view - RL_SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color - RL_SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color - RL_SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color - RL_SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: RL_SHADER_LOC_MAP_DIFFUSE) - RL_SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: RL_SHADER_LOC_MAP_SPECULAR) - RL_SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal - RL_SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness - RL_SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion - RL_SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission - RL_SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height - RL_SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap - RL_SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance - RL_SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter - RL_SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf -} rlShaderLocationIndex; - -#define RL_SHADER_LOC_MAP_DIFFUSE RL_SHADER_LOC_MAP_ALBEDO -#define RL_SHADER_LOC_MAP_SPECULAR RL_SHADER_LOC_MAP_METALNESS - -// Shader uniform data type -typedef enum { - RL_SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float - RL_SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) - RL_SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) - RL_SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) - RL_SHADER_UNIFORM_INT, // Shader uniform type: int - RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) - RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) - RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) - RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int - RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) - RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) - RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) - RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d -} rlShaderUniformDataType; - -// Shader attribute data types -typedef enum { - RL_SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float - RL_SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) - RL_SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) - RL_SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) -} rlShaderAttributeDataType; - -// Framebuffer attachment type -// NOTE: By default up to 8 color channels defined, but it can be more -typedef enum { - RL_ATTACHMENT_COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 - RL_ATTACHMENT_COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 - RL_ATTACHMENT_COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 - RL_ATTACHMENT_COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 - RL_ATTACHMENT_COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 - RL_ATTACHMENT_COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 - RL_ATTACHMENT_COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 - RL_ATTACHMENT_COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 - RL_ATTACHMENT_DEPTH = 100, // Framebuffer attachment type: depth - RL_ATTACHMENT_STENCIL = 200, // Framebuffer attachment type: stencil -} rlFramebufferAttachType; - -// Framebuffer texture attachment type -typedef enum { - RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side - RL_ATTACHMENT_CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side - RL_ATTACHMENT_CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side - RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side - RL_ATTACHMENT_CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side - RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side - RL_ATTACHMENT_TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d - RL_ATTACHMENT_RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer -} rlFramebufferAttachTextureType; - -// Face culling mode -typedef enum { - RL_CULL_FACE_FRONT = 0, - RL_CULL_FACE_BACK -} rlCullMode; - -//------------------------------------------------------------------------------------ -// Functions Declaration - Matrix operations -//------------------------------------------------------------------------------------ - -#if defined(__cplusplus) -extern "C" { // Prevents name mangling of functions -#endif - -RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed -RLAPI void rlPushMatrix(void); // Push the current matrix to stack -RLAPI void rlPopMatrix(void); // Pop latest inserted matrix from stack -RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix -RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix -RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix -RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix -RLAPI void rlMultMatrixf(const float *matf); // Multiply the current matrix by another matrix -RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); -RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); -RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area -RLAPI void rlSetClipPlanes(double nearPlane, double farPlane); // Set clip planes distances -RLAPI double rlGetCullDistanceNear(void); // Get cull plane distance near -RLAPI double rlGetCullDistanceFar(void); // Get cull plane distance far - -//------------------------------------------------------------------------------------ -// Functions Declaration - Vertex level operations -//------------------------------------------------------------------------------------ -RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) -RLAPI void rlEnd(void); // Finish vertex providing -RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int -RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float -RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float -RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float -RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float -RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte -RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float -RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float - -//------------------------------------------------------------------------------------ -// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) -// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, -// some of them are direct wrappers over OpenGL calls, some others are custom -//------------------------------------------------------------------------------------ - -// Vertex buffers state -RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) -RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) -RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) -RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) -RLAPI void rlEnableVertexBufferElement(unsigned int id); // Enable vertex buffer element (VBO element) -RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) -RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index -RLAPI void rlDisableVertexAttribute(unsigned int index); // Disable vertex attribute index -#if defined(GRAPHICS_API_OPENGL_11) -RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer -RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer -#endif - -// Textures state -RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot -RLAPI void rlEnableTexture(unsigned int id); // Enable texture -RLAPI void rlDisableTexture(void); // Disable texture -RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap -RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap -RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) -RLAPI void rlCubemapParameters(unsigned int id, int param, int value); // Set cubemap parameters (filter, wrap) - -// Shader state -RLAPI void rlEnableShader(unsigned int id); // Enable shader program -RLAPI void rlDisableShader(void); // Disable shader program - -// Framebuffer state -RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) -RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer -RLAPI unsigned int rlGetActiveFramebuffer(void); // Get the currently active render texture (fbo), 0 for default framebuffer -RLAPI void rlActiveDrawBuffers(int count); // Activate multiple draw color buffers -RLAPI void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask); // Blit active framebuffer to main framebuffer -RLAPI void rlBindFramebuffer(unsigned int target, unsigned int framebuffer); // Bind framebuffer (FBO) - -// General render state -RLAPI void rlEnableColorBlend(void); // Enable color blending -RLAPI void rlDisableColorBlend(void); // Disable color blending -RLAPI void rlEnableDepthTest(void); // Enable depth test -RLAPI void rlDisableDepthTest(void); // Disable depth test -RLAPI void rlEnableDepthMask(void); // Enable depth write -RLAPI void rlDisableDepthMask(void); // Disable depth write -RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling -RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling -RLAPI void rlColorMask(bool r, bool g, bool b, bool a); // Color mask control -RLAPI void rlSetCullFace(int mode); // Set face culling mode -RLAPI void rlEnableScissorTest(void); // Enable scissor test -RLAPI void rlDisableScissorTest(void); // Disable scissor test -RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test -RLAPI void rlEnableWireMode(void); // Enable wire mode -RLAPI void rlEnablePointMode(void); // Enable point mode -RLAPI void rlDisableWireMode(void); // Disable wire (and point) mode -RLAPI void rlSetLineWidth(float width); // Set the line drawing width -RLAPI float rlGetLineWidth(void); // Get the line drawing width -RLAPI void rlEnableSmoothLines(void); // Enable line aliasing -RLAPI void rlDisableSmoothLines(void); // Disable line aliasing -RLAPI void rlEnableStereoRender(void); // Enable stereo rendering -RLAPI void rlDisableStereoRender(void); // Disable stereo rendering -RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled - -RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color -RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) -RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes -RLAPI void rlSetBlendMode(int mode); // Set blending mode -RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) -RLAPI void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha); // Set blending mode factors and equations separately (using OpenGL factors) - -//------------------------------------------------------------------------------------ -// Functions Declaration - rlgl functionality -//------------------------------------------------------------------------------------ -// rlgl initialization functions -RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) -RLAPI void rlglClose(void); // De-initialize rlgl (buffers, shaders, textures) -RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) -RLAPI int rlGetVersion(void); // Get current OpenGL version -RLAPI void rlSetFramebufferWidth(int width); // Set current framebuffer width -RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width -RLAPI void rlSetFramebufferHeight(int height); // Set current framebuffer height -RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height - -RLAPI unsigned int rlGetTextureIdDefault(void); // Get default texture id -RLAPI unsigned int rlGetShaderIdDefault(void); // Get default shader id -RLAPI int *rlGetShaderLocsDefault(void); // Get default shader locations - -// Render batch management -// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode -// but this render batch API is exposed in case of custom batches are required -RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system -RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system -RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) -RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) -RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch -RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex - -RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits - -//------------------------------------------------------------------------------------------------------------------------ - -// Vertex buffers management -RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported -RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer object -RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load vertex buffer elements object -RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update vertex buffer object data on GPU buffer -RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements data on GPU buffer -RLAPI void rlUnloadVertexArray(unsigned int vaoId); // Unload vertex array (vao) -RLAPI void rlUnloadVertexBuffer(unsigned int vboId); // Unload vertex buffer object -RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset); // Set vertex attribute data configuration -RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); // Set vertex attribute data divisor -RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value, when attribute to provided -RLAPI void rlDrawVertexArray(int offset, int count); // Draw vertex array (currently active vao) -RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); // Draw vertex array elements -RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); // Draw vertex array (currently active vao) with instancing -RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); // Draw vertex array elements with instancing - -// Textures management -RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture data -RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) -RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount); // Load texture cubemap data -RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update texture with new data on GPU -RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats -RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format -RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory -RLAPI void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps); // Generate mipmap data for selected texture -RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data -RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) - -// Framebuffer management (fbo) -RLAPI unsigned int rlLoadFramebuffer(void); // Load an empty framebuffer -RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer -RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete -RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU - -// Shaders management -RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings -RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) -RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program -RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program -RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform -RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute -RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform -RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix -RLAPI void rlSetUniformMatrices(int locIndex, const Matrix *mat, int count); // Set shader value matrices -RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler -RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) - -// Compute shader management -RLAPI unsigned int rlLoadComputeShaderProgram(unsigned int shaderId); // Load compute shader program -RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pipeline) - -// Shader buffer storage object management (ssbo) -RLAPI unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint); // Load shader storage buffer object (SSBO) -RLAPI void rlUnloadShaderBuffer(unsigned int ssboId); // Unload shader storage buffer object (SSBO) -RLAPI void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset); // Update SSBO buffer data -RLAPI void rlBindShaderBuffer(unsigned int id, unsigned int index); // Bind SSBO buffer -RLAPI void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset); // Read SSBO buffer data (GPU->CPU) -RLAPI void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count); // Copy SSBO data between buffers -RLAPI unsigned int rlGetShaderBufferSize(unsigned int id); // Get SSBO buffer size - -// Buffer management -RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly); // Bind image texture - -// Matrix state management -RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix -RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix -RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix -RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) -RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) -RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) -RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) -RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering -RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering - -// Quick and dirty cube/quad buffers load->draw->unload -RLAPI void rlLoadDrawCube(void); // Load and draw a cube -RLAPI void rlLoadDrawQuad(void); // Load and draw a quad - -#if defined(__cplusplus) -} -#endif - -#endif // RLGL_H - -/*********************************************************************************** -* -* RLGL IMPLEMENTATION -* -************************************************************************************/ - -#if defined(RLGL_IMPLEMENTATION) - -// Expose OpenGL functions from glad in raylib -#if defined(BUILD_LIBTYPE_SHARED) - #define GLAD_API_CALL_EXPORT - #define GLAD_API_CALL_EXPORT_BUILD -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - #if defined(__APPLE__) - #include // OpenGL 1.1 library for OSX - #include // OpenGL extensions library - #else - // APIENTRY for OpenGL function pointer declarations is required - #if !defined(APIENTRY) - #if defined(_WIN32) - #define APIENTRY __stdcall - #else - #define APIENTRY - #endif - #endif - // WINGDIAPI definition. Some Windows OpenGL headers need it - #if !defined(WINGDIAPI) && defined(_WIN32) - #define WINGDIAPI __declspec(dllimport) - #endif - - #include // OpenGL 1.1 library - #endif -#endif - -#if defined(GRAPHICS_API_OPENGL_33) - #define GLAD_MALLOC RL_MALLOC - #define GLAD_FREE RL_FREE - - #define GLAD_GL_IMPLEMENTATION - #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers -#endif - -#if defined(GRAPHICS_API_OPENGL_ES3) - #include // OpenGL ES 3.0 library - #define GL_GLEXT_PROTOTYPES - #include // OpenGL ES 2.0 extensions library -#elif defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: OpenGL ES 2.0 can be enabled on Desktop platforms, - // in that case, functions are loaded from a custom glad for OpenGL ES 2.0 - #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) - #define GLAD_GLES2_IMPLEMENTATION - #include "external/glad_gles2.h" - #else - #define GL_GLEXT_PROTOTYPES - //#include // EGL library -> not required, platform layer - #include // OpenGL ES 2.0 library - #include // OpenGL ES 2.0 extensions library - #endif - - // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi - // provided headers (despite being defined in official Khronos GLES2 headers) - #if defined(PLATFORM_DRM) - typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); - typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); - typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); - #endif -#endif - -#include // Required for: malloc(), free() -#include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] -#include // Required for: sqrtf(), sinf(), cosf(), floor(), log() - -//---------------------------------------------------------------------------------- -// Defines and Macros -//---------------------------------------------------------------------------------- -#ifndef PI - #define PI 3.14159265358979323846f -#endif -#ifndef DEG2RAD - #define DEG2RAD (PI/180.0f) -#endif -#ifndef RAD2DEG - #define RAD2DEG (180.0f/PI) -#endif - -#ifndef GL_SHADING_LANGUAGE_VERSION - #define GL_SHADING_LANGUAGE_VERSION 0x8B8C -#endif - -#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT - #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 -#endif -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 -#endif -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 -#endif -#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT - #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 -#endif -#ifndef GL_ETC1_RGB8_OES - #define GL_ETC1_RGB8_OES 0x8D64 -#endif -#ifndef GL_COMPRESSED_RGB8_ETC2 - #define GL_COMPRESSED_RGB8_ETC2 0x9274 -#endif -#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC - #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 -#endif -#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG - #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 -#endif -#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG - #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 -#endif -#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR - #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 -#endif -#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR - #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 -#endif - -#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF -#endif -#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE -#endif - -#ifndef GL_PROGRAM_POINT_SIZE - #define GL_PROGRAM_POINT_SIZE 0x8642 -#endif - -#ifndef GL_LINE_WIDTH - #define GL_LINE_WIDTH 0x0B21 -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - #define GL_UNSIGNED_SHORT_5_6_5 0x8363 - #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 - #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 -#endif - -#if defined(GRAPHICS_API_OPENGL_21) - #define GL_LUMINANCE 0x1909 - #define GL_LUMINANCE_ALPHA 0x190A -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - #define glClearDepth glClearDepthf - #if !defined(GRAPHICS_API_OPENGL_ES3) - #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER - #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER - #endif -#endif - -// Default shader vertex attribute names to set location points -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION - #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL - #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR - #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 - #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS - #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS -#endif -#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS - #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS -#endif - -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MVP - #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW - #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION - #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL - #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL - #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR - #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) -#endif -#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES - #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices -#endif -#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 - #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) -#endif -#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 - #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) -#endif -#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 - #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) -#endif - -//---------------------------------------------------------------------------------- -// Types and Structures Definition -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -typedef struct rlglData { - rlRenderBatch *currentBatch; // Current render batch - rlRenderBatch defaultBatch; // Default internal render batch - - struct { - int vertexCounter; // Current active render batch vertex counter (generic, used for all batches) - float texcoordx, texcoordy; // Current active texture coordinate (added on glVertex*()) - float normalx, normaly, normalz; // Current active normal (added on glVertex*()) - unsigned char colorr, colorg, colorb, colora; // Current active color (added on glVertex*()) - - int currentMatrixMode; // Current matrix mode - Matrix *currentMatrix; // Current matrix pointer - Matrix modelview; // Default modelview matrix - Matrix projection; // Default projection matrix - Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale - bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) - Matrix stack[RL_MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop - int stackCounter; // Matrix stack counter - - unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) - unsigned int activeTextureId[RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS]; // Active texture ids to be enabled on batch drawing (0 active by default) - unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) - unsigned int defaultFShaderId; // Default fragment shader id (used by default shader program) - unsigned int defaultShaderId; // Default shader program id, supports vertex color and diffuse texture - int *defaultShaderLocs; // Default shader locations pointer to be used on rendering - unsigned int currentShaderId; // Current shader id to be used on rendering (by default, defaultShaderId) - int *currentShaderLocs; // Current shader locations pointer to be used on rendering (by default, defaultShaderLocs) - - bool stereoRender; // Stereo rendering flag - Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices - Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices - - // Blending variables - int currentBlendMode; // Blending mode active - int glBlendSrcFactor; // Blending source factor - int glBlendDstFactor; // Blending destination factor - int glBlendEquation; // Blending equation - int glBlendSrcFactorRGB; // Blending source RGB factor - int glBlendDestFactorRGB; // Blending destination RGB factor - int glBlendSrcFactorAlpha; // Blending source alpha factor - int glBlendDestFactorAlpha; // Blending destination alpha factor - int glBlendEquationRGB; // Blending equation for RGB - int glBlendEquationAlpha; // Blending equation for alpha - bool glCustomBlendModeModified; // Custom blending factor and equation modification status - - int framebufferWidth; // Current framebuffer width - int framebufferHeight; // Current framebuffer height - - } State; // Renderer state - struct { - bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) - bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) - bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) - bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_OES_depth_texture) - bool texDepthWebGL; // Depth textures supported WebGL specific (GL_WEBGL_depth_texture) - bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) - bool texFloat16; // half float textures support (16 bit per channel) (GL_OES_texture_half_float) - bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) - bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) - bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) - bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) - bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) - bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) - bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) - bool computeShader; // Compute shaders support (GL_ARB_compute_shader) - bool ssbo; // Shader storage buffer object support (GL_ARB_shader_storage_buffer_object) - - float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) - int maxDepthBits; // Maximum bits for depth component - - } ExtSupported; // Extensions supported flags -} rlglData; - -typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -//---------------------------------------------------------------------------------- -// Global Variables Definition -//---------------------------------------------------------------------------------- -static double rlCullDistanceNear = RL_CULL_DISTANCE_NEAR; -static double rlCullDistanceFar = RL_CULL_DISTANCE_FAR; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static rlglData RLGL = { 0 }; -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -#if defined(GRAPHICS_API_OPENGL_ES2) && !defined(GRAPHICS_API_OPENGL_ES3) -// NOTE: VAO functionality is exposed through extensions (OES) -static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; -static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; -static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; - -// NOTE: Instancing functionality could also be available through extension -static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; -static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; -static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; -#endif - -//---------------------------------------------------------------------------------- -// Module specific Functions Declaration -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -static void rlLoadShaderDefault(void); // Load default shader -static void rlUnloadShaderDefault(void); // Unload default shader -#if defined(RLGL_SHOW_GL_DETAILS_INFO) -static const char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name -#endif // RLGL_SHOW_GL_DETAILS_INFO -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) - -// Auxiliar matrix math functions -typedef struct rl_float16 { - float v[16]; -} rl_float16; -static rl_float16 rlMatrixToFloatV(Matrix mat); // Get float array of matrix data -#define rlMatrixToFloat(mat) (rlMatrixToFloatV(mat).v) // Get float vector for Matrix -static Matrix rlMatrixIdentity(void); // Get identity matrix -static Matrix rlMatrixMultiply(Matrix left, Matrix right); // Multiply two matrices -static Matrix rlMatrixTranspose(Matrix mat); // Transposes provided matrix -static Matrix rlMatrixInvert(Matrix mat); // Invert provided matrix - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Matrix operations -//---------------------------------------------------------------------------------- - -#if defined(GRAPHICS_API_OPENGL_11) -// Fallback to OpenGL 1.1 function calls -//--------------------------------------- -void rlMatrixMode(int mode) -{ - switch (mode) - { - case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; - case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; - case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; - default: break; - } -} - -void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) -{ - glFrustum(left, right, bottom, top, znear, zfar); -} - -void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) -{ - glOrtho(left, right, bottom, top, znear, zfar); -} - -void rlPushMatrix(void) { glPushMatrix(); } -void rlPopMatrix(void) { glPopMatrix(); } -void rlLoadIdentity(void) { glLoadIdentity(); } -void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } -void rlRotatef(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); } -void rlScalef(float x, float y, float z) { glScalef(x, y, z); } -void rlMultMatrixf(const float *matf) { glMultMatrixf(matf); } -#endif -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Choose the current matrix to be transformed -void rlMatrixMode(int mode) -{ - if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; - else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; - //else if (mode == RL_TEXTURE) // Not supported - - RLGL.State.currentMatrixMode = mode; -} - -// Push the current matrix into RLGL.State.stack -void rlPushMatrix(void) -{ - if (RLGL.State.stackCounter >= RL_MAX_MATRIX_STACK_SIZE) TRACELOG(RL_LOG_ERROR, "RLGL: Matrix stack overflow (RL_MAX_MATRIX_STACK_SIZE)"); - - if (RLGL.State.currentMatrixMode == RL_MODELVIEW) - { - RLGL.State.transformRequired = true; - RLGL.State.currentMatrix = &RLGL.State.transform; - } - - RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; - RLGL.State.stackCounter++; -} - -// Pop lattest inserted matrix from RLGL.State.stack -void rlPopMatrix(void) -{ - if (RLGL.State.stackCounter > 0) - { - Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; - *RLGL.State.currentMatrix = mat; - RLGL.State.stackCounter--; - } - - if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) - { - RLGL.State.currentMatrix = &RLGL.State.modelview; - RLGL.State.transformRequired = false; - } -} - -// Reset current matrix to identity matrix -void rlLoadIdentity(void) -{ - *RLGL.State.currentMatrix = rlMatrixIdentity(); -} - -// Multiply the current matrix by a translation matrix -void rlTranslatef(float x, float y, float z) -{ - Matrix matTranslation = { - 1.0f, 0.0f, 0.0f, x, - 0.0f, 1.0f, 0.0f, y, - 0.0f, 0.0f, 1.0f, z, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - // NOTE: We transpose matrix with multiplication order - *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by a rotation matrix -// NOTE: The provided angle must be in degrees -void rlRotatef(float angle, float x, float y, float z) -{ - Matrix matRotation = rlMatrixIdentity(); - - // Axis vector (x, y, z) normalization - float lengthSquared = x*x + y*y + z*z; - if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) - { - float inverseLength = 1.0f/sqrtf(lengthSquared); - x *= inverseLength; - y *= inverseLength; - z *= inverseLength; - } - - // Rotation matrix generation - float sinres = sinf(DEG2RAD*angle); - float cosres = cosf(DEG2RAD*angle); - float t = 1.0f - cosres; - - matRotation.m0 = x*x*t + cosres; - matRotation.m1 = y*x*t + z*sinres; - matRotation.m2 = z*x*t - y*sinres; - matRotation.m3 = 0.0f; - - matRotation.m4 = x*y*t - z*sinres; - matRotation.m5 = y*y*t + cosres; - matRotation.m6 = z*y*t + x*sinres; - matRotation.m7 = 0.0f; - - matRotation.m8 = x*z*t + y*sinres; - matRotation.m9 = y*z*t - x*sinres; - matRotation.m10 = z*z*t + cosres; - matRotation.m11 = 0.0f; - - matRotation.m12 = 0.0f; - matRotation.m13 = 0.0f; - matRotation.m14 = 0.0f; - matRotation.m15 = 1.0f; - - // NOTE: We transpose matrix with multiplication order - *RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by a scaling matrix -void rlScalef(float x, float y, float z) -{ - Matrix matScale = { - x, 0.0f, 0.0f, 0.0f, - 0.0f, y, 0.0f, 0.0f, - 0.0f, 0.0f, z, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - // NOTE: We transpose matrix with multiplication order - *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by another matrix -void rlMultMatrixf(const float *matf) -{ - // Matrix creation from array - Matrix mat = { matf[0], matf[4], matf[8], matf[12], - matf[1], matf[5], matf[9], matf[13], - matf[2], matf[6], matf[10], matf[14], - matf[3], matf[7], matf[11], matf[15] }; - - *RLGL.State.currentMatrix = rlMatrixMultiply(mat, *RLGL.State.currentMatrix); -} - -// Multiply the current matrix by a perspective matrix generated by parameters -void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) -{ - Matrix matFrustum = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(zfar - znear); - - matFrustum.m0 = ((float) znear*2.0f)/rl; - matFrustum.m1 = 0.0f; - matFrustum.m2 = 0.0f; - matFrustum.m3 = 0.0f; - - matFrustum.m4 = 0.0f; - matFrustum.m5 = ((float) znear*2.0f)/tb; - matFrustum.m6 = 0.0f; - matFrustum.m7 = 0.0f; - - matFrustum.m8 = ((float)right + (float)left)/rl; - matFrustum.m9 = ((float)top + (float)bottom)/tb; - matFrustum.m10 = -((float)zfar + (float)znear)/fn; - matFrustum.m11 = -1.0f; - - matFrustum.m12 = 0.0f; - matFrustum.m13 = 0.0f; - matFrustum.m14 = -((float)zfar*(float)znear*2.0f)/fn; - matFrustum.m15 = 0.0f; - - *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matFrustum); -} - -// Multiply the current matrix by an orthographic matrix generated by parameters -void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) -{ - // NOTE: If left-right and top-botton values are equal it could create a division by zero, - // response to it is platform/compiler dependant - Matrix matOrtho = { 0 }; - - float rl = (float)(right - left); - float tb = (float)(top - bottom); - float fn = (float)(zfar - znear); - - matOrtho.m0 = 2.0f/rl; - matOrtho.m1 = 0.0f; - matOrtho.m2 = 0.0f; - matOrtho.m3 = 0.0f; - matOrtho.m4 = 0.0f; - matOrtho.m5 = 2.0f/tb; - matOrtho.m6 = 0.0f; - matOrtho.m7 = 0.0f; - matOrtho.m8 = 0.0f; - matOrtho.m9 = 0.0f; - matOrtho.m10 = -2.0f/fn; - matOrtho.m11 = 0.0f; - matOrtho.m12 = -((float)left + (float)right)/rl; - matOrtho.m13 = -((float)top + (float)bottom)/tb; - matOrtho.m14 = -((float)zfar + (float)znear)/fn; - matOrtho.m15 = 1.0f; - - *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho); -} -#endif - -// Set the viewport area (transformation from normalized device coordinates to window coordinates) -// NOTE: We store current viewport dimensions -void rlViewport(int x, int y, int width, int height) -{ - glViewport(x, y, width, height); -} - -// Set clip planes distances -void rlSetClipPlanes(double nearPlane, double farPlane) -{ - rlCullDistanceNear = nearPlane; - rlCullDistanceFar = farPlane; -} - -// Get cull plane distance near -double rlGetCullDistanceNear(void) -{ - return rlCullDistanceNear; -} - -// Get cull plane distance far -double rlGetCullDistanceFar(void) -{ - return rlCullDistanceFar; -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - Vertex level operations -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_11) -// Fallback to OpenGL 1.1 function calls -//--------------------------------------- -void rlBegin(int mode) -{ - switch (mode) - { - case RL_LINES: glBegin(GL_LINES); break; - case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; - case RL_QUADS: glBegin(GL_QUADS); break; - default: break; - } -} - -void rlEnd(void) { glEnd(); } -void rlVertex2i(int x, int y) { glVertex2i(x, y); } -void rlVertex2f(float x, float y) { glVertex2f(x, y); } -void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } -void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } -void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } -void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } -void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } -void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } -#endif -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Initialize drawing mode (how to organize vertex) -void rlBegin(int mode) -{ - // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS - // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode != mode) - { - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) - { - // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, - // that way, following QUADS drawing will keep aligned with index processing - // It implies adding some extra alignment vertex at the end of the draw, - // those vertex are not processed but they are considered as an additional offset - // for the next set of vertex to be drawn - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); - else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); - else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; - - if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) - { - RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; - RLGL.currentBatch->drawCounter++; - } - } - - if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); - - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = mode; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = RLGL.State.defaultTextureId; - } -} - -// Finish vertex providing -void rlEnd(void) -{ - // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, - // as well as depth buffer bit-depth (16bit or 24bit or 32bit) - // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) - RLGL.currentBatch->currentDepth += (1.0f/20000.0f); -} - -// Define one vertex (position) -// NOTE: Vertex position data is the basic information required for drawing -void rlVertex3f(float x, float y, float z) -{ - float tx = x; - float ty = y; - float tz = z; - - // Transform provided vector if required - if (RLGL.State.transformRequired) - { - tx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z + RLGL.State.transform.m12; - ty = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z + RLGL.State.transform.m13; - tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; - } - - // WARNING: We can't break primitives when launching a new batch - // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices - // We must check current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 - if (RLGL.State.vertexCounter > (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) - { - if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) && - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%2 == 0)) - { - // Reached the maximum number of vertices for RL_LINES drawing - // Launch a draw call but keep current state for next vertices comming - // NOTE: We add +1 vertex to the check for security - rlCheckRenderBatchLimit(2 + 1); - } - else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) && - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%3 == 0)) - { - rlCheckRenderBatchLimit(3 + 1); - } - else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_QUADS) && - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4 == 0)) - { - rlCheckRenderBatchLimit(4 + 1); - } - } - - // Add vertices - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter] = tx; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 1] = ty; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 2] = tz; - - // Add current texcoord - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter] = RLGL.State.texcoordx; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter + 1] = RLGL.State.texcoordy; - - // Add current normal - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter] = RLGL.State.normalx; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 1] = RLGL.State.normaly; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 2] = RLGL.State.normalz; - - // Add current color - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter] = RLGL.State.colorr; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 1] = RLGL.State.colorg; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 2] = RLGL.State.colorb; - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 3] = RLGL.State.colora; - - RLGL.State.vertexCounter++; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount++; -} - -// Define one vertex (position) -void rlVertex2f(float x, float y) -{ - rlVertex3f(x, y, RLGL.currentBatch->currentDepth); -} - -// Define one vertex (position) -void rlVertex2i(int x, int y) -{ - rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); -} - -// Define one vertex (texture coordinate) -// NOTE: Texture coordinates are limited to QUADS only -void rlTexCoord2f(float x, float y) -{ - RLGL.State.texcoordx = x; - RLGL.State.texcoordy = y; -} - -// Define one vertex (normal) -// NOTE: Normals limited to TRIANGLES only? -void rlNormal3f(float x, float y, float z) -{ - float normalx = x; - float normaly = y; - float normalz = z; - if (RLGL.State.transformRequired) - { - normalx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z; - normaly = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z; - normalz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z; - } - float length = sqrtf(normalx*normalx + normaly*normaly + normalz*normalz); - if (length != 0.0f) - { - float ilength = 1.0f/length; - normalx *= ilength; - normaly *= ilength; - normalz *= ilength; - } - RLGL.State.normalx = normalx; - RLGL.State.normaly = normaly; - RLGL.State.normalz = normalz; -} - -// Define one vertex (color) -void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) -{ - RLGL.State.colorr = x; - RLGL.State.colorg = y; - RLGL.State.colorb = z; - RLGL.State.colora = w; -} - -// Define one vertex (color) -void rlColor4f(float r, float g, float b, float a) -{ - rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); -} - -// Define one vertex (color) -void rlColor3f(float x, float y, float z) -{ - rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); -} - -#endif - -//-------------------------------------------------------------------------------------- -// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) -//-------------------------------------------------------------------------------------- - -// Set current texture to use -void rlSetTexture(unsigned int id) -{ - if (id == 0) - { -#if defined(GRAPHICS_API_OPENGL_11) - rlDisableTexture(); -#else - // NOTE: If quads batch limit is reached, we force a draw call and next batch starts - if (RLGL.State.vertexCounter >= - RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4) - { - rlDrawRenderBatch(RLGL.currentBatch); - } -#endif - } - else - { -#if defined(GRAPHICS_API_OPENGL_11) - rlEnableTexture(id); -#else - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId != id) - { - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) - { - // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, - // that way, following QUADS drawing will keep aligned with index processing - // It implies adding some extra alignment vertex at the end of the draw, - // those vertex are not processed but they are considered as an additional offset - // for the next set of vertex to be drawn - if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); - else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); - else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; - - if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) - { - RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; - - RLGL.currentBatch->drawCounter++; - } - } - - if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); - - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = id; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; - } -#endif - } -} - -// Select and active a texture slot -void rlActiveTextureSlot(int slot) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glActiveTexture(GL_TEXTURE0 + slot); -#endif -} - -// Enable texture -void rlEnableTexture(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_11) - glEnable(GL_TEXTURE_2D); -#endif - glBindTexture(GL_TEXTURE_2D, id); -} - -// Disable texture -void rlDisableTexture(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) - glDisable(GL_TEXTURE_2D); -#endif - glBindTexture(GL_TEXTURE_2D, 0); -} - -// Enable texture cubemap -void rlEnableTextureCubemap(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindTexture(GL_TEXTURE_CUBE_MAP, id); -#endif -} - -// Disable texture cubemap -void rlDisableTextureCubemap(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#endif -} - -// Set texture parameters (wrap mode/filter mode) -void rlTextureParameters(unsigned int id, int param, int value) -{ - glBindTexture(GL_TEXTURE_2D, id); - -#if !defined(GRAPHICS_API_OPENGL_11) - // Reset anisotropy filter, in case it was set - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); -#endif - - switch (param) - { - case RL_TEXTURE_WRAP_S: - case RL_TEXTURE_WRAP_T: - { - if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) - { -#if !defined(GRAPHICS_API_OPENGL_11) - if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); - else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); -#endif - } - else glTexParameteri(GL_TEXTURE_2D, param, value); - - } break; - case RL_TEXTURE_MAG_FILTER: - case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; - case RL_TEXTURE_FILTER_ANISOTROPIC: - { -#if !defined(GRAPHICS_API_OPENGL_11) - if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) - { - TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - } - else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); -#endif - } break; -#if defined(GRAPHICS_API_OPENGL_33) - case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, value/100.0f); -#endif - default: break; - } - - glBindTexture(GL_TEXTURE_2D, 0); -} - -// Set cubemap parameters (wrap mode/filter mode) -void rlCubemapParameters(unsigned int id, int param, int value) -{ -#if !defined(GRAPHICS_API_OPENGL_11) - glBindTexture(GL_TEXTURE_CUBE_MAP, id); - - // Reset anisotropy filter, in case it was set - glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); - - switch (param) - { - case RL_TEXTURE_WRAP_S: - case RL_TEXTURE_WRAP_T: - { - if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) - { - if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); - else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); - } - else glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); - - } break; - case RL_TEXTURE_MAG_FILTER: - case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); break; - case RL_TEXTURE_FILTER_ANISOTROPIC: - { - if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) - { - TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); - glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); - } - else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); - } break; -#if defined(GRAPHICS_API_OPENGL_33) - case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_LOD_BIAS, value/100.0f); -#endif - default: break; - } - - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#endif -} - -// Enable shader program -void rlEnableShader(unsigned int id) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - glUseProgram(id); -#endif -} - -// Disable shader program -void rlDisableShader(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - glUseProgram(0); -#endif -} - -// Enable rendering to texture (fbo) -void rlEnableFramebuffer(unsigned int id) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, id); -#endif -} - -// return the active render texture (fbo) -unsigned int rlGetActiveFramebuffer(void) -{ - GLint fboId = 0; -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) - glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); -#endif - return fboId; -} - -// Disable rendering to texture -void rlDisableFramebuffer(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif -} - -// Blit active framebuffer to main framebuffer -void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBlitFramebuffer(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask, GL_NEAREST); -#endif -} - -// Bind framebuffer object (fbo) -void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(target, framebuffer); -#endif -} - -// Activate multiple draw color buffers -// NOTE: One color buffer is always active by default -void rlActiveDrawBuffers(int count) -{ -#if ((defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT)) - // NOTE: Maximum number of draw buffers supported is implementation dependant, - // it can be queried with glGet*() but it must be at least 8 - //GLint maxDrawBuffers = 0; - //glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); - - if (count > 0) - { - if (count > 8) TRACELOG(LOG_WARNING, "GL: Max color buffers limited to 8"); - else - { - unsigned int buffers[8] = { -#if defined(GRAPHICS_API_OPENGL_ES3) - GL_COLOR_ATTACHMENT0_EXT, - GL_COLOR_ATTACHMENT1_EXT, - GL_COLOR_ATTACHMENT2_EXT, - GL_COLOR_ATTACHMENT3_EXT, - GL_COLOR_ATTACHMENT4_EXT, - GL_COLOR_ATTACHMENT5_EXT, - GL_COLOR_ATTACHMENT6_EXT, - GL_COLOR_ATTACHMENT7_EXT, -#else - GL_COLOR_ATTACHMENT0, - GL_COLOR_ATTACHMENT1, - GL_COLOR_ATTACHMENT2, - GL_COLOR_ATTACHMENT3, - GL_COLOR_ATTACHMENT4, - GL_COLOR_ATTACHMENT5, - GL_COLOR_ATTACHMENT6, - GL_COLOR_ATTACHMENT7, -#endif - }; - -#if defined(GRAPHICS_API_OPENGL_ES3) - glDrawBuffersEXT(count, buffers); -#else - glDrawBuffers(count, buffers); -#endif - } - } - else TRACELOG(LOG_WARNING, "GL: One color buffer active by default"); -#endif -} - -//---------------------------------------------------------------------------------- -// General render state configuration -//---------------------------------------------------------------------------------- - -// Enable color blending -void rlEnableColorBlend(void) { glEnable(GL_BLEND); } - -// Disable color blending -void rlDisableColorBlend(void) { glDisable(GL_BLEND); } - -// Enable depth test -void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } - -// Disable depth test -void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } - -// Enable depth write -void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } - -// Disable depth write -void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } - -// Enable backface culling -void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } - -// Disable backface culling -void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } - -// Set color mask active for screen read/draw -void rlColorMask(bool r, bool g, bool b, bool a) { glColorMask(r, g, b, a); } - -// Set face culling mode -void rlSetCullFace(int mode) -{ - switch (mode) - { - case RL_CULL_FACE_BACK: glCullFace(GL_BACK); break; - case RL_CULL_FACE_FRONT: glCullFace(GL_FRONT); break; - default: break; - } -} - -// Enable scissor test -void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } - -// Disable scissor test -void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } - -// Scissor test -void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } - -// Enable wire mode -void rlEnableWireMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: glPolygonMode() not available on OpenGL ES - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); -#endif -} - -// Enable point mode -void rlEnablePointMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: glPolygonMode() not available on OpenGL ES - glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); - glEnable(GL_PROGRAM_POINT_SIZE); -#endif -} - -// Disable wire mode -void rlDisableWireMode(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - // NOTE: glPolygonMode() not available on OpenGL ES - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); -#endif -} - -// Set the line drawing width -void rlSetLineWidth(float width) { glLineWidth(width); } - -// Get the line drawing width -float rlGetLineWidth(void) -{ - float width = 0; - glGetFloatv(GL_LINE_WIDTH, &width); - return width; -} - -// Enable line aliasing -void rlEnableSmoothLines(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) - glEnable(GL_LINE_SMOOTH); -#endif -} - -// Disable line aliasing -void rlDisableSmoothLines(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) - glDisable(GL_LINE_SMOOTH); -#endif -} - -// Enable stereo rendering -void rlEnableStereoRender(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - RLGL.State.stereoRender = true; -#endif -} - -// Disable stereo rendering -void rlDisableStereoRender(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - RLGL.State.stereoRender = false; -#endif -} - -// Check if stereo render is enabled -bool rlIsStereoRenderEnabled(void) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) - return RLGL.State.stereoRender; -#else - return false; -#endif -} - -// Clear color buffer with color -void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) -{ - // Color values clamp to 0.0f(0) and 1.0f(255) - float cr = (float)r/255; - float cg = (float)g/255; - float cb = (float)b/255; - float ca = (float)a/255; - - glClearColor(cr, cg, cb, ca); -} - -// Clear used screen buffers (color and depth) -void rlClearScreenBuffers(void) -{ - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) - //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... -} - -// Check and log OpenGL error codes -void rlCheckErrors(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - int check = 1; - while (check) - { - const GLenum err = glGetError(); - switch (err) - { - case GL_NO_ERROR: check = 0; break; - case 0x0500: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; - case 0x0501: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; - case 0x0502: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; - case 0x0503: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; - case 0x0504: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; - case 0x0505: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; - case 0x0506: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; - default: TRACELOG(RL_LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; - } - } -#endif -} - -// Set blend mode -void rlSetBlendMode(int mode) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.currentBlendMode != mode) || ((mode == RL_BLEND_CUSTOM || mode == RL_BLEND_CUSTOM_SEPARATE) && RLGL.State.glCustomBlendModeModified)) - { - rlDrawRenderBatch(RLGL.currentBatch); - - switch (mode) - { - case RL_BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; - case RL_BLEND_ALPHA_PREMULTIPLY: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; - case RL_BLEND_CUSTOM: - { - // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactors() - glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); - - } break; - case RL_BLEND_CUSTOM_SEPARATE: - { - // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactorsSeparate() - glBlendFuncSeparate(RLGL.State.glBlendSrcFactorRGB, RLGL.State.glBlendDestFactorRGB, RLGL.State.glBlendSrcFactorAlpha, RLGL.State.glBlendDestFactorAlpha); - glBlendEquationSeparate(RLGL.State.glBlendEquationRGB, RLGL.State.glBlendEquationAlpha); - - } break; - default: break; - } - - RLGL.State.currentBlendMode = mode; - RLGL.State.glCustomBlendModeModified = false; - } -#endif -} - -// Set blending mode factor and equation -void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.glBlendSrcFactor != glSrcFactor) || - (RLGL.State.glBlendDstFactor != glDstFactor) || - (RLGL.State.glBlendEquation != glEquation)) - { - RLGL.State.glBlendSrcFactor = glSrcFactor; - RLGL.State.glBlendDstFactor = glDstFactor; - RLGL.State.glBlendEquation = glEquation; - - RLGL.State.glCustomBlendModeModified = true; - } -#endif -} - -// Set blending mode factor and equation separately for RGB and alpha -void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.glBlendSrcFactorRGB != glSrcRGB) || - (RLGL.State.glBlendDestFactorRGB != glDstRGB) || - (RLGL.State.glBlendSrcFactorAlpha != glSrcAlpha) || - (RLGL.State.glBlendDestFactorAlpha != glDstAlpha) || - (RLGL.State.glBlendEquationRGB != glEqRGB) || - (RLGL.State.glBlendEquationAlpha != glEqAlpha)) - { - RLGL.State.glBlendSrcFactorRGB = glSrcRGB; - RLGL.State.glBlendDestFactorRGB = glDstRGB; - RLGL.State.glBlendSrcFactorAlpha = glSrcAlpha; - RLGL.State.glBlendDestFactorAlpha = glDstAlpha; - RLGL.State.glBlendEquationRGB = glEqRGB; - RLGL.State.glBlendEquationAlpha = glEqAlpha; - - RLGL.State.glCustomBlendModeModified = true; - } -#endif -} - -//---------------------------------------------------------------------------------- -// Module Functions Definition - OpenGL Debug -//---------------------------------------------------------------------------------- -#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) -static void GLAPIENTRY rlDebugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) -{ - // Ignore non-significant error/warning codes (NVidia drivers) - // NOTE: Here there are the details with a sample output: - // - #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low) - // - #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4) - // will use VIDEO memory as the source for buffer object operations. (severity: low) - // - #131218 - Program/shader state performance warning: Vertex shader in program 7 is being recompiled based on GL state. (severity: medium) - // - #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 does not have - // a defined base level and cannot be used for texture mapping. (severity: low) - if ((id == 131169) || (id == 131185) || (id == 131218) || (id == 131204)) return; - - const char *msgSource = NULL; - switch (source) - { - case GL_DEBUG_SOURCE_API: msgSource = "API"; break; - case GL_DEBUG_SOURCE_WINDOW_SYSTEM: msgSource = "WINDOW_SYSTEM"; break; - case GL_DEBUG_SOURCE_SHADER_COMPILER: msgSource = "SHADER_COMPILER"; break; - case GL_DEBUG_SOURCE_THIRD_PARTY: msgSource = "THIRD_PARTY"; break; - case GL_DEBUG_SOURCE_APPLICATION: msgSource = "APPLICATION"; break; - case GL_DEBUG_SOURCE_OTHER: msgSource = "OTHER"; break; - default: break; - } - - const char *msgType = NULL; - switch (type) - { - case GL_DEBUG_TYPE_ERROR: msgType = "ERROR"; break; - case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: msgType = "DEPRECATED_BEHAVIOR"; break; - case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: msgType = "UNDEFINED_BEHAVIOR"; break; - case GL_DEBUG_TYPE_PORTABILITY: msgType = "PORTABILITY"; break; - case GL_DEBUG_TYPE_PERFORMANCE: msgType = "PERFORMANCE"; break; - case GL_DEBUG_TYPE_MARKER: msgType = "MARKER"; break; - case GL_DEBUG_TYPE_PUSH_GROUP: msgType = "PUSH_GROUP"; break; - case GL_DEBUG_TYPE_POP_GROUP: msgType = "POP_GROUP"; break; - case GL_DEBUG_TYPE_OTHER: msgType = "OTHER"; break; - default: break; - } - - const char *msgSeverity = "DEFAULT"; - switch (severity) - { - case GL_DEBUG_SEVERITY_LOW: msgSeverity = "LOW"; break; - case GL_DEBUG_SEVERITY_MEDIUM: msgSeverity = "MEDIUM"; break; - case GL_DEBUG_SEVERITY_HIGH: msgSeverity = "HIGH"; break; - case GL_DEBUG_SEVERITY_NOTIFICATION: msgSeverity = "NOTIFICATION"; break; - default: break; - } - - TRACELOG(LOG_WARNING, "GL: OpenGL debug message: %s", message); - TRACELOG(LOG_WARNING, " > Type: %s", msgType); - TRACELOG(LOG_WARNING, " > Source = %s", msgSource); - TRACELOG(LOG_WARNING, " > Severity = %s", msgSeverity); -} -#endif - -//---------------------------------------------------------------------------------- -// Module Functions Definition - rlgl functionality -//---------------------------------------------------------------------------------- - -// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states -void rlglInit(int width, int height) -{ - // Enable OpenGL debug context if required -#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) - if ((glDebugMessageCallback != NULL) && (glDebugMessageControl != NULL)) - { - glDebugMessageCallback(rlDebugMessageCallback, 0); - // glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE); - - // Debug context options: - // - GL_DEBUG_OUTPUT - Faster version but not useful for breakpoints - // - GL_DEBUG_OUTPUT_SYNCHRONUS - Callback is in sync with errors, so a breakpoint can be placed on the callback in order to get a stacktrace for the GL error - glEnable(GL_DEBUG_OUTPUT); - glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); - } -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Init default white texture - unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) - RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); - - if (RLGL.State.defaultTextureId != 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); - else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load default texture"); - - // Init default Shader (customized for GL 3.3 and ES2) - // Loaded: RLGL.State.defaultShaderId + RLGL.State.defaultShaderLocs - rlLoadShaderDefault(); - RLGL.State.currentShaderId = RLGL.State.defaultShaderId; - RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs; - - // Init default vertex arrays buffers - // Simulate that the default shader has the location RL_SHADER_LOC_VERTEX_NORMAL to bind the normal buffer for the default render batch - RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL; - RLGL.defaultBatch = rlLoadRenderBatch(RL_DEFAULT_BATCH_BUFFERS, RL_DEFAULT_BATCH_BUFFER_ELEMENTS); - RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = -1; - RLGL.currentBatch = &RLGL.defaultBatch; - - // Init stack matrices (emulating OpenGL 1.1) - for (int i = 0; i < RL_MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = rlMatrixIdentity(); - - // Init internal matrices - RLGL.State.transform = rlMatrixIdentity(); - RLGL.State.projection = rlMatrixIdentity(); - RLGL.State.modelview = rlMatrixIdentity(); - RLGL.State.currentMatrix = &RLGL.State.modelview; -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - - // Initialize OpenGL default states - //---------------------------------------------------------- - // Init state: Depth test - glDepthFunc(GL_LEQUAL); // Type of depth testing to apply - glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) - - // Init state: Blending mode - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) - glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) - - // Init state: Culling - // NOTE: All shapes/models triangles are drawn CCW - glCullFace(GL_BACK); // Cull the back face (default) - glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) - glEnable(GL_CULL_FACE); // Enable backface culling - - // Init state: Cubemap seamless -#if defined(GRAPHICS_API_OPENGL_33) - glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) -#endif - -#if defined(GRAPHICS_API_OPENGL_11) - // Init state: Color hints (deprecated in OpenGL 3.0+) - glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation - glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) -#endif - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Store screen size into global variables - RLGL.State.framebufferWidth = width; - RLGL.State.framebufferHeight = height; - - TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); - //---------------------------------------------------------- -#endif - - // Init state: Color/Depth buffers clear - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) - glClearDepth(1.0f); // Set clear depth value (default) - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) -} - -// Vertex Buffer Object deinitialization (memory free) -void rlglClose(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - rlUnloadRenderBatch(RLGL.defaultBatch); - - rlUnloadShaderDefault(); // Unload default shader - - glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture - TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); -#endif -} - -// Load OpenGL extensions -// NOTE: External loader function must be provided -void rlLoadExtensions(void *loader) -{ -#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 - // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) - if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); - else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); - - // Get number of supported extensions - GLint numExt = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); - TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) - // Get supported extensions list - // WARNING: glGetStringi() not available on OpenGL 2.1 - TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); - for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", glGetStringi(GL_EXTENSIONS, i)); -#endif - -#if defined(GRAPHICS_API_OPENGL_21) - // Register supported extensions flags - // Optional OpenGL 2.1 extensions - RLGL.ExtSupported.vao = GLAD_GL_ARB_vertex_array_object; - RLGL.ExtSupported.instancing = (GLAD_GL_EXT_draw_instanced && GLAD_GL_ARB_instanced_arrays); - RLGL.ExtSupported.texNPOT = GLAD_GL_ARB_texture_non_power_of_two; - RLGL.ExtSupported.texFloat32 = GLAD_GL_ARB_texture_float; - RLGL.ExtSupported.texFloat16 = GLAD_GL_ARB_texture_float; - RLGL.ExtSupported.texDepth = GLAD_GL_ARB_depth_texture; - RLGL.ExtSupported.maxDepthBits = 32; - RLGL.ExtSupported.texAnisoFilter = GLAD_GL_EXT_texture_filter_anisotropic; - RLGL.ExtSupported.texMirrorClamp = GLAD_GL_EXT_texture_mirror_clamp; -#else - // Register supported extensions flags - // OpenGL 3.3 extensions supported by default (core) - RLGL.ExtSupported.vao = true; - RLGL.ExtSupported.instancing = true; - RLGL.ExtSupported.texNPOT = true; - RLGL.ExtSupported.texFloat32 = true; - RLGL.ExtSupported.texFloat16 = true; - RLGL.ExtSupported.texDepth = true; - RLGL.ExtSupported.maxDepthBits = 32; - RLGL.ExtSupported.texAnisoFilter = true; - RLGL.ExtSupported.texMirrorClamp = true; -#endif - - // Optional OpenGL 3.3 extensions - RLGL.ExtSupported.texCompASTC = GLAD_GL_KHR_texture_compression_astc_hdr && GLAD_GL_KHR_texture_compression_astc_ldr; - RLGL.ExtSupported.texCompDXT = GLAD_GL_EXT_texture_compression_s3tc; // Texture compression: DXT - RLGL.ExtSupported.texCompETC2 = GLAD_GL_ARB_ES3_compatibility; // Texture compression: ETC2/EAC - #if defined(GRAPHICS_API_OPENGL_43) - RLGL.ExtSupported.computeShader = GLAD_GL_ARB_compute_shader; - RLGL.ExtSupported.ssbo = GLAD_GL_ARB_shader_storage_buffer_object; - #endif - -#endif // GRAPHICS_API_OPENGL_33 - -#if defined(GRAPHICS_API_OPENGL_ES3) - // Register supported extensions flags - // OpenGL ES 3.0 extensions supported by default (or it should be) - RLGL.ExtSupported.vao = true; - RLGL.ExtSupported.instancing = true; - RLGL.ExtSupported.texNPOT = true; - RLGL.ExtSupported.texFloat32 = true; - RLGL.ExtSupported.texFloat16 = true; - RLGL.ExtSupported.texDepth = true; - RLGL.ExtSupported.texDepthWebGL = true; - RLGL.ExtSupported.maxDepthBits = 24; - RLGL.ExtSupported.texAnisoFilter = true; - RLGL.ExtSupported.texMirrorClamp = true; - // TODO: Check for additional OpenGL ES 3.0 supported extensions: - //RLGL.ExtSupported.texCompDXT = true; - //RLGL.ExtSupported.texCompETC1 = true; - //RLGL.ExtSupported.texCompETC2 = true; - //RLGL.ExtSupported.texCompPVRT = true; - //RLGL.ExtSupported.texCompASTC = true; - //RLGL.ExtSupported.maxAnisotropyLevel = true; - //RLGL.ExtSupported.computeShader = true; - //RLGL.ExtSupported.ssbo = true; - -#elif defined(GRAPHICS_API_OPENGL_ES2) - - #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) - // TODO: Support GLAD loader for OpenGL ES 3.0 - if (gladLoadGLES2((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL ES2.0 functions"); - else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL ES 2.0 loaded successfully"); - #endif - - // Get supported extensions list - GLint numExt = 0; - const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) - const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string - - // NOTE: We have to duplicate string because glGetString() returns a const string - int size = strlen(extensions) + 1; // Get extensions string size in bytes - char *extensionsDup = (char *)RL_CALLOC(size, sizeof(char)); - strcpy(extensionsDup, extensions); - extList[numExt] = extensionsDup; - - for (int i = 0; i < size; i++) - { - if (extensionsDup[i] == ' ') - { - extensionsDup[i] = '\0'; - numExt++; - extList[numExt] = &extensionsDup[i + 1]; - } - } - - TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) - TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); - for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", extList[i]); -#endif - - // Check required extensions - for (int i = 0; i < numExt; i++) - { - // Check VAO support - // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature - if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) - { - // The extension is supported by our hardware and driver, try to get related functions pointers - // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... - glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); - glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); - glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); - //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted - - if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; - } - - // Check instanced rendering support - if (strstr(extList[i], (const char*)"instanced_arrays") != NULL) // Broad check for instanced_arrays - { - // Specific check - if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); - } - else if (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0) // EXT - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); - } - else if (strcmp(extList[i], (const char *)"GL_NV_instanced_arrays") == 0) // NVIDIA GLES - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); - glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorNV"); - } - - // The feature will only be marked as supported if the elements from GL_XXX_instanced_arrays are present - if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; - } - else if (strstr(extList[i], (const char *)"draw_instanced") != NULL) - { - // GL_ANGLE_draw_instanced doesn't exist - if (strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); - } - else if (strcmp(extList[i], (const char*)"GL_NV_draw_instanced") == 0) - { - glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); - glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); - } - - // But the functions will at least be loaded if only GL_XX_EXT_draw_instanced exist - if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; - } - - // Check NPOT textures support - // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature - if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; - - // Check texture float support - if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; - if (strcmp(extList[i], (const char *)"GL_OES_texture_half_float") == 0) RLGL.ExtSupported.texFloat16 = true; - - // Check depth texture support - if (strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) RLGL.ExtSupported.texDepth = true; - if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true; // WebGL requires unsized internal format - if (RLGL.ExtSupported.texDepthWebGL) RLGL.ExtSupported.texDepth = true; - - if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; // Not available on WebGL - if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; // Not available on WebGL - - // Check texture compression support: DXT - if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; - - // Check texture compression support: ETC1 - if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || - (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; - - // Check texture compression support: ETC2/EAC - if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; - - // Check texture compression support: PVR - if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; - - // Check texture compression support: ASTC - if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; - - // Check anisotropic texture filter support - if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; - - // Check clamp mirror wrap mode support - if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; - } - - // Free extensions pointers - RL_FREE(extList); - RL_FREE(extensionsDup); // Duplicated string must be deallocated -#endif // GRAPHICS_API_OPENGL_ES2 - - // Check OpenGL information and capabilities - //------------------------------------------------------------------------------ - // Show current OpenGL and GLSL version - TRACELOG(RL_LOG_INFO, "GL: OpenGL device information:"); - TRACELOG(RL_LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); - TRACELOG(RL_LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); - TRACELOG(RL_LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); - TRACELOG(RL_LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Anisotropy levels capability is an extension - #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT - #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF - #endif - glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) - // Show some OpenGL GPU capabilities - TRACELOG(RL_LOG_INFO, "GL: OpenGL capabilities:"); - GLint capability = 0; - glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); - glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); - glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); - glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); - #if !defined(GRAPHICS_API_OPENGL_ES2) - glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); - glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); - if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); - #endif - glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); - TRACELOG(RL_LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); - GLint *compFormats = (GLint *)RL_CALLOC(capability, sizeof(GLint)); - glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compFormats); - for (int i = 0; i < capability; i++) TRACELOG(RL_LOG_INFO, " %s", rlGetCompressedFormatName(compFormats[i])); - RL_FREE(compFormats); - -#if defined(GRAPHICS_API_OPENGL_43) - glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); - glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); - TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); -#endif // GRAPHICS_API_OPENGL_43 -#else // RLGL_SHOW_GL_DETAILS_INFO - - // Show some basic info about GL supported features - if (RLGL.ExtSupported.vao) TRACELOG(RL_LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); - else TRACELOG(RL_LOG_WARNING, "GL: VAO extension not found, VAO not supported"); - if (RLGL.ExtSupported.texNPOT) TRACELOG(RL_LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); - else TRACELOG(RL_LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); - if (RLGL.ExtSupported.texCompDXT) TRACELOG(RL_LOG_INFO, "GL: DXT compressed textures supported"); - if (RLGL.ExtSupported.texCompETC1) TRACELOG(RL_LOG_INFO, "GL: ETC1 compressed textures supported"); - if (RLGL.ExtSupported.texCompETC2) TRACELOG(RL_LOG_INFO, "GL: ETC2/EAC compressed textures supported"); - if (RLGL.ExtSupported.texCompPVRT) TRACELOG(RL_LOG_INFO, "GL: PVRT compressed textures supported"); - if (RLGL.ExtSupported.texCompASTC) TRACELOG(RL_LOG_INFO, "GL: ASTC compressed textures supported"); - if (RLGL.ExtSupported.computeShader) TRACELOG(RL_LOG_INFO, "GL: Compute shaders supported"); - if (RLGL.ExtSupported.ssbo) TRACELOG(RL_LOG_INFO, "GL: Shader storage buffer objects supported"); -#endif // RLGL_SHOW_GL_DETAILS_INFO - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 -} - -// Get current OpenGL version -int rlGetVersion(void) -{ - int glVersion = 0; -#if defined(GRAPHICS_API_OPENGL_11) - glVersion = RL_OPENGL_11; -#endif -#if defined(GRAPHICS_API_OPENGL_21) - glVersion = RL_OPENGL_21; -#elif defined(GRAPHICS_API_OPENGL_43) - glVersion = RL_OPENGL_43; -#elif defined(GRAPHICS_API_OPENGL_33) - glVersion = RL_OPENGL_33; -#endif -#if defined(GRAPHICS_API_OPENGL_ES3) - glVersion = RL_OPENGL_ES_30; -#elif defined(GRAPHICS_API_OPENGL_ES2) - glVersion = RL_OPENGL_ES_20; -#endif - - return glVersion; -} - -// Set current framebuffer width -void rlSetFramebufferWidth(int width) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.framebufferWidth = width; -#endif -} - -// Set current framebuffer height -void rlSetFramebufferHeight(int height) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.framebufferHeight = height; -#endif -} - -// Get default framebuffer width -int rlGetFramebufferWidth(void) -{ - int width = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - width = RLGL.State.framebufferWidth; -#endif - return width; -} - -// Get default framebuffer height -int rlGetFramebufferHeight(void) -{ - int height = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - height = RLGL.State.framebufferHeight; -#endif - return height; -} - -// Get default internal texture (white texture) -// NOTE: Default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 -unsigned int rlGetTextureIdDefault(void) -{ - unsigned int id = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - id = RLGL.State.defaultTextureId; -#endif - return id; -} - -// Get default shader id -unsigned int rlGetShaderIdDefault(void) -{ - unsigned int id = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - id = RLGL.State.defaultShaderId; -#endif - return id; -} - -// Get default shader locs -int *rlGetShaderLocsDefault(void) -{ - int *locs = NULL; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - locs = RLGL.State.defaultShaderLocs; -#endif - return locs; -} - -// Render batch management -//------------------------------------------------------------------------------------------------ -// Load render batch -rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) -{ - rlRenderBatch batch = { 0 }; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) - //-------------------------------------------------------------------------------------------- - batch.vertexBuffer = (rlVertexBuffer *)RL_MALLOC(numBuffers*sizeof(rlVertexBuffer)); - - for (int i = 0; i < numBuffers; i++) - { - batch.vertexBuffer[i].elementCount = bufferElements; - - batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad - batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad - batch.vertexBuffer[i].normals = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad - batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad -#if defined(GRAPHICS_API_OPENGL_33) - batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) - batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) -#endif - - for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; - for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; - for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].normals[j] = 0.0f; - for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; - - int k = 0; - - // Indices can be initialized right now - for (int j = 0; j < (6*bufferElements); j += 6) - { - batch.vertexBuffer[i].indices[j] = 4*k; - batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; - batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; - batch.vertexBuffer[i].indices[j + 3] = 4*k; - batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; - batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; - - k++; - } - - RLGL.State.vertexCounter = 0; - } - - TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); - //-------------------------------------------------------------------------------------------- - - // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs - //-------------------------------------------------------------------------------------------- - for (int i = 0; i < numBuffers; i++) - { - if (RLGL.ExtSupported.vao) - { - // Initialize Quads VAO - glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); - glBindVertexArray(batch.vertexBuffer[i].vaoId); - } - - // Quads - Vertex buffers binding and attributes enable - // Vertex position buffer (shader-location = 0) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - - // Vertex texcoord buffer (shader-location = 1) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - - // Vertex normal buffer (shader-location = 2) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].normals, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); - - // Vertex color buffer (shader-location = 3) - glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); - glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); - glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - - // Fill index buffer - glGenBuffers(1, &batch.vertexBuffer[i].vboId[4]); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[4]); -#if defined(GRAPHICS_API_OPENGL_33) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); -#endif -#if defined(GRAPHICS_API_OPENGL_ES2) - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); -#endif - } - - TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); - - // Unbind the current VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - //-------------------------------------------------------------------------------------------- - - // Init draw calls tracking system - //-------------------------------------------------------------------------------------------- - batch.draws = (rlDrawCall *)RL_MALLOC(RL_DEFAULT_BATCH_DRAWCALLS*sizeof(rlDrawCall)); - - for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) - { - batch.draws[i].mode = RL_QUADS; - batch.draws[i].vertexCount = 0; - batch.draws[i].vertexAlignment = 0; - //batch.draws[i].vaoId = 0; - //batch.draws[i].shaderId = 0; - batch.draws[i].textureId = RLGL.State.defaultTextureId; - //batch.draws[i].RLGL.State.projection = rlMatrixIdentity(); - //batch.draws[i].RLGL.State.modelview = rlMatrixIdentity(); - } - - batch.bufferCount = numBuffers; // Record buffer count - batch.drawCounter = 1; // Reset draws counter - batch.currentDepth = -1.0f; // Reset depth value - //-------------------------------------------------------------------------------------------- -#endif - - return batch; -} - -// Unload default internal buffers vertex data from CPU and GPU -void rlUnloadRenderBatch(rlRenderBatch batch) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Unbind everything - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - - // Unload all vertex buffers data - for (int i = 0; i < batch.bufferCount; i++) - { - // Unbind VAO attribs data - if (RLGL.ExtSupported.vao) - { - glBindVertexArray(batch.vertexBuffer[i].vaoId); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); - glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); - glBindVertexArray(0); - } - - // Delete VBOs from GPU (VRAM) - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); - glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[4]); - - // Delete VAOs from GPU (VRAM) - if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); - - // Free vertex arrays memory from CPU (RAM) - RL_FREE(batch.vertexBuffer[i].vertices); - RL_FREE(batch.vertexBuffer[i].texcoords); - RL_FREE(batch.vertexBuffer[i].normals); - RL_FREE(batch.vertexBuffer[i].colors); - RL_FREE(batch.vertexBuffer[i].indices); - } - - // Unload arrays - RL_FREE(batch.vertexBuffer); - RL_FREE(batch.draws); -#endif -} - -// Draw render batch -// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) -void rlDrawRenderBatch(rlRenderBatch *batch) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Update batch vertex buffers - //------------------------------------------------------------------------------------------------------------ - // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) - // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (use a change detector flag?) - if (RLGL.State.vertexCounter > 0) - { - // Activate elements VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); - - // Vertex positions buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer - - // Texture coordinates buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer - - // Normals buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].normals); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].normals, GL_DYNAMIC_DRAW); // Update all buffer - - // Colors buffer - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); - glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); - //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer - - // NOTE: glMapBuffer() causes sync issue - // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job - // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer() - // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new - // allocated pointer immediately even if GPU is still working with the previous data - - // Another option: map the buffer object into client's memory - // Probably this code could be moved somewhere else... - // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); - // if (batch->vertexBuffer[batch->currentBuffer].vertices) - // { - // Update vertex data - // } - // glUnmapBuffer(GL_ARRAY_BUFFER); - - // Unbind the current VAO - if (RLGL.ExtSupported.vao) glBindVertexArray(0); - } - //------------------------------------------------------------------------------------------------------------ - - // Draw batch vertex buffers (considering VR stereo if required) - //------------------------------------------------------------------------------------------------------------ - Matrix matProjection = RLGL.State.projection; - Matrix matModelView = RLGL.State.modelview; - - int eyeCount = 1; - if (RLGL.State.stereoRender) eyeCount = 2; - - for (int eye = 0; eye < eyeCount; eye++) - { - if (eyeCount == 2) - { - // Setup current eye viewport (half screen width) - rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); - - // Set current eye view offset to modelview matrix - rlSetMatrixModelview(rlMatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); - // Set current eye projection matrix - rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); - } - - // Draw buffers - if (RLGL.State.vertexCounter > 0) - { - // Set current shader and upload current MVP matrix - glUseProgram(RLGL.State.currentShaderId); - - // Create modelview-projection matrix and upload to shader - Matrix matMVP = rlMatrixMultiply(RLGL.State.modelview, RLGL.State.projection); - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, rlMatrixToFloat(matMVP)); - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION], 1, false, rlMatrixToFloat(RLGL.State.projection)); - } - - // WARNING: For the following setup of the view, model, and normal matrices, it is expected that - // transformations and rendering occur between rlPushMatrix() and rlPopMatrix() - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW], 1, false, rlMatrixToFloat(RLGL.State.modelview)); - } - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL], 1, false, rlMatrixToFloat(RLGL.State.transform)); - } - - if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL] != -1) - { - glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL], 1, false, rlMatrixToFloat(rlMatrixTranspose(rlMatrixInvert(RLGL.State.transform)))); - } - - if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); - else - { - // Bind vertex attrib: position (shader-location = 0) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); - - // Bind vertex attrib: texcoord (shader-location = 1) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); - - // Bind vertex attrib: normal (shader-location = 2) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); - - // Bind vertex attrib: color (shader-location = 3) - glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); - glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); - glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); - - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[4]); - } - - // Setup some default shader values - glUniform4f(RLGL.State.currentShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); - glUniform1i(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 - - // Activate additional sampler textures - // Those additional textures will be common for all draw calls of the batch - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) - { - if (RLGL.State.activeTextureId[i] > 0) - { - glActiveTexture(GL_TEXTURE0 + 1 + i); - glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); - } - } - - // Activate default sampler2D texture0 (one texture is always active for default batch shader) - // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls - glActiveTexture(GL_TEXTURE0); - - for (int i = 0, vertexOffset = 0; i < batch->drawCounter; i++) - { - // Bind current draw call texture, activated as GL_TEXTURE0 and Bound to sampler2D texture0 by default - glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); - - if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); - else - { - #if defined(GRAPHICS_API_OPENGL_33) - // We need to define the number of indices to be processed: elementCount*6 - // NOTE: The final parameter tells the GPU the offset in bytes from the - // start of the index buffer to the location of the first index to process - glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); - #endif - #if defined(GRAPHICS_API_OPENGL_ES2) - glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); - #endif - } - - vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); - } - - if (!RLGL.ExtSupported.vao) - { - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); - } - - glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures - } - - if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO - - glUseProgram(0); // Unbind shader program - } - - // Restore viewport to default measures - if (eyeCount == 2) rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); - //------------------------------------------------------------------------------------------------------------ - - // Reset batch buffers - //------------------------------------------------------------------------------------------------------------ - // Reset vertex counter for next frame - RLGL.State.vertexCounter = 0; - - // Reset depth for next draw - batch->currentDepth = -1.0f; - - // Restore projection/modelview matrices - RLGL.State.projection = matProjection; - RLGL.State.modelview = matModelView; - - // Reset RLGL.currentBatch->draws array - for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) - { - batch->draws[i].mode = RL_QUADS; - batch->draws[i].vertexCount = 0; - batch->draws[i].textureId = RLGL.State.defaultTextureId; - } - - // Reset active texture units for next batch - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) RLGL.State.activeTextureId[i] = 0; - - // Reset draws counter to one draw for the batch - batch->drawCounter = 1; - //------------------------------------------------------------------------------------------------------------ - - // Change to next buffer in the list (in case of multi-buffering) - batch->currentBuffer++; - if (batch->currentBuffer >= batch->bufferCount) batch->currentBuffer = 0; -#endif -} - -// Set the active render batch for rlgl -void rlSetRenderBatchActive(rlRenderBatch *batch) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - rlDrawRenderBatch(RLGL.currentBatch); - - if (batch != NULL) RLGL.currentBatch = batch; - else RLGL.currentBatch = &RLGL.defaultBatch; -#endif -} - -// Update and draw internal render batch -void rlDrawRenderBatchActive(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside -#endif -} - -// Check internal buffer overflow for a given number of vertex -// and force a rlRenderBatch draw call if required -bool rlCheckRenderBatchLimit(int vCount) -{ - bool overflow = false; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((RLGL.State.vertexCounter + vCount) >= - (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4)) - { - overflow = true; - - // Store current primitive drawing mode and texture id - int currentMode = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode; - int currentTexture = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId; - - rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside - - // Restore state of last batch so we can continue adding vertices - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = currentMode; - RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = currentTexture; - } -#endif - - return overflow; -} - -// Textures data management -//----------------------------------------------------------------------------------------- -// Convert image data to OpenGL texture (returns OpenGL valid Id) -unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount) -{ - unsigned int id = 0; - - glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding - - // Check texture format support by OpenGL 1.1 (compressed textures not supported) -#if defined(GRAPHICS_API_OPENGL_11) - if (format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) - { - TRACELOG(RL_LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); - return id; - } -#else - if ((!RLGL.ExtSupported.texCompDXT) && ((format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA) || - (format == RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: DXT compressed texture format not supported"); - return id; - } -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if ((!RLGL.ExtSupported.texCompETC1) && (format == RL_PIXELFORMAT_COMPRESSED_ETC1_RGB)) - { - TRACELOG(RL_LOG_WARNING, "GL: ETC1 compressed texture format not supported"); - return id; - } - - if ((!RLGL.ExtSupported.texCompETC2) && ((format == RL_PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: ETC2 compressed texture format not supported"); - return id; - } - - if ((!RLGL.ExtSupported.texCompPVRT) && ((format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: PVRT compressed texture format not supported"); - return id; - } - - if ((!RLGL.ExtSupported.texCompASTC) && ((format == RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) - { - TRACELOG(RL_LOG_WARNING, "GL: ASTC compressed texture format not supported"); - return id; - } -#endif -#endif // GRAPHICS_API_OPENGL_11 - - glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - - glGenTextures(1, &id); // Generate texture id - - glBindTexture(GL_TEXTURE_2D, id); - - int mipWidth = width; - int mipHeight = height; - int mipOffset = 0; // Mipmap data offset, only used for tracelog - - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned char *dataPtr = NULL; - if (data != NULL) dataPtr = (unsigned char *)data; - - // Load the different mipmap levels - for (int i = 0; i < mipmapCount; i++) - { - unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - - TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); - - if (glInternalFormat != 0) - { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, dataPtr); -#if !defined(GRAPHICS_API_OPENGL_11) - else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, dataPtr); -#endif - -#if defined(GRAPHICS_API_OPENGL_33) - if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) - { - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } - else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) - { -#if defined(GRAPHICS_API_OPENGL_21) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; -#elif defined(GRAPHICS_API_OPENGL_33) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; -#endif - glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } -#endif - } - - mipWidth /= 2; - mipHeight /= 2; - mipOffset += mipSize; // Increment offset position to next mipmap - if (data != NULL) dataPtr += mipSize; // Increment data pointer to next mipmap - - // Security check for NPOT textures - if (mipWidth < 1) mipWidth = 1; - if (mipHeight < 1) mipHeight = 1; - } - - // Texture parameters configuration - // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used -#if defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used - if (RLGL.ExtSupported.texNPOT) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis - } - else - { - // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis - } -#else - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis -#endif - - // Magnification and minification filters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR - -#if defined(GRAPHICS_API_OPENGL_33) - if (mipmapCount > 1) - { - // Activate Trilinear filtering if mipmaps are available - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } -#endif - - // At this point we have the texture loaded in GPU and texture parameters configured - - // NOTE: If mipmaps were not in data, they are not generated automatically - - // Unbind current texture - glBindTexture(GL_TEXTURE_2D, 0); - - if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i | %s | %i mipmaps)", id, width, height, rlGetPixelFormatName(format), mipmapCount); - else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load texture"); - - return id; -} - -// Load depth texture/renderbuffer (to be attached to fbo) -// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture and WebGL requires WEBGL_depth_texture extensions -unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // In case depth textures not supported, we force renderbuffer usage - if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; - - // NOTE: We let the implementation to choose the best bit-depth - // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F - unsigned int glInternalFormat = GL_DEPTH_COMPONENT; - -#if (defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_ES3)) - // WARNING: WebGL platform requires unsized internal format definition (GL_DEPTH_COMPONENT) - // while other platforms using OpenGL ES 2.0 require/support sized internal formats depending on the GPU capabilities - if (!RLGL.ExtSupported.texDepthWebGL || useRenderBuffer) - { - if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; - else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; - else glInternalFormat = GL_DEPTH_COMPONENT16; - } -#endif - - if (!useRenderBuffer && RLGL.ExtSupported.texDepth) - { - glGenTextures(1, &id); - glBindTexture(GL_TEXTURE_2D, id); - glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - glBindTexture(GL_TEXTURE_2D, 0); - - TRACELOG(RL_LOG_INFO, "TEXTURE: Depth texture loaded successfully"); - } - else - { - // Create the renderbuffer that will serve as the depth attachment for the framebuffer - // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices - glGenRenderbuffers(1, &id); - glBindRenderbuffer(GL_RENDERBUFFER, id); - glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); - - glBindRenderbuffer(GL_RENDERBUFFER, 0); - - TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); - } -#endif - - return id; -} - -// Load texture cubemap -// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), -// expected the following convention: +X, -X, +Y, -Y, +Z, -Z -unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - int mipSize = size; - - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned char *dataPtr = NULL; - if (data != NULL) dataPtr = (unsigned char *)data; - - unsigned int dataSize = rlGetPixelDataSize(size, size, format); - - glGenTextures(1, &id); - glBindTexture(GL_TEXTURE_CUBE_MAP, id); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - - if (glInternalFormat != 0) - { - // Load cubemap faces/mipmaps - for (int i = 0; i < 6*mipmapCount; i++) - { - int mipmapLevel = i/6; - int face = i%6; - - if (data == NULL) - { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) - { - if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || - (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) || - (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || - (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); - else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, NULL); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); - } - else - { - if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize); - else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize); - } - -#if defined(GRAPHICS_API_OPENGL_33) - if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) - { - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; - glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } - else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) - { -#if defined(GRAPHICS_API_OPENGL_21) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; -#elif defined(GRAPHICS_API_OPENGL_33) - GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; -#endif - glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); - } -#endif - if (face == 5) - { - mipSize /= 2; - if (data != NULL) dataPtr += dataSize*6; // Increment data pointer to next mipmap - - // Security check for NPOT textures - if (mipSize < 1) mipSize = 1; - - dataSize = rlGetPixelDataSize(mipSize, mipSize, format); - } - } - } - - // Set cubemap texture sampling parameters - if (mipmapCount > 1) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - else glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); -#if defined(GRAPHICS_API_OPENGL_33) - glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 -#endif - - glBindTexture(GL_TEXTURE_CUBE_MAP, 0); -#endif - - if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); - else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); - - return id; -} - -// Update already loaded texture in GPU with new data -// NOTE: We don't know safely if internal texture format is the expected one... -void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) -{ - glBindTexture(GL_TEXTURE_2D, id); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - - if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) - { - glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, data); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); -} - -// Get OpenGL internal formats and data type from raylib PixelFormat -void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) -{ - *glInternalFormat = 0; - *glFormat = 0; - *glType = 0; - - switch (format) - { - #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; - #if !defined(GRAPHICS_API_OPENGL_11) - #if defined(GRAPHICS_API_OPENGL_ES3) - case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F_EXT; *glFormat = GL_RED_EXT; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F_EXT; *glFormat = GL_RGB; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F_EXT; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F_EXT; *glFormat = GL_RED_EXT; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F_EXT; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F_EXT; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; - #else - case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float - #if defined(GRAPHICS_API_OPENGL_21) - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_ARB; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_ARB; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_ARB; break; - #else // defined(GRAPHICS_API_OPENGL_ES2) - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float - #endif - #endif - #endif - #elif defined(GRAPHICS_API_OPENGL_33) - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F; *glFormat = GL_RED; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; - #endif - #if !defined(GRAPHICS_API_OPENGL_11) - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; - case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 - #endif - default: TRACELOG(RL_LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; - } -} - -// Unload texture from GPU memory -void rlUnloadTexture(unsigned int id) -{ - glDeleteTextures(1, &id); -} - -// Generate mipmap data for selected texture -// NOTE: Only supports GPU mipmap generation -void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindTexture(GL_TEXTURE_2D, id); - - // Check if texture is power-of-two (POT) - bool texIsPOT = false; - - if (((width > 0) && ((width & (width - 1)) == 0)) && - ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; - - if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) - { - //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorithm: GL_FASTEST, GL_NICEST, GL_DONT_CARE - glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically - - #define MIN(a,b) (((a)<(b))? (a):(b)) - #define MAX(a,b) (((a)>(b))? (a):(b)) - - *mipmaps = 1 + (int)floor(log(MAX(width, height))/log(2)); - TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", id, *mipmaps); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", id); - - glBindTexture(GL_TEXTURE_2D, 0); -#else - TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] GPU mipmap generation not supported", id); -#endif -} - -// Read texture pixel data -void *rlReadTexturePixels(unsigned int id, int width, int height, int format) -{ - void *pixels = NULL; - -#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) - glBindTexture(GL_TEXTURE_2D, id); - - // NOTE: Using texture id, we can retrieve some texture info (but not on OpenGL ES 2.0) - // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE - //int width, height, format; - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); - //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); - - // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding - // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting - // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) - // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) - glPixelStorei(GL_PACK_ALIGNMENT, 1); - - unsigned int glInternalFormat, glFormat, glType; - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - unsigned int size = rlGetPixelDataSize(width, height, format); - - if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) - { - pixels = RL_MALLOC(size); - glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); - } - else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", id, format); - - glBindTexture(GL_TEXTURE_2D, 0); -#endif - -#if defined(GRAPHICS_API_OPENGL_ES2) - // glGetTexImage() is not available on OpenGL ES 2.0 - // Texture width and height are required on OpenGL ES 2.0, there is no way to get it from texture id - // Two possible Options: - // 1 - Bind texture to color fbo attachment and glReadPixels() - // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() - // We are using Option 1, just need to care for texture format on retrieval - // NOTE: This behaviour could be conditioned by graphic driver... - unsigned int fboId = rlLoadFramebuffer(); - - glBindFramebuffer(GL_FRAMEBUFFER, fboId); - glBindTexture(GL_TEXTURE_2D, 0); - - // Attach our texture to FBO - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); - - // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format - pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(width, height, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - // Clean up temporal fbo - rlUnloadFramebuffer(fboId); -#endif - - return pixels; -} - -// Read screen pixel data (color buffer) -unsigned char *rlReadScreenPixels(int width, int height) -{ - unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); - - // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer - // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! - glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); - - // Flip image vertically! - unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); - - for (int y = height - 1; y >= 0; y--) - { - for (int x = 0; x < (width*4); x++) - { - imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line - - // Set alpha component value to 255 (no trasparent image retrieval) - // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! - if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; - } - } - - RL_FREE(screenData); - - return imgData; // NOTE: image data should be freed -} - -// Framebuffer management (fbo) -//----------------------------------------------------------------------------------------- -// Load a framebuffer to be used for rendering -// NOTE: No textures attached -unsigned int rlLoadFramebuffer(void) -{ - unsigned int fboId = 0; - -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glGenFramebuffers(1, &fboId); // Create the framebuffer object - glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer -#endif - - return fboId; -} - -// Attach color buffer texture to an fbo (unloads previous attachment) -// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture -void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, fboId); - - switch (attachType) - { - case RL_ATTACHMENT_COLOR_CHANNEL0: - case RL_ATTACHMENT_COLOR_CHANNEL1: - case RL_ATTACHMENT_COLOR_CHANNEL2: - case RL_ATTACHMENT_COLOR_CHANNEL3: - case RL_ATTACHMENT_COLOR_CHANNEL4: - case RL_ATTACHMENT_COLOR_CHANNEL5: - case RL_ATTACHMENT_COLOR_CHANNEL6: - case RL_ATTACHMENT_COLOR_CHANNEL7: - { - if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); - else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); - else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); - - } break; - case RL_ATTACHMENT_DEPTH: - { - if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); - else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); - - } break; - case RL_ATTACHMENT_STENCIL: - { - if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); - else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); - - } break; - default: break; - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif -} - -// Verify render texture is complete -bool rlFramebufferComplete(unsigned int id) -{ - bool result = false; - -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - glBindFramebuffer(GL_FRAMEBUFFER, id); - - GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); - - if (status != GL_FRAMEBUFFER_COMPLETE) - { - switch (status) - { - case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; -#if defined(GRAPHICS_API_OPENGL_ES2) - case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; -#endif - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; - default: break; - } - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - - result = (status == GL_FRAMEBUFFER_COMPLETE); -#endif - - return result; -} - -// Unload framebuffer from GPU memory -// NOTE: All attached textures/cubemaps/renderbuffers are also deleted -void rlUnloadFramebuffer(unsigned int id) -{ -#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) - // Query depth attachment to automatically delete texture/renderbuffer - int depthType = 0, depthId = 0; - glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type - glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); - - // TODO: Review warning retrieving object name in WebGL - // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name - // https://registry.khronos.org/webgl/specs/latest/1.0/ - glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); - - unsigned int depthIdU = (unsigned int)depthId; - if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); - else if (depthType == GL_TEXTURE) glDeleteTextures(1, &depthIdU); - - // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, - // the texture image is automatically detached from the currently bound framebuffer - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &id); - - TRACELOG(RL_LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); -#endif -} - -// Vertex data management -//----------------------------------------------------------------------------------------- -// Load a new attributes buffer -unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glGenBuffers(1, &id); - glBindBuffer(GL_ARRAY_BUFFER, id); - glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); -#endif - - return id; -} - -// Load a new attributes element buffer -unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glGenBuffers(1, &id); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); -#endif - - return id; -} - -// Enable vertex buffer (VBO) -void rlEnableVertexBuffer(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, id); -#endif -} - -// Disable vertex buffer (VBO) -void rlDisableVertexBuffer(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, 0); -#endif -} - -// Enable vertex buffer element (VBO element) -void rlEnableVertexBufferElement(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); -#endif -} - -// Disable vertex buffer element (VBO element) -void rlDisableVertexBufferElement(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); -#endif -} - -// Update vertex buffer with new data -// NOTE: dataSize and offset must be provided in bytes -void rlUpdateVertexBuffer(unsigned int id, const void *data, int dataSize, int offset) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ARRAY_BUFFER, id); - glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); -#endif -} - -// Update vertex buffer elements with new data -// NOTE: dataSize and offset must be provided in bytes -void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, dataSize, data); -#endif -} - -// Enable vertex array object (VAO) -bool rlEnableVertexArray(unsigned int vaoId) -{ - bool result = false; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glBindVertexArray(vaoId); - result = true; - } -#endif - return result; -} - -// Disable vertex array object (VAO) -void rlDisableVertexArray(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) glBindVertexArray(0); -#endif -} - -// Enable vertex attribute index -void rlEnableVertexAttribute(unsigned int index) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glEnableVertexAttribArray(index); -#endif -} - -// Disable vertex attribute index -void rlDisableVertexAttribute(unsigned int index) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDisableVertexAttribArray(index); -#endif -} - -// Draw vertex array -void rlDrawVertexArray(int offset, int count) -{ - glDrawArrays(GL_TRIANGLES, offset, count); -} - -// Draw vertex array elements -void rlDrawVertexArrayElements(int offset, int count, const void *buffer) -{ - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned short *bufferPtr = (unsigned short *)buffer; - if (offset > 0) bufferPtr += offset; - - glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr); -} - -// Draw vertex array instanced -void rlDrawVertexArrayInstanced(int offset, int count, int instances) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); -#endif -} - -// Draw vertex array elements instanced -void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Added pointer math separately from function to avoid UBSAN complaining - unsigned short *bufferPtr = (unsigned short *)buffer; - if (offset > 0) bufferPtr += offset; - - glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr, instances); -#endif -} - -#if defined(GRAPHICS_API_OPENGL_11) -// Enable vertex state pointer -void rlEnableStatePointer(int vertexAttribType, void *buffer) -{ - if (buffer != NULL) glEnableClientState(vertexAttribType); - switch (vertexAttribType) - { - case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; - case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; - case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; - case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; - //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors - default: break; - } -} - -// Disable vertex state pointer -void rlDisableStatePointer(int vertexAttribType) -{ - glDisableClientState(vertexAttribType); -} -#endif - -// Load vertex array object (VAO) -unsigned int rlLoadVertexArray(void) -{ - unsigned int vaoId = 0; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glGenVertexArrays(1, &vaoId); - } -#endif - return vaoId; -} - -// Set vertex attribute -void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // NOTE: Data type could be: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT - // Additional types (depends on OpenGL version or extensions): - // - GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, - // - GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV - - size_t offsetNative = offset; - glVertexAttribPointer(index, compSize, type, normalized, stride, (void *)offsetNative); -#endif -} - -// Set vertex attribute divisor -void rlSetVertexAttributeDivisor(unsigned int index, int divisor) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glVertexAttribDivisor(index, divisor); -#endif -} - -// Unload vertex array object (VAO) -void rlUnloadVertexArray(unsigned int vaoId) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.ExtSupported.vao) - { - glBindVertexArray(0); - glDeleteVertexArrays(1, &vaoId); - TRACELOG(RL_LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); - } -#endif -} - -// Unload vertex buffer (VBO) -void rlUnloadVertexBuffer(unsigned int vboId) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDeleteBuffers(1, &vboId); - //TRACELOG(RL_LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); -#endif -} - -// Shaders management -//----------------------------------------------------------------------------------------------- -// Load shader from code strings -// NOTE: If shader string is NULL, using default vertex/fragment shaders -unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) -{ - unsigned int id = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int vertexShaderId = 0; - unsigned int fragmentShaderId = 0; - - // Compile vertex shader (if provided) - // NOTE: If not vertex shader is provided, use default one - if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); - else vertexShaderId = RLGL.State.defaultVShaderId; - - // Compile fragment shader (if provided) - // NOTE: If not vertex shader is provided, use default one - if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); - else fragmentShaderId = RLGL.State.defaultFShaderId; - - // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id - if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; - else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) - { - // One of or both shader are new, we need to compile a new shader program - id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); - - // We can detach and delete vertex/fragment shaders (if not default ones) - // NOTE: We detach shader before deletion to make sure memory is freed - if (vertexShaderId != RLGL.State.defaultVShaderId) - { - // WARNING: Shader program linkage could fail and returned id is 0 - if (id > 0) glDetachShader(id, vertexShaderId); - glDeleteShader(vertexShaderId); - } - if (fragmentShaderId != RLGL.State.defaultFShaderId) - { - // WARNING: Shader program linkage could fail and returned id is 0 - if (id > 0) glDetachShader(id, fragmentShaderId); - glDeleteShader(fragmentShaderId); - } - - // In case shader program loading failed, we assign default shader - if (id == 0) - { - // In case shader loading fails, we return the default shader - TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code, using default shader"); - id = RLGL.State.defaultShaderId; - } - /* - else - { - // Get available shader uniforms - // NOTE: This information is useful for debug... - int uniformCount = -1; - glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); - - for (int i = 0; i < uniformCount; i++) - { - int namelen = -1; - int num = -1; - char name[256] = { 0 }; // Assume no variable names longer than 256 - GLenum type = GL_ZERO; - - // Get the name of the uniforms - glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); - - name[namelen] = 0; - TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); - } - } - */ - } -#endif - - return id; -} - -// Compile custom shader and return shader id -unsigned int rlCompileShader(const char *shaderCode, int type) -{ - unsigned int shader = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - shader = glCreateShader(type); - glShaderSource(shader, 1, &shaderCode, NULL); - - GLint success = 0; - glCompileShader(shader); - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - - if (success == GL_FALSE) - { - switch (type) - { - case GL_VERTEX_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; - case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; - //case GL_GEOMETRY_SHADER: - #if defined(GRAPHICS_API_OPENGL_43) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; - #elif defined(GRAPHICS_API_OPENGL_33) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; - #endif - default: break; - } - - int maxLength = 0; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); - - if (maxLength > 0) - { - int length = 0; - char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); - glGetShaderInfoLog(shader, maxLength, &length, log); - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); - RL_FREE(log); - } - - shader = 0; - } - else - { - switch (type) - { - case GL_VERTEX_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; - case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; - //case GL_GEOMETRY_SHADER: - #if defined(GRAPHICS_API_OPENGL_43) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; - #elif defined(GRAPHICS_API_OPENGL_33) - case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; - #endif - default: break; - } - } -#endif - - return shader; -} - -// Load custom shader strings and return program id -unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) -{ - unsigned int program = 0; - -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - GLint success = 0; - program = glCreateProgram(); - - glAttachShader(program, vShaderId); - glAttachShader(program, fShaderId); - - // NOTE: Default attribute shader locations must be Bound before linking - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); - -#ifdef RL_SUPPORT_MESH_GPU_SKINNING - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); - glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); -#endif - - // NOTE: If some attrib name is no found on the shader, it locations becomes -1 - - glLinkProgram(program); - - // NOTE: All uniform variables are intitialised to 0 when a program links - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); - - int maxLength = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - if (maxLength > 0) - { - int length = 0; - char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); - glGetProgramInfoLog(program, maxLength, &length, log); - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); - RL_FREE(log); - } - - glDeleteProgram(program); - - program = 0; - } - else - { - // Get the size of compiled shader program (not available on OpenGL ES 2.0) - // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero - //GLint binarySize = 0; - //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); - } -#endif - return program; -} - -// Unload shader program -void rlUnloadShaderProgram(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - glDeleteProgram(id); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); -#endif -} - -// Get shader location uniform -int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) -{ - int location = -1; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - location = glGetUniformLocation(shaderId, uniformName); - - //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); - //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); -#endif - return location; -} - -// Get shader location attribute -int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) -{ - int location = -1; -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - location = glGetAttribLocation(shaderId, attribName); - - //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); - //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); -#endif - return location; -} - -// Set shader value uniform -void rlSetUniform(int locIndex, const void *value, int uniformType, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - switch (uniformType) - { - case RL_SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; - case RL_SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; - case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; - case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; - case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; - #if !defined(GRAPHICS_API_OPENGL_ES2) - case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; - case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; - case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; - case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; - #endif - case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; - default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); - - // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() - } -#endif -} - -// Set shader value attribute -void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - switch (attribType) - { - case RL_SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; - case RL_SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; - case RL_SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; - case RL_SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; - default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); - } -#endif -} - -// Set shader value uniform matrix -void rlSetUniformMatrix(int locIndex, Matrix mat) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - float matfloat[16] = { - mat.m0, mat.m1, mat.m2, mat.m3, - mat.m4, mat.m5, mat.m6, mat.m7, - mat.m8, mat.m9, mat.m10, mat.m11, - mat.m12, mat.m13, mat.m14, mat.m15 - }; - glUniformMatrix4fv(locIndex, 1, false, matfloat); -#endif -} - -// Set shader value uniform matrix -void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) -{ -#if defined(GRAPHICS_API_OPENGL_33) - glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); -#elif defined(GRAPHICS_API_OPENGL_ES2) - // WARNING: WebGL does not support Matrix transpose ("true" parameter) - // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix - glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); -#endif -} - -// Set shader value uniform sampler -void rlSetUniformSampler(int locIndex, unsigned int textureId) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // Check if texture is already active - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) - { - if (RLGL.State.activeTextureId[i] == textureId) - { - glUniform1i(locIndex, 1 + i); - return; - } - } - - // Register a new active texture for the internal batch system - // NOTE: Default texture is always activated as GL_TEXTURE0 - for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) - { - if (RLGL.State.activeTextureId[i] == 0) - { - glUniform1i(locIndex, 1 + i); // Activate new texture unit - RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing - break; - } - } -#endif -} - -// Set shader currently active (id and locations) -void rlSetShader(unsigned int id, int *locs) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - if (RLGL.State.currentShaderId != id) - { - rlDrawRenderBatch(RLGL.currentBatch); - RLGL.State.currentShaderId = id; - RLGL.State.currentShaderLocs = locs; - } -#endif -} - -// Load compute shader program -unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) -{ - unsigned int program = 0; - -#if defined(GRAPHICS_API_OPENGL_43) - GLint success = 0; - program = glCreateProgram(); - glAttachShader(program, shaderId); - glLinkProgram(program); - - // NOTE: All uniform variables are intitialised to 0 when a program links - - glGetProgramiv(program, GL_LINK_STATUS, &success); - - if (success == GL_FALSE) - { - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link compute shader program", program); - - int maxLength = 0; - glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); - - if (maxLength > 0) - { - int length = 0; - char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); - glGetProgramInfoLog(program, maxLength, &length, log); - TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); - RL_FREE(log); - } - - glDeleteProgram(program); - - program = 0; - } - else - { - // Get the size of compiled shader program (not available on OpenGL ES 2.0) - // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero - //GLint binarySize = 0; - //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); - } -#else - TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif - - return program; -} - -// Dispatch compute shader (equivalent to *draw* for graphics pilepine) -void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glDispatchCompute(groupX, groupY, groupZ); -#endif -} - -// Load shader storage buffer object (SSBO) -unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint) -{ - unsigned int ssbo = 0; - -#if defined(GRAPHICS_API_OPENGL_43) - glGenBuffers(1, &ssbo); - glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); - glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); - if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 - glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); -#else - TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif - - return ssbo; -} - -// Unload shader storage buffer object (SSBO) -void rlUnloadShaderBuffer(unsigned int ssboId) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glDeleteBuffers(1, &ssboId); -#else - TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif - -} - -// Update SSBO buffer data -void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, dataSize, data); -#endif -} - -// Get SSBO buffer size -unsigned int rlGetShaderBufferSize(unsigned int id) -{ -#if defined(GRAPHICS_API_OPENGL_43) - GLint64 size = 0; - glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); - return (size > 0)? (unsigned int)size : 0; -#else - return 0; -#endif -} - -// Read SSBO buffer data (GPU->CPU) -void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); - glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, count, dest); -#endif -} - -// Bind SSBO buffer -void rlBindShaderBuffer(unsigned int id, unsigned int index) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, id); -#endif -} - -// Copy SSBO buffer data -void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count) -{ -#if defined(GRAPHICS_API_OPENGL_43) - glBindBuffer(GL_COPY_READ_BUFFER, srcId); - glBindBuffer(GL_COPY_WRITE_BUFFER, destId); - glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, srcOffset, destOffset, count); -#endif -} - -// Bind image texture -void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly) -{ -#if defined(GRAPHICS_API_OPENGL_43) - unsigned int glInternalFormat = 0, glFormat = 0, glType = 0; - - rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); - glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); -#else - TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); -#endif -} - -// Matrix state management -//----------------------------------------------------------------------------------------- -// Get internal modelview matrix -Matrix rlGetMatrixModelview(void) -{ - Matrix matrix = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_11) - float mat[16]; - glGetFloatv(GL_MODELVIEW_MATRIX, mat); - matrix.m0 = mat[0]; - matrix.m1 = mat[1]; - matrix.m2 = mat[2]; - matrix.m3 = mat[3]; - matrix.m4 = mat[4]; - matrix.m5 = mat[5]; - matrix.m6 = mat[6]; - matrix.m7 = mat[7]; - matrix.m8 = mat[8]; - matrix.m9 = mat[9]; - matrix.m10 = mat[10]; - matrix.m11 = mat[11]; - matrix.m12 = mat[12]; - matrix.m13 = mat[13]; - matrix.m14 = mat[14]; - matrix.m15 = mat[15]; -#else - matrix = RLGL.State.modelview; -#endif - return matrix; -} - -// Get internal projection matrix -Matrix rlGetMatrixProjection(void) -{ -#if defined(GRAPHICS_API_OPENGL_11) - float mat[16]; - glGetFloatv(GL_PROJECTION_MATRIX,mat); - Matrix m; - m.m0 = mat[0]; - m.m1 = mat[1]; - m.m2 = mat[2]; - m.m3 = mat[3]; - m.m4 = mat[4]; - m.m5 = mat[5]; - m.m6 = mat[6]; - m.m7 = mat[7]; - m.m8 = mat[8]; - m.m9 = mat[9]; - m.m10 = mat[10]; - m.m11 = mat[11]; - m.m12 = mat[12]; - m.m13 = mat[13]; - m.m14 = mat[14]; - m.m15 = mat[15]; - return m; -#else - return RLGL.State.projection; -#endif -} - -// Get internal accumulated transform matrix -Matrix rlGetMatrixTransform(void) -{ - Matrix mat = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - // TODO: Consider possible transform matrices in the RLGL.State.stack - // Is this the right order? or should we start with the first stored matrix instead of the last one? - //Matrix matStackTransform = rlMatrixIdentity(); - //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform); - mat = RLGL.State.transform; -#endif - return mat; -} - -// Get internal projection matrix for stereo render (selected eye) -Matrix rlGetMatrixProjectionStereo(int eye) -{ - Matrix mat = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - mat = RLGL.State.projectionStereo[eye]; -#endif - return mat; -} - -// Get internal view offset matrix for stereo render (selected eye) -Matrix rlGetMatrixViewOffsetStereo(int eye) -{ - Matrix mat = rlMatrixIdentity(); -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - mat = RLGL.State.viewOffsetStereo[eye]; -#endif - return mat; -} - -// Set a custom modelview matrix (replaces internal modelview matrix) -void rlSetMatrixModelview(Matrix view) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.modelview = view; -#endif -} - -// Set a custom projection matrix (replaces internal projection matrix) -void rlSetMatrixProjection(Matrix projection) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.projection = projection; -#endif -} - -// Set eyes projection matrices for stereo rendering -void rlSetMatrixProjectionStereo(Matrix right, Matrix left) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.projectionStereo[0] = right; - RLGL.State.projectionStereo[1] = left; -#endif -} - -// Set eyes view offsets matrices for stereo rendering -void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - RLGL.State.viewOffsetStereo[0] = right; - RLGL.State.viewOffsetStereo[1] = left; -#endif -} - -// Load and draw a quad in NDC -void rlLoadDrawQuad(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int quadVAO = 0; - unsigned int quadVBO = 0; - - float vertices[] = { - // Positions Texcoords - -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, - 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, - }; - - // Gen VAO to contain VBO - glGenVertexArrays(1, &quadVAO); - glBindVertexArray(quadVAO); - - // Gen and fill vertex buffer (VBO) - glGenBuffers(1, &quadVBO); - glBindBuffer(GL_ARRAY_BUFFER, quadVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); - - // Bind vertex attributes (position, texcoords) - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords - - // Draw quad - glBindVertexArray(quadVAO); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - glBindVertexArray(0); - - // Delete buffers (VBO and VAO) - glDeleteBuffers(1, &quadVBO); - glDeleteVertexArrays(1, &quadVAO); -#endif -} - -// Load and draw a cube in NDC -void rlLoadDrawCube(void) -{ -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) - unsigned int cubeVAO = 0; - unsigned int cubeVBO = 0; - - float vertices[] = { - // Positions Normals Texcoords - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, - -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, - 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, - -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, - -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f - }; - - // Gen VAO to contain VBO - glGenVertexArrays(1, &cubeVAO); - glBindVertexArray(cubeVAO); - - // Gen and fill vertex buffer (VBO) - glGenBuffers(1, &cubeVBO); - glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); - - // Bind vertex attributes (position, normals, texcoords) - glBindVertexArray(cubeVAO); - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals - glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); - glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindVertexArray(0); - - // Draw cube - glBindVertexArray(cubeVAO); - glDrawArrays(GL_TRIANGLES, 0, 36); - glBindVertexArray(0); - - // Delete VBO and VAO - glDeleteBuffers(1, &cubeVBO); - glDeleteVertexArrays(1, &cubeVAO); -#endif -} - -// Get name string for pixel format -const char *rlGetPixelFormatName(unsigned int format) -{ - switch (format) - { - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: return "GRAYSCALE"; break; // 8 bit per pixel (no alpha) - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: return "GRAY_ALPHA"; break; // 8*2 bpp (2 channels) - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: return "R5G6B5"; break; // 16 bpp - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: return "R8G8B8"; break; // 24 bpp - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: return "R5G5B5A1"; break; // 16 bpp (1 bit alpha) - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: return "R4G4B4A4"; break; // 16 bpp (4 bit alpha) - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: return "R8G8B8A8"; break; // 32 bpp - case RL_PIXELFORMAT_UNCOMPRESSED_R32: return "R32"; break; // 32 bpp (1 channel - float) - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: return "R32G32B32"; break; // 32*3 bpp (3 channels - float) - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: return "R32G32B32A32"; break; // 32*4 bpp (4 channels - float) - case RL_PIXELFORMAT_UNCOMPRESSED_R16: return "R16"; break; // 16 bpp (1 channel - half float) - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: return "R16G16B16"; break; // 16*3 bpp (3 channels - half float) - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: return "R16G16B16A16"; break; // 16*4 bpp (4 channels - half float) - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: return "DXT1_RGB"; break; // 4 bpp (no alpha) - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: return "DXT1_RGBA"; break; // 4 bpp (1 bit alpha) - case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: return "DXT3_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: return "DXT5_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: return "ETC1_RGB"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: return "ETC2_RGB"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: return "ETC2_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: return "PVRT_RGB"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: return "PVRT_RGBA"; break; // 4 bpp - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: return "ASTC_4x4_RGBA"; break; // 8 bpp - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: return "ASTC_8x8_RGBA"; break; // 2 bpp - default: return "UNKNOWN"; break; - } -} - -//---------------------------------------------------------------------------------- -// Module specific Functions Definition -//---------------------------------------------------------------------------------- -#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) -// Load default shader (just vertex positioning and texture coloring) -// NOTE: This shader program is used for internal buffers -// NOTE: Loaded: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs -static void rlLoadShaderDefault(void) -{ - RLGL.State.defaultShaderLocs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); - - // NOTE: All locations must be reseted to -1 (no location) - for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShaderLocs[i] = -1; - - // Vertex shader directly defined, no external file required - const char *defaultVShaderCode = -#if defined(GRAPHICS_API_OPENGL_21) - "#version 120 \n" - "attribute vec3 vertexPosition; \n" - "attribute vec2 vertexTexCoord; \n" - "attribute vec4 vertexColor; \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" -#elif defined(GRAPHICS_API_OPENGL_33) - "#version 330 \n" - "in vec3 vertexPosition; \n" - "in vec2 vertexTexCoord; \n" - "in vec4 vertexColor; \n" - "out vec2 fragTexCoord; \n" - "out vec4 fragColor; \n" -#endif - -#if defined(GRAPHICS_API_OPENGL_ES3) - "#version 300 es \n" - "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) (on some browsers) - "in vec3 vertexPosition; \n" - "in vec2 vertexTexCoord; \n" - "in vec4 vertexColor; \n" - "out vec2 fragTexCoord; \n" - "out vec4 fragColor; \n" -#elif defined(GRAPHICS_API_OPENGL_ES2) - "#version 100 \n" - "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) (on some browsers) - "attribute vec3 vertexPosition; \n" - "attribute vec2 vertexTexCoord; \n" - "attribute vec4 vertexColor; \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" -#endif - - "uniform mat4 mvp; \n" - "void main() \n" - "{ \n" - " fragTexCoord = vertexTexCoord; \n" - " fragColor = vertexColor; \n" - " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" - "} \n"; - - // Fragment shader directly defined, no external file required - const char *defaultFShaderCode = -#if defined(GRAPHICS_API_OPENGL_21) - "#version 120 \n" - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" - " gl_FragColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#elif defined(GRAPHICS_API_OPENGL_33) - "#version 330 \n" - "in vec2 fragTexCoord; \n" - "in vec4 fragColor; \n" - "out vec4 finalColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture(texture0, fragTexCoord); \n" - " finalColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#endif - -#if defined(GRAPHICS_API_OPENGL_ES3) - "#version 300 es \n" - "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) - "in vec2 fragTexCoord; \n" - "in vec4 fragColor; \n" - "out vec4 finalColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture(texture0, fragTexCoord); \n" - " finalColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#elif defined(GRAPHICS_API_OPENGL_ES2) - "#version 100 \n" - "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) - "varying vec2 fragTexCoord; \n" - "varying vec4 fragColor; \n" - "uniform sampler2D texture0; \n" - "uniform vec4 colDiffuse; \n" - "void main() \n" - "{ \n" - " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" - " gl_FragColor = texelColor*colDiffuse*fragColor; \n" - "} \n"; -#endif - - // NOTE: Compiled vertex/fragment shaders are not deleted, - // they are kept for re-use as default shaders in case some shader loading fails - RLGL.State.defaultVShaderId = rlCompileShader(defaultVShaderCode, GL_VERTEX_SHADER); // Compile default vertex shader - RLGL.State.defaultFShaderId = rlCompileShader(defaultFShaderCode, GL_FRAGMENT_SHADER); // Compile default fragment shader - - RLGL.State.defaultShaderId = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); - - if (RLGL.State.defaultShaderId > 0) - { - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShaderId); - - // Set default shader locations: attributes locations - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); - - // Set default shader locations: uniform locations - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); - RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); - } - else TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShaderId); -} - -// Unload default shader -// NOTE: Unloads: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs -static void rlUnloadShaderDefault(void) -{ - glUseProgram(0); - - glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultVShaderId); - glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultFShaderId); - glDeleteShader(RLGL.State.defaultVShaderId); - glDeleteShader(RLGL.State.defaultFShaderId); - - glDeleteProgram(RLGL.State.defaultShaderId); - - RL_FREE(RLGL.State.defaultShaderLocs); - - TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShaderId); -} - -#if defined(RLGL_SHOW_GL_DETAILS_INFO) -// Get compressed format official GL identifier name -static const char *rlGetCompressedFormatName(int format) -{ - switch (format) - { - // GL_EXT_texture_compression_s3tc - case 0x83F0: return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"; break; - case 0x83F1: return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"; break; - case 0x83F2: return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"; break; - case 0x83F3: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"; break; - // GL_3DFX_texture_compression_FXT1 - case 0x86B0: return "GL_COMPRESSED_RGB_FXT1_3DFX"; break; - case 0x86B1: return "GL_COMPRESSED_RGBA_FXT1_3DFX"; break; - // GL_IMG_texture_compression_pvrtc - case 0x8C00: return "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"; break; - case 0x8C01: return "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"; break; - case 0x8C02: return "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"; break; - case 0x8C03: return "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"; break; - // GL_OES_compressed_ETC1_RGB8_texture - case 0x8D64: return "GL_ETC1_RGB8_OES"; break; - // GL_ARB_texture_compression_rgtc - case 0x8DBB: return "GL_COMPRESSED_RED_RGTC1"; break; - case 0x8DBC: return "GL_COMPRESSED_SIGNED_RED_RGTC1"; break; - case 0x8DBD: return "GL_COMPRESSED_RG_RGTC2"; break; - case 0x8DBE: return "GL_COMPRESSED_SIGNED_RG_RGTC2"; break; - // GL_ARB_texture_compression_bptc - case 0x8E8C: return "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"; break; - case 0x8E8D: return "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"; break; - case 0x8E8E: return "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"; break; - case 0x8E8F: return "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"; break; - // GL_ARB_ES3_compatibility - case 0x9274: return "GL_COMPRESSED_RGB8_ETC2"; break; - case 0x9275: return "GL_COMPRESSED_SRGB8_ETC2"; break; - case 0x9276: return "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; - case 0x9277: return "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; - case 0x9278: return "GL_COMPRESSED_RGBA8_ETC2_EAC"; break; - case 0x9279: return "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"; break; - case 0x9270: return "GL_COMPRESSED_R11_EAC"; break; - case 0x9271: return "GL_COMPRESSED_SIGNED_R11_EAC"; break; - case 0x9272: return "GL_COMPRESSED_RG11_EAC"; break; - case 0x9273: return "GL_COMPRESSED_SIGNED_RG11_EAC"; break; - // GL_KHR_texture_compression_astc_hdr - case 0x93B0: return "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"; break; - case 0x93B1: return "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"; break; - case 0x93B2: return "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"; break; - case 0x93B3: return "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"; break; - case 0x93B4: return "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"; break; - case 0x93B5: return "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"; break; - case 0x93B6: return "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"; break; - case 0x93B7: return "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"; break; - case 0x93B8: return "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"; break; - case 0x93B9: return "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"; break; - case 0x93BA: return "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"; break; - case 0x93BB: return "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"; break; - case 0x93BC: return "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"; break; - case 0x93BD: return "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"; break; - case 0x93D0: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"; break; - case 0x93D1: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"; break; - case 0x93D2: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"; break; - case 0x93D3: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"; break; - case 0x93D4: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"; break; - case 0x93D5: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"; break; - case 0x93D6: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"; break; - case 0x93D7: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"; break; - case 0x93D8: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"; break; - case 0x93D9: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"; break; - case 0x93DA: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"; break; - case 0x93DB: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"; break; - case 0x93DC: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"; break; - case 0x93DD: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"; break; - default: return "GL_COMPRESSED_UNKNOWN"; break; - } -} -#endif // RLGL_SHOW_GL_DETAILS_INFO - -#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 - -// Get pixel data size in bytes (image or texture) -// NOTE: Size depends on pixel format -static int rlGetPixelDataSize(int width, int height, int format) -{ - int dataSize = 0; // Size in bytes - int bpp = 0; // Bits per pixel - - switch (format) - { - case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; - case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: - case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: - case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: - case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 16; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 16*3; break; - case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 16*4; break; - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: - case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: - case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: - case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; - case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: - case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: - case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; - case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; - default: break; - } - - double bytesPerPixel = (double)bpp/8.0; - dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes - - // Most compressed formats works on 4x4 blocks, - // if texture is smaller, minimum dataSize is 8 or 16 - if ((width < 4) && (height < 4)) - { - if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; - else if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; - } - - return dataSize; -} - -// Auxiliar math functions - -// Get float array of matrix data -static rl_float16 rlMatrixToFloatV(Matrix mat) -{ - rl_float16 result = { 0 }; - - result.v[0] = mat.m0; - result.v[1] = mat.m1; - result.v[2] = mat.m2; - result.v[3] = mat.m3; - result.v[4] = mat.m4; - result.v[5] = mat.m5; - result.v[6] = mat.m6; - result.v[7] = mat.m7; - result.v[8] = mat.m8; - result.v[9] = mat.m9; - result.v[10] = mat.m10; - result.v[11] = mat.m11; - result.v[12] = mat.m12; - result.v[13] = mat.m13; - result.v[14] = mat.m14; - result.v[15] = mat.m15; - - return result; -} - -// Get identity matrix -static Matrix rlMatrixIdentity(void) -{ - Matrix result = { - 1.0f, 0.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 0.0f, 1.0f - }; - - return result; -} - -// Get two matrix multiplication -// NOTE: When multiplying matrices... the order matters! -static Matrix rlMatrixMultiply(Matrix left, Matrix right) -{ - Matrix result = { 0 }; - - result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; - result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; - result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; - result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; - result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; - result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; - result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; - result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; - result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; - result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; - result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; - result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; - result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; - result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; - result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; - result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; - - return result; -} - -// Transposes provided matrix -static Matrix rlMatrixTranspose(Matrix mat) -{ - Matrix result = { 0 }; - - result.m0 = mat.m0; - result.m1 = mat.m4; - result.m2 = mat.m8; - result.m3 = mat.m12; - result.m4 = mat.m1; - result.m5 = mat.m5; - result.m6 = mat.m9; - result.m7 = mat.m13; - result.m8 = mat.m2; - result.m9 = mat.m6; - result.m10 = mat.m10; - result.m11 = mat.m14; - result.m12 = mat.m3; - result.m13 = mat.m7; - result.m14 = mat.m11; - result.m15 = mat.m15; - - return result; -} - -// Invert provided matrix -static Matrix rlMatrixInvert(Matrix mat) -{ - Matrix result = { 0 }; - - // Cache the matrix values (speed optimization) - float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; - float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; - float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; - float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; - - float b00 = a00*a11 - a01*a10; - float b01 = a00*a12 - a02*a10; - float b02 = a00*a13 - a03*a10; - float b03 = a01*a12 - a02*a11; - float b04 = a01*a13 - a03*a11; - float b05 = a02*a13 - a03*a12; - float b06 = a20*a31 - a21*a30; - float b07 = a20*a32 - a22*a30; - float b08 = a20*a33 - a23*a30; - float b09 = a21*a32 - a22*a31; - float b10 = a21*a33 - a23*a31; - float b11 = a22*a33 - a23*a32; - - // Calculate the invert determinant (inlined to avoid double-caching) - float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); - - result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; - result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; - result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; - result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; - result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; - result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; - result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; - result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; - result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; - result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; - result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; - result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; - result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; - result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; - result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; - result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; - - return result; -} - -#endif // RLGL_IMPLEMENTATION diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a deleted file mode 100644 index fa538e52143..00000000000 --- a/third_party/raylib/larch64/libraylib.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f760af8b4693cf60e3760341e5275890d78d933da2354c4bad0572ec575b970a -size 2001860 diff --git a/third_party/raylib/x86_64/libraylib.a b/third_party/raylib/x86_64/libraylib.a deleted file mode 100644 index ea124c1bcfe..00000000000 --- a/third_party/raylib/x86_64/libraylib.a +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3c928e849b51b04d8e3603cd649184299efed0e9e0fb02201612b967b31efd73 -size 2771092 diff --git a/uv.lock b/uv.lock index f5c128fcb76..e12842ac5b5 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "casadi" @@ -381,7 +381,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "execnet" @@ -395,7 +395,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "fonttools" @@ -442,7 +442,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "ghp-import" @@ -459,7 +459,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "google-crc32c" @@ -577,7 +577,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "libusb1" @@ -593,7 +593,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "markdown" @@ -745,7 +745,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "numpy" @@ -912,7 +912,7 @@ requires-dist = [ { name = "python3-dev", git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", specifier = ">5.5.0.3" }, + { name = "raylib", git = "https://github.com/commaai/dependencies.git?subdirectory=raylib&rev=releases" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -935,7 +935,7 @@ provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "openssl3" version = "3.4.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "packaging" @@ -1306,7 +1306,7 @@ wheels = [ [[package]] name = "python3-dev" version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "pyyaml" @@ -1374,20 +1374,10 @@ wheels = [ [[package]] name = "raylib" version = "5.5.0.4" -source = { registry = "https://pypi.org/simple" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=raylib&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } dependencies = [ { name = "cffi" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, - { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, - { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, - { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, -] [[package]] name = "requests" @@ -1674,7 +1664,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } [[package]] name = "zstandard" @@ -1704,4 +1694,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } From c3d5c5f016114deac3eb723b53459bd3fc59c49e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 14:12:27 -0800 Subject: [PATCH 437/685] fix nigthly build (#37516) --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index db0e12234bc..a90f064b82c 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,7 +22,7 @@ jobs: running-workflow-name: 'build master-ci' repo-token: ${{ secrets.GITHUB_TOKEN }} check-regexp: ^((?!.*(build prebuilt|create badges).*).)*$ - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 with: submodules: true fetch-depth: 0 From 6b52ee7ef267f5e13dd19d72dac1841f49729ed5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 15:40:10 -0800 Subject: [PATCH 438/685] tools cleanup (#37520) --- .../measure_steering_accuracy.py | 0 tools/jotpluggler/README.md | 67 --- tools/jotpluggler/assets/pause.png | 3 - tools/jotpluggler/assets/play.png | 3 - tools/jotpluggler/assets/plus.png | 3 - tools/jotpluggler/assets/split_h.png | 3 - tools/jotpluggler/assets/split_v.png | 3 - tools/jotpluggler/assets/x.png | 3 - tools/jotpluggler/data.py | 360 ------------- tools/jotpluggler/datatree.py | 315 ------------ tools/jotpluggler/layout.py | 477 ------------------ .../layouts/torque-controller.yaml | 128 ----- tools/jotpluggler/pluggle.py | 368 -------------- tools/jotpluggler/views.py | 294 ----------- 14 files changed, 2027 deletions(-) rename tools/{tuning => car_porting}/measure_steering_accuracy.py (100%) delete mode 100644 tools/jotpluggler/README.md delete mode 100644 tools/jotpluggler/assets/pause.png delete mode 100644 tools/jotpluggler/assets/play.png delete mode 100644 tools/jotpluggler/assets/plus.png delete mode 100644 tools/jotpluggler/assets/split_h.png delete mode 100644 tools/jotpluggler/assets/split_v.png delete mode 100644 tools/jotpluggler/assets/x.png delete mode 100644 tools/jotpluggler/data.py delete mode 100644 tools/jotpluggler/datatree.py delete mode 100644 tools/jotpluggler/layout.py delete mode 100644 tools/jotpluggler/layouts/torque-controller.yaml delete mode 100755 tools/jotpluggler/pluggle.py delete mode 100644 tools/jotpluggler/views.py diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/car_porting/measure_steering_accuracy.py similarity index 100% rename from tools/tuning/measure_steering_accuracy.py rename to tools/car_porting/measure_steering_accuracy.py diff --git a/tools/jotpluggler/README.md b/tools/jotpluggler/README.md deleted file mode 100644 index d5e4b8ab0f5..00000000000 --- a/tools/jotpluggler/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# JotPluggler - -JotPluggler is a tool to quickly visualize openpilot logs. - -## Usage - -``` -$ ./jotpluggler/pluggle.py -h -usage: pluggle.py [-h] [--demo] [--layout LAYOUT] [route] - -A tool for visualizing openpilot logs. - -positional arguments: - route Optional route name to load on startup. - -options: - -h, --help show this help message and exit - --demo Use the demo route instead of providing one - --layout LAYOUT Path to YAML layout file to load on startup -``` - -Example using route name: - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496"` - -Examples using segment: - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/1"` - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/1/q" # use qlogs` - -Example using segment range: - -`./pluggle.py "5beb9b58bd12b691/0000010a--a51155e496/0:1"` - -## Demo - -For a quick demo, run this command: - -`./pluggle.py --demo --layout=layouts/torque-controller.yaml` - - -## Basic Usage/Features: -- The text box to load a route is a the top left of the page, accepts standard openpilot format routes (e.g. `5beb9b58bd12b691/0000010a--a51155e496/0:1`, `https://connect.comma.ai/5beb9b58bd12b691/0000010a--a51155e496/`) -- The Play/Pause button is at the bottom of the screen, you can drag the bottom slider to seek. The timeline in timeseries plots are synced with the slider. -- The Timeseries List sidebar has several dropdowns, the fields each show the field name and value, synced with the timeline (will show N/A until the time of the first message in that field is reached). -- There is a search bar for the timeseries list, you can search for structs or fields, or both by separating with a "/" -- You can drag and drop any numeric/boolean field from the timeseries list into a timeseries panel. -- You can create more panels with the split buttons (buttons with two rectangles, either horizontal or vertical). You can resize the panels by dragging the grip in between any panel. -- You can load and save layouts with the corresponding buttons. Layouts will save all tabs, panels, titles, timeseries, etc. - -## Layouts - -If you create a layout that's useful for others, consider upstreaming it. - -## Plot Interaction Controls - -- **Left click and drag within the plot area** to pan X - - Left click and drag on an axis to pan an individual axis (disabled for Y-axis) -- **Scroll in the plot area** to zoom in X axes, Y-axis is autofit - - Scroll on an axis to zoom an individual axis -- **Right click and drag** to select data and zoom into the selected data - - Left click while box selecting to cancel the selection -- **Double left click** to fit all visible data - - Double left click on an axis to fit the individual axis (disabled for Y-axis, always autofit) -- **Double right click** to open the plot context menu -- **Click legend label icons** to show/hide plot items diff --git a/tools/jotpluggler/assets/pause.png b/tools/jotpluggler/assets/pause.png deleted file mode 100644 index 80400998318..00000000000 --- a/tools/jotpluggler/assets/pause.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3ea96d8193eb9067a5efdc5d88a3099730ecafa40efcd09d7402bb3efd723603 -size 2305 diff --git a/tools/jotpluggler/assets/play.png b/tools/jotpluggler/assets/play.png deleted file mode 100644 index b1556cf0abf..00000000000 --- a/tools/jotpluggler/assets/play.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:53097ac5403b725ff1841dfa186ea770b4bb3714205824bde36ec3c2a0fb5dba -size 2758 diff --git a/tools/jotpluggler/assets/plus.png b/tools/jotpluggler/assets/plus.png deleted file mode 100644 index 6f8388b24df..00000000000 --- a/tools/jotpluggler/assets/plus.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:248b71eafd1b42b0861da92114da3d625221cd88121fff01e0514bf3d79ff3b1 -size 1364 diff --git a/tools/jotpluggler/assets/split_h.png b/tools/jotpluggler/assets/split_h.png deleted file mode 100644 index 4fd88806e16..00000000000 --- a/tools/jotpluggler/assets/split_h.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:54dd035ff898d881509fa686c402a61af8ef5fb408b92414722da01f773b0d33 -size 2900 diff --git a/tools/jotpluggler/assets/split_v.png b/tools/jotpluggler/assets/split_v.png deleted file mode 100644 index 752e62a4ae9..00000000000 --- a/tools/jotpluggler/assets/split_v.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:adbd4e5df1f58694dca9dde46d1d95b4e7471684e42e6bca9f41ea5d346e67c5 -size 3669 diff --git a/tools/jotpluggler/assets/x.png b/tools/jotpluggler/assets/x.png deleted file mode 100644 index 3b2eabd4478..00000000000 --- a/tools/jotpluggler/assets/x.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a6d9c90cb0dd906e0b15e1f7f3fd9f0dfad3c3b0b34eeed7a7882768dc5f3961 -size 2053 diff --git a/tools/jotpluggler/data.py b/tools/jotpluggler/data.py deleted file mode 100644 index 756b87bd20c..00000000000 --- a/tools/jotpluggler/data.py +++ /dev/null @@ -1,360 +0,0 @@ -import numpy as np -import threading -import multiprocessing -import bisect -from collections import defaultdict -from tqdm import tqdm -from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.test.process_replay.migration import migrate_all -from openpilot.tools.lib.logreader import _LogFileReader, LogReader - - -def flatten_dict(d: dict, sep: str = "/", prefix: str | None = None) -> dict: - result = {} - stack: list[tuple] = [(d, prefix)] - - while stack: - obj, current_prefix = stack.pop() - - if isinstance(obj, dict): - for key, val in obj.items(): - new_prefix = key if current_prefix is None else f"{current_prefix}{sep}{key}" - if isinstance(val, (dict, list)): - stack.append((val, new_prefix)) - else: - result[new_prefix] = val - elif isinstance(obj, list): - for i, item in enumerate(obj): - new_prefix = f"{current_prefix}{sep}{i}" - if isinstance(item, (dict, list)): - stack.append((item, new_prefix)) - else: - result[new_prefix] = item - else: - if current_prefix is not None: - result[current_prefix] = obj - return result - - -def extract_field_types(schema, prefix, field_types_dict): - stack = [(schema, prefix)] - - while stack: - current_schema, current_prefix = stack.pop() - - for field in current_schema.fields_list: - field_name = field.proto.name - field_path = f"{current_prefix}/{field_name}" - field_proto = field.proto - field_which = field_proto.which() - - field_type = field_proto.slot.type.which() if field_which == 'slot' else field_which - field_types_dict[field_path] = field_type - - if field_which == 'slot': - slot_type = field_proto.slot.type - type_which = slot_type.which() - - if type_which == 'list': - element_type = slot_type.list.elementType.which() - list_path = f"{field_path}/*" - field_types_dict[list_path] = element_type - - if element_type == 'struct': - stack.append((field.schema.elementType, list_path)) - - elif type_which == 'struct': - stack.append((field.schema, field_path)) - - elif field_which == 'group': - stack.append((field.schema, field_path)) - - -def _convert_to_optimal_dtype(values_list, capnp_type): - dtype_mapping = { - 'bool': np.bool_, 'int8': np.int8, 'int16': np.int16, 'int32': np.int32, 'int64': np.int64, - 'uint8': np.uint8, 'uint16': np.uint16, 'uint32': np.uint32, 'uint64': np.uint64, - 'float32': np.float32, 'float64': np.float64, 'text': object, 'data': object, - 'enum': object, 'anyPointer': object, - } - - target_dtype = dtype_mapping.get(capnp_type, object) - return np.array(values_list, dtype=target_dtype) - - -def _match_field_type(field_path, field_types): - if field_path in field_types: - return field_types[field_path] - - path_parts = field_path.split('/') - template_parts = [p if not p.isdigit() else '*' for p in path_parts] - template_path = '/'.join(template_parts) - return field_types.get(template_path) - - -def _get_field_times_values(segment, field_name): - if field_name not in segment: - return None, None - - field_data = segment[field_name] - segment_times = segment['t'] - - if field_data['sparse']: - if len(field_data['t_index']) == 0: - return None, None - return segment_times[field_data['t_index']], field_data['values'] - else: - return segment_times, field_data['values'] - - -def msgs_to_time_series(msgs): - """Extract scalar fields and return (time_series_data, start_time, end_time).""" - collected_data = defaultdict(lambda: {'timestamps': [], 'columns': defaultdict(list), 'sparse_fields': set()}) - field_types = {} - extracted_schemas = set() - min_time = max_time = None - - for msg in msgs: - typ = msg.which() - timestamp = msg.logMonoTime * 1e-9 - if typ != 'initData': - if min_time is None: - min_time = timestamp - max_time = timestamp - - sub_msg = getattr(msg, typ) - if not hasattr(sub_msg, 'to_dict'): - continue - - if hasattr(sub_msg, 'schema') and typ not in extracted_schemas: - extract_field_types(sub_msg.schema, typ, field_types) - extracted_schemas.add(typ) - - try: - msg_dict = sub_msg.to_dict(verbose=True) - except Exception as e: - cloudlog.warning(f"Failed to convert sub_msg.to_dict() for message of type: {typ}: {e}") - continue - - flat_dict = flatten_dict(msg_dict) - flat_dict['_valid'] = msg.valid - field_types[f"{typ}/_valid"] = 'bool' - - type_data = collected_data[typ] - columns, sparse_fields = type_data['columns'], type_data['sparse_fields'] - known_fields = set(columns.keys()) - missing_fields = known_fields - flat_dict.keys() - - for field, value in flat_dict.items(): - if field not in known_fields and type_data['timestamps']: - sparse_fields.add(field) - columns[field].append(value) - if value is None: - sparse_fields.add(field) - - for field in missing_fields: - columns[field].append(None) - sparse_fields.add(field) - - type_data['timestamps'].append(timestamp) - - final_result = {} - for typ, data in collected_data.items(): - if not data['timestamps']: - continue - - typ_result = {'t': np.array(data['timestamps'], dtype=np.float64)} - sparse_fields = data['sparse_fields'] - - for field_name, values in data['columns'].items(): - if len(values) < len(data['timestamps']): - values = [None] * (len(data['timestamps']) - len(values)) + values - sparse_fields.add(field_name) - - capnp_type = _match_field_type(f"{typ}/{field_name}", field_types) - - if field_name in sparse_fields: # extract non-None values and their indices - non_none_indices = [] - non_none_values = [] - for i, value in enumerate(values): - if value is not None: - non_none_indices.append(i) - non_none_values.append(value) - - if non_none_values: # check if indices > uint16 max, currently would require a 1000+ Hz signal since indices are within segments - assert max(non_none_indices) <= 65535, f"Sparse field {typ}/{field_name} has timestamp indices exceeding uint16 max. Max: {max(non_none_indices)}" - - typ_result[field_name] = { - 'values': _convert_to_optimal_dtype(non_none_values, capnp_type), - 'sparse': True, - 't_index': np.array(non_none_indices, dtype=np.uint16), - } - else: # dense representation - typ_result[field_name] = {'values': _convert_to_optimal_dtype(values, capnp_type), 'sparse': False} - - final_result[typ] = typ_result - - return final_result, min_time or 0.0, max_time or 0.0 - - -def _process_segment(segment_identifier: str): - try: - lr = _LogFileReader(segment_identifier, sort_by_time=True) - migrated_msgs = migrate_all(lr) - return msgs_to_time_series(migrated_msgs) - except Exception as e: - cloudlog.warning(f"Warning: Failed to process segment {segment_identifier}: {e}") - return {}, 0.0, 0.0 - - -class DataManager: - def __init__(self): - self._segments = [] - self._segment_starts = [] - self._start_time = 0.0 - self._duration = 0.0 - self._paths = set() - self._observers = [] - self._loading = False - self._lock = threading.RLock() - - def load_route(self, route: str) -> None: - if self._loading: - return - self._reset() - threading.Thread(target=self._load_async, args=(route,), daemon=True).start() - - def get_timeseries(self, path: str): - with self._lock: - msg_type, field = path.split('/', 1) - times, values = [], [] - - for segment in self._segments: - if msg_type in segment: - field_times, field_values = _get_field_times_values(segment[msg_type], field) - if field_times is not None: - times.append(field_times) - values.append(field_values) - - if not times: - return np.array([]), np.array([]) - - combined_times = np.concatenate(times) - self._start_time - - if len(values) > 1: - first_dtype = values[0].dtype - if all(arr.dtype == first_dtype for arr in values): # check if all arrays have compatible dtypes - combined_values = np.concatenate(values) - else: - combined_values = np.concatenate([arr.astype(object) for arr in values]) - else: - combined_values = values[0] if values else np.array([]) - - return combined_times, combined_values - - def get_value_at(self, path: str, time: float): - with self._lock: - MAX_LOOKBACK = 5.0 # seconds - absolute_time = self._start_time + time - message_type, field = path.split('/', 1) - current_index = bisect.bisect_right(self._segment_starts, absolute_time) - 1 - for index in (current_index, current_index - 1): - if not 0 <= index < len(self._segments): - continue - segment = self._segments[index].get(message_type) - if not segment: - continue - times, values = _get_field_times_values(segment, field) - if times is None or len(times) == 0 or (index != current_index and absolute_time - times[-1] > MAX_LOOKBACK): - continue - position = np.searchsorted(times, absolute_time, 'right') - 1 - if position >= 0 and absolute_time - times[position] <= MAX_LOOKBACK: - return values[position] - return None - - def get_all_paths(self): - with self._lock: - return sorted(self._paths) - - def get_duration(self): - with self._lock: - return self._duration - - def is_plottable(self, path: str): - _, values = self.get_timeseries(path) - if len(values) == 0: - return False - return np.issubdtype(values.dtype, np.number) or np.issubdtype(values.dtype, np.bool_) - - def add_observer(self, callback): - with self._lock: - self._observers.append(callback) - - def remove_observer(self, callback): - with self._lock: - if callback in self._observers: - self._observers.remove(callback) - - def _reset(self): - with self._lock: - self._loading = True - self._segments.clear() - self._segment_starts.clear() - self._paths.clear() - self._start_time = self._duration = 0.0 - observers = self._observers.copy() - - for callback in observers: - callback({'reset': True}) - - def _load_async(self, route: str): - try: - lr = LogReader(route, sort_by_time=True) - if not lr.logreader_identifiers: - cloudlog.warning(f"Warning: No log segments found for route: {route}") - return - - total_segments = len(lr.logreader_identifiers) - with self._lock: - observers = self._observers.copy() - for callback in observers: - callback({'metadata_loaded': True, 'total_segments': total_segments}) - - num_processes = max(1, multiprocessing.cpu_count() // 2) - with multiprocessing.Pool(processes=num_processes) as pool, tqdm(total=len(lr.logreader_identifiers), desc="Processing Segments") as pbar: - for segment_result, start_time, end_time in pool.imap(_process_segment, lr.logreader_identifiers): - pbar.update(1) - if segment_result: - self._add_segment(segment_result, start_time, end_time) - except Exception: - cloudlog.exception(f"Error loading route {route}:") - finally: - self._finalize_loading() - - def _add_segment(self, segment_data: dict, start_time: float, end_time: float): - with self._lock: - self._segments.append(segment_data) - self._segment_starts.append(start_time) - - if len(self._segments) == 1: - self._start_time = start_time - self._duration = end_time - self._start_time - - for msg_type, data in segment_data.items(): - for field_name in data.keys(): - if field_name != 't': - self._paths.add(f"{msg_type}/{field_name}") - - observers = self._observers.copy() - - for callback in observers: - callback({'segment_added': True, 'duration': self._duration, 'segment_count': len(self._segments)}) - - def _finalize_loading(self): - with self._lock: - self._loading = False - observers = self._observers.copy() - duration = self._duration - - for callback in observers: - callback({'loading_complete': True, 'duration': duration}) diff --git a/tools/jotpluggler/datatree.py b/tools/jotpluggler/datatree.py deleted file mode 100644 index 4f3219dc1b4..00000000000 --- a/tools/jotpluggler/datatree.py +++ /dev/null @@ -1,315 +0,0 @@ -import os -import re -import threading -import numpy as np -import dearpygui.dearpygui as dpg - - -class DataTreeNode: - def __init__(self, name: str, full_path: str = "", parent=None): - self.name = name - self.full_path = full_path - self.parent = parent - self.children: dict[str, DataTreeNode] = {} - self.filtered_children: dict[str, DataTreeNode] = {} - self.created_children: dict[str, DataTreeNode] = {} - self.is_leaf = False - self.is_plottable: bool | None = None - self.ui_created = False - self.children_ui_created = False - self.ui_tag: str | None = None - - -class DataTree: - MAX_NODES_PER_FRAME = 50 - - def __init__(self, data_manager, playback_manager): - self.data_manager = data_manager - self.playback_manager = playback_manager - self.current_search = "" - self.data_tree = DataTreeNode(name="root") - self._build_queue: dict[str, tuple[DataTreeNode, DataTreeNode, str | int]] = {} # full_path -> (node, parent, before_tag) - self._current_created_paths: set[str] = set() - self._current_filtered_paths: set[str] = set() - self._path_to_node: dict[str, DataTreeNode] = {} # full_path -> node - self._expanded_tags: set[str] = set() - self._item_handlers: dict[str, str] = {} # ui_tag -> handler_tag - self._char_width = None - self._queued_search = None - self._new_data = False - self._ui_lock = threading.RLock() - self._handlers_to_delete = [] - self.data_manager.add_observer(self._on_data_loaded) - - def create_ui(self, parent_tag: str): - with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1): - dpg.add_text("Timeseries List") - dpg.add_separator() - dpg.add_input_text(tag="search_input", width=-1, hint="Search fields...", callback=self.search_data) - dpg.add_separator() - with dpg.child_window(border=False, width=-1, height=-1): - with dpg.group(tag="data_tree_container"): - pass - - def _on_data_loaded(self, data: dict): - with self._ui_lock: - if data.get('segment_added') or data.get('reset'): - self._new_data = True - - def update_frame(self, font): - if self._handlers_to_delete: # we need to do everything in main thread, frame callbacks are flaky - dpg.render_dearpygui_frame() # wait a frame to ensure queued callbacks are done - with self._ui_lock: - for handler in self._handlers_to_delete: - dpg.delete_item(handler) - self._handlers_to_delete.clear() - - with self._ui_lock: - if self._char_width is None: - if size := dpg.get_text_size(" ", font=font): - self._char_width = size[0] / 2 # we scale font 2x and downscale to fix hidpi bug - - if self._new_data: - self._process_path_change() - self._new_data = False - return - - if self._queued_search is not None: - self.current_search = self._queued_search - self._process_path_change() - self._queued_search = None - return - - nodes_processed = 0 - while self._build_queue and nodes_processed < self.MAX_NODES_PER_FRAME: - child_node, parent, before_tag = self._build_queue.pop(next(iter(self._build_queue))) - parent_tag = "data_tree_container" if parent.name == "root" else parent.ui_tag - if not child_node.ui_created: - if child_node.is_leaf: - self._create_leaf_ui(child_node, parent_tag, before_tag) - else: - self._create_tree_node_ui(child_node, parent_tag, before_tag) - parent.created_children[child_node.name] = parent.children[child_node.name] - self._current_created_paths.add(child_node.full_path) - nodes_processed += 1 - - def _process_path_change(self): - self._build_queue.clear() - search_term = self.current_search.strip().lower() - all_paths = set(self.data_manager.get_all_paths()) - new_filtered_leafs = {path for path in all_paths if self._should_show_path(path, search_term)} - new_filtered_paths = set(new_filtered_leafs) - for path in new_filtered_leafs: - parts = path.split('/') - for i in range(1, len(parts)): - prefix = '/'.join(parts[:i]) - new_filtered_paths.add(prefix) - created_paths_to_remove = self._current_created_paths - new_filtered_paths - filtered_paths_to_remove = self._current_filtered_paths - new_filtered_leafs - - if created_paths_to_remove or filtered_paths_to_remove: - self._remove_paths_from_tree(created_paths_to_remove, filtered_paths_to_remove) - self._apply_expansion_to_tree(self.data_tree, search_term) - - paths_to_add = new_filtered_leafs - self._current_created_paths - if paths_to_add: - self._add_paths_to_tree(paths_to_add) - self._apply_expansion_to_tree(self.data_tree, search_term) - self._current_filtered_paths = new_filtered_paths - - def _remove_paths_from_tree(self, created_paths_to_remove, filtered_paths_to_remove): - for path in sorted(created_paths_to_remove, reverse=True): - current_node = self._path_to_node[path] - - if len(current_node.created_children) == 0: - self._current_created_paths.remove(current_node.full_path) - if item_handler_tag := self._item_handlers.get(current_node.ui_tag): - dpg.configure_item(item_handler_tag, show=False) - self._handlers_to_delete.append(item_handler_tag) - del self._item_handlers[current_node.ui_tag] - dpg.delete_item(current_node.ui_tag) - current_node.ui_created = False - current_node.ui_tag = None - current_node.children_ui_created = False - del current_node.parent.created_children[current_node.name] - del current_node.parent.filtered_children[current_node.name] - - for path in filtered_paths_to_remove: - parts = path.split('/') - current_node = self._path_to_node[path] - - part_array_index = -1 - while len(current_node.filtered_children) == 0 and part_array_index >= -len(parts): - current_node = current_node.parent - if parts[part_array_index] in current_node.filtered_children: - del current_node.filtered_children[parts[part_array_index]] - part_array_index -= 1 - - def _add_paths_to_tree(self, paths): - parent_nodes_to_recheck = set() - for path in sorted(paths): - parts = path.split('/') - current_node = self.data_tree - current_path_prefix = "" - - for i, part in enumerate(parts): - current_path_prefix = f"{current_path_prefix}/{part}" if current_path_prefix else part - if i < len(parts): - parent_nodes_to_recheck.add(current_node) # for incremental changes from new data - if part not in current_node.children: - current_node.children[part] = DataTreeNode(name=part, full_path=current_path_prefix, parent=current_node) - self._path_to_node[current_path_prefix] = current_node.children[part] - current_node.filtered_children[part] = current_node.children[part] - current_node = current_node.children[part] - - if not current_node.is_leaf: - current_node.is_leaf = True - - for p_node in parent_nodes_to_recheck: - p_node.children_ui_created = False - self._request_children_build(p_node) - - def _get_node_label_and_expand(self, node: DataTreeNode, search_term: str): - label = f"{node.name} ({len(node.filtered_children)} fields)" - expand = len(search_term) > 0 and any(search_term in path for path in self._get_descendant_paths(node)) - if expand and node.parent and len(node.parent.filtered_children) > 100 and len(node.filtered_children) > 2: - label += " (+)" # symbol for large lists which aren't fully expanded for performance (only affects procLog rn) - expand = False - return label, expand - - def _apply_expansion_to_tree(self, node: DataTreeNode, search_term: str): - if node.ui_created and not node.is_leaf and node.ui_tag and dpg.does_item_exist(node.ui_tag): - label, expand = self._get_node_label_and_expand(node, search_term) - if expand: - self._expanded_tags.add(node.ui_tag) - dpg.set_value(node.ui_tag, expand) - elif node.ui_tag in self._expanded_tags: # not expanded and was expanded - self._expanded_tags.remove(node.ui_tag) - dpg.set_value(node.ui_tag, expand) - dpg.delete_item(node.ui_tag, children_only=True) # delete children (not visible since collapsed) - self._reset_ui_state_recursive(node) - node.children_ui_created = False - dpg.set_item_label(node.ui_tag, label) - for child in node.created_children.values(): - self._apply_expansion_to_tree(child, search_term) - - def _reset_ui_state_recursive(self, node: DataTreeNode): - for child in node.created_children.values(): - if child.ui_tag is not None: - if item_handler_tag := self._item_handlers.get(child.ui_tag): - self._handlers_to_delete.append(item_handler_tag) - dpg.configure_item(item_handler_tag, show=False) - del self._item_handlers[child.ui_tag] - self._reset_ui_state_recursive(child) - child.ui_created = False - child.ui_tag = None - child.children_ui_created = False - self._current_created_paths.remove(child.full_path) - node.created_children.clear() - - def search_data(self): - with self._ui_lock: - self._queued_search = dpg.get_value("search_input") - - def _create_tree_node_ui(self, node: DataTreeNode, parent_tag: str, before: str | int): - node.ui_tag = f"tree_{node.full_path}" - search_term = self.current_search.strip().lower() - label, expand = self._get_node_label_and_expand(node, search_term) - if expand: - self._expanded_tags.add(node.ui_tag) - elif node.ui_tag in self._expanded_tags: - self._expanded_tags.remove(node.ui_tag) - - with dpg.tree_node( - label=label, parent=parent_tag, tag=node.ui_tag, default_open=expand, open_on_arrow=True, open_on_double_click=True, before=before, delay_search=True - ): - with dpg.item_handler_registry() as handler_tag: - dpg.add_item_toggled_open_handler(callback=lambda s, a, u: self._request_children_build(node)) - dpg.add_item_visible_handler(callback=lambda s, a, u: self._request_children_build(node)) - dpg.bind_item_handler_registry(node.ui_tag, handler_tag) - self._item_handlers[node.ui_tag] = handler_tag - node.ui_created = True - - def _create_leaf_ui(self, node: DataTreeNode, parent_tag: str, before: str | int): - node.ui_tag = f"leaf_{node.full_path}" - with dpg.group(parent=parent_tag, tag=node.ui_tag, before=before, delay_search=True): - with dpg.table(header_row=False, policy=dpg.mvTable_SizingStretchProp, delay_search=True): - dpg.add_table_column(init_width_or_weight=0.5) - dpg.add_table_column(init_width_or_weight=0.5) - with dpg.table_row(): - dpg.add_text(node.name) - dpg.add_text("N/A", tag=f"value_{node.full_path}") - - if node.is_plottable is None: - node.is_plottable = self.data_manager.is_plottable(node.full_path) - if node.is_plottable: - with dpg.drag_payload(parent=node.ui_tag, drag_data=node.full_path, payload_type="TIMESERIES_PAYLOAD"): - dpg.add_text(f"Plot: {node.full_path}") - - with dpg.item_handler_registry() as handler_tag: - dpg.add_item_visible_handler(callback=self._on_item_visible, user_data=node.full_path) - dpg.bind_item_handler_registry(node.ui_tag, handler_tag) - self._item_handlers[node.ui_tag] = handler_tag - node.ui_created = True - - def _on_item_visible(self, sender, app_data, user_data): - with self._ui_lock: - path = user_data - value_tag = f"value_{path}" - if not dpg.does_item_exist(value_tag): - return - value_column_width = dpg.get_item_rect_size(f"leaf_{path}")[0] // 2 - value = self.data_manager.get_value_at(path, self.playback_manager.current_time_s) - if value is not None: - formatted_value = self.format_and_truncate(value, value_column_width, self._char_width) - dpg.set_value(value_tag, formatted_value) - else: - dpg.set_value(value_tag, "N/A") - - def _request_children_build(self, node: DataTreeNode): - with self._ui_lock: - if not node.children_ui_created and (node.name == "root" or (node.ui_tag is not None and dpg.get_value(node.ui_tag))): # check root or node expanded - sorted_children = sorted(node.filtered_children.values(), key=self._natural_sort_key) - next_existing: list[int | str] = [0] * len(sorted_children) - current_before_tag: int | str = 0 - - for i in range(len(sorted_children) - 1, -1, -1): # calculate "before_tag" for correct ordering when incrementally building tree - child = sorted_children[i] - next_existing[i] = current_before_tag - if child.ui_created: - candidate_tag = f"leaf_{child.full_path}" if child.is_leaf else f"tree_{child.full_path}" - if dpg.does_item_exist(candidate_tag): - current_before_tag = candidate_tag - - for i, child_node in enumerate(sorted_children): - if not child_node.ui_created: - before_tag = next_existing[i] - self._build_queue[child_node.full_path] = (child_node, node, before_tag) - node.children_ui_created = True - - def _should_show_path(self, path: str, search_term: str) -> bool: - if 'DEPRECATED' in path and not os.environ.get('SHOW_DEPRECATED'): - return False - return not search_term or search_term in path.lower() - - def _natural_sort_key(self, node: DataTreeNode): - node_type_key = node.is_leaf - parts = [int(p) if p.isdigit() else p.lower() for p in re.split(r'(\d+)', node.name) if p] - return (node_type_key, parts) - - def _get_descendant_paths(self, node: DataTreeNode): - for child_name, child_node in node.filtered_children.items(): - child_name_lower = child_name.lower() - if child_node.is_leaf: - yield child_name_lower - else: - for path in self._get_descendant_paths(child_node): - yield f"{child_name_lower}/{path}" - - @staticmethod - def format_and_truncate(value, available_width: float, char_width: float) -> str: - s = f"{value:.5f}" if np.issubdtype(type(value), np.floating) else str(value) - max_chars = int(available_width / char_width) - if len(s) > max_chars: - return s[: max(0, max_chars - 3)] + "..." - return s diff --git a/tools/jotpluggler/layout.py b/tools/jotpluggler/layout.py deleted file mode 100644 index 13fbee54e20..00000000000 --- a/tools/jotpluggler/layout.py +++ /dev/null @@ -1,477 +0,0 @@ -import dearpygui.dearpygui as dpg -from openpilot.tools.jotpluggler.data import DataManager -from openpilot.tools.jotpluggler.views import TimeSeriesPanel - -GRIP_SIZE = 4 -MIN_PANE_SIZE = 60 - -class LayoutManager: - def __init__(self, data_manager, playback_manager, worker_manager, scale: float = 1.0): - self.data_manager = data_manager - self.playback_manager = playback_manager - self.worker_manager = worker_manager - self.scale = scale - self.container_tag = "plot_layout_container" - self.tab_bar_tag = "tab_bar_container" - self.tab_content_tag = "tab_content_area" - - self.active_tab = 0 - initial_panel_layout = PanelLayoutManager(data_manager, playback_manager, worker_manager, scale) - self.tabs: dict = {0: {"name": "Tab 1", "panel_layout": initial_panel_layout}} - self._next_tab_id = self.active_tab + 1 - - def to_dict(self) -> dict: - return { - "tabs": { - str(tab_id): { - "name": tab_data["name"], - "panel_layout": tab_data["panel_layout"].to_dict() - } - for tab_id, tab_data in self.tabs.items() - } - } - - def clear_and_load_from_dict(self, data: dict): - tab_ids_to_close = list(self.tabs.keys()) - for tab_id in tab_ids_to_close: - self.close_tab(tab_id, force=True) - - for tab_id_str, tab_data in data["tabs"].items(): - tab_id = int(tab_id_str) - panel_layout = PanelLayoutManager.load_from_dict( - tab_data["panel_layout"], self.data_manager, self.playback_manager, - self.worker_manager, self.scale - ) - self.tabs[tab_id] = { - "name": tab_data["name"], - "panel_layout": panel_layout - } - - self.active_tab = min(self.tabs.keys()) if self.tabs else 0 - self._next_tab_id = max(self.tabs.keys()) + 1 if self.tabs else 1 - - def create_ui(self, parent_tag: str): - if dpg.does_item_exist(self.container_tag): - dpg.delete_item(self.container_tag) - - with dpg.child_window(tag=self.container_tag, parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): - self._create_tab_bar() - self._create_tab_content() - dpg.bind_item_theme(self.tab_bar_tag, "tab_bar_theme") - - def _create_tab_bar(self): - text_size = int(13 * self.scale) - with dpg.child_window(tag=self.tab_bar_tag, parent=self.container_tag, height=(text_size + 8), border=False, horizontal_scrollbar=True): - with dpg.group(horizontal=True, tag="tab_bar_group"): - for tab_id, tab_data in self.tabs.items(): - self._create_tab_ui(tab_id, tab_data["name"]) - dpg.add_image_button(texture_tag="plus_texture", callback=self.add_tab, width=text_size, height=text_size, tag="add_tab_button") - dpg.bind_item_theme("add_tab_button", "inactive_tab_theme") - - def _create_tab_ui(self, tab_id: int, tab_name: str): - text_size = int(13 * self.scale) - tab_width = int(140 * self.scale) - with dpg.child_window(width=tab_width, height=-1, border=False, no_scrollbar=True, tag=f"tab_window_{tab_id}", parent="tab_bar_group"): - with dpg.group(horizontal=True, tag=f"tab_group_{tab_id}"): - dpg.add_input_text( - default_value=tab_name, width=tab_width - text_size - 16, callback=lambda s, v, u: self.rename_tab(u, v), user_data=tab_id, tag=f"tab_input_{tab_id}" - ) - dpg.add_image_button( - texture_tag="x_texture", callback=lambda s, a, u: self.close_tab(u), user_data=tab_id, width=text_size, height=text_size, tag=f"tab_close_{tab_id}" - ) - with dpg.item_handler_registry(tag=f"tab_handler_{tab_id}"): - dpg.add_item_clicked_handler(callback=lambda s, a, u: self.switch_tab(u), user_data=tab_id) - dpg.bind_item_handler_registry(f"tab_group_{tab_id}", f"tab_handler_{tab_id}") - - theme_tag = "active_tab_theme" if tab_id == self.active_tab else "inactive_tab_theme" - dpg.bind_item_theme(f"tab_window_{tab_id}", theme_tag) - - def _create_tab_content(self): - with dpg.child_window(tag=self.tab_content_tag, parent=self.container_tag, border=False, width=-1, height=-1, no_scrollbar=True, no_scroll_with_mouse=True): - if self.active_tab in self.tabs: - active_panel_layout = self.tabs[self.active_tab]["panel_layout"] - active_panel_layout.create_ui() - - def add_tab(self): - new_panel_layout = PanelLayoutManager(self.data_manager, self.playback_manager, self.worker_manager, self.scale) - new_tab = {"name": f"Tab {self._next_tab_id + 1}", "panel_layout": new_panel_layout} - self.tabs[self._next_tab_id] = new_tab - self._create_tab_ui(self._next_tab_id, new_tab["name"]) - dpg.move_item("add_tab_button", parent="tab_bar_group") # move plus button to end - self.switch_tab(self._next_tab_id) - self._next_tab_id += 1 - - def close_tab(self, tab_id: int, force = False): - if len(self.tabs) <= 1 and not force: - return # don't allow closing the last tab - - tab_to_close = self.tabs[tab_id] - tab_to_close["panel_layout"].destroy_ui() - for suffix in ["window", "group", "input", "close", "handler"]: - tag = f"tab_{suffix}_{tab_id}" - if dpg.does_item_exist(tag): - dpg.delete_item(tag) - del self.tabs[tab_id] - - if self.active_tab == tab_id and self.tabs: # switch to another tab if we closed the active one - self.active_tab = next(iter(self.tabs.keys())) - self._switch_tab_content() - dpg.bind_item_theme(f"tab_window_{self.active_tab}", "active_tab_theme") - - def switch_tab(self, tab_id: int): - if tab_id == self.active_tab or tab_id not in self.tabs: - return - - current_panel_layout = self.tabs[self.active_tab]["panel_layout"] - current_panel_layout.destroy_ui() - dpg.bind_item_theme(f"tab_window_{self.active_tab}", "inactive_tab_theme") # deactivate old tab - self.active_tab = tab_id - dpg.bind_item_theme(f"tab_window_{tab_id}", "active_tab_theme") # activate new tab - self._switch_tab_content() - - def _switch_tab_content(self): - dpg.delete_item(self.tab_content_tag, children_only=True) - active_panel_layout = self.tabs[self.active_tab]["panel_layout"] - active_panel_layout.create_ui() - active_panel_layout.update_all_panels() - - def rename_tab(self, tab_id: int, new_name: str): - if tab_id in self.tabs: - self.tabs[tab_id]["name"] = new_name - - def update_all_panels(self): - self.tabs[self.active_tab]["panel_layout"].update_all_panels() - - def on_viewport_resize(self): - self.tabs[self.active_tab]["panel_layout"].on_viewport_resize() - -class PanelLayoutManager: - def __init__(self, data_manager: DataManager, playback_manager, worker_manager, scale: float = 1.0): - self.data_manager = data_manager - self.playback_manager = playback_manager - self.worker_manager = worker_manager - self.scale = scale - self.active_panels: list = [] - self.parent_tag = "tab_content_area" - self._queue_resize = False - self._created_handler_tags: set[str] = set() - - self.grip_size = int(GRIP_SIZE * self.scale) - self.min_pane_size = int(MIN_PANE_SIZE * self.scale) - - initial_panel = TimeSeriesPanel(data_manager, playback_manager, worker_manager) - self.layout: dict = {"type": "panel", "panel": initial_panel} - - def to_dict(self) -> dict: - return self._layout_to_dict(self.layout) - - def _layout_to_dict(self, layout: dict) -> dict: - if layout["type"] == "panel": - return { - "type": "panel", - "panel": layout["panel"].to_dict() - } - else: # split - return { - "type": "split", - "orientation": layout["orientation"], - "proportions": layout["proportions"], - "children": [self._layout_to_dict(child) for child in layout["children"]] - } - - @classmethod - def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager, scale: float = 1.0): - manager = cls(data_manager, playback_manager, worker_manager, scale) - manager.layout = manager._dict_to_layout(data) - return manager - - def _dict_to_layout(self, data: dict) -> dict: - if data["type"] == "panel": - panel_data = data["panel"] - if panel_data["type"] == "timeseries": - panel = TimeSeriesPanel.load_from_dict( - panel_data, self.data_manager, self.playback_manager, self.worker_manager - ) - return {"type": "panel", "panel": panel} - else: - # Handle future panel types here or make a general mapping - raise ValueError(f"Unknown panel type: {panel_data['type']}") - else: # split - return { - "type": "split", - "orientation": data["orientation"], - "proportions": data["proportions"], - "children": [self._dict_to_layout(child) for child in data["children"]] - } - - def create_ui(self): - self.active_panels.clear() - if dpg.does_item_exist(self.parent_tag): - dpg.delete_item(self.parent_tag, children_only=True) - self._cleanup_all_handlers() - - container_width, container_height = dpg.get_item_rect_size(self.parent_tag) - if container_width == 0 and container_height == 0: - self._queue_resize = True - self._create_ui_recursive(self.layout, self.parent_tag, [], container_width, container_height) - - def destroy_ui(self): - self._cleanup_ui_recursive(self.layout, []) - self._cleanup_all_handlers() - self.active_panels.clear() - - def _cleanup_all_handlers(self): - for handler_tag in list(self._created_handler_tags): - if dpg.does_item_exist(handler_tag): - dpg.delete_item(handler_tag) - self._created_handler_tags.clear() - - def _create_ui_recursive(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): - if layout["type"] == "panel": - self._create_panel_ui(layout, parent_tag, path, width, height) - else: - self._create_split_ui(layout, parent_tag, path, width, height) - - def _create_panel_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): - panel_tag = self._path_to_tag(path, "panel") - panel = layout["panel"] - self.active_panels.append(panel) - text_size = int(13 * self.scale) - bar_height = (text_size + 24) if width < int(329 * self.scale + 64) else (text_size + 8) # adjust height to allow for scrollbar - - with dpg.child_window(parent=parent_tag, border=False, width=-1, height=-1, no_scrollbar=True): - with dpg.group(horizontal=True): - with dpg.child_window(tag=panel_tag, width=-(text_size + 16), height=bar_height, horizontal_scrollbar=True, no_scroll_with_mouse=True, border=False): - with dpg.group(horizontal=True): - # if you change the widths make sure to change the sum of widths (currently 329 * scale) - dpg.add_input_text(default_value=panel.title, width=int(150 * self.scale), callback=lambda s, v: setattr(panel, "title", v)) - dpg.add_combo(items=["Time Series"], default_value="Time Series", width=int(100 * self.scale)) - dpg.add_button(label="Clear", callback=lambda: self.clear_panel(panel), width=int(40 * self.scale)) - dpg.add_image_button(texture_tag="split_h_texture", callback=lambda: self.split_panel(path, 0), width=text_size, height=text_size) - dpg.add_image_button(texture_tag="split_v_texture", callback=lambda: self.split_panel(path, 1), width=text_size, height=text_size) - dpg.add_image_button(texture_tag="x_texture", callback=lambda: self.delete_panel(path), width=text_size, height=text_size) - - dpg.add_separator() - - content_tag = self._path_to_tag(path, "content") - with dpg.child_window(tag=content_tag, border=False, height=-1, width=-1, no_scrollbar=True): - panel.create_ui(content_tag) - - def _create_split_ui(self, layout: dict, parent_tag: str, path: list[int], width: int, height: int): - split_tag = self._path_to_tag(path, "split") - orientation, _, pane_sizes = self._get_split_geometry(layout, (width, height)) - - with dpg.group(tag=split_tag, parent=parent_tag, horizontal=orientation == 0): - for i, child_layout in enumerate(layout["children"]): - child_path = path + [i] - container_tag = self._path_to_tag(child_path, "container") - pane_width, pane_height = [(pane_sizes[i], -1), (-1, pane_sizes[i])][orientation] # fill 2nd dim up to the border - with dpg.child_window(tag=container_tag, width=pane_width, height=pane_height, border=False, no_scrollbar=True): - child_width, child_height = [(pane_sizes[i], height), (width, pane_sizes[i])][orientation] - self._create_ui_recursive(child_layout, container_tag, child_path, child_width, child_height) - if i < len(layout["children"]) - 1: - self._create_grip(split_tag, path, i, orientation) - - def clear_panel(self, panel): - panel.clear() - - def delete_panel(self, panel_path: list[int]): - if not panel_path: # Root deletion - old_panel = self.layout["panel"] - old_panel.destroy_ui() - self.active_panels.remove(old_panel) - new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager, self.worker_manager) - self.layout = {"type": "panel", "panel": new_panel} - self._rebuild_ui_at_path([]) - return - - parent, child_index = self._get_parent_and_index(panel_path) - layout_to_delete = parent["children"][child_index] - self._cleanup_ui_recursive(layout_to_delete, panel_path) - - parent["children"].pop(child_index) - parent["proportions"].pop(child_index) - - if len(parent["children"]) == 1: # remove parent and collapse - remaining_child = parent["children"][0] - if len(panel_path) == 1: # parent is at root level - promote remaining child to root - self.layout = remaining_child - self._rebuild_ui_at_path([]) - else: # replace parent with remaining child in grandparent - grandparent_path = panel_path[:-2] - parent_index = panel_path[-2] - self._replace_layout_at_path(grandparent_path + [parent_index], remaining_child) - self._rebuild_ui_at_path(grandparent_path + [parent_index]) - else: # redistribute proportions - equal_prop = 1.0 / len(parent["children"]) - parent["proportions"] = [equal_prop] * len(parent["children"]) - self._rebuild_ui_at_path(panel_path[:-1]) - - def split_panel(self, panel_path: list[int], orientation: int): - current_layout = self._get_layout_at_path(panel_path) - existing_panel = current_layout["panel"] - new_panel = TimeSeriesPanel(self.data_manager, self.playback_manager, self.worker_manager) - parent, child_index = self._get_parent_and_index(panel_path) - - if parent is None: # Root split - self.layout = { - "type": "split", - "orientation": orientation, - "children": [{"type": "panel", "panel": existing_panel}, {"type": "panel", "panel": new_panel}], - "proportions": [0.5, 0.5], - } - self._rebuild_ui_at_path([]) - elif parent["type"] == "split" and parent["orientation"] == orientation: # Same orientation - insert into existing split - parent["children"].insert(child_index + 1, {"type": "panel", "panel": new_panel}) - parent["proportions"] = [1.0 / len(parent["children"])] * len(parent["children"]) - self._rebuild_ui_at_path(panel_path[:-1]) - else: # Different orientation - create new split level - new_split = {"type": "split", "orientation": orientation, "children": [current_layout, {"type": "panel", "panel": new_panel}], "proportions": [0.5, 0.5]} - self._replace_layout_at_path(panel_path, new_split) - self._rebuild_ui_at_path(panel_path) - - def _rebuild_ui_at_path(self, path: list[int]): - layout = self._get_layout_at_path(path) - if path: - container_tag = self._path_to_tag(path, "container") - else: # Root update - container_tag = self.parent_tag - - self._cleanup_ui_recursive(layout, path) - dpg.delete_item(container_tag, children_only=True) - width, height = dpg.get_item_rect_size(container_tag) - self._create_ui_recursive(layout, container_tag, path, width, height) - - def _cleanup_ui_recursive(self, layout: dict, path: list[int]): - if layout["type"] == "panel": - panel = layout["panel"] - panel.destroy_ui() - if panel in self.active_panels: - self.active_panels.remove(panel) - else: - for i in range(len(layout["children"]) - 1): - handler_tag = f"{self._path_to_tag(path, f'grip_{i}')}_handler" - if dpg.does_item_exist(handler_tag): - dpg.delete_item(handler_tag) - self._created_handler_tags.discard(handler_tag) - - for i, child in enumerate(layout["children"]): - self._cleanup_ui_recursive(child, path + [i]) - - def update_all_panels(self): - if self._queue_resize: - if (size := dpg.get_item_rect_size(self.parent_tag)) != [0, 0]: - self._queue_resize = False - self._resize_splits_recursive(self.layout, [], *size) - for panel in self.active_panels: - panel.update() - - def on_viewport_resize(self): - self._resize_splits_recursive(self.layout, []) - - def _resize_splits_recursive(self, layout: dict, path: list[int], width: int | None = None, height: int | None = None): - if layout["type"] == "split": - split_tag = self._path_to_tag(path, "split") - if dpg.does_item_exist(split_tag): - available_sizes = (width, height) if width and height else dpg.get_item_rect_size(dpg.get_item_parent(split_tag)) - orientation, _, pane_sizes = self._get_split_geometry(layout, available_sizes) - size_properties = ("width", "height") - - for i, child_layout in enumerate(layout["children"]): - child_path = path + [i] - container_tag = self._path_to_tag(child_path, "container") - if dpg.does_item_exist(container_tag): - dpg.configure_item(container_tag, **{size_properties[orientation]: pane_sizes[i]}) - child_width, child_height = [(pane_sizes[i], available_sizes[1]), (available_sizes[0], pane_sizes[i])][orientation] - self._resize_splits_recursive(child_layout, child_path, child_width, child_height) - else: # leaf node/panel - adjust bar height to allow for scrollbar - panel_tag = self._path_to_tag(path, "panel") - if width is not None and width < int(329 * self.scale + 64): # scaled widths of the elements in top bar + fixed 8 padding on left and right of each item - dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 24)) - else: - dpg.configure_item(panel_tag, height=(int(13 * self.scale) + 8)) - - def _get_split_geometry(self, layout: dict, available_size: tuple[int, int]) -> tuple[int, int, list[int]]: - orientation = layout["orientation"] - num_grips = len(layout["children"]) - 1 - usable_size = max(self.min_pane_size, available_size[orientation] - (num_grips * (self.grip_size + 8 * (2 - orientation)))) # approximate, scaling is weird - pane_sizes = [max(self.min_pane_size, int(usable_size * prop)) for prop in layout["proportions"]] - return orientation, usable_size, pane_sizes - - def _get_layout_at_path(self, path: list[int]) -> dict: - current = self.layout - for index in path: - current = current["children"][index] - return current - - def _get_parent_and_index(self, path: list[int]) -> tuple: - return (None, -1) if not path else (self._get_layout_at_path(path[:-1]), path[-1]) - - def _replace_layout_at_path(self, path: list[int], new_layout: dict): - if not path: - self.layout = new_layout - else: - parent, index = self._get_parent_and_index(path) - parent["children"][index] = new_layout - - def _path_to_tag(self, path: list[int], prefix: str = "") -> str: - path_str = "_".join(map(str, path)) if path else "root" - return f"{prefix}_{path_str}" if prefix else path_str - - def _create_grip(self, parent_tag: str, path: list[int], grip_index: int, orientation: int): - grip_tag = self._path_to_tag(path, f"grip_{grip_index}") - handler_tag = f"{grip_tag}_handler" - width, height = [(self.grip_size, -1), (-1, self.grip_size)][orientation] - - with dpg.child_window(tag=grip_tag, parent=parent_tag, width=width, height=height, no_scrollbar=True, border=False): - button_tag = dpg.add_button(label="", width=-1, height=-1) - - with dpg.item_handler_registry(tag=handler_tag): - user_data = (path, grip_index, orientation) - dpg.add_item_active_handler(callback=self._on_grip_drag, user_data=user_data) - dpg.add_item_deactivated_handler(callback=self._on_grip_end, user_data=user_data) - dpg.bind_item_handler_registry(button_tag, handler_tag) - self._created_handler_tags.add(handler_tag) - - def _on_grip_drag(self, sender, app_data, user_data): - path, grip_index, orientation = user_data - layout = self._get_layout_at_path(path) - - if "_drag_data" not in layout: - layout["_drag_data"] = {"initial_proportions": layout["proportions"][:], "start_mouse": dpg.get_mouse_pos(local=False)[orientation]} - return - - drag_data = layout["_drag_data"] - split_tag = self._path_to_tag(path, "split") - if not dpg.does_item_exist(split_tag): - return - - _, usable_size, _ = self._get_split_geometry(layout, dpg.get_item_rect_size(split_tag)) - current_coord = dpg.get_mouse_pos(local=False)[orientation] - delta = current_coord - drag_data["start_mouse"] - delta_prop = delta / usable_size - - left_idx = grip_index - right_idx = left_idx + 1 - initial = drag_data["initial_proportions"] - min_prop = self.min_pane_size / usable_size - - new_left = max(min_prop, initial[left_idx] + delta_prop) - new_right = max(min_prop, initial[right_idx] - delta_prop) - - total_available = initial[left_idx] + initial[right_idx] - if new_left + new_right > total_available: - if new_left > new_right: - new_left = total_available - new_right - else: - new_right = total_available - new_left - - layout["proportions"] = initial[:] - layout["proportions"][left_idx] = new_left - layout["proportions"][right_idx] = new_right - - self._resize_splits_recursive(layout, path) - - def _on_grip_end(self, sender, app_data, user_data): - path, _, _ = user_data - self._get_layout_at_path(path).pop("_drag_data", None) diff --git a/tools/jotpluggler/layouts/torque-controller.yaml b/tools/jotpluggler/layouts/torque-controller.yaml deleted file mode 100644 index 5503be9e64c..00000000000 --- a/tools/jotpluggler/layouts/torque-controller.yaml +++ /dev/null @@ -1,128 +0,0 @@ -tabs: - '0': - name: Lateral Plan Conformance - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: desired vs actual - series_paths: - - controlsState/lateralControlState/torqueState/desiredLateralAccel - - controlsState/lateralControlState/torqueState/actualLateralAccel - - type: panel - panel: - type: timeseries - title: ff vs output - series_paths: - - controlsState/lateralControlState/torqueState/f - - carState/steeringPressed - - carControl/actuators/torque - - type: panel - panel: - type: timeseries - title: vehicle speed - series_paths: - - carState/vEgo - '1': - name: Actuator Performance - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: calc vs learned latAccelFactor - series_paths: - - liveTorqueParameters/latAccelFactorFiltered - - liveTorqueParameters/latAccelFactorRaw - - carParams/lateralTuning/torque/latAccelFactor - - type: panel - panel: - type: timeseries - title: learned latAccelOffset - series_paths: - - liveTorqueParameters/latAccelOffsetRaw - - liveTorqueParameters/latAccelOffsetFiltered - - type: panel - panel: - type: timeseries - title: calc vs learned friction - series_paths: - - liveTorqueParameters/frictionCoefficientFiltered - - liveTorqueParameters/frictionCoefficientRaw - - carParams/lateralTuning/torque/friction - '2': - name: Vehicle Dynamics - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: initial vs learned steerRatio - series_paths: - - carParams/steerRatio - - liveParameters/steerRatio - - type: panel - panel: - type: timeseries - title: initial vs learned tireStiffnessFactor - series_paths: - - carParams/tireStiffnessFactor - - liveParameters/stiffnessFactor - - type: panel - panel: - type: timeseries - title: live steering angle offsets - series_paths: - - liveParameters/angleOffsetDeg - - liveParameters/angleOffsetAverageDeg - '3': - name: Controller PIF Terms - panel_layout: - type: split - orientation: 1 - proportions: - - 0.3333333333333333 - - 0.3333333333333333 - - 0.3333333333333333 - children: - - type: panel - panel: - type: timeseries - title: ff vs output - series_paths: - - carControl/actuators/torque - - controlsState/lateralControlState/torqueState/f - - carState/steeringPressed - - type: panel - panel: - type: timeseries - title: PIF terms - series_paths: - - controlsState/lateralControlState/torqueState/f - - controlsState/lateralControlState/torqueState/p - - controlsState/lateralControlState/torqueState/i - - type: panel - panel: - type: timeseries - title: road roll angle - series_paths: - - liveParameters/roll diff --git a/tools/jotpluggler/pluggle.py b/tools/jotpluggler/pluggle.py deleted file mode 100755 index 2fb6e3e2f4e..00000000000 --- a/tools/jotpluggler/pluggle.py +++ /dev/null @@ -1,368 +0,0 @@ -#!/usr/bin/env python3 -import argparse -import os -import dearpygui.dearpygui as dpg -import multiprocessing -import uuid -import signal -import yaml -from openpilot.common.swaglog import cloudlog -from openpilot.common.basedir import BASEDIR -from openpilot.tools.jotpluggler.data import DataManager -from openpilot.tools.jotpluggler.datatree import DataTree -from openpilot.tools.jotpluggler.layout import LayoutManager - -DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496" - - -class WorkerManager: - def __init__(self, max_workers=None): - self.pool = multiprocessing.Pool(max_workers or min(4, multiprocessing.cpu_count()), initializer=WorkerManager.worker_initializer) - self.active_tasks = {} - - def submit_task(self, func, args_list, callback=None, task_id=None): - task_id = task_id or str(uuid.uuid4()) - - if task_id in self.active_tasks: - try: - self.active_tasks[task_id].terminate() - except Exception: - pass - - def handle_success(result): - self.active_tasks.pop(task_id, None) - if callback: - try: - callback(result) - except Exception as e: - print(f"Callback for task {task_id} failed: {e}") - - def handle_error(error): - self.active_tasks.pop(task_id, None) - print(f"Task {task_id} failed: {error}") - - async_result = self.pool.starmap_async(func, args_list, callback=handle_success, error_callback=handle_error) - self.active_tasks[task_id] = async_result - return task_id - - @staticmethod - def worker_initializer(): - signal.signal(signal.SIGINT, signal.SIG_IGN) - - def shutdown(self): - for task in self.active_tasks.values(): - try: - task.terminate() - except Exception: - pass - self.pool.terminate() - self.pool.join() - - -class PlaybackManager: - def __init__(self): - self.is_playing = False - self.current_time_s = 0.0 - self.duration_s = 0.0 - self.num_segments = 0 - - self.x_axis_bounds = (0.0, 0.0) # (min_time, max_time) - self.x_axis_observers = [] # callbacks for x-axis changes - self._updating_x_axis = False - - def set_route_duration(self, duration: float): - self.duration_s = duration - self.seek(min(self.current_time_s, duration)) - - def toggle_play_pause(self): - if not self.is_playing and self.current_time_s >= self.duration_s: - self.seek(0.0) - self.is_playing = not self.is_playing - texture_tag = "pause_texture" if self.is_playing else "play_texture" - dpg.configure_item("play_pause_button", texture_tag=texture_tag) - - def seek(self, time_s: float): - self.current_time_s = max(0.0, min(time_s, self.duration_s)) - - def update_time(self, delta_t: float): - if self.is_playing: - self.current_time_s = min(self.current_time_s + delta_t, self.duration_s) - if self.current_time_s >= self.duration_s: - self.is_playing = False - dpg.configure_item("play_pause_button", texture_tag="play_texture") - return self.current_time_s - - def set_x_axis_bounds(self, min_time: float, max_time: float, source_panel=None): - if self._updating_x_axis: - return - - new_bounds = (min_time, max_time) - if new_bounds == self.x_axis_bounds: - return - - self.x_axis_bounds = new_bounds - self._updating_x_axis = True # prevent recursive updates - - try: - for callback in self.x_axis_observers: - try: - callback(min_time, max_time, source_panel) - except Exception as e: - print(f"Error in x-axis sync callback: {e}") - finally: - self._updating_x_axis = False - - def add_x_axis_observer(self, callback): - if callback not in self.x_axis_observers: - self.x_axis_observers.append(callback) - - def remove_x_axis_observer(self, callback): - if callback in self.x_axis_observers: - self.x_axis_observers.remove(callback) - -class MainController: - def __init__(self, scale: float = 1.0): - self.scale = scale - self.data_manager = DataManager() - self.playback_manager = PlaybackManager() - self.worker_manager = WorkerManager() - self._create_global_themes() - self.data_tree = DataTree(self.data_manager, self.playback_manager) - self.layout_manager = LayoutManager(self.data_manager, self.playback_manager, self.worker_manager, scale=self.scale) - self.data_manager.add_observer(self.on_data_loaded) - self._total_segments = 0 - - def _create_global_themes(self): - with dpg.theme(tag="line_theme"): - with dpg.theme_component(dpg.mvLineSeries): - scaled_thickness = max(1.0, self.scale) - dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, scaled_thickness, category=dpg.mvThemeCat_Plots) - - with dpg.theme(tag="timeline_theme"): - with dpg.theme_component(dpg.mvInfLineSeries): - scaled_thickness = max(1.0, self.scale) - dpg.add_theme_style(dpg.mvPlotStyleVar_LineWeight, scaled_thickness, category=dpg.mvThemeCat_Plots) - dpg.add_theme_color(dpg.mvPlotCol_Line, (255, 0, 0, 128), category=dpg.mvThemeCat_Plots) - - for tag, color in (("active_tab_theme", (37, 37, 38, 255)), ("inactive_tab_theme", (70, 70, 75, 255))): - with dpg.theme(tag=tag): - for cmp, target in ((dpg.mvChildWindow, dpg.mvThemeCol_ChildBg), (dpg.mvInputText, dpg.mvThemeCol_FrameBg), (dpg.mvImageButton, dpg.mvThemeCol_Button)): - with dpg.theme_component(cmp): - dpg.add_theme_color(target, color) - - with dpg.theme(tag="tab_bar_theme"): - with dpg.theme_component(dpg.mvChildWindow): - dpg.add_theme_color(dpg.mvThemeCol_ChildBg, (51, 51, 55, 255)) - - def on_data_loaded(self, data: dict): - duration = data.get('duration', 0.0) - self.playback_manager.set_route_duration(duration) - - if data.get('metadata_loaded'): - self.playback_manager.num_segments = data.get('total_segments', 0) - self._total_segments = data.get('total_segments', 0) - dpg.set_value("load_status", f"Loading... 0/{self._total_segments} segments processed") - elif data.get('reset'): - self.playback_manager.current_time_s = 0.0 - self.playback_manager.duration_s = 0.0 - self.playback_manager.is_playing = False - self._total_segments = 0 - dpg.set_value("load_status", "Loading...") - dpg.set_value("timeline_slider", 0.0) - dpg.configure_item("timeline_slider", max_value=0.0) - dpg.configure_item("play_pause_button", texture_tag="play_texture") - dpg.configure_item("load_button", enabled=True) - elif data.get('loading_complete'): - num_paths = len(self.data_manager.get_all_paths()) - dpg.set_value("load_status", f"Loaded {num_paths} data paths") - dpg.configure_item("load_button", enabled=True) - elif data.get('segment_added'): - segment_count = data.get('segment_count', 0) - dpg.set_value("load_status", f"Loading... {segment_count}/{self._total_segments} segments processed") - - dpg.configure_item("timeline_slider", max_value=duration) - - def save_layout_to_yaml(self, filepath: str): - layout_dict = self.layout_manager.to_dict() - with open(filepath, 'w') as f: - yaml.dump(layout_dict, f, default_flow_style=False, sort_keys=False) - - def load_layout_from_yaml(self, filepath: str): - with open(filepath) as f: - layout_dict = yaml.safe_load(f) - self.layout_manager.clear_and_load_from_dict(layout_dict) - self.layout_manager.create_ui("main_plot_area") - - def save_layout_dialog(self): - if dpg.does_item_exist("save_layout_dialog"): - dpg.delete_item("save_layout_dialog") - with dpg.file_dialog( - callback=self._save_layout_callback, tag="save_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), - default_filename="layout", default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") - ): - dpg.add_file_extension(".yaml") - - def load_layout_dialog(self): - if dpg.does_item_exist("load_layout_dialog"): - dpg.delete_item("load_layout_dialog") - with dpg.file_dialog( - callback=self._load_layout_callback, tag="load_layout_dialog", width=int(700 * self.scale), height=int(400 * self.scale), - default_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "layouts") - ): - dpg.add_file_extension(".yaml") - - def _save_layout_callback(self, sender, app_data): - filepath = app_data['file_path_name'] - try: - self.save_layout_to_yaml(filepath) - dpg.set_value("load_status", f"Layout saved to {os.path.basename(filepath)}") - except Exception: - dpg.set_value("load_status", "Error saving layout") - cloudlog.exception(f"Error saving layout to {filepath}") - dpg.delete_item("save_layout_dialog") - - def _load_layout_callback(self, sender, app_data): - filepath = app_data['file_path_name'] - try: - self.load_layout_from_yaml(filepath) - dpg.set_value("load_status", f"Layout loaded from {os.path.basename(filepath)}") - except Exception: - dpg.set_value("load_status", "Error loading layout") - cloudlog.exception(f"Error loading layout from {filepath}:") - dpg.delete_item("load_layout_dialog") - - def setup_ui(self): - with dpg.texture_registry(): - script_dir = os.path.dirname(os.path.realpath(__file__)) - for image in ["play", "pause", "x", "split_h", "split_v", "plus"]: - texture = dpg.load_image(os.path.join(script_dir, "assets", f"{image}.png")) - dpg.add_static_texture(width=texture[0], height=texture[1], default_value=texture[3], tag=f"{image}_texture") - - with dpg.window(tag="Primary Window"): - with dpg.group(horizontal=True): - # Left panel - Data tree - with dpg.child_window(label="Sidebar", width=int(300 * self.scale), tag="sidebar_window", border=True, resizable_x=True): - with dpg.group(horizontal=True): - dpg.add_input_text(tag="route_input", width=int(-75 * self.scale), hint="Enter route name...") - dpg.add_button(label="Load", callback=self.load_route, tag="load_button", width=-1) - dpg.add_text("Ready to load route", tag="load_status") - dpg.add_separator() - - with dpg.table(header_row=False, policy=dpg.mvTable_SizingStretchProp): - dpg.add_table_column(init_width_or_weight=0.5) - dpg.add_table_column(init_width_or_weight=0.5) - with dpg.table_row(): - dpg.add_button(label="Save Layout", callback=self.save_layout_dialog, width=-1) - dpg.add_button(label="Load Layout", callback=self.load_layout_dialog, width=-1) - dpg.add_separator() - - self.data_tree.create_ui("sidebar_window") - - # Right panel - Plots and timeline - with dpg.group(tag="right_panel"): - with dpg.child_window(label="Plot Window", border=True, height=int(-(32 + 13 * self.scale)), tag="main_plot_area"): - self.layout_manager.create_ui("main_plot_area") - - with dpg.child_window(label="Timeline", border=True): - with dpg.table(header_row=False): - btn_size = int(13 * self.scale) - dpg.add_table_column(width_fixed=True, init_width_or_weight=(btn_size + 8)) # Play button - dpg.add_table_column(width_stretch=True) # Timeline slider - dpg.add_table_column(width_fixed=True, init_width_or_weight=int(50 * self.scale)) # FPS counter - with dpg.table_row(): - dpg.add_image_button(texture_tag="play_texture", tag="play_pause_button", callback=self.toggle_play_pause, width=btn_size, height=btn_size) - dpg.add_slider_float(tag="timeline_slider", default_value=0.0, label="", width=-1, callback=self.timeline_drag) - dpg.add_text("", tag="fps_counter") - with dpg.item_handler_registry(tag="plot_resize_handler"): - dpg.add_item_resize_handler(callback=self.on_plot_resize) - dpg.bind_item_handler_registry("right_panel", "plot_resize_handler") - - dpg.set_primary_window("Primary Window", True) - - def on_plot_resize(self, sender, app_data, user_data): - self.layout_manager.on_viewport_resize() - - def load_route(self): - route_name = dpg.get_value("route_input").strip() - if route_name: - dpg.set_value("load_status", "Loading route...") - dpg.configure_item("load_button", enabled=False) - self.data_manager.load_route(route_name) - - def toggle_play_pause(self, sender): - self.playback_manager.toggle_play_pause() - - def timeline_drag(self, sender, app_data): - self.playback_manager.seek(app_data) - - def update_frame(self, font): - self.data_tree.update_frame(font) - - new_time = self.playback_manager.update_time(dpg.get_delta_time()) - if not dpg.is_item_active("timeline_slider"): - dpg.set_value("timeline_slider", new_time) - - self.layout_manager.update_all_panels() - - dpg.set_value("fps_counter", f"{dpg.get_frame_rate():.1f} FPS") - - def shutdown(self): - self.worker_manager.shutdown() - - -def main(route_to_load=None, layout_to_load=None): - dpg.create_context() - - # TODO: find better way of calculating display scaling - #try: - # w, h = next(tuple(map(int, l.split()[0].split('x'))) for l in subprocess.check_output(['xrandr']).decode().split('\n') if '*' in l) # actual resolution - # scale = pyautogui.size()[0] / w # scaled resolution - #except Exception: - # scale = 1 - scale = 1 - - with dpg.font_registry(): - default_font = dpg.add_font(os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf"), int(13 * scale * 2)) # 2x then scale for hidpi - dpg.bind_font(default_font) - dpg.set_global_font_scale(0.5) - - viewport_width, viewport_height = int(1200 * scale), int(800 * scale) - dpg.create_viewport( - title='JotPluggler', width=viewport_width, height=viewport_height, - ) - dpg.setup_dearpygui() - - controller = MainController(scale=scale) - controller.setup_ui() - - if layout_to_load: - try: - controller.load_layout_from_yaml(layout_to_load) - print(f"Loaded layout from {layout_to_load}") - except Exception as e: - print(f"Failed to load layout from {layout_to_load}: {e}") - cloudlog.exception(f"Error loading layout from {layout_to_load}") - - if route_to_load: - dpg.set_value("route_input", route_to_load) - controller.load_route() - - dpg.show_viewport() - - # Main loop - try: - while dpg.is_dearpygui_running(): - controller.update_frame(default_font) - dpg.render_dearpygui_frame() - finally: - controller.shutdown() - dpg.destroy_context() - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="A tool for visualizing openpilot logs.") - parser.add_argument("--demo", action="store_true", help="Use the demo route instead of providing one") - parser.add_argument("--layout", type=str, help="Path to YAML layout file to load on startup") - parser.add_argument("route", nargs='?', default=None, help="Optional route name to load on startup.") - args = parser.parse_args() - route = DEMO_ROUTE if args.demo else args.route - main(route_to_load=route, layout_to_load=args.layout) diff --git a/tools/jotpluggler/views.py b/tools/jotpluggler/views.py deleted file mode 100644 index cd723d161e9..00000000000 --- a/tools/jotpluggler/views.py +++ /dev/null @@ -1,294 +0,0 @@ -import uuid -import threading -import numpy as np -from collections import deque -import dearpygui.dearpygui as dpg -from abc import ABC, abstractmethod - - -class ViewPanel(ABC): - """Abstract base class for all view panels that can be displayed in a plot container""" - - def __init__(self, panel_id: str | None = None): - self.panel_id = panel_id or str(uuid.uuid4()) - self.title = "Untitled Panel" - - @abstractmethod - def clear(self): - pass - - @abstractmethod - def create_ui(self, parent_tag: str): - pass - - @abstractmethod - def destroy_ui(self): - pass - - @abstractmethod - def get_panel_type(self) -> str: - pass - - @abstractmethod - def update(self): - pass - - @abstractmethod - def to_dict(self) -> dict: - pass - - @classmethod - @abstractmethod - def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager): - pass - - -class TimeSeriesPanel(ViewPanel): - def __init__(self, data_manager, playback_manager, worker_manager, panel_id: str | None = None): - super().__init__(panel_id) - self.data_manager = data_manager - self.playback_manager = playback_manager - self.worker_manager = worker_manager - self.title = "Time Series Plot" - self.plot_tag = f"plot_{self.panel_id}" - self.x_axis_tag = f"{self.plot_tag}_x_axis" - self.y_axis_tag = f"{self.plot_tag}_y_axis" - self.timeline_indicator_tag = f"{self.plot_tag}_timeline" - self._ui_created = False - self._series_data: dict[str, tuple[np.ndarray, np.ndarray]] = {} - self._last_plot_duration = 0 - self._update_lock = threading.RLock() - self._results_deque: deque[tuple[str, list, list]] = deque() - self._new_data = False - self._last_x_limits = (0.0, 0.0) - self._queued_x_sync: tuple | None = None - self._queued_reallow_x_zoom = False - self._total_segments = self.playback_manager.num_segments - - def to_dict(self) -> dict: - return { - "type": "timeseries", - "title": self.title, - "series_paths": list(self._series_data.keys()) - } - - @classmethod - def load_from_dict(cls, data: dict, data_manager, playback_manager, worker_manager): - panel = cls(data_manager, playback_manager, worker_manager) - panel.title = data.get("title", "Time Series Plot") - panel._series_data = {path: (np.array([]), np.array([])) for path in data.get("series_paths", [])} - return panel - - def create_ui(self, parent_tag: str): - self.data_manager.add_observer(self.on_data_loaded) - self.playback_manager.add_x_axis_observer(self._on_x_axis_sync) - with dpg.plot(height=-1, width=-1, tag=self.plot_tag, parent=parent_tag, drop_callback=self._on_series_drop, payload_type="TIMESERIES_PAYLOAD"): - dpg.add_plot_legend() - dpg.add_plot_axis(dpg.mvXAxis, no_label=True, tag=self.x_axis_tag) - dpg.add_plot_axis(dpg.mvYAxis, no_label=True, tag=self.y_axis_tag) - timeline_series_tag = dpg.add_inf_line_series(x=[0], label="Timeline", parent=self.y_axis_tag, tag=self.timeline_indicator_tag) - dpg.bind_item_theme(timeline_series_tag, "timeline_theme") - - self._new_data = True - self._queued_x_sync = self.playback_manager.x_axis_bounds - self._ui_created = True - - def update(self): - with self._update_lock: - if not self._ui_created: - return - - if self._queued_x_sync: - min_time, max_time = self._queued_x_sync - self._queued_x_sync = None - dpg.set_axis_limits(self.x_axis_tag, min_time, max_time) - self._last_x_limits = (min_time, max_time) - self._fit_y_axis(min_time, max_time) - self._queued_reallow_x_zoom = True # must wait a frame before allowing user changes so that axis limits take effect - return - - if self._queued_reallow_x_zoom: - self._queued_reallow_x_zoom = False - if tuple(dpg.get_axis_limits(self.x_axis_tag)) == self._last_x_limits: - dpg.set_axis_limits_auto(self.x_axis_tag) - else: - self._queued_x_sync = self._last_x_limits # retry, likely too early - return - - if self._new_data: # handle new data in main thread - self._new_data = False - if self._total_segments > 0: - dpg.set_axis_limits_constraints(self.x_axis_tag, -10, self._total_segments * 60 + 10) - self._fit_y_axis(*dpg.get_axis_limits(self.x_axis_tag)) - for series_path in list(self._series_data.keys()): - self.add_series(series_path, update=True) - - current_limits = dpg.get_axis_limits(self.x_axis_tag) - # downsample if plot zoom changed significantly - plot_duration = current_limits[1] - current_limits[0] - if plot_duration > self._last_plot_duration * 2 or plot_duration < self._last_plot_duration * 0.5: - self._downsample_all_series(plot_duration) - # sync x-axis if changed by user - if self._last_x_limits != current_limits: - self.playback_manager.set_x_axis_bounds(current_limits[0], current_limits[1], source_panel=self) - self._last_x_limits = current_limits - self._fit_y_axis(current_limits[0], current_limits[1]) - - while self._results_deque: # handle downsampled results in main thread - results = self._results_deque.popleft() - for series_path, downsampled_time, downsampled_values in results: - series_tag = f"series_{self.panel_id}_{series_path}" - if dpg.does_item_exist(series_tag): - dpg.set_value(series_tag, (downsampled_time, downsampled_values.astype(float))) - - # update timeline - current_time_s = self.playback_manager.current_time_s - dpg.set_value(self.timeline_indicator_tag, [[current_time_s], [0]]) - - # update timeseries legend label - for series_path, (time_array, value_array) in self._series_data.items(): - position = np.searchsorted(time_array, current_time_s, side='right') - 1 - if position >= 0 and (current_time_s - time_array[position]) <= 1.0: - value = value_array[position] - formatted_value = f"{value:.5f}" if np.issubdtype(type(value), np.floating) else str(value) - series_tag = f"series_{self.panel_id}_{series_path}" - if dpg.does_item_exist(series_tag): - dpg.configure_item(series_tag, label=f"{series_path}: {formatted_value}") - - def _on_x_axis_sync(self, min_time: float, max_time: float, source_panel): - with self._update_lock: - if source_panel != self: - self._queued_x_sync = (min_time, max_time) - - def _fit_y_axis(self, x_min: float, x_max: float): - if not self._series_data: - dpg.set_axis_limits(self.y_axis_tag, -1, 1) - return - - global_min = float('inf') - global_max = float('-inf') - found_data = False - - for time_array, value_array in self._series_data.values(): - if len(time_array) == 0: - continue - start_idx, end_idx = np.searchsorted(time_array, [x_min, x_max]) - end_idx = min(end_idx, len(time_array) - 1) - if start_idx <= end_idx: - y_slice = value_array[start_idx:end_idx + 1] - series_min, series_max = np.min(y_slice), np.max(y_slice) - global_min = min(global_min, series_min) - global_max = max(global_max, series_max) - found_data = True - - if not found_data: - dpg.set_axis_limits(self.y_axis_tag, -1, 1) - return - - if global_min == global_max: - padding = max(abs(global_min) * 0.1, 1.0) - y_min, y_max = global_min - padding, global_max + padding - else: - range_size = global_max - global_min - padding = range_size * 0.1 - y_min, y_max = global_min - padding, global_max + padding - - dpg.set_axis_limits(self.y_axis_tag, y_min, y_max) - - def _downsample_all_series(self, plot_duration): - plot_width = dpg.get_item_rect_size(self.plot_tag)[0] - if plot_width <= 0 or plot_duration <= 0: - return - - self._last_plot_duration = plot_duration - target_points_per_second = plot_width / plot_duration - work_items = [] - for series_path, (time_array, value_array) in self._series_data.items(): - if len(time_array) == 0: - continue - series_duration = time_array[-1] - time_array[0] if len(time_array) > 1 else 1 - points_per_second = len(time_array) / series_duration - if points_per_second > target_points_per_second * 2: - target_points = max(int(target_points_per_second * series_duration), plot_width) - work_items.append((series_path, time_array, value_array, target_points)) - elif dpg.does_item_exist(f"series_{self.panel_id}_{series_path}"): - dpg.set_value(f"series_{self.panel_id}_{series_path}", (time_array, value_array.astype(float))) - - if work_items: - self.worker_manager.submit_task( - TimeSeriesPanel._downsample_worker, work_items, callback=lambda results: self._results_deque.append(results), task_id=f"downsample_{self.panel_id}" - ) - - def add_series(self, series_path: str, update: bool = False): - with self._update_lock: - if update or series_path not in self._series_data: - self._series_data[series_path] = self.data_manager.get_timeseries(series_path) - - time_array, value_array = self._series_data[series_path] - series_tag = f"series_{self.panel_id}_{series_path}" - if dpg.does_item_exist(series_tag): - dpg.set_value(series_tag, (time_array, value_array.astype(float))) - else: - line_series_tag = dpg.add_line_series(x=time_array, y=value_array.astype(float), label=series_path, parent=self.y_axis_tag, tag=series_tag) - dpg.bind_item_theme(line_series_tag, "line_theme") - self._fit_y_axis(*dpg.get_axis_limits(self.x_axis_tag)) - plot_duration = dpg.get_axis_limits(self.x_axis_tag)[1] - dpg.get_axis_limits(self.x_axis_tag)[0] - self._downsample_all_series(plot_duration) - - def destroy_ui(self): - with self._update_lock: - self.data_manager.remove_observer(self.on_data_loaded) - self.playback_manager.remove_x_axis_observer(self._on_x_axis_sync) - if dpg.does_item_exist(self.plot_tag): - dpg.delete_item(self.plot_tag) - self._ui_created = False - - def get_panel_type(self) -> str: - return "timeseries" - - def clear(self): - with self._update_lock: - for series_path in list(self._series_data.keys()): - self.remove_series(series_path) - - def remove_series(self, series_path: str): - with self._update_lock: - if series_path in self._series_data: - if dpg.does_item_exist(f"series_{self.panel_id}_{series_path}"): - dpg.delete_item(f"series_{self.panel_id}_{series_path}") - del self._series_data[series_path] - - def on_data_loaded(self, data: dict): - with self._update_lock: - self._new_data = True - if data.get('metadata_loaded'): - self._total_segments = data.get('total_segments', 0) - limits = (-10, self._total_segments * 60 + 10) - self._queued_x_sync = limits - - def _on_series_drop(self, sender, app_data, user_data): - self.add_series(app_data) - - @staticmethod - def _downsample_worker(series_path, time_array, value_array, target_points): - if len(time_array) <= target_points: - return series_path, time_array, value_array - - step = len(time_array) / target_points - indices = [] - - for i in range(target_points): - start_idx = int(i * step) - end_idx = int((i + 1) * step) - if start_idx == end_idx: - indices.append(start_idx) - else: - bucket_values = value_array[start_idx:end_idx] - min_idx = start_idx + np.argmin(bucket_values) - max_idx = start_idx + np.argmax(bucket_values) - if min_idx != max_idx: - indices.extend([min(min_idx, max_idx), max(min_idx, max_idx)]) - else: - indices.append(min_idx) - indices = sorted(set(indices)) - return series_path, time_array[indices], value_array[indices] From 0b9ab8bb91f0e8c7f0ca274c67310caaa4a776ed Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 15:51:16 -0800 Subject: [PATCH 439/685] cabana: replace Qt types with stdlib (#37519) * cabana: replace Qt types with stdlib * lil more * cleanup sconscript --- tools/cabana/SConscript | 5 +- tools/cabana/binaryview.cc | 2 +- tools/cabana/cabana.cc | 6 +- tools/cabana/chart/chart.cc | 14 +-- tools/cabana/chart/chartswidget.cc | 6 +- tools/cabana/chart/signalselector.cc | 6 +- tools/cabana/commands.cc | 22 ++--- tools/cabana/commands.h | 12 +-- tools/cabana/dbc/dbc.cc | 30 +++---- tools/cabana/dbc/dbc.h | 62 +++++++------ tools/cabana/dbc/dbcfile.cc | 111 +++++++++++++----------- tools/cabana/dbc/dbcfile.h | 27 +++--- tools/cabana/dbc/dbcmanager.cc | 43 ++++----- tools/cabana/dbc/dbcmanager.h | 24 ++--- tools/cabana/detailwidget.cc | 32 +++---- tools/cabana/historylog.cc | 12 +-- tools/cabana/mainwin.cc | 34 ++++---- tools/cabana/messageswidget.cc | 8 +- tools/cabana/signalview.cc | 58 +++++++------ tools/cabana/signalview.h | 9 +- tools/cabana/streams/abstractstream.h | 6 +- tools/cabana/streams/devicestream.h | 4 +- tools/cabana/streams/pandastream.cc | 8 +- tools/cabana/streams/pandastream.h | 6 +- tools/cabana/streams/replaystream.cc | 16 ++-- tools/cabana/streams/replaystream.h | 6 +- tools/cabana/streams/socketcanstream.cc | 6 +- tools/cabana/streams/socketcanstream.h | 6 +- tools/cabana/tests/test_cabana.cc | 18 ++-- tools/cabana/tools/findsignal.cc | 2 +- tools/cabana/tools/findsimilarbits.cc | 2 +- tools/cabana/utils/export.cc | 2 +- tools/cabana/utils/util.cc | 2 +- tools/cabana/videowidget.cc | 8 +- tools/cabana/videowidget.h | 5 +- 35 files changed, 327 insertions(+), 293 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index cecb5ed8d92..a56ddff7a6b 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -13,7 +13,6 @@ if arch == "Darwin": has_qt = False else: has_qt = shutil.which('qmake') is not None - if not has_qt: Return() @@ -21,7 +20,7 @@ SConscript(['#tools/replay/SConscript']) Import('replay_lib') qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "DBus", "Xml"] +qt_modules = ["Widgets", "Gui", "Core", "Concurrent", "Xml"] qt_libs = [] if arch == "Darwin": @@ -51,7 +50,7 @@ else: qt_env['QT3DIR'] = qt_env['QTDIR'] qt_env.Tool('qt3') -qt_env['CPPPATH'] += qt_dirs + ["#third_party/qrcode"] +qt_env['CPPPATH'] += qt_dirs qt_flags = [ "-D_REENTRANT", "-DQT_NO_DEBUG", diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index b5a68c6b269..90c7c1c3413 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -258,7 +258,7 @@ void BinaryViewModel::refresh() { int pos = sig->is_little_endian ? flipBitPos(sig->start_bit + j) : flipBitPos(sig->start_bit) + j; int idx = column_count * (pos / 8) + pos % 8; if (idx >= items.size()) { - qWarning() << "signal " << sig->name << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; + qWarning() << "signal " << sig->name.c_str() << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; break; } if (j == 0) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index bc50afc03ad..9e4cfc4a51c 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -46,13 +46,13 @@ int main(int argc, char *argv[]) { stream = new DeviceStream(&app, cmd_parser.value("zmq")); } else if (cmd_parser.isSet("panda") || cmd_parser.isSet("panda-serial")) { try { - stream = new PandaStream(&app, {.serial = cmd_parser.value("panda-serial")}); + stream = new PandaStream(&app, {.serial = cmd_parser.value("panda-serial").toStdString()}); } catch (std::exception &e) { qWarning() << e.what(); return 0; } } else if (SocketCanStream::available() && cmd_parser.isSet("socketcan")) { - stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan")}); + stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan").toStdString()}); } else { uint32_t replay_flags = REPLAY_FLAG_NONE; if (cmd_parser.isSet("ecam")) replay_flags |= REPLAY_FLAG_ECAM; @@ -70,7 +70,7 @@ int main(int argc, char *argv[]) { if (!route.isEmpty()) { auto replay_stream = std::make_unique(&app); bool auto_source = cmd_parser.isSet("auto"); - if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"), replay_flags, auto_source)) { + if (!replay_stream->loadRoute(route.toStdString(), cmd_parser.value("data_dir").toStdString(), replay_flags, auto_source)) { return 0; } stream = replay_stream.release(); diff --git a/tools/cabana/chart/chart.cc b/tools/cabana/chart/chart.cc index 14491b1effe..9dfdc595f02 100644 --- a/tools/cabana/chart/chart.cc +++ b/tools/cabana/chart/chart.cc @@ -237,8 +237,8 @@ void ChartView::updateTitle() { for (auto &s : sigs) { auto decoration = s.series->isVisible() ? "none" : "line-through"; s.series->setName(QString("%3 %5 %6") - .arg(decoration, titleColorCss, s.sig->name, - msgColorCss, msgName(s.msg_id), s.msg_id.toString())); + .arg(decoration, titleColorCss, QString::fromStdString(s.sig->name), + msgColorCss, QString::fromStdString(msgName(s.msg_id)), QString::fromStdString(s.msg_id.toString()))); } split_chart_act->setEnabled(sigs.size() > 1); resetChartCache(); @@ -339,13 +339,13 @@ void ChartView::updateAxisY() { double min = std::numeric_limits::max(); double max = std::numeric_limits::lowest(); - QString unit = sigs[0].sig->unit; + QString unit = QString::fromStdString(sigs[0].sig->unit); for (auto &s : sigs) { if (!s.series->isVisible()) continue; // Only show unit when all signals have the same unit - if (unit != s.sig->unit) { + if (unit != QString::fromStdString(s.sig->unit)) { unit.clear(); } @@ -573,11 +573,11 @@ void ChartView::showTip(double sec) { // use reverse iterator to find last item <= sec. auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), sec, [](auto &p, double v) { return p.x() > v; }); if (it != s.vals.crend() && it->x() >= axis_x->min()) { - value = s.sig->formatValue(it->y(), false); + value = QString::fromStdString(s.sig->formatValue(it->y(), false)); s.track_pt = *it; x = std::max(x, chart()->mapToPosition(*it).x()); } - QString name = sigs.size() > 1 ? s.sig->name + ": " : ""; + QString name = sigs.size() > 1 ? QString::fromStdString(s.sig->name) + ": " : ""; QString min = s.min == std::numeric_limits::max() ? "--" : QString::number(s.min); QString max = s.max == std::numeric_limits::lowest() ? "--" : QString::number(s.max); text_list << QString("%2%3 (%4, %5)") @@ -766,7 +766,7 @@ void ChartView::drawSignalValue(QPainter *painter) { for (auto &s : sigs) { auto it = std::lower_bound(s.vals.crbegin(), s.vals.crend(), cur_sec, [](auto &p, double x) { return p.x() > x + EPSILON; }); - QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? s.sig->formatValue(it->y()) : "--"; + QString value = (it != s.vals.crend() && it->x() >= axis_x->min()) ? QString::fromStdString(s.sig->formatValue(it->y())) : "--"; QRectF marker_rect = legend_markers[i++]->sceneBoundingRect(); QRectF value_rect(marker_rect.bottomLeft() - QPoint(0, 1), marker_rect.size()); QString elided_val = painter->fontMetrics().elidedText(value, Qt::ElideRight, value_rect.width()); diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index aba25dcf83b..4e44a560b37 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -327,7 +327,7 @@ QStringList ChartsWidget::serializeChartIds() const { for (auto c : charts) { QStringList ids; for (const auto& s : c->sigs) - ids += QString("%1|%2").arg(s.msg_id.toString(), s.sig->name); + ids += QString("%1|%2").arg(QString::fromStdString(s.msg_id.toString()), QString::fromStdString(s.sig->name)); chart_ids += ids.join(','); } std::reverse(chart_ids.begin(), chart_ids.end()); @@ -340,9 +340,9 @@ void ChartsWidget::restoreChartsFromIds(const QStringList& chart_ids) { for (const auto& part : chart_id.split(',')) { const auto sig_parts = part.split('|'); if (sig_parts.size() != 2) continue; - MessageId msg_id = MessageId::fromString(sig_parts[0]); + MessageId msg_id = MessageId::fromString(sig_parts[0].toStdString()); if (auto* msg = dbc()->msg(msg_id)) - if (auto* sig = msg->sig(sig_parts[1])) + if (auto* sig = msg->sig(sig_parts[1].toStdString())) showChart(msg_id, sig, true, index++ > 0); } } diff --git a/tools/cabana/chart/signalselector.cc b/tools/cabana/chart/signalselector.cc index 63f3a7d5750..168bf824dfa 100644 --- a/tools/cabana/chart/signalselector.cc +++ b/tools/cabana/chart/signalselector.cc @@ -46,7 +46,7 @@ SignalSelector::SignalSelector(QString title, QWidget *parent) : QDialog(parent) for (const auto &[id, _] : can->lastMessages()) { if (auto m = dbc()->msg(id)) { - msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(id.toString()), QVariant::fromValue(id)); + msgs_combo->addItem(QString("%1 (%2)").arg(QString::fromStdString(m->name)).arg(QString::fromStdString(id.toString())), QVariant::fromValue(id)); } } msgs_combo->model()->sort(0); @@ -92,8 +92,8 @@ void SignalSelector::updateAvailableList(int index) { } void SignalSelector::addItemToList(QListWidget *parent, const MessageId id, const cabana::Signal *sig, bool show_msg_name) { - QString text = QString(" %1").arg(sig->color.name(), sig->name); - if (show_msg_name) text += QString(" %0 %1").arg(msgName(id), id.toString()); + QString text = QString(" %1").arg(sig->color.name(), QString::fromStdString(sig->name)); + if (show_msg_name) text += QString(" %0 %1").arg(QString::fromStdString(msgName(id)), QString::fromStdString(id.toString())); QLabel *label = new QLabel(text); label->setContentsMargins(5, 0, 5, 0); diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc index 52861723f41..f158528b51e 100644 --- a/tools/cabana/commands.cc +++ b/tools/cabana/commands.cc @@ -4,22 +4,22 @@ // EditMsgCommand -EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size, - const QString &node, const QString &comment, QUndoCommand *parent) +EditMsgCommand::EditMsgCommand(const MessageId &id, const std::string &name, int size, + const std::string &node, const std::string &comment, QUndoCommand *parent) : id(id), new_name(name), new_size(size), new_node(node), new_comment(comment), QUndoCommand(parent) { if (auto msg = dbc()->msg(id)) { old_name = msg->name; old_size = msg->size; old_node = msg->transmitter; old_comment = msg->comment; - setText(QObject::tr("edit message %1:%2").arg(name).arg(id.address)); + setText(QObject::tr("edit message %1:%2").arg(QString::fromStdString(name)).arg(id.address)); } else { - setText(QObject::tr("new message %1:%2").arg(name).arg(id.address)); + setText(QObject::tr("new message %1:%2").arg(QString::fromStdString(name)).arg(id.address)); } } void EditMsgCommand::undo() { - if (old_name.isEmpty()) + if (old_name.empty()) dbc()->removeMsg(id); else dbc()->updateMsg(id, old_name, old_size, old_node, old_comment); @@ -34,12 +34,12 @@ void EditMsgCommand::redo() { RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) { if (auto msg = dbc()->msg(id)) { message = *msg; - setText(QObject::tr("remove message %1:%2").arg(message.name).arg(id.address)); + setText(QObject::tr("remove message %1:%2").arg(QString::fromStdString(message.name)).arg(id.address)); } } void RemoveMsgCommand::undo() { - if (!message.name.isEmpty()) { + if (!message.name.empty()) { dbc()->updateMsg(id, message.name, message.size, message.transmitter, message.comment); for (auto s : message.getSignals()) dbc()->addSignal(id, *s); @@ -47,7 +47,7 @@ void RemoveMsgCommand::undo() { } void RemoveMsgCommand::redo() { - if (!message.name.isEmpty()) + if (!message.name.empty()) dbc()->removeMsg(id); } @@ -55,7 +55,7 @@ void RemoveMsgCommand::redo() { AddSigCommand::AddSigCommand(const MessageId &id, const cabana::Signal &sig, QUndoCommand *parent) : id(id), signal(sig), QUndoCommand(parent) { - setText(QObject::tr("add signal %1 to %2:%3").arg(sig.name).arg(msgName(id)).arg(id.address)); + setText(QObject::tr("add signal %1 to %2:%3").arg(QString::fromStdString(sig.name)).arg(QString::fromStdString(msgName(id))).arg(id.address)); } void AddSigCommand::undo() { @@ -85,7 +85,7 @@ RemoveSigCommand::RemoveSigCommand(const MessageId &id, const cabana::Signal *si } } } - setText(QObject::tr("remove signal %1 from %2:%3").arg(sig->name).arg(msgName(id)).arg(id.address)); + setText(QObject::tr("remove signal %1 from %2:%3").arg(QString::fromStdString(sig->name)).arg(QString::fromStdString(msgName(id))).arg(id.address)); } void RemoveSigCommand::undo() { for (const auto &s : sigs) dbc()->addSignal(id, s); } @@ -108,7 +108,7 @@ EditSignalCommand::EditSignalCommand(const MessageId &id, const cabana::Signal * } } } - setText(QObject::tr("edit signal %1 in %2:%3").arg(sig->name).arg(msgName(id)).arg(id.address)); + setText(QObject::tr("edit signal %1 in %2:%3").arg(QString::fromStdString(sig->name)).arg(QString::fromStdString(msgName(id))).arg(id.address)); } void EditSignalCommand::undo() { for (const auto &s : sigs) dbc()->updateSignal(id, s.second.name, s.first); } diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h index 0736d9b83f6..4081f869858 100644 --- a/tools/cabana/commands.h +++ b/tools/cabana/commands.h @@ -1,6 +1,8 @@ #pragma once +#include #include +#include #include #include @@ -10,14 +12,14 @@ class EditMsgCommand : public QUndoCommand { public: - EditMsgCommand(const MessageId &id, const QString &name, int size, const QString &node, - const QString &comment, QUndoCommand *parent = nullptr); + EditMsgCommand(const MessageId &id, const std::string &name, int size, const std::string &node, + const std::string &comment, QUndoCommand *parent = nullptr); void undo() override; void redo() override; private: const MessageId id; - QString old_name, new_name, old_comment, new_comment, old_node, new_node; + std::string old_name, new_name, old_comment, new_comment, old_node, new_node; int old_size = 0, new_size = 0; }; @@ -52,7 +54,7 @@ class RemoveSigCommand : public QUndoCommand { private: const MessageId id; - QList sigs; + std::vector sigs; }; class EditSignalCommand : public QUndoCommand { @@ -63,7 +65,7 @@ class EditSignalCommand : public QUndoCommand { private: const MessageId id; - QList> sigs; // QList<{old_sig, new_sig}> + std::vector> sigs; // {old_sig, new_sig} }; namespace UndoStack { diff --git a/tools/cabana/dbc/dbc.cc b/tools/cabana/dbc/dbc.cc index 9b0de922182..8e41cf54e39 100644 --- a/tools/cabana/dbc/dbc.cc +++ b/tools/cabana/dbc/dbc.cc @@ -4,10 +4,6 @@ #include "tools/cabana/utils/util.h" -uint qHash(const MessageId &item) { - return qHash(item.source) ^ qHash(item.address); -} - // cabana::Msg cabana::Msg::~Msg() { @@ -22,7 +18,7 @@ cabana::Signal *cabana::Msg::addSignal(const cabana::Signal &sig) { return s; } -cabana::Signal *cabana::Msg::updateSignal(const QString &sig_name, const cabana::Signal &new_sig) { +cabana::Signal *cabana::Msg::updateSignal(const std::string &sig_name, const cabana::Signal &new_sig) { auto s = sig(sig_name); if (s) { *s = new_sig; @@ -31,7 +27,7 @@ cabana::Signal *cabana::Msg::updateSignal(const QString &sig_name, const cabana: return s; } -void cabana::Msg::removeSignal(const QString &sig_name) { +void cabana::Msg::removeSignal(const std::string &sig_name) { auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s->name == sig_name; }); if (it != sigs.end()) { delete *it; @@ -57,7 +53,7 @@ cabana::Msg &cabana::Msg::operator=(const cabana::Msg &other) { return *this; } -cabana::Signal *cabana::Msg::sig(const QString &sig_name) const { +cabana::Signal *cabana::Msg::sig(const std::string &sig_name) const { auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s->name == sig_name; }); return it != sigs.end() ? *it : nullptr; } @@ -69,17 +65,17 @@ int cabana::Msg::indexOf(const cabana::Signal *sig) const { return -1; } -QString cabana::Msg::newSignalName() { - QString new_name; +std::string cabana::Msg::newSignalName() { + std::string new_name; for (int i = 1; /**/; ++i) { - new_name = QString("NEW_SIGNAL_%1").arg(i); + new_name = "NEW_SIGNAL_" + std::to_string(i); if (sig(new_name) == nullptr) break; } return new_name; } void cabana::Msg::update() { - if (transmitter.isEmpty()) { + if (transmitter.empty()) { transmitter = DEFAULT_NODE_NAME; } mask.assign(size, 0x00); @@ -129,13 +125,13 @@ void cabana::Msg::update() { void cabana::Signal::update() { updateMsbLsb(*this); - if (receiver_name.isEmpty()) { + if (receiver_name.empty()) { receiver_name = DEFAULT_NODE_NAME; } float h = 19 * (float)lsb / 64.0; h = fmod(h, 1.0); - size_t hash = qHash(name); + size_t hash = std::hash{}(name); float s = 0.25 + 0.25 * (float)(hash & 0xff) / 255.0; float v = 0.75 + 0.25 * (float)((hash >> 8) & 0xff) / 255.0; @@ -143,7 +139,7 @@ void cabana::Signal::update() { precision = std::max(num_decimals(factor), num_decimals(offset)); } -QString cabana::Signal::formatValue(double value, bool with_unit) const { +std::string cabana::Signal::formatValue(double value, bool with_unit) const { // Show enum string int64_t raw_value = round((value - offset) / factor); for (const auto &[val, desc] : val_desc) { @@ -152,8 +148,10 @@ QString cabana::Signal::formatValue(double value, bool with_unit) const { } } - QString val_str = QString::number(value, 'f', precision); - if (with_unit && !unit.isEmpty()) { + char buf[64]; + snprintf(buf, sizeof(buf), "%.*f", precision, value); + std::string val_str(buf); + if (with_unit && !unit.empty()) { val_str += " " + unit; } return val_str; diff --git a/tools/cabana/dbc/dbc.h b/tools/cabana/dbc/dbc.h index 134d88a9195..a10e7871fee 100644 --- a/tools/cabana/dbc/dbc.h +++ b/tools/cabana/dbc/dbc.h @@ -1,29 +1,35 @@ #pragma once +#include +#include +#include #include +#include #include #include #include #include -#include -const QString UNTITLED = "untitled"; -const QString DEFAULT_NODE_NAME = "XXX"; +const std::string UNTITLED = "untitled"; +const std::string DEFAULT_NODE_NAME = "XXX"; constexpr int CAN_MAX_DATA_BYTES = 64; struct MessageId { uint8_t source = 0; uint32_t address = 0; - QString toString() const { - return QString("%1:%2").arg(source).arg(QString::number(address, 16).toUpper()); + std::string toString() const { + char buf[64]; + snprintf(buf, sizeof(buf), "%u:%X", source, address); + return buf; } - inline static MessageId fromString(const QString &str) { - auto parts = str.split(':'); - if (parts.size() != 2) return {}; - return MessageId{.source = uint8_t(parts[0].toUInt()), .address = parts[1].toUInt(nullptr, 16)}; + inline static MessageId fromString(const std::string &str) { + auto pos = str.find(':'); + if (pos == std::string::npos) return {}; + return MessageId{.source = uint8_t(std::stoul(str.substr(0, pos))), + .address = uint32_t(std::stoul(str.substr(pos + 1), nullptr, 16))}; } bool operator==(const MessageId &other) const { @@ -43,15 +49,17 @@ struct MessageId { } }; -uint qHash(const MessageId &item); Q_DECLARE_METATYPE(MessageId); template <> struct std::hash { - std::size_t operator()(const MessageId &k) const noexcept { return qHash(k); } + std::size_t operator()(const MessageId &k) const noexcept { + return std::hash{}(k.source) ^ (std::hash{}(k.address) << 1); + } }; -typedef std::vector> ValueDescription; +typedef std::vector> ValueDescription; +Q_DECLARE_METATYPE(ValueDescription); namespace cabana { @@ -61,7 +69,7 @@ class Signal { Signal(const Signal &other) = default; void update(); bool getValue(const uint8_t *data, size_t data_size, double *val) const; - QString formatValue(double value, bool with_unit = true) const; + std::string formatValue(double value, bool with_unit = true) const; bool operator==(const cabana::Signal &other) const; inline bool operator!=(const cabana::Signal &other) const { return !(*this == other); } @@ -72,16 +80,16 @@ class Signal { }; Type type = Type::Normal; - QString name; + std::string name; int start_bit, msb, lsb, size; double factor = 1.0; double offset = 0; bool is_signed; bool is_little_endian; double min, max; - QString unit; - QString comment; - QString receiver_name; + std::string unit; + std::string comment; + std::string receiver_name; ValueDescription val_desc; int precision = 0; QColor color; @@ -97,20 +105,20 @@ class Msg { Msg(const Msg &other) { *this = other; } ~Msg(); cabana::Signal *addSignal(const cabana::Signal &sig); - cabana::Signal *updateSignal(const QString &sig_name, const cabana::Signal &sig); - void removeSignal(const QString &sig_name); + cabana::Signal *updateSignal(const std::string &sig_name, const cabana::Signal &sig); + void removeSignal(const std::string &sig_name); Msg &operator=(const Msg &other); int indexOf(const cabana::Signal *sig) const; - cabana::Signal *sig(const QString &sig_name) const; - QString newSignalName(); + cabana::Signal *sig(const std::string &sig_name) const; + std::string newSignalName(); void update(); inline const std::vector &getSignals() const { return sigs; } uint32_t address; - QString name; + std::string name; uint32_t size; - QString comment; - QString transmitter; + std::string comment; + std::string transmitter; std::vector sigs; std::vector mask; @@ -123,4 +131,8 @@ class Msg { double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig); void updateMsbLsb(cabana::Signal &s); inline int flipBitPos(int start_bit) { return 8 * (start_bit / 8) + 7 - start_bit % 8; } -inline QString doubleToString(double value) { return QString::number(value, 'g', std::numeric_limits::digits10); } +inline std::string doubleToString(double value) { + char buf[64]; + snprintf(buf, sizeof(buf), "%.*g", std::numeric_limits::digits10, value); + return buf; +} diff --git a/tools/cabana/dbc/dbcfile.cc b/tools/cabana/dbc/dbcfile.cc index 1c03c8a0aa7..d9c129ee815 100644 --- a/tools/cabana/dbc/dbcfile.cc +++ b/tools/cabana/dbc/dbcfile.cc @@ -3,11 +3,12 @@ #include #include #include +#include -DBCFile::DBCFile(const QString &dbc_file_name) { - QFile file(dbc_file_name); +DBCFile::DBCFile(const std::string &dbc_file_name) { + QFile file(QString::fromStdString(dbc_file_name)); if (file.open(QIODevice::ReadOnly)) { - name_ = QFileInfo(dbc_file_name).baseName(); + name_ = QFileInfo(QString::fromStdString(dbc_file_name)).baseName().toStdString(); filename = dbc_file_name; parse(file.readAll()); } else { @@ -15,34 +16,35 @@ DBCFile::DBCFile(const QString &dbc_file_name) { } } -DBCFile::DBCFile(const QString &name, const QString &content) : name_(name), filename("") { - parse(content); +DBCFile::DBCFile(const std::string &name, const std::string &content) : name_(name), filename("") { + parse(QString::fromStdString(content)); } bool DBCFile::save() { - assert(!filename.isEmpty()); + assert(!filename.empty()); return writeContents(filename); } -bool DBCFile::saveAs(const QString &new_filename) { +bool DBCFile::saveAs(const std::string &new_filename) { filename = new_filename; return save(); } -bool DBCFile::writeContents(const QString &fn) { - QFile file(fn); +bool DBCFile::writeContents(const std::string &fn) { + QFile file(QString::fromStdString(fn)); if (file.open(QIODevice::WriteOnly)) { - return file.write(generateDBC().toUtf8()) >= 0; + std::string content = generateDBC(); + return file.write(content.c_str(), content.size()) >= 0; } return false; } -void DBCFile::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) { +void DBCFile::updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment) { auto &m = msgs[id.address]; m.address = id.address; m.name = name; m.size = size; - m.transmitter = node.isEmpty() ? DEFAULT_NODE_NAME : node; + m.transmitter = node.empty() ? DEFAULT_NODE_NAME : node; m.comment = comment; } @@ -51,12 +53,12 @@ cabana::Msg *DBCFile::msg(uint32_t address) { return it != msgs.end() ? &it->second : nullptr; } -cabana::Msg *DBCFile::msg(const QString &name) { +cabana::Msg *DBCFile::msg(const std::string &name) { auto it = std::find_if(msgs.begin(), msgs.end(), [&name](auto &m) { return m.second.name == name; }); return it != msgs.end() ? &(it->second) : nullptr; } -cabana::Signal *DBCFile::signal(uint32_t address, const QString &name) { +cabana::Signal *DBCFile::signal(uint32_t address, const std::string &name) { auto m = msg(address); return m ? (cabana::Signal *)m->sig(name) : nullptr; } @@ -93,13 +95,13 @@ void DBCFile::parse(const QString &content) { seen = false; } } catch (std::exception &e) { - throw std::runtime_error(QString("[%1:%2]%3: %4").arg(filename).arg(line_num).arg(e.what()).arg(line).toStdString()); + throw std::runtime_error(QString("[%1:%2]%3: %4").arg(QString::fromStdString(filename)).arg(line_num).arg(e.what()).arg(line).toStdString()); } if (seen) { seen_first = true; } else if (!seen_first) { - header += raw_line + "\n"; + header += raw_line.toStdString() + "\n"; } } @@ -122,9 +124,9 @@ cabana::Msg *DBCFile::parseBO(const QString &line) { // Create a new message object cabana::Msg *msg = &msgs[address]; msg->address = address; - msg->name = match.captured("name"); + msg->name = match.captured("name").toStdString(); msg->size = match.captured("size").toULong(); - msg->transmitter = match.captured("transmitter").trimmed(); + msg->transmitter = match.captured("transmitter").trimmed().toStdString(); return msg; } @@ -141,7 +143,7 @@ void DBCFile::parseCM_BO(const QString &line, const QString &content, const QStr throw std::runtime_error("Invalid message comment format"); if (auto m = (cabana::Msg *)msg(match.captured("address").toUInt())) - m->comment = match.captured("comment").trimmed().replace("\\\"", "\""); + m->comment = match.captured("comment").trimmed().replace("\\\"", "\"").toStdString(); } void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multiplexor_cnt) { @@ -160,7 +162,7 @@ void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multip if (!match.hasMatch()) throw std::runtime_error("Invalid SG_ line format"); - QString name = match.captured(1); + std::string name = match.captured(1).toStdString(); if (current_msg->sig(name) != nullptr) throw std::runtime_error("Duplicate signal name"); @@ -188,8 +190,8 @@ void DBCFile::parseSG(const QString &line, cabana::Msg *current_msg, int &multip s.offset = match.captured(offset + 7).toDouble(); s.min = match.captured(8 + offset).toDouble(); s.max = match.captured(9 + offset).toDouble(); - s.unit = match.captured(10 + offset); - s.receiver_name = match.captured(11 + offset).trimmed(); + s.unit = match.captured(10 + offset).toStdString(); + s.receiver_name = match.captured(11 + offset).trimmed().toStdString(); current_msg->sigs.push_back(new cabana::Signal(s)); } @@ -205,8 +207,8 @@ void DBCFile::parseCM_SG(const QString &line, const QString &content, const QStr if (!match.hasMatch()) throw std::runtime_error("Invalid CM_ SG_ line format"); - if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) { - s->comment = match.captured(3).trimmed().replace("\\\"", "\""); + if (auto s = signal(match.captured(1).toUInt(), match.captured(2).toStdString())) { + s->comment = match.captured(3).trimmed().replace("\\\"", "\"").toStdString(); } } @@ -217,55 +219,60 @@ void DBCFile::parseVAL(const QString &line) { if (!match.hasMatch()) throw std::runtime_error("invalid VAL_ line format"); - if (auto s = signal(match.captured(1).toUInt(), match.captured(2))) { + if (auto s = signal(match.captured(1).toUInt(), match.captured(2).toStdString())) { QStringList desc_list = match.captured(3).trimmed().split('"'); for (int i = 0; i < desc_list.size(); i += 2) { auto val = desc_list[i].trimmed(); if (!val.isEmpty() && (i + 1) < desc_list.size()) { auto desc = desc_list[i + 1].trimmed(); - s->val_desc.push_back({val.toDouble(), desc}); + s->val_desc.push_back({val.toDouble(), desc.toStdString()}); } } } } -QString DBCFile::generateDBC() { - QString dbc_string, comment, val_desc; +std::string DBCFile::generateDBC() { + std::string dbc_string, comment, val_desc; for (const auto &[address, m] : msgs) { - const QString transmitter = m.transmitter.isEmpty() ? DEFAULT_NODE_NAME : m.transmitter; - dbc_string += QString("BO_ %1 %2: %3 %4\n").arg(address).arg(m.name).arg(m.size).arg(transmitter); - if (!m.comment.isEmpty()) { - comment += QString("CM_ BO_ %1 \"%2\";\n").arg(address).arg(QString(m.comment).replace("\"", "\\\"")); + const std::string &transmitter = m.transmitter.empty() ? DEFAULT_NODE_NAME : m.transmitter; + dbc_string += "BO_ " + std::to_string(address) + " " + m.name + ": " + std::to_string(m.size) + " " + transmitter + "\n"; + if (!m.comment.empty()) { + std::string escaped_comment = m.comment; + // Replace " with \" + for (size_t pos = 0; (pos = escaped_comment.find('"', pos)) != std::string::npos; pos += 2) + escaped_comment.replace(pos, 1, "\\\""); + comment += "CM_ BO_ " + std::to_string(address) + " \"" + escaped_comment + "\";\n"; } for (auto sig : m.getSignals()) { - QString multiplexer_indicator; + std::string multiplexer_indicator; if (sig->type == cabana::Signal::Type::Multiplexor) { multiplexer_indicator = "M "; } else if (sig->type == cabana::Signal::Type::Multiplexed) { - multiplexer_indicator = QString("m%1 ").arg(sig->multiplex_value); + multiplexer_indicator = "m" + std::to_string(sig->multiplex_value) + " "; } - dbc_string += QString(" SG_ %1 %2: %3|%4@%5%6 (%7,%8) [%9|%10] \"%11\" %12\n") - .arg(sig->name) - .arg(multiplexer_indicator) - .arg(sig->start_bit) - .arg(sig->size) - .arg(sig->is_little_endian ? '1' : '0') - .arg(sig->is_signed ? '-' : '+') - .arg(doubleToString(sig->factor)) - .arg(doubleToString(sig->offset)) - .arg(doubleToString(sig->min)) - .arg(doubleToString(sig->max)) - .arg(sig->unit) - .arg(sig->receiver_name.isEmpty() ? DEFAULT_NODE_NAME : sig->receiver_name); - if (!sig->comment.isEmpty()) { - comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(QString(sig->comment).replace("\"", "\\\"")); + const std::string &recv = sig->receiver_name.empty() ? DEFAULT_NODE_NAME : sig->receiver_name; + dbc_string += " SG_ " + sig->name + " " + multiplexer_indicator + ": " + + std::to_string(sig->start_bit) + "|" + std::to_string(sig->size) + "@" + + std::string(1, sig->is_little_endian ? '1' : '0') + + std::string(1, sig->is_signed ? '-' : '+') + + " (" + doubleToString(sig->factor) + "," + doubleToString(sig->offset) + ")" + + " [" + doubleToString(sig->min) + "|" + doubleToString(sig->max) + "]" + + " \"" + sig->unit + "\" " + recv + "\n"; + if (!sig->comment.empty()) { + std::string escaped_comment = sig->comment; + for (size_t pos = 0; (pos = escaped_comment.find('"', pos)) != std::string::npos; pos += 2) + escaped_comment.replace(pos, 1, "\\\""); + comment += "CM_ SG_ " + std::to_string(address) + " " + sig->name + " \"" + escaped_comment + "\";\n"; } if (!sig->val_desc.empty()) { - QStringList text; + std::string text; for (auto &[val, desc] : sig->val_desc) { - text << QString("%1 \"%2\"").arg(val).arg(desc); + if (!text.empty()) text += " "; + char val_buf[64]; + snprintf(val_buf, sizeof(val_buf), "%g", val); + text += std::string(val_buf) + " \"" + desc + "\""; } - val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig->name).arg(text.join(" ")); + val_desc += "VAL_ " + std::to_string(address) + " " + sig->name + " " + text + ";\n"; } } dbc_string += "\n"; diff --git a/tools/cabana/dbc/dbcfile.h b/tools/cabana/dbc/dbcfile.h index bd267898f94..decb566abd4 100644 --- a/tools/cabana/dbc/dbcfile.h +++ b/tools/cabana/dbc/dbcfile.h @@ -1,34 +1,35 @@ #pragma once #include +#include #include #include "tools/cabana/dbc/dbc.h" class DBCFile { public: - DBCFile(const QString &dbc_file_name); - DBCFile(const QString &name, const QString &content); + DBCFile(const std::string &dbc_file_name); + DBCFile(const std::string &name, const std::string &content); ~DBCFile() {} bool save(); - bool saveAs(const QString &new_filename); - bool writeContents(const QString &fn); - QString generateDBC(); + bool saveAs(const std::string &new_filename); + bool writeContents(const std::string &fn); + std::string generateDBC(); - void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment); + void updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment); inline void removeMsg(const MessageId &id) { msgs.erase(id.address); } inline const std::map &getMessages() const { return msgs; } cabana::Msg *msg(uint32_t address); - cabana::Msg *msg(const QString &name); + cabana::Msg *msg(const std::string &name); inline cabana::Msg *msg(const MessageId &id) { return msg(id.address); } - cabana::Signal *signal(uint32_t address, const QString &name); + cabana::Signal *signal(uint32_t address, const std::string &name); - inline QString name() const { return name_.isEmpty() ? "untitled" : name_; } - inline bool isEmpty() const { return msgs.empty() && name_.isEmpty(); } + inline std::string name() const { return name_.empty() ? "untitled" : name_; } + inline bool isEmpty() const { return msgs.empty() && name_.empty(); } - QString filename; + std::string filename; private: void parse(const QString &content); @@ -38,7 +39,7 @@ class DBCFile { void parseCM_SG(const QString &line, const QString &content, const QString &raw_line, const QTextStream &stream); void parseVAL(const QString &line); - QString header; + std::string header; std::map msgs; - QString name_; + std::string name_; }; diff --git a/tools/cabana/dbc/dbcmanager.cc b/tools/cabana/dbc/dbcmanager.cc index 8e98d95322c..2236a93da14 100644 --- a/tools/cabana/dbc/dbcmanager.cc +++ b/tools/cabana/dbc/dbcmanager.cc @@ -1,10 +1,9 @@ #include "tools/cabana/dbc/dbcmanager.h" -#include #include -#include +#include -bool DBCManager::open(const SourceSet &sources, const QString &dbc_file_name, QString *error) { +bool DBCManager::open(const SourceSet &sources, const std::string &dbc_file_name, QString *error) { try { auto it = std::find_if(dbc_files.begin(), dbc_files.end(), [&](auto &f) { return f.second && f.second->filename == dbc_file_name; }); @@ -21,7 +20,7 @@ bool DBCManager::open(const SourceSet &sources, const QString &dbc_file_name, QS return true; } -bool DBCManager::open(const SourceSet &sources, const QString &name, const QString &content, QString *error) { +bool DBCManager::open(const SourceSet &sources, const std::string &name, const std::string &content, QString *error) { try { auto file = std::make_shared(name, content); for (auto s : sources) { @@ -64,7 +63,7 @@ void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) { } } -void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) { +void DBCManager::updateSignal(const MessageId &id, const std::string &sig_name, const cabana::Signal &sig) { if (auto m = msg(id)) { if (auto s = m->updateSignal(sig_name, sig)) { emit signalUpdated(s); @@ -73,7 +72,7 @@ void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, cons } } -void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { +void DBCManager::removeSignal(const MessageId &id, const std::string &sig_name) { if (auto m = msg(id)) { if (auto s = m->sig(sig_name)) { emit signalRemoved(s); @@ -83,7 +82,7 @@ void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { } } -void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment) { +void DBCManager::updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment) { auto dbc_file = findDBCFile(id); assert(dbc_file); // This should be impossible dbc_file->updateMsg(id, name, size, node, comment); @@ -98,11 +97,13 @@ void DBCManager::removeMsg(const MessageId &id) { emit maskUpdated(); } -QString DBCManager::newMsgName(const MessageId &id) { - return QString("NEW_MSG_") + QString::number(id.address, 16).toUpper(); +std::string DBCManager::newMsgName(const MessageId &id) { + char buf[64]; + snprintf(buf, sizeof(buf), "NEW_MSG_%X", id.address); + return buf; } -QString DBCManager::newSignalName(const MessageId &id) { +std::string DBCManager::newSignalName(const MessageId &id) { auto m = msg(id); return m ? m->newSignalName() : ""; } @@ -118,14 +119,14 @@ cabana::Msg *DBCManager::msg(const MessageId &id) { return dbc_file ? dbc_file->msg(id) : nullptr; } -cabana::Msg *DBCManager::msg(uint8_t source, const QString &name) { +cabana::Msg *DBCManager::msg(uint8_t source, const std::string &name) { auto dbc_file = findDBCFile(source); return dbc_file ? dbc_file->msg(name) : nullptr; } -QStringList DBCManager::signalNames() { +std::vector DBCManager::signalNames() { // Used for autocompletion - QSet names; + std::set names; for (auto &f : allDBCFiles()) { for (auto &[_, m] : f->getMessages()) { for (auto sig : m.getSignals()) { @@ -133,8 +134,8 @@ QStringList DBCManager::signalNames() { } } } - QStringList ret = names.values(); - ret.sort(); + std::vector ret(names.begin(), names.end()); + std::sort(ret.begin(), ret.end()); return ret; } @@ -165,11 +166,13 @@ const SourceSet DBCManager::sources(const DBCFile *dbc_file) const { return sources; } -QString toString(const SourceSet &ss) { - return std::accumulate(ss.cbegin(), ss.cend(), QString(), [](QString str, int source) { - if (!str.isEmpty()) str += ", "; - return str + (source == -1 ? QStringLiteral("all") : QString::number(source)); - }); +std::string toString(const SourceSet &ss) { + std::string result; + for (int source : ss) { + if (!result.empty()) result += ", "; + result += (source == -1) ? "all" : std::to_string(source); + } + return result; } DBCManager *dbc() { diff --git a/tools/cabana/dbc/dbcmanager.h b/tools/cabana/dbc/dbcmanager.h index 5f183752d29..4a122073eaa 100644 --- a/tools/cabana/dbc/dbcmanager.h +++ b/tools/cabana/dbc/dbcmanager.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "tools/cabana/dbc/dbcfile.h" @@ -18,27 +20,27 @@ class DBCManager : public QObject { public: DBCManager(QObject *parent) : QObject(parent) {} ~DBCManager() {} - bool open(const SourceSet &sources, const QString &dbc_file_name, QString *error = nullptr); - bool open(const SourceSet &sources, const QString &name, const QString &content, QString *error = nullptr); + bool open(const SourceSet &sources, const std::string &dbc_file_name, QString *error = nullptr); + bool open(const SourceSet &sources, const std::string &name, const std::string &content, QString *error = nullptr); void close(const SourceSet &sources); void close(DBCFile *dbc_file); void closeAll(); void addSignal(const MessageId &id, const cabana::Signal &sig); - void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); - void removeSignal(const MessageId &id, const QString &sig_name); + void updateSignal(const MessageId &id, const std::string &sig_name, const cabana::Signal &sig); + void removeSignal(const MessageId &id, const std::string &sig_name); - void updateMsg(const MessageId &id, const QString &name, uint32_t size, const QString &node, const QString &comment); + void updateMsg(const MessageId &id, const std::string &name, uint32_t size, const std::string &node, const std::string &comment); void removeMsg(const MessageId &id); - QString newMsgName(const MessageId &id); - QString newSignalName(const MessageId &id); + std::string newMsgName(const MessageId &id); + std::string newSignalName(const MessageId &id); const std::map &getMessages(uint8_t source); cabana::Msg *msg(const MessageId &id); - cabana::Msg* msg(uint8_t source, const QString &name); + cabana::Msg* msg(uint8_t source, const std::string &name); - QStringList signalNames(); + std::vector signalNames(); inline int dbcCount() { return allDBCFiles().size(); } int nonEmptyDBCCount(); @@ -62,8 +64,8 @@ class DBCManager : public QObject { DBCManager *dbc(); -QString toString(const SourceSet &ss); -inline QString msgName(const MessageId &id) { +std::string toString(const SourceSet &ss); +inline std::string msgName(const MessageId &id) { auto msg = dbc()->msg(id); return msg ? msg->name : UNTITLED; } diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 35492c8efae..148b059e5b7 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -124,9 +124,9 @@ int DetailWidget::findOrAddTab(const MessageId& message_id) { if (tabbar->tabData(index).value() == message_id) break; } if (index == -1) { - index = tabbar->addTab(message_id.toString()); + index = tabbar->addTab(QString::fromStdString(message_id.toString())); tabbar->setTabData(index, QVariant::fromValue(message_id)); - tabbar->setTabToolTip(index, msgName(message_id)); + tabbar->setTabToolTip(index, QString::fromStdString(msgName(message_id))); } return index; } @@ -151,21 +151,21 @@ std::pair DetailWidget::serializeMessageIds() const { QStringList msgs; for (int i = 0; i < tabbar->count(); ++i) { MessageId id = tabbar->tabData(i).value(); - msgs.append(id.toString()); + msgs.append(QString::fromStdString(id.toString())); } - return std::make_pair(msg_id.toString(), msgs); + return std::make_pair(QString::fromStdString(msg_id.toString()), msgs); } void DetailWidget::restoreTabs(const QString active_msg_id, const QStringList& msg_ids) { tabbar->blockSignals(true); for (const auto& str_id : msg_ids) { - MessageId id = MessageId::fromString(str_id); + MessageId id = MessageId::fromString(str_id.toStdString()); if (dbc()->msg(id) != nullptr) findOrAddTab(id); } tabbar->blockSignals(false); - auto active_id = MessageId::fromString(active_msg_id); + auto active_id = MessageId::fromString(active_msg_id.toStdString()); if (dbc()->msg(active_id) != nullptr) setMessage(active_id); } @@ -180,10 +180,10 @@ void DetailWidget::refresh() { warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); } for (auto s : binary_view->getOverlappingSignals()) { - warnings.push_back(tr("%1 has overlapping bits.").arg(s->name)); + warnings.push_back(tr("%1 has overlapping bits.").arg(QString::fromStdString(s->name))); } } - QString msg_name = msg ? QString("%1 (%2)").arg(msg->name, msg->transmitter) : msgName(msg_id); + QString msg_name = msg ? QString("%1 (%2)").arg(QString::fromStdString(msg->name), QString::fromStdString(msg->transmitter)) : QString::fromStdString(msgName(msg_id)); name_label->setText(msg_name); name_label->setToolTip(msg_name); action_remove_msg->setEnabled(msg != nullptr); @@ -208,10 +208,10 @@ void DetailWidget::updateState(const std::set *msgs) { void DetailWidget::editMsg() { auto msg = dbc()->msg(msg_id); int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); - EditMessageDialog dlg(msg_id, msgName(msg_id), size, this); + EditMessageDialog dlg(msg_id, QString::fromStdString(msgName(msg_id)), size, this); if (dlg.exec()) { - UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed(), dlg.size_spin->value(), - dlg.node->text().trimmed(), dlg.comment_edit->toPlainText().trimmed())); + UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text().trimmed().toStdString(), dlg.size_spin->value(), + dlg.node->text().trimmed().toStdString(), dlg.comment_edit->toPlainText().trimmed().toStdString())); } } @@ -223,7 +223,7 @@ void DetailWidget::removeMsg() { EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent) : original_name(title), msg_id(msg_id), QDialog(parent) { - setWindowTitle(tr("Edit message: %1").arg(msg_id.toString())); + setWindowTitle(tr("Edit message: %1").arg(QString::fromStdString(msg_id.toString()))); QFormLayout *form_layout = new QFormLayout(this); form_layout->addRow("", error_label = new QLabel); @@ -241,8 +241,8 @@ EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &tit form_layout->addRow(btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)); if (auto msg = dbc()->msg(msg_id)) { - node->setText(msg->transmitter); - comment_edit->setText(msg->comment); + node->setText(QString::fromStdString(msg->transmitter)); + comment_edit->setText(QString::fromStdString(msg->comment)); } validateName(name_edit->text()); setFixedWidth(parent->width() * 0.9); @@ -252,10 +252,10 @@ EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &tit } void EditMessageDialog::validateName(const QString &text) { - bool valid = text.compare(UNTITLED, Qt::CaseInsensitive) != 0; + bool valid = text.compare(QString::fromStdString(UNTITLED), Qt::CaseInsensitive) != 0; error_label->setVisible(false); if (!text.isEmpty() && valid && text != original_name) { - valid = dbc()->msg(msg_id.source, text) == nullptr; + valid = dbc()->msg(msg_id.source, text.toStdString()) == nullptr; if (!valid) { error_label->setText(tr("Name already exists")); error_label->setVisible(true); diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index 3dbdf5a7cd1..fb79ff9cea1 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -14,7 +14,7 @@ QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { const int col = index.column(); if (role == Qt::DisplayRole) { if (col == 0) return QString::number(can->toSeconds(m.mono_time), 'f', 3); - if (!isHexMode()) return sigs[col - 1]->formatValue(m.sig_values[col - 1], false); + if (!isHexMode()) return QString::fromStdString(sigs[col - 1]->formatValue(m.sig_values[col - 1], false)); } else if (role == Qt::TextAlignmentRole) { return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter); } @@ -49,8 +49,8 @@ QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, i if (section == 0) return "Time"; if (isHexMode()) return "Data"; - QString name = sigs[section - 1]->name; - QString unit = sigs[section - 1]->unit; + QString name = QString::fromStdString(sigs[section - 1]->name); + QString unit = QString::fromStdString(sigs[section - 1]->unit); return unit.isEmpty() ? name : QString("%1 (%2)").arg(name, unit); } else if (role == Qt::BackgroundRole && section > 0 && !isHexMode()) { // Alpha-blend the signal color with the background to ensure contrast @@ -216,7 +216,7 @@ LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { void LogsWidget::modelReset() { signals_cb->clear(); for (auto s : model->sigs) { - signals_cb->addItem(s->name); + signals_cb->addItem(QString::fromStdString(s->name)); } export_btn->setEnabled(false); value_edit->clear(); @@ -238,8 +238,8 @@ void LogsWidget::filterChanged() { } void LogsWidget::exportToCSV() { - QString dir = QString("%1/%2_%3.csv").arg(settings.last_dir).arg(can->routeName()).arg(msgName(model->msg_id)); - QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(msgName(model->msg_id)), + QString dir = QString("%1/%2_%3.csv").arg(settings.last_dir).arg(QString::fromStdString(can->routeName())).arg(QString::fromStdString(msgName(model->msg_id))); + QString fn = QFileDialog::getSaveFileName(this, QString("Export %1 to CSV file").arg(QString::fromStdString(msgName(model->msg_id))), dir, tr("csv (*.csv)")); if (!fn.isEmpty()) { model->isHexMode() ? utils::exportToCSV(fn, model->msg_id) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index a7040f891e1..39fb979c798 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -234,7 +234,7 @@ void MainWindow::DBCFileChanged() { QStringList title; for (auto f : dbc()->allDBCFiles()) { - title.push_back(tr("(%1) %2").arg(toString(dbc()->sources(f)), f->name())); + title.push_back(tr("(%1) %2").arg(QString::fromStdString(toString(dbc()->sources(f))), QString::fromStdString(f->name()))); } setWindowFilePath(title.join(" | ")); @@ -259,7 +259,7 @@ void MainWindow::closeStream() { } void MainWindow::exportToCSV() { - QString dir = QString("%1/%2.csv").arg(settings.last_dir).arg(can->routeName()); + QString dir = QString("%1/%2.csv").arg(settings.last_dir).arg(QString::fromStdString(can->routeName())); QString fn = QFileDialog::getSaveFileName(this, "Export stream to CSV file", dir, tr("csv (*.csv)")); if (!fn.isEmpty()) { utils::exportToCSV(fn); @@ -268,7 +268,7 @@ void MainWindow::exportToCSV() { void MainWindow::newFile(SourceSet s) { closeFile(s); - dbc()->open(s, "", ""); + dbc()->open(s, std::string(""), std::string("")); } void MainWindow::openFile(SourceSet s) { @@ -284,7 +284,7 @@ void MainWindow::loadFile(const QString &fn, SourceSet s) { closeFile(s); QString error; - if (dbc()->open(s, fn, &error)) { + if (dbc()->open(s, fn.toStdString(), &error)) { updateRecentFiles(fn); statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000); } else { @@ -304,7 +304,7 @@ void MainWindow::loadFromClipboard(SourceSet s, bool close_all) { QString dbc_str = QGuiApplication::clipboard()->text(); QString error; - bool ret = dbc()->open(s, "", dbc_str, &error); + bool ret = dbc()->open(s, std::string(""), dbc_str.toStdString(), &error); if (ret && dbc()->nonEmptyDBCCount() > 0) { QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!")); } else { @@ -333,7 +333,7 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { can->start(); loadFile(dbc_file); - statusBar()->showMessage(tr("Stream [%1] started").arg(can->routeName()), 2000); + statusBar()->showMessage(tr("Stream [%1] started").arg(QString::fromStdString(can->routeName())), 2000); bool has_stream = dynamic_cast(can) == nullptr; close_stream_act->setEnabled(has_stream); @@ -341,7 +341,7 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { tools_menu->setEnabled(has_stream); createDockWidgets(); - video_dock->setWindowTitle(can->routeName()); + video_dock->setWindowTitle(QString::fromStdString(can->routeName())); if (can->liveStreaming() || video_splitter->sizes()[0] == 0) { // display video at minimum size. video_splitter->setSizes({1, 1}); @@ -368,9 +368,9 @@ void MainWindow::startStream(AbstractStream *stream, QString dbc_file) { } void MainWindow::eventsMerged() { - if (!can->liveStreaming() && std::exchange(car_fingerprint, can->carFingerprint()) != car_fingerprint) { + if (!can->liveStreaming() && std::exchange(car_fingerprint, QString::fromStdString(can->carFingerprint())) != car_fingerprint) { video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPRINT: %2") - .arg(can->routeName()) + .arg(QString::fromStdString(can->routeName())) .arg(car_fingerprint.isEmpty() ? tr("Unknown Car") : car_fingerprint)); // Don't overwrite already loaded DBC if (!dbc()->nonEmptyDBCCount() && fingerprint_to_dbc.object().contains(car_fingerprint)) { @@ -416,7 +416,7 @@ void MainWindow::closeFile(DBCFile *dbc_file) { void MainWindow::saveFile(DBCFile *dbc_file) { assert(dbc_file != nullptr); - if (!dbc_file->filename.isEmpty()) { + if (!dbc_file->filename.empty()) { dbc_file->save(); UndoStack::instance()->setClean(); statusBar()->showMessage(tr("File saved"), 2000); @@ -426,10 +426,10 @@ void MainWindow::saveFile(DBCFile *dbc_file) { } void MainWindow::saveFileAs(DBCFile *dbc_file) { - QString title = tr("Save File (bus: %1)").arg(toString(dbc()->sources(dbc_file))); + QString title = tr("Save File (bus: %1)").arg(QString::fromStdString(toString(dbc()->sources(dbc_file)))); QString fn = QFileDialog::getSaveFileName(this, title, QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)")); if (!fn.isEmpty()) { - dbc_file->saveAs(fn); + dbc_file->saveAs(fn.toStdString()); UndoStack::instance()->setClean(); statusBar()->showMessage(tr("File saved as %1").arg(fn), 2000); updateRecentFiles(fn); @@ -446,7 +446,7 @@ void MainWindow::saveToClipboard() { void MainWindow::saveFileToClipboard(DBCFile *dbc_file) { assert(dbc_file != nullptr); - QGuiApplication::clipboard()->setText(dbc_file->generateDBC()); + QGuiApplication::clipboard()->setText(QString::fromStdString(dbc_file->generateDBC())); QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!")); } @@ -467,14 +467,14 @@ void MainWindow::updateLoadSaveMenus() { auto dbc_file = dbc()->findDBCFile(source); if (dbc_file) { bus_menu->addSeparator(); - bus_menu->addAction(dbc_file->name() + " (" + toString(dbc()->sources(dbc_file)) + ")")->setEnabled(false); + bus_menu->addAction(QString::fromStdString(dbc_file->name()) + " (" + QString::fromStdString(toString(dbc()->sources(dbc_file))) + ")")->setEnabled(false); bus_menu->addAction(tr("Save..."), [=]() { saveFile(dbc_file); }); bus_menu->addAction(tr("Save As..."), [=]() { saveFileAs(dbc_file); }); bus_menu->addAction(tr("Copy to Clipboard..."), [=]() { saveFileToClipboard(dbc_file); }); bus_menu->addAction(tr("Remove from this bus..."), [=]() { closeFile(ss); }); bus_menu->addAction(tr("Remove from all buses..."), [=]() { closeFile(dbc_file); }); } - bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(dbc_file ? dbc_file->name() : "No DBCs loaded")); + bus_menu->setTitle(tr("Bus %1 (%2)").arg(source).arg(dbc_file ? QString::fromStdString(dbc_file->name()) : "No DBCs loaded")); manage_dbcs_menu->addMenu(bus_menu); } @@ -627,7 +627,7 @@ void MainWindow::saveSessionState() { settings.active_charts.clear(); for (auto &f : dbc()->allDBCFiles()) - if (!f->isEmpty()) { settings.recent_dbc_file = f->filename; break; } + if (!f->isEmpty()) { settings.recent_dbc_file = QString::fromStdString(f->filename); break; } if (auto *detail = center_widget->getDetailWidget()) { auto [active_id, ids] = detail->serializeMessageIds(); @@ -643,7 +643,7 @@ void MainWindow::restoreSessionState() { QString dbc_file; for (auto& f : dbc()->allDBCFiles()) - if (!f->isEmpty()) { dbc_file = f->filename; break; } + if (!f->isEmpty()) { dbc_file = QString::fromStdString(f->filename); break; } if (dbc_file != settings.recent_dbc_file) return; if (!settings.selected_msg_ids.isEmpty()) diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index ed9aeaf3114..ec8c82dd98f 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -205,7 +205,7 @@ QVariant MessageListModel::data(const QModelIndex &index, int role) const { } else if (role == Qt::ToolTipRole && index.column() == Column::NAME) { auto msg = dbc()->msg(item.id); auto tooltip = item.name; - if (msg && !msg->comment.isEmpty()) tooltip += "
" + msg->comment + ""; + if (msg && !msg->comment.empty()) tooltip += "
" + QString::fromStdString(msg->comment) + ""; return tooltip; } return {}; @@ -277,7 +277,7 @@ bool MessageListModel::match(const MessageListModel::Item &item) { if (!match) { const auto m = dbc()->msg(item.id); match = m && std::any_of(m->sigs.cbegin(), m->sigs.cend(), - [&txt](const auto &s) { return s->name.contains(txt, Qt::CaseInsensitive); }); + [&txt](const auto &s) { return QString::fromStdString(s->name).contains(txt, Qt::CaseInsensitive); }); } break; } @@ -323,8 +323,8 @@ bool MessageListModel::filterAndSort() { if (show_inactive_messages || can->isMessageActive(id)) { auto msg = dbc()->msg(id); Item item = {.id = id, - .name = msg ? msg->name : UNTITLED, - .node = msg ? msg->transmitter : QString()}; + .name = msg ? QString::fromStdString(msg->name) : QString::fromStdString(UNTITLED), + .node = msg ? QString::fromStdString(msg->transmitter) : QString()}; if (match(item)) items.emplace_back(item); } diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index a9ceacd8061..df05d287f49 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -34,8 +34,8 @@ SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(p } void SignalModel::insertItem(SignalModel::Item *root_item, int pos, const cabana::Signal *sig) { - Item *parent_item = new Item{.type = Item::Sig, .parent = root_item, .sig = sig, .title = sig->name}; - root_item->children.insert(pos, parent_item); + Item *parent_item = new Item{.type = Item::Sig, .parent = root_item, .sig = sig, .title = QString::fromStdString(sig->name)}; + root_item->children.insert(root_item->children.begin() + pos, parent_item); QString titles[]{"Name", "Size", "Receiver Nodes", "Little Endian", "Signed", "Offset", "Factor", "Type", "Multiplex Value", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Table"}; for (int i = 0; i < std::size(titles); ++i) { @@ -63,7 +63,7 @@ void SignalModel::refresh() { root.reset(new SignalModel::Item); if (auto msg = dbc()->msg(msg_id)) { for (auto s : msg->getSignals()) { - if (filter_str.isEmpty() || s->name.contains(filter_str, Qt::CaseInsensitive)) { + if (filter_str.isEmpty() || QString::fromStdString(s->name).contains(filter_str, Qt::CaseInsensitive)) { insertItem(root.get(), root->children.size(), s); } } @@ -124,25 +124,25 @@ QVariant SignalModel::data(const QModelIndex &index, int role) const { const Item *item = getItem(index); if (role == Qt::DisplayRole || role == Qt::EditRole) { if (index.column() == 0) { - return item->type == Item::Sig ? item->sig->name : item->title; + return item->type == Item::Sig ? QString::fromStdString(item->sig->name) : item->title; } else { switch (item->type) { case Item::Sig: return item->sig_val; - case Item::Name: return item->sig->name; + case Item::Name: return QString::fromStdString(item->sig->name); case Item::Size: return item->sig->size; - case Item::Node: return item->sig->receiver_name; + case Item::Node: return QString::fromStdString(item->sig->receiver_name); case Item::SignalType: return signalTypeToString(item->sig->type); case Item::MultiplexValue: return item->sig->multiplex_value; - case Item::Offset: return doubleToString(item->sig->offset); - case Item::Factor: return doubleToString(item->sig->factor); - case Item::Unit: return item->sig->unit; - case Item::Comment: return item->sig->comment; - case Item::Min: return doubleToString(item->sig->min); - case Item::Max: return doubleToString(item->sig->max); + case Item::Offset: return QString::fromStdString(doubleToString(item->sig->offset)); + case Item::Factor: return QString::fromStdString(doubleToString(item->sig->factor)); + case Item::Unit: return QString::fromStdString(item->sig->unit); + case Item::Comment: return QString::fromStdString(item->sig->comment); + case Item::Min: return QString::fromStdString(doubleToString(item->sig->min)); + case Item::Max: return QString::fromStdString(doubleToString(item->sig->max)); case Item::Desc: { QStringList val_desc; for (auto &[val, desc] : item->sig->val_desc) { - val_desc << QString("%1 \"%2\"").arg(val).arg(desc); + val_desc << QString("%1 \"%2\"").arg(val).arg(QString::fromStdString(desc)); } return val_desc.join(" "); } @@ -165,17 +165,17 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r Item *item = getItem(index); cabana::Signal s = *item->sig; switch (item->type) { - case Item::Name: s.name = value.toString(); break; + case Item::Name: s.name = value.toString().toStdString(); break; case Item::Size: s.size = value.toInt(); break; - case Item::Node: s.receiver_name = value.toString().trimmed(); break; + case Item::Node: s.receiver_name = value.toString().trimmed().toStdString(); break; case Item::SignalType: s.type = (cabana::Signal::Type)value.toInt(); break; case Item::MultiplexValue: s.multiplex_value = value.toInt(); break; case Item::Endian: s.is_little_endian = value.toBool(); break; case Item::Signed: s.is_signed = value.toBool(); break; case Item::Offset: s.offset = value.toDouble(); break; case Item::Factor: s.factor = value.toDouble(); break; - case Item::Unit: s.unit = value.toString(); break; - case Item::Comment: s.comment = value.toString(); break; + case Item::Unit: s.unit = value.toString().toStdString(); break; + case Item::Comment: s.comment = value.toString().toStdString(); break; case Item::Min: s.min = value.toDouble(); break; case Item::Max: s.max = value.toDouble(); break; case Item::Desc: s.val_desc = value.value(); break; @@ -189,7 +189,7 @@ bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int r bool SignalModel::saveSignal(const cabana::Signal *origin_s, cabana::Signal &s) { auto msg = dbc()->msg(msg_id); if (s.name != origin_s->name && msg->sig(s.name) != nullptr) { - QString text = tr("There is already a signal with the same name '%1'").arg(s.name); + QString text = tr("There is already a signal with the same name '%1'").arg(QString::fromStdString(s.name)); QMessageBox::warning(nullptr, tr("Failed to save signal"), text); return false; } @@ -214,7 +214,7 @@ void SignalModel::handleSignalAdded(MessageId id, const cabana::Signal *sig) { beginInsertRows({}, i, i); insertItem(root.get(), i, sig); endInsertRows(); - } else if (sig->name.contains(filter_str, Qt::CaseInsensitive)) { + } else if (QString::fromStdString(sig->name).contains(filter_str, Qt::CaseInsensitive)) { refresh(); } } @@ -229,7 +229,9 @@ void SignalModel::handleSignalUpdated(const cabana::Signal *sig) { int to = dbc()->msg(msg_id)->indexOf(sig); if (to != row) { beginMoveRows({}, row, row, {}, to > row ? to + 1 : to); - root->children.move(row, to); + auto item = root->children[row]; + root->children.erase(root->children.begin() + row); + root->children.insert(root->children.begin() + to, item); endMoveRows(); } } @@ -239,7 +241,8 @@ void SignalModel::handleSignalUpdated(const cabana::Signal *sig) { void SignalModel::handleSignalRemoved(const cabana::Signal *sig) { if (int row = signalRow(sig); row != -1) { beginRemoveRows({}, row, row); - delete root->children.takeAt(row); + delete root->children[row]; + root->children.erase(root->children.begin() + row); endRemoveRows(); } } @@ -373,7 +376,10 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie else e->setValidator(double_validator); if (item->type == SignalModel::Item::Name) { - QCompleter *completer = new QCompleter(dbc()->signalNames(), e); + auto names = dbc()->signalNames(); + QStringList qnames; + for (const auto &n : names) qnames.push_back(QString::fromStdString(n)); + QCompleter *completer = new QCompleter(qnames, e); completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setFilterMode(Qt::MatchContains); e->setCompleter(completer); @@ -395,7 +401,7 @@ QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionVie return c; } else if (item->type == SignalModel::Item::Desc) { ValueDescriptionDlg dlg(item->sig->val_desc, parent); - dlg.setWindowTitle(item->sig->name); + dlg.setWindowTitle(QString::fromStdString(item->sig->name)); if (dlg.exec()) { ((QAbstractItemModel *)index.model())->setData(index, QVariant::fromValue(dlg.val_desc)); } @@ -621,7 +627,7 @@ void SignalView::updateState(const std::set *msgs) { for (auto item : model->root->children) { double value = 0; if (item->sig->getValue(last_msg.dat.data(), last_msg.dat.size(), &value)) { - item->sig_val = item->sig->formatValue(value); + item->sig_val = QString::fromStdString(item->sig->formatValue(value)); max_value_width = std::max(max_value_width, fontMetrics().horizontalAdvance(item->sig_val)); } } @@ -677,7 +683,7 @@ ValueDescriptionDlg::ValueDescriptionDlg(const ValueDescription &descriptions, Q int row = 0; for (auto &[val, desc] : descriptions) { table->setItem(row, 0, new QTableWidgetItem(QString::number(val))); - table->setItem(row, 1, new QTableWidgetItem(desc)); + table->setItem(row, 1, new QTableWidgetItem(QString::fromStdString(desc))); ++row; } @@ -706,7 +712,7 @@ void ValueDescriptionDlg::save() { QString val = table->item(i, 0)->text().trimmed(); QString desc = table->item(i, 1)->text().trimmed(); if (!val.isEmpty() && !desc.isEmpty()) { - val_desc.push_back({val.toDouble(), desc}); + val_desc.push_back({val.toDouble(), desc.toStdString()}); } } QDialog::accept(); diff --git a/tools/cabana/signalview.h b/tools/cabana/signalview.h index 4e746ea105b..42db830df7f 100644 --- a/tools/cabana/signalview.h +++ b/tools/cabana/signalview.h @@ -20,12 +20,15 @@ class SignalModel : public QAbstractItemModel { public: struct Item { enum Type {Root, Sig, Name, Size, Node, Endian, Signed, Offset, Factor, SignalType, MultiplexValue, ExtraInfo, Unit, Comment, Min, Max, Desc }; - ~Item() { qDeleteAll(children); } - inline int row() { return parent->children.indexOf(this); } + ~Item() { for (auto c : children) delete c; } + inline int row() { + auto it = std::find(parent->children.begin(), parent->children.end(), this); + return it != parent->children.end() ? std::distance(parent->children.begin(), it) : -1; + } Type type = Type::Root; Item *parent = nullptr; - QList children; + std::vector children; const cabana::Signal *sig = nullptr; QString title; diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h index f35b19d34f7..7d66a420dc8 100644 --- a/tools/cabana/streams/abstractstream.h +++ b/tools/cabana/streams/abstractstream.h @@ -65,8 +65,8 @@ class AbstractStream : public QObject { virtual void start() = 0; virtual bool liveStreaming() const { return true; } virtual void seekTo(double ts) {} - virtual QString routeName() const = 0; - virtual QString carFingerprint() const { return ""; } + virtual std::string routeName() const = 0; + virtual std::string carFingerprint() const { return ""; } virtual QDateTime beginDateTime() const { return {}; } virtual uint64_t beginMonoTime() const { return 0; } virtual double minSeconds() const { return 0; } @@ -149,7 +149,7 @@ class DummyStream : public AbstractStream { Q_OBJECT public: DummyStream(QObject *parent) : AbstractStream(parent) {} - QString routeName() const override { return tr("No Stream"); } + std::string routeName() const override { return "No Stream"; } void start() override {} }; diff --git a/tools/cabana/streams/devicestream.h b/tools/cabana/streams/devicestream.h index 6beb300d7ad..4bcdb5351d0 100644 --- a/tools/cabana/streams/devicestream.h +++ b/tools/cabana/streams/devicestream.h @@ -9,8 +9,8 @@ class DeviceStream : public LiveStream { public: DeviceStream(QObject *parent, QString address = {}); ~DeviceStream(); - inline QString routeName() const override { - return QString("Live Streaming From %1").arg(zmq_address.isEmpty() ? "127.0.0.1" : zmq_address); + inline std::string routeName() const override { + return "Live Streaming From " + (zmq_address.isEmpty() ? std::string("127.0.0.1") : zmq_address.toStdString()); } protected: diff --git a/tools/cabana/streams/pandastream.cc b/tools/cabana/streams/pandastream.cc index a2430c665f1..3692f71a11c 100644 --- a/tools/cabana/streams/pandastream.cc +++ b/tools/cabana/streams/pandastream.cc @@ -16,8 +16,8 @@ PandaStream::PandaStream(QObject *parent, PandaStreamConfig config_) : config(co bool PandaStream::connect() { try { - qDebug() << "Connecting to panda " << config.serial; - panda.reset(new Panda(config.serial.toStdString())); + qDebug() << "Connecting to panda " << config.serial.c_str(); + panda.reset(new Panda(config.serial)); config.bus_config.resize(3); qDebug() << "Connected"; } catch (const std::exception& e) { @@ -81,7 +81,7 @@ void PandaStream::streamThread() { OpenPandaWidget::OpenPandaWidget(QWidget *parent) : AbstractOpenStreamWidget(parent) { form_layout = new QFormLayout(this); if (can && dynamic_cast(can) != nullptr) { - form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(can->routeName()))); + form_layout->addWidget(new QLabel(tr("Already connected to %1.").arg(QString::fromStdString(can->routeName())))); form_layout->addWidget(new QLabel("Close the current connection via [File menu -> Close Stream] before connecting to another Panda.")); QTimer::singleShot(0, [this]() { emit enableOpenButton(false); }); return; @@ -129,7 +129,7 @@ void OpenPandaWidget::buildConfigForm() { } if (has_panda) { - config.serial = serial; + config.serial = serial.toStdString(); config.bus_config.resize(3); for (int i = 0; i < config.bus_config.size(); i++) { QHBoxLayout *bus_layout = new QHBoxLayout; diff --git a/tools/cabana/streams/pandastream.h b/tools/cabana/streams/pandastream.h index e17ad887fc3..f8847f65e5e 100644 --- a/tools/cabana/streams/pandastream.h +++ b/tools/cabana/streams/pandastream.h @@ -19,7 +19,7 @@ struct BusConfig { }; struct PandaStreamConfig { - QString serial = ""; + std::string serial = ""; std::vector bus_config; }; @@ -28,8 +28,8 @@ class PandaStream : public LiveStream { public: PandaStream(QObject *parent, PandaStreamConfig config_ = {}); ~PandaStream() { stop(); } - inline QString routeName() const override { - return QString("Panda: %1").arg(config.serial); + inline std::string routeName() const override { + return "Panda: " + config.serial; } protected: diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc index b8cf1be2992..f42bf2601ca 100644 --- a/tools/cabana/streams/replaystream.cc +++ b/tools/cabana/streams/replaystream.cc @@ -46,9 +46,9 @@ void ReplayStream::mergeSegments() { } } -bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags, bool auto_source) { - replay.reset(new Replay(route.toStdString(), {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"}, - {}, nullptr, replay_flags, data_dir.toStdString(), auto_source)); +bool ReplayStream::loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags, bool auto_source) { + replay.reset(new Replay(route, {"can", "roadEncodeIdx", "driverEncodeIdx", "wideRoadEncodeIdx", "carParams"}, + {}, nullptr, replay_flags, data_dir, auto_source)); replay->setSegmentCacheLimit(settings.max_cached_minutes); replay->installEventFilter([this](const Event *event) { return eventFilter(event); }); @@ -72,17 +72,17 @@ bool ReplayStream::loadRoute(const QString &route, const QString &data_dir, uint "This will grant access to routes from your comma account."; } else { message = tr("Access Denied. You do not have permission to access route:\n\n%1\n\n" - "This is likely a private route.").arg(route); + "This is likely a private route.").arg(QString::fromStdString(route)); } QMessageBox::warning(nullptr, tr("Access Denied"), message); } else if (replay->lastRouteError() == RouteLoadError::NetworkError) { QMessageBox::warning(nullptr, tr("Network Error"), - tr("Unable to load the route:\n\n %1.\n\nPlease check your network connection and try again.").arg(route)); + tr("Unable to load the route:\n\n %1.\n\nPlease check your network connection and try again.").arg(QString::fromStdString(route))); } else if (replay->lastRouteError() == RouteLoadError::FileNotFound) { QMessageBox::warning(nullptr, tr("Route Not Found"), - tr("The specified route could not be found:\n\n %1.\n\nPlease check the route name and try again.").arg(route)); + tr("The specified route could not be found:\n\n %1.\n\nPlease check the route name and try again.").arg(QString::fromStdString(route))); } else { - QMessageBox::warning(nullptr, tr("Route Load Failed"), tr("Failed to load route: '%1'").arg(route)); + QMessageBox::warning(nullptr, tr("Route Load Failed"), tr("Failed to load route: '%1'").arg(QString::fromStdString(route))); } } return success; @@ -168,7 +168,7 @@ AbstractStream *OpenReplayWidget::open() { if (cameras[2]->isChecked()) flags |= REPLAY_FLAG_ECAM; if (flags == REPLAY_FLAG_NONE && !cameras[0]->isChecked()) flags = REPLAY_FLAG_NO_VIPC; - if (replay_stream->loadRoute(route, data_dir, flags)) { + if (replay_stream->loadRoute(route.toStdString(), data_dir.toStdString(), flags)) { return replay_stream.release(); } } diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h index d429ed1f951..40f8ec8cfbd 100644 --- a/tools/cabana/streams/replaystream.h +++ b/tools/cabana/streams/replaystream.h @@ -18,12 +18,12 @@ class ReplayStream : public AbstractStream { public: ReplayStream(QObject *parent); void start() override { replay->start(); } - bool loadRoute(const QString &route, const QString &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE, bool auto_source = false); + bool loadRoute(const std::string &route, const std::string &data_dir, uint32_t replay_flags = REPLAY_FLAG_NONE, bool auto_source = false); bool eventFilter(const Event *event); void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); } bool liveStreaming() const override { return false; } - inline QString routeName() const override { return QString::fromStdString(replay->route().name()); } - inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); } + inline std::string routeName() const override { return replay->route().name(); } + inline std::string carFingerprint() const override { return replay->carFingerprint(); } double minSeconds() const override { return replay->minSeconds(); } double maxSeconds() const { return replay->maxSeconds(); } inline QDateTime beginDateTime() const { return QDateTime::fromSecsSinceEpoch(replay->routeDateTime()); } diff --git a/tools/cabana/streams/socketcanstream.cc b/tools/cabana/streams/socketcanstream.cc index e4801df9c05..4f558ba0497 100644 --- a/tools/cabana/streams/socketcanstream.cc +++ b/tools/cabana/streams/socketcanstream.cc @@ -12,7 +12,7 @@ SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) throw std::runtime_error("SocketCAN plugin not available"); } - qDebug() << "Connecting to SocketCAN device" << config.device; + qDebug() << "Connecting to SocketCAN device" << config.device.c_str(); if (!connect()) { throw std::runtime_error("Failed to connect to SocketCAN device"); } @@ -26,7 +26,7 @@ bool SocketCanStream::connect() { // Connecting might generate some warnings about missing socketcan/libsocketcan libraries // These are expected and can be ignored, we don't need the advanced features of libsocketcan QString errorString; - device.reset(QCanBus::instance()->createDevice("socketcan", config.device, &errorString)); + device.reset(QCanBus::instance()->createDevice("socketcan", QString::fromStdString(config.device), &errorString)); device->setConfigurationParameter(QCanBusDevice::CanFdKey, true); if (!device) { @@ -87,7 +87,7 @@ OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWi main_layout->addStretch(1); QObject::connect(refresh, &QPushButton::clicked, this, &OpenSocketCanWidget::refreshDevices); - QObject::connect(device_edit, &QComboBox::currentTextChanged, this, [=]{ config.device = device_edit->currentText(); }); + QObject::connect(device_edit, &QComboBox::currentTextChanged, this, [=]{ config.device = device_edit->currentText().toStdString(); }); // Populate devices refreshDevices(); diff --git a/tools/cabana/streams/socketcanstream.h b/tools/cabana/streams/socketcanstream.h index 8083b687e93..dc3cf7928ae 100644 --- a/tools/cabana/streams/socketcanstream.h +++ b/tools/cabana/streams/socketcanstream.h @@ -10,7 +10,7 @@ #include "tools/cabana/streams/livestream.h" struct SocketCanStreamConfig { - QString device = ""; // TODO: support multiple devices/buses at once + std::string device = ""; // TODO: support multiple devices/buses at once }; class SocketCanStream : public LiveStream { @@ -20,8 +20,8 @@ class SocketCanStream : public LiveStream { ~SocketCanStream() { stop(); } static bool available(); - inline QString routeName() const override { - return QString("Live Streaming From Socket CAN %1").arg(config.device); + inline std::string routeName() const override { + return "Live Streaming From Socket CAN " + config.device; } protected: diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index d9fcae6f21a..833cfbe4b58 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -8,7 +8,7 @@ const std::string TEST_RLOG_URL = "https://commadataci.blob.core.windows.net/openpilotci/0c94aa1e1296d7c6/2021-05-05--19-48-37/0/rlog.bz2"; TEST_CASE("DBCFile::generateDBC") { - QString fn = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, "tesla_can"); + std::string fn = std::string(OPENDBC_FILE_PATH) + "/tesla_can.dbc"; DBCFile dbc_origin(fn); DBCFile dbc_from_generated("", dbc_origin.generateDBC()); @@ -30,7 +30,7 @@ TEST_CASE("DBCFile::generateDBC") { TEST_CASE("DBCFile::generateDBC - comment order") { // Ensure that message comments are followed by signal comments and in the correct order - auto content = R"(BO_ 160 message_1: 8 EON + std::string content = R"(BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX BO_ 162 message_2: 8 EON @@ -46,7 +46,7 @@ CM_ SG_ 162 signal_2 "signal comment"; } TEST_CASE("DBCFile::generateDBC -- preserve original header") { - QString content = R"(VERSION "1.0" + std::string content = R"(VERSION "1.0" NS_ : CM_ @@ -66,7 +66,7 @@ CM_ SG_ 160 signal_1 "signal comment"; } TEST_CASE("DBCFile::generateDBC - escaped quotes") { - QString content = R"(BO_ 160 message_1: 8 EON + std::string content = R"(BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX CM_ BO_ 160 "message comment with \"escaped quotes\""; @@ -77,7 +77,7 @@ CM_ SG_ 160 signal_1 "signal comment with \"escaped quotes\""; } TEST_CASE("parse_dbc") { - QString content = R"( + std::string content = R"( BO_ 160 message_1: 8 EON SG_ signal_1 : 0|12@1+ (1,0) [0|4095] "unit" XXX SG_ signal_2 : 12|1@1+ (1.0,0.0) [0.0|1] "" XXX @@ -119,9 +119,9 @@ CM_ SG_ 162 signal_1 "signal comment with \"escaped quotes\""; REQUIRE(sig_1->comment == "signal comment"); REQUIRE(sig_1->receiver_name == "XXX"); REQUIRE(sig_1->val_desc.size() == 3); - REQUIRE(sig_1->val_desc[0] == std::pair{0, "disabled"}); - REQUIRE(sig_1->val_desc[1] == std::pair{1.2, "initializing"}); - REQUIRE(sig_1->val_desc[2] == std::pair{2, "fault"}); + REQUIRE(sig_1->val_desc[0] == std::pair{0, "disabled"}); + REQUIRE(sig_1->val_desc[1] == std::pair{1.2, "initializing"}); + REQUIRE(sig_1->val_desc[2] == std::pair{2, "fault"}); auto &sig_2 = msg->sigs[1]; REQUIRE(sig_2->comment == "multiple line comment \n1\n2"); @@ -147,7 +147,7 @@ TEST_CASE("parse_opendbc") { QStringList errors; for (auto fn : dir.entryList({"*.dbc"}, QDir::Files, QDir::Name)) { try { - auto dbc = DBCFile(dir.filePath(fn)); + auto dbc = DBCFile(dir.filePath(fn).toStdString()); } catch (std::exception &e) { errors.push_back(e.what()); } diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index ec56fcaac08..41da8fc4bca 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -20,7 +20,7 @@ QVariant FindSignalModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole) { const auto &s = filtered_signals[index.row()]; switch (index.column()) { - case 0: return s.id.toString(); + case 0: return QString::fromStdString(s.id.toString()); case 1: return QString("%1, %2").arg(s.sig.start_bit).arg(s.sig.size); case 2: return s.values.join(" "); } diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index c3c659791a7..7cc7b5004f5 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -31,7 +31,7 @@ FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::Wi msg_cb = new QComboBox(this); // TODO: update when src_bus_combo changes for (auto &[address, msg] : dbc()->getMessages(-1)) { - msg_cb->addItem(msg.name, address); + msg_cb->addItem(QString::fromStdString(msg.name), address); } msg_cb->model()->sort(0); msg_cb->setCurrentIndex(0); diff --git a/tools/cabana/utils/export.cc b/tools/cabana/utils/export.cc index 79ca97ba8f2..a7f910193f2 100644 --- a/tools/cabana/utils/export.cc +++ b/tools/cabana/utils/export.cc @@ -26,7 +26,7 @@ void exportSignalsToCSV(const QString &file_name, const MessageId &msg_id) { QTextStream stream(&file); stream << "time,addr,bus"; for (auto s : msg->sigs) - stream << "," << s->name; + stream << "," << s->name.c_str(); stream << "\n"; for (auto e : can->events(msg_id)) { diff --git a/tools/cabana/utils/util.cc b/tools/cabana/utils/util.cc index 61e4ee05140..0aee2aeabec 100644 --- a/tools/cabana/utils/util.cc +++ b/tools/cabana/utils/util.cc @@ -278,7 +278,7 @@ QString signalToolTip(const cabana::Signal *sig) { Start Bit: %2 Size: %3
MSB: %4 LSB: %5
Little Endian: %6 Signed: %7 - )").arg(sig->name).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb) + )").arg(QString::fromStdString(sig->name)).arg(sig->start_bit).arg(sig->size).arg(sig->msb).arg(sig->lsb) .arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N"); } diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index a05bf24b4af..850999640b1 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -384,9 +384,9 @@ QPixmap StreamCameraView::generateThumbnail(QPixmap thumb, double seconds) { void StreamCameraView::drawScrubThumbnail(QPainter &p) { p.fillRect(rect(), Qt::black); - auto it = big_thumbnails.lowerBound(can->toMonoTime(thumbnail_dispaly_time)); + auto it = big_thumbnails.lower_bound(can->toMonoTime(thumbnail_dispaly_time)); if (it != big_thumbnails.end()) { - QPixmap scaled_thumb = it.value().scaled(rect().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap scaled_thumb = it->second.scaled(rect().size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); QRect thumb_rect(rect().center() - scaled_thumb.rect().center(), scaled_thumb.size()); p.drawPixmap(thumb_rect.topLeft(), scaled_thumb); drawTime(p, thumb_rect, thumbnail_dispaly_time); @@ -394,9 +394,9 @@ void StreamCameraView::drawScrubThumbnail(QPainter &p) { } void StreamCameraView::drawThumbnail(QPainter &p) { - auto it = thumbnails.lowerBound(can->toMonoTime(thumbnail_dispaly_time)); + auto it = thumbnails.lower_bound(can->toMonoTime(thumbnail_dispaly_time)); if (it != thumbnails.end()) { - const QPixmap &thumb = it.value(); + const QPixmap &thumb = it->second; auto [min_sec, max_sec] = can->timeRange().value_or(std::make_pair(can->minSeconds(), can->maxSeconds())); int pos = (thumbnail_dispaly_time - min_sec) * width() / (max_sec - min_sec); int x = std::clamp(pos - thumb.width() / 2, THUMBNAIL_MARGIN, width() - thumb.width() - THUMBNAIL_MARGIN + 1); diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index 6da0023123b..e52e92ebd12 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -47,8 +48,8 @@ class StreamCameraView : public CameraWidget { void drawTime(QPainter &p, const QRect &rect, double seconds); QPropertyAnimation *fade_animation; - QMap big_thumbnails; - QMap thumbnails; + std::map big_thumbnails; + std::map thumbnails; double thumbnail_dispaly_time = -1; friend class VideoWidget; }; From 0c7abf38557990706a8a0dc57a95da73338bda79 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 15:55:57 -0800 Subject: [PATCH 440/685] cabana: remove QtXml (#37521) --- tools/cabana/SConscript | 2 +- tools/cabana/utils/util.cc | 52 +++++++++++++++++++++++--------------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index a56ddff7a6b..d3de2dcb720 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -20,7 +20,7 @@ SConscript(['#tools/replay/SConscript']) Import('replay_lib') qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Concurrent", "Xml"] +qt_modules = ["Widgets", "Gui", "Core", "Concurrent"] qt_libs = [] if arch == "Darwin": diff --git a/tools/cabana/utils/util.cc b/tools/cabana/utils/util.cc index 0aee2aeabec..50ab7644235 100644 --- a/tools/cabana/utils/util.cc +++ b/tools/cabana/utils/util.cc @@ -17,8 +17,7 @@ #include #include #include -#include -#include +#include #include "common/util.h" // SegmentTree @@ -325,36 +324,49 @@ void initApp(int argc, char *argv[], bool disable_hidpi) { setSurfaceFormat(); } -static QHash load_bootstrap_icons() { - QHash icons; +static std::unordered_map load_bootstrap_icons() { + std::unordered_map icons; QFile f(":/bootstrap-icons.svg"); if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { - QDomDocument xml; - xml.setContent(&f); - QDomNode n = xml.documentElement().firstChild(); - while (!n.isNull()) { - QDomElement e = n.toElement(); - if (!e.isNull() && e.hasAttribute("id")) { - QString svg_str; - QTextStream stream(&svg_str); - n.save(stream, 0); - svg_str.replace("", ""); - icons[e.attribute("id")] = svg_str.toUtf8(); + std::string content = f.readAll().toStdString(); + const std::string sym_open = " with + svg_str.replace(0, 7, " ""); // "" (9) -> "" (6) + icons[id] = std::move(svg_str); + } } - n = n.nextSibling(); + pos = end; } } return icons; } QPixmap bootstrapPixmap(const QString &id) { - static QHash icons = load_bootstrap_icons(); + static auto icons = load_bootstrap_icons(); QPixmap pixmap; - if (auto it = icons.find(id); it != icons.end()) { - pixmap.loadFromData(it.value(), "svg"); + auto it = icons.find(id.toStdString()); + if (it != icons.end()) { + pixmap.loadFromData((const uchar *)it->second.data(), it->second.size(), "svg"); } return pixmap; } From ce04d25f7ddbaa8a4319e33bed30b298a9fc5910 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 16:00:29 -0800 Subject: [PATCH 441/685] cabana: remove QtConcurrent (#37522) --- tools/cabana/SConscript | 2 +- tools/cabana/chart/chartswidget.cc | 9 +++--- tools/cabana/signalview.cc | 10 +++---- tools/cabana/streams/routes.cc | 10 +++---- tools/cabana/tools/findsignal.cc | 46 +++++++++++++++++++----------- tools/cabana/videowidget.cc | 38 +++++++++++++++--------- 6 files changed, 71 insertions(+), 44 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d3de2dcb720..5747d6e621b 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -20,7 +20,7 @@ SConscript(['#tools/replay/SConscript']) Import('replay_lib') qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Concurrent"] +qt_modules = ["Widgets", "Gui", "Core"] qt_libs = [] if arch == "Darwin": diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index 4e44a560b37..efae1dded01 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -1,13 +1,13 @@ #include "tools/cabana/chart/chartswidget.h" #include +#include #include -#include #include +#include #include #include -#include #include "tools/cabana/chart/chart.h" @@ -171,10 +171,11 @@ void ChartsWidget::updateTabBar() { } void ChartsWidget::eventsMerged(const MessageEventsMap &new_events) { - QFutureSynchronizer future_synchronizer; + std::vector> futures; for (auto c : charts) { - future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr, &new_events)); + futures.push_back(std::async(std::launch::async, &ChartView::updateSeries, c, nullptr, &new_events)); } + for (auto &f : futures) f.get(); } void ChartsWidget::timeRangeChanged(const std::optional> &time_range) { diff --git a/tools/cabana/signalview.cc b/tools/cabana/signalview.cc index df05d287f49..15baabe512a 100644 --- a/tools/cabana/signalview.cc +++ b/tools/cabana/signalview.cc @@ -1,6 +1,7 @@ #include "tools/cabana/signalview.h" #include +#include #include #include @@ -11,7 +12,6 @@ #include #include #include -#include #include #include "tools/cabana/commands.h" @@ -641,13 +641,13 @@ void SignalView::updateState(const std::set *msgs) { delegate->button_size.height() - style()->pixelMetric(QStyle::PM_FocusFrameVMargin) * 2); auto [first, last] = can->eventsInRange(model->msg_id, std::make_pair(last_msg.ts -settings.sparkline_range, last_msg.ts)); - QFutureSynchronizer synchronizer; + std::vector> futures; for (int i = first_visible.row(); i <= last_visible.row(); ++i) { auto item = model->getItem(model->index(i, 1)); - synchronizer.addFuture(QtConcurrent::run( - &item->sparkline, &Sparkline::update, item->sig, first, last, settings.sparkline_range, size)); + futures.push_back(std::async(std::launch::async, + &Sparkline::update, &item->sparkline, item->sig, first, last, settings.sparkline_range, size)); } - synchronizer.waitForFinished(); + for (auto &f : futures) f.get(); } for (int i = 0; i < model->rowCount(); ++i) { diff --git a/tools/cabana/streams/routes.cc b/tools/cabana/streams/routes.cc index 8539a00b5ba..07d8d1a0b16 100644 --- a/tools/cabana/streams/routes.cc +++ b/tools/cabana/streams/routes.cc @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "tools/replay/py_downloader.h" @@ -72,13 +72,13 @@ RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) { // Fetch devices QPointer self = this; - QtConcurrent::run([self]() { + std::thread([self]() { std::string result = PyDownloader::getDevices(); auto [success, error_code] = checkApiResponse(result); QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code]() { if (self) self->parseDeviceList(r, success, error_code); }, Qt::QueuedConnection); - }); + }).detach(); } void RoutesDialog::parseDeviceList(const QString &json, bool success, int error_code) { @@ -114,14 +114,14 @@ void RoutesDialog::fetchRoutes() { int request_id = ++fetch_id_; QPointer self = this; - QtConcurrent::run([self, did, start_ms, end_ms, preserved, request_id]() { + std::thread([self, did, start_ms, end_ms, preserved, request_id]() { std::string result = PyDownloader::getDeviceRoutes(did, start_ms, end_ms, preserved); if (!self || self->fetch_id_ != request_id) return; auto [success, error_code] = checkApiResponse(result); QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code, request_id]() { if (self && self->fetch_id_ == request_id) self->parseRouteList(r, success, error_code); }, Qt::QueuedConnection); - }); + }).detach(); } void RoutesDialog::parseRouteList(const QString &json, bool success, int error_code) { diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index 41da8fc4bca..3dc28bb159a 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -1,10 +1,11 @@ #include "tools/cabana/tools/findsignal.h" +#include + #include #include #include #include -#include #include #include @@ -35,22 +36,35 @@ void FindSignalModel::search(std::function cmp) { const auto prev_sigs = !histories.isEmpty() ? histories.back() : initial_signals; filtered_signals.clear(); filtered_signals.reserve(prev_sigs.size()); - QtConcurrent::blockingMap(prev_sigs, [&](auto &s) { - const auto &events = can->events(s.id); - auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, CompareCanEvent()); - auto last = events.cend(); - if (last_time < std::numeric_limits::max()) { - last = std::upper_bound(events.cbegin(), events.cend(), last_time, CompareCanEvent()); - } - auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); }); - if (it != last) { - auto values = s.values; - values += QString("(%1, %2)").arg(can->toSeconds((*it)->mono_time), 0, 'f', 3).arg(get_raw_value((*it)->dat, (*it)->size, s.sig)); - std::lock_guard lk(lock); - filtered_signals.push_back({.id = s.id, .mono_time = (*it)->mono_time, .sig = s.sig, .values = values}); - } - }); + unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency()); + size_t chunk = (prev_sigs.size() + num_threads - 1) / num_threads; + std::vector threads; + for (unsigned int t = 0; t < num_threads && t * chunk < (size_t)prev_sigs.size(); ++t) { + size_t start = t * chunk; + size_t end = std::min(start + chunk, (size_t)prev_sigs.size()); + threads.emplace_back([&, start, end]() { + for (size_t i = start; i < end; ++i) { + const auto &s = prev_sigs[i]; + const auto &events = can->events(s.id); + auto first = std::upper_bound(events.cbegin(), events.cend(), s.mono_time, CompareCanEvent()); + auto last = events.cend(); + if (last_time < std::numeric_limits::max()) { + last = std::upper_bound(events.cbegin(), events.cend(), last_time, CompareCanEvent()); + } + + auto it = std::find_if(first, last, [&](const CanEvent *e) { return cmp(get_raw_value(e->dat, e->size, s.sig)); }); + if (it != last) { + auto values = s.values; + values += QString("(%1, %2)").arg(can->toSeconds((*it)->mono_time), 0, 'f', 3).arg(get_raw_value((*it)->dat, (*it)->size, s.sig)); + std::lock_guard lk(lock); + filtered_signals.push_back({.id = s.id, .mono_time = (*it)->mono_time, .sig = s.sig, .values = values}); + } + } + }); + } + for (auto &th : threads) th.join(); + histories.push_back(filtered_signals); endResetModel(); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 850999640b1..f203ec663ee 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,6 +1,7 @@ #include "tools/cabana/videowidget.h" #include +#include #include #include @@ -9,7 +10,6 @@ #include #include #include -#include #include "tools/cabana/tools/routeinfo.h" @@ -334,19 +334,31 @@ StreamCameraView::StreamCameraView(std::string stream_name, VisionStreamType str void StreamCameraView::parseQLog(std::shared_ptr qlog) { std::mutex mutex; - QtConcurrent::blockingMap(qlog->events.cbegin(), qlog->events.cend(), [this, &mutex](const Event &e) { - if (e.which == cereal::Event::Which::THUMBNAIL) { - capnp::FlatArrayMessageReader reader(e.data); - auto thumb_data = reader.getRoot().getThumbnail(); - auto image_data = thumb_data.getThumbnail(); - if (QPixmap thumb; thumb.loadFromData(image_data.begin(), image_data.size(), "jpeg")) { - QPixmap generated_thumb = generateThumbnail(thumb, can->toSeconds(thumb_data.getTimestampEof())); - std::lock_guard lock(mutex); - thumbnails[thumb_data.getTimestampEof()] = generated_thumb; - big_thumbnails[thumb_data.getTimestampEof()] = thumb; + const auto &events = qlog->events; + unsigned int num_threads = std::max(1u, std::thread::hardware_concurrency()); + size_t chunk = (events.size() + num_threads - 1) / num_threads; + std::vector threads; + for (unsigned int t = 0; t < num_threads && t * chunk < events.size(); ++t) { + size_t start = t * chunk; + size_t end = std::min(start + chunk, events.size()); + threads.emplace_back([this, &mutex, &events, start, end]() { + for (size_t i = start; i < end; ++i) { + const Event &e = events[i]; + if (e.which == cereal::Event::Which::THUMBNAIL) { + capnp::FlatArrayMessageReader reader(e.data); + auto thumb_data = reader.getRoot().getThumbnail(); + auto image_data = thumb_data.getThumbnail(); + if (QPixmap thumb; thumb.loadFromData(image_data.begin(), image_data.size(), "jpeg")) { + QPixmap generated_thumb = generateThumbnail(thumb, can->toSeconds(thumb_data.getTimestampEof())); + std::lock_guard lock(mutex); + thumbnails[thumb_data.getTimestampEof()] = generated_thumb; + big_thumbnails[thumb_data.getTimestampEof()] = thumb; + } + } } - } - }); + }); + } + for (auto &th : threads) th.join(); update(); } From 3478ac13387ccfcc3c04c197f8ac4d43c4325c24 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 1 Mar 2026 16:12:04 -0800 Subject: [PATCH 442/685] cabana: remove QtSerialBus (#37523) --- tools/cabana/SConscript | 2 - tools/cabana/binaryview.cc | 19 ++--- tools/cabana/binaryview.h | 7 +- tools/cabana/chart/chartswidget.cc | 29 ++++---- tools/cabana/chart/chartswidget.h | 6 +- tools/cabana/chart/signalselector.cc | 4 +- tools/cabana/chart/signalselector.h | 2 +- tools/cabana/streams/socketcanstream.cc | 96 +++++++++++++++++-------- tools/cabana/streams/socketcanstream.h | 9 +-- tools/cabana/tools/findsignal.cc | 18 ++--- tools/cabana/tools/findsignal.h | 10 +-- tools/cabana/tools/findsimilarbits.cc | 19 ++--- tools/cabana/tools/findsimilarbits.h | 4 +- 13 files changed, 132 insertions(+), 93 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 5747d6e621b..098a6351b76 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -68,10 +68,8 @@ base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthrea if arch == "Darwin": base_frameworks.append('QtCharts') - base_frameworks.append('QtSerialBus') else: base_libs.append('Qt5Charts') - base_libs.append('Qt5SerialBus') qt_libs = base_libs diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 90c7c1c3413..0be28f06b79 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -111,7 +111,8 @@ void BinaryView::highlight(const cabana::Signal *sig) { if (sig != hovered_sig) { for (int i = 0; i < model->items.size(); ++i) { auto &item_sigs = model->items[i].sigs; - if ((sig && item_sigs.contains(sig)) || (hovered_sig && item_sigs.contains(hovered_sig))) { + auto has = [](const auto &v, auto p) { return std::find(v.begin(), v.end(), p) != v.end(); }; + if ((sig && has(item_sigs, sig)) || (hovered_sig && has(item_sigs, hovered_sig))) { auto index = model->index(i / model->columnCount(), i % model->columnCount()); emit model->dataChanged(index, index, {Qt::DisplayRole}); } @@ -157,7 +158,7 @@ void BinaryView::mousePressEvent(QMouseEvent *event) { void BinaryView::highlightPosition(const QPoint &pos) { if (auto index = indexAt(viewport()->mapFromGlobal(pos)); index.isValid()) { auto item = (BinaryViewModel::Item *)index.internalPointer(); - const cabana::Signal *sig = item->sigs.isEmpty() ? nullptr : item->sigs.back(); + const cabana::Signal *sig = item->sigs.empty() ? nullptr : item->sigs.back(); highlight(sig); } } @@ -208,12 +209,12 @@ void BinaryView::refresh() { highlightPosition(QCursor::pos()); } -QSet BinaryView::getOverlappingSignals() const { - QSet overlapping; +std::set BinaryView::getOverlappingSignals() const { + std::set overlapping; for (const auto &item : model->items) { if (item.sigs.size() > 1) { for (auto s : item.sigs) { - if (s->type == cabana::Signal::Type::Normal) overlapping += s; + if (s->type == cabana::Signal::Type::Normal) overlapping.insert(s); } } } @@ -404,7 +405,9 @@ bool BinaryItemDelegate::hasSignal(const QModelIndex &index, int dx, int dy, con if (!index.isValid()) return false; auto model = (const BinaryViewModel*)(index.model()); int idx = (index.row() + dy) * model->columnCount() + index.column() + dx; - return (idx >=0 && idx < model->items.size()) ? model->items[idx].sigs.contains(sig) : false; + if (idx < 0 || idx >= (int)model->items.size()) return false; + auto &s = model->items[idx].sigs; + return std::find(s.begin(), s.end(), sig) != s.end(); } void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { @@ -421,7 +424,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op auto color = bin_view->resize_sig ? bin_view->resize_sig->color : option.palette.color(QPalette::Active, QPalette::Highlight); painter->fillRect(option.rect, color); painter->setPen(option.palette.color(QPalette::BrightText)); - } else if (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig)) { // not resizing + } else if (!bin_view->selectionModel()->hasSelection() || std::find(item->sigs.begin(), item->sigs.end(), bin_view->resize_sig) == item->sigs.end()) { // not resizing if (item->sigs.size() > 0) { for (auto &s : item->sigs) { if (s == bin_view->hovered_sig) { @@ -433,7 +436,7 @@ void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &op } else if (item->valid && item->bg_color.alpha() > 0) { painter->fillRect(option.rect, item->bg_color); } - auto color_role = item->sigs.contains(bin_view->hovered_sig) ? QPalette::BrightText : QPalette::Text; + auto color_role = (std::find(item->sigs.begin(), item->sigs.end(), bin_view->hovered_sig) != item->sigs.end()) ? QPalette::BrightText : QPalette::Text; painter->setPen(option.palette.color(bin_view->is_message_active ? QPalette::Normal : QPalette::Disabled, color_role)); } diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 920deb0018e..e568228b37d 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -1,10 +1,9 @@ #pragma once +#include #include #include -#include -#include #include #include @@ -51,7 +50,7 @@ class BinaryViewModel : public QAbstractTableModel { bool is_msb = false; bool is_lsb = false; uint8_t val; - QList sigs; + std::vector sigs; bool valid = false; }; std::vector items; @@ -68,7 +67,7 @@ class BinaryView : public QTableView { BinaryView(QWidget *parent = nullptr); void setMessage(const MessageId &message_id); void highlight(const cabana::Signal *sig); - QSet getOverlappingSignals() const; + std::set getOverlappingSignals() const; void updateState() { model->updateState(); } void paintEvent(QPaintEvent *event) override { is_message_active = can->isMessageActive(model->msg_id); diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index efae1dded01..acdb6d064d3 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -166,7 +166,7 @@ void ChartsWidget::removeTab(int index) { void ChartsWidget::updateTabBar() { for (int i = 0; i < tabbar->count(); ++i) { const auto &charts_in_tab = tab_charts[tabbar->tabData(i).toInt()]; - tabbar->setTabText(i, QString("Tab %1 (%2)").arg(i + 1).arg(charts_in_tab.count())); + tabbar->setTabText(i, QString("Tab %1 (%2)").arg(i + 1).arg((int)charts_in_tab.size())); } } @@ -204,7 +204,7 @@ void ChartsWidget::showValueTip(double sec) { } void ChartsWidget::updateState() { - if (charts.isEmpty()) return; + if (charts.empty()) return; const auto &time_range = can->timeRange(); const double cur_sec = can->currentSec(); @@ -248,7 +248,7 @@ void ChartsWidget::updateToolBar() { redo_zoom_action->setVisible(is_zoomed); reset_zoom_action->setVisible(is_zoomed); reset_zoom_btn->setText(is_zoomed ? tr("%1-%2").arg(can->timeRange()->first, 0, 'f', 2).arg(can->timeRange()->second, 0, 'f', 2) : ""); - remove_all_btn->setEnabled(!charts.isEmpty()); + remove_all_btn->setEnabled(!charts.empty()); } void ChartsWidget::settingChanged() { @@ -282,9 +282,9 @@ ChartView *ChartsWidget::createChart(int pos) { chart->setMinimumWidth(CHART_MIN_WIDTH); chart->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); QObject::connect(chart, &ChartView::axisYLabelWidthChanged, align_timer, qOverload<>(&QTimer::start)); - pos = std::clamp(pos, 0, charts.size()); - charts.insert(pos, chart); - currentCharts().insert(pos, chart); + pos = std::clamp(pos, 0, (int)charts.size()); + charts.insert(charts.begin() + pos, chart); + currentCharts().insert(currentCharts().begin() + pos, chart); updateLayout(true); updateToolBar(); return chart; @@ -303,7 +303,7 @@ void ChartsWidget::showChart(const MessageId &id, const cabana::Signal *sig, boo void ChartsWidget::splitChart(ChartView *src_chart) { if (src_chart->sigs.size() > 1) { - int pos = charts.indexOf(src_chart) + 1; + int pos = std::find(charts.begin(), charts.end(), src_chart) - charts.begin() + 1; for (auto it = src_chart->sigs.begin() + 1; it != src_chart->sigs.end(); /**/) { auto c = createChart(pos); src_chart->chart()->removeSeries(it->series); @@ -434,7 +434,7 @@ void ChartsWidget::newChart() { SignalSelector dlg(tr("New Chart"), this); if (dlg.exec() == QDialog::Accepted) { auto items = dlg.seletedItems(); - if (!items.isEmpty()) { + if (!items.empty()) { auto c = createChart(); for (auto it : items) { c->addSignal(it->msg_id, it->sig); @@ -444,10 +444,10 @@ void ChartsWidget::newChart() { } void ChartsWidget::removeChart(ChartView *chart) { - charts.removeOne(chart); + charts.erase(std::remove(charts.begin(), charts.end(), chart), charts.end()); chart->deleteLater(); for (auto &[_, list] : tab_charts) { - list.removeOne(chart); + list.erase(std::remove(list.begin(), list.end(), chart), list.end()); } updateToolBar(); updateLayout(true); @@ -461,7 +461,7 @@ void ChartsWidget::removeAll() { } tab_charts.clear(); - if (!charts.isEmpty()) { + if (!charts.empty()) { for (auto c : charts) { delete c; } @@ -561,10 +561,11 @@ void ChartsContainer::dropEvent(QDropEvent *event) { auto chart = qobject_cast(event->source()); if (w != chart) { for (auto &[_, list] : charts_widget->tab_charts) { - list.removeOne(chart); + list.erase(std::remove(list.begin(), list.end(), chart), list.end()); } - int to = w ? charts_widget->currentCharts().indexOf(w) + 1 : 0; - charts_widget->currentCharts().insert(to, chart); + auto &cur = charts_widget->currentCharts(); + int to = w ? std::find(cur.begin(), cur.end(), w) - cur.begin() + 1 : 0; + cur.insert(cur.begin() + to, chart); charts_widget->updateLayout(true); charts_widget->updateTabBar(); event->acceptProposedAction(); diff --git a/tools/cabana/chart/chartswidget.h b/tools/cabana/chart/chartswidget.h index f87b1276c5d..ef3fbc471ac 100644 --- a/tools/cabana/chart/chartswidget.h +++ b/tools/cabana/chart/chartswidget.h @@ -81,7 +81,7 @@ public slots: bool eventFilter(QObject *obj, QEvent *event) override; void newTab(); void removeTab(int index); - inline QList ¤tCharts() { return tab_charts[tabbar->tabData(tabbar->currentIndex()).toInt()]; } + inline std::vector ¤tCharts() { return tab_charts[tabbar->tabData(tabbar->currentIndex()).toInt()]; } ChartView *findChart(const MessageId &id, const cabana::Signal *sig); QLabel *title_label; @@ -100,8 +100,8 @@ public slots: QUndoStack *zoom_undo_stack; ToolButton *remove_all_btn; - QList charts; - std::unordered_map> tab_charts; + std::vector charts; + std::unordered_map> tab_charts; TabBar *tabbar; ChartsContainer *charts_container; QScrollArea *charts_scroll; diff --git a/tools/cabana/chart/signalselector.cc b/tools/cabana/chart/signalselector.cc index 168bf824dfa..6f2fd8de46b 100644 --- a/tools/cabana/chart/signalselector.cc +++ b/tools/cabana/chart/signalselector.cc @@ -102,8 +102,8 @@ void SignalSelector::addItemToList(QListWidget *parent, const MessageId id, cons parent->setItemWidget(new_item, label); } -QList SignalSelector::seletedItems() { - QList ret; +std::vector SignalSelector::seletedItems() { + std::vector ret; for (int i = 0; i < selected_list->count(); ++i) ret.push_back((ListItem *)selected_list->item(i)); return ret; } diff --git a/tools/cabana/chart/signalselector.h b/tools/cabana/chart/signalselector.h index f46779f044e..5b6e37e56aa 100644 --- a/tools/cabana/chart/signalselector.h +++ b/tools/cabana/chart/signalselector.h @@ -15,7 +15,7 @@ class SignalSelector : public QDialog { }; SignalSelector(QString title, QWidget *parent); - QList seletedItems(); + std::vector seletedItems(); inline void addSelected(const MessageId &id, const cabana::Signal *sig) { addItemToList(selected_list, id, sig, true); } private: diff --git a/tools/cabana/streams/socketcanstream.cc b/tools/cabana/streams/socketcanstream.cc index 4f558ba0497..768465d5a3c 100644 --- a/tools/cabana/streams/socketcanstream.cc +++ b/tools/cabana/streams/socketcanstream.cc @@ -1,6 +1,14 @@ #include "tools/cabana/streams/socketcanstream.h" +#include +#include +#include +#include +#include +#include + #include +#include #include #include #include @@ -9,7 +17,7 @@ SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) : config(config_), LiveStream(parent) { if (!available()) { - throw std::runtime_error("SocketCAN plugin not available"); + throw std::runtime_error("SocketCAN not available"); } qDebug() << "Connecting to SocketCAN device" << config.device.c_str(); @@ -18,50 +26,73 @@ SocketCanStream::SocketCanStream(QObject *parent, SocketCanStreamConfig config_) } } +SocketCanStream::~SocketCanStream() { + stop(); + if (sock_fd >= 0) { + ::close(sock_fd); + sock_fd = -1; + } +} + bool SocketCanStream::available() { - return QCanBus::instance()->plugins().contains("socketcan"); + int fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (fd < 0) return false; + ::close(fd); + return true; } bool SocketCanStream::connect() { - // Connecting might generate some warnings about missing socketcan/libsocketcan libraries - // These are expected and can be ignored, we don't need the advanced features of libsocketcan - QString errorString; - device.reset(QCanBus::instance()->createDevice("socketcan", QString::fromStdString(config.device), &errorString)); - device->setConfigurationParameter(QCanBusDevice::CanFdKey, true); - - if (!device) { - qDebug() << "Failed to create SocketCAN device" << errorString; + sock_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW); + if (sock_fd < 0) { + qDebug() << "Failed to create CAN socket"; + return false; + } + + // Enable CAN-FD + int fd_enable = 1; + setsockopt(sock_fd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &fd_enable, sizeof(fd_enable)); + + struct ifreq ifr = {}; + strncpy(ifr.ifr_name, config.device.c_str(), IFNAMSIZ - 1); + if (ioctl(sock_fd, SIOCGIFINDEX, &ifr) < 0) { + qDebug() << "Failed to get interface index for" << config.device.c_str(); + ::close(sock_fd); + sock_fd = -1; return false; } - if (!device->connectDevice()) { - qDebug() << "Failed to connect to device"; + struct sockaddr_can addr = {}; + addr.can_family = AF_CAN; + addr.can_ifindex = ifr.ifr_ifindex; + if (bind(sock_fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + qDebug() << "Failed to bind CAN socket"; + ::close(sock_fd); + sock_fd = -1; return false; } + // Set read timeout so the thread can check for interruption + struct timeval tv = {.tv_sec = 0, .tv_usec = 100000}; // 100ms + setsockopt(sock_fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); + return true; } void SocketCanStream::streamThread() { + struct canfd_frame frame; + while (!QThread::currentThread()->isInterruptionRequested()) { - QThread::msleep(1); + ssize_t nbytes = read(sock_fd, &frame, sizeof(frame)); + if (nbytes <= 0) continue; - auto frames = device->readAllFrames(); - if (frames.size() == 0) continue; + uint8_t len = (nbytes == CAN_MTU) ? frame.len : frame.len; // works for both CAN and CAN-FD MessageBuilder msg; auto evt = msg.initEvent(); - auto canData = evt.initCan(frames.size()); - - for (uint i = 0; i < frames.size(); i++) { - if (!frames[i].isValid()) continue; - - canData[i].setAddress(frames[i].frameId()); - canData[i].setSrc(0); - - auto payload = frames[i].payload(); - canData[i].setDat(kj::arrayPtr((uint8_t*)payload.data(), payload.size())); - } + auto canData = evt.initCan(1); + canData[0].setAddress(frame.can_id & CAN_EFF_MASK); + canData[0].setSrc(0); + canData[0].setDat(kj::arrayPtr(frame.data, len)); handleEvent(capnp::messageToFlatArray(msg)); } @@ -95,12 +126,19 @@ OpenSocketCanWidget::OpenSocketCanWidget(QWidget *parent) : AbstractOpenStreamWi void OpenSocketCanWidget::refreshDevices() { device_edit->clear(); - for (auto device : QCanBus::instance()->availableDevices(QStringLiteral("socketcan"))) { - device_edit->addItem(device.name()); + // Scan /sys/class/net/ for CAN interfaces (type 280 = ARPHRD_CAN) + QDir net_dir("/sys/class/net"); + for (const auto &iface : net_dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + QFile type_file(net_dir.filePath(iface) + "/type"); + if (type_file.open(QIODevice::ReadOnly)) { + int type = type_file.readAll().trimmed().toInt(); + if (type == 280) { + device_edit->addItem(iface); + } + } } } - AbstractStream *OpenSocketCanWidget::open() { try { return new SocketCanStream(qApp, config); diff --git a/tools/cabana/streams/socketcanstream.h b/tools/cabana/streams/socketcanstream.h index dc3cf7928ae..3c5cd184f74 100644 --- a/tools/cabana/streams/socketcanstream.h +++ b/tools/cabana/streams/socketcanstream.h @@ -1,10 +1,5 @@ #pragma once -#include - -#include -#include -#include #include #include "tools/cabana/streams/livestream.h" @@ -17,7 +12,7 @@ class SocketCanStream : public LiveStream { Q_OBJECT public: SocketCanStream(QObject *parent, SocketCanStreamConfig config_ = {}); - ~SocketCanStream() { stop(); } + ~SocketCanStream(); static bool available(); inline std::string routeName() const override { @@ -29,7 +24,7 @@ class SocketCanStream : public LiveStream { bool connect(); SocketCanStreamConfig config = {}; - std::unique_ptr device; + int sock_fd = -1; }; class OpenSocketCanWidget : public AbstractOpenStreamWidget { diff --git a/tools/cabana/tools/findsignal.cc b/tools/cabana/tools/findsignal.cc index 3dc28bb159a..b1318939427 100644 --- a/tools/cabana/tools/findsignal.cc +++ b/tools/cabana/tools/findsignal.cc @@ -33,7 +33,7 @@ void FindSignalModel::search(std::function cmp) { beginResetModel(); std::mutex lock; - const auto prev_sigs = !histories.isEmpty() ? histories.back() : initial_signals; + const auto prev_sigs = !histories.empty() ? histories.back() : initial_signals; filtered_signals.clear(); filtered_signals.reserve(prev_sigs.size()); @@ -71,11 +71,11 @@ void FindSignalModel::search(std::function cmp) { } void FindSignalModel::undo() { - if (!histories.isEmpty()) { + if (!histories.empty()) { beginResetModel(); histories.pop_back(); filtered_signals.clear(); - if (!histories.isEmpty()) filtered_signals = histories.back(); + if (!histories.empty()) filtered_signals = histories.back(); endResetModel(); } } @@ -186,7 +186,7 @@ FindSignalDlg::FindSignalDlg(QWidget *parent) : QDialog(parent, Qt::WindowFlags( } void FindSignalDlg::search() { - if (model->histories.isEmpty()) { + if (model->histories.empty()) { setInitialSignals(); } auto v1 = value1->text().toDouble(); @@ -260,12 +260,12 @@ void FindSignalDlg::setInitialSignals() { } void FindSignalDlg::modelReset() { - properties_group->setEnabled(model->histories.isEmpty()); - message_group->setEnabled(model->histories.isEmpty()); - search_btn->setText(model->histories.isEmpty() ? tr("Find") : tr("Find Next")); - reset_btn->setEnabled(!model->histories.isEmpty()); + properties_group->setEnabled(model->histories.empty()); + message_group->setEnabled(model->histories.empty()); + search_btn->setText(model->histories.empty() ? tr("Find") : tr("Find Next")); + reset_btn->setEnabled(!model->histories.empty()); undo_btn->setEnabled(model->histories.size() > 1); - search_btn->setEnabled(model->rowCount() > 0 || model->histories.isEmpty()); + search_btn->setEnabled(model->rowCount() > 0 || model->histories.empty()); stats_label->setVisible(true); stats_label->setText(tr("%1 matches. right click on an item to create signal. double click to open message").arg(model->filtered_signals.size())); } diff --git a/tools/cabana/tools/findsignal.h b/tools/cabana/tools/findsignal.h index 5ef7461fee2..239a08c9c48 100644 --- a/tools/cabana/tools/findsignal.h +++ b/tools/cabana/tools/findsignal.h @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include @@ -26,14 +28,14 @@ class FindSignalModel : public QAbstractTableModel { QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 3; } - int rowCount(const QModelIndex &parent = QModelIndex()) const override { return std::min(filtered_signals.size(), 300); } + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return std::min((int)filtered_signals.size(), 300); } void search(std::function cmp); void reset(); void undo(); - QList filtered_signals; - QList initial_signals; - QList> histories; + std::vector filtered_signals; + std::vector initial_signals; + std::vector> histories; uint64_t last_time = std::numeric_limits::max(); }; diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc index 7cc7b5004f5..8062b611990 100644 --- a/tools/cabana/tools/findsimilarbits.cc +++ b/tools/cabana/tools/findsimilarbits.cc @@ -1,6 +1,7 @@ #include "tools/cabana/tools/findsimilarbits.h" #include +#include #include #include @@ -114,10 +115,10 @@ void FindSimilarBitsDlg::find() { search_btn->setEnabled(true); } -QList FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, - int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt) { - QHash> mismatches; - QHash msg_count; +std::vector FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, + int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt) { + std::unordered_map> mismatches; + std::unordered_map msg_count; const auto &events = can->allEvents(); int bit_to_find = -1; for (const CanEvent *e : events) { @@ -143,14 +144,14 @@ QList FindSimilarBitsDlg::calcBits(uint8_ } } - QList result; + std::vector result; result.reserve(mismatches.size()); for (auto it = mismatches.begin(); it != mismatches.end(); ++it) { - if (auto cnt = msg_count[it.key()]; cnt > min_msgs_cnt) { - auto &mismatched = it.value(); - for (int i = 0; i < mismatched.size(); ++i) { + if (auto cnt = msg_count[it->first]; cnt > (uint32_t)min_msgs_cnt) { + auto &mismatched = it->second; + for (int i = 0; i < (int)mismatched.size(); ++i) { if (float perc = (mismatched[i] / (double)cnt) * 100; perc < 50) { - result.push_back({it.key(), (uint32_t)i / 8, (uint32_t)i % 8, mismatched[i], cnt, perc}); + result.push_back({it->first, (uint32_t)i / 8, (uint32_t)i % 8, mismatched[i], cnt, perc}); } } } diff --git a/tools/cabana/tools/findsimilarbits.h b/tools/cabana/tools/findsimilarbits.h index 77bfac19cae..3451360654d 100644 --- a/tools/cabana/tools/findsimilarbits.h +++ b/tools/cabana/tools/findsimilarbits.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -22,7 +24,7 @@ class FindSimilarBitsDlg : public QDialog { uint32_t address, byte_idx, bit_idx, mismatches, total; float perc; }; - QList calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, uint8_t find_bus, + std::vector calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt); void find(); From 06b2c68e03240d33174621e664d3ac04fdd954f8 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 1 Mar 2026 21:14:41 -0500 Subject: [PATCH 443/685] macOS: fix cabana builds (#37518) --- tools/cabana/streams/routes.cc | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tools/cabana/streams/routes.cc b/tools/cabana/streams/routes.cc index 07d8d1a0b16..1e69a45cea9 100644 --- a/tools/cabana/streams/routes.cc +++ b/tools/cabana/streams/routes.cc @@ -74,9 +74,8 @@ RoutesDialog::RoutesDialog(QWidget *parent) : QDialog(parent) { QPointer self = this; std::thread([self]() { std::string result = PyDownloader::getDevices(); - auto [success, error_code] = checkApiResponse(result); - QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code]() { - if (self) self->parseDeviceList(r, success, error_code); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), response = checkApiResponse(result)]() { + if (self) self->parseDeviceList(r, response.first, response.second); }, Qt::QueuedConnection); }).detach(); } @@ -117,9 +116,8 @@ void RoutesDialog::fetchRoutes() { std::thread([self, did, start_ms, end_ms, preserved, request_id]() { std::string result = PyDownloader::getDeviceRoutes(did, start_ms, end_ms, preserved); if (!self || self->fetch_id_ != request_id) return; - auto [success, error_code] = checkApiResponse(result); - QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), success, error_code, request_id]() { - if (self && self->fetch_id_ == request_id) self->parseRouteList(r, success, error_code); + QMetaObject::invokeMethod(qApp, [self, r = QString::fromStdString(result), response = checkApiResponse(result), request_id]() { + if (self && self->fetch_id_ == request_id) self->parseRouteList(r, response.first, response.second); }, Qt::QueuedConnection); }).detach(); } From 443cd795a3024925e49da650befd619e2270ccf2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Mar 2026 15:37:18 -0800 Subject: [PATCH 444/685] Onboarding: set real width --- selfdrive/ui/mici/layouts/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 7b619f31ec9..09769a11e6c 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -445,7 +445,7 @@ def __init__(self): self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING - self.set_rect(rl.Rectangle(0, 0, 458, gui_app.height)) + self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) # Windows self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_terms_declined) From 3504ccb639659686dbc1b42f758aaf7491729097 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Mar 2026 19:49:50 -0800 Subject: [PATCH 445/685] ui: keyboard goes back on . or / (#37534) switch back to letters on . or / --- selfdrive/ui/mici/widgets/dialog.py | 5 +++-- system/ui/mici_setup.py | 2 +- system/ui/widgets/mici_keyboard.py | 7 ++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 619c1ca28f4..b2662d8a3bf 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -103,11 +103,12 @@ def __init__(self, hint: str, default_text: str = "", minimum_length: int = 1, - confirm_callback: Callable[[str], None] | None = None): + confirm_callback: Callable[[str], None] | None = None, + auto_return_to_letters: str = ""): super().__init__() self._hint_label = UnifiedLabel(hint, font_size=35, text_color=rl.Color(255, 255, 255, int(255 * 0.35)), font_weight=FontWeight.MEDIUM) - self._keyboard = MiciKeyboard() + self._keyboard = MiciKeyboard(auto_return_to_letters=auto_return_to_letters) self._keyboard.set_text(default_text) self._keyboard.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget self._minimum_length = minimum_length diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 75ea316ece1..5761fd12120 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -623,7 +623,7 @@ def handle_keyboard_result(text): gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(url) - keyboard = BigInputDialog("custom software URL", confirm_callback=handle_keyboard_result) + keyboard = BigInputDialog("custom software URL...", confirm_callback=handle_keyboard_result, auto_return_to_letters="./") gui_app.push_widget(keyboard) def _download(self, url: str): diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 18384fd905d..74f1f563463 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -146,8 +146,9 @@ class CapsState(IntEnum): class MiciKeyboard(Widget): - def __init__(self): + def __init__(self, auto_return_to_letters: str = ""): super().__init__() + self._auto_return_to_letters = auto_return_to_letters lower_chars = [ "qwertyuiop", @@ -305,6 +306,10 @@ def _handle_mouse_release(self, mouse_pos: MousePos): if self._caps_state == CapsState.UPPER: self._set_uppercase(False) + # Switch back to letters after common URL delimiters + if self._closest_key[0].char in self._auto_return_to_letters and self._current_keys in (self._special_keys, self._super_special_keys): + self._set_uppercase(False) + # ensure minimum selected animation time key_selected_dt = rl.get_time() - (self._selected_key_t or 0) cur_t = rl.get_time() From 90af6be9b8d0f25f527371b75477a66341d95ac5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Mar 2026 23:24:43 -0800 Subject: [PATCH 446/685] Render offroad text centered --- selfdrive/ui/mici/onroad/augmented_road_view.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 92874068219..2c083e84b98 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -247,7 +247,7 @@ def _render(self, _): # Draw darkened background and text if not onroad if not ui_state.started: rl.draw_rectangle(int(self.rect.x), int(self.rect.y), int(self.rect.width), int(self.rect.height), rl.Color(0, 0, 0, 175)) - self._offroad_label.render(self._content_rect) + self._offroad_label.render(self._rect) # publish uiDebug msg = messaging.new_message('uiDebug') From 2ebf09eb0705b097167900c5575ab0376525449f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 2 Mar 2026 23:25:23 -0800 Subject: [PATCH 447/685] Clear frame on offroad transition --- selfdrive/ui/mici/onroad/cameraview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/cameraview.py b/selfdrive/ui/mici/onroad/cameraview.py index 89a4926ce9a..62fcfd0654e 100644 --- a/selfdrive/ui/mici/onroad/cameraview.py +++ b/selfdrive/ui/mici/onroad/cameraview.py @@ -155,11 +155,11 @@ def _offroad_transition(self): # Prevent old frames from showing when going onroad. Qt has a separate thread # which drains the VisionIpcClient SubSocket for us. Re-connecting is not enough # and only clears internal buffers, not the message queue. - self.frame = None self.available_streams.clear() if self.client: del self.client self.client = VisionIpcClient(self._name, self._stream_type, conflate=True) + self.frame = None def _set_placeholder_color(self, color: rl.Color): """Set a placeholder color to be drawn when no frame is available.""" From 91b77522680dfa2987b2eb9f510862b2a1e2315e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Mar 2026 01:06:51 -0800 Subject: [PATCH 448/685] Setup: improvements (#37264) * pressed state for larger sliders * wifibutton * fix * clean up * some work * don't nee this now * stash * more * new pressed bigcircle * black * interp * just check position * clean up and fix slider reset * fix custom * no speed * stash * even chatter couldn't figure this one out * makes sense to combine together, less split mentality * clean that up * fix lag * match ui.py prio to eliminate lag on wifiui show event. separately, why is this slow? * night mode * delay scroll over * fix auto scrolling * stash * waiting looks disabled * clean up and don't reset sliders until user goes back * rm * fix * add termsheader back * fix callbacks * ctrl alt l * fix text spacing * clean up * stash * fix style * i want to go back * guard on exit * kinda useless stuff * Revert "kinda useless stuff" This reverts commit a4acbac31523408f358c5f68262cb630aa13ad8e. * Revert "guard on exit" This reverts commit 63ccfbf64edfbe1a144a441681f5ec78d8021ff7. * wide * setup pressed! * grow animation * 10s after initial * slow fast * start onboarding (terms) * rm duplicate page * add qr code * final grey * fix visual lag on first start * clean up dead code * dont exit from cancel * revert grey * clean up, REVIEW ME * Revert "clean up, REVIEW ME" This reverts commit c66fa60947c5f922520e7cf58c630b4bbe2d0177. * reboot slider * kb fix * Revert "kb fix" This reverts commit 883039448e6c37ae1d25d4f75ada6e96b6736358. * ./ goes to letters * Revert "./ goes to letters" This reverts commit 0d97442427edb1a000638863a3f2181204ddc160. * clean up * some more clean up * more * clean up * rename block * reset pending scroll so it can't use stale data in rare sequence * remove unused assets * clean up imports * fix updater * clean up * fix double reboot * demo time - reset to setup on reboot * let manager restart * Revert "demo time - reset to setup on reboot" This reverts commit 9468657e8438a1ce8fcb5266403b7bb3539f131f. * url... and no grow animation on start button * one next button * grow instead of shake wifi button * 36 pt font size in setup * touch up onboarding a lil * Revert "rm cpp bz2 (#37332)" This reverts commit f4a36f7f743f2fb3499af4ab6769a8819150917f. * more onboarding and clean up * clean up * wow what an amazing future clean up * back to software select * fix * copy * fix dm confirmation dialog not disabling widget underneath, all fixed with real nav stack in here * uploading * lint * add review terms to device w/ close button * todo * remove old Terms vertical scrolling classes * use new Scroller! * installer * tweak to match figma exactly * revert * fixup updater * demo day * demo day v2 * ... for percent while finishing setup * demo day v3 * demo day v4 * remove ... * demo day v6 -- "why does it do that!!" * demo day v7 -- no flash * hmm * demo day v7 * prebuilt * revert demo day * scroll after pop animation * back -> retry * stash fixes * damn, need back_callback * scroll over immediately if already in network setup * tweaks * going down is confusing * more * Revert "more" This reverts commit 29ce75b1f81eb40e7527a71d27842d9a66802206. * Revert "going down is confusing" This reverts commit 0cd2ae30d4135db1ccba6478429b45e886714e9c. * dupl * nl * sort functions * more clean up from merge * move * more * dismiss to download (hack) * Revert "dismiss to download (hack)" This reverts commit 53c45ed1f63db1f0cebbce0dfab1777c8658f505. * onboarding work * set brightness and timeout in root onboarding only * clean up * type * keep 5m for settings preview * switch back to letters on . or / * reset first step scroller * custom software warning goes down network comes up and back cb fix * clean up * smaller qr * ReviewTermsPage just for device as NavWidget * clean up * installer: stay on 100% * reset has internet while in wifiui * try this * try this * see what error we get exactly see what error we get exactly * not final solution but see how good * rm * copy changes * reset on disconnect * for separate pr * Revert "reset on disconnect" This reverts commit 552372fa4d497ba7d9de7f2edb730ee63798ffa4. * revert this, too buggy * fix for updater * sort * fix test * minor cleanup * more leaks than this rn * onboarding clean up * clean up application * click delay to small button * clean up * reset more state * fix training guide not cleaning up driverview * Revert "fix training guide not cleaning up driverview" This reverts commit cac7c5f436056cc9e747f80905d390790fb83c22. * simpler fix :( * nice catch, if you go back to terms it will reset 300s timeout and brightness * duplicate show * unused --- selfdrive/assets/icons_mici/setup/cancel.png | 3 + .../assets/icons_mici/setup/continue.png | 3 + .../icons_mici/setup/continue_disabled.png | 3 + .../icons_mici/setup/continue_pressed.png | 3 + selfdrive/assets/icons_mici/setup/restore.png | 4 +- selfdrive/ui/installer/installer.cc | 12 +- selfdrive/ui/mici/layouts/main.py | 6 +- selfdrive/ui/mici/layouts/onboarding.py | 447 +++++++---------- selfdrive/ui/mici/layouts/settings/device.py | 30 +- selfdrive/ui/mici/tests/test_widget_leaks.py | 4 +- selfdrive/ui/mici/widgets/button.py | 13 +- system/ui/lib/application.py | 3 + system/ui/mici_setup.py | 457 +++++++----------- system/ui/mici_updater.py | 28 +- system/ui/widgets/button.py | 1 + system/ui/widgets/nav_widget.py | 8 +- 16 files changed, 447 insertions(+), 578 deletions(-) create mode 100644 selfdrive/assets/icons_mici/setup/cancel.png create mode 100644 selfdrive/assets/icons_mici/setup/continue.png create mode 100644 selfdrive/assets/icons_mici/setup/continue_disabled.png create mode 100644 selfdrive/assets/icons_mici/setup/continue_pressed.png diff --git a/selfdrive/assets/icons_mici/setup/cancel.png b/selfdrive/assets/icons_mici/setup/cancel.png new file mode 100644 index 00000000000..f50cc9ef3fe --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/cancel.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a6892bd4d9b14b587fa491a6d608562e38819b4c618b1d7a3e8c384f05d52a2b +size 1245 diff --git a/selfdrive/assets/icons_mici/setup/continue.png b/selfdrive/assets/icons_mici/setup/continue.png new file mode 100644 index 00000000000..7a67bb0c960 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/continue.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3428d8fcf2ecf9542c524706124f82b7fc809453c63418c9234ac9df5d85bd24 +size 10074 diff --git a/selfdrive/assets/icons_mici/setup/continue_disabled.png b/selfdrive/assets/icons_mici/setup/continue_disabled.png new file mode 100644 index 00000000000..8a2bcc2ffee --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/continue_disabled.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2810add4943dd4f20a984ed6011b520925919a58d5c0dd0d846fc4d7f8a1d02 +size 7109 diff --git a/selfdrive/assets/icons_mici/setup/continue_pressed.png b/selfdrive/assets/icons_mici/setup/continue_pressed.png new file mode 100644 index 00000000000..3eaee7bf1c9 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/continue_pressed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a3a87454a3d2f1ebb327211062c52480de945673dcfd137c5da3df8fa98d731 +size 22400 diff --git a/selfdrive/assets/icons_mici/setup/restore.png b/selfdrive/assets/icons_mici/setup/restore.png index 5eff9240406..5c62086f64f 100644 --- a/selfdrive/assets/icons_mici/setup/restore.png +++ b/selfdrive/assets/icons_mici/setup/restore.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f5ee67cd334d259ac33f932281db36533877009b5769c92d9cff3054fd5627c -size 2942 +oid sha256:63c1499106621a4d927c21b2b04c87235a927216d9f513a0205f0fe03b8c799b +size 12320 diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 338bcad34e4..ec7a4adc922 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -81,16 +81,14 @@ void run(const char* cmd) { } void finishInstall() { - BeginDrawing(); - ClearBackground(BLACK); - if (tici_device) { + if (tici_device) { + BeginDrawing(); + ClearBackground(BLACK); const char *m = "Finishing install..."; int text_width = MeasureText(m, FONT_SIZE); DrawTextEx(font_display, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE); - } else { - DrawTextEx(font_display, "finishing setup", (Vector2){12, 0}, 77, 0, (Color){255, 255, 255, (unsigned char)(255 * 0.9)}); - } - EndDrawing(); + EndDrawing(); + } util::sleep_for(60 * 1000); } diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 2c3fea0d322..95258e2795d 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -53,7 +53,7 @@ def __init__(self): gui_app.push_widget(self) # Start onboarding if terms or training not completed, make sure to push after self - self._onboarding_window = OnboardingWindow() + self._onboarding_window = OnboardingWindow(lambda: gui_app.pop_widgets_to(self)) if not self._onboarding_window.completed: gui_app.push_widget(self._onboarding_window) @@ -79,7 +79,7 @@ def _render(self, _): def _handle_transitions(self): # Don't pop if onboarding - if gui_app.get_active_widget() == self._onboarding_window: + if gui_app.widget_in_stack(self._onboarding_window): return if ui_state.started != self._prev_onroad: @@ -105,7 +105,7 @@ def _handle_transitions(self): def _on_interactive_timeout(self): # Don't pop if onboarding - if gui_app.get_active_widget() == self._onboarding_window: + if gui_app.widget_in_stack(self._onboarding_window): return if ui_state.started: diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 09769a11e6c..cf633192ea9 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -1,29 +1,23 @@ -from enum import IntEnum - -import weakref import math import numpy as np +import qrcode import pyray as rl +from collections.abc import Callable from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import FontWeight, gui_app from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.button import SmallButton, SmallCircleIconButton -from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.slider import SmallSlider -from openpilot.system.ui.mici_setup import TermsHeader, TermsPage as SetupTermsPage -from openpilot.selfdrive.ui.ui_state import ui_state, device -from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer -from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog +from openpilot.system.ui.widgets.button import SmallCircleIconButton +from openpilot.system.ui.widgets.scroller import NavScroller, Scroller +from openpilot.system.ui.widgets.nav_widget import NavWidget +from openpilot.system.ui.mici_setup import GreyBigButton, BigPillButton from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.lib.multilang import tr from openpilot.system.version import terms_version, training_version - - -class OnboardingState(IntEnum): - TERMS = 0 - ONBOARDING = 1 - DECLINE = 2 +from openpilot.selfdrive.ui.ui_state import ui_state, device +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer +from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog class DriverCameraSetupDialog(BaseDriverCameraDialog): @@ -57,91 +51,62 @@ def _render(self, rect): rl.end_scissor_mode() -class TrainingGuidePreDMTutorial(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("driver monitoring setup", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) +class TrainingGuidePreDMTutorial(NavScroller): + def __init__(self, continue_callback: Callable[[], None]): + super().__init__() + + continue_button = BigPillButton("next") + continue_button.set_click_callback(continue_callback) - self._dm_label = UnifiedLabel("Next, we'll ensure comma four is mounted properly.\n\nIf it does not have a clear view of the driver, " + - "unplug and remount before continuing.", 42, - FontWeight.ROMAN) + self._scroller.add_widgets([ + GreyBigButton("driver monitoring\ncheck", "scroll to continue", + gui_app.texture("icons_mici/setup/green_dm.png", 64, 64)), + GreyBigButton("", "Next, we'll check if comma four can detect the driver properly."), + GreyBigButton("", "openpilot uses the cabin camera to check if the driver is distracted."), + GreyBigButton("", "If it does not have a clear view of the driver, unplug and remount before continuing."), + continue_button, + ]) def show_event(self): super().show_event() # Get driver monitoring model ready for next step - ui_state.params.put_bool("IsDriverViewEnabled", True) + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", True) - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) - - -class DMBadFaceDetected(SetupTermsPage): - def __init__(self, continue_callback, back_callback): - super().__init__(continue_callback, back_callback, continue_text="power off") - self._title_header = TermsHeader("make sure comma four can see your face", gui_app.texture("icons_mici/setup/orange_dm.png", 60, 60)) - self._dm_label = UnifiedLabel("Re-mount if your face is occluded or driver monitoring has difficulty tracking your face.", 42, FontWeight.ROMAN) - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) - - -class TrainingGuideDMTutorial(Widget): +class DMBadFaceDetected(NavScroller): + def __init__(self): + super().__init__() + + back_button = BigPillButton("back") + back_button.set_click_callback(self.dismiss) + + self._scroller.add_widgets([ + GreyBigButton("looking for driver", "make sure comma\nfour can see your face", + gui_app.texture("icons_mici/setup/orange_dm.png", 64, 64)), + GreyBigButton("", "Remount if your face is blocked, or driver monitoring has difficulty tracking your face."), + back_button, + ]) + + +class TrainingGuideDMTutorial(NavWidget): PROGRESS_DURATION = 4 LOOKING_THRESHOLD_DEG = 30.0 - def __init__(self, continue_callback): + def __init__(self, continue_callback: Callable[[], None]): super().__init__() - self_ref = weakref.ref(self) - self._back_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_question.png", 28, 48)) - self._back_button.set_click_callback(lambda: self_ref() and self_ref()._show_bad_face_page()) + self._back_button.set_click_callback(lambda: gui_app.push_widget(self._bad_face_page)) + self._back_button.set_touch_valid_callback(lambda: self.enabled and not self.is_dismissing) # for nav stack self._good_button = SmallCircleIconButton(gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 42, 42)) + self._good_button.set_touch_valid_callback(lambda: self.enabled and not self.is_dismissing) # for nav stack - # Wrap the continue callback to restore settings - def wrapped_continue_callback(): - device.set_offroad_brightness(None) - continue_callback() - - self._good_button.set_click_callback(wrapped_continue_callback) + self._good_button.set_click_callback(continue_callback) self._good_button.set_enabled(False) self._progress = FirstOrderFilter(0.0, 0.5, 1 / gui_app.target_fps) self._dialog = DriverCameraSetupDialog() - self._bad_face_page = DMBadFaceDetected(HARDWARE.shutdown, lambda: self_ref() and self_ref()._hide_bad_face_page()) - self._should_show_bad_face_page = False + self._bad_face_page = DMBadFaceDetected() # Disable driver monitoring model when device times out for inactivity def inactivity_callback(): @@ -149,23 +114,11 @@ def inactivity_callback(): device.add_interactive_timeout_callback(inactivity_callback) - def _show_bad_face_page(self): - self._bad_face_page.show_event() - self.hide_event() - self._should_show_bad_face_page = True - - def _hide_bad_face_page(self): - self._bad_face_page.hide_event() - self.show_event() - self._should_show_bad_face_page = False - def show_event(self): super().show_event() self._dialog.show_event() self._progress.x = 0.0 - device.set_offroad_brightness(100) - def _update_state(self): super()._update_state() if device.awake and not ui_state.params.get_bool("IsDriverViewEnabled"): @@ -185,7 +138,8 @@ def _update_state(self): looking_center = False # stay at 100% once reached - if (dm_state.faceDetected and looking_center) or self._progress.x > 0.99: + in_bad_face = gui_app.get_active_widget() == self._bad_face_page + if ((dm_state.faceDetected and looking_center) or self._progress.x > 0.99) and not in_bad_face: slow = self._progress.x < 0.25 duration = self.PROGRESS_DURATION * 2 if slow else self.PROGRESS_DURATION self._progress.x += 1.0 / (duration * gui_app.target_fps) @@ -196,9 +150,6 @@ def _update_state(self): self._good_button.set_enabled(self._progress.x >= 0.999) def _render(self, _): - if self._should_show_bad_face_page: - return self._bad_face_page.render(self._rect) - self._dialog.render(self._rect) rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 80), @@ -255,228 +206,193 @@ def _render(self, _): )) # rounded border + rl.begin_scissor_mode(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height)) rl.draw_rectangle_rounded_lines_ex(self._rect, 0.2 * 1.02, 10, 50, rl.BLACK) + rl.end_scissor_mode() -class TrainingGuideRecordFront(SetupTermsPage): - def __init__(self, continue_callback): - def on_back(): - ui_state.params.put_bool("RecordFront", False) - continue_callback() +class TrainingGuideRecordFront(NavScroller): + def __init__(self, continue_callback: Callable[[], None]): + super().__init__() - def on_continue(): - ui_state.params.put_bool("RecordFront", True) - continue_callback() + def show_accept_dialog(): + def on_accept(): + ui_state.params.put_bool_nonblocking("RecordFront", True) + continue_callback() - super().__init__(on_continue, back_callback=on_back, back_text="no", continue_text="yes") - self._title_header = TermsHeader("improve driver monitoring", gui_app.texture("icons_mici/setup/green_dm.png", 60, 60)) + gui_app.push_widget(BigConfirmationDialogV2("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", exit_on_confirm=False, + confirm_callback=on_accept)) - self._dm_label = UnifiedLabel("Do you want to upload driver camera data?", 42, - FontWeight.ROMAN) + def show_decline_dialog(): + def on_decline(): + ui_state.params.put_bool_nonblocking("RecordFront", False) + continue_callback() - def show_event(self): - super().show_event() - # Disable driver monitoring model after last step - ui_state.params.put_bool("IsDriverViewEnabled", False) + gui_app.push_widget(BigConfirmationDialogV2("no, don't upload", "icons_mici/setup/cancel.png", exit_on_confirm=False, confirm_callback=on_decline)) - @property - def _content_height(self): - return self._dm_label.rect.y + self._dm_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._dm_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._dm_label.get_content_height(int(self._rect.width - 32)), - )) - - -class TrainingGuideAttentionNotice(SetupTermsPage): - def __init__(self, continue_callback): - super().__init__(continue_callback, continue_text="continue") - self._title_header = TermsHeader("driver assistance", gui_app.texture("icons_mici/setup/warning.png", 60, 60)) - self._warning_label = UnifiedLabel("1. openpilot is a driver assistance system.\n\n" + - "2. You must pay attention at all times.\n\n" + - "3. You must be ready to take over at any time.\n\n" + - "4. You are fully responsible for driving the car.", 42, - FontWeight.ROMAN) + self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") + self._accept_button.set_click_callback(show_accept_dialog) - @property - def _content_height(self): - return self._warning_label.rect.y + self._warning_label.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16 + scroll_offset, - self._title_header.rect.width, - self._title_header.rect.height, - )) - - self._warning_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + 16, - self._rect.width - 32, - self._warning_label.get_content_height(int(self._rect.width - 32)), - )) - - -class TrainingGuide(Widget): - def __init__(self, completed_callback=None): + self._decline_button = BigCircleButton("icons_mici/setup/cancel.png") + self._decline_button.set_click_callback(show_decline_dialog) + + self._scroller.add_widgets([ + GreyBigButton("driver camera data", "do you want to share video data for training?", + gui_app.texture("icons_mici/setup/green_dm.png", 64, 64)), + GreyBigButton("", "Sharing your data with comma helps improve openpilot for everyone."), + self._accept_button, + self._decline_button, + ]) + + +class TrainingGuideAttentionNotice(Scroller): + def __init__(self, continue_callback: Callable[[], None]): super().__init__() - self._completed_callback = completed_callback - self._step = 0 - self_ref = weakref.ref(self) + continue_button = BigPillButton("next") + continue_button.set_click_callback(continue_callback) + + self._scroller.add_widgets([ + GreyBigButton("what is openpilot?", "scroll to continue", + gui_app.texture("icons_mici/setup/green_info.png", 64, 64)), + GreyBigButton("", "1. openpilot is a driver assistance system."), + GreyBigButton("", "2. You must pay attention at all times."), + GreyBigButton("", "3. You must be ready to take over at any time."), + GreyBigButton("", "4. You are fully responsible for driving the car."), + continue_button, + ]) - def on_continue(): - if obj := self_ref(): - obj._advance_step() + +class TrainingGuide(NavWidget): + def __init__(self, completed_callback: Callable[[], None]): + super().__init__() self._steps = [ - TrainingGuideAttentionNotice(continue_callback=on_continue), - TrainingGuidePreDMTutorial(continue_callback=on_continue), - TrainingGuideDMTutorial(continue_callback=on_continue), - TrainingGuideRecordFront(continue_callback=on_continue), + TrainingGuideAttentionNotice(continue_callback=lambda: gui_app.push_widget(self._steps[1])), + TrainingGuidePreDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[2])), + TrainingGuideDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[3])), + TrainingGuideRecordFront(continue_callback=completed_callback), ] + self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + def show_event(self): super().show_event() - device.set_override_interactive_timeout(300) - - def hide_event(self): - super().hide_event() - device.set_override_interactive_timeout(None) - - def _advance_step(self): - if self._step < len(self._steps) - 1: - self._step += 1 - self._steps[self._step].show_event() - else: - self._step = 0 - if self._completed_callback: - self._completed_callback() + self._steps[0].show_event() def _render(self, _): - rl.draw_rectangle_rec(self._rect, rl.BLACK) - if self._step < len(self._steps): - self._steps[self._step].render(self._rect) + self._steps[0].render(self._rect) -class DeclinePage(Widget): - def __init__(self, back_callback=None): +class QRCodeWidget(Widget): + def __init__(self, url: str, size: int = 170): super().__init__() - self._uninstall_slider = SmallSlider("uninstall openpilot", self._on_uninstall) + self.set_rect(rl.Rectangle(0, 0, size, size)) + self._size = size + self._qr_texture: rl.Texture | None = None + self._generate_qr(url) - self._back_button = SmallButton("back") - self._back_button.set_click_callback(back_callback) + def _generate_qr(self, url: str): + qr = qrcode.QRCode(version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=0) + qr.add_data(url) + qr.make(fit=True) - self._warning_header = TermsHeader("you must accept the\nterms to use openpilot", - gui_app.texture("icons_mici/setup/red_warning.png", 66, 60)) + pil_img = qr.make_image(fill_color="white", back_color="black").convert('RGBA') + img_array = np.array(pil_img, dtype=np.uint8) - def _on_uninstall(self): - ui_state.params.put_bool("DoUninstall", True) - gui_app.request_close() + rl_image = rl.Image() + rl_image.data = rl.ffi.cast("void *", img_array.ctypes.data) + rl_image.width = pil_img.width + rl_image.height = pil_img.height + rl_image.mipmaps = 1 + rl_image.format = rl.PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 + + self._qr_texture = rl.load_texture_from_image(rl_image) def _render(self, _): - self._warning_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16, - self._warning_header.rect.width, - self._warning_header.rect.height, - )) - - self._back_button.set_opacity(1 - self._uninstall_slider.slider_percentage) - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) - - self._uninstall_slider.render(rl.Rectangle( - self._rect.x + self._rect.width - self._uninstall_slider.rect.width, - self._rect.y + self._rect.height - self._uninstall_slider.rect.height, - self._uninstall_slider.rect.width, - self._uninstall_slider.rect.height, - )) - - -class TermsPage(SetupTermsPage): - def __init__(self, on_accept=None, on_decline=None): - super().__init__(on_accept, on_decline, "decline") - - info_txt = gui_app.texture("icons_mici/setup/green_info.png", 60, 60) - self._title_header = TermsHeader("terms & conditions", info_txt) - - self._terms_label = UnifiedLabel("You must accept the Terms and Conditions to use openpilot. " + - "Read the latest terms at https://comma.ai/terms before continuing.", 36, - FontWeight.ROMAN) + if self._qr_texture: + scale = self._size / self._qr_texture.height + rl.draw_texture_ex(self._qr_texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, scale, rl.WHITE) - @property - def _content_height(self): - return self._terms_label.rect.y + self._terms_label.rect.height - self._scroll_panel.get_offset() + def __del__(self): + if self._qr_texture and self._qr_texture.id != 0: + rl.unload_texture(self._qr_texture) - def _render_content(self, scroll_offset): - self._title_header.set_position(self._rect.x + 16, self._rect.y + 12 + scroll_offset) - self._title_header.render() - self._terms_label.render(rl.Rectangle( - self._rect.x + 16, - self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, - self._rect.width - 100, - self._terms_label.get_content_height(int(self._rect.width - 100)), - )) +class TermsPage(Scroller): + def __init__(self, on_accept, on_decline): + super().__init__() + + def show_accept_dialog(): + gui_app.push_widget(BigConfirmationDialogV2("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", + confirm_callback=on_accept)) + + def show_decline_dialog(): + gui_app.push_widget(BigConfirmationDialogV2("decline &\nuninstall", "icons_mici/setup/cancel.png", + red=True, exit_on_confirm=False, confirm_callback=on_decline)) + + self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") + self._accept_button.set_click_callback(show_accept_dialog) + + self._decline_button = BigCircleButton("icons_mici/setup/cancel.png", red=True) + self._decline_button.set_click_callback(show_decline_dialog) + + self._scroller.add_widgets([ + GreyBigButton("terms and\nconditions", "scroll to continue", + gui_app.texture("icons_mici/setup/green_info.png", 64, 64)), + GreyBigButton("swipe for QR code", "or go to https://comma.ai/terms", + gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True)), + QRCodeWidget("https://comma.ai/terms"), + GreyBigButton("", "You must accept the Terms & Conditions to use openpilot."), + self._accept_button, + self._decline_button, + ]) + + def _render(self, _): + rl.draw_rectangle_rec(self._rect, rl.BLACK) + super()._render(_) class OnboardingWindow(Widget): - def __init__(self): + def __init__(self, completed_callback: Callable[[], None]): super().__init__() + self._completed_callback = completed_callback self._accepted_terms: bool = ui_state.params.get("HasAcceptedTerms") == terms_version self._training_done: bool = ui_state.params.get("CompletedTrainingVersion") == training_version - self._state = OnboardingState.TERMS if not self._accepted_terms else OnboardingState.ONBOARDING - self.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) # Windows - self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_terms_declined) + self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_uninstall) + self._terms.set_enabled(lambda: self.enabled) # for nav stack self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) - self._decline_page = DeclinePage(back_callback=self._on_decline_back) + self._training_guide.set_enabled(lambda: self.enabled) # for nav stack + + def _on_uninstall(self): + ui_state.params.put_bool("DoUninstall", True) def show_event(self): super().show_event() device.set_override_interactive_timeout(300) + device.set_offroad_brightness(100) def hide_event(self): super().hide_event() + # FIXME: when nav stack sends hide event to widget 2 below on push, this needs to be moved device.set_override_interactive_timeout(None) + device.set_offroad_brightness(None) @property def completed(self) -> bool: return self._accepted_terms and self._training_done - def _on_terms_declined(self): - self._state = OnboardingState.DECLINE - - def _on_decline_back(self): - self._state = OnboardingState.TERMS - def close(self): - ui_state.params.put_bool("IsDriverViewEnabled", False) - gui_app.pop_widget() + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", False) + self._completed_callback() def _on_terms_accepted(self): ui_state.params.put("HasAcceptedTerms", terms_version) - self._state = OnboardingState.ONBOARDING + gui_app.push_widget(self._training_guide) def _on_completed_training(self): ui_state.params.put("CompletedTrainingVersion", training_version) @@ -484,9 +400,4 @@ def _on_completed_training(self): def _render(self, _): rl.draw_rectangle_rec(self._rect, rl.BLACK) - if self._state == OnboardingState.TERMS: - self._terms.render(self._rect) - elif self._state == OnboardingState.ONBOARDING: - self._training_guide.render(self._rect) - elif self._state == OnboardingState.DECLINE: - self._decline_page.render(self._rect) + self._terms.render(self._rect) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index b7ee5b6f45a..ed29a6a84ac 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -13,15 +13,39 @@ from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage +from openpilot.system.ui.mici_setup import BigPillButton from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget -from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.system.ui.widgets.label import MiciLabel from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID +class ReviewTermsPage(TermsPage, NavScroller): + """TermsPage with NavWidget swipe-to-dismiss for reviewing in device settings.""" + def __init__(self): + super().__init__(on_accept=self.dismiss, on_decline=self.dismiss) + self._accept_button.set_visible(False) + self._decline_button.set_visible(False) + + close_button = BigPillButton("close") + close_button.set_click_callback(self.dismiss) + self._scroller.add_widget(close_button) + + +class ReviewTrainingGuide(TrainingGuide): + def show_event(self): + super().show_event() + device.set_override_interactive_timeout(300) + + def hide_event(self): + super().hide_event() + device.set_override_interactive_timeout(None) + ui_state.params.put_bool_nonblocking("IsDriverViewEnabled", False) + + class MiciFccModal(NavRawScrollPanel): def __init__(self, file_path: str | None = None, text: str | None = None): super().__init__() @@ -311,11 +335,11 @@ def uninstall_openpilot_callback(): driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png") - review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(TrainingGuide(completed_callback=gui_app.pop_widget))) + review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTrainingGuide(completed_callback=lambda: gui_app.pop_widgets_to(self)))) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png") - terms_btn.set_click_callback(lambda: gui_app.push_widget(TermsPage(on_accept=gui_app.pop_widget))) + terms_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTermsPage())) terms_btn.set_enabled(lambda: ui_state.is_offroad()) self._scroller.add_widgets([ diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index be12839cd7c..ea7af84293a 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -68,7 +68,9 @@ def test_dialogs_do_not_leak(): for ctor in ( # mici - MiciDriverCameraDialog, MiciTrainingGuide, MiciOnboardingWindow, MiciPairingDialog, + MiciDriverCameraDialog, MiciPairingDialog, + lambda: MiciTrainingGuide(lambda: None), + lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), lambda: BigConfirmationDialogV2("test", "icons_mici/settings/network/new/trash.png"), lambda: BigInputDialog("test"), diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index b5bd65e2de8..5559a1181c9 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -120,6 +120,7 @@ def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "" self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) self._click_delay = 0.075 self._shake_start: float | None = None + self._grow_animation_until: float | None = None self._rotate_icon_t: float | None = None @@ -145,6 +146,9 @@ def _load_images(self): self._txt_pressed_bg = gui_app.texture("icons_mici/buttons/button_rectangle_pressed.png", 402, 180) self._txt_disabled_bg = gui_app.texture("icons_mici/buttons/button_rectangle_disabled.png", 402, 180) + def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: + super().set_touch_valid_callback(lambda: touch_callback() and self._grow_animation_until is None) + def _width_hint(self) -> int: # Single line if scrolling, so hide behind icon if exists icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0 @@ -182,6 +186,9 @@ def get_text(self): def trigger_shake(self): self._shake_start = rl.get_time() + def trigger_grow_animation(self, duration: float = 0.65): + self._grow_animation_until = rl.get_time() + duration + @property def _shake_offset(self) -> float: SHAKE_DURATION = 0.5 @@ -197,6 +204,10 @@ def set_position(self, x: float, y: float) -> None: super().set_position(x + self._shake_offset, y) def _handle_background(self) -> tuple[rl.Texture, float, float, float]: + if self._grow_animation_until is not None: + if rl.get_time() >= self._grow_animation_until: + self._grow_animation_until = None + # draw _txt_default_bg txt_bg = self._txt_default_bg if not self.enabled: @@ -204,7 +215,7 @@ def _handle_background(self) -> tuple[rl.Texture, float, float, float]: elif self.is_pressed: txt_bg = self._txt_pressed_bg - scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed else 1.0) + scale = self._scale_filter.update(PRESSED_SCALE if self.is_pressed or self._grow_animation_until is not None else 1.0) btn_x = self._rect.x + (self._rect.width * (1 - scale)) / 2 btn_y = self._rect.y + (self._rect.height * (1 - scale)) / 2 return txt_bg, btn_x, btn_y, scale diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 98e05e1127c..8bb919cfe1a 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -431,6 +431,9 @@ def get_active_widget(self): return self._nav_stack[-1] return None + def widget_in_stack(self, widget: object) -> bool: + return widget in self._nav_stack + def add_nav_stack_tick(self, tick_function: Callable[[], None]): if tick_function not in self._nav_stack_ticks: self._nav_stack_ticks.append(tick_function) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 5761fd12120..22ba8c40329 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from abc import abstractmethod import os import re import threading @@ -14,21 +13,21 @@ from cereal import log from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.common.swaglog import cloudlog from openpilot.common.utils import run_cmd -from openpilot.system.hardware import HARDWARE, TICI from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager -from openpilot.system.ui.lib.scroll_panel2 import GuiScrollPanel2 from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.button import (IconButton, SmallButton, WideRoundedButton, SmallerRoundedButton, - SmallCircleIconButton, WidishRoundedButton, FullRoundedButton) +from openpilot.system.ui.widgets.button import SmallButton from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPACING from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider -from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiUIMici +from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton, WifiUIMici from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog +from openpilot.selfdrive.ui.mici.widgets.button import BigButton NetworkType = log.DeviceState.NetworkType @@ -122,9 +121,9 @@ def __init__(self, use_openpilot_callback: Callable, use_custom_software_callback: Callable): super().__init__() - self._openpilot_slider = LargerSlider("slide to use\nopenpilot", use_openpilot_callback) + self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to use\ncustom software", use_custom_software_callback, green=False) + self._custom_software_slider = LargerSlider("slide to install\nother software", use_custom_software_callback, green=False) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -161,199 +160,24 @@ def _render(self, rect: rl.Rectangle): self._custom_software_slider.render(custom_software_rect) -class TermsHeader(Widget): - def __init__(self, text: str, icon_texture: rl.Texture): - super().__init__() - - self._title = UnifiedLabel(text, 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.BOLD, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, - line_height=0.8) - self._icon_texture = icon_texture - - self.set_rect(rl.Rectangle(0, 0, gui_app.width - 16 * 2, self._icon_texture.height)) - - def set_title(self, text: str): - self._title.set_text(text) - - def set_icon(self, icon_texture: rl.Texture): - self._icon_texture = icon_texture - - def _render(self, _): - rl.draw_texture_ex(self._icon_texture, rl.Vector2(self._rect.x, self._rect.y), - 0.0, 1.0, rl.WHITE) - - # May expand outside parent rect - title_content_height = self._title.get_content_height(int(self._rect.width - self._icon_texture.width - 16)) - title_rect = rl.Rectangle( - self._rect.x + self._icon_texture.width + 16, - self._rect.y + (self._rect.height - title_content_height) / 2, - self._rect.width - self._icon_texture.width - 16, - title_content_height, - ) - self._title.render(title_rect) - - -class TermsPage(Widget): - ITEM_SPACING = 20 - - def __init__(self, continue_callback: Callable, back_callback: Callable | None = None, - back_text: str = "back", continue_text: str = "accept"): - super().__init__() - - # TODO: use Scroller - self._scroll_panel = GuiScrollPanel2(horizontal=False) - - self._continue_text = continue_text - self._continue_slider: bool = continue_text in ("reboot", "power off") - self._continue_button: WideRoundedButton | FullRoundedButton | SmallSlider - if self._continue_slider: - self._continue_button = SmallSlider(continue_text, confirm_callback=continue_callback) - self._scroll_panel.set_enabled(lambda: not self._continue_button.is_pressed) - elif back_callback is not None: - self._continue_button = WideRoundedButton(continue_text) - else: - self._continue_button = FullRoundedButton(continue_text) - self._continue_button.set_enabled(False) - self._continue_button.set_opacity(0.0) - self._continue_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid) - if not self._continue_slider: - self._continue_button.set_click_callback(continue_callback) - - self._enable_back = back_callback is not None - self._back_button = SmallButton(back_text) - self._back_button.set_opacity(0.0) - self._back_button.set_touch_valid_callback(self._scroll_panel.is_touch_valid) - self._back_button.set_click_callback(back_callback) - - self._scroll_down_indicator = IconButton(gui_app.texture("icons_mici/setup/scroll_down_indicator.png", 64, 78)) - self._scroll_down_indicator.set_enabled(False) - - def reset(self): - self._scroll_panel.set_offset(0) - self._continue_button.set_enabled(False) - self._continue_button.set_opacity(0.0) - self._back_button.set_enabled(False) - self._back_button.set_opacity(0.0) - self._scroll_down_indicator.set_opacity(1.0) - - def show_event(self): - super().show_event() - self.reset() - - @property - @abstractmethod - def _content_height(self): - pass - - @property - def _scrolled_down_offset(self): - return -self._content_height + (self._continue_button.rect.height + 16 + 30) - - @abstractmethod - def _render_content(self, scroll_offset): - pass - - def _render(self, _): - rl.draw_rectangle_rec(self._rect, rl.BLACK) - scroll_offset = round(self._scroll_panel.update(self._rect, self._content_height + self._continue_button.rect.height + 16)) - - if scroll_offset <= self._scrolled_down_offset: - # don't show back if not enabled - if self._enable_back: - self._back_button.set_enabled(True) - self._back_button.set_opacity(1.0, smooth=True) - self._continue_button.set_enabled(True) - self._continue_button.set_opacity(1.0, smooth=True) - self._scroll_down_indicator.set_opacity(0.0, smooth=True) - else: - self._back_button.set_enabled(False) - self._back_button.set_opacity(0.0, smooth=True) - self._continue_button.set_enabled(False) - self._continue_button.set_opacity(0.0, smooth=True) - self._scroll_down_indicator.set_opacity(1.0, smooth=True) - - # Render content - self._render_content(scroll_offset) - - # black gradient at top and bottom for scrolling content - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y), - int(self._rect.width), 20, rl.BLACK, rl.BLANK) - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 20), - int(self._rect.width), 20, rl.BLANK, rl.BLACK) - - # fade out back button as slider is moved - if self._continue_slider and scroll_offset <= self._scrolled_down_offset: - self._back_button.set_opacity(1.0 - self._continue_button.slider_percentage) - self._back_button.set_visible(self._continue_button.slider_percentage < 0.99) - - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) - - continue_x = self._rect.x + 8 - if self._enable_back: - continue_x = self._rect.x + self._rect.width - self._continue_button.rect.width - 8 - if self._continue_slider: - continue_x += 8 - self._continue_button.render(rl.Rectangle( - continue_x, - self._rect.y + self._rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) - - self._scroll_down_indicator.render(rl.Rectangle( - self._rect.x + self._rect.width - self._scroll_down_indicator.rect.width - 8, - self._rect.y + self._rect.height - self._scroll_down_indicator.rect.height - 8, - self._scroll_down_indicator.rect.width, - self._scroll_down_indicator.rect.height, - )) - - -class CustomSoftwareWarningPage(TermsPage): +class CustomSoftwareWarningPage(NavScroller): def __init__(self, continue_callback: Callable, back_callback: Callable): - super().__init__(continue_callback, back_callback) - - self._title_header = TermsHeader("use caution installing\n3rd party software", - gui_app.texture("icons_mici/setup/warning.png", 66, 60)) - self._body = UnifiedLabel("• It has not been tested by comma.\n" + - "• It may not comply with relevant safety standards.\n" + - "• It may cause damage to your device and/or vehicle.\n", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) - - self._restore_header = TermsHeader("how to backup &\nrestore", gui_app.texture("icons_mici/setup/restore.png", 60, 60)) - self._restore_body = UnifiedLabel("To restore your device to a factory state later, use https://flash.comma.ai", - 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) - - @property - def _content_height(self): - return self._restore_body.rect.y + self._restore_body.rect.height - self._scroll_panel.get_offset() - - def _render_content(self, scroll_offset): - self._title_header.set_position(self._rect.x + 16, self._rect.y + 8 + scroll_offset) - self._title_header.render() - - body_rect = rl.Rectangle( - self._rect.x + 8, - self._title_header.rect.y + self._title_header.rect.height + self.ITEM_SPACING, - self._rect.width - 50, - self._body.get_content_height(int(self._rect.width - 50)), - ) - self._body.render(body_rect) + super().__init__() + self.set_back_callback(back_callback) - self._restore_header.set_position(self._rect.x + 16, self._body.rect.y + self._body.rect.height + self.ITEM_SPACING) - self._restore_header.render() + self._continue_button = BigPillButton("next") + self._continue_button.set_click_callback(continue_callback) - self._restore_body.render(rl.Rectangle( - self._rect.x + 8, - self._restore_header.rect.y + self._restore_header.rect.height + self.ITEM_SPACING, - self._rect.width - 50, - self._restore_body.get_content_height(int(self._rect.width - 50)), - )) + self._scroller.add_widgets([ + GreyBigButton("use caution", "when installing\n3rd party software", + gui_app.texture("icons_mici/setup/warning.png", 64, 58)), + GreyBigButton("", "• It has not been tested by comma"), + GreyBigButton("", "• It may not comply with relevant safety standards."), + GreyBigButton("", "• It may cause damage to your device and/or vehicle."), + GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai", + gui_app.texture("icons_mici/setup/restore.png", 64, 64)), + self._continue_button, + ]) class DownloadingPage(Widget): @@ -391,11 +215,9 @@ def _render(self, rect: rl.Rectangle): )) -class FailedPage(NavWidget): +class FailedPageBase(Widget): def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): super().__init__() - self.set_back_callback(retry_callback) - self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), @@ -446,11 +268,86 @@ def _render(self, rect: rl.Rectangle): )) -class NetworkSetupPage(NavWidget): +class FailedPage(FailedPageBase, NavWidget): + def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): + super().__init__(reboot_callback, retry_callback, title) + self.set_back_callback(retry_callback) + + +class GreyBigButton(BigButton): + """Users should manage newlines with this class themselves""" + + LABEL_HORIZONTAL_PADDING = 30 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_touch_valid_callback(lambda: False) + + self._rect.width = 476 + + self._label.set_font_size(36) + self._label.set_font_weight(FontWeight.BOLD) + self._label.set_line_height(1.0) + + self._sub_label.set_font_size(36) + self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR) + self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else + rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + self._sub_label.set_line_height(0.95) + + @property + def LABEL_VERTICAL_PADDING(self): + return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18 + + def _width_hint(self) -> int: + return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2) + + def _render(self, _): + rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15))) + self._draw_content(self._rect.y) + + +class BigPillButton(BigButton): + def __init__(self, *args, green: bool = False, disabled_background: bool = False, **kwargs): + self._green = green + self._disabled_background = disabled_background + super().__init__(*args, **kwargs) + + self._label.set_font_size(48) + self._label.set_alignment(rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + self._label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + def _load_images(self): + if self._green: + self._txt_default_bg = gui_app.texture("icons_mici/setup/start_button.png", 402, 180) + self._txt_pressed_bg = gui_app.texture("icons_mici/setup/start_button_pressed.png", 402, 180) + else: + self._txt_default_bg = gui_app.texture("icons_mici/setup/continue.png", 402, 180) + self._txt_pressed_bg = gui_app.texture("icons_mici/setup/continue_pressed.png", 402, 180) + self._txt_disabled_bg = gui_app.texture("icons_mici/setup/continue_disabled.png", 402, 180) + + def set_green(self, green: bool): + if self._green != green: + self._green = green + self._load_images() + + def _update_label_layout(self): + # Don't change label text size + pass + + def _handle_background(self) -> tuple[rl.Texture, float, float, float]: + txt_bg, btn_x, btn_y, scale = super()._handle_background() + + if self._disabled_background: + txt_bg = self._txt_disabled_bg + return txt_bg, btn_x, btn_y, scale + + +class NetworkSetupPageBase(Scroller): def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], - back_callback: Callable[[], None] | None): + disable_connect_hint: bool = False): super().__init__() - self.set_back_callback(back_callback) self._wifi_manager = WifiManager() self._wifi_manager.set_active(True) @@ -459,83 +356,106 @@ def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callbac self._prev_has_internet = False self._wifi_ui = WifiUIMici(self._wifi_manager) - self._no_wifi_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_slash.png", 58, 50) - self._wifi_full_txt = gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 58, 50) - self._waiting_text = "waiting for internet..." - self._network_header = TermsHeader(self._waiting_text, self._no_wifi_txt) - - back_txt = gui_app.texture("icons_mici/setup/back_new.png", 37, 32) - self._back_button = SmallCircleIconButton(back_txt) - self._back_button.set_click_callback(back_callback) - self._back_button.set_enabled(lambda: self.enabled) # for nav stack + self._connect_button = GreyBigButton("connect to\ninternet", "swipe down to go back", + gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True)) + self._connect_button.set_visible(not disable_connect_hint) - self._wifi_button = SmallerRoundedButton("wifi") + self._wifi_button = WifiNetworkButton(self._wifi_manager) self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) - self._wifi_button.set_enabled(lambda: self.enabled) - self._continue_button = WidishRoundedButton("continue") - self._continue_button.set_enabled(False) + self._show_time = 0.0 + self._pending_has_internet_scroll = False + self._pending_continue_grow_animation = False + self._pending_wifi_grow_animation = False + + def on_waiting_click(): + offset = (self._wifi_button.rect.x + self._wifi_button.rect.width / 2) - (self._rect.x + self._rect.width / 2) + self._scroller.scroll_to(offset, smooth=True, block_interaction=True) + # trigger grow when wifi button in view + self._pending_wifi_grow_animation = True + + self._waiting_button = BigPillButton("waiting for\ninternet...", disabled_background=True) + self._waiting_button.set_click_callback(on_waiting_click) + self._continue_button = BigPillButton("install openpilot", green=True) self._continue_button.set_click_callback(lambda: continue_callback(self._custom_software)) + self._scroller.add_widgets([ + self._connect_button, + self._wifi_button, + self._continue_button, + self._waiting_button, + ]) + gui_app.add_nav_stack_tick(self._nav_stack_tick) def show_event(self): super().show_event() + self._show_time = rl.get_time() self._prev_has_internet = False - self._network_monitor.reset() - self._set_has_internet(False) + self._pending_has_internet_scroll = False + self._pending_continue_grow_animation = False + self._pending_wifi_grow_animation = False def _nav_stack_tick(self): self._wifi_manager.process_callbacks() has_internet = self._network_monitor.network_connected.is_set() - if has_internet != self._prev_has_internet: - self._set_has_internet(has_internet) - if has_internet: - gui_app.pop_widgets_to(self) - self._prev_has_internet = has_internet - - def _set_has_internet(self, has_internet: bool): - if has_internet: - self._network_header.set_title("connected to internet") - self._network_header.set_icon(self._wifi_full_txt) - self._continue_button.set_enabled(lambda: self.enabled) - else: - self._network_header.set_title(self._waiting_text) - self._network_header.set_icon(self._no_wifi_txt) - self._continue_button.set_enabled(False) + if has_internet and not self._prev_has_internet: + self._pending_has_internet_scroll = True + self._prev_has_internet = has_internet + + if self._pending_has_internet_scroll: + # Scrolls over to continue button, then grows once in view + elapsed = rl.get_time() - self._show_time + if elapsed > 0.5: + self._pending_has_internet_scroll = False + + def scroll_to_download(): + self._scroller._layout() + end_offset = -(self._scroller.content_size - self._rect.width) + remaining = self._scroller.scroll_panel.get_offset() - end_offset + self._scroller.scroll_to(remaining, smooth=True, block_interaction=True) + self._pending_continue_grow_animation = True + + # Animate WifiUi down first before scroll + gui_app.pop_widgets_to(self, scroll_to_download) def set_custom_software(self, custom_software: bool): self._custom_software = custom_software + self._continue_button.set_text("install openpilot" if not custom_software else "choose software") + self._continue_button.set_green(not custom_software) - def _render(self, _): - self._network_header.render(rl.Rectangle( - self._rect.x + 16, - self._rect.y + 16, - self._rect.width - 32, - self._network_header.rect.height, - )) + def set_is_updater(self): + self._continue_button.set_text("download\n& install") + self._continue_button.set_green(False) - self._back_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._back_button.rect.height, - self._back_button.rect.width, - self._back_button.rect.height, - )) + def _update_state(self): + super()._update_state() - self._wifi_button.render(rl.Rectangle( - self._rect.x + 8 + self._back_button.rect.width + 10, - self._rect.y + self._rect.height - self._wifi_button.rect.height, - self._wifi_button.rect.width, - self._wifi_button.rect.height, - )) + if self._pending_continue_grow_animation: + btn_right = self._continue_button.rect.x + self._continue_button.rect.width + visible_right = self._rect.x + self._rect.width + if btn_right < visible_right + 50: + self._pending_continue_grow_animation = False + self._continue_button.trigger_grow_animation() + + if self._pending_wifi_grow_animation and abs(self._wifi_button.rect.x - ITEM_SPACING) < 50: + self._pending_wifi_grow_animation = False + self._wifi_button.trigger_grow_animation() + + if self._network_monitor.network_connected.is_set(): + self._continue_button.set_visible(True) + self._waiting_button.set_visible(False) + else: + self._continue_button.set_visible(False) + self._waiting_button.set_visible(True) - self._continue_button.render(rl.Rectangle( - self._rect.x + self._rect.width - self._continue_button.rect.width - 8, - self._rect.y + self._rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) + +class NetworkSetupPage(NetworkSetupPageBase, NavScroller): + def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], + back_callback: Callable[[], None] | None): + super().__init__(network_monitor, continue_callback) + self.set_back_callback(back_callback) class Setup(Widget): @@ -557,13 +477,13 @@ def getting_started_button_callback(): self._start_page.set_click_callback(getting_started_button_callback) self._start_page.set_enabled(lambda: self.enabled) # for nav stack - self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_button_callback, - self._pop_to_software_selection) + self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_callback, self._pop_to_software_selection) + self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) self._download_failed_page = FailedPage(HARDWARE.reboot, self._pop_to_software_selection) - self._custom_software_warning_page = CustomSoftwareWarningPage(self._software_selection_custom_software_continue, self._pop_to_software_selection) + self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection) self._downloading_page = DownloadingPage() @@ -602,17 +522,14 @@ def _use_openpilot(self): time.sleep(0.1) gui_app.request_close() else: - self._push_network_setup(custom_software=False) + self._push_network_setup() - def _push_network_setup(self, custom_software: bool): + def _push_network_setup(self, custom_software: bool = False): + # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) - gui_app.push_widget(self._network_setup_page) - - def _software_selection_custom_software_continue(self): - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders - self._push_network_setup(custom_software=True) + gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._network_setup_page)) - def _network_setup_continue_button_callback(self, custom_software): + def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(OPENPILOT_URL) diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index c98b3107097..50ea4129252 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -7,11 +7,10 @@ from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.button import FullRoundedButton -from openpilot.system.ui.mici_setup import NetworkSetupPage, FailedPage, NetworkConnectivityMonitor +from openpilot.system.ui.mici_setup import NetworkSetupPageBase, FailedPageBase, NetworkConnectivityMonitor class Screen(IntEnum): @@ -32,16 +31,14 @@ def __init__(self, updater_path, manifest_path): self.progress_text = "loading" self.process = None self.update_thread = None - self._wifi_manager = WifiManager() - self._wifi_manager.set_active(True) - - self._network_setup_page = NetworkSetupPage(self._wifi_manager, self._network_setup_continue_callback, - self._network_setup_back_callback) - self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack - self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() + self._network_setup_page = NetworkSetupPageBase(self._network_monitor, self._network_setup_continue_callback, + disable_connect_hint=True) + self._network_setup_page.set_is_updater() + self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack + # Buttons self._continue_button = FullRoundedButton("continue") self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI)) @@ -52,8 +49,8 @@ def __init__(self, updater_path, manifest_path): text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.ROMAN) - self._update_failed_page = FailedPage(HARDWARE.reboot, self._update_failed_retry_callback, - title="update failed") + self._update_failed_page = FailedPageBase(HARDWARE.reboot, self._update_failed_retry_callback, + title="update failed") self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY, line_height=0.8) @@ -61,10 +58,7 @@ def __init__(self, updater_path, manifest_path): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) - def _network_setup_back_callback(self): - self.set_current_screen(Screen.PROMPT) - - def _network_setup_continue_callback(self): + def _network_setup_continue_callback(self, _): self.install_update() def _update_failed_retry_callback(self): @@ -160,14 +154,10 @@ def render_progress_screen(self, rect: rl.Rectangle): rect.height, )) - def _update_state(self): - self._wifi_manager.process_callbacks() - def _render(self, rect: rl.Rectangle): if self.current_screen == Screen.PROMPT: self.render_prompt_screen(rect) elif self.current_screen == Screen.WIFI: - self._network_setup_page.set_has_internet(self._network_monitor.network_connected.is_set()) self._network_setup_page.render(rect) elif self.current_screen == Screen.PROGRESS: self.render_progress_screen(rect) diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 67125d70919..a608344710a 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -228,6 +228,7 @@ def _render(self, _): class SmallButton(Widget): def __init__(self, text: str): super().__init__() + self._click_delay = 0.075 self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) self._load_assets() diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 67203d53f49..3292c53ce8b 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -63,7 +63,7 @@ def __init__(self): self._playing_dismiss_animation = False # released and animating away self._y_pos_filter = BounceFilter(0.0, 0.1, 1 / gui_app.target_fps, bounce=1) - self._back_callback: Callable[[], None] | None = None # persistent callback for any back navigation + self._back_callback: Callable[[], None] | None = None # persistent callback for user-initiated back navigation self._dismiss_callback: Callable[[], None] | None = None # transient callback for programmatic dismiss # TODO: move this state into NavBar @@ -150,12 +150,12 @@ def _update_state(self): if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: gui_app.pop_widget() - if self._back_callback is not None: - self._back_callback() - + # Only one callback should ever be fired if self._dismiss_callback is not None: self._dismiss_callback() self._dismiss_callback = None + elif self._back_callback is not None: + self._back_callback() self._playing_dismiss_animation = False self._drag_start_pos = None From 92f9684fdb79a14a8a95d3755390e43abc4131a1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Mar 2026 01:13:11 -0800 Subject: [PATCH 449/685] Revert "use vendored raylib from dependencies repo" (#37537) Revert "use vendored raylib from dependencies repo (#37489)" This reverts commit 037497939778c3fff8d721b60b7c85ac7f995683. --- SConstruct | 3 +- pyproject.toml | 2 +- scripts/lint/check_raylib_includes.sh | 10 + selfdrive/ui/SConscript | 9 +- selfdrive/ui/installer/installer.cc | 2 +- third_party/raylib/.gitignore | 4 + third_party/raylib/Darwin/libraylib.a | 3 + third_party/raylib/build.sh | 93 + third_party/raylib/include/raygui.h | 5759 ++++++++++++++++++++++++ third_party/raylib/include/raylib.h | 1766 ++++++++ third_party/raylib/include/raymath.h | 2949 ++++++++++++ third_party/raylib/include/rlgl.h | 5262 ++++++++++++++++++++++ third_party/raylib/larch64/libraylib.a | 3 + third_party/raylib/x86_64/libraylib.a | 3 + uv.lock | 40 +- 15 files changed, 15882 insertions(+), 26 deletions(-) create mode 100755 scripts/lint/check_raylib_includes.sh create mode 100644 third_party/raylib/.gitignore create mode 100644 third_party/raylib/Darwin/libraylib.a create mode 100755 third_party/raylib/build.sh create mode 100644 third_party/raylib/include/raygui.h create mode 100644 third_party/raylib/include/raylib.h create mode 100644 third_party/raylib/include/raymath.h create mode 100644 third_party/raylib/include/rlgl.h create mode 100644 third_party/raylib/larch64/libraylib.a create mode 100644 third_party/raylib/x86_64/libraylib.a diff --git a/SConstruct b/SConstruct index 7003abbf05a..da70bc39247 100644 --- a/SConstruct +++ b/SConstruct @@ -48,10 +48,9 @@ if arch != "larch64": import ncurses import openssl3 import python3_dev - import raylib import zeromq import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, raylib, zeromq, zstd] + pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs diff --git a/pyproject.toml b/pyproject.toml index d0020b21e7e..6516c8cd5bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ dependencies = [ "zstandard", # ui - "raylib @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=raylib", + "raylib > 5.5.0.3", "qrcode", "jeepney", ] diff --git a/scripts/lint/check_raylib_includes.sh b/scripts/lint/check_raylib_includes.sh new file mode 100755 index 00000000000..e3be73a4897 --- /dev/null +++ b/scripts/lint/check_raylib_includes.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +FAIL=0 + +if grep -n '#include "third_party/raylib/include/raylib\.h"' $@ | grep -v '^system/ui/raylib/raylib\.h'; then + echo -e "Bad raylib include found! Use '#include \"system/ui/raylib/raylib.h\"' instead\n" + FAIL=1 +fi + +exit $FAIL diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index bebfd0011fc..4d7448c62f8 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -16,17 +16,12 @@ env.Command( action=f"python3 {generator}", ) -try: - import raylib -except ImportError: - raylib = None -if GetOption('extras') and arch == "larch64" and raylib is not None: +if GetOption('extras') and arch == "larch64": # build installers if arch != "Darwin": raylib_env = env.Clone() - raylib_env['CPPPATH'] += [raylib.INCLUDE_DIR] - raylib_env['LIBPATH'] += [raylib.LIB_DIR] + raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') raylib_libs = common + ["raylib"] diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index ec7a4adc922..9c35b0465e7 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -6,7 +6,7 @@ #include "common/swaglog.h" #include "common/util.h" #include "system/hardware/hw.h" -#include "raylib.h" +#include "third_party/raylib/include/raylib.h" int freshClone(); int cachedFetch(const std::string &cache); diff --git a/third_party/raylib/.gitignore b/third_party/raylib/.gitignore new file mode 100644 index 00000000000..6b1d3ad7482 --- /dev/null +++ b/third_party/raylib/.gitignore @@ -0,0 +1,4 @@ +/raylib_repo/ +/raylib_python_repo/ +/wheel/ +!*.a diff --git a/third_party/raylib/Darwin/libraylib.a b/third_party/raylib/Darwin/libraylib.a new file mode 100644 index 00000000000..dd2e9b33f12 --- /dev/null +++ b/third_party/raylib/Darwin/libraylib.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fd045c1d4bca5c9b2ad044ea730826ff6cedeef0b64451b123717b136f1cd702 +size 6392532 diff --git a/third_party/raylib/build.sh b/third_party/raylib/build.sh new file mode 100755 index 00000000000..d20f9d33af1 --- /dev/null +++ b/third_party/raylib/build.sh @@ -0,0 +1,93 @@ +#!/usr/bin/env bash +set -e + +export SOURCE_DATE_EPOCH=0 +export ZERO_AR_DATE=1 + +SUDO="" + +# Use sudo if not root +if [[ ! $(id -u) -eq 0 ]]; then + if [[ -z $(which sudo) ]]; then + echo "Please install sudo or run as root" + exit 1 + fi + SUDO="sudo" +fi + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +RAYLIB_PLATFORM="PLATFORM_DESKTOP" + +ARCHNAME=$(uname -m) +if [ -f /TICI ]; then + ARCHNAME="larch64" + RAYLIB_PLATFORM="PLATFORM_COMMA" +elif [[ "$OSTYPE" == "linux"* ]]; then + # required dependencies on Linux PC + $SUDO apt install \ + libxcursor-dev \ + libxi-dev \ + libxinerama-dev \ + libxrandr-dev +fi + +if [[ "$OSTYPE" == "darwin"* ]]; then + ARCHNAME="Darwin" +fi + +INSTALL_DIR="$DIR/$ARCHNAME" +rm -rf $INSTALL_DIR +mkdir -p $INSTALL_DIR + +INSTALL_H_DIR="$DIR/include" +rm -rf $INSTALL_H_DIR +mkdir -p $INSTALL_H_DIR + +if [ ! -d raylib_repo ]; then + git clone -b master --no-tags https://github.com/commaai/raylib.git raylib_repo +fi + +cd raylib_repo + +COMMIT=${1:-3425bd9d1fb292ede4d80f97a1f4f258f614cffc} +git fetch origin $COMMIT +git reset --hard $COMMIT +git clean -xdff . + +cd src + +make -j$(nproc) PLATFORM=$RAYLIB_PLATFORM RAYLIB_RELEASE_PATH=$INSTALL_DIR +cp raylib.h raymath.h rlgl.h $INSTALL_H_DIR/ +echo "raylib development files installed/updated in $INSTALL_H_DIR" + +# this commit needs to be in line with raylib +set -x +RAYGUI_COMMIT="76b36b597edb70ffaf96f046076adc20d67e7827" +curl -fsSLo $INSTALL_H_DIR/raygui.h https://raw.githubusercontent.com/raysan5/raygui/$RAYGUI_COMMIT/src/raygui.h + +if [ -f /TICI ]; then + + # Building the python bindings + cd $DIR + + if [ ! -d raylib_python_repo ]; then + git clone -b master --no-tags https://github.com/commaai/raylib-python-cffi.git raylib_python_repo + fi + + cd raylib_python_repo + + BINDINGS_COMMIT="a0710d95af3c12fd7f4b639589be9a13dad93cb6" + git fetch origin $BINDINGS_COMMIT + git reset --hard $BINDINGS_COMMIT + git clean -xdff . + + RAYLIB_PLATFORM=$RAYLIB_PLATFORM RAYLIB_INCLUDE_PATH=$INSTALL_H_DIR RAYLIB_LIB_PATH=$INSTALL_DIR python setup.py bdist_wheel + cd $DIR + + rm -rf wheel + mkdir wheel + cp raylib_python_repo/dist/*.whl wheel/ + +fi diff --git a/third_party/raylib/include/raygui.h b/third_party/raylib/include/raygui.h new file mode 100644 index 00000000000..fe233a16cf6 --- /dev/null +++ b/third_party/raylib/include/raygui.h @@ -0,0 +1,5759 @@ +/******************************************************************************************* +* +* raygui v4.5-dev - A simple and easy-to-use immediate-mode gui library +* +* DESCRIPTION: +* raygui is a tools-dev-focused immediate-mode-gui library based on raylib but also +* available as a standalone library, as long as input and drawing functions are provided. +* +* FEATURES: +* - Immediate-mode gui, minimal retained data +* - +25 controls provided (basic and advanced) +* - Styling system for colors, font and metrics +* - Icons supported, embedded as a 1-bit icons pack +* - Standalone mode option (custom input/graphics backend) +* - Multiple support tools provided for raygui development +* +* POSSIBLE IMPROVEMENTS: +* - Better standalone mode API for easy plug of custom backends +* - Externalize required inputs, allow user easier customization +* +* LIMITATIONS: +* - No editable multi-line word-wraped text box supported +* - No auto-layout mechanism, up to the user to define controls position and size +* - Standalone mode requires library modification and some user work to plug another backend +* +* NOTES: +* - WARNING: GuiLoadStyle() and GuiLoadStyle{Custom}() functions, allocate memory for +* font atlas recs and glyphs, freeing that memory is (usually) up to the user, +* no unload function is explicitly provided... but note that GuiLoadStyleDefault() unloads +* by default any previously loaded font (texture, recs, glyphs). +* - Global UI alpha (guiAlpha) is applied inside GuiDrawRectangle() and GuiDrawText() functions +* +* CONTROLS PROVIDED: +* # Container/separators Controls +* - WindowBox --> StatusBar, Panel +* - GroupBox --> Line +* - Line +* - Panel --> StatusBar +* - ScrollPanel --> StatusBar +* - TabBar --> Button +* +* # Basic Controls +* - Label +* - LabelButton --> Label +* - Button +* - Toggle +* - ToggleGroup --> Toggle +* - ToggleSlider +* - CheckBox +* - ComboBox +* - DropdownBox +* - TextBox +* - ValueBox --> TextBox +* - Spinner --> Button, ValueBox +* - Slider +* - SliderBar --> Slider +* - ProgressBar +* - StatusBar +* - DummyRec +* - Grid +* +* # Advance Controls +* - ListView +* - ColorPicker --> ColorPanel, ColorBarHue +* - MessageBox --> Window, Label, Button +* - TextInputBox --> Window, Label, TextBox, Button +* +* It also provides a set of functions for styling the controls based on its properties (size, color). +* +* +* RAYGUI STYLE (guiStyle): +* raygui uses a global data array for all gui style properties (allocated on data segment by default), +* when a new style is loaded, it is loaded over the global style... but a default gui style could always be +* recovered with GuiLoadStyleDefault() function, that overwrites the current style to the default one +* +* The global style array size is fixed and depends on the number of controls and properties: +* +* static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)]; +* +* guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +* +* Note that the first set of BASE properties (by default guiStyle[0..15]) belong to the generic style +* used for all controls, when any of those base values is set, it is automatically populated to all +* controls, so, specific control values overwriting generic style should be set after base values. +* +* After the first BASE set we have the EXTENDED properties (by default guiStyle[16..23]), those +* properties are actually common to all controls and can not be overwritten individually (like BASE ones) +* Some of those properties are: TEXT_SIZE, TEXT_SPACING, LINE_COLOR, BACKGROUND_COLOR +* +* Custom control properties can be defined using the EXTENDED properties for each independent control. +* +* TOOL: rGuiStyler is a visual tool to customize raygui style: github.com/raysan5/rguistyler +* +* +* RAYGUI ICONS (guiIcons): +* raygui could use a global array containing icons data (allocated on data segment by default), +* a custom icons set could be loaded over this array using GuiLoadIcons(), but loaded icons set +* must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS will be loaded +* +* Every icon is codified in binary form, using 1 bit per pixel, so, every 16x16 icon +* requires 8 integers (16*16/32) to be stored in memory. +* +* When the icon is draw, actually one quad per pixel is drawn if the bit for that pixel is set. +* +* The global icons array size is fixed and depends on the number of icons and size: +* +* static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS]; +* +* guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +* +* TOOL: rGuiIcons is a visual tool to customize/create raygui icons: github.com/raysan5/rguiicons +* +* RAYGUI LAYOUT: +* raygui currently does not provide an auto-layout mechanism like other libraries, +* layouts must be defined manually on controls drawing, providing the right bounds Rectangle for it. +* +* TOOL: rGuiLayout is a visual tool to create raygui layouts: github.com/raysan5/rguilayout +* +* CONFIGURATION: +* #define RAYGUI_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYGUI_STANDALONE +* Avoid raylib.h header inclusion in this file. Data types defined on raylib are defined +* internally in the library and input management and drawing functions must be provided by +* the user (check library implementation for further details). +* +* #define RAYGUI_NO_ICONS +* Avoid including embedded ricons data (256 icons, 16x16 pixels, 1-bit per pixel, 2KB) +* +* #define RAYGUI_CUSTOM_ICONS +* Includes custom ricons.h header defining a set of custom icons, +* this file can be generated using rGuiIcons tool +* +* #define RAYGUI_DEBUG_RECS_BOUNDS +* Draw control bounds rectangles for debug +* +* #define RAYGUI_DEBUG_TEXT_BOUNDS +* Draw text bounds rectangles for debug +* +* VERSIONS HISTORY: +* 4.5-dev (Sep-2024) Current dev version... +* ADDED: guiControlExclusiveMode and guiControlExclusiveRec for exclusive modes +* ADDED: GuiValueBoxFloat() +* ADDED: GuiDropdonwBox() properties: DROPDOWN_ARROW_HIDDEN, DROPDOWN_ROLL_UP +* ADDED: GuiListView() property: LIST_ITEMS_BORDER_WIDTH +* ADDED: Multiple new icons +* REVIEWED: GuiTabBar(), close tab with mouse middle button +* REVIEWED: GuiScrollPanel(), scroll speed proportional to content +* REVIEWED: GuiDropdownBox(), support roll up and hidden arrow +* REVIEWED: GuiTextBox(), cursor position initialization +* REVIEWED: GuiSliderPro(), control value change check +* REVIEWED: GuiGrid(), simplified implementation +* REVIEWED: GuiIconText(), increase buffer size and reviewed padding +* REVIEWED: GuiDrawText(), improved wrap mode drawing +* REVIEWED: GuiScrollBar(), minor tweaks +* REVIEWED: Functions descriptions, removed wrong return value reference +* REDESIGNED: GuiColorPanel(), improved HSV <-> RGBA convertion +* +* 4.0 (12-Sep-2023) ADDED: GuiToggleSlider() +* ADDED: GuiColorPickerHSV() and GuiColorPanelHSV() +* ADDED: Multiple new icons, mostly compiler related +* ADDED: New DEFAULT properties: TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE +* ADDED: New enum values: GuiTextAlignment, GuiTextAlignmentVertical, GuiTextWrapMode +* ADDED: Support loading styles with custom font charset from external file +* REDESIGNED: GuiTextBox(), support mouse cursor positioning +* REDESIGNED: GuiDrawText(), support multiline and word-wrap modes (read only) +* REDESIGNED: GuiProgressBar() to be more visual, progress affects border color +* REDESIGNED: Global alpha consideration moved to GuiDrawRectangle() and GuiDrawText() +* REDESIGNED: GuiScrollPanel(), get parameters by reference and return result value +* REDESIGNED: GuiToggleGroup(), get parameters by reference and return result value +* REDESIGNED: GuiComboBox(), get parameters by reference and return result value +* REDESIGNED: GuiCheckBox(), get parameters by reference and return result value +* REDESIGNED: GuiSlider(), get parameters by reference and return result value +* REDESIGNED: GuiSliderBar(), get parameters by reference and return result value +* REDESIGNED: GuiProgressBar(), get parameters by reference and return result value +* REDESIGNED: GuiListView(), get parameters by reference and return result value +* REDESIGNED: GuiColorPicker(), get parameters by reference and return result value +* REDESIGNED: GuiColorPanel(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarAlpha(), get parameters by reference and return result value +* REDESIGNED: GuiColorBarHue(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), get parameters by reference and return result value +* REDESIGNED: GuiGrid(), added extra parameter +* REDESIGNED: GuiListViewEx(), change parameters order +* REDESIGNED: All controls return result as int value +* REVIEWED: GuiScrollPanel() to avoid smallish scroll-bars +* REVIEWED: All examples and specially controls_test_suite +* RENAMED: gui_file_dialog module to gui_window_file_dialog +* UPDATED: All styles to include ISO-8859-15 charset (as much as possible) +* +* 3.6 (10-May-2023) ADDED: New icon: SAND_TIMER +* ADDED: GuiLoadStyleFromMemory() (binary only) +* REVIEWED: GuiScrollBar() horizontal movement key +* REVIEWED: GuiTextBox() crash on cursor movement +* REVIEWED: GuiTextBox(), additional inputs support +* REVIEWED: GuiLabelButton(), avoid text cut +* REVIEWED: GuiTextInputBox(), password input +* REVIEWED: Local GetCodepointNext(), aligned with raylib +* REDESIGNED: GuiSlider*()/GuiScrollBar() to support out-of-bounds +* +* 3.5 (20-Apr-2023) ADDED: GuiTabBar(), based on GuiToggle() +* ADDED: Helper functions to split text in separate lines +* ADDED: Multiple new icons, useful for code editing tools +* REMOVED: Unneeded icon editing functions +* REMOVED: GuiTextBoxMulti(), very limited and broken +* REMOVED: MeasureTextEx() dependency, logic directly implemented +* REMOVED: DrawTextEx() dependency, logic directly implemented +* REVIEWED: GuiScrollBar(), improve mouse-click behaviour +* REVIEWED: Library header info, more info, better organized +* REDESIGNED: GuiTextBox() to support cursor movement +* REDESIGNED: GuiDrawText() to divide drawing by lines +* +* 3.2 (22-May-2022) RENAMED: Some enum values, for unification, avoiding prefixes +* REMOVED: GuiScrollBar(), only internal +* REDESIGNED: GuiPanel() to support text parameter +* REDESIGNED: GuiScrollPanel() to support text parameter +* REDESIGNED: GuiColorPicker() to support text parameter +* REDESIGNED: GuiColorPanel() to support text parameter +* REDESIGNED: GuiColorBarAlpha() to support text parameter +* REDESIGNED: GuiColorBarHue() to support text parameter +* REDESIGNED: GuiTextInputBox() to support password +* +* 3.1 (12-Jan-2022) REVIEWED: Default style for consistency (aligned with rGuiLayout v2.5 tool) +* REVIEWED: GuiLoadStyle() to support compressed font atlas image data and unload previous textures +* REVIEWED: External icons usage logic +* REVIEWED: GuiLine() for centered alignment when including text +* RENAMED: Multiple controls properties definitions to prepend RAYGUI_ +* RENAMED: RICON_ references to RAYGUI_ICON_ for library consistency +* Projects updated and multiple tweaks +* +* 3.0 (04-Nov-2021) Integrated ricons data to avoid external file +* REDESIGNED: GuiTextBoxMulti() +* REMOVED: GuiImageButton*() +* Multiple minor tweaks and bugs corrected +* +* 2.9 (17-Mar-2021) REMOVED: Tooltip API +* 2.8 (03-May-2020) Centralized rectangles drawing to GuiDrawRectangle() +* 2.7 (20-Feb-2020) ADDED: Possible tooltips API +* 2.6 (09-Sep-2019) ADDED: GuiTextInputBox() +* REDESIGNED: GuiListView*(), GuiDropdownBox(), GuiSlider*(), GuiProgressBar(), GuiMessageBox() +* REVIEWED: GuiTextBox(), GuiSpinner(), GuiValueBox(), GuiLoadStyle() +* Replaced property INNER_PADDING by TEXT_PADDING, renamed some properties +* ADDED: 8 new custom styles ready to use +* Multiple minor tweaks and bugs corrected +* +* 2.5 (28-May-2019) Implemented extended GuiTextBox(), GuiValueBox(), GuiSpinner() +* 2.3 (29-Apr-2019) ADDED: rIcons auxiliar library and support for it, multiple controls reviewed +* Refactor all controls drawing mechanism to use control state +* 2.2 (05-Feb-2019) ADDED: GuiScrollBar(), GuiScrollPanel(), reviewed GuiListView(), removed Gui*Ex() controls +* 2.1 (26-Dec-2018) REDESIGNED: GuiCheckBox(), GuiComboBox(), GuiDropdownBox(), GuiToggleGroup() > Use combined text string +* REDESIGNED: Style system (breaking change) +* 2.0 (08-Nov-2018) ADDED: Support controls guiLock and custom fonts +* REVIEWED: GuiComboBox(), GuiListView()... +* 1.9 (09-Oct-2018) REVIEWED: GuiGrid(), GuiTextBox(), GuiTextBoxMulti(), GuiValueBox()... +* 1.8 (01-May-2018) Lot of rework and redesign to align with rGuiStyler and rGuiLayout +* 1.5 (21-Jun-2017) Working in an improved styles system +* 1.4 (15-Jun-2017) Rewritten all GUI functions (removed useless ones) +* 1.3 (12-Jun-2017) Complete redesign of style system +* 1.1 (01-Jun-2017) Complete review of the library +* 1.0 (07-Jun-2016) Converted to header-only by Ramon Santamaria. +* 0.9 (07-Mar-2016) Reviewed and tested by Albert Martos, Ian Eito, Sergio Martinez and Ramon Santamaria. +* 0.8 (27-Aug-2015) Initial release. Implemented by Kevin Gato, Daniel Nicolás and Ramon Santamaria. +* +* DEPENDENCIES: +* raylib 5.0 - Inputs reading (keyboard/mouse), shapes drawing, font loading and text drawing +* +* STANDALONE MODE: +* By default raygui depends on raylib mostly for the inputs and the drawing functionality but that dependency can be disabled +* with the config flag RAYGUI_STANDALONE. In that case is up to the user to provide another backend to cover library needs. +* +* The following functions should be redefined for a custom backend: +* +* - Vector2 GetMousePosition(void); +* - float GetMouseWheelMove(void); +* - bool IsMouseButtonDown(int button); +* - bool IsMouseButtonPressed(int button); +* - bool IsMouseButtonReleased(int button); +* - bool IsKeyDown(int key); +* - bool IsKeyPressed(int key); +* - int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +* +* - void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +* - void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +* +* - Font GetFontDefault(void); // -- GuiLoadStyleDefault() +* - Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle() +* - Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +* - void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) +* - char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +* - void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data +* - const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs +* - int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +* - void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list +* - unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +* +* CONTRIBUTORS: +* Ramon Santamaria: Supervision, review, redesign, update and maintenance +* Vlad Adrian: Complete rewrite of GuiTextBox() to support extended features (2019) +* Sergio Martinez: Review, testing (2015) and redesign of multiple controls (2018) +* Adria Arranz: Testing and implementation of additional controls (2018) +* Jordi Jorba: Testing and implementation of additional controls (2018) +* Albert Martos: Review and testing of the library (2015) +* Ian Eito: Review and testing of the library (2015) +* Kevin Gato: Initial implementation of basic components (2014) +* Daniel Nicolas: Initial implementation of basic components (2014) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2025 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYGUI_H +#define RAYGUI_H + +#define RAYGUI_VERSION_MAJOR 4 +#define RAYGUI_VERSION_MINOR 5 +#define RAYGUI_VERSION_PATCH 0 +#define RAYGUI_VERSION "4.5-dev" + +#if !defined(RAYGUI_STANDALONE) + #include "raylib.h" +#endif + +// Function specifiers in case library is build/used as a shared library (Windows) +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +#if defined(_WIN32) + #if defined(BUILD_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RAYGUIAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#endif + +// Function specifiers definition +#ifndef RAYGUIAPI + #define RAYGUIAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +// Allow custom memory allocators +#ifndef RAYGUI_MALLOC + #define RAYGUI_MALLOC(sz) malloc(sz) +#endif +#ifndef RAYGUI_CALLOC + #define RAYGUI_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RAYGUI_FREE + #define RAYGUI_FREE(p) free(p) +#endif + +// Simple log system to avoid printf() calls if required +// NOTE: Avoiding those calls, also avoids const strings memory usage +#define RAYGUI_SUPPORT_LOG_INFO +#if defined(RAYGUI_SUPPORT_LOG_INFO) + #define RAYGUI_LOG(...) printf(__VA_ARGS__) +#else + #define RAYGUI_LOG(...) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +// NOTE: Some types are required for RAYGUI_STANDALONE usage +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + #ifndef __cplusplus + // Boolean type + #ifndef true + typedef enum { false, true } bool; + #endif + #endif + + // Vector2 type + typedef struct Vector2 { + float x; + float y; + } Vector2; + + // Vector3 type // -- ConvertHSVtoRGB(), ConvertRGBtoHSV() + typedef struct Vector3 { + float x; + float y; + float z; + } Vector3; + + // Color type, RGBA (32bit) + typedef struct Color { + unsigned char r; + unsigned char g; + unsigned char b; + unsigned char a; + } Color; + + // Rectangle type + typedef struct Rectangle { + float x; + float y; + float width; + float height; + } Rectangle; + + // TODO: Texture2D type is very coupled to raylib, required by Font type + // It should be redesigned to be provided by user + typedef struct Texture2D { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Texture2D; + + // Image, pixel data stored in CPU memory (RAM) + typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) + } Image; + + // GlyphInfo, font characters glyphs info + typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data + } GlyphInfo; + + // TODO: Font type is very coupled to raylib, mostly required by GuiLoadStyle() + // It should be redesigned to be provided by user + typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data + } Font; +#endif + +// Style property +// NOTE: Used when exporting style as code for convenience +typedef struct GuiStyleProp { + unsigned short controlId; // Control identifier + unsigned short propertyId; // Property identifier + int propertyValue; // Property value +} GuiStyleProp; + +/* +// Controls text style -NOT USED- +// NOTE: Text style is defined by control +typedef struct GuiTextStyle { + unsigned int size; + int charSpacing; + int lineSpacing; + int alignmentH; + int alignmentV; + int padding; +} GuiTextStyle; +*/ + +// Gui control state +typedef enum { + STATE_NORMAL = 0, + STATE_FOCUSED, + STATE_PRESSED, + STATE_DISABLED +} GuiState; + +// Gui control text alignment +typedef enum { + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT +} GuiTextAlignment; + +// Gui control text alignment vertical +// NOTE: Text vertical position inside the text bounds +typedef enum { + TEXT_ALIGN_TOP = 0, + TEXT_ALIGN_MIDDLE, + TEXT_ALIGN_BOTTOM +} GuiTextAlignmentVertical; + +// Gui control text wrap mode +// NOTE: Useful for multiline text +typedef enum { + TEXT_WRAP_NONE = 0, + TEXT_WRAP_CHAR, + TEXT_WRAP_WORD +} GuiTextWrapMode; + +// Gui controls +typedef enum { + // Default -> populates to all controls when set + DEFAULT = 0, + + // Basic controls + LABEL, // Used also for: LABELBUTTON + BUTTON, + TOGGLE, // Used also for: TOGGLEGROUP + SLIDER, // Used also for: SLIDERBAR, TOGGLESLIDER + PROGRESSBAR, + CHECKBOX, + COMBOBOX, + DROPDOWNBOX, + TEXTBOX, // Used also for: TEXTBOXMULTI + VALUEBOX, + SPINNER, // Uses: BUTTON, VALUEBOX + LISTVIEW, + COLORPICKER, + SCROLLBAR, + STATUSBAR +} GuiControl; + +// Gui base properties for every control +// NOTE: RAYGUI_MAX_PROPS_BASE properties (by default 16 properties) +typedef enum { + BORDER_COLOR_NORMAL = 0, // Control border color in STATE_NORMAL + BASE_COLOR_NORMAL, // Control base color in STATE_NORMAL + TEXT_COLOR_NORMAL, // Control text color in STATE_NORMAL + BORDER_COLOR_FOCUSED, // Control border color in STATE_FOCUSED + BASE_COLOR_FOCUSED, // Control base color in STATE_FOCUSED + TEXT_COLOR_FOCUSED, // Control text color in STATE_FOCUSED + BORDER_COLOR_PRESSED, // Control border color in STATE_PRESSED + BASE_COLOR_PRESSED, // Control base color in STATE_PRESSED + TEXT_COLOR_PRESSED, // Control text color in STATE_PRESSED + BORDER_COLOR_DISABLED, // Control border color in STATE_DISABLED + BASE_COLOR_DISABLED, // Control base color in STATE_DISABLED + TEXT_COLOR_DISABLED, // Control text color in STATE_DISABLED + BORDER_WIDTH, // Control border size, 0 for no border + //TEXT_SIZE, // Control text size (glyphs max height) -> GLOBAL for all controls + //TEXT_SPACING, // Control text spacing between glyphs -> GLOBAL for all controls + //TEXT_LINE_SPACING // Control text spacing between lines -> GLOBAL for all controls + TEXT_PADDING, // Control text padding, not considering border + TEXT_ALIGNMENT, // Control text horizontal alignment inside control text bound (after border and padding) + //TEXT_WRAP_MODE // Control text wrap-mode inside text bounds -> GLOBAL for all controls +} GuiControlProperty; + +// TODO: Which text styling properties should be global or per-control? +// At this moment TEXT_PADDING and TEXT_ALIGNMENT is configured and saved per control while +// TEXT_SIZE, TEXT_SPACING, TEXT_LINE_SPACING, TEXT_ALIGNMENT_VERTICAL, TEXT_WRAP_MODE are global and +// should be configured by user as needed while defining the UI layout + +// Gui extended properties depend on control +// NOTE: RAYGUI_MAX_PROPS_EXTENDED properties (by default, max 8 properties) +//---------------------------------------------------------------------------------- +// DEFAULT extended properties +// NOTE: Those properties are common to all controls or global +// WARNING: We only have 8 slots for those properties by default!!! -> New global control: TEXT? +typedef enum { + TEXT_SIZE = 16, // Text size (glyphs max height) + TEXT_SPACING, // Text spacing between glyphs + LINE_COLOR, // Line control color + BACKGROUND_COLOR, // Background color + TEXT_LINE_SPACING, // Text spacing between lines + TEXT_ALIGNMENT_VERTICAL, // Text vertical alignment inside text bounds (after border and padding) + TEXT_WRAP_MODE // Text wrap-mode inside text bounds + //TEXT_DECORATION // Text decoration: 0-None, 1-Underline, 2-Line-through, 3-Overline + //TEXT_DECORATION_THICK // Text decoration line thickness +} GuiDefaultProperty; + +// Other possible text properties: +// TEXT_WEIGHT // Normal, Italic, Bold -> Requires specific font change +// TEXT_INDENT // Text indentation -> Now using TEXT_PADDING... + +// Label +//typedef enum { } GuiLabelProperty; + +// Button/Spinner +//typedef enum { } GuiButtonProperty; + +// Toggle/ToggleGroup +typedef enum { + GROUP_PADDING = 16, // ToggleGroup separation between toggles +} GuiToggleProperty; + +// Slider/SliderBar +typedef enum { + SLIDER_WIDTH = 16, // Slider size of internal bar + SLIDER_PADDING // Slider/SliderBar internal bar padding +} GuiSliderProperty; + +// ProgressBar +typedef enum { + PROGRESS_PADDING = 16, // ProgressBar internal padding +} GuiProgressBarProperty; + +// ScrollBar +typedef enum { + ARROWS_SIZE = 16, // ScrollBar arrows size + ARROWS_VISIBLE, // ScrollBar arrows visible + SCROLL_SLIDER_PADDING, // ScrollBar slider internal padding + SCROLL_SLIDER_SIZE, // ScrollBar slider size + SCROLL_PADDING, // ScrollBar scroll padding from arrows + SCROLL_SPEED, // ScrollBar scrolling speed +} GuiScrollBarProperty; + +// CheckBox +typedef enum { + CHECK_PADDING = 16 // CheckBox internal check padding +} GuiCheckBoxProperty; + +// ComboBox +typedef enum { + COMBO_BUTTON_WIDTH = 16, // ComboBox right button width + COMBO_BUTTON_SPACING // ComboBox button separation +} GuiComboBoxProperty; + +// DropdownBox +typedef enum { + ARROW_PADDING = 16, // DropdownBox arrow separation from border and items + DROPDOWN_ITEMS_SPACING, // DropdownBox items separation + DROPDOWN_ARROW_HIDDEN, // DropdownBox arrow hidden + DROPDOWN_ROLL_UP // DropdownBox roll up flag (default rolls down) +} GuiDropdownBoxProperty; + +// TextBox/TextBoxMulti/ValueBox/Spinner +typedef enum { + TEXT_READONLY = 16, // TextBox in read-only mode: 0-text editable, 1-text no-editable +} GuiTextBoxProperty; + +// Spinner +typedef enum { + SPIN_BUTTON_WIDTH = 16, // Spinner left/right buttons width + SPIN_BUTTON_SPACING, // Spinner buttons separation +} GuiSpinnerProperty; + +// ListView +typedef enum { + LIST_ITEMS_HEIGHT = 16, // ListView items height + LIST_ITEMS_SPACING, // ListView items separation + SCROLLBAR_WIDTH, // ListView scrollbar size (usually width) + SCROLLBAR_SIDE, // ListView scrollbar side (0-SCROLLBAR_LEFT_SIDE, 1-SCROLLBAR_RIGHT_SIDE) + LIST_ITEMS_BORDER_WIDTH // ListView items border width +} GuiListViewProperty; + +// ColorPicker +typedef enum { + COLOR_SELECTOR_SIZE = 16, + HUEBAR_WIDTH, // ColorPicker right hue bar width + HUEBAR_PADDING, // ColorPicker right hue bar separation from panel + HUEBAR_SELECTOR_HEIGHT, // ColorPicker right hue bar selector height + HUEBAR_SELECTOR_OVERFLOW // ColorPicker right hue bar selector overflow +} GuiColorPickerProperty; + +#define SCROLLBAR_LEFT_SIDE 0 +#define SCROLLBAR_RIGHT_SIDE 1 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +// ... + +//---------------------------------------------------------------------------------- +// Module Functions Declaration +//---------------------------------------------------------------------------------- + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Global gui state control functions +RAYGUIAPI void GuiEnable(void); // Enable gui controls (global state) +RAYGUIAPI void GuiDisable(void); // Disable gui controls (global state) +RAYGUIAPI void GuiLock(void); // Lock gui controls (global state) +RAYGUIAPI void GuiUnlock(void); // Unlock gui controls (global state) +RAYGUIAPI bool GuiIsLocked(void); // Check if gui is locked (global state) +RAYGUIAPI void GuiSetAlpha(float alpha); // Set gui controls alpha (global state), alpha goes from 0.0f to 1.0f +RAYGUIAPI void GuiSetState(int state); // Set gui state (global state) +RAYGUIAPI int GuiGetState(void); // Get gui state (global state) + +// Font set/get functions +RAYGUIAPI void GuiSetFont(Font font); // Set gui custom font (global state) +RAYGUIAPI Font GuiGetFont(void); // Get gui custom font (global state) + +// Style set/get functions +RAYGUIAPI void GuiSetStyle(int control, int property, int value); // Set one style property +RAYGUIAPI int GuiGetStyle(int control, int property); // Get one style property + +// Styles loading functions +RAYGUIAPI void GuiLoadStyle(const char *fileName); // Load style file over global style variable (.rgs) +RAYGUIAPI void GuiLoadStyleDefault(void); // Load style default over global style + +// Tooltips management functions +RAYGUIAPI void GuiEnableTooltip(void); // Enable gui tooltips (global state) +RAYGUIAPI void GuiDisableTooltip(void); // Disable gui tooltips (global state) +RAYGUIAPI void GuiSetTooltip(const char *tooltip); // Set tooltip string + +// Icons functionality +RAYGUIAPI const char *GuiIconText(int iconId, const char *text); // Get text with icon id prepended (if supported) +#if !defined(RAYGUI_NO_ICONS) +RAYGUIAPI void GuiSetIconScale(int scale); // Set default icon drawing size +RAYGUIAPI unsigned int *GuiGetIcons(void); // Get raygui icons data pointer +RAYGUIAPI char **GuiLoadIcons(const char *fileName, bool loadIconsName); // Load raygui icons file (.rgi) into internal icons data +RAYGUIAPI void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color); // Draw icon using pixel size at specified position +#endif + +// Controls +//---------------------------------------------------------------------------------------------------------- +// Container/separator controls, useful for controls organization +RAYGUIAPI int GuiWindowBox(Rectangle bounds, const char *title); // Window Box control, shows a window that can be closed +RAYGUIAPI int GuiGroupBox(Rectangle bounds, const char *text); // Group Box control with text name +RAYGUIAPI int GuiLine(Rectangle bounds, const char *text); // Line separator control, could contain text +RAYGUIAPI int GuiPanel(Rectangle bounds, const char *text); // Panel control, useful to group controls +RAYGUIAPI int GuiTabBar(Rectangle bounds, const char **text, int count, int *active); // Tab Bar control, returns TAB to be closed or -1 +RAYGUIAPI int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view); // Scroll Panel control + +// Basic controls set +RAYGUIAPI int GuiLabel(Rectangle bounds, const char *text); // Label control +RAYGUIAPI int GuiButton(Rectangle bounds, const char *text); // Button control, returns true when clicked +RAYGUIAPI int GuiLabelButton(Rectangle bounds, const char *text); // Label button control, returns true when clicked +RAYGUIAPI int GuiToggle(Rectangle bounds, const char *text, bool *active); // Toggle Button control +RAYGUIAPI int GuiToggleGroup(Rectangle bounds, const char *text, int *active); // Toggle Group control +RAYGUIAPI int GuiToggleSlider(Rectangle bounds, const char *text, int *active); // Toggle Slider control +RAYGUIAPI int GuiCheckBox(Rectangle bounds, const char *text, bool *checked); // Check Box control, returns true when active +RAYGUIAPI int GuiComboBox(Rectangle bounds, const char *text, int *active); // Combo Box control + +RAYGUIAPI int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode); // Dropdown Box control +RAYGUIAPI int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Spinner control +RAYGUIAPI int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode); // Value Box control, updates input text with numbers +RAYGUIAPI int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode); // Value box control for float values +RAYGUIAPI int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode); // Text Box control, updates input text + +RAYGUIAPI int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider control +RAYGUIAPI int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Slider Bar control +RAYGUIAPI int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue); // Progress Bar control +RAYGUIAPI int GuiStatusBar(Rectangle bounds, const char *text); // Status Bar control, shows info text +RAYGUIAPI int GuiDummyRec(Rectangle bounds, const char *text); // Dummy control for placeholders +RAYGUIAPI int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell); // Grid control + +// Advance controls set +RAYGUIAPI int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active); // List View control +RAYGUIAPI int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus); // List View with extended parameters +RAYGUIAPI int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons); // Message Box control, displays a message +RAYGUIAPI int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive); // Text Input Box control, ask for text, supports secret +RAYGUIAPI int GuiColorPicker(Rectangle bounds, const char *text, Color *color); // Color Picker control (multiple color controls) +RAYGUIAPI int GuiColorPanel(Rectangle bounds, const char *text, Color *color); // Color Panel control +RAYGUIAPI int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha); // Color Bar Alpha control +RAYGUIAPI int GuiColorBarHue(Rectangle bounds, const char *text, float *value); // Color Bar Hue control +RAYGUIAPI int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Picker control that avoids conversion to RGB on each call (multiple color controls) +RAYGUIAPI int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv); // Color Panel control that updates Hue-Saturation-Value color value, used by GuiColorPickerHSV() +//---------------------------------------------------------------------------------------------------------- + +#if !defined(RAYGUI_NO_ICONS) + +#if !defined(RAYGUI_CUSTOM_ICONS) +//---------------------------------------------------------------------------------- +// Icons enumeration +//---------------------------------------------------------------------------------- +typedef enum { + ICON_NONE = 0, + ICON_FOLDER_FILE_OPEN = 1, + ICON_FILE_SAVE_CLASSIC = 2, + ICON_FOLDER_OPEN = 3, + ICON_FOLDER_SAVE = 4, + ICON_FILE_OPEN = 5, + ICON_FILE_SAVE = 6, + ICON_FILE_EXPORT = 7, + ICON_FILE_ADD = 8, + ICON_FILE_DELETE = 9, + ICON_FILETYPE_TEXT = 10, + ICON_FILETYPE_AUDIO = 11, + ICON_FILETYPE_IMAGE = 12, + ICON_FILETYPE_PLAY = 13, + ICON_FILETYPE_VIDEO = 14, + ICON_FILETYPE_INFO = 15, + ICON_FILE_COPY = 16, + ICON_FILE_CUT = 17, + ICON_FILE_PASTE = 18, + ICON_CURSOR_HAND = 19, + ICON_CURSOR_POINTER = 20, + ICON_CURSOR_CLASSIC = 21, + ICON_PENCIL = 22, + ICON_PENCIL_BIG = 23, + ICON_BRUSH_CLASSIC = 24, + ICON_BRUSH_PAINTER = 25, + ICON_WATER_DROP = 26, + ICON_COLOR_PICKER = 27, + ICON_RUBBER = 28, + ICON_COLOR_BUCKET = 29, + ICON_TEXT_T = 30, + ICON_TEXT_A = 31, + ICON_SCALE = 32, + ICON_RESIZE = 33, + ICON_FILTER_POINT = 34, + ICON_FILTER_BILINEAR = 35, + ICON_CROP = 36, + ICON_CROP_ALPHA = 37, + ICON_SQUARE_TOGGLE = 38, + ICON_SYMMETRY = 39, + ICON_SYMMETRY_HORIZONTAL = 40, + ICON_SYMMETRY_VERTICAL = 41, + ICON_LENS = 42, + ICON_LENS_BIG = 43, + ICON_EYE_ON = 44, + ICON_EYE_OFF = 45, + ICON_FILTER_TOP = 46, + ICON_FILTER = 47, + ICON_TARGET_POINT = 48, + ICON_TARGET_SMALL = 49, + ICON_TARGET_BIG = 50, + ICON_TARGET_MOVE = 51, + ICON_CURSOR_MOVE = 52, + ICON_CURSOR_SCALE = 53, + ICON_CURSOR_SCALE_RIGHT = 54, + ICON_CURSOR_SCALE_LEFT = 55, + ICON_UNDO = 56, + ICON_REDO = 57, + ICON_REREDO = 58, + ICON_MUTATE = 59, + ICON_ROTATE = 60, + ICON_REPEAT = 61, + ICON_SHUFFLE = 62, + ICON_EMPTYBOX = 63, + ICON_TARGET = 64, + ICON_TARGET_SMALL_FILL = 65, + ICON_TARGET_BIG_FILL = 66, + ICON_TARGET_MOVE_FILL = 67, + ICON_CURSOR_MOVE_FILL = 68, + ICON_CURSOR_SCALE_FILL = 69, + ICON_CURSOR_SCALE_RIGHT_FILL = 70, + ICON_CURSOR_SCALE_LEFT_FILL = 71, + ICON_UNDO_FILL = 72, + ICON_REDO_FILL = 73, + ICON_REREDO_FILL = 74, + ICON_MUTATE_FILL = 75, + ICON_ROTATE_FILL = 76, + ICON_REPEAT_FILL = 77, + ICON_SHUFFLE_FILL = 78, + ICON_EMPTYBOX_SMALL = 79, + ICON_BOX = 80, + ICON_BOX_TOP = 81, + ICON_BOX_TOP_RIGHT = 82, + ICON_BOX_RIGHT = 83, + ICON_BOX_BOTTOM_RIGHT = 84, + ICON_BOX_BOTTOM = 85, + ICON_BOX_BOTTOM_LEFT = 86, + ICON_BOX_LEFT = 87, + ICON_BOX_TOP_LEFT = 88, + ICON_BOX_CENTER = 89, + ICON_BOX_CIRCLE_MASK = 90, + ICON_POT = 91, + ICON_ALPHA_MULTIPLY = 92, + ICON_ALPHA_CLEAR = 93, + ICON_DITHERING = 94, + ICON_MIPMAPS = 95, + ICON_BOX_GRID = 96, + ICON_GRID = 97, + ICON_BOX_CORNERS_SMALL = 98, + ICON_BOX_CORNERS_BIG = 99, + ICON_FOUR_BOXES = 100, + ICON_GRID_FILL = 101, + ICON_BOX_MULTISIZE = 102, + ICON_ZOOM_SMALL = 103, + ICON_ZOOM_MEDIUM = 104, + ICON_ZOOM_BIG = 105, + ICON_ZOOM_ALL = 106, + ICON_ZOOM_CENTER = 107, + ICON_BOX_DOTS_SMALL = 108, + ICON_BOX_DOTS_BIG = 109, + ICON_BOX_CONCENTRIC = 110, + ICON_BOX_GRID_BIG = 111, + ICON_OK_TICK = 112, + ICON_CROSS = 113, + ICON_ARROW_LEFT = 114, + ICON_ARROW_RIGHT = 115, + ICON_ARROW_DOWN = 116, + ICON_ARROW_UP = 117, + ICON_ARROW_LEFT_FILL = 118, + ICON_ARROW_RIGHT_FILL = 119, + ICON_ARROW_DOWN_FILL = 120, + ICON_ARROW_UP_FILL = 121, + ICON_AUDIO = 122, + ICON_FX = 123, + ICON_WAVE = 124, + ICON_WAVE_SINUS = 125, + ICON_WAVE_SQUARE = 126, + ICON_WAVE_TRIANGULAR = 127, + ICON_CROSS_SMALL = 128, + ICON_PLAYER_PREVIOUS = 129, + ICON_PLAYER_PLAY_BACK = 130, + ICON_PLAYER_PLAY = 131, + ICON_PLAYER_PAUSE = 132, + ICON_PLAYER_STOP = 133, + ICON_PLAYER_NEXT = 134, + ICON_PLAYER_RECORD = 135, + ICON_MAGNET = 136, + ICON_LOCK_CLOSE = 137, + ICON_LOCK_OPEN = 138, + ICON_CLOCK = 139, + ICON_TOOLS = 140, + ICON_GEAR = 141, + ICON_GEAR_BIG = 142, + ICON_BIN = 143, + ICON_HAND_POINTER = 144, + ICON_LASER = 145, + ICON_COIN = 146, + ICON_EXPLOSION = 147, + ICON_1UP = 148, + ICON_PLAYER = 149, + ICON_PLAYER_JUMP = 150, + ICON_KEY = 151, + ICON_DEMON = 152, + ICON_TEXT_POPUP = 153, + ICON_GEAR_EX = 154, + ICON_CRACK = 155, + ICON_CRACK_POINTS = 156, + ICON_STAR = 157, + ICON_DOOR = 158, + ICON_EXIT = 159, + ICON_MODE_2D = 160, + ICON_MODE_3D = 161, + ICON_CUBE = 162, + ICON_CUBE_FACE_TOP = 163, + ICON_CUBE_FACE_LEFT = 164, + ICON_CUBE_FACE_FRONT = 165, + ICON_CUBE_FACE_BOTTOM = 166, + ICON_CUBE_FACE_RIGHT = 167, + ICON_CUBE_FACE_BACK = 168, + ICON_CAMERA = 169, + ICON_SPECIAL = 170, + ICON_LINK_NET = 171, + ICON_LINK_BOXES = 172, + ICON_LINK_MULTI = 173, + ICON_LINK = 174, + ICON_LINK_BROKE = 175, + ICON_TEXT_NOTES = 176, + ICON_NOTEBOOK = 177, + ICON_SUITCASE = 178, + ICON_SUITCASE_ZIP = 179, + ICON_MAILBOX = 180, + ICON_MONITOR = 181, + ICON_PRINTER = 182, + ICON_PHOTO_CAMERA = 183, + ICON_PHOTO_CAMERA_FLASH = 184, + ICON_HOUSE = 185, + ICON_HEART = 186, + ICON_CORNER = 187, + ICON_VERTICAL_BARS = 188, + ICON_VERTICAL_BARS_FILL = 189, + ICON_LIFE_BARS = 190, + ICON_INFO = 191, + ICON_CROSSLINE = 192, + ICON_HELP = 193, + ICON_FILETYPE_ALPHA = 194, + ICON_FILETYPE_HOME = 195, + ICON_LAYERS_VISIBLE = 196, + ICON_LAYERS = 197, + ICON_WINDOW = 198, + ICON_HIDPI = 199, + ICON_FILETYPE_BINARY = 200, + ICON_HEX = 201, + ICON_SHIELD = 202, + ICON_FILE_NEW = 203, + ICON_FOLDER_ADD = 204, + ICON_ALARM = 205, + ICON_CPU = 206, + ICON_ROM = 207, + ICON_STEP_OVER = 208, + ICON_STEP_INTO = 209, + ICON_STEP_OUT = 210, + ICON_RESTART = 211, + ICON_BREAKPOINT_ON = 212, + ICON_BREAKPOINT_OFF = 213, + ICON_BURGER_MENU = 214, + ICON_CASE_SENSITIVE = 215, + ICON_REG_EXP = 216, + ICON_FOLDER = 217, + ICON_FILE = 218, + ICON_SAND_TIMER = 219, + ICON_WARNING = 220, + ICON_HELP_BOX = 221, + ICON_INFO_BOX = 222, + ICON_PRIORITY = 223, + ICON_LAYERS_ISO = 224, + ICON_LAYERS2 = 225, + ICON_MLAYERS = 226, + ICON_MAPS = 227, + ICON_HOT = 228, + ICON_229 = 229, + ICON_230 = 230, + ICON_231 = 231, + ICON_232 = 232, + ICON_233 = 233, + ICON_234 = 234, + ICON_235 = 235, + ICON_236 = 236, + ICON_237 = 237, + ICON_238 = 238, + ICON_239 = 239, + ICON_240 = 240, + ICON_241 = 241, + ICON_242 = 242, + ICON_243 = 243, + ICON_244 = 244, + ICON_245 = 245, + ICON_246 = 246, + ICON_247 = 247, + ICON_248 = 248, + ICON_249 = 249, + ICON_250 = 250, + ICON_251 = 251, + ICON_252 = 252, + ICON_253 = 253, + ICON_254 = 254, + ICON_255 = 255, +} GuiIconName; +#endif + +#endif + +#if defined(__cplusplus) +} // Prevents name mangling of functions +#endif + +#endif // RAYGUI_H + +/*********************************************************************************** +* +* RAYGUI IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RAYGUI_IMPLEMENTATION) + +#include // required for: isspace() [GuiTextBox()] +#include // Required for: FILE, fopen(), fclose(), fprintf(), feof(), fscanf(), vsprintf() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: malloc(), calloc(), free() [GuiLoadStyle(), GuiLoadIcons()] +#include // Required for: strlen() [GuiTextBox(), GuiValueBox()], memset(), memcpy() +#include // Required for: va_list, va_start(), vfprintf(), va_end() [TextFormat()] +#include // Required for: roundf() [GuiColorPicker()] + +#ifdef __cplusplus + #define RAYGUI_CLITERAL(name) name +#else + #define RAYGUI_CLITERAL(name) (name) +#endif + +// Check if two rectangles are equal, used to validate a slider bounds as an id +#ifndef CHECK_BOUNDS_ID + #define CHECK_BOUNDS_ID(src, dst) ((src.x == dst.x) && (src.y == dst.y) && (src.width == dst.width) && (src.height == dst.height)) +#endif + +#if !defined(RAYGUI_NO_ICONS) && !defined(RAYGUI_CUSTOM_ICONS) + +// Embedded icons, no external file provided +#define RAYGUI_ICON_SIZE 16 // Size of icons in pixels (squared) +#define RAYGUI_ICON_MAX_ICONS 256 // Maximum number of icons +#define RAYGUI_ICON_MAX_NAME_LENGTH 32 // Maximum length of icon name id + +// Icons data is defined by bit array (every bit represents one pixel) +// Those arrays are stored as unsigned int data arrays, so, +// every array element defines 32 pixels (bits) of information +// One icon is defined by 8 int, (8 int * 32 bit = 256 bit = 16*16 pixels) +// NOTE: Number of elemens depend on RAYGUI_ICON_SIZE (by default 16x16 pixels) +#define RAYGUI_ICON_DATA_ELEMENTS (RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32) + +//---------------------------------------------------------------------------------- +// Icons data for all gui possible icons (allocated on data segment by default) +// +// NOTE 1: Every icon is codified in binary form, using 1 bit per pixel, so, +// every 16x16 icon requires 8 integers (16*16/32) to be stored +// +// NOTE 2: A different icon set could be loaded over this array using GuiLoadIcons(), +// but loaded icons set must be same RAYGUI_ICON_SIZE and no more than RAYGUI_ICON_MAX_ICONS +// +// guiIcons size is by default: 256*(16*16/32) = 2048*4 = 8192 bytes = 8 KB +//---------------------------------------------------------------------------------- +static unsigned int guiIcons[RAYGUI_ICON_MAX_ICONS*RAYGUI_ICON_DATA_ELEMENTS] = { + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_NONE + 0x3ff80000, 0x2f082008, 0x2042207e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x00007ffe, // ICON_FOLDER_FILE_OPEN + 0x3ffe0000, 0x44226422, 0x400247e2, 0x5ffa4002, 0x57ea500a, 0x500a500a, 0x40025ffa, 0x00007ffe, // ICON_FILE_SAVE_CLASSIC + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024002, 0x44424282, 0x793e4102, 0x00000100, // ICON_FOLDER_OPEN + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x41024102, 0x44424102, 0x793e4282, 0x00000000, // ICON_FOLDER_SAVE + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x24442284, 0x21042104, 0x20042104, 0x00003ffc, // ICON_FILE_OPEN + 0x3ff00000, 0x201c2010, 0x20042004, 0x21042004, 0x21042104, 0x22842444, 0x20042104, 0x00003ffc, // ICON_FILE_SAVE + 0x3ff00000, 0x201c2010, 0x00042004, 0x20041004, 0x20844784, 0x00841384, 0x20042784, 0x00003ffc, // ICON_FILE_EXPORT + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x22042204, 0x22042f84, 0x20042204, 0x00003ffc, // ICON_FILE_ADD + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x25042884, 0x25042204, 0x20042884, 0x00003ffc, // ICON_FILE_DELETE + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_FILETYPE_TEXT + 0x3ff00000, 0x201c2010, 0x27042004, 0x244424c4, 0x26442444, 0x20642664, 0x20042004, 0x00003ffc, // ICON_FILETYPE_AUDIO + 0x3ff00000, 0x201c2010, 0x26042604, 0x20042004, 0x35442884, 0x2414222c, 0x20042004, 0x00003ffc, // ICON_FILETYPE_IMAGE + 0x3ff00000, 0x201c2010, 0x20c42004, 0x22442144, 0x22442444, 0x20c42144, 0x20042004, 0x00003ffc, // ICON_FILETYPE_PLAY + 0x3ff00000, 0x3ffc2ff0, 0x3f3c2ff4, 0x3dbc2eb4, 0x3dbc2bb4, 0x3f3c2eb4, 0x3ffc2ff4, 0x00002ff4, // ICON_FILETYPE_VIDEO + 0x3ff00000, 0x201c2010, 0x21842184, 0x21842004, 0x21842184, 0x21842184, 0x20042184, 0x00003ffc, // ICON_FILETYPE_INFO + 0x0ff00000, 0x381c0810, 0x28042804, 0x28042804, 0x28042804, 0x28042804, 0x20102ffc, 0x00003ff0, // ICON_FILE_COPY + 0x00000000, 0x701c0000, 0x079c1e14, 0x55a000f0, 0x079c00f0, 0x701c1e14, 0x00000000, 0x00000000, // ICON_FILE_CUT + 0x01c00000, 0x13e41bec, 0x3f841004, 0x204420c4, 0x20442044, 0x20442044, 0x207c2044, 0x00003fc0, // ICON_FILE_PASTE + 0x00000000, 0x3aa00fe0, 0x2abc2aa0, 0x2aa42aa4, 0x20042aa4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_CURSOR_HAND + 0x00000000, 0x003c000c, 0x030800c8, 0x30100c10, 0x10202020, 0x04400840, 0x01800280, 0x00000000, // ICON_CURSOR_POINTER + 0x00000000, 0x00180000, 0x01f00078, 0x03e007f0, 0x07c003e0, 0x04000e40, 0x00000000, 0x00000000, // ICON_CURSOR_CLASSIC + 0x00000000, 0x04000000, 0x11000a00, 0x04400a80, 0x01100220, 0x00580088, 0x00000038, 0x00000000, // ICON_PENCIL + 0x04000000, 0x15000a00, 0x50402880, 0x14102820, 0x05040a08, 0x015c028c, 0x007c00bc, 0x00000000, // ICON_PENCIL_BIG + 0x01c00000, 0x01400140, 0x01400140, 0x0ff80140, 0x0ff80808, 0x0aa80808, 0x0aa80aa8, 0x00000ff8, // ICON_BRUSH_CLASSIC + 0x1ffc0000, 0x5ffc7ffe, 0x40004000, 0x00807f80, 0x01c001c0, 0x01c001c0, 0x01c001c0, 0x00000080, // ICON_BRUSH_PAINTER + 0x00000000, 0x00800000, 0x01c00080, 0x03e001c0, 0x07f003e0, 0x036006f0, 0x000001c0, 0x00000000, // ICON_WATER_DROP + 0x00000000, 0x3e003800, 0x1f803f80, 0x0c201e40, 0x02080c10, 0x00840104, 0x00380044, 0x00000000, // ICON_COLOR_PICKER + 0x00000000, 0x07800300, 0x1fe00fc0, 0x3f883fd0, 0x0e021f04, 0x02040402, 0x00f00108, 0x00000000, // ICON_RUBBER + 0x00c00000, 0x02800140, 0x08200440, 0x20081010, 0x2ffe3004, 0x03f807fc, 0x00e001f0, 0x00000040, // ICON_COLOR_BUCKET + 0x00000000, 0x21843ffc, 0x01800180, 0x01800180, 0x01800180, 0x01800180, 0x03c00180, 0x00000000, // ICON_TEXT_T + 0x00800000, 0x01400180, 0x06200340, 0x0c100620, 0x1ff80c10, 0x380c1808, 0x70067004, 0x0000f80f, // ICON_TEXT_A + 0x78000000, 0x50004000, 0x00004800, 0x03c003c0, 0x03c003c0, 0x00100000, 0x0002000a, 0x0000000e, // ICON_SCALE + 0x75560000, 0x5e004002, 0x54001002, 0x41001202, 0x408200fe, 0x40820082, 0x40820082, 0x00006afe, // ICON_RESIZE + 0x00000000, 0x3f003f00, 0x3f003f00, 0x3f003f00, 0x00400080, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_POINT + 0x6d800000, 0x00004080, 0x40804080, 0x40800000, 0x00406d80, 0x001c0020, 0x001c001c, 0x00000000, // ICON_FILTER_BILINEAR + 0x40080000, 0x1ffe2008, 0x14081008, 0x11081208, 0x10481088, 0x10081028, 0x10047ff8, 0x00001002, // ICON_CROP + 0x00100000, 0x3ffc0010, 0x2ab03550, 0x22b02550, 0x20b02150, 0x20302050, 0x2000fff0, 0x00002000, // ICON_CROP_ALPHA + 0x40000000, 0x1ff82000, 0x04082808, 0x01082208, 0x00482088, 0x00182028, 0x35542008, 0x00000002, // ICON_SQUARE_TOGGLE + 0x00000000, 0x02800280, 0x06c006c0, 0x0ea00ee0, 0x1e901eb0, 0x3e883e98, 0x7efc7e8c, 0x00000000, // ICON_SYMMETRY + 0x01000000, 0x05600100, 0x1d480d50, 0x7d423d44, 0x3d447d42, 0x0d501d48, 0x01000560, 0x00000100, // ICON_SYMMETRY_HORIZONTAL + 0x01800000, 0x04200240, 0x10080810, 0x00001ff8, 0x00007ffe, 0x0ff01ff8, 0x03c007e0, 0x00000180, // ICON_SYMMETRY_VERTICAL + 0x00000000, 0x010800f0, 0x02040204, 0x02040204, 0x07f00308, 0x1c000e00, 0x30003800, 0x00000000, // ICON_LENS + 0x00000000, 0x061803f0, 0x08240c0c, 0x08040814, 0x0c0c0804, 0x23f01618, 0x18002400, 0x00000000, // ICON_LENS_BIG + 0x00000000, 0x00000000, 0x1c7007c0, 0x638e3398, 0x1c703398, 0x000007c0, 0x00000000, 0x00000000, // ICON_EYE_ON + 0x00000000, 0x10002000, 0x04700fc0, 0x610e3218, 0x1c703098, 0x001007a0, 0x00000008, 0x00000000, // ICON_EYE_OFF + 0x00000000, 0x00007ffc, 0x40047ffc, 0x10102008, 0x04400820, 0x02800280, 0x02800280, 0x00000100, // ICON_FILTER_TOP + 0x00000000, 0x40027ffe, 0x10082004, 0x04200810, 0x02400240, 0x02400240, 0x01400240, 0x000000c0, // ICON_FILTER + 0x00800000, 0x00800080, 0x00000080, 0x3c9e0000, 0x00000000, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_POINT + 0x00800000, 0x00800080, 0x00800080, 0x3f7e01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL + 0x00800000, 0x00800080, 0x03e00080, 0x3e3e0220, 0x03e00220, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG + 0x01000000, 0x04400280, 0x01000100, 0x43842008, 0x43849ab2, 0x01002008, 0x04400100, 0x01000280, // ICON_TARGET_MOVE + 0x01000000, 0x04400280, 0x01000100, 0x41042108, 0x41049ff2, 0x01002108, 0x04400100, 0x01000280, // ICON_CURSOR_MOVE + 0x781e0000, 0x500a4002, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x4002500a, 0x0000781e, // ICON_CURSOR_SCALE + 0x00000000, 0x20003c00, 0x24002800, 0x01000200, 0x00400080, 0x00140024, 0x003c0004, 0x00000000, // ICON_CURSOR_SCALE_RIGHT + 0x00000000, 0x0004003c, 0x00240014, 0x00800040, 0x02000100, 0x28002400, 0x3c002000, 0x00000000, // ICON_CURSOR_SCALE_LEFT + 0x00000000, 0x00100020, 0x10101fc8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO + 0x00000000, 0x08000400, 0x080813f8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3f902020, 0x00400020, 0x00000000, // ICON_REREDO + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3fc82010, 0x00200010, 0x00000000, // ICON_MUTATE + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18101020, 0x00100fc8, 0x00000020, // ICON_ROTATE + 0x00000000, 0x04000200, 0x240429fc, 0x20042204, 0x20442004, 0x3f942024, 0x00400020, 0x00000000, // ICON_REPEAT + 0x00000000, 0x20001000, 0x22104c0e, 0x00801120, 0x11200040, 0x4c0e2210, 0x10002000, 0x00000000, // ICON_SHUFFLE + 0x7ffe0000, 0x50024002, 0x44024802, 0x41024202, 0x40424082, 0x40124022, 0x4002400a, 0x00007ffe, // ICON_EMPTYBOX + 0x00800000, 0x03e00080, 0x08080490, 0x3c9e0808, 0x08080808, 0x03e00490, 0x00800080, 0x00000000, // ICON_TARGET + 0x00800000, 0x00800080, 0x00800080, 0x3ffe01c0, 0x008001c0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_SMALL_FILL + 0x00800000, 0x00800080, 0x03e00080, 0x3ffe03e0, 0x03e003e0, 0x00800080, 0x00800080, 0x00000000, // ICON_TARGET_BIG_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x638c2008, 0x638cfbbe, 0x01002008, 0x07c00100, 0x01000380, // ICON_TARGET_MOVE_FILL + 0x01000000, 0x07c00380, 0x01000100, 0x610c2108, 0x610cfffe, 0x01002108, 0x07c00100, 0x01000380, // ICON_CURSOR_MOVE_FILL + 0x781e0000, 0x6006700e, 0x04204812, 0x00000240, 0x02400000, 0x48120420, 0x700e6006, 0x0000781e, // ICON_CURSOR_SCALE_FILL + 0x00000000, 0x38003c00, 0x24003000, 0x01000200, 0x00400080, 0x000c0024, 0x003c001c, 0x00000000, // ICON_CURSOR_SCALE_RIGHT_FILL + 0x00000000, 0x001c003c, 0x0024000c, 0x00800040, 0x02000100, 0x30002400, 0x3c003800, 0x00000000, // ICON_CURSOR_SCALE_LEFT_FILL + 0x00000000, 0x00300020, 0x10301ff8, 0x10001020, 0x10001000, 0x10001000, 0x00001fc0, 0x00000000, // ICON_UNDO_FILL + 0x00000000, 0x0c000400, 0x0c081ff8, 0x00080408, 0x00080008, 0x00080008, 0x000003f8, 0x00000000, // ICON_REDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x20002000, 0x20402000, 0x3ff02060, 0x00400060, 0x00000000, // ICON_REREDO_FILL + 0x00000000, 0x3ffc0000, 0x20042004, 0x27fc2004, 0x20202000, 0x3ff82030, 0x00200030, 0x00000000, // ICON_MUTATE_FILL + 0x00000000, 0x0ff00000, 0x10081818, 0x11801008, 0x10001180, 0x18301020, 0x00300ff8, 0x00000020, // ICON_ROTATE_FILL + 0x00000000, 0x06000200, 0x26042ffc, 0x20042204, 0x20442004, 0x3ff42064, 0x00400060, 0x00000000, // ICON_REPEAT_FILL + 0x00000000, 0x30001000, 0x32107c0e, 0x00801120, 0x11200040, 0x7c0e3210, 0x10003000, 0x00000000, // ICON_SHUFFLE_FILL + 0x00000000, 0x30043ffc, 0x24042804, 0x21042204, 0x20442084, 0x20142024, 0x3ffc200c, 0x00000000, // ICON_EMPTYBOX_SMALL + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX + 0x00000000, 0x23c43ffc, 0x23c423c4, 0x200423c4, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP + 0x00000000, 0x3e043ffc, 0x3e043e04, 0x20043e04, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x3e043e04, 0x3e043e04, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x3e042004, 0x3e043e04, 0x3ffc3e04, 0x00000000, // ICON_BOX_BOTTOM_RIGHT + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x23c42004, 0x23c423c4, 0x3ffc23c4, 0x00000000, // ICON_BOX_BOTTOM + 0x00000000, 0x20043ffc, 0x20042004, 0x20042004, 0x207c2004, 0x207c207c, 0x3ffc207c, 0x00000000, // ICON_BOX_BOTTOM_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x207c207c, 0x207c207c, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_LEFT + 0x00000000, 0x207c3ffc, 0x207c207c, 0x2004207c, 0x20042004, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_TOP_LEFT + 0x00000000, 0x20043ffc, 0x20042004, 0x23c423c4, 0x23c423c4, 0x20042004, 0x3ffc2004, 0x00000000, // ICON_BOX_CENTER + 0x7ffe0000, 0x40024002, 0x47e24182, 0x4ff247e2, 0x47e24ff2, 0x418247e2, 0x40024002, 0x00007ffe, // ICON_BOX_CIRCLE_MASK + 0x7fff0000, 0x40014001, 0x40014001, 0x49555ddd, 0x4945495d, 0x400149c5, 0x40014001, 0x00007fff, // ICON_POT + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x404e40ce, 0x48125432, 0x4006540e, 0x00007ffe, // ICON_ALPHA_MULTIPLY + 0x7ffe0000, 0x53327332, 0x44ce4cce, 0x41324332, 0x5c4e40ce, 0x44124432, 0x40065c0e, 0x00007ffe, // ICON_ALPHA_CLEAR + 0x7ffe0000, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x42fe417e, 0x00007ffe, // ICON_DITHERING + 0x07fe0000, 0x1ffa0002, 0x7fea000a, 0x402a402a, 0x5b2a512a, 0x5128552a, 0x40205128, 0x00007fe0, // ICON_MIPMAPS + 0x00000000, 0x1ff80000, 0x12481248, 0x12481ff8, 0x1ff81248, 0x12481248, 0x00001ff8, 0x00000000, // ICON_BOX_GRID + 0x12480000, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x7ffe1248, 0x12481248, 0x12487ffe, 0x00001248, // ICON_GRID + 0x00000000, 0x1c380000, 0x1c3817e8, 0x08100810, 0x08100810, 0x17e81c38, 0x00001c38, 0x00000000, // ICON_BOX_CORNERS_SMALL + 0x700e0000, 0x700e5ffa, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x5ffa700e, 0x0000700e, // ICON_BOX_CORNERS_BIG + 0x3f7e0000, 0x21422142, 0x21422142, 0x00003f7e, 0x21423f7e, 0x21422142, 0x3f7e2142, 0x00000000, // ICON_FOUR_BOXES + 0x00000000, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x3bb80000, 0x3bb83bb8, 0x00000000, // ICON_GRID_FILL + 0x7ffe0000, 0x7ffe7ffe, 0x77fe7000, 0x77fe77fe, 0x777e7700, 0x777e777e, 0x777e777e, 0x0000777e, // ICON_BOX_MULTISIZE + 0x781e0000, 0x40024002, 0x00004002, 0x01800000, 0x00000180, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_SMALL + 0x781e0000, 0x40024002, 0x00004002, 0x03c003c0, 0x03c003c0, 0x40020000, 0x40024002, 0x0000781e, // ICON_ZOOM_MEDIUM + 0x781e0000, 0x40024002, 0x07e04002, 0x07e007e0, 0x07e007e0, 0x400207e0, 0x40024002, 0x0000781e, // ICON_ZOOM_BIG + 0x781e0000, 0x5ffa4002, 0x1ff85ffa, 0x1ff81ff8, 0x1ff81ff8, 0x5ffa1ff8, 0x40025ffa, 0x0000781e, // ICON_ZOOM_ALL + 0x00000000, 0x2004381c, 0x00002004, 0x00000000, 0x00000000, 0x20040000, 0x381c2004, 0x00000000, // ICON_ZOOM_CENTER + 0x00000000, 0x1db80000, 0x10081008, 0x10080000, 0x00001008, 0x10081008, 0x00001db8, 0x00000000, // ICON_BOX_DOTS_SMALL + 0x35560000, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x00002002, 0x35562002, 0x00000000, // ICON_BOX_DOTS_BIG + 0x7ffe0000, 0x40024002, 0x48124ff2, 0x49924812, 0x48124992, 0x4ff24812, 0x40024002, 0x00007ffe, // ICON_BOX_CONCENTRIC + 0x00000000, 0x10841ffc, 0x10841084, 0x1ffc1084, 0x10841084, 0x10841084, 0x00001ffc, 0x00000000, // ICON_BOX_GRID_BIG + 0x00000000, 0x00000000, 0x10000000, 0x04000800, 0x01040200, 0x00500088, 0x00000020, 0x00000000, // ICON_OK_TICK + 0x00000000, 0x10080000, 0x04200810, 0x01800240, 0x02400180, 0x08100420, 0x00001008, 0x00000000, // ICON_CROSS + 0x00000000, 0x02000000, 0x00800100, 0x00200040, 0x00200010, 0x00800040, 0x02000100, 0x00000000, // ICON_ARROW_LEFT + 0x00000000, 0x00400000, 0x01000080, 0x04000200, 0x04000800, 0x01000200, 0x00400080, 0x00000000, // ICON_ARROW_RIGHT + 0x00000000, 0x00000000, 0x00000000, 0x08081004, 0x02200410, 0x00800140, 0x00000000, 0x00000000, // ICON_ARROW_DOWN + 0x00000000, 0x00000000, 0x01400080, 0x04100220, 0x10040808, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP + 0x00000000, 0x02000000, 0x03800300, 0x03e003c0, 0x03e003f0, 0x038003c0, 0x02000300, 0x00000000, // ICON_ARROW_LEFT_FILL + 0x00000000, 0x00400000, 0x01c000c0, 0x07c003c0, 0x07c00fc0, 0x01c003c0, 0x004000c0, 0x00000000, // ICON_ARROW_RIGHT_FILL + 0x00000000, 0x00000000, 0x00000000, 0x0ff81ffc, 0x03e007f0, 0x008001c0, 0x00000000, 0x00000000, // ICON_ARROW_DOWN_FILL + 0x00000000, 0x00000000, 0x01c00080, 0x07f003e0, 0x1ffc0ff8, 0x00000000, 0x00000000, 0x00000000, // ICON_ARROW_UP_FILL + 0x00000000, 0x18a008c0, 0x32881290, 0x24822686, 0x26862482, 0x12903288, 0x08c018a0, 0x00000000, // ICON_AUDIO + 0x00000000, 0x04800780, 0x004000c0, 0x662000f0, 0x08103c30, 0x130a0e18, 0x0000318e, 0x00000000, // ICON_FX + 0x00000000, 0x00800000, 0x08880888, 0x2aaa0a8a, 0x0a8a2aaa, 0x08880888, 0x00000080, 0x00000000, // ICON_WAVE + 0x00000000, 0x00600000, 0x01080090, 0x02040108, 0x42044204, 0x24022402, 0x00001800, 0x00000000, // ICON_WAVE_SINUS + 0x00000000, 0x07f80000, 0x04080408, 0x04080408, 0x04080408, 0x7c0e0408, 0x00000000, 0x00000000, // ICON_WAVE_SQUARE + 0x00000000, 0x00000000, 0x00a00040, 0x22084110, 0x08021404, 0x00000000, 0x00000000, 0x00000000, // ICON_WAVE_TRIANGULAR + 0x00000000, 0x00000000, 0x04200000, 0x01800240, 0x02400180, 0x00000420, 0x00000000, 0x00000000, // ICON_CROSS_SMALL + 0x00000000, 0x18380000, 0x12281428, 0x10a81128, 0x112810a8, 0x14281228, 0x00001838, 0x00000000, // ICON_PLAYER_PREVIOUS + 0x00000000, 0x18000000, 0x11801600, 0x10181060, 0x10601018, 0x16001180, 0x00001800, 0x00000000, // ICON_PLAYER_PLAY_BACK + 0x00000000, 0x00180000, 0x01880068, 0x18080608, 0x06081808, 0x00680188, 0x00000018, 0x00000000, // ICON_PLAYER_PLAY + 0x00000000, 0x1e780000, 0x12481248, 0x12481248, 0x12481248, 0x12481248, 0x00001e78, 0x00000000, // ICON_PLAYER_PAUSE + 0x00000000, 0x1ff80000, 0x10081008, 0x10081008, 0x10081008, 0x10081008, 0x00001ff8, 0x00000000, // ICON_PLAYER_STOP + 0x00000000, 0x1c180000, 0x14481428, 0x15081488, 0x14881508, 0x14281448, 0x00001c18, 0x00000000, // ICON_PLAYER_NEXT + 0x00000000, 0x03c00000, 0x08100420, 0x10081008, 0x10081008, 0x04200810, 0x000003c0, 0x00000000, // ICON_PLAYER_RECORD + 0x00000000, 0x0c3007e0, 0x13c81818, 0x14281668, 0x14281428, 0x1c381c38, 0x08102244, 0x00000000, // ICON_MAGNET + 0x07c00000, 0x08200820, 0x3ff80820, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_CLOSE + 0x07c00000, 0x08000800, 0x3ff80800, 0x23882008, 0x21082388, 0x20082108, 0x1ff02008, 0x00000000, // ICON_LOCK_OPEN + 0x01c00000, 0x0c180770, 0x3086188c, 0x60832082, 0x60034781, 0x30062002, 0x0c18180c, 0x01c00770, // ICON_CLOCK + 0x0a200000, 0x1b201b20, 0x04200e20, 0x04200420, 0x04700420, 0x0e700e70, 0x0e700e70, 0x04200e70, // ICON_TOOLS + 0x01800000, 0x3bdc318c, 0x0ff01ff8, 0x7c3e1e78, 0x1e787c3e, 0x1ff80ff0, 0x318c3bdc, 0x00000180, // ICON_GEAR + 0x01800000, 0x3ffc318c, 0x1c381ff8, 0x781e1818, 0x1818781e, 0x1ff81c38, 0x318c3ffc, 0x00000180, // ICON_GEAR_BIG + 0x00000000, 0x08080ff8, 0x08081ffc, 0x0aa80aa8, 0x0aa80aa8, 0x0aa80aa8, 0x08080aa8, 0x00000ff8, // ICON_BIN + 0x00000000, 0x00000000, 0x20043ffc, 0x08043f84, 0x04040f84, 0x04040784, 0x000007fc, 0x00000000, // ICON_HAND_POINTER + 0x00000000, 0x24400400, 0x00001480, 0x6efe0e00, 0x00000e00, 0x24401480, 0x00000400, 0x00000000, // ICON_LASER + 0x00000000, 0x03c00000, 0x08300460, 0x11181118, 0x11181118, 0x04600830, 0x000003c0, 0x00000000, // ICON_COIN + 0x00000000, 0x10880080, 0x06c00810, 0x366c07e0, 0x07e00240, 0x00001768, 0x04200240, 0x00000000, // ICON_EXPLOSION + 0x00000000, 0x3d280000, 0x2528252c, 0x3d282528, 0x05280528, 0x05e80528, 0x00000000, 0x00000000, // ICON_1UP + 0x01800000, 0x03c003c0, 0x018003c0, 0x0ff007e0, 0x0bd00bd0, 0x0a500bd0, 0x02400240, 0x02400240, // ICON_PLAYER + 0x01800000, 0x03c003c0, 0x118013c0, 0x03c81ff8, 0x07c003c8, 0x04400440, 0x0c080478, 0x00000000, // ICON_PLAYER_JUMP + 0x3ff80000, 0x30183ff8, 0x30183018, 0x3ff83ff8, 0x03000300, 0x03c003c0, 0x03e00300, 0x000003e0, // ICON_KEY + 0x3ff80000, 0x3ff83ff8, 0x33983ff8, 0x3ff83398, 0x3ff83ff8, 0x00000540, 0x0fe00aa0, 0x00000fe0, // ICON_DEMON + 0x00000000, 0x0ff00000, 0x20041008, 0x25442004, 0x10082004, 0x06000bf0, 0x00000300, 0x00000000, // ICON_TEXT_POPUP + 0x00000000, 0x11440000, 0x07f00be8, 0x1c1c0e38, 0x1c1c0c18, 0x07f00e38, 0x11440be8, 0x00000000, // ICON_GEAR_EX + 0x00000000, 0x20080000, 0x0c601010, 0x07c00fe0, 0x07c007c0, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK + 0x00000000, 0x20080000, 0x0c601010, 0x04400fe0, 0x04405554, 0x0c600fe0, 0x20081010, 0x00000000, // ICON_CRACK_POINTS + 0x00000000, 0x00800080, 0x01c001c0, 0x1ffc3ffe, 0x03e007f0, 0x07f003e0, 0x0c180770, 0x00000808, // ICON_STAR + 0x0ff00000, 0x08180810, 0x08100818, 0x0a100810, 0x08180810, 0x08100818, 0x08100810, 0x00001ff8, // ICON_DOOR + 0x0ff00000, 0x08100810, 0x08100810, 0x10100010, 0x4f902010, 0x10102010, 0x08100010, 0x00000ff0, // ICON_EXIT + 0x00040000, 0x001f000e, 0x0ef40004, 0x12f41284, 0x0ef41214, 0x10040004, 0x7ffc3004, 0x10003000, // ICON_MODE_2D + 0x78040000, 0x501f600e, 0x0ef44004, 0x12f41284, 0x0ef41284, 0x10140004, 0x7ffc300c, 0x10003000, // ICON_MODE_3D + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE + 0x7fe00000, 0x5ff87ff0, 0x47fe4ffc, 0x44224402, 0x44224422, 0x241275e2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_TOP + 0x7fe00000, 0x50386030, 0x47c2483c, 0x443e443e, 0x443e443e, 0x241e75fe, 0x0c06140e, 0x000007fe, // ICON_CUBE_FACE_LEFT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x47fe47fe, 0x47fe47fe, 0x27fe77fe, 0x0ffe17fe, 0x000007fe, // ICON_CUBE_FACE_FRONT + 0x7fe00000, 0x50286030, 0x47fe4804, 0x44224402, 0x44224422, 0x3bf27be2, 0x0bfe1bfa, 0x000007fe, // ICON_CUBE_FACE_BOTTOM + 0x7fe00000, 0x70286030, 0x7ffe7804, 0x7c227c02, 0x7c227c22, 0x3c127de2, 0x0c061c0a, 0x000007fe, // ICON_CUBE_FACE_RIGHT + 0x7fe00000, 0x6fe85ff0, 0x781e77e4, 0x7be27be2, 0x7be27be2, 0x24127be2, 0x0c06140a, 0x000007fe, // ICON_CUBE_FACE_BACK + 0x00000000, 0x2a0233fe, 0x22022602, 0x22022202, 0x2a022602, 0x00a033fe, 0x02080110, 0x00000000, // ICON_CAMERA + 0x00000000, 0x200c3ffc, 0x000c000c, 0x3ffc000c, 0x30003000, 0x30003000, 0x3ffc3004, 0x00000000, // ICON_SPECIAL + 0x00000000, 0x0022003e, 0x012201e2, 0x0100013e, 0x01000100, 0x79000100, 0x4f004900, 0x00007800, // ICON_LINK_NET + 0x00000000, 0x44007c00, 0x45004600, 0x00627cbe, 0x00620022, 0x45007cbe, 0x44004600, 0x00007c00, // ICON_LINK_BOXES + 0x00000000, 0x0044007c, 0x0010007c, 0x3f100010, 0x3f1021f0, 0x3f100010, 0x3f0021f0, 0x00000000, // ICON_LINK_MULTI + 0x00000000, 0x0044007c, 0x00440044, 0x0010007c, 0x00100010, 0x44107c10, 0x440047f0, 0x00007c00, // ICON_LINK + 0x00000000, 0x0044007c, 0x00440044, 0x0000007c, 0x00000010, 0x44007c10, 0x44004550, 0x00007c00, // ICON_LINK_BROKE + 0x02a00000, 0x22a43ffc, 0x20042004, 0x20042ff4, 0x20042ff4, 0x20042ff4, 0x20042004, 0x00003ffc, // ICON_TEXT_NOTES + 0x3ffc0000, 0x20042004, 0x245e27c4, 0x27c42444, 0x2004201e, 0x201e2004, 0x20042004, 0x00003ffc, // ICON_NOTEBOOK + 0x00000000, 0x07e00000, 0x04200420, 0x24243ffc, 0x24242424, 0x24242424, 0x3ffc2424, 0x00000000, // ICON_SUITCASE + 0x00000000, 0x0fe00000, 0x08200820, 0x40047ffc, 0x7ffc5554, 0x40045554, 0x7ffc4004, 0x00000000, // ICON_SUITCASE_ZIP + 0x00000000, 0x20043ffc, 0x3ffc2004, 0x13c81008, 0x100813c8, 0x10081008, 0x1ff81008, 0x00000000, // ICON_MAILBOX + 0x00000000, 0x40027ffe, 0x5ffa5ffa, 0x5ffa5ffa, 0x40025ffa, 0x03c07ffe, 0x1ff81ff8, 0x00000000, // ICON_MONITOR + 0x0ff00000, 0x6bfe7ffe, 0x7ffe7ffe, 0x68167ffe, 0x08106816, 0x08100810, 0x0ff00810, 0x00000000, // ICON_PRINTER + 0x3ff80000, 0xfffe2008, 0x870a8002, 0x904a888a, 0x904a904a, 0x870a888a, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA + 0x0fc00000, 0xfcfe0cd8, 0x8002fffe, 0x84428382, 0x84428442, 0x80028382, 0xfffe8002, 0x00000000, // ICON_PHOTO_CAMERA_FLASH + 0x00000000, 0x02400180, 0x08100420, 0x20041008, 0x23c42004, 0x22442244, 0x3ffc2244, 0x00000000, // ICON_HOUSE + 0x00000000, 0x1c700000, 0x3ff83ef8, 0x3ff83ff8, 0x0fe01ff0, 0x038007c0, 0x00000100, 0x00000000, // ICON_HEART + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x80000000, 0xe000c000, // ICON_CORNER + 0x00000000, 0x14001c00, 0x15c01400, 0x15401540, 0x155c1540, 0x15541554, 0x1ddc1554, 0x00000000, // ICON_VERTICAL_BARS + 0x00000000, 0x03000300, 0x1b001b00, 0x1b601b60, 0x1b6c1b60, 0x1b6c1b6c, 0x1b6c1b6c, 0x00000000, // ICON_VERTICAL_BARS_FILL + 0x00000000, 0x00000000, 0x403e7ffe, 0x7ffe403e, 0x7ffe0000, 0x43fe43fe, 0x00007ffe, 0x00000000, // ICON_LIFE_BARS + 0x7ffc0000, 0x43844004, 0x43844284, 0x43844004, 0x42844284, 0x42844284, 0x40044384, 0x00007ffc, // ICON_INFO + 0x40008000, 0x10002000, 0x04000800, 0x01000200, 0x00400080, 0x00100020, 0x00040008, 0x00010002, // ICON_CROSSLINE + 0x00000000, 0x1ff01ff0, 0x18301830, 0x1f001830, 0x03001f00, 0x00000300, 0x03000300, 0x00000000, // ICON_HELP + 0x3ff00000, 0x2abc3550, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x2aac3554, 0x00003ffc, // ICON_FILETYPE_ALPHA + 0x3ff00000, 0x201c2010, 0x22442184, 0x28142424, 0x29942814, 0x2ff42994, 0x20042004, 0x00003ffc, // ICON_FILETYPE_HOME + 0x07fe0000, 0x04020402, 0x7fe20402, 0x44224422, 0x44224422, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS_VISIBLE + 0x07fe0000, 0x04020402, 0x7c020402, 0x44024402, 0x44024402, 0x402047fe, 0x40204020, 0x00007fe0, // ICON_LAYERS + 0x00000000, 0x40027ffe, 0x7ffe4002, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_WINDOW + 0x09100000, 0x09f00910, 0x09100910, 0x00000910, 0x24a2779e, 0x27a224a2, 0x709e20a2, 0x00000000, // ICON_HIDPI + 0x3ff00000, 0x201c2010, 0x2a842e84, 0x2e842a84, 0x2ba42004, 0x2aa42aa4, 0x20042ba4, 0x00003ffc, // ICON_FILETYPE_BINARY + 0x00000000, 0x00000000, 0x00120012, 0x4a5e4bd2, 0x485233d2, 0x00004bd2, 0x00000000, 0x00000000, // ICON_HEX + 0x01800000, 0x381c0660, 0x23c42004, 0x23c42044, 0x13c82204, 0x08101008, 0x02400420, 0x00000180, // ICON_SHIELD + 0x007e0000, 0x20023fc2, 0x40227fe2, 0x400a403a, 0x400a400a, 0x400a400a, 0x4008400e, 0x00007ff8, // ICON_FILE_NEW + 0x00000000, 0x0042007e, 0x40027fc2, 0x44024002, 0x5f024402, 0x44024402, 0x7ffe4002, 0x00000000, // ICON_FOLDER_ADD + 0x44220000, 0x12482244, 0xf3cf0000, 0x14280420, 0x48122424, 0x08100810, 0x1ff81008, 0x03c00420, // ICON_ALARM + 0x0aa00000, 0x1ff80aa0, 0x1068700e, 0x1008706e, 0x1008700e, 0x1008700e, 0x0aa01ff8, 0x00000aa0, // ICON_CPU + 0x07e00000, 0x04201db8, 0x04a01c38, 0x04a01d38, 0x04a01d38, 0x04a01d38, 0x04201d38, 0x000007e0, // ICON_ROM + 0x00000000, 0x03c00000, 0x3c382ff0, 0x3c04380c, 0x01800000, 0x03c003c0, 0x00000180, 0x00000000, // ICON_STEP_OVER + 0x01800000, 0x01800180, 0x01800180, 0x03c007e0, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_INTO + 0x01800000, 0x07e003c0, 0x01800180, 0x01800180, 0x00000180, 0x01800000, 0x03c003c0, 0x00000180, // ICON_STEP_OUT + 0x00000000, 0x0ff003c0, 0x181c1c34, 0x303c301c, 0x30003000, 0x1c301800, 0x03c00ff0, 0x00000000, // ICON_RESTART + 0x00000000, 0x00000000, 0x07e003c0, 0x0ff00ff0, 0x0ff00ff0, 0x03c007e0, 0x00000000, 0x00000000, // ICON_BREAKPOINT_ON + 0x00000000, 0x00000000, 0x042003c0, 0x08100810, 0x08100810, 0x03c00420, 0x00000000, 0x00000000, // ICON_BREAKPOINT_OFF + 0x00000000, 0x00000000, 0x1ff81ff8, 0x1ff80000, 0x00001ff8, 0x1ff81ff8, 0x00000000, 0x00000000, // ICON_BURGER_MENU + 0x00000000, 0x00000000, 0x00880070, 0x0c880088, 0x1e8810f8, 0x3e881288, 0x00000000, 0x00000000, // ICON_CASE_SENSITIVE + 0x00000000, 0x02000000, 0x07000a80, 0x07001fc0, 0x02000a80, 0x00300030, 0x00000000, 0x00000000, // ICON_REG_EXP + 0x00000000, 0x0042007e, 0x40027fc2, 0x40024002, 0x40024002, 0x40024002, 0x7ffe4002, 0x00000000, // ICON_FOLDER + 0x3ff00000, 0x201c2010, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x20042004, 0x00003ffc, // ICON_FILE + 0x1ff00000, 0x20082008, 0x17d02fe8, 0x05400ba0, 0x09200540, 0x23881010, 0x2fe827c8, 0x00001ff0, // ICON_SAND_TIMER + 0x01800000, 0x02400240, 0x05a00420, 0x09900990, 0x11881188, 0x21842004, 0x40024182, 0x00003ffc, // ICON_WARNING + 0x7ffe0000, 0x4ff24002, 0x4c324ff2, 0x4f824c02, 0x41824f82, 0x41824002, 0x40024182, 0x00007ffe, // ICON_HELP_BOX + 0x7ffe0000, 0x41824002, 0x40024182, 0x41824182, 0x41824182, 0x41824182, 0x40024182, 0x00007ffe, // ICON_INFO_BOX + 0x01800000, 0x04200240, 0x10080810, 0x7bde2004, 0x0a500a50, 0x08500bd0, 0x08100850, 0x00000ff0, // ICON_PRIORITY + 0x01800000, 0x18180660, 0x80016006, 0x98196006, 0x99996666, 0x19986666, 0x01800660, 0x00000000, // ICON_LAYERS_ISO + 0x07fe0000, 0x1c020402, 0x74021402, 0x54025402, 0x54025402, 0x500857fe, 0x40205ff8, 0x00007fe0, // ICON_LAYERS2 + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x422a422a, 0x422e422a, 0x40384e28, 0x00007fe0, // ICON_MLAYERS + 0x0ffe0000, 0x3ffa0802, 0x7fea200a, 0x402a402a, 0x5b2a512a, 0x512e552a, 0x40385128, 0x00007fe0, // ICON_MAPS + 0x04200000, 0x1cf00c60, 0x11f019f0, 0x0f3807b8, 0x1e3c0f3c, 0x1c1c1e1c, 0x1e3c1c1c, 0x00000f70, // ICON_HOT + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_229 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_230 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_231 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_232 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_233 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_234 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_235 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_236 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_237 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_238 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_239 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_240 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_241 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_242 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_243 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_244 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_245 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_246 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_247 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_248 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_249 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_250 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_251 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_252 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_253 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_254 + 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, // ICON_255 +}; + +// NOTE: A pointer to current icons array should be defined +static unsigned int *guiIconsPtr = guiIcons; + +#endif // !RAYGUI_NO_ICONS && !RAYGUI_CUSTOM_ICONS + +#ifndef RAYGUI_ICON_SIZE + #define RAYGUI_ICON_SIZE 0 +#endif + +// WARNING: Those values define the total size of the style data array, +// if changed, previous saved styles could become incompatible +#define RAYGUI_MAX_CONTROLS 16 // Maximum number of controls +#define RAYGUI_MAX_PROPS_BASE 16 // Maximum number of base properties +#define RAYGUI_MAX_PROPS_EXTENDED 8 // Maximum number of extended properties + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +// Gui control property style color element +typedef enum { BORDER = 0, BASE, TEXT, OTHER } GuiPropertyElement; + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static GuiState guiState = STATE_NORMAL; // Gui global state, if !STATE_NORMAL, forces defined state + +static Font guiFont = { 0 }; // Gui current font (WARNING: highly coupled to raylib) +static bool guiLocked = false; // Gui lock state (no inputs processed) +static float guiAlpha = 1.0f; // Gui controls transparency + +static unsigned int guiIconScale = 1; // Gui icon default scale (if icons enabled) + +static bool guiTooltip = false; // Tooltip enabled/disabled +static const char *guiTooltipPtr = NULL; // Tooltip string pointer (string provided by user) + +static bool guiControlExclusiveMode = false; // Gui control exclusive mode (no inputs processed except current control) +static Rectangle guiControlExclusiveRec = { 0 }; // Gui control exclusive bounds rectangle, used as an unique identifier + +static int textBoxCursorIndex = 0; // Cursor index, shared by all GuiTextBox*() +//static int blinkCursorFrameCounter = 0; // Frame counter for cursor blinking +static int autoCursorCooldownCounter = 0; // Cooldown frame counter for automatic cursor movement on key-down +static int autoCursorDelayCounter = 0; // Delay frame counter for automatic cursor movement + +//---------------------------------------------------------------------------------- +// Style data array for all gui style properties (allocated on data segment by default) +// +// NOTE 1: First set of BASE properties are generic to all controls but could be individually +// overwritten per control, first set of EXTENDED properties are generic to all controls and +// can not be overwritten individually but custom EXTENDED properties can be used by control +// +// NOTE 2: A new style set could be loaded over this array using GuiLoadStyle(), +// but default gui style could always be recovered with GuiLoadStyleDefault() +// +// guiStyle size is by default: 16*(16 + 8) = 384*4 = 1536 bytes = 1.5 KB +//---------------------------------------------------------------------------------- +static unsigned int guiStyle[RAYGUI_MAX_CONTROLS*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED)] = { 0 }; + +static bool guiStyleLoaded = false; // Style loaded flag for lazy style initialization + +//---------------------------------------------------------------------------------- +// Standalone Mode Functions Declaration +// +// NOTE: raygui depend on some raylib input and drawing functions +// To use raygui as standalone library, below functions must be defined by the user +//---------------------------------------------------------------------------------- +#if defined(RAYGUI_STANDALONE) + +#define KEY_RIGHT 262 +#define KEY_LEFT 263 +#define KEY_DOWN 264 +#define KEY_UP 265 +#define KEY_BACKSPACE 259 +#define KEY_ENTER 257 + +#define MOUSE_LEFT_BUTTON 0 + +// Input required functions +//------------------------------------------------------------------------------- +static Vector2 GetMousePosition(void); +static float GetMouseWheelMove(void); +static bool IsMouseButtonDown(int button); +static bool IsMouseButtonPressed(int button); +static bool IsMouseButtonReleased(int button); + +static bool IsKeyDown(int key); +static bool IsKeyPressed(int key); +static int GetCharPressed(void); // -- GuiTextBox(), GuiValueBox() +//------------------------------------------------------------------------------- + +// Drawing required functions +//------------------------------------------------------------------------------- +static void DrawRectangle(int x, int y, int width, int height, Color color); // -- GuiDrawRectangle() +static void DrawRectangleGradientEx(Rectangle rec, Color col1, Color col2, Color col3, Color col4); // -- GuiColorPicker() +//------------------------------------------------------------------------------- + +// Text required functions +//------------------------------------------------------------------------------- +static Font GetFontDefault(void); // -- GuiLoadStyleDefault() +static Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // -- GuiLoadStyle(), load font + +static Texture2D LoadTextureFromImage(Image image); // -- GuiLoadStyle(), required to load texture from embedded font atlas image +static void SetShapesTexture(Texture2D tex, Rectangle rec); // -- GuiLoadStyle(), required to set shapes rec to font white rec (optimization) + +static char *LoadFileText(const char *fileName); // -- GuiLoadStyle(), required to load charset data +static void UnloadFileText(char *text); // -- GuiLoadStyle(), required to unload charset data + +static const char *GetDirectoryPath(const char *filePath); // -- GuiLoadStyle(), required to find charset/font file from text .rgs + +static int *LoadCodepoints(const char *text, int *count); // -- GuiLoadStyle(), required to load required font codepoints list +static void UnloadCodepoints(int *codepoints); // -- GuiLoadStyle(), required to unload codepoints list + +static unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // -- GuiLoadStyle() +//------------------------------------------------------------------------------- + +// raylib functions already implemented in raygui +//------------------------------------------------------------------------------- +static Color GetColor(int hexValue); // Returns a Color struct from hexadecimal value +static int ColorToInt(Color color); // Returns hexadecimal value for a Color +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +static const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' +static const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +static int TextToInteger(const char *text); // Get integer value from text +static float TextToFloat(const char *text); // Get float value from text + +static int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded text +static const char *CodepointToUTF8(int codepoint, int *byteSize); // Encode codepoint into UTF-8 text (char array size returned as parameter) + +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2); // Draw rectangle vertical gradient +//------------------------------------------------------------------------------- + +#endif // RAYGUI_STANDALONE + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize); // Load style from memory (binary only) + +static int GetTextWidth(const char *text); // Gui get text width using gui font and style +static Rectangle GetTextBounds(int control, Rectangle bounds); // Get text bounds considering control bounds +static const char *GetTextIcon(const char *text, int *iconId); // Get text icon if provided and move text cursor + +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint); // Gui draw text using default font +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color); // Gui draw rectangle using default raygui style + +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow); // Split controls text into multiple strings +static Vector3 ConvertHSVtoRGB(Vector3 hsv); // Convert color data from HSV to RGB +static Vector3 ConvertRGBtoHSV(Vector3 rgb); // Convert color data from RGB to HSV + +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue); // Scroll bar control, used by GuiScrollPanel() +static void GuiTooltip(Rectangle controlRec); // Draw tooltip using control rec position + +static Color GuiFade(Color color, float alpha); // Fade color by an alpha factor + +//---------------------------------------------------------------------------------- +// Gui Setup Functions Definition +//---------------------------------------------------------------------------------- +// Enable gui global state +// NOTE: We check for STATE_DISABLED to avoid messing custom global state setups +void GuiEnable(void) { if (guiState == STATE_DISABLED) guiState = STATE_NORMAL; } + +// Disable gui global state +// NOTE: We check for STATE_NORMAL to avoid messing custom global state setups +void GuiDisable(void) { if (guiState == STATE_NORMAL) guiState = STATE_DISABLED; } + +// Lock gui global state +void GuiLock(void) { guiLocked = true; } + +// Unlock gui global state +void GuiUnlock(void) { guiLocked = false; } + +// Check if gui is locked (global state) +bool GuiIsLocked(void) { return guiLocked; } + +// Set gui controls alpha global state +void GuiSetAlpha(float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + guiAlpha = alpha; +} + +// Set gui state (global state) +void GuiSetState(int state) { guiState = (GuiState)state; } + +// Get gui state (global state) +int GuiGetState(void) { return guiState; } + +// Set custom gui font +// NOTE: Font loading/unloading is external to raygui +void GuiSetFont(Font font) +{ + if (font.texture.id > 0) + { + // NOTE: If we try to setup a font but default style has not been + // lazily loaded before, it will be overwritten, so we need to force + // default style loading first + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + guiFont = font; + } +} + +// Get custom gui font +Font GuiGetFont(void) +{ + return guiFont; +} + +// Set control style property value +void GuiSetStyle(int control, int property, int value) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + + // Default properties are propagated to all controls + if ((control == 0) && (property < RAYGUI_MAX_PROPS_BASE)) + { + for (int i = 1; i < RAYGUI_MAX_CONTROLS; i++) guiStyle[i*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property] = value; + } +} + +// Get control style property value +int GuiGetStyle(int control, int property) +{ + if (!guiStyleLoaded) GuiLoadStyleDefault(); + return guiStyle[control*(RAYGUI_MAX_PROPS_BASE + RAYGUI_MAX_PROPS_EXTENDED) + property]; +} + +//---------------------------------------------------------------------------------- +// Gui Controls Functions Definition +//---------------------------------------------------------------------------------- + +// Window Box control +int GuiWindowBox(Rectangle bounds, const char *title) +{ + // Window title bar height (including borders) + // NOTE: This define is also used by GuiMessageBox() and GuiTextInputBox() + #if !defined(RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT) + #define RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT 24 + #endif + + int result = 0; + //GuiState state = guiState; + + int statusBarHeight = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT; + + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)statusBarHeight }; + if (bounds.height < statusBarHeight*2.0f) bounds.height = statusBarHeight*2.0f; + + Rectangle windowPanel = { bounds.x, bounds.y + (float)statusBarHeight - 1, bounds.width, bounds.height - (float)statusBarHeight + 1 }; + Rectangle closeButtonRec = { statusBar.x + statusBar.width - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - 20, + statusBar.y + statusBarHeight/2.0f - 18.0f/2.0f, 18, 18 }; + + // Update control + //-------------------------------------------------------------------- + // NOTE: Logic is directly managed by button + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiStatusBar(statusBar, title); // Draw window header as status bar + GuiPanel(windowPanel, NULL); // Draw window base + + // Draw window close button + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + result = GuiButton(closeButtonRec, "x"); +#else + result = GuiButton(closeButtonRec, GuiIconText(ICON_CROSS_SMALL, NULL)); +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + //-------------------------------------------------------------------- + + return result; // Window close button clicked: result = 1 +} + +// Group Box control with text name +int GuiGroupBox(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_GROUPBOX_LINE_THICK) + #define RAYGUI_GROUPBOX_LINE_THICK 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, RAYGUI_GROUPBOX_LINE_THICK }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y, RAYGUI_GROUPBOX_LINE_THICK, bounds.height }, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR))); + + GuiLine(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y - GuiGetStyle(DEFAULT, TEXT_SIZE)/2, bounds.width, (float)GuiGetStyle(DEFAULT, TEXT_SIZE) }, text); + //-------------------------------------------------------------------- + + return result; +} + +// Line control +int GuiLine(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_LINE_ORIGIN_SIZE) + #define RAYGUI_LINE_MARGIN_TEXT 12 + #endif + #if !defined(RAYGUI_LINE_TEXT_PADDING) + #define RAYGUI_LINE_TEXT_PADDING 4 + #endif + + int result = 0; + GuiState state = guiState; + + Color color = GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED : (int)LINE_COLOR)); + + // Draw control + //-------------------------------------------------------------------- + if (text == NULL) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, bounds.width, 1 }, 0, BLANK, color); + else + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = bounds.height; + textBounds.x = bounds.x + RAYGUI_LINE_MARGIN_TEXT; + textBounds.y = bounds.y; + + // Draw line with embedded text label: "--- text --------------" + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height/2, RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + GuiDrawText(text, textBounds, TEXT_ALIGN_LEFT, color); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + 12 + textBounds.width + 4, bounds.y + bounds.height/2, bounds.width - textBounds.width - RAYGUI_LINE_MARGIN_TEXT - RAYGUI_LINE_TEXT_PADDING, 1 }, 0, BLANK, color); + } + //-------------------------------------------------------------------- + + return result; +} + +// Panel control +int GuiPanel(Rectangle bounds, const char *text) +{ + #if !defined(RAYGUI_PANEL_BORDER_WIDTH) + #define RAYGUI_PANEL_BORDER_WIDTH 1 + #endif + + int result = 0; + GuiState state = guiState; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if ((text != NULL) && (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f)) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + } + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, RAYGUI_PANEL_BORDER_WIDTH, GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? (int)BORDER_COLOR_DISABLED: (int)LINE_COLOR)), + GetColor(GuiGetStyle(DEFAULT, (state == STATE_DISABLED)? BASE_COLOR_DISABLED : BACKGROUND_COLOR))); + //-------------------------------------------------------------------- + + return result; +} + +// Tab Bar control +// NOTE: Using GuiToggle() for the TABS +int GuiTabBar(Rectangle bounds, const char **text, int count, int *active) +{ + #define RAYGUI_TABBAR_ITEM_WIDTH 160 + + int result = -1; + //GuiState state = guiState; + + Rectangle tabBounds = { bounds.x, bounds.y, RAYGUI_TABBAR_ITEM_WIDTH, bounds.height }; + + if (*active < 0) *active = 0; + else if (*active > count - 1) *active = count - 1; + + int offsetX = 0; // Required in case tabs go out of screen + offsetX = (*active + 2)*RAYGUI_TABBAR_ITEM_WIDTH - GetScreenWidth(); + if (offsetX < 0) offsetX = 0; + + bool toggle = false; // Required for individual toggles + + // Draw control + //-------------------------------------------------------------------- + for (int i = 0; i < count; i++) + { + tabBounds.x = bounds.x + (RAYGUI_TABBAR_ITEM_WIDTH + 4)*i - offsetX; + + if (tabBounds.x < GetScreenWidth()) + { + // Draw tabs as toggle controls + int textAlignment = GuiGetStyle(TOGGLE, TEXT_ALIGNMENT); + int textPadding = GuiGetStyle(TOGGLE, TEXT_PADDING); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(TOGGLE, TEXT_PADDING, 8); + + if (i == (*active)) + { + toggle = true; + GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + } + else + { + toggle = false; + GuiToggle(tabBounds, GuiIconText(12, text[i]), &toggle); + if (toggle) *active = i; + } + + // Close tab with middle mouse button pressed + if (CheckCollisionPointRec(GetMousePosition(), tabBounds) && IsMouseButtonPressed(MOUSE_MIDDLE_BUTTON)) result = i; + + GuiSetStyle(TOGGLE, TEXT_PADDING, textPadding); + GuiSetStyle(TOGGLE, TEXT_ALIGNMENT, textAlignment); + + // Draw tab close button + // NOTE: Only draw close button for current tab: if (CheckCollisionPointRec(mousePosition, tabBounds)) + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, "x")) result = i; +#else + if (GuiButton(RAYGUI_CLITERAL(Rectangle){ tabBounds.x + tabBounds.width - 14 - 5, tabBounds.y + 5, 14, 14 }, GuiIconText(ICON_CROSS_SMALL, NULL))) result = i; +#endif + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlignment); + } + } + + // Draw tab-bar bottom line + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, bounds.width, 1 }, 0, BLANK, GetColor(GuiGetStyle(TOGGLE, BORDER_COLOR_NORMAL))); + //-------------------------------------------------------------------- + + return result; // Return as result the current TAB closing requested +} + +// Scroll Panel control +int GuiScrollPanel(Rectangle bounds, const char *text, Rectangle content, Vector2 *scroll, Rectangle *view) +{ + #define RAYGUI_MIN_SCROLLBAR_WIDTH 40 + #define RAYGUI_MIN_SCROLLBAR_HEIGHT 40 + #define RAYGUI_MIN_MOUSE_WHEEL_SPEED 20 + + int result = 0; + GuiState state = guiState; + + Rectangle temp = { 0 }; + if (view == NULL) view = &temp; + + Vector2 scrollPos = { 0.0f, 0.0f }; + if (scroll != NULL) scrollPos = *scroll; + + // Text will be drawn as a header bar (if provided) + Rectangle statusBar = { bounds.x, bounds.y, bounds.width, (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT }; + if (bounds.height < RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f) bounds.height = RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT*2.0f; + + if (text != NULL) + { + // Move panel bounds after the header bar + bounds.y += (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 1; + bounds.height -= (float)RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + 1; + } + + bool hasHorizontalScrollBar = (content.width > bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + bool hasVerticalScrollBar = (content.height > bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH))? true : false; + + // Recheck to account for the other scrollbar being visible + if (!hasHorizontalScrollBar) hasHorizontalScrollBar = (hasVerticalScrollBar && (content.width > (bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + if (!hasVerticalScrollBar) hasVerticalScrollBar = (hasHorizontalScrollBar && (content.height > (bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH))))? true : false; + + int horizontalScrollBarWidth = hasHorizontalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + int verticalScrollBarWidth = hasVerticalScrollBar? GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH) : 0; + Rectangle horizontalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + verticalScrollBarWidth : (float)bounds.x) + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.y + bounds.height - horizontalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)bounds.width - verticalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)horizontalScrollBarWidth + }; + Rectangle verticalScrollBar = { + (float)((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)bounds.x + bounds.width - verticalScrollBarWidth - GuiGetStyle(DEFAULT, BORDER_WIDTH)), + (float)bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), + (float)verticalScrollBarWidth, + (float)bounds.height - horizontalScrollBarWidth - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Make sure scroll bars have a minimum width/height + if (horizontalScrollBar.width < RAYGUI_MIN_SCROLLBAR_WIDTH) horizontalScrollBar.width = RAYGUI_MIN_SCROLLBAR_WIDTH; + if (verticalScrollBar.height < RAYGUI_MIN_SCROLLBAR_HEIGHT) verticalScrollBar.height = RAYGUI_MIN_SCROLLBAR_HEIGHT; + + // Calculate view area (area without the scrollbars) + *view = (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? + RAYGUI_CLITERAL(Rectangle){ bounds.x + verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth } : + RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.y + GuiGetStyle(DEFAULT, BORDER_WIDTH), bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth, bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth }; + + // Clip view area to the actual content size + if (view->width > content.width) view->width = content.width; + if (view->height > content.height) view->height = content.height; + + float horizontalMin = hasHorizontalScrollBar? ((GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)-verticalScrollBarWidth : 0) - (float)GuiGetStyle(DEFAULT, BORDER_WIDTH); + float horizontalMax = hasHorizontalScrollBar? content.width - bounds.width + (float)verticalScrollBarWidth + GuiGetStyle(DEFAULT, BORDER_WIDTH) - (((float)GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (float)verticalScrollBarWidth : 0) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + float verticalMin = hasVerticalScrollBar? 0.0f : -1.0f; + float verticalMax = hasVerticalScrollBar? content.height - bounds.height + (float)horizontalScrollBarWidth + (float)GuiGetStyle(DEFAULT, BORDER_WIDTH) : (float)-GuiGetStyle(DEFAULT, BORDER_WIDTH); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + +#if defined(SUPPORT_SCROLLBAR_KEY_INPUT) + if (hasHorizontalScrollBar) + { + if (IsKeyDown(KEY_RIGHT)) scrollPos.x -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_LEFT)) scrollPos.x += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } + + if (hasVerticalScrollBar) + { + if (IsKeyDown(KEY_DOWN)) scrollPos.y -= GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + if (IsKeyDown(KEY_UP)) scrollPos.y += GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + } +#endif + float wheelMove = GetMouseWheelMove(); + + // Set scrolling speed with mouse wheel based on ratio between bounds and content + Vector2 mouseWheelSpeed = { content.width/bounds.width, content.height/bounds.height }; + if (mouseWheelSpeed.x < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.x = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + if (mouseWheelSpeed.y < RAYGUI_MIN_MOUSE_WHEEL_SPEED) mouseWheelSpeed.y = RAYGUI_MIN_MOUSE_WHEEL_SPEED; + + // Horizontal and vertical scrolling with mouse wheel + if (hasHorizontalScrollBar && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_LEFT_SHIFT))) scrollPos.x += wheelMove*mouseWheelSpeed.x; + else scrollPos.y += wheelMove*mouseWheelSpeed.y; // Vertical scroll + } + } + + // Normalize scroll values + if (scrollPos.x > -horizontalMin) scrollPos.x = -horizontalMin; + if (scrollPos.x < -horizontalMax) scrollPos.x = -horizontalMax; + if (scrollPos.y > -verticalMin) scrollPos.y = -verticalMin; + if (scrollPos.y < -verticalMax) scrollPos.y = -verticalMax; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (text != NULL) GuiStatusBar(statusBar, text); // Draw panel header as status bar + + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Save size of the scrollbar slider + const int slider = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + + // Draw horizontal scrollbar if visible + if (hasHorizontalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content width and the widget width + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth)/(int)content.width)*((int)bounds.width - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - verticalScrollBarWidth))); + scrollPos.x = (float)-GuiScrollBar(horizontalScrollBar, (int)-scrollPos.x, (int)horizontalMin, (int)horizontalMax); + } + else scrollPos.x = 0.0f; + + // Draw vertical scrollbar if visible + if (hasVerticalScrollBar) + { + // Change scrollbar slider size to show the diff in size between the content height and the widget height + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)(((bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth)/(int)content.height)*((int)bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) - horizontalScrollBarWidth))); + scrollPos.y = (float)-GuiScrollBar(verticalScrollBar, (int)-scrollPos.y, (int)verticalMin, (int)verticalMax); + } + else scrollPos.y = 0.0f; + + // Draw detail corner rectangle if both scroll bars are visible + if (hasHorizontalScrollBar && hasVerticalScrollBar) + { + Rectangle corner = { (GuiGetStyle(LISTVIEW, SCROLLBAR_SIDE) == SCROLLBAR_LEFT_SIDE)? (bounds.x + GuiGetStyle(DEFAULT, BORDER_WIDTH) + 2) : (horizontalScrollBar.x + horizontalScrollBar.width + 2), verticalScrollBar.y + verticalScrollBar.height + 2, (float)horizontalScrollBarWidth - 4, (float)verticalScrollBarWidth - 4 }; + GuiDrawRectangle(corner, 0, BLANK, GetColor(GuiGetStyle(LISTVIEW, TEXT + (state*3)))); + } + + // Draw scrollbar lines depending on current state + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + (state*3))), BLANK); + + // Set scrollbar slider size back to the way it was before + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, slider); + //-------------------------------------------------------------------- + + if (scroll != NULL) *scroll = scrollPos; + + return result; +} + +// Label control +int GuiLabel(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + //... + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Button control, returns true when clicked +int GuiButton(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(BUTTON, BORDER_WIDTH), GetColor(GuiGetStyle(BUTTON, BORDER + (state*3))), GetColor(GuiGetStyle(BUTTON, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(BUTTON, bounds), GuiGetStyle(BUTTON, TEXT_ALIGNMENT), GetColor(GuiGetStyle(BUTTON, TEXT + (state*3)))); + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //------------------------------------------------------------------ + + return result; // Button pressed: result = 1 +} + +// Label button control +int GuiLabelButton(Rectangle bounds, const char *text) +{ + GuiState state = guiState; + bool pressed = false; + + // NOTE: We force bounds.width to be all text + float textWidth = (float)GetTextWidth(text); + if ((bounds.width - 2*GuiGetStyle(LABEL, BORDER_WIDTH) - 2*GuiGetStyle(LABEL, TEXT_PADDING)) < textWidth) bounds.width = textWidth + 2*GuiGetStyle(LABEL, BORDER_WIDTH) + 2*GuiGetStyle(LABEL, TEXT_PADDING) + 2; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) pressed = true; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawText(text, GetTextBounds(LABEL, bounds), GuiGetStyle(LABEL, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return pressed; +} + +// Toggle Button control +int GuiToggle(Rectangle bounds, const char *text, bool *active) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (active == NULL) active = &temp; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check toggle button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_NORMAL; + *active = !(*active); + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_NORMAL) + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, ((*active)? BORDER_COLOR_PRESSED : (BORDER + state*3)))), GetColor(GuiGetStyle(TOGGLE, ((*active)? BASE_COLOR_PRESSED : (BASE + state*3))))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, ((*active)? TEXT_COLOR_PRESSED : (TEXT + state*3))))); + } + else + { + GuiDrawRectangle(bounds, GuiGetStyle(TOGGLE, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + state*3)), GetColor(GuiGetStyle(TOGGLE, BASE + state*3))); + GuiDrawText(text, GetTextBounds(TOGGLE, bounds), GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TOGGLE, TEXT + state*3))); + } + + if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; +} + +// Toggle Group control +int GuiToggleGroup(Rectangle bounds, const char *text, int *active) +{ + #if !defined(RAYGUI_TOGGLEGROUP_MAX_ITEMS) + #define RAYGUI_TOGGLEGROUP_MAX_ITEMS 32 + #endif + + int result = 0; + float initBoundsX = bounds.x; + + int temp = 0; + if (active == NULL) active = &temp; + + bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int rows[RAYGUI_TOGGLEGROUP_MAX_ITEMS] = { 0 }; + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, rows); + + int prevRow = rows[0]; + + for (int i = 0; i < itemCount; i++) + { + if (prevRow != rows[i]) + { + bounds.x = initBoundsX; + bounds.y += (bounds.height + GuiGetStyle(TOGGLE, GROUP_PADDING)); + prevRow = rows[i]; + } + + if (i == (*active)) + { + toggle = true; + GuiToggle(bounds, items[i], &toggle); + } + else + { + toggle = false; + GuiToggle(bounds, items[i], &toggle); + if (toggle) *active = i; + } + + bounds.x += (bounds.width + GuiGetStyle(TOGGLE, GROUP_PADDING)); + } + + return result; +} + +// Toggle Slider control extended +int GuiToggleSlider(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + //bool toggle = false; // Required for individual toggles + + // Get substrings items from text (items pointers) + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle slider = { + 0, // Calculated later depending on the active toggle + bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + (bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - (itemCount + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING))/itemCount, + bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + (*active)++; + result = 1; + } + else state = STATE_FOCUSED; + } + + if ((*active) && (state != STATE_FOCUSED)) state = STATE_PRESSED; + } + + if (*active >= itemCount) *active = 0; + slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH) + (*active + 1)*GuiGetStyle(SLIDER, SLIDER_PADDING) + (*active)*slider.width; + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(TOGGLE, BORDER + (state*3))), + GetColor(GuiGetStyle(TOGGLE, BASE_COLOR_NORMAL))); + + // Draw internal slider + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + + // Draw text in slider + if (text != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(text); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = slider.x + slider.width/2 - textBounds.width/2; + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(items[*active], textBounds, GuiGetStyle(TOGGLE, TEXT_ALIGNMENT), Fade(GetColor(GuiGetStyle(TOGGLE, TEXT + (state*3))), guiAlpha)); + } + //-------------------------------------------------------------------- + + return result; +} + +// Check Box control, returns 1 when state changed +int GuiCheckBox(Rectangle bounds, const char *text, bool *checked) +{ + int result = 0; + GuiState state = guiState; + + bool temp = false; + if (checked == NULL) checked = &temp; + + Rectangle textBounds = { 0 }; + + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(CHECKBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + Rectangle totalBounds = { + (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT)? textBounds.x : bounds.x, + bounds.y, + bounds.width + textBounds.width + GuiGetStyle(CHECKBOX, TEXT_PADDING), + bounds.height, + }; + + // Check checkbox state + if (CheckCollisionPointRec(mousePoint, totalBounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + *checked = !(*checked); + result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(CHECKBOX, BORDER_WIDTH), GetColor(GuiGetStyle(CHECKBOX, BORDER + (state*3))), BLANK); + + if (*checked) + { + Rectangle check = { bounds.x + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.y + GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING), + bounds.width - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)), + bounds.height - 2*(GuiGetStyle(CHECKBOX, BORDER_WIDTH) + GuiGetStyle(CHECKBOX, CHECK_PADDING)) }; + GuiDrawRectangle(check, 0, BLANK, GetColor(GuiGetStyle(CHECKBOX, TEXT + state*3))); + } + + GuiDrawText(text, textBounds, (GuiGetStyle(CHECKBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Combo Box control +int GuiComboBox(Rectangle bounds, const char *text, int *active) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + bounds.width -= (GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH) + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING)); + + Rectangle selector = { (float)bounds.x + bounds.width + GuiGetStyle(COMBOBOX, COMBO_BUTTON_SPACING), + (float)bounds.y, (float)GuiGetStyle(COMBOBOX, COMBO_BUTTON_WIDTH), (float)bounds.height }; + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + if (*active < 0) *active = 0; + else if (*active > (itemCount - 1)) *active = itemCount - 1; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (CheckCollisionPointRec(mousePoint, bounds) || + CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + *active += 1; + if (*active >= itemCount) *active = 0; // Cyclic combobox + } + + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + // Draw combo box main + GuiDrawRectangle(bounds, GuiGetStyle(COMBOBOX, BORDER_WIDTH), GetColor(GuiGetStyle(COMBOBOX, BORDER + (state*3))), GetColor(GuiGetStyle(COMBOBOX, BASE + (state*3)))); + GuiDrawText(items[*active], GetTextBounds(COMBOBOX, bounds), GuiGetStyle(COMBOBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(COMBOBOX, TEXT + (state*3)))); + + // Draw selector using a custom button + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 1); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiButton(selector, TextFormat("%i/%i", *active + 1, itemCount)); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + //-------------------------------------------------------------------- + + return result; +} + +// Dropdown Box control +// NOTE: Returns mouse click +int GuiDropdownBox(Rectangle bounds, const char *text, int *active, bool editMode) +{ + int result = 0; + GuiState state = guiState; + + int temp = 0; + if (active == NULL) active = &temp; + + int itemSelected = *active; + int itemFocused = -1; + + int direction = 0; // Dropdown box open direction: down (default) + if (GuiGetStyle(DROPDOWNBOX, DROPDOWN_ROLL_UP) == 1) direction = 1; // Up + + // Get substrings items from text (items pointers, lengths and count) + int itemCount = 0; + const char **items = GuiTextSplit(text, ';', &itemCount, NULL); + + Rectangle boundsOpen = bounds; + boundsOpen.height = (itemCount + 1)*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + if (direction == 1) boundsOpen.y -= itemCount*(bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)) + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING); + + Rectangle itemBounds = bounds; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && (editMode || !guiLocked) && (itemCount > 1) && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + if (editMode) + { + state = STATE_PRESSED; + + // Check if mouse has been pressed or released outside limits + if (!CheckCollisionPointRec(mousePoint, boundsOpen)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON) || IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) result = 1; + } + + // Check if already selected item has been pressed again + if (CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + + // Check focused and selected item + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = i; + if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) + { + itemSelected = i; + result = 1; // Item selected + } + break; + } + } + + itemBounds = bounds; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + result = 1; + state = STATE_PRESSED; + } + else state = STATE_FOCUSED; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (editMode) GuiPanel(boundsOpen, NULL); + + GuiDrawRectangle(bounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER + state*3)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE + state*3))); + GuiDrawText(items[itemSelected], GetTextBounds(DROPDOWNBOX, bounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + state*3))); + + if (editMode) + { + // Draw visible items + for (int i = 0; i < itemCount; i++) + { + // Update item rectangle y position for next item + if (direction == 0) itemBounds.y += (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + else itemBounds.y -= (bounds.height + GuiGetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING)); + + if (i == itemSelected) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_PRESSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_PRESSED))); + } + else if (i == itemFocused) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(DROPDOWNBOX, BORDER_WIDTH), GetColor(GuiGetStyle(DROPDOWNBOX, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(DROPDOWNBOX, BASE_COLOR_FOCUSED))); + GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_FOCUSED))); + } + else GuiDrawText(items[i], GetTextBounds(DROPDOWNBOX, itemBounds), GuiGetStyle(DROPDOWNBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(DROPDOWNBOX, TEXT_COLOR_NORMAL))); + } + } + + if (!GuiGetStyle(DROPDOWNBOX, DROPDOWN_ARROW_HIDDEN)) + { + // Draw arrows (using icon if available) +#if defined(RAYGUI_NO_ICONS) + GuiDrawText("v", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 2, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(direction? "#121#" : "#120#", RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - GuiGetStyle(DROPDOWNBOX, ARROW_PADDING), bounds.y + bounds.height/2 - 6, 10, 10 }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); // ICON_ARROW_DOWN_FILL +#endif + } + //-------------------------------------------------------------------- + + *active = itemSelected; + + // TODO: Use result to return more internal states: mouse-press out-of-bounds, mouse-press over selected-item... + return result; // Mouse click: result = 1 +} + +// Text Box control +// NOTE: Returns true on ENTER pressed (useful for data validation) +int GuiTextBox(Rectangle bounds, char *text, int textSize, bool editMode) +{ + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN 40 // Frames to wait for autocursor movement + #endif + #if !defined(RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) + #define RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY 1 // Frames delay for autocursor movement + #endif + + int result = 0; + GuiState state = guiState; + + bool multiline = false; // TODO: Consider multiline text input + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); + + Rectangle textBounds = GetTextBounds(TEXTBOX, bounds); + int textLength = (int)strlen(text); // Get current text length + int thisCursorIndex = textBoxCursorIndex; + if (thisCursorIndex > textLength) thisCursorIndex = textLength; + int textWidth = GetTextWidth(text) - GetTextWidth(text + thisCursorIndex); + int textIndexOffset = 0; // Text index offset to start drawing in the box + + // Cursor rectangle + // NOTE: Position X value should be updated + Rectangle cursor = { + textBounds.x + textWidth + GuiGetStyle(DEFAULT, TEXT_SPACING), + textBounds.y + textBounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE), + 2, + (float)GuiGetStyle(DEFAULT, TEXT_SIZE)*2 + }; + + if (cursor.height >= bounds.height) cursor.height = bounds.height - GuiGetStyle(TEXTBOX, BORDER_WIDTH)*2; + if (cursor.y < (bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH))) cursor.y = bounds.y + GuiGetStyle(TEXTBOX, BORDER_WIDTH); + + // Mouse cursor rectangle + // NOTE: Initialized outside of screen + Rectangle mouseCursor = cursor; + mouseCursor.x = -1; + mouseCursor.width = 1; + + // Auto-cursor movement logic + // NOTE: Cursor moves automatically when key down after some time + if (IsKeyDown(KEY_LEFT) || IsKeyDown(KEY_RIGHT) || IsKeyDown(KEY_UP) || IsKeyDown(KEY_DOWN) || IsKeyDown(KEY_BACKSPACE) || IsKeyDown(KEY_DELETE)) autoCursorCooldownCounter++; + else + { + autoCursorCooldownCounter = 0; // GLOBAL: Cursor cooldown counter + autoCursorDelayCounter = 0; // GLOBAL: Cursor delay counter + } + + // Blink-cursor frame counter + //if (!autoCursorMode) blinkCursorFrameCounter++; + //else blinkCursorFrameCounter = 0; + + // Update control + //-------------------------------------------------------------------- + // WARNING: Text editing is only supported under certain conditions: + if ((state != STATE_DISABLED) && // Control not disabled + !GuiGetStyle(TEXTBOX, TEXT_READONLY) && // TextBox not on read-only mode + !guiLocked && // Gui not locked + !guiControlExclusiveMode && // No gui slider on dragging + (wrapMode == TEXT_WRAP_NONE)) // No wrap mode + { + Vector2 mousePosition = GetMousePosition(); + + if (editMode) + { + state = STATE_PRESSED; + + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + + // If text does not fit in the textbox and current cursor position is out of bounds, + // we add an index offset to text for drawing only what requires depending on cursor + while (textWidth >= textBounds.width) + { + int nextCodepointSize = 0; + GetCodepointNext(text + textIndexOffset, &nextCodepointSize); + + textIndexOffset += nextCodepointSize; + + textWidth = GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex); + } + + int codepoint = GetCharPressed(); // Get Unicode codepoint + if (multiline && IsKeyPressed(KEY_ENTER)) codepoint = (int)'\n'; + + // Encode codepoint as UTF-8 + int codepointSize = 0; + const char *charEncoded = CodepointToUTF8(codepoint, &codepointSize); + + // Add codepoint to text, at current cursor position + // NOTE: Make sure we do not overflow buffer size + if (((multiline && (codepoint == (int)'\n')) || (codepoint >= 32)) && ((textLength + codepointSize) < textSize)) + { + // Move forward data from cursor position + for (int i = (textLength + codepointSize); i > textBoxCursorIndex; i--) text[i] = text[i - codepointSize]; + + // Add new codepoint in current cursor position + for (int i = 0; i < codepointSize; i++) text[textBoxCursorIndex + i] = charEncoded[i]; + + textBoxCursorIndex += codepointSize; + textLength += codepointSize; + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + + // Move cursor to start + if ((textLength > 0) && IsKeyPressed(KEY_HOME)) textBoxCursorIndex = 0; + + // Move cursor to end + if ((textLength > textBoxCursorIndex) && IsKeyPressed(KEY_END)) textBoxCursorIndex = textLength; + + // Delete codepoint from text, after current cursor position + if ((textLength > textBoxCursorIndex) && (IsKeyPressed(KEY_DELETE) || (IsKeyDown(KEY_DELETE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_DELETE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + // Move backward text from cursor position + for (int i = textBoxCursorIndex; i < textLength; i++) text[i] = text[i + nextCodepointSize]; + + textLength -= codepointSize; + if (textBoxCursorIndex > textLength) textBoxCursorIndex = textLength; + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + } + + // Delete related codepoints from text, before current cursor position + if ((textLength > 0) && IsKeyPressed(KEY_BACKSPACE) && (IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL))) + { + int i = textBoxCursorIndex - 1; + int accCodepointSize = 0; + + // Move cursor to the end of word if on space already + while ((i > 0) && isspace(text[i])) + { + int prevCodepointSize = 0; + GetCodepointPrevious(text + i, &prevCodepointSize); + i -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Move cursor to the start of the word + while ((i > 0) && !isspace(text[i])) + { + int prevCodepointSize = 0; + GetCodepointPrevious(text + i, &prevCodepointSize); + i -= prevCodepointSize; + accCodepointSize += prevCodepointSize; + } + + // Move forward text from cursor position + for (int j = (textBoxCursorIndex - accCodepointSize); j < textLength; j++) text[j] = text[j + accCodepointSize]; + + // Prevent cursor index from decrementing past 0 + if (textBoxCursorIndex > 0) + { + textBoxCursorIndex -= accCodepointSize; + textLength -= accCodepointSize; + } + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + else if ((textLength > 0) && (IsKeyPressed(KEY_BACKSPACE) || (IsKeyDown(KEY_BACKSPACE) && (autoCursorCooldownCounter >= RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN)))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_BACKSPACE) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int prevCodepointSize = 0; + + // Prevent cursor index from decrementing past 0 + if (textBoxCursorIndex > 0) + { + GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + // Move backward text from cursor position + for (int i = (textBoxCursorIndex - prevCodepointSize); i < textLength; i++) text[i] = text[i + prevCodepointSize]; + + textBoxCursorIndex -= codepointSize; + textLength -= codepointSize; + } + + // Make sure text last character is EOL + text[textLength] = '\0'; + } + } + + // Move cursor position with keys + if (IsKeyPressed(KEY_LEFT) || (IsKeyDown(KEY_LEFT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_LEFT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int prevCodepointSize = 0; + if (textBoxCursorIndex > 0) GetCodepointPrevious(text + textBoxCursorIndex, &prevCodepointSize); + + if (textBoxCursorIndex >= prevCodepointSize) textBoxCursorIndex -= prevCodepointSize; + } + } + else if (IsKeyPressed(KEY_RIGHT) || (IsKeyDown(KEY_RIGHT) && (autoCursorCooldownCounter > RAYGUI_TEXTBOX_AUTO_CURSOR_COOLDOWN))) + { + autoCursorDelayCounter++; + + if (IsKeyPressed(KEY_RIGHT) || (autoCursorDelayCounter%RAYGUI_TEXTBOX_AUTO_CURSOR_DELAY) == 0) // Delay every movement some frames + { + int nextCodepointSize = 0; + GetCodepointNext(text + textBoxCursorIndex, &nextCodepointSize); + + if ((textBoxCursorIndex + nextCodepointSize) <= textLength) textBoxCursorIndex += nextCodepointSize; + } + } + + // Move cursor position with mouse + if (CheckCollisionPointRec(mousePosition, textBounds)) // Mouse hover text + { + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/(float)guiFont.baseSize; + int codepointIndex = 0; + float glyphWidth = 0.0f; + float widthToMouseX = 0; + int mouseCursorIndex = 0; + + for (int i = textIndexOffset; i < textLength; i++) + { + codepoint = GetCodepointNext(&text[i], &codepointSize); + codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + if (mousePosition.x <= (textBounds.x + (widthToMouseX + glyphWidth/2))) + { + mouseCursor.x = textBounds.x + widthToMouseX; + mouseCursorIndex = i; + break; + } + + widthToMouseX += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + + // Check if mouse cursor is at the last position + int textEndWidth = GetTextWidth(text + textIndexOffset); + if (GetMousePosition().x >= (textBounds.x + textEndWidth - glyphWidth/2)) + { + mouseCursor.x = textBounds.x + textEndWidth; + mouseCursorIndex = textLength; + } + + // Place cursor at required index on mouse click + if ((mouseCursor.x >= 0) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + cursor.x = mouseCursor.x; + textBoxCursorIndex = mouseCursorIndex; + } + } + else mouseCursor.x = -1; + + // Recalculate cursor position.y depending on textBoxCursorIndex + cursor.x = bounds.x + GuiGetStyle(TEXTBOX, TEXT_PADDING) + GetTextWidth(text + textIndexOffset) - GetTextWidth(text + textBoxCursorIndex) + GuiGetStyle(DEFAULT, TEXT_SPACING); + //if (multiline) cursor.y = GetTextLines() + + // Finish text editing on ENTER or mouse click outside bounds + if ((!multiline && IsKeyPressed(KEY_ENTER)) || + (!CheckCollisionPointRec(mousePosition, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + textBoxCursorIndex = 0; // GLOBAL: Reset the shared cursor index + result = 1; + } + } + else + { + if (CheckCollisionPointRec(mousePosition, bounds)) + { + state = STATE_FOCUSED; + + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + textBoxCursorIndex = textLength; // GLOBAL: Place cursor index to the end of current text + result = 1; + } + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_PRESSED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_PRESSED))); + } + else if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), GetColor(GuiGetStyle(TEXTBOX, BASE_COLOR_DISABLED))); + } + else GuiDrawRectangle(bounds, GuiGetStyle(TEXTBOX, BORDER_WIDTH), GetColor(GuiGetStyle(TEXTBOX, BORDER + (state*3))), BLANK); + + // Draw text considering index offset if required + // NOTE: Text index offset depends on cursor position + GuiDrawText(text + textIndexOffset, textBounds, GuiGetStyle(TEXTBOX, TEXT_ALIGNMENT), GetColor(GuiGetStyle(TEXTBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode && !GuiGetStyle(TEXTBOX, TEXT_READONLY)) + { + //if (autoCursorMode || ((blinkCursorFrameCounter/40)%2 == 0)) + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + + // Draw mouse position cursor (if required) + if (mouseCursor.x >= 0) GuiDrawRectangle(mouseCursor, 0, BLANK, GetColor(GuiGetStyle(TEXTBOX, BORDER_COLOR_PRESSED))); + } + else if (state == STATE_FOCUSED) GuiTooltip(bounds); + //-------------------------------------------------------------------- + + return result; // Mouse button pressed: result = 1 +} + +/* +// Text Box control with multiple lines and word-wrap +// NOTE: This text-box is readonly, no editing supported by default +bool GuiTextBoxMulti(Rectangle bounds, char *text, int textSize, bool editMode) +{ + bool pressed = false; + + GuiSetStyle(TEXTBOX, TEXT_READONLY, 1); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_WORD); // WARNING: If wrap mode enabled, text editing is not supported + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_TOP); + + // TODO: Implement methods to calculate cursor position properly + pressed = GuiTextBox(bounds, text, textSize, editMode); + + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); + GuiSetStyle(DEFAULT, TEXT_WRAP_MODE, TEXT_WRAP_NONE); + GuiSetStyle(TEXTBOX, TEXT_READONLY, 0); + + return pressed; +} +*/ + +// Spinner control, returns selected value +int GuiSpinner(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + int result = 1; + GuiState state = guiState; + + int tempValue = *value; + + Rectangle spinner = { bounds.x + GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING), bounds.y, + bounds.width - 2*(GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH) + GuiGetStyle(SPINNER, SPIN_BUTTON_SPACING)), bounds.height }; + Rectangle leftButtonBound = { (float)bounds.x, (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + Rectangle rightButtonBound = { (float)bounds.x + bounds.width - GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.y, (float)GuiGetStyle(SPINNER, SPIN_BUTTON_WIDTH), (float)bounds.height }; + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SPINNER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SPINNER, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check spinner state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + +#if defined(RAYGUI_NO_ICONS) + if (GuiButton(leftButtonBound, "<")) tempValue--; + if (GuiButton(rightButtonBound, ">")) tempValue++; +#else + if (GuiButton(leftButtonBound, GuiIconText(ICON_ARROW_LEFT_FILL, NULL))) tempValue--; + if (GuiButton(rightButtonBound, GuiIconText(ICON_ARROW_RIGHT_FILL, NULL))) tempValue++; +#endif + + if (!editMode) + { + if (tempValue < minValue) tempValue = minValue; + if (tempValue > maxValue) tempValue = maxValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + result = GuiValueBox(spinner, NULL, &tempValue, minValue, maxValue, editMode); + + // Draw value selector custom buttons + // NOTE: BORDER_WIDTH and TEXT_ALIGNMENT forced values + int tempBorderWidth = GuiGetStyle(BUTTON, BORDER_WIDTH); + int tempTextAlign = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, BORDER_WIDTH, GuiGetStyle(SPINNER, BORDER_WIDTH)); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, tempTextAlign); + GuiSetStyle(BUTTON, BORDER_WIDTH, tempBorderWidth); + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(SPINNER, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + *value = tempValue; + return result; +} + +// Value Box control, updates input text with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBox(Rectangle bounds, const char *text, int *value, int minValue, int maxValue, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + sprintf(textValue, "%i", *value); + + Rectangle textBounds = { 0 }; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if ((key >= 48) && (key <= 57)) + { + textValue[keyCount] = (char)key; + keyCount++; + valueHasChanged = true; + } + } + } + + // Delete text + if (keyCount > 0) + { + if (IsKeyPressed(KEY_BACKSPACE)) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToInteger(textValue); + + // NOTE: We are not clamp values until user input finishes + //if (*value > maxValue) *value = maxValue; + //else if (*value < minValue) *value = minValue; + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + result = 1; + } + } + else + { + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = { bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH) }; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Floating point Value Box control, updates input val_str with numbers +// NOTE: Requires static variables: frameCounter +int GuiValueBoxFloat(Rectangle bounds, const char *text, char *textValue, float *value, bool editMode) +{ + #if !defined(RAYGUI_VALUEBOX_MAX_CHARS) + #define RAYGUI_VALUEBOX_MAX_CHARS 32 + #endif + + int result = 0; + GuiState state = guiState; + + //char textValue[RAYGUI_VALUEBOX_MAX_CHARS + 1] = "\0"; + //sprintf(textValue, "%2.2f", *value); + + Rectangle textBounds = {0}; + if (text != NULL) + { + textBounds.width = (float)GetTextWidth(text) + 2; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(VALUEBOX, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + if (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_LEFT) textBounds.x = bounds.x - textBounds.width - GuiGetStyle(VALUEBOX, TEXT_PADDING); + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + bool valueHasChanged = false; + + if (editMode) + { + state = STATE_PRESSED; + + int keyCount = (int)strlen(textValue); + + // Only allow keys in range [48..57] + if (keyCount < RAYGUI_VALUEBOX_MAX_CHARS) + { + if (GetTextWidth(textValue) < bounds.width) + { + int key = GetCharPressed(); + if (((key >= 48) && (key <= 57)) || + (key == '.') || + ((keyCount == 0) && (key == '+')) || // NOTE: Sign can only be in first position + ((keyCount == 0) && (key == '-'))) + { + textValue[keyCount] = (char)key; + keyCount++; + + valueHasChanged = true; + } + } + } + + // Pressed backspace + if (IsKeyPressed(KEY_BACKSPACE)) + { + if (keyCount > 0) + { + keyCount--; + textValue[keyCount] = '\0'; + valueHasChanged = true; + } + } + + if (valueHasChanged) *value = TextToFloat(textValue); + + if ((IsKeyPressed(KEY_ENTER) || IsKeyPressed(KEY_KP_ENTER)) || (!CheckCollisionPointRec(mousePoint, bounds) && IsMouseButtonPressed(MOUSE_LEFT_BUTTON))) result = 1; + } + else + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) result = 1; + } + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + Color baseColor = BLANK; + if (state == STATE_PRESSED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_PRESSED)); + else if (state == STATE_DISABLED) baseColor = GetColor(GuiGetStyle(VALUEBOX, BASE_COLOR_DISABLED)); + + GuiDrawRectangle(bounds, GuiGetStyle(VALUEBOX, BORDER_WIDTH), GetColor(GuiGetStyle(VALUEBOX, BORDER + (state*3))), baseColor); + GuiDrawText(textValue, GetTextBounds(VALUEBOX, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(VALUEBOX, TEXT + (state*3)))); + + // Draw cursor + if (editMode) + { + // NOTE: ValueBox internal text is always centered + Rectangle cursor = {bounds.x + GetTextWidth(textValue)/2 + bounds.width/2 + 1, + bounds.y + 2*GuiGetStyle(VALUEBOX, BORDER_WIDTH), 4, + bounds.height - 4*GuiGetStyle(VALUEBOX, BORDER_WIDTH)}; + GuiDrawRectangle(cursor, 0, BLANK, GetColor(GuiGetStyle(VALUEBOX, BORDER_COLOR_PRESSED))); + } + + // Draw text label if provided + GuiDrawText(text, textBounds, + (GuiGetStyle(VALUEBOX, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT)? TEXT_ALIGN_LEFT : TEXT_ALIGN_RIGHT, + GetColor(GuiGetStyle(LABEL, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Slider control with pro parameters +// NOTE: Other GuiSlider*() controls use this one +int GuiSliderPro(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue, int sliderWidth) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + float oldValue = *value; + + Rectangle slider = { bounds.x, bounds.y + GuiGetStyle(SLIDER, BORDER_WIDTH) + GuiGetStyle(SLIDER, SLIDER_PADDING), + 0, bounds.height - 2*GuiGetStyle(SLIDER, BORDER_WIDTH) - 2*GuiGetStyle(SLIDER, SLIDER_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + if (!CheckCollisionPointRec(mousePoint, slider)) + { + // Get equivalent value and slider position from mousePosition.x + *value = (maxValue - minValue)*((mousePoint.x - bounds.x - sliderWidth/2)/(bounds.width-sliderWidth)) + minValue; + } + } + else state = STATE_FOCUSED; + } + + if (*value > maxValue) *value = maxValue; + else if (*value < minValue) *value = minValue; + } + + // Control value change check + if (oldValue == *value) result = 0; + else result = 1; + + // Slider bar limits check + float sliderValue = (((*value - minValue)/(maxValue - minValue))*(bounds.width - sliderWidth - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))); + if (sliderWidth > 0) // Slider + { + slider.x += sliderValue; + slider.width = (float)sliderWidth; + if (slider.x <= (bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH))) slider.x = bounds.x + GuiGetStyle(SLIDER, BORDER_WIDTH); + else if ((slider.x + slider.width) >= (bounds.x + bounds.width)) slider.x = bounds.x + bounds.width - slider.width - GuiGetStyle(SLIDER, BORDER_WIDTH); + } + else if (sliderWidth == 0) // SliderBar + { + slider.x += GuiGetStyle(SLIDER, BORDER_WIDTH); + slider.width = sliderValue; + if (slider.width > bounds.width) slider.width = bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SLIDER, BORDER_WIDTH), GetColor(GuiGetStyle(SLIDER, BORDER + (state*3))), GetColor(GuiGetStyle(SLIDER, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + + // Draw slider internal bar (depends on state) + if (state == STATE_NORMAL) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BASE_COLOR_PRESSED))); + else if (state == STATE_FOCUSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_FOCUSED))); + else if (state == STATE_PRESSED) GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, TEXT_COLOR_PRESSED))); + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(SLIDER, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(SLIDER, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Slider control extended, returns selected value and has text +int GuiSlider(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, GuiGetStyle(SLIDER, SLIDER_WIDTH)); +} + +// Slider Bar control extended, returns selected value +int GuiSliderBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + return GuiSliderPro(bounds, textLeft, textRight, value, minValue, maxValue, 0); +} + +// Progress Bar control extended, shows current progress value +int GuiProgressBar(Rectangle bounds, const char *textLeft, const char *textRight, float *value, float minValue, float maxValue) +{ + int result = 0; + GuiState state = guiState; + + float temp = (maxValue - minValue)/2.0f; + if (value == NULL) value = &temp; + + // Progress bar + Rectangle progress = { bounds.x + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), + bounds.y + GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) + GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING), 0, + bounds.height - 2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) - 2*GuiGetStyle(PROGRESSBAR, PROGRESS_PADDING) }; + + // Update control + //-------------------------------------------------------------------- + if (*value > maxValue) *value = maxValue; + + // WARNING: Working with floats could lead to rounding issues + if ((state != STATE_DISABLED)) progress.width = (float)(*value/(maxValue - minValue))*bounds.width - ((*value >= maxValue)? (float)(2*GuiGetStyle(PROGRESSBAR, BORDER_WIDTH)) : 0.0f); + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) + { + GuiDrawRectangle(bounds, GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(PROGRESSBAR, BORDER + (state*3))), BLANK); + } + else + { + if (*value > minValue) + { + // Draw progress bar with colored border, more visual + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y + bounds.height - 1, (int)progress.width + (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + } + else GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + + if (*value >= maxValue) GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + progress.width + 1, bounds.y, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_FOCUSED))); + else + { + // Draw borders not yet reached by value + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + (int)progress.width + 1, bounds.y + bounds.height - 1, bounds.width - (int)progress.width - 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH) }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ bounds.x + bounds.width - 1, bounds.y + 1, (float)GuiGetStyle(PROGRESSBAR, BORDER_WIDTH), bounds.height - 2 }, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BORDER_COLOR_NORMAL))); + } + + // Draw slider internal progress bar (depends on state) + GuiDrawRectangle(progress, 0, BLANK, GetColor(GuiGetStyle(PROGRESSBAR, BASE_COLOR_PRESSED))); + } + + // Draw left/right text if provided + if (textLeft != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textLeft); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x - textBounds.width - GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textLeft, textBounds, TEXT_ALIGN_RIGHT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + } + + if (textRight != NULL) + { + Rectangle textBounds = { 0 }; + textBounds.width = (float)GetTextWidth(textRight); + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + textBounds.x = bounds.x + bounds.width + GuiGetStyle(PROGRESSBAR, TEXT_PADDING); + textBounds.y = bounds.y + bounds.height/2 - GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + + GuiDrawText(textRight, textBounds, TEXT_ALIGN_LEFT, GetColor(GuiGetStyle(PROGRESSBAR, TEXT + (state*3)))); + } + //-------------------------------------------------------------------- + + return result; +} + +// Status Bar control +int GuiStatusBar(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(STATUSBAR, BORDER_WIDTH), GetColor(GuiGetStyle(STATUSBAR, BORDER + (state*3))), GetColor(GuiGetStyle(STATUSBAR, BASE + (state*3)))); + GuiDrawText(text, GetTextBounds(STATUSBAR, bounds), GuiGetStyle(STATUSBAR, TEXT_ALIGNMENT), GetColor(GuiGetStyle(STATUSBAR, TEXT + (state*3)))); + //-------------------------------------------------------------------- + + return result; +} + +// Dummy rectangle control, intended for placeholding +int GuiDummyRec(Rectangle bounds, const char *text) +{ + int result = 0; + GuiState state = guiState; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check button state + if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) state = STATE_PRESSED; + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, 0, BLANK, GetColor(GuiGetStyle(DEFAULT, (state != STATE_DISABLED)? BASE_COLOR_NORMAL : BASE_COLOR_DISABLED))); + GuiDrawText(text, GetTextBounds(DEFAULT, bounds), TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(BUTTON, (state != STATE_DISABLED)? TEXT_COLOR_NORMAL : TEXT_COLOR_DISABLED))); + //------------------------------------------------------------------ + + return result; +} + +// List View control +int GuiListView(Rectangle bounds, const char *text, int *scrollIndex, int *active) +{ + int result = 0; + int itemCount = 0; + const char **items = NULL; + + if (text != NULL) items = GuiTextSplit(text, ';', &itemCount, NULL); + + result = GuiListViewEx(bounds, items, itemCount, scrollIndex, active, NULL); + + return result; +} + +// List View control with extended parameters +int GuiListViewEx(Rectangle bounds, const char **text, int count, int *scrollIndex, int *active, int *focus) +{ + int result = 0; + GuiState state = guiState; + + int itemFocused = (focus == NULL)? -1 : *focus; + int itemSelected = (active == NULL)? -1 : *active; + + // Check if we need a scroll bar + bool useScrollBar = false; + if ((GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING))*count > bounds.height) useScrollBar = true; + + // Define base item rectangle [0] + Rectangle itemBounds = { 0 }; + itemBounds.x = bounds.x + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING); + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.width = bounds.width - 2*GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) - GuiGetStyle(DEFAULT, BORDER_WIDTH); + itemBounds.height = (float)GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT); + if (useScrollBar) itemBounds.width -= GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH); + + // Get items on the list + int visibleItems = (int)bounds.height/(GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + if (visibleItems > count) visibleItems = count; + + int startIndex = (scrollIndex == NULL)? 0 : *scrollIndex; + if ((startIndex < 0) || (startIndex > (count - visibleItems))) startIndex = 0; + int endIndex = startIndex + visibleItems; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + Vector2 mousePoint = GetMousePosition(); + + // Check mouse inside list view + if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Check focused and selected item + for (int i = 0; i < visibleItems; i++) + { + if (CheckCollisionPointRec(mousePoint, itemBounds)) + { + itemFocused = startIndex + i; + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + if (itemSelected == (startIndex + i)) itemSelected = -1; + else itemSelected = startIndex + i; + } + break; + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + int wheelMove = (int)GetMouseWheelMove(); + startIndex -= wheelMove; + + if (startIndex < 0) startIndex = 0; + else if (startIndex > (count - visibleItems)) startIndex = count - visibleItems; + + endIndex = startIndex + visibleItems; + if (endIndex > count) endIndex = count; + } + } + else itemFocused = -1; + + // Reset item rectangle y to [0] + itemBounds.y = bounds.y + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING) + GuiGetStyle(DEFAULT, BORDER_WIDTH); + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BACKGROUND_COLOR))); // Draw background + + // Draw visible items + for (int i = 0; ((i < visibleItems) && (text != NULL)); i++) + { + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, LIST_ITEMS_BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_NORMAL)), BLANK); + + if (state == STATE_DISABLED) + { + if ((startIndex + i) == itemSelected) GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_DISABLED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_DISABLED))); + + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_DISABLED))); + } + else + { + if (((startIndex + i) == itemSelected) && (active != NULL)) + { + // Draw item selected + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_PRESSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_PRESSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_PRESSED))); + } + else if (((startIndex + i) == itemFocused)) // && (focus != NULL)) // NOTE: We want items focused, despite not returned! + { + // Draw item focused + GuiDrawRectangle(itemBounds, GuiGetStyle(LISTVIEW, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER_COLOR_FOCUSED)), GetColor(GuiGetStyle(LISTVIEW, BASE_COLOR_FOCUSED))); + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_FOCUSED))); + } + else + { + // Draw item normal + GuiDrawText(text[startIndex + i], GetTextBounds(DEFAULT, itemBounds), GuiGetStyle(LISTVIEW, TEXT_ALIGNMENT), GetColor(GuiGetStyle(LISTVIEW, TEXT_COLOR_NORMAL))); + } + } + + // Update item rectangle y position for next item + itemBounds.y += (GuiGetStyle(LISTVIEW, LIST_ITEMS_HEIGHT) + GuiGetStyle(LISTVIEW, LIST_ITEMS_SPACING)); + } + + if (useScrollBar) + { + Rectangle scrollBarBounds = { + bounds.x + bounds.width - GuiGetStyle(LISTVIEW, BORDER_WIDTH) - GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.y + GuiGetStyle(LISTVIEW, BORDER_WIDTH), (float)GuiGetStyle(LISTVIEW, SCROLLBAR_WIDTH), + bounds.height - 2*GuiGetStyle(DEFAULT, BORDER_WIDTH) + }; + + // Calculate percentage of visible items and apply same percentage to scrollbar + float percentVisible = (float)(endIndex - startIndex)/count; + float sliderSize = bounds.height*percentVisible; + + int prevSliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); // Save default slider size + int prevScrollSpeed = GuiGetStyle(SCROLLBAR, SCROLL_SPEED); // Save default scroll speed + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, (int)sliderSize); // Change slider size + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, count - visibleItems); // Change scroll speed + + startIndex = GuiScrollBar(scrollBarBounds, startIndex, 0, count - visibleItems); + + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, prevScrollSpeed); // Reset scroll speed to default + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, prevSliderSize); // Reset slider size to default + } + //-------------------------------------------------------------------- + + if (active != NULL) *active = itemSelected; + if (focus != NULL) *focus = itemFocused; + if (scrollIndex != NULL) *scrollIndex = startIndex; + + return result; +} + +// Color Panel control - Color (RGBA) variant. +int GuiColorPanel(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Vector3 vcolor = { (float)color->r/255.0f, (float)color->g/255.0f, (float)color->b/255.0f }; + Vector3 hsv = ConvertRGBtoHSV(vcolor); + Vector3 prevHsv = hsv; // workaround to see if GuiColorPanelHSV modifies the hsv. + + GuiColorPanelHSV(bounds, text, &hsv); + + // Check if the hsv was changed, only then change the color. + // This is required, because the Color->HSV->Color conversion has precision errors. + // Thus the assignment from HSV to Color should only be made, if the HSV has a new user-entered value. + // Otherwise GuiColorPanel would often modify it's color without user input. + // TODO: GuiColorPanelHSV could return 1 if the slider was dragged, to simplify this check. + if (hsv.x != prevHsv.x || hsv.y != prevHsv.y || hsv.z != prevHsv.z) + { + Vector3 rgb = ConvertHSVtoRGB(hsv); + + // NOTE: Vector3ToColor() only available on raylib 1.8.1 + *color = RAYGUI_CLITERAL(Color){ (unsigned char)(255.0f*rgb.x), + (unsigned char)(255.0f*rgb.y), + (unsigned char)(255.0f*rgb.z), + color->a }; + } + return result; +} + +// Color Bar Alpha control +// NOTE: Returns alpha value normalized [0..1] +int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +{ + #if !defined(RAYGUI_COLORBARALPHA_CHECKED_SIZE) + #define RAYGUI_COLORBARALPHA_CHECKED_SIZE 10 + #endif + + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x + (*alpha)*bounds.width - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.y - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT), (float)bounds.height + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *alpha = (mousePoint.x - bounds.x)/bounds.width; + if (*alpha <= 0.0f) *alpha = 0.0f; + if (*alpha >= 1.0f) *alpha = 1.0f; + //selector.x = bounds.x + (int)(((alpha - 0)/(100 - 0))*(bounds.width - 2*GuiGetStyle(SLIDER, BORDER_WIDTH))) - selector.width/2; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + + // Draw alpha bar: checked background + if (state != STATE_DISABLED) + { + int checksX = (int)bounds.width/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + int checksY = (int)bounds.height/RAYGUI_COLORBARALPHA_CHECKED_SIZE; + + for (int x = 0; x < checksX; x++) + { + for (int y = 0; y < checksY; y++) + { + Rectangle check = { bounds.x + x*RAYGUI_COLORBARALPHA_CHECKED_SIZE, bounds.y + y*RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE, RAYGUI_COLORBARALPHA_CHECKED_SIZE }; + GuiDrawRectangle(check, 0, BLANK, ((x + y)%2)? Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.4f) : Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.4f)); + } + } + + DrawRectangleGradientEx(bounds, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, RAYGUI_CLITERAL(Color){ 255, 255, 255, 0 }, Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientEx(bounds, Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw alpha bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Bar Hue control +// Returns hue value normalized [0..1] +// NOTE: Other similar bars (for reference): +// Color GuiColorBarSat() [WHITE->color] +// Color GuiColorBarValue() [BLACK->color], HSV/HSL +// float GuiColorBarLuminance() [BLACK->WHITE] +int GuiColorBarHue(Rectangle bounds, const char *text, float *hue) +{ + int result = 0; + GuiState state = guiState; + Rectangle selector = { (float)bounds.x - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW), (float)bounds.y + (*hue)/360.0f*bounds.height - GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT)/2, (float)bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW)*2, (float)GuiGetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT) }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds) || CheckCollisionPointRec(mousePoint, selector)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + *hue = (mousePoint.y - bounds.y)*360/bounds.height; + if (*hue <= 0.0f) *hue = 0.0f; + if (*hue >= 359.0f) *hue = 359.0f; + + } + else state = STATE_FOCUSED; + + /*if (IsKeyDown(KEY_UP)) + { + hue -= 2.0f; + if (hue <= 0.0f) hue = 0.0f; + } + else if (IsKeyDown(KEY_DOWN)) + { + hue += 2.0f; + if (hue >= 360.0f) hue = 360.0f; + }*/ + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + // Draw hue bar:color bars + // TODO: Use directly DrawRectangleGradientEx(bounds, color1, color2, color2, color1); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + bounds.height/6), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 2*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 0, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 3*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 255, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 4*(bounds.height/6)), (int)bounds.width, (int)ceilf(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 0, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha)); + DrawRectangleGradientV((int)bounds.x, (int)(bounds.y + 5*(bounds.height/6)), (int)bounds.width, (int)(bounds.height/6), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 255, 255 }, guiAlpha), Fade(RAYGUI_CLITERAL(Color){ 255, 0, 0, 255 }, guiAlpha)); + } + else DrawRectangleGradientV((int)bounds.x, (int)bounds.y, (int)bounds.width, (int)bounds.height, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), guiAlpha)); + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + + // Draw hue bar: selector + GuiDrawRectangle(selector, 0, BLANK, GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3))); + //-------------------------------------------------------------------- + + return result; +} + +// Color Picker control +// NOTE: It's divided in multiple controls: +// Color GuiColorPanel(Rectangle bounds, Color color) +// float GuiColorBarAlpha(Rectangle bounds, float alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanel() size +// NOTE: this picker converts RGB to HSV, which can cause the Hue control to jump. If you have this problem, consider using the HSV variant instead +int GuiColorPicker(Rectangle bounds, const char *text, Color *color) +{ + int result = 0; + + Color temp = { 200, 0, 0, 255 }; + if (color == NULL) color = &temp; + + GuiColorPanel(bounds, NULL, color); + + Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + //Rectangle boundsAlpha = { bounds.x, bounds.y + bounds.height + GuiGetStyle(COLORPICKER, BARS_PADDING), bounds.width, GuiGetStyle(COLORPICKER, BARS_THICK) }; + + // NOTE: this conversion can cause low hue-resolution, if the r, g and b value are very similar, which causes the hue bar to shift around when only the GuiColorPanel is used. + Vector3 hsv = ConvertRGBtoHSV(RAYGUI_CLITERAL(Vector3){ (*color).r/255.0f, (*color).g/255.0f, (*color).b/255.0f }); + + GuiColorBarHue(boundsHue, NULL, &hsv.x); + + //color.a = (unsigned char)(GuiColorBarAlpha(boundsAlpha, (float)color.a/255.0f)*255.0f); + Vector3 rgb = ConvertHSVtoRGB(hsv); + + *color = RAYGUI_CLITERAL(Color){ (unsigned char)roundf(rgb.x*255.0f), (unsigned char)roundf(rgb.y*255.0f), (unsigned char)roundf(rgb.z*255.0f), (*color).a }; + + return result; +} + +// Color Picker control that avoids conversion to RGB and back to HSV on each call, thus avoiding jittering. +// The user can call ConvertHSVtoRGB() to convert *colorHsv value to RGB. +// NOTE: It's divided in multiple controls: +// int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +// int GuiColorBarAlpha(Rectangle bounds, const char *text, float *alpha) +// float GuiColorBarHue(Rectangle bounds, float value) +// NOTE: bounds define GuiColorPanelHSV() size +int GuiColorPickerHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + + Vector3 tempHsv = { 0 }; + + if (colorHsv == NULL) + { + const Vector3 tempColor = { 200.0f/255.0f, 0.0f, 0.0f }; + tempHsv = ConvertRGBtoHSV(tempColor); + colorHsv = &tempHsv; + } + + GuiColorPanelHSV(bounds, NULL, colorHsv); + + const Rectangle boundsHue = { (float)bounds.x + bounds.width + GuiGetStyle(COLORPICKER, HUEBAR_PADDING), (float)bounds.y, (float)GuiGetStyle(COLORPICKER, HUEBAR_WIDTH), (float)bounds.height }; + + GuiColorBarHue(boundsHue, NULL, &colorHsv->x); + + return result; +} + +// Color Panel control - HSV variant +int GuiColorPanelHSV(Rectangle bounds, const char *text, Vector3 *colorHsv) +{ + int result = 0; + GuiState state = guiState; + Vector2 pickerSelector = { 0 }; + + const Color colWhite = { 255, 255, 255, 255 }; + const Color colBlack = { 0, 0, 0, 255 }; + + pickerSelector.x = bounds.x + (float)colorHsv->y*bounds.width; // HSV: Saturation + pickerSelector.y = bounds.y + (1.0f - (float)colorHsv->z)*bounds.height; // HSV: Value + + Vector3 maxHue = { colorHsv->x, 1.0f, 1.0f }; + Vector3 rgbHue = ConvertHSVtoRGB(maxHue); + Color maxHueCol = { (unsigned char)(255.0f*rgbHue.x), + (unsigned char)(255.0f*rgbHue.y), + (unsigned char)(255.0f*rgbHue.z), 255 }; + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + pickerSelector = mousePoint; + + if (pickerSelector.x < bounds.x) pickerSelector.x = bounds.x; + if (pickerSelector.x > bounds.x + bounds.width) pickerSelector.x = bounds.x + bounds.width; + if (pickerSelector.y < bounds.y) pickerSelector.y = bounds.y; + if (pickerSelector.y > bounds.y + bounds.height) pickerSelector.y = bounds.y + bounds.height; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON)) + { + state = STATE_PRESSED; + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; + pickerSelector = mousePoint; + + // Calculate color from picker + Vector2 colorPick = { pickerSelector.x - bounds.x, pickerSelector.y - bounds.y }; + + colorPick.x /= (float)bounds.width; // Get normalized value on x + colorPick.y /= (float)bounds.height; // Get normalized value on y + + colorHsv->y = colorPick.x; + colorHsv->z = 1.0f - colorPick.y; + } + else state = STATE_FOCUSED; + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state != STATE_DISABLED) + { + DrawRectangleGradientEx(bounds, Fade(colWhite, guiAlpha), Fade(colWhite, guiAlpha), Fade(maxHueCol, guiAlpha), Fade(maxHueCol, guiAlpha)); + DrawRectangleGradientEx(bounds, Fade(colBlack, 0), Fade(colBlack, guiAlpha), Fade(colBlack, guiAlpha), Fade(colBlack, 0)); + + // Draw color picker: selector + Rectangle selector = { pickerSelector.x - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, pickerSelector.y - GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE)/2, (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE), (float)GuiGetStyle(COLORPICKER, COLOR_SELECTOR_SIZE) }; + GuiDrawRectangle(selector, 0, BLANK, colWhite); + } + else + { + DrawRectangleGradientEx(bounds, Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BASE_COLOR_DISABLED)), 0.1f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(colBlack, 0.6f), guiAlpha), Fade(Fade(GetColor(GuiGetStyle(COLORPICKER, BORDER_COLOR_DISABLED)), 0.6f), guiAlpha)); + } + + GuiDrawRectangle(bounds, GuiGetStyle(COLORPICKER, BORDER_WIDTH), GetColor(GuiGetStyle(COLORPICKER, BORDER + state*3)), BLANK); + //-------------------------------------------------------------------- + + return result; +} + +// Message Box control +int GuiMessageBox(Rectangle bounds, const char *title, const char *message, const char *buttons) +{ + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_HEIGHT) + #define RAYGUI_MESSAGEBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_MESSAGEBOX_BUTTON_PADDING) + #define RAYGUI_MESSAGEBOX_BUTTON_PADDING 12 + #endif + + int result = -1; // Returns clicked button from buttons list, 0 refers to closed window button + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT - RAYGUI_MESSAGEBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + //int textWidth = GetTextWidth(message) + 2; + + Rectangle textBounds = { 0 }; + textBounds.x = bounds.x + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + RAYGUI_MESSAGEBOX_BUTTON_PADDING; + textBounds.width = bounds.width - RAYGUI_MESSAGEBOX_BUTTON_PADDING*2; + textBounds.height = bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - 3*RAYGUI_MESSAGEBOX_BUTTON_PADDING - RAYGUI_MESSAGEBOX_BUTTON_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + + prevTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevTextAlignment); + //-------------------------------------------------------------------- + + return result; +} + +// Text Input Box control, ask for text +int GuiTextInputBox(Rectangle bounds, const char *title, const char *message, const char *buttons, char *text, int textMaxSize, bool *secretViewActive) +{ + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT 24 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_BUTTON_PADDING) + #define RAYGUI_TEXTINPUTBOX_BUTTON_PADDING 12 + #endif + #if !defined(RAYGUI_TEXTINPUTBOX_HEIGHT) + #define RAYGUI_TEXTINPUTBOX_HEIGHT 26 + #endif + + // Used to enable text edit mode + // WARNING: No more than one GuiTextInputBox() should be open at the same time + static bool textEditMode = false; + + int result = -1; + + int buttonCount = 0; + const char **buttonsText = GuiTextSplit(buttons, ';', &buttonCount, NULL); + Rectangle buttonBounds = { 0 }; + buttonBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.y = bounds.y + bounds.height - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + buttonBounds.width = (bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*(buttonCount + 1))/buttonCount; + buttonBounds.height = RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT; + + int messageInputHeight = (int)bounds.height - RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - GuiGetStyle(STATUSBAR, BORDER_WIDTH) - RAYGUI_TEXTINPUTBOX_BUTTON_HEIGHT - 2*RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + + Rectangle textBounds = { 0 }; + if (message != NULL) + { + int textSize = GetTextWidth(message) + 2; + + textBounds.x = bounds.x + bounds.width/2 - textSize/2; + textBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT + messageInputHeight/4 - (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/2; + textBounds.width = (float)textSize; + textBounds.height = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + } + + Rectangle textBoxBounds = { 0 }; + textBoxBounds.x = bounds.x + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + textBoxBounds.y = bounds.y + RAYGUI_WINDOWBOX_STATUSBAR_HEIGHT - RAYGUI_TEXTINPUTBOX_HEIGHT/2; + if (message == NULL) textBoxBounds.y = bounds.y + 24 + RAYGUI_TEXTINPUTBOX_BUTTON_PADDING; + else textBoxBounds.y += (messageInputHeight/2 + messageInputHeight/4); + textBoxBounds.width = bounds.width - RAYGUI_TEXTINPUTBOX_BUTTON_PADDING*2; + textBoxBounds.height = RAYGUI_TEXTINPUTBOX_HEIGHT; + + // Draw control + //-------------------------------------------------------------------- + if (GuiWindowBox(bounds, title)) result = 0; + + // Draw message if available + if (message != NULL) + { + int prevTextAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(textBounds, message); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, prevTextAlignment); + } + + if (secretViewActive != NULL) + { + static char stars[] = "****************"; + if (GuiTextBox(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x, textBoxBounds.y, textBoxBounds.width - 4 - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.height }, + ((*secretViewActive == 1) || textEditMode)? text : stars, textMaxSize, textEditMode)) textEditMode = !textEditMode; + + GuiToggle(RAYGUI_CLITERAL(Rectangle){ textBoxBounds.x + textBoxBounds.width - RAYGUI_TEXTINPUTBOX_HEIGHT, textBoxBounds.y, RAYGUI_TEXTINPUTBOX_HEIGHT, RAYGUI_TEXTINPUTBOX_HEIGHT }, (*secretViewActive == 1)? "#44#" : "#45#", secretViewActive); + } + else + { + if (GuiTextBox(textBoxBounds, text, textMaxSize, textEditMode)) textEditMode = !textEditMode; + } + + int prevBtnTextAlignment = GuiGetStyle(BUTTON, TEXT_ALIGNMENT); + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + for (int i = 0; i < buttonCount; i++) + { + if (GuiButton(buttonBounds, buttonsText[i])) result = i + 1; + buttonBounds.x += (buttonBounds.width + RAYGUI_MESSAGEBOX_BUTTON_PADDING); + } + + if (result >= 0) textEditMode = false; + + GuiSetStyle(BUTTON, TEXT_ALIGNMENT, prevBtnTextAlignment); + //-------------------------------------------------------------------- + + return result; // Result is the pressed button index +} + +// Grid control +// NOTE: Returns grid mouse-hover selected cell +// About drawing lines at subpixel spacing, simple put, not easy solution: +// https://stackoverflow.com/questions/4435450/2d-opengl-drawing-lines-that-dont-exactly-fit-pixel-raster +int GuiGrid(Rectangle bounds, const char *text, float spacing, int subdivs, Vector2 *mouseCell) +{ + // Grid lines alpha amount + #if !defined(RAYGUI_GRID_ALPHA) + #define RAYGUI_GRID_ALPHA 0.15f + #endif + + int result = 0; + GuiState state = guiState; + + Vector2 mousePoint = GetMousePosition(); + Vector2 currentMouseCell = { -1, -1 }; + + float spaceWidth = spacing/(float)subdivs; + int linesV = (int)(bounds.width/spaceWidth) + 1; + int linesH = (int)(bounds.height/spaceWidth) + 1; + + int color = GuiGetStyle(DEFAULT, LINE_COLOR); + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked && !guiControlExclusiveMode) + { + if (CheckCollisionPointRec(mousePoint, bounds)) + { + // NOTE: Cell values must be the upper left of the cell the mouse is in + currentMouseCell.x = floorf((mousePoint.x - bounds.x)/spacing); + currentMouseCell.y = floorf((mousePoint.y - bounds.y)/spacing); + } + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + if (state == STATE_DISABLED) color = GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED); + + if (subdivs > 0) + { + // Draw vertical grid lines + for (int i = 0; i < linesV; i++) + { + Rectangle lineV = { bounds.x + spacing*i/subdivs, bounds.y, 1, bounds.height + 1 }; + GuiDrawRectangle(lineV, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + + // Draw horizontal grid lines + for (int i = 0; i < linesH; i++) + { + Rectangle lineH = { bounds.x, bounds.y + spacing*i/subdivs, bounds.width + 1, 1 }; + GuiDrawRectangle(lineH, 0, BLANK, ((i%subdivs) == 0)? GuiFade(GetColor(color), RAYGUI_GRID_ALPHA*4) : GuiFade(GetColor(color), RAYGUI_GRID_ALPHA)); + } + } + + if (mouseCell != NULL) *mouseCell = currentMouseCell; + return result; +} + +//---------------------------------------------------------------------------------- +// Tooltip management functions +// NOTE: Tooltips requires some global variables: tooltipPtr +//---------------------------------------------------------------------------------- +// Enable gui tooltips (global state) +void GuiEnableTooltip(void) { guiTooltip = true; } + +// Disable gui tooltips (global state) +void GuiDisableTooltip(void) { guiTooltip = false; } + +// Set tooltip string +void GuiSetTooltip(const char *tooltip) { guiTooltipPtr = tooltip; } + +//---------------------------------------------------------------------------------- +// Styles loading functions +//---------------------------------------------------------------------------------- + +// Load raygui style file (.rgs) +// NOTE: By default a binary file is expected, that file could contain a custom font, +// in that case, custom font image atlas is GRAY+ALPHA and pixel data can be compressed (DEFLATE) +void GuiLoadStyle(const char *fileName) +{ + #define MAX_LINE_BUFFER_SIZE 256 + + bool tryBinary = false; + if (!guiStyleLoaded) GuiLoadStyleDefault(); + + // Try reading the files as text file first + FILE *rgsFile = fopen(fileName, "rt"); + + if (rgsFile != NULL) + { + char buffer[MAX_LINE_BUFFER_SIZE] = { 0 }; + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + + if (buffer[0] == '#') + { + int controlId = 0; + int propertyId = 0; + unsigned int propertyValue = 0; + + while (!feof(rgsFile)) + { + switch (buffer[0]) + { + case 'p': + { + // Style property: p + + sscanf(buffer, "p %d %d 0x%x", &controlId, &propertyId, &propertyValue); + GuiSetStyle(controlId, propertyId, (int)propertyValue); + + } break; + case 'f': + { + // Style font: f + + int fontSize = 0; + char charmapFileName[256] = { 0 }; + char fontFileName[256] = { 0 }; + sscanf(buffer, "f %d %s %[^\r\n]s", &fontSize, charmapFileName, fontFileName); + + Font font = { 0 }; + int *codepoints = NULL; + int codepointCount = 0; + + if (charmapFileName[0] != '0') + { + // Load text data from file + // NOTE: Expected an UTF-8 array of codepoints, no separation + char *textData = LoadFileText(TextFormat("%s/%s", GetDirectoryPath(fileName), charmapFileName)); + codepoints = LoadCodepoints(textData, &codepointCount); + UnloadFileText(textData); + } + + if (fontFileName[0] != '\0') + { + // In case a font is already loaded and it is not default internal font, unload it + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + + if (codepointCount > 0) font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, codepoints, codepointCount); + else font = LoadFontEx(TextFormat("%s/%s", GetDirectoryPath(fileName), fontFileName), fontSize, NULL, 0); // Default to 95 standard codepoints + } + + // If font texture not properly loaded, revert to default font and size/spacing + if (font.texture.id == 0) + { + font = GetFontDefault(); + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); + } + + UnloadCodepoints(codepoints); + + if ((font.texture.id > 0) && (font.glyphCount > 0)) GuiSetFont(font); + + } break; + default: break; + } + + fgets(buffer, MAX_LINE_BUFFER_SIZE, rgsFile); + } + } + else tryBinary = true; + + fclose(rgsFile); + } + + if (tryBinary) + { + rgsFile = fopen(fileName, "rb"); + + if (rgsFile != NULL) + { + fseek(rgsFile, 0, SEEK_END); + int fileDataSize = ftell(rgsFile); + fseek(rgsFile, 0, SEEK_SET); + + if (fileDataSize > 0) + { + unsigned char *fileData = (unsigned char *)RAYGUI_MALLOC(fileDataSize*sizeof(unsigned char)); + fread(fileData, sizeof(unsigned char), fileDataSize, rgsFile); + + GuiLoadStyleFromMemory(fileData, fileDataSize); + + RAYGUI_FREE(fileData); + } + + fclose(rgsFile); + } + } +} + +// Load style default over global style +void GuiLoadStyleDefault(void) +{ + // We set this variable first to avoid cyclic function calls + // when calling GuiSetStyle() and GuiGetStyle() + guiStyleLoaded = true; + + // Initialize default LIGHT style property values + // WARNING: Default value are applied to all controls on set but + // they can be overwritten later on for every custom control + GuiSetStyle(DEFAULT, BORDER_COLOR_NORMAL, 0x838383ff); + GuiSetStyle(DEFAULT, BASE_COLOR_NORMAL, 0xc9c9c9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_NORMAL, 0x686868ff); + GuiSetStyle(DEFAULT, BORDER_COLOR_FOCUSED, 0x5bb2d9ff); + GuiSetStyle(DEFAULT, BASE_COLOR_FOCUSED, 0xc9effeff); + GuiSetStyle(DEFAULT, TEXT_COLOR_FOCUSED, 0x6c9bbcff); + GuiSetStyle(DEFAULT, BORDER_COLOR_PRESSED, 0x0492c7ff); + GuiSetStyle(DEFAULT, BASE_COLOR_PRESSED, 0x97e8ffff); + GuiSetStyle(DEFAULT, TEXT_COLOR_PRESSED, 0x368bafff); + GuiSetStyle(DEFAULT, BORDER_COLOR_DISABLED, 0xb5c1c2ff); + GuiSetStyle(DEFAULT, BASE_COLOR_DISABLED, 0xe6e9e9ff); + GuiSetStyle(DEFAULT, TEXT_COLOR_DISABLED, 0xaeb7b8ff); + GuiSetStyle(DEFAULT, BORDER_WIDTH, 1); + GuiSetStyle(DEFAULT, TEXT_PADDING, 0); + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + + // Initialize default extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(DEFAULT, TEXT_SIZE, 10); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, TEXT_SPACING, 1); // DEFAULT, shared by all controls + GuiSetStyle(DEFAULT, LINE_COLOR, 0x90abb5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, BACKGROUND_COLOR, 0xf5f5f5ff); // DEFAULT specific property + GuiSetStyle(DEFAULT, TEXT_LINE_SPACING, 15); // DEFAULT, 15 pixels between lines + GuiSetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL, TEXT_ALIGN_MIDDLE); // DEFAULT, text aligned vertically to middle of text-bounds + + // Initialize control-specific property values + // NOTE: Those properties are in default list but require specific values by control type + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(BUTTON, BORDER_WIDTH, 2); + GuiSetStyle(SLIDER, TEXT_PADDING, 4); + GuiSetStyle(PROGRESSBAR, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_PADDING, 4); + GuiSetStyle(CHECKBOX, TEXT_ALIGNMENT, TEXT_ALIGN_RIGHT); + GuiSetStyle(DROPDOWNBOX, TEXT_PADDING, 0); + GuiSetStyle(DROPDOWNBOX, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiSetStyle(TEXTBOX, TEXT_PADDING, 4); + GuiSetStyle(TEXTBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(VALUEBOX, TEXT_PADDING, 0); + GuiSetStyle(VALUEBOX, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(SPINNER, TEXT_PADDING, 0); + GuiSetStyle(SPINNER, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + GuiSetStyle(STATUSBAR, TEXT_PADDING, 8); + GuiSetStyle(STATUSBAR, TEXT_ALIGNMENT, TEXT_ALIGN_LEFT); + + // Initialize extended property values + // NOTE: By default, extended property values are initialized to 0 + GuiSetStyle(TOGGLE, GROUP_PADDING, 2); + GuiSetStyle(SLIDER, SLIDER_WIDTH, 16); + GuiSetStyle(SLIDER, SLIDER_PADDING, 1); + GuiSetStyle(PROGRESSBAR, PROGRESS_PADDING, 1); + GuiSetStyle(CHECKBOX, CHECK_PADDING, 1); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_WIDTH, 32); + GuiSetStyle(COMBOBOX, COMBO_BUTTON_SPACING, 2); + GuiSetStyle(DROPDOWNBOX, ARROW_PADDING, 16); + GuiSetStyle(DROPDOWNBOX, DROPDOWN_ITEMS_SPACING, 2); + GuiSetStyle(SPINNER, SPIN_BUTTON_WIDTH, 24); + GuiSetStyle(SPINNER, SPIN_BUTTON_SPACING, 2); + GuiSetStyle(SCROLLBAR, BORDER_WIDTH, 0); + GuiSetStyle(SCROLLBAR, ARROWS_VISIBLE, 0); + GuiSetStyle(SCROLLBAR, ARROWS_SIZE, 6); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE, 16); + GuiSetStyle(SCROLLBAR, SCROLL_PADDING, 0); + GuiSetStyle(SCROLLBAR, SCROLL_SPEED, 12); + GuiSetStyle(LISTVIEW, LIST_ITEMS_HEIGHT, 28); + GuiSetStyle(LISTVIEW, LIST_ITEMS_SPACING, 2); + GuiSetStyle(LISTVIEW, SCROLLBAR_WIDTH, 12); + GuiSetStyle(LISTVIEW, SCROLLBAR_SIDE, SCROLLBAR_RIGHT_SIDE); + GuiSetStyle(COLORPICKER, COLOR_SELECTOR_SIZE, 8); + GuiSetStyle(COLORPICKER, HUEBAR_WIDTH, 16); + GuiSetStyle(COLORPICKER, HUEBAR_PADDING, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_HEIGHT, 8); + GuiSetStyle(COLORPICKER, HUEBAR_SELECTOR_OVERFLOW, 2); + + if (guiFont.texture.id != GetFontDefault().texture.id) + { + // Unload previous font texture + UnloadTexture(guiFont.texture); + RL_FREE(guiFont.recs); + RL_FREE(guiFont.glyphs); + guiFont.recs = NULL; + guiFont.glyphs = NULL; + + // Setup default raylib font + guiFont = GetFontDefault(); + + // NOTE: Default raylib font character 95 is a white square + Rectangle whiteChar = guiFont.recs[95]; + + // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding on MSAA filtering + SetShapesTexture(guiFont.texture, RAYGUI_CLITERAL(Rectangle){ whiteChar.x + 1, whiteChar.y + 1, whiteChar.width - 2, whiteChar.height - 2 }); + } +} + +// Get text with icon id prepended +// NOTE: Useful to add icons by name id (enum) instead of +// a number that can change between ricon versions +const char *GuiIconText(int iconId, const char *text) +{ +#if defined(RAYGUI_NO_ICONS) + return NULL; +#else + static char buffer[1024] = { 0 }; + static char iconBuffer[16] = { 0 }; + + if (text != NULL) + { + memset(buffer, 0, 1024); + sprintf(buffer, "#%03i#", iconId); + + for (int i = 5; i < 1024; i++) + { + buffer[i] = text[i - 5]; + if (text[i - 5] == '\0') break; + } + + return buffer; + } + else + { + sprintf(iconBuffer, "#%03i#", iconId); + + return iconBuffer; + } +#endif +} + +#if !defined(RAYGUI_NO_ICONS) +// Get full icons data pointer +unsigned int *GuiGetIcons(void) { return guiIconsPtr; } + +// Load raygui icons file (.rgi) +// NOTE: In case nameIds are required, they can be requested with loadIconsName, +// they are returned as a guiIconsName[iconCount][RAYGUI_ICON_MAX_NAME_LENGTH], +// WARNING: guiIconsName[]][] memory should be manually freed! +char **GuiLoadIcons(const char *fileName, bool loadIconsName) +{ + // Style File Structure (.rgi) + // ------------------------------------------------------ + // Offset | Size | Type | Description + // ------------------------------------------------------ + // 0 | 4 | char | Signature: "rGI " + // 4 | 2 | short | Version: 100 + // 6 | 2 | short | reserved + + // 8 | 2 | short | Num icons (N) + // 10 | 2 | short | Icons size (Options: 16, 32, 64) (S) + + // Icons name id (32 bytes per name id) + // foreach (icon) + // { + // 12+32*i | 32 | char | Icon NameId + // } + + // Icons data: One bit per pixel, stored as unsigned int array (depends on icon size) + // S*S pixels/32bit per unsigned int = K unsigned int per icon + // foreach (icon) + // { + // ... | K | unsigned int | Icon Data + // } + + FILE *rgiFile = fopen(fileName, "rb"); + + char **guiIconsName = NULL; + + if (rgiFile != NULL) + { + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + short iconCount = 0; + short iconSize = 0; + + fread(signature, 1, 4, rgiFile); + fread(&version, sizeof(short), 1, rgiFile); + fread(&reserved, sizeof(short), 1, rgiFile); + fread(&iconCount, sizeof(short), 1, rgiFile); + fread(&iconSize, sizeof(short), 1, rgiFile); + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'I') && + (signature[3] == ' ')) + { + if (loadIconsName) + { + guiIconsName = (char **)RAYGUI_MALLOC(iconCount*sizeof(char **)); + for (int i = 0; i < iconCount; i++) + { + guiIconsName[i] = (char *)RAYGUI_MALLOC(RAYGUI_ICON_MAX_NAME_LENGTH); + fread(guiIconsName[i], 1, RAYGUI_ICON_MAX_NAME_LENGTH, rgiFile); + } + } + else fseek(rgiFile, iconCount*RAYGUI_ICON_MAX_NAME_LENGTH, SEEK_CUR); + + // Read icons data directly over internal icons array + fread(guiIconsPtr, sizeof(unsigned int), iconCount*(iconSize*iconSize/32), rgiFile); + } + + fclose(rgiFile); + } + + return guiIconsName; +} + +// Draw selected icon using rectangles pixel-by-pixel +void GuiDrawIcon(int iconId, int posX, int posY, int pixelSize, Color color) +{ + #define BIT_CHECK(a,b) ((a) & (1u<<(b))) + + for (int i = 0, y = 0; i < RAYGUI_ICON_SIZE*RAYGUI_ICON_SIZE/32; i++) + { + for (int k = 0; k < 32; k++) + { + if (BIT_CHECK(guiIconsPtr[iconId*RAYGUI_ICON_DATA_ELEMENTS + i], k)) + { + #if !defined(RAYGUI_STANDALONE) + GuiDrawRectangle(RAYGUI_CLITERAL(Rectangle){ (float)posX + (k%RAYGUI_ICON_SIZE)*pixelSize, (float)posY + y*pixelSize, (float)pixelSize, (float)pixelSize }, 0, BLANK, color); + #endif + } + + if ((k == 15) || (k == 31)) y++; + } + } +} + +// Set icon drawing size +void GuiSetIconScale(int scale) +{ + if (scale >= 1) guiIconScale = scale; +} + +#endif // !RAYGUI_NO_ICONS + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- + +// Load style from memory +// WARNING: Binary files only +static void GuiLoadStyleFromMemory(const unsigned char *fileData, int dataSize) +{ + unsigned char *fileDataPtr = (unsigned char *)fileData; + + char signature[5] = { 0 }; + short version = 0; + short reserved = 0; + int propertyCount = 0; + + memcpy(signature, fileDataPtr, 4); + memcpy(&version, fileDataPtr + 4, sizeof(short)); + memcpy(&reserved, fileDataPtr + 4 + 2, sizeof(short)); + memcpy(&propertyCount, fileDataPtr + 4 + 2 + 2, sizeof(int)); + fileDataPtr += 12; + + if ((signature[0] == 'r') && + (signature[1] == 'G') && + (signature[2] == 'S') && + (signature[3] == ' ')) + { + short controlId = 0; + short propertyId = 0; + unsigned int propertyValue = 0; + + for (int i = 0; i < propertyCount; i++) + { + memcpy(&controlId, fileDataPtr, sizeof(short)); + memcpy(&propertyId, fileDataPtr + 2, sizeof(short)); + memcpy(&propertyValue, fileDataPtr + 2 + 2, sizeof(unsigned int)); + fileDataPtr += 8; + + if (controlId == 0) // DEFAULT control + { + // If a DEFAULT property is loaded, it is propagated to all controls + // NOTE: All DEFAULT properties should be defined first in the file + GuiSetStyle(0, (int)propertyId, propertyValue); + + if (propertyId < RAYGUI_MAX_PROPS_BASE) for (int j = 1; j < RAYGUI_MAX_CONTROLS; j++) GuiSetStyle(j, (int)propertyId, propertyValue); + } + else GuiSetStyle((int)controlId, (int)propertyId, propertyValue); + } + + // Font loading is highly dependant on raylib API to load font data and image + +#if !defined(RAYGUI_STANDALONE) + // Load custom font if available + int fontDataSize = 0; + memcpy(&fontDataSize, fileDataPtr, sizeof(int)); + fileDataPtr += 4; + + if (fontDataSize > 0) + { + Font font = { 0 }; + int fontType = 0; // 0-Normal, 1-SDF + + memcpy(&font.baseSize, fileDataPtr, sizeof(int)); + memcpy(&font.glyphCount, fileDataPtr + 4, sizeof(int)); + memcpy(&fontType, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + // Load font white rectangle + Rectangle fontWhiteRec = { 0 }; + memcpy(&fontWhiteRec, fileDataPtr, sizeof(Rectangle)); + fileDataPtr += 16; + + // Load font image parameters + int fontImageUncompSize = 0; + int fontImageCompSize = 0; + memcpy(&fontImageUncompSize, fileDataPtr, sizeof(int)); + memcpy(&fontImageCompSize, fileDataPtr + 4, sizeof(int)); + fileDataPtr += 8; + + Image imFont = { 0 }; + imFont.mipmaps = 1; + memcpy(&imFont.width, fileDataPtr, sizeof(int)); + memcpy(&imFont.height, fileDataPtr + 4, sizeof(int)); + memcpy(&imFont.format, fileDataPtr + 4 + 4, sizeof(int)); + fileDataPtr += 12; + + if ((fontImageCompSize > 0) && (fontImageCompSize != fontImageUncompSize)) + { + // Compressed font atlas image data (DEFLATE), it requires DecompressData() + int dataUncompSize = 0; + unsigned char *compData = (unsigned char *)RAYGUI_MALLOC(fontImageCompSize); + memcpy(compData, fileDataPtr, fontImageCompSize); + fileDataPtr += fontImageCompSize; + + imFont.data = DecompressData(compData, fontImageCompSize, &dataUncompSize); + + // Security check, dataUncompSize must match the provided fontImageUncompSize + if (dataUncompSize != fontImageUncompSize) RAYGUI_LOG("WARNING: Uncompressed font atlas image data could be corrupted"); + + RAYGUI_FREE(compData); + } + else + { + // Font atlas image data is not compressed + imFont.data = (unsigned char *)RAYGUI_MALLOC(fontImageUncompSize); + memcpy(imFont.data, fileDataPtr, fontImageUncompSize); + fileDataPtr += fontImageUncompSize; + } + + if (font.texture.id != GetFontDefault().texture.id) UnloadTexture(font.texture); + font.texture = LoadTextureFromImage(imFont); + + RAYGUI_FREE(imFont.data); + + // Validate font atlas texture was loaded correctly + if (font.texture.id != 0) + { + // Load font recs data + int recsDataSize = font.glyphCount*sizeof(Rectangle); + int recsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed recs data + memcpy(&recsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + if ((recsDataCompressedSize > 0) && (recsDataCompressedSize != recsDataSize)) + { + // Recs data is compressed, uncompress it + unsigned char *recsDataCompressed = (unsigned char *)RAYGUI_MALLOC(recsDataCompressedSize); + + memcpy(recsDataCompressed, fileDataPtr, recsDataCompressedSize); + fileDataPtr += recsDataCompressedSize; + + int recsDataUncompSize = 0; + font.recs = (Rectangle *)DecompressData(recsDataCompressed, recsDataCompressedSize, &recsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (recsDataUncompSize != recsDataSize) RAYGUI_LOG("WARNING: Uncompressed font recs data could be corrupted"); + + RAYGUI_FREE(recsDataCompressed); + } + else + { + // Recs data is uncompressed + font.recs = (Rectangle *)RAYGUI_CALLOC(font.glyphCount, sizeof(Rectangle)); + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.recs[i], fileDataPtr, sizeof(Rectangle)); + fileDataPtr += sizeof(Rectangle); + } + } + + // Load font glyphs info data + int glyphsDataSize = font.glyphCount*16; // 16 bytes data per glyph + int glyphsDataCompressedSize = 0; + + // WARNING: Version 400 adds the compression size parameter + if (version >= 400) + { + // RGS files version 400 support compressed glyphs data + memcpy(&glyphsDataCompressedSize, fileDataPtr, sizeof(int)); + fileDataPtr += sizeof(int); + } + + // Allocate required glyphs space to fill with data + font.glyphs = (GlyphInfo *)RAYGUI_CALLOC(font.glyphCount, sizeof(GlyphInfo)); + + if ((glyphsDataCompressedSize > 0) && (glyphsDataCompressedSize != glyphsDataSize)) + { + // Glyphs data is compressed, uncompress it + unsigned char *glypsDataCompressed = (unsigned char *)RAYGUI_MALLOC(glyphsDataCompressedSize); + + memcpy(glypsDataCompressed, fileDataPtr, glyphsDataCompressedSize); + fileDataPtr += glyphsDataCompressedSize; + + int glyphsDataUncompSize = 0; + unsigned char *glyphsDataUncomp = DecompressData(glypsDataCompressed, glyphsDataCompressedSize, &glyphsDataUncompSize); + + // Security check, data uncompressed size must match the expected original data size + if (glyphsDataUncompSize != glyphsDataSize) RAYGUI_LOG("WARNING: Uncompressed font glyphs data could be corrupted"); + + unsigned char *glyphsDataUncompPtr = glyphsDataUncomp; + + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, glyphsDataUncompPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, glyphsDataUncompPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, glyphsDataUncompPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, glyphsDataUncompPtr + 12, sizeof(int)); + glyphsDataUncompPtr += 16; + } + + RAYGUI_FREE(glypsDataCompressed); + RAYGUI_FREE(glyphsDataUncomp); + } + else + { + // Glyphs data is uncompressed + for (int i = 0; i < font.glyphCount; i++) + { + memcpy(&font.glyphs[i].value, fileDataPtr, sizeof(int)); + memcpy(&font.glyphs[i].offsetX, fileDataPtr + 4, sizeof(int)); + memcpy(&font.glyphs[i].offsetY, fileDataPtr + 8, sizeof(int)); + memcpy(&font.glyphs[i].advanceX, fileDataPtr + 12, sizeof(int)); + fileDataPtr += 16; + } + } + } + else font = GetFontDefault(); // Fallback in case of errors loading font atlas texture + + GuiSetFont(font); + + // Set font texture source rectangle to be used as white texture to draw shapes + // NOTE: It makes possible to draw shapes and text (full UI) in a single draw call + if ((fontWhiteRec.x > 0) && + (fontWhiteRec.y > 0) && + (fontWhiteRec.width > 0) && + (fontWhiteRec.height > 0)) SetShapesTexture(font.texture, fontWhiteRec); + } +#endif + } +} + +// Gui get text width considering icon +static int GetTextWidth(const char *text) +{ + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + Vector2 textSize = { 0 }; + int textIconOffset = 0; + + if ((text != NULL) && (text[0] != '\0')) + { + if (text[0] == '#') + { + for (int i = 1; (i < 5) && (text[i] != '\0'); i++) + { + if (text[i] == '#') + { + textIconOffset = i; + break; + } + } + } + + text += textIconOffset; + + // Make sure guiFont is set, GuiGetStyle() initializes it lazynessly + float fontSize = (float)GuiGetStyle(DEFAULT, TEXT_SIZE); + + // Custom MeasureText() implementation + if ((guiFont.texture.id > 0) && (text != NULL)) + { + // Get size in bytes of text, considering end of line and line break + int size = 0; + for (int i = 0; i < MAX_LINE_BUFFER_SIZE; i++) + { + if ((text[i] != '\0') && (text[i] != '\n')) size++; + else break; + } + + float scaleFactor = fontSize/(float)guiFont.baseSize; + textSize.y = (float)guiFont.baseSize*scaleFactor; + float glyphWidth = 0.0f; + + for (int i = 0, codepointSize = 0; i < size; i += codepointSize) + { + int codepoint = GetCodepointNext(&text[i], &codepointSize); + int codepointIndex = GetGlyphIndex(guiFont, codepoint); + + if (guiFont.glyphs[codepointIndex].advanceX == 0) glyphWidth = ((float)guiFont.recs[codepointIndex].width*scaleFactor); + else glyphWidth = ((float)guiFont.glyphs[codepointIndex].advanceX*scaleFactor); + + textSize.x += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (textIconOffset > 0) textSize.x += (RAYGUI_ICON_SIZE + ICON_TEXT_PADDING); + } + + return (int)textSize.x; +} + +// Get text bounds considering control bounds +static Rectangle GetTextBounds(int control, Rectangle bounds) +{ + Rectangle textBounds = bounds; + + textBounds.x = bounds.x + GuiGetStyle(control, BORDER_WIDTH); + textBounds.y = bounds.y + GuiGetStyle(control, BORDER_WIDTH) + GuiGetStyle(control, TEXT_PADDING); + textBounds.width = bounds.width - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); + textBounds.height = bounds.height - 2*GuiGetStyle(control, BORDER_WIDTH) - 2*GuiGetStyle(control, TEXT_PADDING); // NOTE: Text is processed line per line! + + // Depending on control, TEXT_PADDING and TEXT_ALIGNMENT properties could affect the text-bounds + switch (control) + { + case COMBOBOX: + case DROPDOWNBOX: + case LISTVIEW: + // TODO: Special cases (no label): COMBOBOX, DROPDOWNBOX, LISTVIEW + case SLIDER: + case CHECKBOX: + case VALUEBOX: + case SPINNER: + // TODO: More special cases (label on side): SLIDER, CHECKBOX, VALUEBOX, SPINNER + default: + { + // TODO: WARNING: TEXT_ALIGNMENT is already considered in GuiDrawText() + if (GuiGetStyle(control, TEXT_ALIGNMENT) == TEXT_ALIGN_RIGHT) textBounds.x -= GuiGetStyle(control, TEXT_PADDING); + else textBounds.x += GuiGetStyle(control, TEXT_PADDING); + } + break; + } + + return textBounds; +} + +// Get text icon if provided and move text cursor +// NOTE: We support up to 999 values for iconId +static const char *GetTextIcon(const char *text, int *iconId) +{ +#if !defined(RAYGUI_NO_ICONS) + *iconId = -1; + if (text[0] == '#') // Maybe we have an icon! + { + char iconValue[4] = { 0 }; // Maximum length for icon value: 3 digits + '\0' + + int pos = 1; + while ((pos < 4) && (text[pos] >= '0') && (text[pos] <= '9')) + { + iconValue[pos - 1] = text[pos]; + pos++; + } + + if (text[pos] == '#') + { + *iconId = TextToInteger(iconValue); + + // Move text pointer after icon + // WARNING: If only icon provided, it could point to EOL character: '\0' + if (*iconId >= 0) text += (pos + 1); + } + } +#endif + + return text; +} + +// Get text divided into lines (by line-breaks '\n') +const char **GetTextLines(const char *text, int *count) +{ + #define RAYGUI_MAX_TEXT_LINES 128 + + static const char *lines[RAYGUI_MAX_TEXT_LINES] = { 0 }; + for (int i = 0; i < RAYGUI_MAX_TEXT_LINES; i++) lines[i] = NULL; // Init NULL pointers to substrings + + int textSize = (int)strlen(text); + + lines[0] = text; + int len = 0; + *count = 1; + //int lineSize = 0; // Stores current line size, not returned + + for (int i = 0, k = 0; (i < textSize) && (*count < RAYGUI_MAX_TEXT_LINES); i++) + { + if (text[i] == '\n') + { + //lineSize = len; + k++; + lines[k] = &text[i + 1]; // WARNING: next value is valid? + len = 0; + *count += 1; + } + else len++; + } + + //lines[*count - 1].size = len; + + return lines; +} + +// Get text width to next space for provided string +static float GetNextSpaceWidth(const char *text, int *nextSpaceIndex) +{ + float width = 0; + int codepointByteCount = 0; + int codepoint = 0; + int index = 0; + float glyphWidth = 0; + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + for (int i = 0; text[i] != '\0'; i++) + { + if (text[i] != ' ') + { + codepoint = GetCodepoint(&text[i], &codepointByteCount); + index = GetGlyphIndex(guiFont, codepoint); + glyphWidth = (guiFont.glyphs[index].advanceX == 0)? guiFont.recs[index].width*scaleFactor : guiFont.glyphs[index].advanceX*scaleFactor; + width += (glyphWidth + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + else + { + *nextSpaceIndex = i; + break; + } + } + + return width; +} + +// Gui draw text using default font +static void GuiDrawText(const char *text, Rectangle textBounds, int alignment, Color tint) +{ + #define TEXT_VALIGN_PIXEL_OFFSET(h) ((int)h%2) // Vertical alignment for pixel perfect + + #if !defined(ICON_TEXT_PADDING) + #define ICON_TEXT_PADDING 4 + #endif + + if ((text == NULL) || (text[0] == '\0')) return; // Security check + + // PROCEDURE: + // - Text is processed line per line + // - For every line, horizontal alignment is defined + // - For all text, vertical alignment is defined (multiline text only) + // - For every line, wordwrap mode is checked (useful for GuitextBox(), read-only) + + // Get text lines (using '\n' as delimiter) to be processed individually + // WARNING: We can't use GuiTextSplit() function because it can be already used + // before the GuiDrawText() call and its buffer is static, it would be overriden :( + int lineCount = 0; + const char **lines = GetTextLines(text, &lineCount); + + // Text style variables + //int alignment = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT); + int alignmentVertical = GuiGetStyle(DEFAULT, TEXT_ALIGNMENT_VERTICAL); + int wrapMode = GuiGetStyle(DEFAULT, TEXT_WRAP_MODE); // Wrap-mode only available in read-only mode, no for text editing + + // TODO: WARNING: This totalHeight is not valid for vertical alignment in case of word-wrap + float totalHeight = (float)(lineCount*GuiGetStyle(DEFAULT, TEXT_SIZE) + (lineCount - 1)*GuiGetStyle(DEFAULT, TEXT_SIZE)/2); + float posOffsetY = 0.0f; + + for (int i = 0; i < lineCount; i++) + { + int iconId = 0; + lines[i] = GetTextIcon(lines[i], &iconId); // Check text for icon and move cursor + + // Get text position depending on alignment and iconId + //--------------------------------------------------------------------------------- + Vector2 textBoundsPosition = { textBounds.x, textBounds.y }; + float textBoundsWidthOffset = 0.0f; + + // NOTE: We get text size after icon has been processed + // WARNING: GetTextWidth() also processes text icon to get width! -> Really needed? + int textSizeX = GetTextWidth(lines[i]); + + // If text requires an icon, add size to measure + if (iconId >= 0) + { + textSizeX += RAYGUI_ICON_SIZE*guiIconScale; + + // WARNING: If only icon provided, text could be pointing to EOF character: '\0' +#if !defined(RAYGUI_NO_ICONS) + if ((lines[i] != NULL) && (lines[i][0] != '\0')) textSizeX += ICON_TEXT_PADDING; +#endif + } + + // Check guiTextAlign global variables + switch (alignment) + { + case TEXT_ALIGN_LEFT: textBoundsPosition.x = textBounds.x; break; + case TEXT_ALIGN_CENTER: textBoundsPosition.x = textBounds.x + textBounds.width/2 - textSizeX/2; break; + case TEXT_ALIGN_RIGHT: textBoundsPosition.x = textBounds.x + textBounds.width - textSizeX; break; + default: break; + } + + if (textSizeX > textBounds.width && (lines[i] != NULL) && (lines[i][0] != '\0')) textBoundsPosition.x = textBounds.x; + + switch (alignmentVertical) + { + // Only valid in case of wordWrap = 0; + case TEXT_ALIGN_TOP: textBoundsPosition.y = textBounds.y + posOffsetY; break; + case TEXT_ALIGN_MIDDLE: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height/2 - totalHeight/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + case TEXT_ALIGN_BOTTOM: textBoundsPosition.y = textBounds.y + posOffsetY + textBounds.height - totalHeight + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height); break; + default: break; + } + + // NOTE: Make sure we get pixel-perfect coordinates, + // In case of decimals we got weird text positioning + textBoundsPosition.x = (float)((int)textBoundsPosition.x); + textBoundsPosition.y = (float)((int)textBoundsPosition.y); + //--------------------------------------------------------------------------------- + + // Draw text (with icon if available) + //--------------------------------------------------------------------------------- +#if !defined(RAYGUI_NO_ICONS) + if (iconId >= 0) + { + // NOTE: We consider icon height, probably different than text size + GuiDrawIcon(iconId, (int)textBoundsPosition.x, (int)(textBounds.y + textBounds.height/2 - RAYGUI_ICON_SIZE*guiIconScale/2 + TEXT_VALIGN_PIXEL_OFFSET(textBounds.height)), guiIconScale, tint); + textBoundsPosition.x += (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + textBoundsWidthOffset = (float)(RAYGUI_ICON_SIZE*guiIconScale + ICON_TEXT_PADDING); + } +#endif + // Get size in bytes of text, + // considering end of line and line break + int lineSize = 0; + for (int c = 0; (lines[i][c] != '\0') && (lines[i][c] != '\n') && (lines[i][c] != '\r'); c++, lineSize++){ } + float scaleFactor = (float)GuiGetStyle(DEFAULT, TEXT_SIZE)/guiFont.baseSize; + + int lastSpaceIndex = 0; + bool tempWrapCharMode = false; + + int textOffsetY = 0; + float textOffsetX = 0.0f; + float glyphWidth = 0; + + int ellipsisWidth = GetTextWidth("..."); + bool textOverflow = false; + for (int c = 0, codepointSize = 0; c < lineSize; c += codepointSize) + { + int codepoint = GetCodepointNext(&lines[i][c], &codepointSize); + int index = GetGlyphIndex(guiFont, codepoint); + + // NOTE: Normally we exit the decoding sequence as soon as a bad byte is found (and return 0x3f) + // but we need to draw all of the bad bytes using the '?' symbol moving one byte + if (codepoint == 0x3f) codepointSize = 1; // TODO: Review not recognized codepoints size + + // Get glyph width to check if it goes out of bounds + if (guiFont.glyphs[index].advanceX == 0) glyphWidth = ((float)guiFont.recs[index].width*scaleFactor); + else glyphWidth = (float)guiFont.glyphs[index].advanceX*scaleFactor; + + // Wrap mode text measuring, to validate if + // it can be drawn or a new line is required + if (wrapMode == TEXT_WRAP_CHAR) + { + // Jump to next line if current character reach end of the box limits + if ((textOffsetX + glyphWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + + if (tempWrapCharMode) // Wrap at char level when too long words + { + wrapMode = TEXT_WRAP_WORD; + tempWrapCharMode = false; + } + } + } + else if (wrapMode == TEXT_WRAP_WORD) + { + if (codepoint == 32) lastSpaceIndex = c; + + // Get width to next space in line + int nextSpaceIndex = 0; + float nextSpaceWidth = GetNextSpaceWidth(lines[i] + c, &nextSpaceIndex); + + int nextSpaceIndex2 = 0; + float nextWordSize = GetNextSpaceWidth(lines[i] + lastSpaceIndex + 1, &nextSpaceIndex2); + + if (nextWordSize > textBounds.width - textBoundsWidthOffset) + { + // Considering the case the next word is longer than bounds + tempWrapCharMode = true; + wrapMode = TEXT_WRAP_CHAR; + } + else if ((textOffsetX + nextSpaceWidth) > textBounds.width - textBoundsWidthOffset) + { + textOffsetX = 0.0f; + textOffsetY += GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + } + } + + if (codepoint == '\n') break; // WARNING: Lines are already processed manually, no need to keep drawing after this codepoint + else + { + // TODO: There are multiple types of spaces in Unicode, + // maybe it's a good idea to add support for more: http://jkorpela.fi/chars/spaces.html + if ((codepoint != ' ') && (codepoint != '\t')) // Do not draw codepoints with no glyph + { + if (wrapMode == TEXT_WRAP_NONE) + { + // Draw only required text glyphs fitting the textBounds.width + if (textSizeX > textBounds.width) + { + if (textOffsetX <= (textBounds.width - glyphWidth - textBoundsWidthOffset - ellipsisWidth)) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + else if (!textOverflow) + { + textOverflow = true; + + for (int j = 0; j < ellipsisWidth; j += ellipsisWidth/3) + { + DrawTextCodepoint(guiFont, '.', RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX + j, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + else + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) + { + // Draw only glyphs inside the bounds + if ((textBoundsPosition.y + textOffsetY) <= (textBounds.y + textBounds.height - GuiGetStyle(DEFAULT, TEXT_SIZE))) + { + DrawTextCodepoint(guiFont, codepoint, RAYGUI_CLITERAL(Vector2){ textBoundsPosition.x + textOffsetX, textBoundsPosition.y + textOffsetY }, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), GuiFade(tint, guiAlpha)); + } + } + } + + if (guiFont.glyphs[index].advanceX == 0) textOffsetX += ((float)guiFont.recs[index].width*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + else textOffsetX += ((float)guiFont.glyphs[index].advanceX*scaleFactor + (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + } + } + + if (wrapMode == TEXT_WRAP_NONE) posOffsetY += (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING); + else if ((wrapMode == TEXT_WRAP_CHAR) || (wrapMode == TEXT_WRAP_WORD)) posOffsetY += (textOffsetY + (float)GuiGetStyle(DEFAULT, TEXT_LINE_SPACING)); + //--------------------------------------------------------------------------------- + } + +#if defined(RAYGUI_DEBUG_TEXT_BOUNDS) + GuiDrawRectangle(textBounds, 0, WHITE, Fade(BLUE, 0.4f)); +#endif +} + +// Gui draw rectangle using default raygui plain style with borders +static void GuiDrawRectangle(Rectangle rec, int borderWidth, Color borderColor, Color color) +{ + if (color.a > 0) + { + // Draw rectangle filled with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, GuiFade(color, guiAlpha)); + } + + if (borderWidth > 0) + { + // Draw rectangle border lines with color + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x + (int)rec.width - borderWidth, (int)rec.y + borderWidth, borderWidth, (int)rec.height - 2*borderWidth, GuiFade(borderColor, guiAlpha)); + DrawRectangle((int)rec.x, (int)rec.y + (int)rec.height - borderWidth, (int)rec.width, borderWidth, GuiFade(borderColor, guiAlpha)); + } + +#if defined(RAYGUI_DEBUG_RECS_BOUNDS) + DrawRectangle((int)rec.x, (int)rec.y, (int)rec.width, (int)rec.height, Fade(RED, 0.4f)); +#endif +} + +// Draw tooltip using control bounds +static void GuiTooltip(Rectangle controlRec) +{ + if (!guiLocked && guiTooltip && (guiTooltipPtr != NULL) && !guiControlExclusiveMode) + { + Vector2 textSize = MeasureTextEx(GuiGetFont(), guiTooltipPtr, (float)GuiGetStyle(DEFAULT, TEXT_SIZE), (float)GuiGetStyle(DEFAULT, TEXT_SPACING)); + + if ((controlRec.x + textSize.x + 16) > GetScreenWidth()) controlRec.x -= (textSize.x + 16 - controlRec.width); + + GuiPanel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, NULL); + + int textPadding = GuiGetStyle(LABEL, TEXT_PADDING); + int textAlignment = GuiGetStyle(LABEL, TEXT_ALIGNMENT); + GuiSetStyle(LABEL, TEXT_PADDING, 0); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, TEXT_ALIGN_CENTER); + GuiLabel(RAYGUI_CLITERAL(Rectangle){ controlRec.x, controlRec.y + controlRec.height + 4, textSize.x + 16, GuiGetStyle(DEFAULT, TEXT_SIZE) + 8.f }, guiTooltipPtr); + GuiSetStyle(LABEL, TEXT_ALIGNMENT, textAlignment); + GuiSetStyle(LABEL, TEXT_PADDING, textPadding); + } +} + +// Split controls text into multiple strings +// Also check for multiple columns (required by GuiToggleGroup()) +static const char **GuiTextSplit(const char *text, char delimiter, int *count, int *textRow) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + // NOTE: Those definitions could be externally provided if required + + // TODO: HACK: GuiTextSplit() - Review how textRows are returned to user + // textRow is an externally provided array of integers that stores row number for every splitted string + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; // String pointers array (points to buffer data) + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; // Buffer data (text input copy with '\0' added) + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 1; + + if (textRow != NULL) textRow[0] = 0; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if ((buffer[i] == delimiter) || (buffer[i] == '\n')) + { + result[counter] = buffer + i + 1; + + if (textRow != NULL) + { + if (buffer[i] == '\n') textRow[counter] = textRow[counter - 1] + 1; + else textRow[counter] = textRow[counter - 1]; + } + + buffer[i] = '\0'; // Set an end of string at this point + + counter++; + if (counter > RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + + *count = counter; + + return result; +} + +// Convert color data from RGB to HSV +// NOTE: Color data should be passed normalized +static Vector3 ConvertRGBtoHSV(Vector3 rgb) +{ + Vector3 hsv = { 0 }; + float min = 0.0f; + float max = 0.0f; + float delta = 0.0f; + + min = (rgb.x < rgb.y)? rgb.x : rgb.y; + min = (min < rgb.z)? min : rgb.z; + + max = (rgb.x > rgb.y)? rgb.x : rgb.y; + max = (max > rgb.z)? max : rgb.z; + + hsv.z = max; // Value + delta = max - min; + + if (delta < 0.00001f) + { + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + if (max > 0.0f) + { + // NOTE: If max is 0, this divide would cause a crash + hsv.y = (delta/max); // Saturation + } + else + { + // NOTE: If max is 0, then r = g = b = 0, s = 0, h is undefined + hsv.y = 0.0f; + hsv.x = 0.0f; // Undefined, maybe NAN? + return hsv; + } + + // NOTE: Comparing float values could not work properly + if (rgb.x >= max) hsv.x = (rgb.y - rgb.z)/delta; // Between yellow & magenta + else + { + if (rgb.y >= max) hsv.x = 2.0f + (rgb.z - rgb.x)/delta; // Between cyan & yellow + else hsv.x = 4.0f + (rgb.x - rgb.y)/delta; // Between magenta & cyan + } + + hsv.x *= 60.0f; // Convert to degrees + + if (hsv.x < 0.0f) hsv.x += 360.0f; + + return hsv; +} + +// Convert color data from HSV to RGB +// NOTE: Color data should be passed normalized +static Vector3 ConvertHSVtoRGB(Vector3 hsv) +{ + Vector3 rgb = { 0 }; + float hh = 0.0f, p = 0.0f, q = 0.0f, t = 0.0f, ff = 0.0f; + long i = 0; + + // NOTE: Comparing float values could not work properly + if (hsv.y <= 0.0f) + { + rgb.x = hsv.z; + rgb.y = hsv.z; + rgb.z = hsv.z; + return rgb; + } + + hh = hsv.x; + if (hh >= 360.0f) hh = 0.0f; + hh /= 60.0f; + + i = (long)hh; + ff = hh - i; + p = hsv.z*(1.0f - hsv.y); + q = hsv.z*(1.0f - (hsv.y*ff)); + t = hsv.z*(1.0f - (hsv.y*(1.0f - ff))); + + switch (i) + { + case 0: + { + rgb.x = hsv.z; + rgb.y = t; + rgb.z = p; + } break; + case 1: + { + rgb.x = q; + rgb.y = hsv.z; + rgb.z = p; + } break; + case 2: + { + rgb.x = p; + rgb.y = hsv.z; + rgb.z = t; + } break; + case 3: + { + rgb.x = p; + rgb.y = q; + rgb.z = hsv.z; + } break; + case 4: + { + rgb.x = t; + rgb.y = p; + rgb.z = hsv.z; + } break; + case 5: + default: + { + rgb.x = hsv.z; + rgb.y = p; + rgb.z = q; + } break; + } + + return rgb; +} + +// Scroll bar control (used by GuiScrollPanel()) +static int GuiScrollBar(Rectangle bounds, int value, int minValue, int maxValue) +{ + GuiState state = guiState; + + // Is the scrollbar horizontal or vertical? + bool isVertical = (bounds.width > bounds.height)? false : true; + + // The size (width or height depending on scrollbar type) of the spinner buttons + const int spinnerSize = GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)? + (isVertical? (int)bounds.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) : + (int)bounds.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH)) : 0; + + // Arrow buttons [<] [>] [∧] [∨] + Rectangle arrowUpLeft = { 0 }; + Rectangle arrowDownRight = { 0 }; + + // Actual area of the scrollbar excluding the arrow buttons + Rectangle scrollbar = { 0 }; + + // Slider bar that moves --[///]----- + Rectangle slider = { 0 }; + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + + int valueRange = maxValue - minValue; + if (valueRange <= 0) valueRange = 1; + + int sliderSize = GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_SIZE); + if (sliderSize < 1) sliderSize = 1; // TODO: Consider a minimum slider size + + // Calculate rectangles for all of the components + arrowUpLeft = RAYGUI_CLITERAL(Rectangle){ + (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), + (float)spinnerSize, (float)spinnerSize }; + + if (isVertical) + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + bounds.height - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), arrowUpLeft.y + arrowUpLeft.height, bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)), bounds.height - arrowUpLeft.height - arrowDownRight.height - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.height)? ((int)scrollbar.height - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + bounds.x + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + scrollbar.y + (int)(((float)(value - minValue)/valueRange)*(scrollbar.height - sliderSize)), + bounds.width - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)), + (float)sliderSize }; + } + else // horizontal + { + arrowDownRight = RAYGUI_CLITERAL(Rectangle){ (float)bounds.x + bounds.width - spinnerSize - GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH), (float)spinnerSize, (float)spinnerSize }; + scrollbar = RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x + arrowUpLeft.width, bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING), bounds.width - arrowUpLeft.width - arrowDownRight.width - 2*GuiGetStyle(SCROLLBAR, BORDER_WIDTH), bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_PADDING)) }; + + // Make sure the slider won't get outside of the scrollbar + sliderSize = (sliderSize >= scrollbar.width)? ((int)scrollbar.width - 2) : sliderSize; + slider = RAYGUI_CLITERAL(Rectangle){ + scrollbar.x + (int)(((float)(value - minValue)/valueRange)*(scrollbar.width - sliderSize)), + bounds.y + GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING), + (float)sliderSize, + bounds.height - 2*(GuiGetStyle(SCROLLBAR, BORDER_WIDTH) + GuiGetStyle(SCROLLBAR, SCROLL_SLIDER_PADDING)) }; + } + + // Update control + //-------------------------------------------------------------------- + if ((state != STATE_DISABLED) && !guiLocked) + { + Vector2 mousePoint = GetMousePosition(); + + if (guiControlExclusiveMode) // Allows to keep dragging outside of bounds + { + if (IsMouseButtonDown(MOUSE_LEFT_BUTTON) && + !CheckCollisionPointRec(mousePoint, arrowUpLeft) && + !CheckCollisionPointRec(mousePoint, arrowDownRight)) + { + if (CHECK_BOUNDS_ID(bounds, guiControlExclusiveRec)) + { + state = STATE_PRESSED; + + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + } + else + { + guiControlExclusiveMode = false; + guiControlExclusiveRec = RAYGUI_CLITERAL(Rectangle){ 0, 0, 0, 0 }; + } + } + else if (CheckCollisionPointRec(mousePoint, bounds)) + { + state = STATE_FOCUSED; + + // Handle mouse wheel + int wheel = (int)GetMouseWheelMove(); + if (wheel != 0) value += wheel; + + // Handle mouse button down + if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) + { + guiControlExclusiveMode = true; + guiControlExclusiveRec = bounds; // Store bounds as an identifier when dragging starts + + // Check arrows click + if (CheckCollisionPointRec(mousePoint, arrowUpLeft)) value -= valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (CheckCollisionPointRec(mousePoint, arrowDownRight)) value += valueRange/GuiGetStyle(SCROLLBAR, SCROLL_SPEED); + else if (!CheckCollisionPointRec(mousePoint, slider)) + { + // If click on scrollbar position but not on slider, place slider directly on that position + if (isVertical) value = (int)(((float)(mousePoint.y - scrollbar.y - slider.height/2)*valueRange)/(scrollbar.height - slider.height) + minValue); + else value = (int)(((float)(mousePoint.x - scrollbar.x - slider.width/2)*valueRange)/(scrollbar.width - slider.width) + minValue); + } + + state = STATE_PRESSED; + } + + // Keyboard control on mouse hover scrollbar + /* + if (isVertical) + { + if (IsKeyDown(KEY_DOWN)) value += 5; + else if (IsKeyDown(KEY_UP)) value -= 5; + } + else + { + if (IsKeyDown(KEY_RIGHT)) value += 5; + else if (IsKeyDown(KEY_LEFT)) value -= 5; + } + */ + } + + // Normalize value + if (value > maxValue) value = maxValue; + if (value < minValue) value = minValue; + } + //-------------------------------------------------------------------- + + // Draw control + //-------------------------------------------------------------------- + GuiDrawRectangle(bounds, GuiGetStyle(SCROLLBAR, BORDER_WIDTH), GetColor(GuiGetStyle(LISTVIEW, BORDER + state*3)), GetColor(GuiGetStyle(DEFAULT, BORDER_COLOR_DISABLED))); // Draw the background + + GuiDrawRectangle(scrollbar, 0, BLANK, GetColor(GuiGetStyle(BUTTON, BASE_COLOR_NORMAL))); // Draw the scrollbar active area background + GuiDrawRectangle(slider, 0, BLANK, GetColor(GuiGetStyle(SLIDER, BORDER + state*3))); // Draw the slider bar + + // Draw arrows (using icon if available) + if (GuiGetStyle(SCROLLBAR, ARROWS_VISIBLE)) + { +#if defined(RAYGUI_NO_ICONS) + GuiDrawText(isVertical? "^" : "<", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); + GuiDrawText(isVertical? "v" : ">", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(DROPDOWNBOX, TEXT + (state*3)))); +#else + GuiDrawText(isVertical? "#121#" : "#118#", + RAYGUI_CLITERAL(Rectangle){ arrowUpLeft.x, arrowUpLeft.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_UP_FILL / ICON_ARROW_LEFT_FILL + GuiDrawText(isVertical? "#120#" : "#119#", + RAYGUI_CLITERAL(Rectangle){ arrowDownRight.x, arrowDownRight.y, isVertical? bounds.width : bounds.height, isVertical? bounds.width : bounds.height }, + TEXT_ALIGN_CENTER, GetColor(GuiGetStyle(SCROLLBAR, TEXT + state*3))); // ICON_ARROW_DOWN_FILL / ICON_ARROW_RIGHT_FILL +#endif + } + //-------------------------------------------------------------------- + + return value; +} + +// Color fade-in or fade-out, alpha goes from 0.0f to 1.0f +// WARNING: It multiplies current alpha by alpha scale factor +static Color GuiFade(Color color, float alpha) +{ + if (alpha < 0.0f) alpha = 0.0f; + else if (alpha > 1.0f) alpha = 1.0f; + + Color result = { color.r, color.g, color.b, (unsigned char)(color.a*alpha) }; + + return result; +} + +#if defined(RAYGUI_STANDALONE) +// Returns a Color struct from hexadecimal value +static Color GetColor(int hexValue) +{ + Color color; + + color.r = (unsigned char)(hexValue >> 24) & 0xFF; + color.g = (unsigned char)(hexValue >> 16) & 0xFF; + color.b = (unsigned char)(hexValue >> 8) & 0xFF; + color.a = (unsigned char)hexValue & 0xFF; + + return color; +} + +// Returns hexadecimal value for a Color +static int ColorToInt(Color color) +{ + return (((int)color.r << 24) | ((int)color.g << 16) | ((int)color.b << 8) | (int)color.a); +} + +// Check if point is inside rectangle +static bool CheckCollisionPointRec(Vector2 point, Rectangle rec) +{ + bool collision = false; + + if ((point.x >= rec.x) && (point.x <= (rec.x + rec.width)) && + (point.y >= rec.y) && (point.y <= (rec.y + rec.height))) collision = true; + + return collision; +} + +// Formatting of text with variables to 'embed' +static const char *TextFormat(const char *text, ...) +{ + #if !defined(RAYGUI_TEXTFORMAT_MAX_SIZE) + #define RAYGUI_TEXTFORMAT_MAX_SIZE 256 + #endif + + static char buffer[RAYGUI_TEXTFORMAT_MAX_SIZE]; + + va_list args; + va_start(args, text); + vsprintf(buffer, text, args); + va_end(args); + + return buffer; +} + +// Draw rectangle with vertical gradient fill color +// NOTE: This function is only used by GuiColorPicker() +static void DrawRectangleGradientV(int posX, int posY, int width, int height, Color color1, Color color2) +{ + Rectangle bounds = { (float)posX, (float)posY, (float)width, (float)height }; + DrawRectangleGradientEx(bounds, color1, color2, color2, color1); +} + +// Split string into multiple strings +const char **TextSplit(const char *text, char delimiter, int *count) +{ + // NOTE: Current implementation returns a copy of the provided string with '\0' (string end delimiter) + // inserted between strings defined by "delimiter" parameter. No memory is dynamically allocated, + // all used memory is static... it has some limitations: + // 1. Maximum number of possible split strings is set by RAYGUI_TEXTSPLIT_MAX_ITEMS + // 2. Maximum size of text to split is RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE + + #if !defined(RAYGUI_TEXTSPLIT_MAX_ITEMS) + #define RAYGUI_TEXTSPLIT_MAX_ITEMS 128 + #endif + #if !defined(RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE) + #define RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE 1024 + #endif + + static const char *result[RAYGUI_TEXTSPLIT_MAX_ITEMS] = { NULL }; + static char buffer[RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE] = { 0 }; + memset(buffer, 0, RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE); + + result[0] = buffer; + int counter = 0; + + if (text != NULL) + { + counter = 1; + + // Count how many substrings we have on text and point to every one + for (int i = 0; i < RAYGUI_TEXTSPLIT_MAX_TEXT_SIZE; i++) + { + buffer[i] = text[i]; + if (buffer[i] == '\0') break; + else if (buffer[i] == delimiter) + { + buffer[i] = '\0'; // Set an end of string at this point + result[counter] = buffer + i + 1; + counter++; + + if (counter == RAYGUI_TEXTSPLIT_MAX_ITEMS) break; + } + } + } + + *count = counter; + return result; +} + +// Get integer value from text +// NOTE: This function replaces atoi() [stdlib.h] +static int TextToInteger(const char *text) +{ + int value = 0; + int sign = 1; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1; + text++; + } + + for (int i = 0; ((text[i] >= '0') && (text[i] <= '9')); ++i) value = value*10 + (int)(text[i] - '0'); + + return value*sign; +} + +// Get float value from text +// NOTE: This function replaces atof() [stdlib.h] +// WARNING: Only '.' character is understood as decimal point +static float TextToFloat(const char *text) +{ + float value = 0.0f; + float sign = 1.0f; + + if ((text[0] == '+') || (text[0] == '-')) + { + if (text[0] == '-') sign = -1.0f; + text++; + } + + int i = 0; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) value = value*10.0f + (float)(text[i] - '0'); + + if (text[i++] != '.') value *= sign; + else + { + float divisor = 10.0f; + for (; ((text[i] >= '0') && (text[i] <= '9')); i++) + { + value += ((float)(text[i] - '0'))/divisor; + divisor = divisor*10.0f; + } + } + + return value; +} + +// Encode codepoint into UTF-8 text (char array size returned as parameter) +static const char *CodepointToUTF8(int codepoint, int *byteSize) +{ + static char utf8[6] = { 0 }; + int size = 0; + + if (codepoint <= 0x7f) + { + utf8[0] = (char)codepoint; + size = 1; + } + else if (codepoint <= 0x7ff) + { + utf8[0] = (char)(((codepoint >> 6) & 0x1f) | 0xc0); + utf8[1] = (char)((codepoint & 0x3f) | 0x80); + size = 2; + } + else if (codepoint <= 0xffff) + { + utf8[0] = (char)(((codepoint >> 12) & 0x0f) | 0xe0); + utf8[1] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[2] = (char)((codepoint & 0x3f) | 0x80); + size = 3; + } + else if (codepoint <= 0x10ffff) + { + utf8[0] = (char)(((codepoint >> 18) & 0x07) | 0xf0); + utf8[1] = (char)(((codepoint >> 12) & 0x3f) | 0x80); + utf8[2] = (char)(((codepoint >> 6) & 0x3f) | 0x80); + utf8[3] = (char)((codepoint & 0x3f) | 0x80); + size = 4; + } + + *byteSize = size; + + return utf8; +} + +// Get next codepoint in a UTF-8 encoded text, scanning until '\0' is found +// When a invalid UTF-8 byte is encountered we exit as soon as possible and a '?'(0x3f) codepoint is returned +// Total number of bytes processed are returned as a parameter +// NOTE: the standard says U+FFFD should be returned in case of errors +// but that character is not supported by the default font in raylib +static int GetCodepointNext(const char *text, int *codepointSize) +{ + const char *ptr = text; + int codepoint = 0x3f; // Codepoint (defaults to '?') + *codepointSize = 1; + + // Get current codepoint and bytes processed + if (0xf0 == (0xf8 & ptr[0])) + { + // 4 byte UTF-8 codepoint + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80) || ((ptr[3] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x07 & ptr[0]) << 18) | ((0x3f & ptr[1]) << 12) | ((0x3f & ptr[2]) << 6) | (0x3f & ptr[3]); + *codepointSize = 4; + } + else if (0xe0 == (0xf0 & ptr[0])) + { + // 3 byte UTF-8 codepoint */ + if (((ptr[1] & 0xC0) ^ 0x80) || ((ptr[2] & 0xC0) ^ 0x80)) { return codepoint; } //10xxxxxx checks + codepoint = ((0x0f & ptr[0]) << 12) | ((0x3f & ptr[1]) << 6) | (0x3f & ptr[2]); + *codepointSize = 3; + } + else if (0xc0 == (0xe0 & ptr[0])) + { + // 2 byte UTF-8 codepoint + if ((ptr[1] & 0xC0) ^ 0x80) { return codepoint; } //10xxxxxx checks + codepoint = ((0x1f & ptr[0]) << 6) | (0x3f & ptr[1]); + *codepointSize = 2; + } + else if (0x00 == (0x80 & ptr[0])) + { + // 1 byte UTF-8 codepoint + codepoint = ptr[0]; + *codepointSize = 1; + } + + return codepoint; +} +#endif // RAYGUI_STANDALONE + +#endif // RAYGUI_IMPLEMENTATION diff --git a/third_party/raylib/include/raylib.h b/third_party/raylib/include/raylib.h new file mode 100644 index 00000000000..ea973ff1bac --- /dev/null +++ b/third_party/raylib/include/raylib.h @@ -0,0 +1,1766 @@ +/********************************************************************************************** +* +* raylib v5.6-dev - A simple and easy-to-use library to enjoy videogames programming (www.raylib.com) +* +* FEATURES: +* - NO external dependencies, all required libraries included with raylib +* - Multiplatform: Windows, Linux, FreeBSD, OpenBSD, NetBSD, DragonFly, +* MacOS, Haiku, Android, Raspberry Pi, DRM native, HTML5. +* - Written in plain C code (C99) in PascalCase/camelCase notation +* - Hardware accelerated with OpenGL (1.1, 2.1, 3.3, 4.3, ES2, ES3 - choose at compile) +* - Unique OpenGL abstraction layer (usable as standalone module): [rlgl] +* - Multiple Fonts formats supported (TTF, OTF, FNT, BDF, Sprite fonts) +* - Outstanding texture formats support, including compressed formats (DXT, ETC, ASTC) +* - Full 3d support for 3d Shapes, Models, Billboards, Heightmaps and more! +* - Flexible Materials system, supporting classic maps and PBR maps +* - Animated 3D models supported (skeletal bones animation) (IQM, M3D, GLTF) +* - Shaders support, including Model shaders and Postprocessing shaders +* - Powerful math module for Vector, Matrix and Quaternion operations: [raymath] +* - Audio loading and playing with streaming support (WAV, OGG, MP3, FLAC, QOA, XM, MOD) +* - VR stereo rendering with configurable HMD device parameters +* - Bindings to multiple programming languages available! +* +* NOTES: +* - One default Font is loaded on InitWindow()->LoadFontDefault() [core, text] +* - One default Texture2D is loaded on rlglInit(), 1x1 white pixel R8G8B8A8 [rlgl] (OpenGL 3.3 or ES2) +* - One default Shader is loaded on rlglInit()->rlLoadShaderDefault() [rlgl] (OpenGL 3.3 or ES2) +* - One default RenderBatch is loaded on rlglInit()->rlLoadRenderBatch() [rlgl] (OpenGL 3.3 or ES2) +* +* DEPENDENCIES (included): +* [rcore][GLFW] rglfw (Camilla Löwy - github.com/glfw/glfw) for window/context management and input +* [rcore][RGFW] rgfw (ColleagueRiley - github.com/ColleagueRiley/RGFW) for window/context management and input +* [rlgl] glad/glad_gles2 (David Herberth - github.com/Dav1dde/glad) for OpenGL 3.3 extensions loading +* [raudio] miniaudio (David Reid - github.com/mackron/miniaudio) for audio device/context management +* +* OPTIONAL DEPENDENCIES (included): +* [rcore] msf_gif (Miles Fogle) for GIF recording +* [rcore] sinfl (Micha Mettke) for DEFLATE decompression algorithm +* [rcore] sdefl (Micha Mettke) for DEFLATE compression algorithm +* [rcore] rprand (Ramon Snatamaria) for pseudo-random numbers generation +* [rtextures] qoi (Dominic Szablewski - https://phoboslab.org) for QOI image manage +* [rtextures] stb_image (Sean Barret) for images loading (BMP, TGA, PNG, JPEG, HDR...) +* [rtextures] stb_image_write (Sean Barret) for image writing (BMP, TGA, PNG, JPG) +* [rtextures] stb_image_resize2 (Sean Barret) for image resizing algorithms +* [rtextures] stb_perlin (Sean Barret) for Perlin Noise image generation +* [rtext] stb_truetype (Sean Barret) for ttf fonts loading +* [rtext] stb_rect_pack (Sean Barret) for rectangles packing +* [rmodels] par_shapes (Philip Rideout) for parametric 3d shapes generation +* [rmodels] tinyobj_loader_c (Syoyo Fujita) for models loading (OBJ, MTL) +* [rmodels] cgltf (Johannes Kuhlmann) for models loading (glTF) +* [rmodels] m3d (bzt) for models loading (M3D, https://bztsrc.gitlab.io/model3d) +* [rmodels] vox_loader (Johann Nadalutti) for models loading (VOX) +* [raudio] dr_wav (David Reid) for WAV audio file loading +* [raudio] dr_flac (David Reid) for FLAC audio file loading +* [raudio] dr_mp3 (David Reid) for MP3 audio file loading +* [raudio] stb_vorbis (Sean Barret) for OGG audio loading +* [raudio] jar_xm (Joshua Reisenauer) for XM audio module loading +* [raudio] jar_mod (Joshua Reisenauer) for MOD audio module loading +* [raudio] qoa (Dominic Szablewski - https://phoboslab.org) for QOA audio manage +* +* +* LICENSE: zlib/libpng +* +* raylib is licensed under an unmodified zlib/libpng license, which is an OSI-certified, +* BSD-like license that allows static linking with closed source software: +* +* Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYLIB_H +#define RAYLIB_H + +#include // Required for: va_list - Only used by TraceLogCallback + +#define RAYLIB_VERSION_MAJOR 5 +#define RAYLIB_VERSION_MINOR 6 +#define RAYLIB_VERSION_PATCH 0 +#define RAYLIB_VERSION "5.6-dev" + +// Function specifiers in case library is build/used as a shared library +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +// NOTE: visibility("default") attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) + #if defined(__TINYC__) + #define __declspec(x) __attribute__((x)) + #endif + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) + #elif defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) + #endif +#else + #if defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // We are building as a Unix shared library (.so/.dylib) + #endif +#endif + +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +//---------------------------------------------------------------------------------- +// Some basic Defines +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Allow custom memory allocators +// NOTE: Require recompiling raylib sources +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(ptr,sz) realloc(ptr,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(ptr) free(ptr) +#endif + +// NOTE: MSVC C++ compiler does not support compound literals (C99 feature) +// Plain structures in C++ (without constructors) can be initialized with { } +// This is called aggregate initialization (C++11 feature) +#if defined(__cplusplus) + #define CLITERAL(type) type +#else + #define CLITERAL(type) (type) +#endif + +// Some compilers (mostly macos clang) default to C++98, +// where aggregate initialization can't be used +// So, give a more clear error stating how to fix this +#if !defined(_MSC_VER) && (defined(__cplusplus) && __cplusplus < 201103L) + #error "C++11 or later is required. Add -std=c++11" +#endif + +// NOTE: We set some defines with some data types declared by raylib +// Other modules (raymath, rlgl) also require some of those types, so, +// to be able to use those other modules as standalone (not depending on raylib) +// this defines are very useful for internal check and avoid type (re)definitions +#define RL_COLOR_TYPE +#define RL_RECTANGLE_TYPE +#define RL_VECTOR2_TYPE +#define RL_VECTOR3_TYPE +#define RL_VECTOR4_TYPE +#define RL_QUATERNION_TYPE +#define RL_MATRIX_TYPE + +// Some Basic Colors +// NOTE: Custom raylib color palette for amazing visuals on WHITE background +#define _rl_LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray +#define _rl_GRAY CLITERAL(Color){ 130, 130, 130, 255 } // Gray +#define _rl_DARKGRAY CLITERAL(Color){ 80, 80, 80, 255 } // Dark Gray +#define _rl_YELLOW CLITERAL(Color){ 253, 249, 0, 255 } // Yellow +#define _rl_GOLD CLITERAL(Color){ 255, 203, 0, 255 } // Gold +#define _rl_ORANGE CLITERAL(Color){ 255, 161, 0, 255 } // Orange +#define _rl_PINK CLITERAL(Color){ 255, 109, 194, 255 } // Pink +#define _rl_RED CLITERAL(Color){ 230, 41, 55, 255 } // Red +#define _rl_MAROON CLITERAL(Color){ 190, 33, 55, 255 } // Maroon +#define _rl_GREEN CLITERAL(Color){ 0, 228, 48, 255 } // Green +#define _rl_LIME CLITERAL(Color){ 0, 158, 47, 255 } // Lime +#define _rl_DARKGREEN CLITERAL(Color){ 0, 117, 44, 255 } // Dark Green +#define _rl_SKYBLUE CLITERAL(Color){ 102, 191, 255, 255 } // Sky Blue +#define _rl_BLUE CLITERAL(Color){ 0, 121, 241, 255 } // Blue +#define _rl_DARKBLUE CLITERAL(Color){ 0, 82, 172, 255 } // Dark Blue +#define _rl_PURPLE CLITERAL(Color){ 200, 122, 255, 255 } // Purple +#define _rl_VIOLET CLITERAL(Color){ 135, 60, 190, 255 } // Violet +#define _rl_DARKPURPLE CLITERAL(Color){ 112, 31, 126, 255 } // Dark Purple +#define _rl_BEIGE CLITERAL(Color){ 211, 176, 131, 255 } // Beige +#define _rl_BROWN CLITERAL(Color){ 127, 106, 79, 255 } // Brown +#define _rl_DARKBROWN CLITERAL(Color){ 76, 63, 47, 255 } // Dark Brown + +#define _rl_WHITE CLITERAL(Color){ 255, 255, 255, 255 } // White +#define _rl_BLACK CLITERAL(Color){ 0, 0, 0, 255 } // Black +#define _rl_BLANK CLITERAL(Color){ 0, 0, 0, 0 } // Blank (Transparent) +#define _rl_MAGENTA CLITERAL(Color){ 255, 0, 255, 255 } // Magenta +#define _rl_RAYWHITE CLITERAL(Color){ 245, 245, 245, 255 } // My own White (raylib logo) + +#ifndef OPENPILOT_RAYLIB + #define LIGHTGRAY _rl_LIGHTGRAY + #define GRAY _rl_GRAY + #define DARKGRAY _rl_DARKGRAY + #define YELLOW _rl_YELLOW + #define GOLD _rl_GOLD + #define ORANGE _rl_ORANGE + #define PINK _rl_PINK + #define RED _rl_RED + #define MAROON _rl_MAROON + #define GREEN _rl_GREEN + #define LIME _rl_LIME + #define DARKGREEN _rl_DARKGREEN + #define SKYBLUE _rl_SKYBLUE + #define BLUE _rl_BLUE + #define DARKBLUE _rl_DARKBLUE + #define PURPLE _rl_PURPLE + #define VIOLET _rl_VIOLET + #define DARKPURPLE _rl_DARKBLUE + #define BEIGE _rl_BEIGE + #define BROWN _rl_BROWN + #define DARKBROWN _rl_DARKBROWN + + #define WHITE _rl_WHITE + #define BLACK _rl_BLACK + #define BLANK _rl_BLANK + #define MAGENTA _rl_MAGENTA + #define RAYWHITE _rl_RAYWHITE +#else + #define RAYLIB_LIGHTGRAY _rl_LIGHTGRAY + #define RAYLIB_GRAY _rl_GRAY + #define RAYLIB_DARKGRAY _rl_DARKGRAY + #define RAYLIB_YELLOW _rl_YELLOW + #define RAYLIB_GOLD _rl_GOLD + #define RAYLIB_ORANGE _rl_ORANGE + #define RAYLIB_PINK _rl_PINK + #define RAYLIB_RED _rl_RED + #define RAYLIB_MAROON _rl_MAROON + #define RAYLIB_GREEN _rl_GREEN + #define RAYLIB_LIME _rl_LIME + #define RAYLIB_DARKGREEN _rl_DARKGREEN + #define RAYLIB_SKYBLUE _rl_SKYBLUE + #define RAYLIB_BLUE _rl_BLUE + #define RAYLIB_DARKBLUE _rl_DARKBLUE + #define RAYLIB_PURPLE _rl_PURPLE + #define RAYLIB_VIOLET _rl_VIOLET + #define RAYLIB_DARKPURPLE _rl_DARKBLUE + #define RAYLIB_BEIGE _rl_BEIGE + #define RAYLIB_BROWN _rl_BROWN + #define RAYLIB_DARKBROWN _rl_DARKBROWN + + #define RAYLIB_WHITE _rl_WHITE + #define RAYLIB_BLACK _rl_BLACK + #define RAYLIB_BLANK _rl_BLANK + #define RAYLIB_MAGENTA _rl_MAGENTA + #define RAYLIB_RAYWHITE _rl_RAYWHITE +#endif + +//---------------------------------------------------------------------------------- +// Structures Definition +//---------------------------------------------------------------------------------- +// Boolean type +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include +#elif !defined(__cplusplus) && !defined(bool) + typedef enum bool { false = 0, true = !false } bool; + #define RL_BOOL_TYPE +#endif + +// Vector2, 2 components +typedef struct Vector2 { + float x; // Vector x component + float y; // Vector y component +} Vector2; + +// Vector3, 3 components +typedef struct Vector3 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component +} Vector3; + +// Vector4, 4 components +typedef struct Vector4 { + float x; // Vector x component + float y; // Vector y component + float z; // Vector z component + float w; // Vector w component +} Vector4; + +// Quaternion, 4 components (Vector4 alias) +typedef Vector4 Quaternion; + +// Matrix, 4x4 components, column major, OpenGL style, right-handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; + +// Color, 4 components, R8G8B8A8 (32bit) +typedef struct Color { + unsigned char r; // Color red value + unsigned char g; // Color green value + unsigned char b; // Color blue value + unsigned char a; // Color alpha value +} Color; + +// Rectangle, 4 components +typedef struct Rectangle { + float x; // Rectangle top-left corner position x + float y; // Rectangle top-left corner position y + float width; // Rectangle width + float height; // Rectangle height +} Rectangle; + +// Image, pixel data stored in CPU memory (RAM) +typedef struct Image { + void *data; // Image raw data + int width; // Image base width + int height; // Image base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Image; + +// Texture, tex data stored in GPU memory (VRAM) +typedef struct Texture { + unsigned int id; // OpenGL texture id + int width; // Texture base width + int height; // Texture base height + int mipmaps; // Mipmap levels, 1 by default + int format; // Data format (PixelFormat type) +} Texture; + +// Texture2D, same as Texture +typedef Texture Texture2D; + +// TextureCubemap, same as Texture +typedef Texture TextureCubemap; + +// RenderTexture, fbo for texture rendering +typedef struct RenderTexture { + unsigned int id; // OpenGL framebuffer object id + Texture texture; // Color buffer attachment texture + Texture depth; // Depth buffer attachment texture +} RenderTexture; + +// RenderTexture2D, same as RenderTexture +typedef RenderTexture RenderTexture2D; + +// NPatchInfo, n-patch layout info +typedef struct NPatchInfo { + Rectangle source; // Texture source rectangle + int left; // Left border offset + int top; // Top border offset + int right; // Right border offset + int bottom; // Bottom border offset + int layout; // Layout of the n-patch: 3x3, 1x3 or 3x1 +} NPatchInfo; + +// GlyphInfo, font characters glyphs info +typedef struct GlyphInfo { + int value; // Character value (Unicode) + int offsetX; // Character offset X when drawing + int offsetY; // Character offset Y when drawing + int advanceX; // Character advance position X + Image image; // Character image data +} GlyphInfo; + +// Font, font texture and GlyphInfo array data +typedef struct Font { + int baseSize; // Base size (default chars height) + int glyphCount; // Number of glyph characters + int glyphPadding; // Padding around the glyph characters + Texture2D texture; // Texture atlas containing the glyphs + Rectangle *recs; // Rectangles in texture for the glyphs + GlyphInfo *glyphs; // Glyphs info data +} Font; + +// Camera, defines position/orientation in 3d space +typedef struct Camera3D { + Vector3 position; // Camera position + Vector3 target; // Camera target it looks-at + Vector3 up; // Camera up vector (rotation over its axis) + float fovy; // Camera field-of-view aperture in Y (degrees) in perspective, used as near plane width in orthographic + int projection; // Camera projection: CAMERA_PERSPECTIVE or CAMERA_ORTHOGRAPHIC +} Camera3D; + +typedef Camera3D Camera; // Camera type fallback, defaults to Camera3D + +// Camera2D, defines position/orientation in 2d space +typedef struct Camera2D { + Vector2 offset; // Camera offset (displacement from target) + Vector2 target; // Camera target (rotation and zoom origin) + float rotation; // Camera rotation in degrees + float zoom; // Camera zoom (scaling), should be 1.0f by default +} Camera2D; + +// Mesh, vertex data and vao/vbo +typedef struct Mesh { + int vertexCount; // Number of vertices stored in arrays + int triangleCount; // Number of triangles stored (indexed or not) + + // Vertex attributes data + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *texcoords2; // Vertex texture second coordinates (UV - 2 components per vertex) (shader-location = 5) + float *normals; // Vertex normals (XYZ - 3 components per vertex) (shader-location = 2) + float *tangents; // Vertex tangents (XYZW - 4 components per vertex) (shader-location = 4) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) + + // Animation vertex data + float *animVertices; // Animated vertex positions (after bones transformations) + float *animNormals; // Animated normals (after bones transformations) + unsigned char *boneIds; // Vertex bone ids, max 255 bone ids, up to 4 bones influence by vertex (skinning) (shader-location = 6) + float *boneWeights; // Vertex bone weight, up to 4 bones influence by vertex (skinning) (shader-location = 7) + Matrix *boneMatrices; // Bones animated transformation matrices + int boneCount; // Number of bones + + // OpenGL identifiers + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int *vboId; // OpenGL Vertex Buffer Objects id (default vertex data) +} Mesh; + +// Shader +typedef struct Shader { + unsigned int id; // Shader program id + int *locs; // Shader locations array (RL_MAX_SHADER_LOCATIONS) +} Shader; + +// MaterialMap +typedef struct MaterialMap { + Texture2D texture; // Material map texture + Color color; // Material map color + float value; // Material map value +} MaterialMap; + +// Material, includes shader and maps +typedef struct Material { + Shader shader; // Material shader + MaterialMap *maps; // Material maps array (MAX_MATERIAL_MAPS) + float params[4]; // Material generic parameters (if required) +} Material; + +// Transform, vertex transformation data +typedef struct Transform { + Vector3 translation; // Translation + Quaternion rotation; // Rotation + Vector3 scale; // Scale +} Transform; + +// Bone, skeletal animation bone +typedef struct BoneInfo { + char name[32]; // Bone name + int parent; // Bone parent +} BoneInfo; + +// Model, meshes, materials and animation data +typedef struct Model { + Matrix transform; // Local transform matrix + + int meshCount; // Number of meshes + int materialCount; // Number of materials + Mesh *meshes; // Meshes array + Material *materials; // Materials array + int *meshMaterial; // Mesh material number + + // Animation data + int boneCount; // Number of bones + BoneInfo *bones; // Bones information (skeleton) + Transform *bindPose; // Bones base transformation (pose) +} Model; + +// ModelAnimation +typedef struct ModelAnimation { + int boneCount; // Number of bones + int frameCount; // Number of animation frames + BoneInfo *bones; // Bones information (skeleton) + Transform **framePoses; // Poses array by frame + char name[32]; // Animation name +} ModelAnimation; + +// Ray, ray for raycasting +typedef struct Ray { + Vector3 position; // Ray position (origin) + Vector3 direction; // Ray direction (normalized) +} Ray; + +// RayCollision, ray hit information +typedef struct RayCollision { + bool hit; // Did the ray hit something? + float distance; // Distance to the nearest hit + Vector3 point; // Point of the nearest hit + Vector3 normal; // Surface normal of hit +} RayCollision; + +// BoundingBox +typedef struct BoundingBox { + Vector3 min; // Minimum vertex box-corner + Vector3 max; // Maximum vertex box-corner +} BoundingBox; + +// Wave, audio wave data +typedef struct Wave { + unsigned int frameCount; // Total number of frames (considering channels) + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) + void *data; // Buffer data pointer +} Wave; + +// Opaque structs declaration +// NOTE: Actual structs are defined internally in raudio module +typedef struct rAudioBuffer rAudioBuffer; +typedef struct rAudioProcessor rAudioProcessor; + +// AudioStream, custom audio stream +typedef struct AudioStream { + rAudioBuffer *buffer; // Pointer to internal data used by the audio system + rAudioProcessor *processor; // Pointer to internal data processor, useful for audio effects + + unsigned int sampleRate; // Frequency (samples per second) + unsigned int sampleSize; // Bit depth (bits per sample): 8, 16, 32 (24 not supported) + unsigned int channels; // Number of channels (1-mono, 2-stereo, ...) +} AudioStream; + +// Sound +typedef struct Sound { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) +} Sound; + +// Music, audio stream, anything longer than ~10 seconds should be streamed +typedef struct Music { + AudioStream stream; // Audio stream + unsigned int frameCount; // Total number of frames (considering channels) + bool looping; // Music looping enable + + int ctxType; // Type of music context (audio filetype) + void *ctxData; // Audio context data, depends on type +} Music; + +// VrDeviceInfo, Head-Mounted-Display device parameters +typedef struct VrDeviceInfo { + int hResolution; // Horizontal resolution in pixels + int vResolution; // Vertical resolution in pixels + float hScreenSize; // Horizontal size in meters + float vScreenSize; // Vertical size in meters + float eyeToScreenDistance; // Distance between eye and display in meters + float lensSeparationDistance; // Lens separation distance in meters + float interpupillaryDistance; // IPD (distance between pupils) in meters + float lensDistortionValues[4]; // Lens distortion constant parameters + float chromaAbCorrection[4]; // Chromatic aberration correction parameters +} VrDeviceInfo; + +// VrStereoConfig, VR stereo rendering configuration for simulator +typedef struct VrStereoConfig { + Matrix projection[2]; // VR projection matrices (per eye) + Matrix viewOffset[2]; // VR view offset matrices (per eye) + float leftLensCenter[2]; // VR left lens center + float rightLensCenter[2]; // VR right lens center + float leftScreenCenter[2]; // VR left screen center + float rightScreenCenter[2]; // VR right screen center + float scale[2]; // VR distortion scale + float scaleIn[2]; // VR distortion scale in +} VrStereoConfig; + +// File path list +typedef struct FilePathList { + unsigned int capacity; // Filepaths max entries + unsigned int count; // Filepaths entries count + char **paths; // Filepaths entries +} FilePathList; + +// Automation event +typedef struct AutomationEvent { + unsigned int frame; // Event frame + unsigned int type; // Event type (AutomationEventType) + int params[4]; // Event parameters (if required) +} AutomationEvent; + +// Automation event list +typedef struct AutomationEventList { + unsigned int capacity; // Events max entries (MAX_AUTOMATION_EVENTS) + unsigned int count; // Events entries count + AutomationEvent *events; // Events entries +} AutomationEventList; + +//---------------------------------------------------------------------------------- +// Enumerators Definition +//---------------------------------------------------------------------------------- +// System/Window config flags +// NOTE: Every bit registers one state (use it with bit masks) +// By default all flags are set to 0 +typedef enum { + FLAG_VSYNC_HINT = 0x00000040, // Set to try enabling V-Sync on GPU + FLAG_FULLSCREEN_MODE = 0x00000002, // Set to run program in fullscreen + FLAG_WINDOW_RESIZABLE = 0x00000004, // Set to allow resizable window + FLAG_WINDOW_UNDECORATED = 0x00000008, // Set to disable window decoration (frame and buttons) + FLAG_WINDOW_HIDDEN = 0x00000080, // Set to hide window + FLAG_WINDOW_MINIMIZED = 0x00000200, // Set to minimize window (iconify) + FLAG_WINDOW_MAXIMIZED = 0x00000400, // Set to maximize window (expanded to monitor) + FLAG_WINDOW_UNFOCUSED = 0x00000800, // Set to window non focused + FLAG_WINDOW_TOPMOST = 0x00001000, // Set to window always on top + FLAG_WINDOW_ALWAYS_RUN = 0x00000100, // Set to allow windows running while minimized + FLAG_WINDOW_TRANSPARENT = 0x00000010, // Set to allow transparent framebuffer + FLAG_WINDOW_HIGHDPI = 0x00002000, // Set to support HighDPI + FLAG_WINDOW_MOUSE_PASSTHROUGH = 0x00004000, // Set to support mouse passthrough, only supported when FLAG_WINDOW_UNDECORATED + FLAG_BORDERLESS_WINDOWED_MODE = 0x00008000, // Set to run program in borderless windowed mode + FLAG_MSAA_4X_HINT = 0x00000020, // Set to try enabling MSAA 4X + FLAG_INTERLACED_HINT = 0x00010000 // Set to try enabling interlaced video format (for V3D) +} ConfigFlags; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + LOG_ALL = 0, // Display all logs + LOG_TRACE, // Trace logging, intended for internal use only + LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + LOG_INFO, // Info logging, used for program execution info + LOG_WARNING, // Warning logging, used on recoverable failures + LOG_ERROR, // Error logging, used on unrecoverable failures + LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + LOG_NONE // Disable logging +} TraceLogLevel; + +// Keyboard keys (US keyboard layout) +// NOTE: Use GetKeyPressed() to allow redefining +// required keys for alternative layouts +typedef enum { + KEY_NULL = 0, // Key: NULL, used for no key pressed + // Alphanumeric keys + KEY_APOSTROPHE = 39, // Key: ' + KEY_COMMA = 44, // Key: , + KEY_MINUS = 45, // Key: - + KEY_PERIOD = 46, // Key: . + KEY_SLASH = 47, // Key: / + KEY_ZERO = 48, // Key: 0 + KEY_ONE = 49, // Key: 1 + KEY_TWO = 50, // Key: 2 + KEY_THREE = 51, // Key: 3 + KEY_FOUR = 52, // Key: 4 + KEY_FIVE = 53, // Key: 5 + KEY_SIX = 54, // Key: 6 + KEY_SEVEN = 55, // Key: 7 + KEY_EIGHT = 56, // Key: 8 + KEY_NINE = 57, // Key: 9 + KEY_SEMICOLON = 59, // Key: ; + KEY_EQUAL = 61, // Key: = + KEY_A = 65, // Key: A | a + KEY_B = 66, // Key: B | b + KEY_C = 67, // Key: C | c + KEY_D = 68, // Key: D | d + KEY_E = 69, // Key: E | e + KEY_F = 70, // Key: F | f + KEY_G = 71, // Key: G | g + KEY_H = 72, // Key: H | h + KEY_I = 73, // Key: I | i + KEY_J = 74, // Key: J | j + KEY_K = 75, // Key: K | k + KEY_L = 76, // Key: L | l + KEY_M = 77, // Key: M | m + KEY_N = 78, // Key: N | n + KEY_O = 79, // Key: O | o + KEY_P = 80, // Key: P | p + KEY_Q = 81, // Key: Q | q + KEY_R = 82, // Key: R | r + KEY_S = 83, // Key: S | s + KEY_T = 84, // Key: T | t + KEY_U = 85, // Key: U | u + KEY_V = 86, // Key: V | v + KEY_W = 87, // Key: W | w + KEY_X = 88, // Key: X | x + KEY_Y = 89, // Key: Y | y + KEY_Z = 90, // Key: Z | z + KEY_LEFT_BRACKET = 91, // Key: [ + KEY_BACKSLASH = 92, // Key: '\' + KEY_RIGHT_BRACKET = 93, // Key: ] + KEY_GRAVE = 96, // Key: ` + // Function keys + KEY_SPACE = 32, // Key: Space + KEY_ESCAPE = 256, // Key: Esc + KEY_ENTER = 257, // Key: Enter + KEY_TAB = 258, // Key: Tab + KEY_BACKSPACE = 259, // Key: Backspace + KEY_INSERT = 260, // Key: Ins + KEY_DELETE = 261, // Key: Del + KEY_RIGHT = 262, // Key: Cursor right + KEY_LEFT = 263, // Key: Cursor left + KEY_DOWN = 264, // Key: Cursor down + KEY_UP = 265, // Key: Cursor up + KEY_PAGE_UP = 266, // Key: Page up + KEY_PAGE_DOWN = 267, // Key: Page down + KEY_HOME = 268, // Key: Home + KEY_END = 269, // Key: End + KEY_CAPS_LOCK = 280, // Key: Caps lock + KEY_SCROLL_LOCK = 281, // Key: Scroll down + KEY_NUM_LOCK = 282, // Key: Num lock + KEY_PRINT_SCREEN = 283, // Key: Print screen + KEY_PAUSE = 284, // Key: Pause + KEY_F1 = 290, // Key: F1 + KEY_F2 = 291, // Key: F2 + KEY_F3 = 292, // Key: F3 + KEY_F4 = 293, // Key: F4 + KEY_F5 = 294, // Key: F5 + KEY_F6 = 295, // Key: F6 + KEY_F7 = 296, // Key: F7 + KEY_F8 = 297, // Key: F8 + KEY_F9 = 298, // Key: F9 + KEY_F10 = 299, // Key: F10 + KEY_F11 = 300, // Key: F11 + KEY_F12 = 301, // Key: F12 + KEY_LEFT_SHIFT = 340, // Key: Shift left + KEY_LEFT_CONTROL = 341, // Key: Control left + KEY_LEFT_ALT = 342, // Key: Alt left + KEY_LEFT_SUPER = 343, // Key: Super left + KEY_RIGHT_SHIFT = 344, // Key: Shift right + KEY_RIGHT_CONTROL = 345, // Key: Control right + KEY_RIGHT_ALT = 346, // Key: Alt right + KEY_RIGHT_SUPER = 347, // Key: Super right + KEY_KB_MENU = 348, // Key: KB menu + // Keypad keys + KEY_KP_0 = 320, // Key: Keypad 0 + KEY_KP_1 = 321, // Key: Keypad 1 + KEY_KP_2 = 322, // Key: Keypad 2 + KEY_KP_3 = 323, // Key: Keypad 3 + KEY_KP_4 = 324, // Key: Keypad 4 + KEY_KP_5 = 325, // Key: Keypad 5 + KEY_KP_6 = 326, // Key: Keypad 6 + KEY_KP_7 = 327, // Key: Keypad 7 + KEY_KP_8 = 328, // Key: Keypad 8 + KEY_KP_9 = 329, // Key: Keypad 9 + KEY_KP_DECIMAL = 330, // Key: Keypad . + KEY_KP_DIVIDE = 331, // Key: Keypad / + KEY_KP_MULTIPLY = 332, // Key: Keypad * + KEY_KP_SUBTRACT = 333, // Key: Keypad - + KEY_KP_ADD = 334, // Key: Keypad + + KEY_KP_ENTER = 335, // Key: Keypad Enter + KEY_KP_EQUAL = 336, // Key: Keypad = + // Android key buttons + KEY_BACK = 4, // Key: Android back button + KEY_MENU = 5, // Key: Android menu button + KEY_VOLUME_UP = 24, // Key: Android volume up button + KEY_VOLUME_DOWN = 25 // Key: Android volume down button +} KeyboardKey; + +// Add backwards compatibility support for deprecated names +#define MOUSE_LEFT_BUTTON MOUSE_BUTTON_LEFT +#define MOUSE_RIGHT_BUTTON MOUSE_BUTTON_RIGHT +#define MOUSE_MIDDLE_BUTTON MOUSE_BUTTON_MIDDLE + +// Mouse buttons +typedef enum { + MOUSE_BUTTON_LEFT = 0, // Mouse button left + MOUSE_BUTTON_RIGHT = 1, // Mouse button right + MOUSE_BUTTON_MIDDLE = 2, // Mouse button middle (pressed wheel) + MOUSE_BUTTON_SIDE = 3, // Mouse button side (advanced mouse device) + MOUSE_BUTTON_EXTRA = 4, // Mouse button extra (advanced mouse device) + MOUSE_BUTTON_FORWARD = 5, // Mouse button forward (advanced mouse device) + MOUSE_BUTTON_BACK = 6, // Mouse button back (advanced mouse device) +} MouseButton; + +// Mouse cursor +typedef enum { + MOUSE_CURSOR_DEFAULT = 0, // Default pointer shape + MOUSE_CURSOR_ARROW = 1, // Arrow shape + MOUSE_CURSOR_IBEAM = 2, // Text writing cursor shape + MOUSE_CURSOR_CROSSHAIR = 3, // Cross shape + MOUSE_CURSOR_POINTING_HAND = 4, // Pointing hand cursor + MOUSE_CURSOR_RESIZE_EW = 5, // Horizontal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NS = 6, // Vertical resize/move arrow shape + MOUSE_CURSOR_RESIZE_NWSE = 7, // Top-left to bottom-right diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_NESW = 8, // The top-right to bottom-left diagonal resize/move arrow shape + MOUSE_CURSOR_RESIZE_ALL = 9, // The omnidirectional resize/move cursor shape + MOUSE_CURSOR_NOT_ALLOWED = 10 // The operation-not-allowed shape +} MouseCursor; + +// Gamepad buttons +typedef enum { + GAMEPAD_BUTTON_UNKNOWN = 0, // Unknown button, just for error checking + GAMEPAD_BUTTON_LEFT_FACE_UP, // Gamepad left DPAD up button + GAMEPAD_BUTTON_LEFT_FACE_RIGHT, // Gamepad left DPAD right button + GAMEPAD_BUTTON_LEFT_FACE_DOWN, // Gamepad left DPAD down button + GAMEPAD_BUTTON_LEFT_FACE_LEFT, // Gamepad left DPAD left button + GAMEPAD_BUTTON_RIGHT_FACE_UP, // Gamepad right button up (i.e. PS3: Triangle, Xbox: Y) + GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, // Gamepad right button right (i.e. PS3: Circle, Xbox: B) + GAMEPAD_BUTTON_RIGHT_FACE_DOWN, // Gamepad right button down (i.e. PS3: Cross, Xbox: A) + GAMEPAD_BUTTON_RIGHT_FACE_LEFT, // Gamepad right button left (i.e. PS3: Square, Xbox: X) + GAMEPAD_BUTTON_LEFT_TRIGGER_1, // Gamepad top/back trigger left (first), it could be a trailing button + GAMEPAD_BUTTON_LEFT_TRIGGER_2, // Gamepad top/back trigger left (second), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_1, // Gamepad top/back trigger right (first), it could be a trailing button + GAMEPAD_BUTTON_RIGHT_TRIGGER_2, // Gamepad top/back trigger right (second), it could be a trailing button + GAMEPAD_BUTTON_MIDDLE_LEFT, // Gamepad center buttons, left one (i.e. PS3: Select) + GAMEPAD_BUTTON_MIDDLE, // Gamepad center buttons, middle one (i.e. PS3: PS, Xbox: XBOX) + GAMEPAD_BUTTON_MIDDLE_RIGHT, // Gamepad center buttons, right one (i.e. PS3: Start) + GAMEPAD_BUTTON_LEFT_THUMB, // Gamepad joystick pressed button left + GAMEPAD_BUTTON_RIGHT_THUMB // Gamepad joystick pressed button right +} GamepadButton; + +// Gamepad axis +typedef enum { + GAMEPAD_AXIS_LEFT_X = 0, // Gamepad left stick X axis + GAMEPAD_AXIS_LEFT_Y = 1, // Gamepad left stick Y axis + GAMEPAD_AXIS_RIGHT_X = 2, // Gamepad right stick X axis + GAMEPAD_AXIS_RIGHT_Y = 3, // Gamepad right stick Y axis + GAMEPAD_AXIS_LEFT_TRIGGER = 4, // Gamepad back trigger left, pressure level: [1..-1] + GAMEPAD_AXIS_RIGHT_TRIGGER = 5 // Gamepad back trigger right, pressure level: [1..-1] +} GamepadAxis; + +// Material map index +typedef enum { + MATERIAL_MAP_ALBEDO = 0, // Albedo material (same as: MATERIAL_MAP_DIFFUSE) + MATERIAL_MAP_METALNESS, // Metalness material (same as: MATERIAL_MAP_SPECULAR) + MATERIAL_MAP_NORMAL, // Normal material + MATERIAL_MAP_ROUGHNESS, // Roughness material + MATERIAL_MAP_OCCLUSION, // Ambient occlusion material + MATERIAL_MAP_EMISSION, // Emission material + MATERIAL_MAP_HEIGHT, // Heightmap material + MATERIAL_MAP_CUBEMAP, // Cubemap material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_IRRADIANCE, // Irradiance material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_PREFILTER, // Prefilter material (NOTE: Uses GL_TEXTURE_CUBE_MAP) + MATERIAL_MAP_BRDF // Brdf material +} MaterialMapIndex; + +#define MATERIAL_MAP_DIFFUSE MATERIAL_MAP_ALBEDO +#define MATERIAL_MAP_SPECULAR MATERIAL_MAP_METALNESS + +// Shader location index +typedef enum { + SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: SHADER_LOC_MAP_DIFFUSE) + SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: SHADER_LOC_MAP_SPECULAR) + SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + SHADER_LOC_MAP_BRDF, // Shader location: sampler2d texture: brdf + SHADER_LOC_VERTEX_BONEIDS, // Shader location: vertex attribute: boneIds + SHADER_LOC_VERTEX_BONEWEIGHTS, // Shader location: vertex attribute: boneWeights + SHADER_LOC_BONE_MATRICES // Shader location: array of matrices uniform: boneMatrices +} ShaderLocationIndex; + +#define SHADER_LOC_MAP_DIFFUSE SHADER_LOC_MAP_ALBEDO +#define SHADER_LOC_MAP_SPECULAR SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + SHADER_UNIFORM_INT, // Shader uniform type: int + SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} ShaderUniformDataType; + +// Shader attribute data types +typedef enum { + SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} ShaderAttributeDataType; + +// Pixel formats +// NOTE: Support depends on OpenGL version and platform +typedef enum { + PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) + PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) + PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} PixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation + TEXTURE_FILTER_BILINEAR, // Linear filtering + TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} TextureFilter; + +// Texture parameters: wrap mode +typedef enum { + TEXTURE_WRAP_REPEAT = 0, // Repeats texture in tiled mode + TEXTURE_WRAP_CLAMP, // Clamps texture to edge pixel in tiled mode + TEXTURE_WRAP_MIRROR_REPEAT, // Mirrors and repeats the texture in tiled mode + TEXTURE_WRAP_MIRROR_CLAMP // Mirrors and clamps to border the texture in tiled mode +} TextureWrap; + +// Cubemap layouts +typedef enum { + CUBEMAP_LAYOUT_AUTO_DETECT = 0, // Automatically detect layout type + CUBEMAP_LAYOUT_LINE_VERTICAL, // Layout is defined by a vertical line with faces + CUBEMAP_LAYOUT_LINE_HORIZONTAL, // Layout is defined by a horizontal line with faces + CUBEMAP_LAYOUT_CROSS_THREE_BY_FOUR, // Layout is defined by a 3x4 cross with cubemap faces + CUBEMAP_LAYOUT_CROSS_FOUR_BY_THREE // Layout is defined by a 4x3 cross with cubemap faces +} CubemapLayout; + +// Font type, defines generation method +typedef enum { + FONT_DEFAULT = 0, // Default font generation, anti-aliased + FONT_BITMAP, // Bitmap font generation, no anti-aliasing + FONT_SDF // SDF font generation, requires external shader +} FontType; + +// Color blending modes (pre-defined) +typedef enum { + BLEND_ALPHA = 0, // Blend textures considering alpha (default) + BLEND_ADDITIVE, // Blend textures adding colors + BLEND_MULTIPLIED, // Blend textures multiplying colors + BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + BLEND_CUSTOM_SEPARATE // Blend textures using custom rgb/alpha separate src/dst factors (use rlSetBlendFactorsSeparate()) +} BlendMode; + +// Gesture +// NOTE: Provided as bit-wise flags to enable only desired gestures +typedef enum { + GESTURE_NONE = 0, // No gesture + GESTURE_TAP = 1, // Tap gesture + GESTURE_DOUBLETAP = 2, // Double tap gesture + GESTURE_HOLD = 4, // Hold gesture + GESTURE_DRAG = 8, // Drag gesture + GESTURE_SWIPE_RIGHT = 16, // Swipe right gesture + GESTURE_SWIPE_LEFT = 32, // Swipe left gesture + GESTURE_SWIPE_UP = 64, // Swipe up gesture + GESTURE_SWIPE_DOWN = 128, // Swipe down gesture + GESTURE_PINCH_IN = 256, // Pinch in gesture + GESTURE_PINCH_OUT = 512 // Pinch out gesture +} Gesture; + +// Camera system modes +typedef enum { + CAMERA_CUSTOM = 0, // Camera custom, controlled by user (UpdateCamera() does nothing) + CAMERA_FREE, // Camera free mode + CAMERA_ORBITAL, // Camera orbital, around target, zoom supported + CAMERA_FIRST_PERSON, // Camera first person + CAMERA_THIRD_PERSON // Camera third person +} CameraMode; + +// Camera projection +typedef enum { + CAMERA_PERSPECTIVE = 0, // Perspective projection + CAMERA_ORTHOGRAPHIC // Orthographic projection +} CameraProjection; + +// N-patch layout +typedef enum { + NPATCH_NINE_PATCH = 0, // Npatch layout: 3x3 tiles + NPATCH_THREE_PATCH_VERTICAL, // Npatch layout: 1x3 tiles + NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles +} NPatchLayout; + +// Callbacks to hook some internal functions +// WARNING: These callbacks are intended for advanced users +typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages +typedef unsigned char *(*LoadFileDataCallback)(const char *fileName, int *dataSize); // FileIO: Load binary data +typedef bool (*SaveFileDataCallback)(const char *fileName, void *data, int dataSize); // FileIO: Save binary data +typedef char *(*LoadFileTextCallback)(const char *fileName); // FileIO: Load text data +typedef bool (*SaveFileTextCallback)(const char *fileName, char *text); // FileIO: Save text data + +//------------------------------------------------------------------------------------ +// Global Variables Definition +//------------------------------------------------------------------------------------ +// It's lonely here... + +//------------------------------------------------------------------------------------ +// Window and Graphics Device Functions (Module: core) +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +// Window-related functions +RLAPI void InitWindow(int width, int height, const char *title); // Initialize window and OpenGL context +RLAPI void CloseWindow(void); // Close window and unload OpenGL context +RLAPI bool WindowShouldClose(void); // Check if application should close (KEY_ESCAPE pressed or windows close icon clicked) +RLAPI bool IsWindowReady(void); // Check if window has been initialized successfully +RLAPI bool IsWindowFullscreen(void); // Check if window is currently fullscreen +RLAPI bool IsWindowHidden(void); // Check if window is currently hidden +RLAPI bool IsWindowMinimized(void); // Check if window is currently minimized +RLAPI bool IsWindowMaximized(void); // Check if window is currently maximized +RLAPI bool IsWindowFocused(void); // Check if window is currently focused +RLAPI bool IsWindowResized(void); // Check if window has been resized last frame +RLAPI bool IsWindowState(unsigned int flag); // Check if one specific window flag is enabled +RLAPI void SetWindowState(unsigned int flags); // Set window configuration state using flags +RLAPI void ClearWindowState(unsigned int flags); // Clear window configuration state flags +RLAPI void ToggleFullscreen(void); // Toggle window state: fullscreen/windowed, resizes monitor to match window resolution +RLAPI void ToggleBorderlessWindowed(void); // Toggle window state: borderless windowed, resizes window to match monitor resolution +RLAPI void MaximizeWindow(void); // Set window state: maximized, if resizable +RLAPI void MinimizeWindow(void); // Set window state: minimized, if resizable +RLAPI void RestoreWindow(void); // Set window state: not minimized/maximized +RLAPI void SetWindowIcon(Image image); // Set icon for window (single image, RGBA 32bit) +RLAPI void SetWindowIcons(Image *images, int count); // Set icon for window (multiple images, RGBA 32bit) +RLAPI void SetWindowTitle(const char *title); // Set title for window +RLAPI void SetWindowPosition(int x, int y); // Set window position on screen +RLAPI void SetWindowMonitor(int monitor); // Set monitor for the current window +RLAPI void SetWindowMinSize(int width, int height); // Set window minimum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowMaxSize(int width, int height); // Set window maximum dimensions (for FLAG_WINDOW_RESIZABLE) +RLAPI void SetWindowSize(int width, int height); // Set window dimensions +RLAPI void SetWindowOpacity(float opacity); // Set window opacity [0.0f..1.0f] +RLAPI void SetWindowFocused(void); // Set window focused +RLAPI void *GetWindowHandle(void); // Get native window handle +RLAPI int GetScreenWidth(void); // Get current screen width +RLAPI int GetScreenHeight(void); // Get current screen height +RLAPI int GetRenderWidth(void); // Get current render width (it considers HiDPI) +RLAPI int GetRenderHeight(void); // Get current render height (it considers HiDPI) +RLAPI int GetMonitorCount(void); // Get number of connected monitors +RLAPI int GetCurrentMonitor(void); // Get current monitor where window is placed +RLAPI Vector2 GetMonitorPosition(int monitor); // Get specified monitor position +RLAPI int GetMonitorWidth(int monitor); // Get specified monitor width (current video mode used by monitor) +RLAPI int GetMonitorHeight(int monitor); // Get specified monitor height (current video mode used by monitor) +RLAPI int GetMonitorPhysicalWidth(int monitor); // Get specified monitor physical width in millimetres +RLAPI int GetMonitorPhysicalHeight(int monitor); // Get specified monitor physical height in millimetres +RLAPI int GetMonitorRefreshRate(int monitor); // Get specified monitor refresh rate +RLAPI Vector2 GetWindowPosition(void); // Get window position XY on monitor +RLAPI Vector2 GetWindowScaleDPI(void); // Get window scale DPI factor +RLAPI const char *GetMonitorName(int monitor); // Get the human-readable, UTF-8 encoded name of the specified monitor +RLAPI void SetClipboardText(const char *text); // Set clipboard text content +RLAPI const char *GetClipboardText(void); // Get clipboard text content +RLAPI Image GetClipboardImage(void); // Get clipboard image content +RLAPI void EnableEventWaiting(void); // Enable waiting for events on EndDrawing(), no automatic event polling +RLAPI void DisableEventWaiting(void); // Disable waiting for events on EndDrawing(), automatic events polling + +// Cursor-related functions +RLAPI void ShowCursor(void); // Shows cursor +RLAPI void HideCursor(void); // Hides cursor +RLAPI bool IsCursorHidden(void); // Check if cursor is not visible +RLAPI void EnableCursor(void); // Enables cursor (unlock cursor) +RLAPI void DisableCursor(void); // Disables cursor (lock cursor) +RLAPI bool IsCursorOnScreen(void); // Check if cursor is on the screen + +// Drawing-related functions +RLAPI void ClearBackground(Color color); // Set background color (framebuffer clear color) +RLAPI void BeginDrawing(void); // Setup canvas (framebuffer) to start drawing +RLAPI void EndDrawing(void); // End canvas drawing and swap buffers (double buffering) +RLAPI void BeginMode2D(Camera2D camera); // Begin 2D mode with custom camera (2D) +RLAPI void EndMode2D(void); // Ends 2D mode with custom camera +RLAPI void BeginMode3D(Camera3D camera); // Begin 3D mode with custom camera (3D) +RLAPI void EndMode3D(void); // Ends 3D mode and returns to default 2D orthographic mode +RLAPI void BeginTextureMode(RenderTexture2D target); // Begin drawing to render texture +RLAPI void EndTextureMode(void); // Ends drawing to render texture +RLAPI void BeginShaderMode(Shader shader); // Begin custom shader drawing +RLAPI void EndShaderMode(void); // End custom shader drawing (use default shader) +RLAPI void BeginBlendMode(int mode); // Begin blending mode (alpha, additive, multiplied, subtract, custom) +RLAPI void EndBlendMode(void); // End blending mode (reset to default: alpha blending) +RLAPI void BeginScissorMode(int x, int y, int width, int height); // Begin scissor mode (define screen area for following drawing) +RLAPI void EndScissorMode(void); // End scissor mode +RLAPI void BeginVrStereoMode(VrStereoConfig config); // Begin stereo rendering (requires VR simulator) +RLAPI void EndVrStereoMode(void); // End stereo rendering (requires VR simulator) + +// VR stereo config functions for VR simulator +RLAPI VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device); // Load VR stereo config for VR simulator device parameters +RLAPI void UnloadVrStereoConfig(VrStereoConfig config); // Unload VR stereo config + +// Shader management functions +// NOTE: Shader functionality is not available on OpenGL 1.1 +RLAPI Shader LoadShader(const char *vsFileName, const char *fsFileName); // Load shader from files and bind default locations +RLAPI Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode); // Load shader from code strings and bind default locations +RLAPI bool IsShaderValid(Shader shader); // Check if a shader is valid (loaded on GPU) +RLAPI int GetShaderLocation(Shader shader, const char *uniformName); // Get shader uniform location +RLAPI int GetShaderLocationAttrib(Shader shader, const char *attribName); // Get shader attribute location +RLAPI void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType); // Set shader uniform value +RLAPI void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count); // Set shader uniform value vector +RLAPI void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat); // Set shader uniform value (matrix 4x4) +RLAPI void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture); // Set shader uniform value for texture (sampler2d) +RLAPI void UnloadShader(Shader shader); // Unload shader from GPU memory (VRAM) + +// Screen-space-related functions +#define GetMouseRay GetScreenToWorldRay // Compatibility hack for previous raylib versions +RLAPI Ray GetScreenToWorldRay(Vector2 position, Camera camera); // Get a ray trace from screen position (i.e mouse) +RLAPI Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height); // Get a ray trace from screen position (i.e mouse) in a viewport +RLAPI Vector2 GetWorldToScreen(Vector3 position, Camera camera); // Get the screen space position for a 3d world space position +RLAPI Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height); // Get size position for a 3d world space position +RLAPI Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera); // Get the screen space position for a 2d camera world space position +RLAPI Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera); // Get the world space position for a 2d camera screen space position +RLAPI Matrix GetCameraMatrix(Camera camera); // Get camera transform matrix (view matrix) +RLAPI Matrix GetCameraMatrix2D(Camera2D camera); // Get camera 2d transform matrix + +// Timing-related functions +RLAPI void SetTargetFPS(int fps); // Set target FPS (maximum) +RLAPI float GetFrameTime(void); // Get time in seconds for last frame drawn (delta time) +RLAPI double GetTime(void); // Get elapsed time in seconds since InitWindow() +RLAPI int GetFPS(void); // Get current FPS + +// Custom frame control functions +// NOTE: Those functions are intended for advanced users that want full control over the frame processing +// By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents() +// To avoid that behaviour and control frame processes manually, enable in config.h: SUPPORT_CUSTOM_FRAME_CONTROL +RLAPI void SwapScreenBuffer(void); // Swap back buffer with front buffer (screen drawing) +RLAPI void PollInputEvents(void); // Register all input events +RLAPI void WaitTime(double seconds); // Wait for some time (halt program execution) + +// Random values generation functions +RLAPI void SetRandomSeed(unsigned int seed); // Set the seed for the random number generator +RLAPI int GetRandomValue(int min, int max); // Get a random value between min and max (both included) +RLAPI int *LoadRandomSequence(unsigned int count, int min, int max); // Load random values sequence, no values repeated +RLAPI void UnloadRandomSequence(int *sequence); // Unload random values sequence + +// Misc. functions +RLAPI void TakeScreenshot(const char *fileName); // Takes a screenshot of current screen (filename extension defines format) +RLAPI void SetConfigFlags(unsigned int flags); // Setup init configuration flags (view FLAGS) +RLAPI void OpenURL(const char *url); // Open URL with default system browser (if available) + +// NOTE: Following functions implemented in module [utils] +//------------------------------------------------------------------ +RLAPI void TraceLog(int logLevel, const char *text, ...); // Show trace log messages (LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERROR...) +RLAPI void SetTraceLogLevel(int logLevel); // Set the current threshold (minimum) log level +RLAPI void *MemAlloc(unsigned int size); // Internal memory allocator +RLAPI void *MemRealloc(void *ptr, unsigned int size); // Internal memory reallocator +RLAPI void MemFree(void *ptr); // Internal memory free + +// Set custom callbacks +// WARNING: Callbacks setup is intended for advanced users +RLAPI void SetTraceLogCallback(TraceLogCallback callback); // Set custom trace log +RLAPI void SetLoadFileDataCallback(LoadFileDataCallback callback); // Set custom file binary data loader +RLAPI void SetSaveFileDataCallback(SaveFileDataCallback callback); // Set custom file binary data saver +RLAPI void SetLoadFileTextCallback(LoadFileTextCallback callback); // Set custom file text data loader +RLAPI void SetSaveFileTextCallback(SaveFileTextCallback callback); // Set custom file text data saver + +// Files management functions +RLAPI unsigned char *LoadFileData(const char *fileName, int *dataSize); // Load file data as byte array (read) +RLAPI void UnloadFileData(unsigned char *data); // Unload file data allocated by LoadFileData() +RLAPI bool SaveFileData(const char *fileName, void *data, int dataSize); // Save data to file from byte array (write), returns true on success +RLAPI bool ExportDataAsCode(const unsigned char *data, int dataSize, const char *fileName); // Export data to code (.h), returns true on success +RLAPI char *LoadFileText(const char *fileName); // Load text data from file (read), returns a '\0' terminated string +RLAPI void UnloadFileText(char *text); // Unload file text data allocated by LoadFileText() +RLAPI bool SaveFileText(const char *fileName, char *text); // Save text data to file (write), string must be '\0' terminated, returns true on success +//------------------------------------------------------------------ + +// File system functions +RLAPI bool FileExists(const char *fileName); // Check if file exists +RLAPI bool DirectoryExists(const char *dirPath); // Check if a directory path exists +RLAPI bool IsFileExtension(const char *fileName, const char *ext); // Check file extension (including point: .png, .wav) +RLAPI int GetFileLength(const char *fileName); // Get file length in bytes (NOTE: GetFileSize() conflicts with windows.h) +RLAPI const char *GetFileExtension(const char *fileName); // Get pointer to extension for a filename string (includes dot: '.png') +RLAPI const char *GetFileName(const char *filePath); // Get pointer to filename for a path string +RLAPI const char *GetFileNameWithoutExt(const char *filePath); // Get filename string without extension (uses static string) +RLAPI const char *GetDirectoryPath(const char *filePath); // Get full path for a given fileName with path (uses static string) +RLAPI const char *GetPrevDirectoryPath(const char *dirPath); // Get previous directory path for a given path (uses static string) +RLAPI const char *GetWorkingDirectory(void); // Get current working directory (uses static string) +RLAPI const char *GetApplicationDirectory(void); // Get the directory of the running application (uses static string) +RLAPI int MakeDirectory(const char *dirPath); // Create directories (including full path requested), returns 0 on success +RLAPI bool ChangeDirectory(const char *dir); // Change working directory, return true on success +RLAPI bool IsPathFile(const char *path); // Check if a given path is a file or a directory +RLAPI bool IsFileNameValid(const char *fileName); // Check if fileName is valid for the platform/OS +RLAPI FilePathList LoadDirectoryFiles(const char *dirPath); // Load directory filepaths +RLAPI FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs); // Load directory filepaths with extension filtering and recursive directory scan. Use 'DIR' in the filter string to include directories in the result +RLAPI void UnloadDirectoryFiles(FilePathList files); // Unload filepaths +RLAPI bool IsFileDropped(void); // Check if a file has been dropped into window +RLAPI FilePathList LoadDroppedFiles(void); // Load dropped filepaths +RLAPI void UnloadDroppedFiles(FilePathList files); // Unload dropped filepaths +RLAPI long GetFileModTime(const char *fileName); // Get file modification time (last write time) + +// Compression/Encoding functionality +RLAPI unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize); // Compress data (DEFLATE algorithm), memory must be MemFree() +RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize); // Decompress data (DEFLATE algorithm), memory must be MemFree() +RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize); // Encode data to Base64 string, memory must be MemFree() +RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize); // Decode Base64 string data, memory must be MemFree() +RLAPI unsigned int ComputeCRC32(unsigned char *data, int dataSize); // Compute CRC32 hash code +RLAPI unsigned int *ComputeMD5(unsigned char *data, int dataSize); // Compute MD5 hash code, returns static int[4] (16 bytes) +RLAPI unsigned int *ComputeSHA1(unsigned char *data, int dataSize); // Compute SHA1 hash code, returns static int[5] (20 bytes) + + +// Automation events functionality +RLAPI AutomationEventList LoadAutomationEventList(const char *fileName); // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS +RLAPI void UnloadAutomationEventList(AutomationEventList list); // Unload automation events list from file +RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName); // Export automation events list as text file +RLAPI void SetAutomationEventList(AutomationEventList *list); // Set automation event list to record to +RLAPI void SetAutomationEventBaseFrame(int frame); // Set automation event internal base frame to start recording +RLAPI void StartAutomationEventRecording(void); // Start recording automation events (AutomationEventList must be set) +RLAPI void StopAutomationEventRecording(void); // Stop recording automation events +RLAPI void PlayAutomationEvent(AutomationEvent event); // Play a recorded automation event + +//------------------------------------------------------------------------------------ +// Input Handling Functions (Module: core) +//------------------------------------------------------------------------------------ + +// Input-related functions: keyboard +RLAPI bool IsKeyPressed(int key); // Check if a key has been pressed once +RLAPI bool IsKeyPressedRepeat(int key); // Check if a key has been pressed again +RLAPI bool IsKeyDown(int key); // Check if a key is being pressed +RLAPI bool IsKeyReleased(int key); // Check if a key has been released once +RLAPI bool IsKeyUp(int key); // Check if a key is NOT being pressed +RLAPI int GetKeyPressed(void); // Get key pressed (keycode), call it multiple times for keys queued, returns 0 when the queue is empty +RLAPI int GetCharPressed(void); // Get char pressed (unicode), call it multiple times for chars queued, returns 0 when the queue is empty +RLAPI void SetExitKey(int key); // Set a custom key to exit program (default is ESC) + +// Input-related functions: gamepads +RLAPI bool IsGamepadAvailable(int gamepad); // Check if a gamepad is available +RLAPI const char *GetGamepadName(int gamepad); // Get gamepad internal name id +RLAPI bool IsGamepadButtonPressed(int gamepad, int button); // Check if a gamepad button has been pressed once +RLAPI bool IsGamepadButtonDown(int gamepad, int button); // Check if a gamepad button is being pressed +RLAPI bool IsGamepadButtonReleased(int gamepad, int button); // Check if a gamepad button has been released once +RLAPI bool IsGamepadButtonUp(int gamepad, int button); // Check if a gamepad button is NOT being pressed +RLAPI int GetGamepadButtonPressed(void); // Get the last gamepad button pressed +RLAPI int GetGamepadAxisCount(int gamepad); // Get gamepad axis count for a gamepad +RLAPI float GetGamepadAxisMovement(int gamepad, int axis); // Get axis movement value for a gamepad axis +RLAPI int SetGamepadMappings(const char *mappings); // Set internal gamepad mappings (SDL_GameControllerDB) +RLAPI void SetGamepadVibration(int gamepad, float leftMotor, float rightMotor, float duration); // Set gamepad vibration for both motors (duration in seconds) + +// Input-related functions: mouse +RLAPI bool IsMouseButtonPressed(int button); // Check if a mouse button has been pressed once +RLAPI bool IsMouseButtonDown(int button); // Check if a mouse button is being pressed +RLAPI bool IsMouseButtonReleased(int button); // Check if a mouse button has been released once +RLAPI bool IsMouseButtonUp(int button); // Check if a mouse button is NOT being pressed +RLAPI int GetMouseX(void); // Get mouse position X +RLAPI int GetMouseY(void); // Get mouse position Y +RLAPI Vector2 GetMousePosition(void); // Get mouse position XY +RLAPI Vector2 GetMouseDelta(void); // Get mouse delta between frames +RLAPI void SetMousePosition(int x, int y); // Set mouse position XY +RLAPI void SetMouseOffset(int offsetX, int offsetY); // Set mouse offset +RLAPI void SetMouseScale(float scaleX, float scaleY); // Set mouse scaling +RLAPI float GetMouseWheelMove(void); // Get mouse wheel movement for X or Y, whichever is larger +RLAPI Vector2 GetMouseWheelMoveV(void); // Get mouse wheel movement for both X and Y +RLAPI void SetMouseCursor(int cursor); // Set mouse cursor + +// Input-related functions: touch +RLAPI int GetTouchX(void); // Get touch position X for touch point 0 (relative to screen size) +RLAPI int GetTouchY(void); // Get touch position Y for touch point 0 (relative to screen size) +RLAPI Vector2 GetTouchPosition(int index); // Get touch position XY for a touch point index (relative to screen size) +RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index +RLAPI int GetTouchPointCount(void); // Get number of touch points + +//------------------------------------------------------------------------------------ +// Gestures and Touch Handling Functions (Module: rgestures) +//------------------------------------------------------------------------------------ +RLAPI void SetGesturesEnabled(unsigned int flags); // Enable a set of gestures using flags +RLAPI bool IsGestureDetected(unsigned int gesture); // Check if a gesture have been detected +RLAPI int GetGestureDetected(void); // Get latest detected gesture +RLAPI float GetGestureHoldDuration(void); // Get gesture hold time in seconds +RLAPI Vector2 GetGestureDragVector(void); // Get gesture drag vector +RLAPI float GetGestureDragAngle(void); // Get gesture drag angle +RLAPI Vector2 GetGesturePinchVector(void); // Get gesture pinch delta +RLAPI float GetGesturePinchAngle(void); // Get gesture pinch angle + +//------------------------------------------------------------------------------------ +// Camera System Functions (Module: rcamera) +//------------------------------------------------------------------------------------ +RLAPI void UpdateCamera(Camera *camera, int mode); // Update camera position for selected mode +RLAPI void UpdateCameraPro(Camera *camera, Vector3 movement, Vector3 rotation, float zoom); // Update camera movement/rotation + +//------------------------------------------------------------------------------------ +// Basic Shapes Drawing Functions (Module: shapes) +//------------------------------------------------------------------------------------ +// Set texture and rectangle to be used on shapes drawing +// NOTE: It can be useful when using basic shapes and one single font, +// defining a font char white rectangle would allow drawing everything in a single draw call +RLAPI void SetShapesTexture(Texture2D texture, Rectangle source); // Set texture and rectangle to be used on shapes drawing +RLAPI Texture2D GetShapesTexture(void); // Get texture that is used for shapes drawing +RLAPI Rectangle GetShapesTextureRectangle(void); // Get texture source rectangle that is used for shapes drawing + +// Basic shapes drawing functions +RLAPI void DrawPixel(int posX, int posY, Color color); // Draw a pixel using geometry [Can be slow, use with care] +RLAPI void DrawPixelV(Vector2 position, Color color); // Draw a pixel using geometry (Vector version) [Can be slow, use with care] +RLAPI void DrawLine(int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw a line +RLAPI void DrawLineV(Vector2 startPos, Vector2 endPos, Color color); // Draw a line (using gl lines) +RLAPI void DrawLineEx(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw a line (using triangles/quads) +RLAPI void DrawLineStrip(const Vector2 *points, int pointCount, Color color); // Draw lines sequence (using gl lines) +RLAPI void DrawLineBezier(Vector2 startPos, Vector2 endPos, float thick, Color color); // Draw line segment cubic-bezier in-out interpolation +RLAPI void DrawCircle(int centerX, int centerY, float radius, Color color); // Draw a color-filled circle +RLAPI void DrawCircleSector(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw a piece of a circle +RLAPI void DrawCircleSectorLines(Vector2 center, float radius, float startAngle, float endAngle, int segments, Color color); // Draw circle sector outline +RLAPI void DrawCircleGradient(int centerX, int centerY, float radius, Color inner, Color outer); // Draw a gradient-filled circle +RLAPI void DrawCircleV(Vector2 center, float radius, Color color); // Draw a color-filled circle (Vector version) +RLAPI void DrawCircleLines(int centerX, int centerY, float radius, Color color); // Draw circle outline +RLAPI void DrawCircleLinesV(Vector2 center, float radius, Color color); // Draw circle outline (Vector version) +RLAPI void DrawEllipse(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse +RLAPI void DrawEllipseLines(int centerX, int centerY, float radiusH, float radiusV, Color color); // Draw ellipse outline +RLAPI void DrawRing(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring +RLAPI void DrawRingLines(Vector2 center, float innerRadius, float outerRadius, float startAngle, float endAngle, int segments, Color color); // Draw ring outline +RLAPI void DrawRectangle(int posX, int posY, int width, int height, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectangleV(Vector2 position, Vector2 size, Color color); // Draw a color-filled rectangle (Vector version) +RLAPI void DrawRectangleRec(Rectangle rec, Color color); // Draw a color-filled rectangle +RLAPI void DrawRectanglePro(Rectangle rec, Vector2 origin, float rotation, Color color); // Draw a color-filled rectangle with pro parameters +RLAPI void DrawRectangleGradientV(int posX, int posY, int width, int height, Color top, Color bottom); // Draw a vertical-gradient-filled rectangle +RLAPI void DrawRectangleGradientH(int posX, int posY, int width, int height, Color left, Color right); // Draw a horizontal-gradient-filled rectangle +RLAPI void DrawRectangleGradientEx(Rectangle rec, Color topLeft, Color bottomLeft, Color topRight, Color bottomRight); // Draw a gradient-filled rectangle with custom vertex colors +RLAPI void DrawRectangleLines(int posX, int posY, int width, int height, Color color); // Draw rectangle outline +RLAPI void DrawRectangleLinesEx(Rectangle rec, float lineThick, Color color); // Draw rectangle outline with extended parameters +RLAPI void DrawRectangleRounded(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle with rounded edges +RLAPI void DrawRectangleRoundedLines(Rectangle rec, float roundness, int segments, Color color); // Draw rectangle lines with rounded edges +RLAPI void DrawRectangleRoundedLinesEx(Rectangle rec, float roundness, int segments, float lineThick, Color color); // Draw rectangle with rounded edges outline +RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline (vertex in counter-clockwise order!) +RLAPI void DrawTriangleFan(const Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points (first vertex is the center) +RLAPI void DrawTriangleStrip(const Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawPoly(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a regular polygon (Vector version) +RLAPI void DrawPolyLines(Vector2 center, int sides, float radius, float rotation, Color color); // Draw a polygon outline of n sides +RLAPI void DrawPolyLinesEx(Vector2 center, int sides, float radius, float rotation, float lineThick, Color color); // Draw a polygon outline of n sides with extended parameters + +// Splines drawing functions +RLAPI void DrawSplineLinear(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Linear, minimum 2 points +RLAPI void DrawSplineBasis(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: B-Spline, minimum 4 points +RLAPI void DrawSplineCatmullRom(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Catmull-Rom, minimum 4 points +RLAPI void DrawSplineBezierQuadratic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Quadratic Bezier, minimum 3 points (1 control point): [p1, c2, p3, c4...] +RLAPI void DrawSplineBezierCubic(const Vector2 *points, int pointCount, float thick, Color color); // Draw spline: Cubic Bezier, minimum 4 points (2 control points): [p1, c2, c3, p4, c5, c6...] +RLAPI void DrawSplineSegmentLinear(Vector2 p1, Vector2 p2, float thick, Color color); // Draw spline segment: Linear, 2 points +RLAPI void DrawSplineSegmentBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: B-Spline, 4 points +RLAPI void DrawSplineSegmentCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float thick, Color color); // Draw spline segment: Catmull-Rom, 4 points +RLAPI void DrawSplineSegmentBezierQuadratic(Vector2 p1, Vector2 c2, Vector2 p3, float thick, Color color); // Draw spline segment: Quadratic Bezier, 2 points, 1 control point +RLAPI void DrawSplineSegmentBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float thick, Color color); // Draw spline segment: Cubic Bezier, 2 points, 2 control points + +// Spline segment point evaluation functions, for a given t [0.0f .. 1.0f] +RLAPI Vector2 GetSplinePointLinear(Vector2 startPos, Vector2 endPos, float t); // Get (evaluate) spline point: Linear +RLAPI Vector2 GetSplinePointBasis(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: B-Spline +RLAPI Vector2 GetSplinePointCatmullRom(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4, float t); // Get (evaluate) spline point: Catmull-Rom +RLAPI Vector2 GetSplinePointBezierQuad(Vector2 p1, Vector2 c2, Vector2 p3, float t); // Get (evaluate) spline point: Quadratic Bezier +RLAPI Vector2 GetSplinePointBezierCubic(Vector2 p1, Vector2 c2, Vector2 c3, Vector2 p4, float t); // Get (evaluate) spline point: Cubic Bezier + +// Basic shapes collision detection functions +RLAPI bool CheckCollisionRecs(Rectangle rec1, Rectangle rec2); // Check collision between two rectangles +RLAPI bool CheckCollisionCircles(Vector2 center1, float radius1, Vector2 center2, float radius2); // Check collision between two circles +RLAPI bool CheckCollisionCircleRec(Vector2 center, float radius, Rectangle rec); // Check collision between circle and rectangle +RLAPI bool CheckCollisionCircleLine(Vector2 center, float radius, Vector2 p1, Vector2 p2); // Check if circle collides with a line created betweeen two points [p1] and [p2] +RLAPI bool CheckCollisionPointRec(Vector2 point, Rectangle rec); // Check if point is inside rectangle +RLAPI bool CheckCollisionPointCircle(Vector2 point, Vector2 center, float radius); // Check if point is inside circle +RLAPI bool CheckCollisionPointTriangle(Vector2 point, Vector2 p1, Vector2 p2, Vector2 p3); // Check if point is inside a triangle +RLAPI bool CheckCollisionPointLine(Vector2 point, Vector2 p1, Vector2 p2, int threshold); // Check if point belongs to line created between two points [p1] and [p2] with defined margin in pixels [threshold] +RLAPI bool CheckCollisionPointPoly(Vector2 point, const Vector2 *points, int pointCount); // Check if point is within a polygon described by array of vertices +RLAPI bool CheckCollisionLines(Vector2 startPos1, Vector2 endPos1, Vector2 startPos2, Vector2 endPos2, Vector2 *collisionPoint); // Check the collision between two lines defined by two points each, returns collision point by reference +RLAPI Rectangle GetCollisionRec(Rectangle rec1, Rectangle rec2); // Get collision rectangle for two rectangles collision + +//------------------------------------------------------------------------------------ +// Texture Loading and Drawing Functions (Module: textures) +//------------------------------------------------------------------------------------ + +// Image loading functions +// NOTE: These functions do not require GPU access +RLAPI Image LoadImage(const char *fileName); // Load image from file into CPU memory (RAM) +RLAPI Image LoadImageRaw(const char *fileName, int width, int height, int format, int headerSize); // Load image from RAW file data +RLAPI Image LoadImageAnim(const char *fileName, int *frames); // Load image sequence from file (frames appended to image.data) +RLAPI Image LoadImageAnimFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int *frames); // Load image sequence from memory buffer +RLAPI Image LoadImageFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load image from memory buffer, fileType refers to extension: i.e. '.png' +RLAPI Image LoadImageFromTexture(Texture2D texture); // Load image from GPU texture data +RLAPI Image LoadImageFromScreen(void); // Load image from screen buffer and (screenshot) +RLAPI bool IsImageValid(Image image); // Check if an image is valid (data and parameters) +RLAPI void UnloadImage(Image image); // Unload image from CPU memory (RAM) +RLAPI bool ExportImage(Image image, const char *fileName); // Export image data to file, returns true on success +RLAPI unsigned char *ExportImageToMemory(Image image, const char *fileType, int *fileSize); // Export image to memory buffer +RLAPI bool ExportImageAsCode(Image image, const char *fileName); // Export image as code file defining an array of bytes, returns true on success + +// Image generation functions +RLAPI Image GenImageColor(int width, int height, Color color); // Generate image: plain color +RLAPI Image GenImageGradientLinear(int width, int height, int direction, Color start, Color end); // Generate image: linear gradient, direction in degrees [0..360], 0=Vertical gradient +RLAPI Image GenImageGradientRadial(int width, int height, float density, Color inner, Color outer); // Generate image: radial gradient +RLAPI Image GenImageGradientSquare(int width, int height, float density, Color inner, Color outer); // Generate image: square gradient +RLAPI Image GenImageChecked(int width, int height, int checksX, int checksY, Color col1, Color col2); // Generate image: checked +RLAPI Image GenImageWhiteNoise(int width, int height, float factor); // Generate image: white noise +RLAPI Image GenImagePerlinNoise(int width, int height, int offsetX, int offsetY, float scale); // Generate image: perlin noise +RLAPI Image GenImageCellular(int width, int height, int tileSize); // Generate image: cellular algorithm, bigger tileSize means bigger cells +RLAPI Image GenImageText(int width, int height, const char *text); // Generate image: grayscale image from text data + +// Image manipulation functions +RLAPI Image ImageCopy(Image image); // Create an image duplicate (useful for transformations) +RLAPI Image ImageFromImage(Image image, Rectangle rec); // Create an image from another image piece +RLAPI Image ImageFromChannel(Image image, int selectedChannel); // Create an image from a selected channel of another image (GRAYSCALE) +RLAPI Image ImageText(const char *text, int fontSize, Color color); // Create an image from text (default font) +RLAPI Image ImageTextEx(Font font, const char *text, float fontSize, float spacing, Color tint); // Create an image from text (custom sprite font) +RLAPI void ImageFormat(Image *image, int newFormat); // Convert image data to desired format +RLAPI void ImageToPOT(Image *image, Color fill); // Convert image to POT (power-of-two) +RLAPI void ImageCrop(Image *image, Rectangle crop); // Crop an image to a defined rectangle +RLAPI void ImageAlphaCrop(Image *image, float threshold); // Crop image depending on alpha value +RLAPI void ImageAlphaClear(Image *image, Color color, float threshold); // Clear alpha channel to desired color +RLAPI void ImageAlphaMask(Image *image, Image alphaMask); // Apply alpha mask to image +RLAPI void ImageAlphaPremultiply(Image *image); // Premultiply alpha channel +RLAPI void ImageBlurGaussian(Image *image, int blurSize); // Apply Gaussian blur using a box blur approximation +RLAPI void ImageKernelConvolution(Image *image, const float *kernel, int kernelSize); // Apply custom square convolution kernel to image +RLAPI void ImageResize(Image *image, int newWidth, int newHeight); // Resize image (Bicubic scaling algorithm) +RLAPI void ImageResizeNN(Image *image, int newWidth,int newHeight); // Resize image (Nearest-Neighbor scaling algorithm) +RLAPI void ImageResizeCanvas(Image *image, int newWidth, int newHeight, int offsetX, int offsetY, Color fill); // Resize canvas and fill with color +RLAPI void ImageMipmaps(Image *image); // Compute all mipmap levels for a provided image +RLAPI void ImageDither(Image *image, int rBpp, int gBpp, int bBpp, int aBpp); // Dither image data to 16bpp or lower (Floyd-Steinberg dithering) +RLAPI void ImageFlipVertical(Image *image); // Flip image vertically +RLAPI void ImageFlipHorizontal(Image *image); // Flip image horizontally +RLAPI void ImageRotate(Image *image, int degrees); // Rotate image by input angle in degrees (-359 to 359) +RLAPI void ImageRotateCW(Image *image); // Rotate image clockwise 90deg +RLAPI void ImageRotateCCW(Image *image); // Rotate image counter-clockwise 90deg +RLAPI void ImageColorTint(Image *image, Color color); // Modify image color: tint +RLAPI void ImageColorInvert(Image *image); // Modify image color: invert +RLAPI void ImageColorGrayscale(Image *image); // Modify image color: grayscale +RLAPI void ImageColorContrast(Image *image, float contrast); // Modify image color: contrast (-100 to 100) +RLAPI void ImageColorBrightness(Image *image, int brightness); // Modify image color: brightness (-255 to 255) +RLAPI void ImageColorReplace(Image *image, Color color, Color replace); // Modify image color: replace color +RLAPI Color *LoadImageColors(Image image); // Load color data from image as a Color array (RGBA - 32bit) +RLAPI Color *LoadImagePalette(Image image, int maxPaletteSize, int *colorCount); // Load colors palette from image as a Color array (RGBA - 32bit) +RLAPI void UnloadImageColors(Color *colors); // Unload color data loaded with LoadImageColors() +RLAPI void UnloadImagePalette(Color *colors); // Unload colors palette loaded with LoadImagePalette() +RLAPI Rectangle GetImageAlphaBorder(Image image, float threshold); // Get image alpha border rectangle +RLAPI Color GetImageColor(Image image, int x, int y); // Get image pixel color at (x, y) position + +// Image drawing functions +// NOTE: Image software-rendering functions (CPU) +RLAPI void ImageClearBackground(Image *dst, Color color); // Clear image background with given color +RLAPI void ImageDrawPixel(Image *dst, int posX, int posY, Color color); // Draw pixel within an image +RLAPI void ImageDrawPixelV(Image *dst, Vector2 position, Color color); // Draw pixel within an image (Vector version) +RLAPI void ImageDrawLine(Image *dst, int startPosX, int startPosY, int endPosX, int endPosY, Color color); // Draw line within an image +RLAPI void ImageDrawLineV(Image *dst, Vector2 start, Vector2 end, Color color); // Draw line within an image (Vector version) +RLAPI void ImageDrawLineEx(Image *dst, Vector2 start, Vector2 end, int thick, Color color); // Draw a line defining thickness within an image +RLAPI void ImageDrawCircle(Image *dst, int centerX, int centerY, int radius, Color color); // Draw a filled circle within an image +RLAPI void ImageDrawCircleV(Image *dst, Vector2 center, int radius, Color color); // Draw a filled circle within an image (Vector version) +RLAPI void ImageDrawCircleLines(Image *dst, int centerX, int centerY, int radius, Color color); // Draw circle outline within an image +RLAPI void ImageDrawCircleLinesV(Image *dst, Vector2 center, int radius, Color color); // Draw circle outline within an image (Vector version) +RLAPI void ImageDrawRectangle(Image *dst, int posX, int posY, int width, int height, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleV(Image *dst, Vector2 position, Vector2 size, Color color); // Draw rectangle within an image (Vector version) +RLAPI void ImageDrawRectangleRec(Image *dst, Rectangle rec, Color color); // Draw rectangle within an image +RLAPI void ImageDrawRectangleLines(Image *dst, Rectangle rec, int thick, Color color); // Draw rectangle lines within an image +RLAPI void ImageDrawTriangle(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle within an image +RLAPI void ImageDrawTriangleEx(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color c1, Color c2, Color c3); // Draw triangle with interpolated colors within an image +RLAPI void ImageDrawTriangleLines(Image *dst, Vector2 v1, Vector2 v2, Vector2 v3, Color color); // Draw triangle outline within an image +RLAPI void ImageDrawTriangleFan(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle fan defined by points within an image (first vertex is the center) +RLAPI void ImageDrawTriangleStrip(Image *dst, Vector2 *points, int pointCount, Color color); // Draw a triangle strip defined by points within an image +RLAPI void ImageDraw(Image *dst, Image src, Rectangle srcRec, Rectangle dstRec, Color tint); // Draw a source image within a destination image (tint applied to source) +RLAPI void ImageDrawText(Image *dst, const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) within an image (destination) +RLAPI void ImageDrawTextEx(Image *dst, Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text (custom sprite font) within an image (destination) + +// Texture loading functions +// NOTE: These functions require GPU access +RLAPI Texture2D LoadTexture(const char *fileName); // Load texture from file into GPU memory (VRAM) +RLAPI Texture2D LoadTextureFromImage(Image image); // Load texture from image data +RLAPI TextureCubemap LoadTextureCubemap(Image image, int layout); // Load cubemap from image, multiple image cubemap layouts supported +RLAPI RenderTexture2D LoadRenderTexture(int width, int height); // Load texture for rendering (framebuffer) +RLAPI bool IsTextureValid(Texture2D texture); // Check if a texture is valid (loaded in GPU) +RLAPI void UnloadTexture(Texture2D texture); // Unload texture from GPU memory (VRAM) +RLAPI bool IsRenderTextureValid(RenderTexture2D target); // Check if a render texture is valid (loaded in GPU) +RLAPI void UnloadRenderTexture(RenderTexture2D target); // Unload render texture from GPU memory (VRAM) +RLAPI void UpdateTexture(Texture2D texture, const void *pixels); // Update GPU texture with new data +RLAPI void UpdateTextureRec(Texture2D texture, Rectangle rec, const void *pixels); // Update GPU texture rectangle with new data + +// Texture configuration functions +RLAPI void GenTextureMipmaps(Texture2D *texture); // Generate GPU mipmaps for a texture +RLAPI void SetTextureFilter(Texture2D texture, int filter); // Set texture scaling filter mode +RLAPI void SetTextureWrap(Texture2D texture, int wrap); // Set texture wrapping mode + +// Texture drawing functions +RLAPI void DrawTexture(Texture2D texture, int posX, int posY, Color tint); // Draw a Texture2D +RLAPI void DrawTextureV(Texture2D texture, Vector2 position, Color tint); // Draw a Texture2D with position defined as Vector2 +RLAPI void DrawTextureEx(Texture2D texture, Vector2 position, float rotation, float scale, Color tint); // Draw a Texture2D with extended parameters +RLAPI void DrawTextureRec(Texture2D texture, Rectangle source, Vector2 position, Color tint); // Draw a part of a texture defined by a rectangle +RLAPI void DrawTexturePro(Texture2D texture, Rectangle source, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draw a part of a texture defined by a rectangle with 'pro' parameters +RLAPI void DrawTextureNPatch(Texture2D texture, NPatchInfo nPatchInfo, Rectangle dest, Vector2 origin, float rotation, Color tint); // Draws a texture (or part of it) that stretches or shrinks nicely + +// Color/pixel related functions +RLAPI bool ColorIsEqual(Color col1, Color col2); // Check if two colors are equal +RLAPI Color Fade(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI int ColorToInt(Color color); // Get hexadecimal value for a Color (0xRRGGBBAA) +RLAPI Vector4 ColorNormalize(Color color); // Get Color normalized as float [0..1] +RLAPI Color ColorFromNormalized(Vector4 normalized); // Get Color from normalized values [0..1] +RLAPI Vector3 ColorToHSV(Color color); // Get HSV values for a Color, hue [0..360], saturation/value [0..1] +RLAPI Color ColorFromHSV(float hue, float saturation, float value); // Get a Color from HSV values, hue [0..360], saturation/value [0..1] +RLAPI Color ColorTint(Color color, Color tint); // Get color multiplied with another color +RLAPI Color ColorBrightness(Color color, float factor); // Get color with brightness correction, brightness factor goes from -1.0f to 1.0f +RLAPI Color ColorContrast(Color color, float contrast); // Get color with contrast correction, contrast values between -1.0f and 1.0f +RLAPI Color ColorAlpha(Color color, float alpha); // Get color with alpha applied, alpha goes from 0.0f to 1.0f +RLAPI Color ColorAlphaBlend(Color dst, Color src, Color tint); // Get src alpha-blended into dst color with tint +RLAPI Color ColorLerp(Color color1, Color color2, float factor); // Get color lerp interpolation between two colors, factor [0.0f..1.0f] +RLAPI Color GetColor(unsigned int hexValue); // Get Color structure from hexadecimal value +RLAPI Color GetPixelColor(void *srcPtr, int format); // Get Color from a source pixel pointer of certain format +RLAPI void SetPixelColor(void *dstPtr, Color color, int format); // Set color formatted into destination pixel pointer +RLAPI int GetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes for certain format + +//------------------------------------------------------------------------------------ +// Font Loading and Text Drawing Functions (Module: text) +//------------------------------------------------------------------------------------ + +// Font loading/unloading functions +RLAPI Font GetFontDefault(void); // Get the default Font +RLAPI Font LoadFont(const char *fileName); // Load font from file into GPU memory (VRAM) +RLAPI Font LoadFontEx(const char *fileName, int fontSize, int *codepoints, int codepointCount); // Load font from file with extended parameters, use NULL for codepoints and 0 for codepointCount to load the default character set, font size is provided in pixels height +RLAPI Font LoadFontFromImage(Image image, Color key, int firstChar); // Load font from Image (XNA style) +RLAPI Font LoadFontFromMemory(const char *fileType, const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount); // Load font from memory buffer, fileType refers to extension: i.e. '.ttf' +RLAPI bool IsFontValid(Font font); // Check if a font is valid (font data loaded, WARNING: GPU texture not checked) +RLAPI GlyphInfo *LoadFontData(const unsigned char *fileData, int dataSize, int fontSize, int *codepoints, int codepointCount, int type); // Load font data for further use +RLAPI Image GenImageFontAtlas(const GlyphInfo *glyphs, Rectangle **glyphRecs, int glyphCount, int fontSize, int padding, int packMethod); // Generate image font atlas using chars info +RLAPI void UnloadFontData(GlyphInfo *glyphs, int glyphCount); // Unload font chars info data (RAM) +RLAPI void UnloadFont(Font font); // Unload font from GPU memory (VRAM) +RLAPI bool ExportFontAsCode(Font font, const char *fileName); // Export font as code file, returns true on success + +// Text drawing functions +RLAPI void DrawFPS(int posX, int posY); // Draw current FPS +RLAPI void DrawText(const char *text, int posX, int posY, int fontSize, Color color); // Draw text (using default font) +RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters +RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation) +RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint) +RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint) + +// Text font info functions +RLAPI void SetTextLineSpacing(int spacing); // Set vertical line spacing when drawing with line-breaks +RLAPI int MeasureText(const char *text, int fontSize); // Measure string width for default font +RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing); // Measure string size for Font +RLAPI int GetGlyphIndex(Font font, int codepoint); // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found +RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint); // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found +RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint); // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found + +// Text codepoints management functions (unicode characters) +RLAPI char *LoadUTF8(const int *codepoints, int length); // Load UTF-8 text encoded from codepoints array +RLAPI void UnloadUTF8(char *text); // Unload UTF-8 text encoded from codepoints array +RLAPI int *LoadCodepoints(const char *text, int *count); // Load all codepoints from a UTF-8 text string, codepoints count returned by parameter +RLAPI void UnloadCodepoints(int *codepoints); // Unload codepoints data from memory +RLAPI int GetCodepointCount(const char *text); // Get total number of codepoints in a UTF-8 encoded string +RLAPI int GetCodepoint(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointNext(const char *text, int *codepointSize); // Get next codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI int GetCodepointPrevious(const char *text, int *codepointSize); // Get previous codepoint in a UTF-8 encoded string, 0x3f('?') is returned on failure +RLAPI const char *CodepointToUTF8(int codepoint, int *utf8Size); // Encode one codepoint into UTF-8 byte array (array length returned as parameter) + +// Text strings management functions (no UTF-8 strings, only byte chars) +// NOTE: Some strings allocate memory internally for returned strings, just be careful! +RLAPI int TextCopy(char *dst, const char *src); // Copy one string to another, returns bytes copied +RLAPI bool TextIsEqual(const char *text1, const char *text2); // Check if two text string are equal +RLAPI unsigned int TextLength(const char *text); // Get text length, checks for '\0' ending +RLAPI const char *TextFormat(const char *text, ...); // Text formatting with variables (sprintf() style) +RLAPI const char *TextSubtext(const char *text, int position, int length); // Get a piece of a text string +RLAPI char *TextReplace(const char *text, const char *replace, const char *by); // Replace text string (WARNING: memory must be freed!) +RLAPI char *TextInsert(const char *text, const char *insert, int position); // Insert text in a position (WARNING: memory must be freed!) +RLAPI const char *TextJoin(const char **textList, int count, const char *delimiter); // Join text strings with delimiter +RLAPI const char **TextSplit(const char *text, char delimiter, int *count); // Split text into multiple strings +RLAPI void TextAppend(char *text, const char *append, int *position); // Append text at specific position and move cursor! +RLAPI int TextFindIndex(const char *text, const char *find); // Find first text occurrence within a string +RLAPI const char *TextToUpper(const char *text); // Get upper case version of provided string +RLAPI const char *TextToLower(const char *text); // Get lower case version of provided string +RLAPI const char *TextToPascal(const char *text); // Get Pascal case notation version of provided string +RLAPI const char *TextToSnake(const char *text); // Get Snake case notation version of provided string +RLAPI const char *TextToCamel(const char *text); // Get Camel case notation version of provided string + +RLAPI int TextToInteger(const char *text); // Get integer value from text (negative values not supported) +RLAPI float TextToFloat(const char *text); // Get float value from text (negative values not supported) + +//------------------------------------------------------------------------------------ +// Basic 3d Shapes Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Basic geometric 3D shapes drawing functions +RLAPI void DrawLine3D(Vector3 startPos, Vector3 endPos, Color color); // Draw a line in 3D world space +RLAPI void DrawPoint3D(Vector3 position, Color color); // Draw a point in 3D space, actually a small line +RLAPI void DrawCircle3D(Vector3 center, float radius, Vector3 rotationAxis, float rotationAngle, Color color); // Draw a circle in 3D world space +RLAPI void DrawTriangle3D(Vector3 v1, Vector3 v2, Vector3 v3, Color color); // Draw a color-filled triangle (vertex in counter-clockwise order!) +RLAPI void DrawTriangleStrip3D(const Vector3 *points, int pointCount, Color color); // Draw a triangle strip defined by points +RLAPI void DrawCube(Vector3 position, float width, float height, float length, Color color); // Draw cube +RLAPI void DrawCubeV(Vector3 position, Vector3 size, Color color); // Draw cube (Vector version) +RLAPI void DrawCubeWires(Vector3 position, float width, float height, float length, Color color); // Draw cube wires +RLAPI void DrawCubeWiresV(Vector3 position, Vector3 size, Color color); // Draw cube wires (Vector version) +RLAPI void DrawSphere(Vector3 centerPos, float radius, Color color); // Draw sphere +RLAPI void DrawSphereEx(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere with extended parameters +RLAPI void DrawSphereWires(Vector3 centerPos, float radius, int rings, int slices, Color color); // Draw sphere wires +RLAPI void DrawCylinder(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone +RLAPI void DrawCylinderEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder with base at startPos and top at endPos +RLAPI void DrawCylinderWires(Vector3 position, float radiusTop, float radiusBottom, float height, int slices, Color color); // Draw a cylinder/cone wires +RLAPI void DrawCylinderWiresEx(Vector3 startPos, Vector3 endPos, float startRadius, float endRadius, int sides, Color color); // Draw a cylinder wires with base at startPos and top at endPos +RLAPI void DrawCapsule(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw a capsule with the center of its sphere caps at startPos and endPos +RLAPI void DrawCapsuleWires(Vector3 startPos, Vector3 endPos, float radius, int slices, int rings, Color color); // Draw capsule wireframe with the center of its sphere caps at startPos and endPos +RLAPI void DrawPlane(Vector3 centerPos, Vector2 size, Color color); // Draw a plane XZ +RLAPI void DrawRay(Ray ray, Color color); // Draw a ray line +RLAPI void DrawGrid(int slices, float spacing); // Draw a grid (centered at (0, 0, 0)) + +//------------------------------------------------------------------------------------ +// Model 3d Loading and Drawing Functions (Module: models) +//------------------------------------------------------------------------------------ + +// Model management functions +RLAPI Model LoadModel(const char *fileName); // Load model from files (meshes and materials) +RLAPI Model LoadModelFromMesh(Mesh mesh); // Load model from generated mesh (default material) +RLAPI bool IsModelValid(Model model); // Check if a model is valid (loaded in GPU, VAO/VBOs) +RLAPI void UnloadModel(Model model); // Unload model (including meshes) from memory (RAM and/or VRAM) +RLAPI BoundingBox GetModelBoundingBox(Model model); // Compute model bounding box limits (considers all meshes) + +// Model drawing functions +RLAPI void DrawModel(Model model, Vector3 position, float scale, Color tint); // Draw a model (with texture if set) +RLAPI void DrawModelEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model with extended parameters +RLAPI void DrawModelWires(Model model, Vector3 position, float scale, Color tint); // Draw a model wires (with texture if set) +RLAPI void DrawModelWiresEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model wires (with texture if set) with extended parameters +RLAPI void DrawModelPoints(Model model, Vector3 position, float scale, Color tint); // Draw a model as points +RLAPI void DrawModelPointsEx(Model model, Vector3 position, Vector3 rotationAxis, float rotationAngle, Vector3 scale, Color tint); // Draw a model as points with extended parameters +RLAPI void DrawBoundingBox(BoundingBox box, Color color); // Draw bounding box (wires) +RLAPI void DrawBillboard(Camera camera, Texture2D texture, Vector3 position, float scale, Color tint); // Draw a billboard texture +RLAPI void DrawBillboardRec(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector2 size, Color tint); // Draw a billboard texture defined by source +RLAPI void DrawBillboardPro(Camera camera, Texture2D texture, Rectangle source, Vector3 position, Vector3 up, Vector2 size, Vector2 origin, float rotation, Color tint); // Draw a billboard texture defined by source and rotation + +// Mesh management functions +RLAPI void UploadMesh(Mesh *mesh, bool dynamic); // Upload mesh vertex data in GPU and provide VAO/VBO ids +RLAPI void UpdateMeshBuffer(Mesh mesh, int index, const void *data, int dataSize, int offset); // Update mesh vertex data in GPU for a specific buffer index +RLAPI void UnloadMesh(Mesh mesh); // Unload mesh data from CPU and GPU +RLAPI void DrawMesh(Mesh mesh, Material material, Matrix transform); // Draw a 3d mesh with material and transform +RLAPI void DrawMeshInstanced(Mesh mesh, Material material, const Matrix *transforms, int instances); // Draw multiple mesh instances with material and different transforms +RLAPI BoundingBox GetMeshBoundingBox(Mesh mesh); // Compute mesh bounding box limits +RLAPI void GenMeshTangents(Mesh *mesh); // Compute mesh tangents +RLAPI bool ExportMesh(Mesh mesh, const char *fileName); // Export mesh data to file, returns true on success +RLAPI bool ExportMeshAsCode(Mesh mesh, const char *fileName); // Export mesh as code file (.h) defining multiple arrays of vertex attributes + +// Mesh generation functions +RLAPI Mesh GenMeshPoly(int sides, float radius); // Generate polygonal mesh +RLAPI Mesh GenMeshPlane(float width, float length, int resX, int resZ); // Generate plane mesh (with subdivisions) +RLAPI Mesh GenMeshCube(float width, float height, float length); // Generate cuboid mesh +RLAPI Mesh GenMeshSphere(float radius, int rings, int slices); // Generate sphere mesh (standard sphere) +RLAPI Mesh GenMeshHemiSphere(float radius, int rings, int slices); // Generate half-sphere mesh (no bottom cap) +RLAPI Mesh GenMeshCylinder(float radius, float height, int slices); // Generate cylinder mesh +RLAPI Mesh GenMeshCone(float radius, float height, int slices); // Generate cone/pyramid mesh +RLAPI Mesh GenMeshTorus(float radius, float size, int radSeg, int sides); // Generate torus mesh +RLAPI Mesh GenMeshKnot(float radius, float size, int radSeg, int sides); // Generate trefoil knot mesh +RLAPI Mesh GenMeshHeightmap(Image heightmap, Vector3 size); // Generate heightmap mesh from image data +RLAPI Mesh GenMeshCubicmap(Image cubicmap, Vector3 cubeSize); // Generate cubes-based map mesh from image data + +// Material loading/unloading functions +RLAPI Material *LoadMaterials(const char *fileName, int *materialCount); // Load materials from model file +RLAPI Material LoadMaterialDefault(void); // Load default material (Supports: DIFFUSE, SPECULAR, NORMAL maps) +RLAPI bool IsMaterialValid(Material material); // Check if a material is valid (shader assigned, map textures loaded in GPU) +RLAPI void UnloadMaterial(Material material); // Unload material from GPU memory (VRAM) +RLAPI void SetMaterialTexture(Material *material, int mapType, Texture2D texture); // Set texture for a material map type (MATERIAL_MAP_DIFFUSE, MATERIAL_MAP_SPECULAR...) +RLAPI void SetModelMeshMaterial(Model *model, int meshId, int materialId); // Set material for a mesh + +// Model animations loading/unloading functions +RLAPI ModelAnimation *LoadModelAnimations(const char *fileName, int *animCount); // Load model animations from file +RLAPI void UpdateModelAnimation(Model model, ModelAnimation anim, int frame); // Update model animation pose (CPU) +RLAPI void UpdateModelAnimationBones(Model model, ModelAnimation anim, int frame); // Update model animation mesh bone matrices (GPU skinning) +RLAPI void UnloadModelAnimation(ModelAnimation anim); // Unload animation data +RLAPI void UnloadModelAnimations(ModelAnimation *animations, int animCount); // Unload animation array data +RLAPI bool IsModelAnimationValid(Model model, ModelAnimation anim); // Check model animation skeleton match + +// Collision detection functions +RLAPI bool CheckCollisionSpheres(Vector3 center1, float radius1, Vector3 center2, float radius2); // Check collision between two spheres +RLAPI bool CheckCollisionBoxes(BoundingBox box1, BoundingBox box2); // Check collision between two bounding boxes +RLAPI bool CheckCollisionBoxSphere(BoundingBox box, Vector3 center, float radius); // Check collision between box and sphere +RLAPI RayCollision GetRayCollisionSphere(Ray ray, Vector3 center, float radius); // Get collision info between ray and sphere +RLAPI RayCollision GetRayCollisionBox(Ray ray, BoundingBox box); // Get collision info between ray and box +RLAPI RayCollision GetRayCollisionMesh(Ray ray, Mesh mesh, Matrix transform); // Get collision info between ray and mesh +RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3); // Get collision info between ray and triangle +RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad + +//------------------------------------------------------------------------------------ +// Audio Loading and Playing Functions (Module: audio) +//------------------------------------------------------------------------------------ +typedef void (*AudioCallback)(void *bufferData, unsigned int frames); + +// Audio device management functions +RLAPI void InitAudioDevice(void); // Initialize audio device and context +RLAPI void CloseAudioDevice(void); // Close the audio device and context +RLAPI bool IsAudioDeviceReady(void); // Check if audio device has been initialized successfully +RLAPI void SetMasterVolume(float volume); // Set master volume (listener) +RLAPI float GetMasterVolume(void); // Get master volume (listener) + +// Wave/Sound loading/unloading functions +RLAPI Wave LoadWave(const char *fileName); // Load wave data from file +RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav' +RLAPI bool IsWaveValid(Wave wave); // Checks if wave data is valid (data loaded and parameters) +RLAPI Sound LoadSound(const char *fileName); // Load sound from file +RLAPI Sound LoadSoundFromWave(Wave wave); // Load sound from wave data +RLAPI Sound LoadSoundAlias(Sound source); // Create a new sound that shares the same sample data as the source sound, does not own the sound data +RLAPI bool IsSoundValid(Sound sound); // Checks if a sound is valid (data loaded and buffers initialized) +RLAPI void UpdateSound(Sound sound, const void *data, int sampleCount); // Update sound buffer with new data +RLAPI void UnloadWave(Wave wave); // Unload wave data +RLAPI void UnloadSound(Sound sound); // Unload sound +RLAPI void UnloadSoundAlias(Sound alias); // Unload a sound alias (does not deallocate sample data) +RLAPI bool ExportWave(Wave wave, const char *fileName); // Export wave data to file, returns true on success +RLAPI bool ExportWaveAsCode(Wave wave, const char *fileName); // Export wave sample data to code (.h), returns true on success + +// Wave/Sound management functions +RLAPI void PlaySound(Sound sound); // Play a sound +RLAPI void StopSound(Sound sound); // Stop playing a sound +RLAPI void PauseSound(Sound sound); // Pause a sound +RLAPI void ResumeSound(Sound sound); // Resume a paused sound +RLAPI bool IsSoundPlaying(Sound sound); // Check if a sound is currently playing +RLAPI void SetSoundVolume(Sound sound, float volume); // Set volume for a sound (1.0 is max level) +RLAPI void SetSoundPitch(Sound sound, float pitch); // Set pitch for a sound (1.0 is base level) +RLAPI void SetSoundPan(Sound sound, float pan); // Set pan for a sound (0.5 is center) +RLAPI Wave WaveCopy(Wave wave); // Copy a wave to a new wave +RLAPI void WaveCrop(Wave *wave, int initFrame, int finalFrame); // Crop a wave to defined frames range +RLAPI void WaveFormat(Wave *wave, int sampleRate, int sampleSize, int channels); // Convert wave data to desired format +RLAPI float *LoadWaveSamples(Wave wave); // Load samples data from wave as a 32bit float data array +RLAPI void UnloadWaveSamples(float *samples); // Unload samples data loaded with LoadWaveSamples() + +// Music management functions +RLAPI Music LoadMusicStream(const char *fileName); // Load music stream from file +RLAPI Music LoadMusicStreamFromMemory(const char *fileType, const unsigned char *data, int dataSize); // Load music stream from data +RLAPI bool IsMusicValid(Music music); // Checks if a music stream is valid (context and buffers initialized) +RLAPI void UnloadMusicStream(Music music); // Unload music stream +RLAPI void PlayMusicStream(Music music); // Start music playing +RLAPI bool IsMusicStreamPlaying(Music music); // Check if music is playing +RLAPI void UpdateMusicStream(Music music); // Updates buffers for music streaming +RLAPI void StopMusicStream(Music music); // Stop music playing +RLAPI void PauseMusicStream(Music music); // Pause music playing +RLAPI void ResumeMusicStream(Music music); // Resume playing paused music +RLAPI void SeekMusicStream(Music music, float position); // Seek music to a position (in seconds) +RLAPI void SetMusicVolume(Music music, float volume); // Set volume for music (1.0 is max level) +RLAPI void SetMusicPitch(Music music, float pitch); // Set pitch for a music (1.0 is base level) +RLAPI void SetMusicPan(Music music, float pan); // Set pan for a music (0.5 is center) +RLAPI float GetMusicTimeLength(Music music); // Get music time length (in seconds) +RLAPI float GetMusicTimePlayed(Music music); // Get current music time played (in seconds) + +// AudioStream management functions +RLAPI AudioStream LoadAudioStream(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Load audio stream (to stream raw audio pcm data) +RLAPI bool IsAudioStreamValid(AudioStream stream); // Checks if an audio stream is valid (buffers initialized) +RLAPI void UnloadAudioStream(AudioStream stream); // Unload audio stream and free memory +RLAPI void UpdateAudioStream(AudioStream stream, const void *data, int frameCount); // Update audio stream buffers with data +RLAPI bool IsAudioStreamProcessed(AudioStream stream); // Check if any audio stream buffers requires refill +RLAPI void PlayAudioStream(AudioStream stream); // Play audio stream +RLAPI void PauseAudioStream(AudioStream stream); // Pause audio stream +RLAPI void ResumeAudioStream(AudioStream stream); // Resume audio stream +RLAPI bool IsAudioStreamPlaying(AudioStream stream); // Check if audio stream is playing +RLAPI void StopAudioStream(AudioStream stream); // Stop audio stream +RLAPI void SetAudioStreamVolume(AudioStream stream, float volume); // Set volume for audio stream (1.0 is max level) +RLAPI void SetAudioStreamPitch(AudioStream stream, float pitch); // Set pitch for audio stream (1.0 is base level) +RLAPI void SetAudioStreamPan(AudioStream stream, float pan); // Set pan for audio stream (0.5 is centered) +RLAPI void SetAudioStreamBufferSizeDefault(int size); // Default size for new audio streams +RLAPI void SetAudioStreamCallback(AudioStream stream, AudioCallback callback); // Audio thread callback to request new data + +RLAPI void AttachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Attach audio stream processor to stream, receives the samples as 'float' +RLAPI void DetachAudioStreamProcessor(AudioStream stream, AudioCallback processor); // Detach audio stream processor from stream + +RLAPI void AttachAudioMixedProcessor(AudioCallback processor); // Attach audio stream processor to the entire audio pipeline, receives the samples as 'float' +RLAPI void DetachAudioMixedProcessor(AudioCallback processor); // Detach audio stream processor from the entire audio pipeline + +#if defined(__cplusplus) +} +#endif + +#endif // RAYLIB_H diff --git a/third_party/raylib/include/raymath.h b/third_party/raylib/include/raymath.h new file mode 100644 index 00000000000..5b5e4c74ff6 --- /dev/null +++ b/third_party/raylib/include/raymath.h @@ -0,0 +1,2949 @@ +/********************************************************************************************** +* +* raymath v2.0 - Math functions to work with Vector2, Vector3, Matrix and Quaternions +* +* CONVENTIONS: +* - Matrix structure is defined as row-major (memory layout) but parameters naming AND all +* math operations performed by the library consider the structure as it was column-major +* It is like transposed versions of the matrices are used for all the maths +* It benefits some functions making them cache-friendly and also avoids matrix +* transpositions sometimes required by OpenGL +* Example: In memory order, row0 is [m0 m4 m8 m12] but in semantic math row0 is [m0 m1 m2 m3] +* - Functions are always self-contained, no function use another raymath function inside, +* required code is directly re-implemented inside +* - Functions input parameters are always received by value (2 unavoidable exceptions) +* - Functions use always a "result" variable for return (except C++ operators) +* - Functions are always defined inline +* - Angles are always in radians (DEG2RAD/RAD2DEG macros provided for convenience) +* - No compound literals used to make sure libray is compatible with C++ +* +* CONFIGURATION: +* #define RAYMATH_IMPLEMENTATION +* Generates the implementation of the library into the included file. +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation. +* +* #define RAYMATH_STATIC_INLINE +* Define static inline functions code, so #include header suffices for use. +* This may use up lots of memory. +* +* #define RAYMATH_DISABLE_CPP_OPERATORS +* Disables C++ operator overloads for raymath types. +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2015-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RAYMATH_H +#define RAYMATH_H + +#if defined(RAYMATH_IMPLEMENTATION) && defined(RAYMATH_STATIC_INLINE) + #error "Specifying both RAYMATH_IMPLEMENTATION and RAYMATH_STATIC_INLINE is contradictory" +#endif + +// Function specifiers definition +#if defined(RAYMATH_IMPLEMENTATION) + #if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __declspec(dllexport) extern inline // We are building raylib as a Win32 shared library (.dll) + #elif defined(BUILD_LIBTYPE_SHARED) + #define RMAPI __attribute__((visibility("default"))) // We are building raylib as a Unix shared library (.so/.dylib) + #elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RMAPI __declspec(dllimport) // We are using raylib as a Win32 shared library (.dll) + #else + #define RMAPI extern inline // Provide external definition + #endif +#elif defined(RAYMATH_STATIC_INLINE) + #define RMAPI static inline // Functions may be inlined, no external out-of-line definition +#else + #if defined(__TINYC__) + #define RMAPI static inline // plain inline not supported by tinycc (See issue #435) + #else + #define RMAPI inline // Functions may be inlined or external definition used + #endif +#endif + + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif + +#ifndef EPSILON + #define EPSILON 0.000001f +#endif + +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif + +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +// Get float vector for Matrix +#ifndef MatrixToFloat + #define MatrixToFloat(mat) (MatrixToFloatV(mat).v) +#endif + +// Get float vector for Vector3 +#ifndef Vector3ToFloat + #define Vector3ToFloat(vec) (Vector3ToFloatV(vec).v) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if !defined(RL_VECTOR2_TYPE) +// Vector2 type +typedef struct Vector2 { + float x; + float y; +} Vector2; +#define RL_VECTOR2_TYPE +#endif + +#if !defined(RL_VECTOR3_TYPE) +// Vector3 type +typedef struct Vector3 { + float x; + float y; + float z; +} Vector3; +#define RL_VECTOR3_TYPE +#endif + +#if !defined(RL_VECTOR4_TYPE) +// Vector4 type +typedef struct Vector4 { + float x; + float y; + float z; + float w; +} Vector4; +#define RL_VECTOR4_TYPE +#endif + +#if !defined(RL_QUATERNION_TYPE) +// Quaternion type +typedef Vector4 Quaternion; +#define RL_QUATERNION_TYPE +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix type (OpenGL style 4x4 - right handed, column major) +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// NOTE: Helper types to be used instead of array return types for *ToFloat functions +typedef struct float3 { + float v[3]; +} float3; + +typedef struct float16 { + float v[16]; +} float16; + +#include // Required for: sinf(), cosf(), tan(), atan2f(), sqrtf(), floor(), fminf(), fmaxf(), fabsf() + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Utils math +//---------------------------------------------------------------------------------- + +// Clamp float value +RMAPI float Clamp(float value, float min, float max) +{ + float result = (value < min)? min : value; + + if (result > max) result = max; + + return result; +} + +// Calculate linear interpolation between two floats +RMAPI float Lerp(float start, float end, float amount) +{ + float result = start + amount*(end - start); + + return result; +} + +// Normalize input value within input range +RMAPI float Normalize(float value, float start, float end) +{ + float result = (value - start)/(end - start); + + return result; +} + +// Remap input value within input range to output range +RMAPI float Remap(float value, float inputStart, float inputEnd, float outputStart, float outputEnd) +{ + float result = (value - inputStart)/(inputEnd - inputStart)*(outputEnd - outputStart) + outputStart; + + return result; +} + +// Wrap input value from min to max +RMAPI float Wrap(float value, float min, float max) +{ + float result = value - (max - min)*floorf((value - min)/(max - min)); + + return result; +} + +// Check whether two given floats are almost equal +RMAPI int FloatEquals(float x, float y) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (fabsf(x - y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(x), fabsf(y)))); + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector2 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector2 Vector2Zero(void) +{ + Vector2 result = { 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector2 Vector2One(void) +{ + Vector2 result = { 1.0f, 1.0f }; + + return result; +} + +// Add two vectors (v1 + v2) +RMAPI Vector2 Vector2Add(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x + v2.x, v1.y + v2.y }; + + return result; +} + +// Add vector and float value +RMAPI Vector2 Vector2AddValue(Vector2 v, float add) +{ + Vector2 result = { v.x + add, v.y + add }; + + return result; +} + +// Subtract two vectors (v1 - v2) +RMAPI Vector2 Vector2Subtract(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x - v2.x, v1.y - v2.y }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector2 Vector2SubtractValue(Vector2 v, float sub) +{ + Vector2 result = { v.x - sub, v.y - sub }; + + return result; +} + +// Calculate vector length +RMAPI float Vector2Length(Vector2 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y)); + + return result; +} + +// Calculate vector square length +RMAPI float Vector2LengthSqr(Vector2 v) +{ + float result = (v.x*v.x) + (v.y*v.y); + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector2DotProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y); + + return result; +} + +// Calculate two vectors cross product +RMAPI float Vector2CrossProduct(Vector2 v1, Vector2 v2) +{ + float result = (v1.x*v2.y - v1.y*v2.x); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector2Distance(Vector2 v1, Vector2 v2) +{ + float result = sqrtf((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector2DistanceSqr(Vector2 v1, Vector2 v2) +{ + float result = ((v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y)); + + return result; +} + +// Calculate angle between two vectors +// NOTE: Angle is calculated from origin point (0, 0) +RMAPI float Vector2Angle(Vector2 v1, Vector2 v2) +{ + float result = 0.0f; + + float dot = v1.x*v2.x + v1.y*v2.y; + float det = v1.x*v2.y - v1.y*v2.x; + + result = atan2f(det, dot); + + return result; +} + +// Calculate angle defined by a two vectors line +// NOTE: Parameters need to be normalized +// Current implementation should be aligned with glm::angle +RMAPI float Vector2LineAngle(Vector2 start, Vector2 end) +{ + float result = 0.0f; + + // TODO(10/9/2023): Currently angles move clockwise, determine if this is wanted behavior + result = -atan2f(end.y - start.y, end.x - start.x); + + return result; +} + +// Scale vector (multiply by value) +RMAPI Vector2 Vector2Scale(Vector2 v, float scale) +{ + Vector2 result = { v.x*scale, v.y*scale }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector2 Vector2Multiply(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x*v2.x, v1.y*v2.y }; + + return result; +} + +// Negate vector +RMAPI Vector2 Vector2Negate(Vector2 v) +{ + Vector2 result = { -v.x, -v.y }; + + return result; +} + +// Divide vector by vector +RMAPI Vector2 Vector2Divide(Vector2 v1, Vector2 v2) +{ + Vector2 result = { v1.x/v2.x, v1.y/v2.y }; + + return result; +} + +// Normalize provided vector +RMAPI Vector2 Vector2Normalize(Vector2 v) +{ + Vector2 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + } + + return result; +} + +// Transforms a Vector2 by a given Matrix +RMAPI Vector2 Vector2Transform(Vector2 v, Matrix mat) +{ + Vector2 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = 0; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector2 Vector2Lerp(Vector2 v1, Vector2 v2, float amount) +{ + Vector2 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector2 Vector2Reflect(Vector2 v, Vector2 normal) +{ + Vector2 result = { 0 }; + + float dotProduct = (v.x*normal.x + v.y*normal.y); // Dot product + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector2 Vector2Min(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector2 Vector2Max(Vector2 v1, Vector2 v2) +{ + Vector2 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + + return result; +} + +// Rotate vector by angle +RMAPI Vector2 Vector2Rotate(Vector2 v, float angle) +{ + Vector2 result = { 0 }; + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.x = v.x*cosres - v.y*sinres; + result.y = v.x*sinres + v.y*cosres; + + return result; +} + +// Move Vector towards target +RMAPI Vector2 Vector2MoveTowards(Vector2 v, Vector2 target, float maxDistance) +{ + Vector2 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float value = (dx*dx) + (dy*dy); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector2 Vector2Invert(Vector2 v) +{ + Vector2 result = { 1.0f/v.x, 1.0f/v.y }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector2 Vector2Clamp(Vector2 v, Vector2 min, Vector2 max) +{ + Vector2 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + + return result; +} + +// Clamp the magnitude of the vector between two min and max values +RMAPI Vector2 Vector2ClampValue(Vector2 v, float min, float max) +{ + Vector2 result = v; + + float length = (v.x*v.x) + (v.y*v.y); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element. + if (length < min) + { + scale = min/length; + } + else if (length > max) + { + scale = max/length; + } + + result.x = v.x*scale; + result.y = v.y*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector2Equals(Vector2 p, Vector2 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector2 Vector2Refract(Vector2 v, Vector2 n, float r) +{ + Vector2 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector3 math +//---------------------------------------------------------------------------------- + +// Vector with components value 0.0f +RMAPI Vector3 Vector3Zero(void) +{ + Vector3 result = { 0.0f, 0.0f, 0.0f }; + + return result; +} + +// Vector with components value 1.0f +RMAPI Vector3 Vector3One(void) +{ + Vector3 result = { 1.0f, 1.0f, 1.0f }; + + return result; +} + +// Add two vectors +RMAPI Vector3 Vector3Add(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x + v2.x, v1.y + v2.y, v1.z + v2.z }; + + return result; +} + +// Add vector and float value +RMAPI Vector3 Vector3AddValue(Vector3 v, float add) +{ + Vector3 result = { v.x + add, v.y + add, v.z + add }; + + return result; +} + +// Subtract two vectors +RMAPI Vector3 Vector3Subtract(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x - v2.x, v1.y - v2.y, v1.z - v2.z }; + + return result; +} + +// Subtract vector by float value +RMAPI Vector3 Vector3SubtractValue(Vector3 v, float sub) +{ + Vector3 result = { v.x - sub, v.y - sub, v.z - sub }; + + return result; +} + +// Multiply vector by scalar +RMAPI Vector3 Vector3Scale(Vector3 v, float scalar) +{ + Vector3 result = { v.x*scalar, v.y*scalar, v.z*scalar }; + + return result; +} + +// Multiply vector by vector +RMAPI Vector3 Vector3Multiply(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z }; + + return result; +} + +// Calculate two vectors cross product +RMAPI Vector3 Vector3CrossProduct(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + + return result; +} + +// Calculate one vector perpendicular vector +RMAPI Vector3 Vector3Perpendicular(Vector3 v) +{ + Vector3 result = { 0 }; + + float min = fabsf(v.x); + Vector3 cardinalAxis = {1.0f, 0.0f, 0.0f}; + + if (fabsf(v.y) < min) + { + min = fabsf(v.y); + Vector3 tmp = {0.0f, 1.0f, 0.0f}; + cardinalAxis = tmp; + } + + if (fabsf(v.z) < min) + { + Vector3 tmp = {0.0f, 0.0f, 1.0f}; + cardinalAxis = tmp; + } + + // Cross product between vectors + result.x = v.y*cardinalAxis.z - v.z*cardinalAxis.y; + result.y = v.z*cardinalAxis.x - v.x*cardinalAxis.z; + result.z = v.x*cardinalAxis.y - v.y*cardinalAxis.x; + + return result; +} + +// Calculate vector length +RMAPI float Vector3Length(const Vector3 v) +{ + float result = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + + return result; +} + +// Calculate vector square length +RMAPI float Vector3LengthSqr(const Vector3 v) +{ + float result = v.x*v.x + v.y*v.y + v.z*v.z; + + return result; +} + +// Calculate two vectors dot product +RMAPI float Vector3DotProduct(Vector3 v1, Vector3 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector3Distance(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = sqrtf(dx*dx + dy*dy + dz*dz); + + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector3DistanceSqr(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + float dx = v2.x - v1.x; + float dy = v2.y - v1.y; + float dz = v2.z - v1.z; + result = dx*dx + dy*dy + dz*dz; + + return result; +} + +// Calculate angle between two vectors +RMAPI float Vector3Angle(Vector3 v1, Vector3 v2) +{ + float result = 0.0f; + + Vector3 cross = { v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x }; + float len = sqrtf(cross.x*cross.x + cross.y*cross.y + cross.z*cross.z); + float dot = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + result = atan2f(len, dot); + + return result; +} + +// Negate provided vector (invert direction) +RMAPI Vector3 Vector3Negate(Vector3 v) +{ + Vector3 result = { -v.x, -v.y, -v.z }; + + return result; +} + +// Divide vector by vector +RMAPI Vector3 Vector3Divide(Vector3 v1, Vector3 v2) +{ + Vector3 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z }; + + return result; +} + +// Normalize provided vector +RMAPI Vector3 Vector3Normalize(Vector3 v) +{ + Vector3 result = v; + + float length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length != 0.0f) + { + float ilength = 1.0f/length; + + result.x *= ilength; + result.y *= ilength; + result.z *= ilength; + } + + return result; +} + +//Calculate the projection of the vector v1 on to v2 +RMAPI Vector3 Vector3Project(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v2.x*mag; + result.y = v2.y*mag; + result.z = v2.z*mag; + + return result; +} + +//Calculate the rejection of the vector v1 on to v2 +RMAPI Vector3 Vector3Reject(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + float v1dv2 = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z); + float v2dv2 = (v2.x*v2.x + v2.y*v2.y + v2.z*v2.z); + + float mag = v1dv2/v2dv2; + + result.x = v1.x - (v2.x*mag); + result.y = v1.y - (v2.y*mag); + result.z = v1.z - (v2.z*mag); + + return result; +} + +// Orthonormalize provided vectors +// Makes vectors normalized and orthogonal to each other +// Gram-Schmidt function implementation +RMAPI void Vector3OrthoNormalize(Vector3 *v1, Vector3 *v2) +{ + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(*v1); + Vector3 v = *v1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + v1->x *= ilength; + v1->y *= ilength; + v1->z *= ilength; + + // Vector3CrossProduct(*v1, *v2) + Vector3 vn1 = { v1->y*v2->z - v1->z*v2->y, v1->z*v2->x - v1->x*v2->z, v1->x*v2->y - v1->y*v2->x }; + + // Vector3Normalize(vn1); + v = vn1; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vn1.x *= ilength; + vn1.y *= ilength; + vn1.z *= ilength; + + // Vector3CrossProduct(vn1, *v1) + Vector3 vn2 = { vn1.y*v1->z - vn1.z*v1->y, vn1.z*v1->x - vn1.x*v1->z, vn1.x*v1->y - vn1.y*v1->x }; + + *v2 = vn2; +} + +// Transforms a Vector3 by a given Matrix +RMAPI Vector3 Vector3Transform(Vector3 v, Matrix mat) +{ + Vector3 result = { 0 }; + + float x = v.x; + float y = v.y; + float z = v.z; + + result.x = mat.m0*x + mat.m4*y + mat.m8*z + mat.m12; + result.y = mat.m1*x + mat.m5*y + mat.m9*z + mat.m13; + result.z = mat.m2*x + mat.m6*y + mat.m10*z + mat.m14; + + return result; +} + +// Transform a vector by quaternion rotation +RMAPI Vector3 Vector3RotateByQuaternion(Vector3 v, Quaternion q) +{ + Vector3 result = { 0 }; + + result.x = v.x*(q.x*q.x + q.w*q.w - q.y*q.y - q.z*q.z) + v.y*(2*q.x*q.y - 2*q.w*q.z) + v.z*(2*q.x*q.z + 2*q.w*q.y); + result.y = v.x*(2*q.w*q.z + 2*q.x*q.y) + v.y*(q.w*q.w - q.x*q.x + q.y*q.y - q.z*q.z) + v.z*(-2*q.w*q.x + 2*q.y*q.z); + result.z = v.x*(-2*q.w*q.y + 2*q.x*q.z) + v.y*(2*q.w*q.x + 2*q.y*q.z)+ v.z*(q.w*q.w - q.x*q.x - q.y*q.y + q.z*q.z); + + return result; +} + +// Rotates a vector around an axis +RMAPI Vector3 Vector3RotateByAxisAngle(Vector3 v, Vector3 axis, float angle) +{ + // Using Euler-Rodrigues Formula + // Ref.: https://en.wikipedia.org/w/index.php?title=Euler%E2%80%93Rodrigues_formula + + Vector3 result = v; + + // Vector3Normalize(axis); + float length = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + angle /= 2.0f; + float a = sinf(angle); + float b = axis.x*a; + float c = axis.y*a; + float d = axis.z*a; + a = cosf(angle); + Vector3 w = { b, c, d }; + + // Vector3CrossProduct(w, v) + Vector3 wv = { w.y*v.z - w.z*v.y, w.z*v.x - w.x*v.z, w.x*v.y - w.y*v.x }; + + // Vector3CrossProduct(w, wv) + Vector3 wwv = { w.y*wv.z - w.z*wv.y, w.z*wv.x - w.x*wv.z, w.x*wv.y - w.y*wv.x }; + + // Vector3Scale(wv, 2*a) + a *= 2; + wv.x *= a; + wv.y *= a; + wv.z *= a; + + // Vector3Scale(wwv, 2) + wwv.x *= 2; + wwv.y *= 2; + wwv.z *= 2; + + result.x += wv.x; + result.y += wv.y; + result.z += wv.z; + + result.x += wwv.x; + result.y += wwv.y; + result.z += wwv.z; + + return result; +} + +// Move Vector towards target +RMAPI Vector3 Vector3MoveTowards(Vector3 v, Vector3 target, float maxDistance) +{ + Vector3 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float value = (dx*dx) + (dy*dy) + (dz*dz); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector3 Vector3Lerp(Vector3 v1, Vector3 v2, float amount) +{ + Vector3 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + + return result; +} + +// Calculate cubic hermite interpolation between two vectors and their tangents +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Vector3 Vector3CubicHermite(Vector3 v1, Vector3 tangent1, Vector3 v2, Vector3 tangent2, float amount) +{ + Vector3 result = { 0 }; + + float amountPow2 = amount*amount; + float amountPow3 = amount*amount*amount; + + result.x = (2*amountPow3 - 3*amountPow2 + 1)*v1.x + (amountPow3 - 2*amountPow2 + amount)*tangent1.x + (-2*amountPow3 + 3*amountPow2)*v2.x + (amountPow3 - amountPow2)*tangent2.x; + result.y = (2*amountPow3 - 3*amountPow2 + 1)*v1.y + (amountPow3 - 2*amountPow2 + amount)*tangent1.y + (-2*amountPow3 + 3*amountPow2)*v2.y + (amountPow3 - amountPow2)*tangent2.y; + result.z = (2*amountPow3 - 3*amountPow2 + 1)*v1.z + (amountPow3 - 2*amountPow2 + amount)*tangent1.z + (-2*amountPow3 + 3*amountPow2)*v2.z + (amountPow3 - amountPow2)*tangent2.z; + + return result; +} + +// Calculate reflected vector to normal +RMAPI Vector3 Vector3Reflect(Vector3 v, Vector3 normal) +{ + Vector3 result = { 0 }; + + // I is the original vector + // N is the normal of the incident plane + // R = I - (2*N*(DotProduct[I, N])) + + float dotProduct = (v.x*normal.x + v.y*normal.y + v.z*normal.z); + + result.x = v.x - (2.0f*normal.x)*dotProduct; + result.y = v.y - (2.0f*normal.y)*dotProduct; + result.z = v.z - (2.0f*normal.z)*dotProduct; + + return result; +} + +// Get min value for each pair of components +RMAPI Vector3 Vector3Min(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector3 Vector3Max(Vector3 v1, Vector3 v2) +{ + Vector3 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + + return result; +} + +// Compute barycenter coordinates (u, v, w) for point p with respect to triangle (a, b, c) +// NOTE: Assumes P is on the plane of the triangle +RMAPI Vector3 Vector3Barycenter(Vector3 p, Vector3 a, Vector3 b, Vector3 c) +{ + Vector3 result = { 0 }; + + Vector3 v0 = { b.x - a.x, b.y - a.y, b.z - a.z }; // Vector3Subtract(b, a) + Vector3 v1 = { c.x - a.x, c.y - a.y, c.z - a.z }; // Vector3Subtract(c, a) + Vector3 v2 = { p.x - a.x, p.y - a.y, p.z - a.z }; // Vector3Subtract(p, a) + float d00 = (v0.x*v0.x + v0.y*v0.y + v0.z*v0.z); // Vector3DotProduct(v0, v0) + float d01 = (v0.x*v1.x + v0.y*v1.y + v0.z*v1.z); // Vector3DotProduct(v0, v1) + float d11 = (v1.x*v1.x + v1.y*v1.y + v1.z*v1.z); // Vector3DotProduct(v1, v1) + float d20 = (v2.x*v0.x + v2.y*v0.y + v2.z*v0.z); // Vector3DotProduct(v2, v0) + float d21 = (v2.x*v1.x + v2.y*v1.y + v2.z*v1.z); // Vector3DotProduct(v2, v1) + + float denom = d00*d11 - d01*d01; + + result.y = (d11*d20 - d01*d21)/denom; + result.z = (d00*d21 - d01*d20)/denom; + result.x = 1.0f - (result.z + result.y); + + return result; +} + +// Projects a Vector3 from screen space into object space +// NOTE: We are avoiding calling other raymath functions despite available +RMAPI Vector3 Vector3Unproject(Vector3 source, Matrix projection, Matrix view) +{ + Vector3 result = { 0 }; + + // Calculate unprojected matrix (multiply view matrix by projection matrix) and invert it + Matrix matViewProj = { // MatrixMultiply(view, projection); + view.m0*projection.m0 + view.m1*projection.m4 + view.m2*projection.m8 + view.m3*projection.m12, + view.m0*projection.m1 + view.m1*projection.m5 + view.m2*projection.m9 + view.m3*projection.m13, + view.m0*projection.m2 + view.m1*projection.m6 + view.m2*projection.m10 + view.m3*projection.m14, + view.m0*projection.m3 + view.m1*projection.m7 + view.m2*projection.m11 + view.m3*projection.m15, + view.m4*projection.m0 + view.m5*projection.m4 + view.m6*projection.m8 + view.m7*projection.m12, + view.m4*projection.m1 + view.m5*projection.m5 + view.m6*projection.m9 + view.m7*projection.m13, + view.m4*projection.m2 + view.m5*projection.m6 + view.m6*projection.m10 + view.m7*projection.m14, + view.m4*projection.m3 + view.m5*projection.m7 + view.m6*projection.m11 + view.m7*projection.m15, + view.m8*projection.m0 + view.m9*projection.m4 + view.m10*projection.m8 + view.m11*projection.m12, + view.m8*projection.m1 + view.m9*projection.m5 + view.m10*projection.m9 + view.m11*projection.m13, + view.m8*projection.m2 + view.m9*projection.m6 + view.m10*projection.m10 + view.m11*projection.m14, + view.m8*projection.m3 + view.m9*projection.m7 + view.m10*projection.m11 + view.m11*projection.m15, + view.m12*projection.m0 + view.m13*projection.m4 + view.m14*projection.m8 + view.m15*projection.m12, + view.m12*projection.m1 + view.m13*projection.m5 + view.m14*projection.m9 + view.m15*projection.m13, + view.m12*projection.m2 + view.m13*projection.m6 + view.m14*projection.m10 + view.m15*projection.m14, + view.m12*projection.m3 + view.m13*projection.m7 + view.m14*projection.m11 + view.m15*projection.m15 }; + + // Calculate inverted matrix -> MatrixInvert(matViewProj); + // Cache the matrix values (speed optimization) + float a00 = matViewProj.m0, a01 = matViewProj.m1, a02 = matViewProj.m2, a03 = matViewProj.m3; + float a10 = matViewProj.m4, a11 = matViewProj.m5, a12 = matViewProj.m6, a13 = matViewProj.m7; + float a20 = matViewProj.m8, a21 = matViewProj.m9, a22 = matViewProj.m10, a23 = matViewProj.m11; + float a30 = matViewProj.m12, a31 = matViewProj.m13, a32 = matViewProj.m14, a33 = matViewProj.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + Matrix matViewProjInv = { + (a11*b11 - a12*b10 + a13*b09)*invDet, + (-a01*b11 + a02*b10 - a03*b09)*invDet, + (a31*b05 - a32*b04 + a33*b03)*invDet, + (-a21*b05 + a22*b04 - a23*b03)*invDet, + (-a10*b11 + a12*b08 - a13*b07)*invDet, + (a00*b11 - a02*b08 + a03*b07)*invDet, + (-a30*b05 + a32*b02 - a33*b01)*invDet, + (a20*b05 - a22*b02 + a23*b01)*invDet, + (a10*b10 - a11*b08 + a13*b06)*invDet, + (-a00*b10 + a01*b08 - a03*b06)*invDet, + (a30*b04 - a31*b02 + a33*b00)*invDet, + (-a20*b04 + a21*b02 - a23*b00)*invDet, + (-a10*b09 + a11*b07 - a12*b06)*invDet, + (a00*b09 - a01*b07 + a02*b06)*invDet, + (-a30*b03 + a31*b01 - a32*b00)*invDet, + (a20*b03 - a21*b01 + a22*b00)*invDet }; + + // Create quaternion from source point + Quaternion quat = { source.x, source.y, source.z, 1.0f }; + + // Multiply quat point by unprojecte matrix + Quaternion qtransformed = { // QuaternionTransform(quat, matViewProjInv) + matViewProjInv.m0*quat.x + matViewProjInv.m4*quat.y + matViewProjInv.m8*quat.z + matViewProjInv.m12*quat.w, + matViewProjInv.m1*quat.x + matViewProjInv.m5*quat.y + matViewProjInv.m9*quat.z + matViewProjInv.m13*quat.w, + matViewProjInv.m2*quat.x + matViewProjInv.m6*quat.y + matViewProjInv.m10*quat.z + matViewProjInv.m14*quat.w, + matViewProjInv.m3*quat.x + matViewProjInv.m7*quat.y + matViewProjInv.m11*quat.z + matViewProjInv.m15*quat.w }; + + // Normalized world points in vectors + result.x = qtransformed.x/qtransformed.w; + result.y = qtransformed.y/qtransformed.w; + result.z = qtransformed.z/qtransformed.w; + + return result; +} + +// Get Vector3 as float array +RMAPI float3 Vector3ToFloatV(Vector3 v) +{ + float3 buffer = { 0 }; + + buffer.v[0] = v.x; + buffer.v[1] = v.y; + buffer.v[2] = v.z; + + return buffer; +} + +// Invert the given vector +RMAPI Vector3 Vector3Invert(Vector3 v) +{ + Vector3 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z }; + + return result; +} + +// Clamp the components of the vector between +// min and max values specified by the given vectors +RMAPI Vector3 Vector3Clamp(Vector3 v, Vector3 min, Vector3 max) +{ + Vector3 result = { 0 }; + + result.x = fminf(max.x, fmaxf(min.x, v.x)); + result.y = fminf(max.y, fmaxf(min.y, v.y)); + result.z = fminf(max.z, fmaxf(min.z, v.z)); + + return result; +} + +// Clamp the magnitude of the vector between two values +RMAPI Vector3 Vector3ClampValue(Vector3 v, float min, float max) +{ + Vector3 result = v; + + float length = (v.x*v.x) + (v.y*v.y) + (v.z*v.z); + if (length > 0.0f) + { + length = sqrtf(length); + + float scale = 1; // By default, 1 as the neutral element. + if (length < min) + { + scale = min/length; + } + else if (length > max) + { + scale = max/length; + } + + result.x = v.x*scale; + result.y = v.y*scale; + result.z = v.z*scale; + } + + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector3Equals(Vector3 p, Vector3 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))); + + return result; +} + +// Compute the direction of a refracted ray +// v: normalized direction of the incoming ray +// n: normalized normal vector of the interface of two optical media +// r: ratio of the refractive index of the medium from where the ray comes +// to the refractive index of the medium on the other side of the surface +RMAPI Vector3 Vector3Refract(Vector3 v, Vector3 n, float r) +{ + Vector3 result = { 0 }; + + float dot = v.x*n.x + v.y*n.y + v.z*n.z; + float d = 1.0f - r*r*(1.0f - dot*dot); + + if (d >= 0.0f) + { + d = sqrtf(d); + v.x = r*v.x - (r*dot + d)*n.x; + v.y = r*v.y - (r*dot + d)*n.y; + v.z = r*v.z - (r*dot + d)*n.z; + + result = v; + } + + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vector4 math +//---------------------------------------------------------------------------------- + +RMAPI Vector4 Vector4Zero(void) +{ + Vector4 result = { 0.0f, 0.0f, 0.0f, 0.0f }; + return result; +} + +RMAPI Vector4 Vector4One(void) +{ + Vector4 result = { 1.0f, 1.0f, 1.0f, 1.0f }; + return result; +} + +RMAPI Vector4 Vector4Add(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x + v2.x, + v1.y + v2.y, + v1.z + v2.z, + v1.w + v2.w + }; + return result; +} + +RMAPI Vector4 Vector4AddValue(Vector4 v, float add) +{ + Vector4 result = { + v.x + add, + v.y + add, + v.z + add, + v.w + add + }; + return result; +} + +RMAPI Vector4 Vector4Subtract(Vector4 v1, Vector4 v2) +{ + Vector4 result = { + v1.x - v2.x, + v1.y - v2.y, + v1.z - v2.z, + v1.w - v2.w + }; + return result; +} + +RMAPI Vector4 Vector4SubtractValue(Vector4 v, float add) +{ + Vector4 result = { + v.x - add, + v.y - add, + v.z - add, + v.w - add + }; + return result; +} + +RMAPI float Vector4Length(Vector4 v) +{ + float result = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + return result; +} + +RMAPI float Vector4LengthSqr(Vector4 v) +{ + float result = (v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w); + return result; +} + +RMAPI float Vector4DotProduct(Vector4 v1, Vector4 v2) +{ + float result = (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z + v1.w*v2.w); + return result; +} + +// Calculate distance between two vectors +RMAPI float Vector4Distance(Vector4 v1, Vector4 v2) +{ + float result = sqrtf( + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w)); + return result; +} + +// Calculate square distance between two vectors +RMAPI float Vector4DistanceSqr(Vector4 v1, Vector4 v2) +{ + float result = + (v1.x - v2.x)*(v1.x - v2.x) + (v1.y - v2.y)*(v1.y - v2.y) + + (v1.z - v2.z)*(v1.z - v2.z) + (v1.w - v2.w)*(v1.w - v2.w); + + return result; +} + +RMAPI Vector4 Vector4Scale(Vector4 v, float scale) +{ + Vector4 result = { v.x*scale, v.y*scale, v.z*scale, v.w*scale }; + return result; +} + +// Multiply vector by vector +RMAPI Vector4 Vector4Multiply(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x*v2.x, v1.y*v2.y, v1.z*v2.z, v1.w*v2.w }; + return result; +} + +// Negate vector +RMAPI Vector4 Vector4Negate(Vector4 v) +{ + Vector4 result = { -v.x, -v.y, -v.z, -v.w }; + return result; +} + +// Divide vector by vector +RMAPI Vector4 Vector4Divide(Vector4 v1, Vector4 v2) +{ + Vector4 result = { v1.x/v2.x, v1.y/v2.y, v1.z/v2.z, v1.w/v2.w }; + return result; +} + +// Normalize provided vector +RMAPI Vector4 Vector4Normalize(Vector4 v) +{ + Vector4 result = { 0 }; + float length = sqrtf((v.x*v.x) + (v.y*v.y) + (v.z*v.z) + (v.w*v.w)); + + if (length > 0) + { + float ilength = 1.0f/length; + result.x = v.x*ilength; + result.y = v.y*ilength; + result.z = v.z*ilength; + result.w = v.w*ilength; + } + + return result; +} + +// Get min value for each pair of components +RMAPI Vector4 Vector4Min(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fminf(v1.x, v2.x); + result.y = fminf(v1.y, v2.y); + result.z = fminf(v1.z, v2.z); + result.w = fminf(v1.w, v2.w); + + return result; +} + +// Get max value for each pair of components +RMAPI Vector4 Vector4Max(Vector4 v1, Vector4 v2) +{ + Vector4 result = { 0 }; + + result.x = fmaxf(v1.x, v2.x); + result.y = fmaxf(v1.y, v2.y); + result.z = fmaxf(v1.z, v2.z); + result.w = fmaxf(v1.w, v2.w); + + return result; +} + +// Calculate linear interpolation between two vectors +RMAPI Vector4 Vector4Lerp(Vector4 v1, Vector4 v2, float amount) +{ + Vector4 result = { 0 }; + + result.x = v1.x + amount*(v2.x - v1.x); + result.y = v1.y + amount*(v2.y - v1.y); + result.z = v1.z + amount*(v2.z - v1.z); + result.w = v1.w + amount*(v2.w - v1.w); + + return result; +} + +// Move Vector towards target +RMAPI Vector4 Vector4MoveTowards(Vector4 v, Vector4 target, float maxDistance) +{ + Vector4 result = { 0 }; + + float dx = target.x - v.x; + float dy = target.y - v.y; + float dz = target.z - v.z; + float dw = target.w - v.w; + float value = (dx*dx) + (dy*dy) + (dz*dz) + (dw*dw); + + if ((value == 0) || ((maxDistance >= 0) && (value <= maxDistance*maxDistance))) return target; + + float dist = sqrtf(value); + + result.x = v.x + dx/dist*maxDistance; + result.y = v.y + dy/dist*maxDistance; + result.z = v.z + dz/dist*maxDistance; + result.w = v.w + dw/dist*maxDistance; + + return result; +} + +// Invert the given vector +RMAPI Vector4 Vector4Invert(Vector4 v) +{ + Vector4 result = { 1.0f/v.x, 1.0f/v.y, 1.0f/v.z, 1.0f/v.w }; + return result; +} + +// Check whether two given vectors are almost equal +RMAPI int Vector4Equals(Vector4 p, Vector4 q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = ((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w))))); + return result; +} + + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix math +//---------------------------------------------------------------------------------- + +// Compute matrix determinant +RMAPI float MatrixDeterminant(Matrix mat) +{ + float result = 0.0f; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + result = a30*a21*a12*a03 - a20*a31*a12*a03 - a30*a11*a22*a03 + a10*a31*a22*a03 + + a20*a11*a32*a03 - a10*a21*a32*a03 - a30*a21*a02*a13 + a20*a31*a02*a13 + + a30*a01*a22*a13 - a00*a31*a22*a13 - a20*a01*a32*a13 + a00*a21*a32*a13 + + a30*a11*a02*a23 - a10*a31*a02*a23 - a30*a01*a12*a23 + a00*a31*a12*a23 + + a10*a01*a32*a23 - a00*a11*a32*a23 - a20*a11*a02*a33 + a10*a21*a02*a33 + + a20*a01*a12*a33 - a00*a21*a12*a33 - a10*a01*a22*a33 + a00*a11*a22*a33; + + return result; +} + +// Get the trace of the matrix (sum of the values along the diagonal) +RMAPI float MatrixTrace(Matrix mat) +{ + float result = (mat.m0 + mat.m5 + mat.m10 + mat.m15); + + return result; +} + +// Transposes provided matrix +RMAPI Matrix MatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +RMAPI Matrix MatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +// Get identity matrix +RMAPI Matrix MatrixIdentity(void) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Add two matrices +RMAPI Matrix MatrixAdd(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 + right.m0; + result.m1 = left.m1 + right.m1; + result.m2 = left.m2 + right.m2; + result.m3 = left.m3 + right.m3; + result.m4 = left.m4 + right.m4; + result.m5 = left.m5 + right.m5; + result.m6 = left.m6 + right.m6; + result.m7 = left.m7 + right.m7; + result.m8 = left.m8 + right.m8; + result.m9 = left.m9 + right.m9; + result.m10 = left.m10 + right.m10; + result.m11 = left.m11 + right.m11; + result.m12 = left.m12 + right.m12; + result.m13 = left.m13 + right.m13; + result.m14 = left.m14 + right.m14; + result.m15 = left.m15 + right.m15; + + return result; +} + +// Subtract two matrices (left - right) +RMAPI Matrix MatrixSubtract(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0 - right.m0; + result.m1 = left.m1 - right.m1; + result.m2 = left.m2 - right.m2; + result.m3 = left.m3 - right.m3; + result.m4 = left.m4 - right.m4; + result.m5 = left.m5 - right.m5; + result.m6 = left.m6 - right.m6; + result.m7 = left.m7 - right.m7; + result.m8 = left.m8 - right.m8; + result.m9 = left.m9 - right.m9; + result.m10 = left.m10 - right.m10; + result.m11 = left.m11 - right.m11; + result.m12 = left.m12 - right.m12; + result.m13 = left.m13 - right.m13; + result.m14 = left.m14 - right.m14; + result.m15 = left.m15 - right.m15; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +RMAPI Matrix MatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Get translation matrix +RMAPI Matrix MatrixTranslate(float x, float y, float z) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Create rotation matrix from axis and angle +// NOTE: Angle should be provided in radians +RMAPI Matrix MatrixRotate(Vector3 axis, float angle) +{ + Matrix result = { 0 }; + + float x = axis.x, y = axis.y, z = axis.z; + + float lengthSquared = x*x + y*y + z*z; + + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float ilength = 1.0f/sqrtf(lengthSquared); + x *= ilength; + y *= ilength; + z *= ilength; + } + + float sinres = sinf(angle); + float cosres = cosf(angle); + float t = 1.0f - cosres; + + result.m0 = x*x*t + cosres; + result.m1 = y*x*t + z*sinres; + result.m2 = z*x*t - y*sinres; + result.m3 = 0.0f; + + result.m4 = x*y*t - z*sinres; + result.m5 = y*y*t + cosres; + result.m6 = z*y*t + x*sinres; + result.m7 = 0.0f; + + result.m8 = x*z*t + y*sinres; + result.m9 = y*z*t - x*sinres; + result.m10 = z*z*t + cosres; + result.m11 = 0.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = 0.0f; + result.m15 = 1.0f; + + return result; +} + +// Get x-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateX(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m5 = cosres; + result.m6 = sinres; + result.m9 = -sinres; + result.m10 = cosres; + + return result; +} + +// Get y-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateY(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m2 = -sinres; + result.m8 = sinres; + result.m10 = cosres; + + return result; +} + +// Get z-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZ(float angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosres = cosf(angle); + float sinres = sinf(angle); + + result.m0 = cosres; + result.m1 = sinres; + result.m4 = -sinres; + result.m5 = cosres; + + return result; +} + + +// Get xyz-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateXYZ(Vector3 angle) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float cosz = cosf(-angle.z); + float sinz = sinf(-angle.z); + float cosy = cosf(-angle.y); + float siny = sinf(-angle.y); + float cosx = cosf(-angle.x); + float sinx = sinf(-angle.x); + + result.m0 = cosz*cosy; + result.m1 = (cosz*siny*sinx) - (sinz*cosx); + result.m2 = (cosz*siny*cosx) + (sinz*sinx); + + result.m4 = sinz*cosy; + result.m5 = (sinz*siny*sinx) + (cosz*cosx); + result.m6 = (sinz*siny*cosx) - (cosz*sinx); + + result.m8 = -siny; + result.m9 = cosy*sinx; + result.m10= cosy*cosx; + + return result; +} + +// Get zyx-rotation matrix +// NOTE: Angle must be provided in radians +RMAPI Matrix MatrixRotateZYX(Vector3 angle) +{ + Matrix result = { 0 }; + + float cz = cosf(angle.z); + float sz = sinf(angle.z); + float cy = cosf(angle.y); + float sy = sinf(angle.y); + float cx = cosf(angle.x); + float sx = sinf(angle.x); + + result.m0 = cz*cy; + result.m4 = cz*sy*sx - cx*sz; + result.m8 = sz*sx + cz*cx*sy; + result.m12 = 0; + + result.m1 = cy*sz; + result.m5 = cz*cx + sz*sy*sx; + result.m9 = cx*sz*sy - cz*sx; + result.m13 = 0; + + result.m2 = -sy; + result.m6 = cy*sx; + result.m10 = cy*cx; + result.m14 = 0; + + result.m3 = 0; + result.m7 = 0; + result.m11 = 0; + result.m15 = 1; + + return result; +} + +// Get scaling matrix +RMAPI Matrix MatrixScale(float x, float y, float z) +{ + Matrix result = { x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Get perspective projection matrix +RMAPI Matrix MatrixFrustum(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + + result.m4 = 0.0f; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + + result.m12 = 0.0f; + result.m13 = 0.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + result.m15 = 0.0f; + + return result; +} + +// Get perspective projection matrix +// NOTE: Fovy angle must be provided in radians +RMAPI Matrix MatrixPerspective(double fovY, double aspect, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + double top = nearPlane*tan(fovY*0.5); + double bottom = -top; + double right = top*aspect; + double left = -right; + + // MatrixFrustum(-right, right, -top, top, near, far); + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = ((float)nearPlane*2.0f)/rl; + result.m5 = ((float)nearPlane*2.0f)/tb; + result.m8 = ((float)right + (float)left)/rl; + result.m9 = ((float)top + (float)bottom)/tb; + result.m10 = -((float)farPlane + (float)nearPlane)/fn; + result.m11 = -1.0f; + result.m14 = -((float)farPlane*(float)nearPlane*2.0f)/fn; + + return result; +} + +// Get orthographic projection matrix +RMAPI Matrix MatrixOrtho(double left, double right, double bottom, double top, double nearPlane, double farPlane) +{ + Matrix result = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(farPlane - nearPlane); + + result.m0 = 2.0f/rl; + result.m1 = 0.0f; + result.m2 = 0.0f; + result.m3 = 0.0f; + result.m4 = 0.0f; + result.m5 = 2.0f/tb; + result.m6 = 0.0f; + result.m7 = 0.0f; + result.m8 = 0.0f; + result.m9 = 0.0f; + result.m10 = -2.0f/fn; + result.m11 = 0.0f; + result.m12 = -((float)left + (float)right)/rl; + result.m13 = -((float)top + (float)bottom)/tb; + result.m14 = -((float)farPlane + (float)nearPlane)/fn; + result.m15 = 1.0f; + + return result; +} + +// Get camera look-at matrix (view matrix) +RMAPI Matrix MatrixLookAt(Vector3 eye, Vector3 target, Vector3 up) +{ + Matrix result = { 0 }; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Subtract(eye, target) + Vector3 vz = { eye.x - target.x, eye.y - target.y, eye.z - target.z }; + + // Vector3Normalize(vz) + Vector3 v = vz; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vz.x *= ilength; + vz.y *= ilength; + vz.z *= ilength; + + // Vector3CrossProduct(up, vz) + Vector3 vx = { up.y*vz.z - up.z*vz.y, up.z*vz.x - up.x*vz.z, up.x*vz.y - up.y*vz.x }; + + // Vector3Normalize(x) + v = vx; + length = sqrtf(v.x*v.x + v.y*v.y + v.z*v.z); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + vx.x *= ilength; + vx.y *= ilength; + vx.z *= ilength; + + // Vector3CrossProduct(vz, vx) + Vector3 vy = { vz.y*vx.z - vz.z*vx.y, vz.z*vx.x - vz.x*vx.z, vz.x*vx.y - vz.y*vx.x }; + + result.m0 = vx.x; + result.m1 = vy.x; + result.m2 = vz.x; + result.m3 = 0.0f; + result.m4 = vx.y; + result.m5 = vy.y; + result.m6 = vz.y; + result.m7 = 0.0f; + result.m8 = vx.z; + result.m9 = vy.z; + result.m10 = vz.z; + result.m11 = 0.0f; + result.m12 = -(vx.x*eye.x + vx.y*eye.y + vx.z*eye.z); // Vector3DotProduct(vx, eye) + result.m13 = -(vy.x*eye.x + vy.y*eye.y + vy.z*eye.z); // Vector3DotProduct(vy, eye) + result.m14 = -(vz.x*eye.x + vz.y*eye.y + vz.z*eye.z); // Vector3DotProduct(vz, eye) + result.m15 = 1.0f; + + return result; +} + +// Get float array of matrix data +RMAPI float16 MatrixToFloatV(Matrix mat) +{ + float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Quaternion math +//---------------------------------------------------------------------------------- + +// Add two quaternions +RMAPI Quaternion QuaternionAdd(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x + q2.x, q1.y + q2.y, q1.z + q2.z, q1.w + q2.w}; + + return result; +} + +// Add quaternion and float value +RMAPI Quaternion QuaternionAddValue(Quaternion q, float add) +{ + Quaternion result = {q.x + add, q.y + add, q.z + add, q.w + add}; + + return result; +} + +// Subtract two quaternions +RMAPI Quaternion QuaternionSubtract(Quaternion q1, Quaternion q2) +{ + Quaternion result = {q1.x - q2.x, q1.y - q2.y, q1.z - q2.z, q1.w - q2.w}; + + return result; +} + +// Subtract quaternion and float value +RMAPI Quaternion QuaternionSubtractValue(Quaternion q, float sub) +{ + Quaternion result = {q.x - sub, q.y - sub, q.z - sub, q.w - sub}; + + return result; +} + +// Get identity quaternion +RMAPI Quaternion QuaternionIdentity(void) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + return result; +} + +// Computes the length of a quaternion +RMAPI float QuaternionLength(Quaternion q) +{ + float result = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + + return result; +} + +// Normalize provided quaternion +RMAPI Quaternion QuaternionNormalize(Quaternion q) +{ + Quaternion result = { 0 }; + + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Invert provided quaternion +RMAPI Quaternion QuaternionInvert(Quaternion q) +{ + Quaternion result = q; + + float lengthSq = q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w; + + if (lengthSq != 0.0f) + { + float invLength = 1.0f/lengthSq; + + result.x *= -invLength; + result.y *= -invLength; + result.z *= -invLength; + result.w *= invLength; + } + + return result; +} + +// Calculate two quaternion multiplication +RMAPI Quaternion QuaternionMultiply(Quaternion q1, Quaternion q2) +{ + Quaternion result = { 0 }; + + float qax = q1.x, qay = q1.y, qaz = q1.z, qaw = q1.w; + float qbx = q2.x, qby = q2.y, qbz = q2.z, qbw = q2.w; + + result.x = qax*qbw + qaw*qbx + qay*qbz - qaz*qby; + result.y = qay*qbw + qaw*qby + qaz*qbx - qax*qbz; + result.z = qaz*qbw + qaw*qbz + qax*qby - qay*qbx; + result.w = qaw*qbw - qax*qbx - qay*qby - qaz*qbz; + + return result; +} + +// Scale quaternion by float value +RMAPI Quaternion QuaternionScale(Quaternion q, float mul) +{ + Quaternion result = { 0 }; + + result.x = q.x*mul; + result.y = q.y*mul; + result.z = q.z*mul; + result.w = q.w*mul; + + return result; +} + +// Divide two quaternions +RMAPI Quaternion QuaternionDivide(Quaternion q1, Quaternion q2) +{ + Quaternion result = { q1.x/q2.x, q1.y/q2.y, q1.z/q2.z, q1.w/q2.w }; + + return result; +} + +// Calculate linear interpolation between two quaternions +RMAPI Quaternion QuaternionLerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + return result; +} + +// Calculate slerp-optimized interpolation between two quaternions +RMAPI Quaternion QuaternionNlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + + // QuaternionLerp(q1, q2, amount) + result.x = q1.x + amount*(q2.x - q1.x); + result.y = q1.y + amount*(q2.y - q1.y); + result.z = q1.z + amount*(q2.z - q1.z); + result.w = q1.w + amount*(q2.w - q1.w); + + // QuaternionNormalize(q); + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Calculates spherical linear interpolation between two quaternions +RMAPI Quaternion QuaternionSlerp(Quaternion q1, Quaternion q2, float amount) +{ + Quaternion result = { 0 }; + +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + float cosHalfTheta = q1.x*q2.x + q1.y*q2.y + q1.z*q2.z + q1.w*q2.w; + + if (cosHalfTheta < 0) + { + q2.x = -q2.x; q2.y = -q2.y; q2.z = -q2.z; q2.w = -q2.w; + cosHalfTheta = -cosHalfTheta; + } + + if (fabsf(cosHalfTheta) >= 1.0f) result = q1; + else if (cosHalfTheta > 0.95f) result = QuaternionNlerp(q1, q2, amount); + else + { + float halfTheta = acosf(cosHalfTheta); + float sinHalfTheta = sqrtf(1.0f - cosHalfTheta*cosHalfTheta); + + if (fabsf(sinHalfTheta) < EPSILON) + { + result.x = (q1.x*0.5f + q2.x*0.5f); + result.y = (q1.y*0.5f + q2.y*0.5f); + result.z = (q1.z*0.5f + q2.z*0.5f); + result.w = (q1.w*0.5f + q2.w*0.5f); + } + else + { + float ratioA = sinf((1 - amount)*halfTheta)/sinHalfTheta; + float ratioB = sinf(amount*halfTheta)/sinHalfTheta; + + result.x = (q1.x*ratioA + q2.x*ratioB); + result.y = (q1.y*ratioA + q2.y*ratioB); + result.z = (q1.z*ratioA + q2.z*ratioB); + result.w = (q1.w*ratioA + q2.w*ratioB); + } + } + + return result; +} + +// Calculate quaternion cubic spline interpolation using Cubic Hermite Spline algorithm +// as described in the GLTF 2.0 specification: https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#interpolation-cubic +RMAPI Quaternion QuaternionCubicHermiteSpline(Quaternion q1, Quaternion outTangent1, Quaternion q2, Quaternion inTangent2, float t) +{ + float t2 = t*t; + float t3 = t2*t; + float h00 = 2*t3 - 3*t2 + 1; + float h10 = t3 - 2*t2 + t; + float h01 = -2*t3 + 3*t2; + float h11 = t3 - t2; + + Quaternion p0 = QuaternionScale(q1, h00); + Quaternion m0 = QuaternionScale(outTangent1, h10); + Quaternion p1 = QuaternionScale(q2, h01); + Quaternion m1 = QuaternionScale(inTangent2, h11); + + Quaternion result = { 0 }; + + result = QuaternionAdd(p0, m0); + result = QuaternionAdd(result, p1); + result = QuaternionAdd(result, m1); + result = QuaternionNormalize(result); + + return result; +} + +// Calculate quaternion based on the rotation from one vector to another +RMAPI Quaternion QuaternionFromVector3ToVector3(Vector3 from, Vector3 to) +{ + Quaternion result = { 0 }; + + float cos2Theta = (from.x*to.x + from.y*to.y + from.z*to.z); // Vector3DotProduct(from, to) + Vector3 cross = { from.y*to.z - from.z*to.y, from.z*to.x - from.x*to.z, from.x*to.y - from.y*to.x }; // Vector3CrossProduct(from, to) + + result.x = cross.x; + result.y = cross.y; + result.z = cross.z; + result.w = 1.0f + cos2Theta; + + // QuaternionNormalize(q); + // NOTE: Normalize to essentially nlerp the original and identity to 0.5 + Quaternion q = result; + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + + return result; +} + +// Get a quaternion for a given rotation matrix +RMAPI Quaternion QuaternionFromMatrix(Matrix mat) +{ + Quaternion result = { 0 }; + + float fourWSquaredMinus1 = mat.m0 + mat.m5 + mat.m10; + float fourXSquaredMinus1 = mat.m0 - mat.m5 - mat.m10; + float fourYSquaredMinus1 = mat.m5 - mat.m0 - mat.m10; + float fourZSquaredMinus1 = mat.m10 - mat.m0 - mat.m5; + + int biggestIndex = 0; + float fourBiggestSquaredMinus1 = fourWSquaredMinus1; + if (fourXSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourXSquaredMinus1; + biggestIndex = 1; + } + + if (fourYSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourYSquaredMinus1; + biggestIndex = 2; + } + + if (fourZSquaredMinus1 > fourBiggestSquaredMinus1) + { + fourBiggestSquaredMinus1 = fourZSquaredMinus1; + biggestIndex = 3; + } + + float biggestVal = sqrtf(fourBiggestSquaredMinus1 + 1.0f)*0.5f; + float mult = 0.25f/biggestVal; + + switch (biggestIndex) + { + case 0: + result.w = biggestVal; + result.x = (mat.m6 - mat.m9)*mult; + result.y = (mat.m8 - mat.m2)*mult; + result.z = (mat.m1 - mat.m4)*mult; + break; + case 1: + result.x = biggestVal; + result.w = (mat.m6 - mat.m9)*mult; + result.y = (mat.m1 + mat.m4)*mult; + result.z = (mat.m8 + mat.m2)*mult; + break; + case 2: + result.y = biggestVal; + result.w = (mat.m8 - mat.m2)*mult; + result.x = (mat.m1 + mat.m4)*mult; + result.z = (mat.m6 + mat.m9)*mult; + break; + case 3: + result.z = biggestVal; + result.w = (mat.m1 - mat.m4)*mult; + result.x = (mat.m8 + mat.m2)*mult; + result.y = (mat.m6 + mat.m9)*mult; + break; + } + + return result; +} + +// Get a matrix for a given quaternion +RMAPI Matrix QuaternionToMatrix(Quaternion q) +{ + Matrix result = { 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f }; // MatrixIdentity() + + float a2 = q.x*q.x; + float b2 = q.y*q.y; + float c2 = q.z*q.z; + float ac = q.x*q.z; + float ab = q.x*q.y; + float bc = q.y*q.z; + float ad = q.w*q.x; + float bd = q.w*q.y; + float cd = q.w*q.z; + + result.m0 = 1 - 2*(b2 + c2); + result.m1 = 2*(ab + cd); + result.m2 = 2*(ac - bd); + + result.m4 = 2*(ab - cd); + result.m5 = 1 - 2*(a2 + c2); + result.m6 = 2*(bc + ad); + + result.m8 = 2*(ac + bd); + result.m9 = 2*(bc - ad); + result.m10 = 1 - 2*(a2 + b2); + + return result; +} + +// Get rotation quaternion for an angle and axis +// NOTE: Angle must be provided in radians +RMAPI Quaternion QuaternionFromAxisAngle(Vector3 axis, float angle) +{ + Quaternion result = { 0.0f, 0.0f, 0.0f, 1.0f }; + + float axisLength = sqrtf(axis.x*axis.x + axis.y*axis.y + axis.z*axis.z); + + if (axisLength != 0.0f) + { + angle *= 0.5f; + + float length = 0.0f; + float ilength = 0.0f; + + // Vector3Normalize(axis) + length = axisLength; + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + axis.x *= ilength; + axis.y *= ilength; + axis.z *= ilength; + + float sinres = sinf(angle); + float cosres = cosf(angle); + + result.x = axis.x*sinres; + result.y = axis.y*sinres; + result.z = axis.z*sinres; + result.w = cosres; + + // QuaternionNormalize(q); + Quaternion q = result; + length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + ilength = 1.0f/length; + result.x = q.x*ilength; + result.y = q.y*ilength; + result.z = q.z*ilength; + result.w = q.w*ilength; + } + + return result; +} + +// Get the rotation angle and axis for a given quaternion +RMAPI void QuaternionToAxisAngle(Quaternion q, Vector3 *outAxis, float *outAngle) +{ + if (fabsf(q.w) > 1.0f) + { + // QuaternionNormalize(q); + float length = sqrtf(q.x*q.x + q.y*q.y + q.z*q.z + q.w*q.w); + if (length == 0.0f) length = 1.0f; + float ilength = 1.0f/length; + + q.x = q.x*ilength; + q.y = q.y*ilength; + q.z = q.z*ilength; + q.w = q.w*ilength; + } + + Vector3 resAxis = { 0.0f, 0.0f, 0.0f }; + float resAngle = 2.0f*acosf(q.w); + float den = sqrtf(1.0f - q.w*q.w); + + if (den > EPSILON) + { + resAxis.x = q.x/den; + resAxis.y = q.y/den; + resAxis.z = q.z/den; + } + else + { + // This occurs when the angle is zero. + // Not a problem: just set an arbitrary normalized axis. + resAxis.x = 1.0f; + } + + *outAxis = resAxis; + *outAngle = resAngle; +} + +// Get the quaternion equivalent to Euler angles +// NOTE: Rotation order is ZYX +RMAPI Quaternion QuaternionFromEuler(float pitch, float yaw, float roll) +{ + Quaternion result = { 0 }; + + float x0 = cosf(pitch*0.5f); + float x1 = sinf(pitch*0.5f); + float y0 = cosf(yaw*0.5f); + float y1 = sinf(yaw*0.5f); + float z0 = cosf(roll*0.5f); + float z1 = sinf(roll*0.5f); + + result.x = x1*y0*z0 - x0*y1*z1; + result.y = x0*y1*z0 + x1*y0*z1; + result.z = x0*y0*z1 - x1*y1*z0; + result.w = x0*y0*z0 + x1*y1*z1; + + return result; +} + +// Get the Euler angles equivalent to quaternion (roll, pitch, yaw) +// NOTE: Angles are returned in a Vector3 struct in radians +RMAPI Vector3 QuaternionToEuler(Quaternion q) +{ + Vector3 result = { 0 }; + + // Roll (x-axis rotation) + float x0 = 2.0f*(q.w*q.x + q.y*q.z); + float x1 = 1.0f - 2.0f*(q.x*q.x + q.y*q.y); + result.x = atan2f(x0, x1); + + // Pitch (y-axis rotation) + float y0 = 2.0f*(q.w*q.y - q.z*q.x); + y0 = y0 > 1.0f ? 1.0f : y0; + y0 = y0 < -1.0f ? -1.0f : y0; + result.y = asinf(y0); + + // Yaw (z-axis rotation) + float z0 = 2.0f*(q.w*q.z + q.x*q.y); + float z1 = 1.0f - 2.0f*(q.y*q.y + q.z*q.z); + result.z = atan2f(z0, z1); + + return result; +} + +// Transform a quaternion given a transformation matrix +RMAPI Quaternion QuaternionTransform(Quaternion q, Matrix mat) +{ + Quaternion result = { 0 }; + + result.x = mat.m0*q.x + mat.m4*q.y + mat.m8*q.z + mat.m12*q.w; + result.y = mat.m1*q.x + mat.m5*q.y + mat.m9*q.z + mat.m13*q.w; + result.z = mat.m2*q.x + mat.m6*q.y + mat.m10*q.z + mat.m14*q.w; + result.w = mat.m3*q.x + mat.m7*q.y + mat.m11*q.z + mat.m15*q.w; + + return result; +} + +// Check whether two given quaternions are almost equal +RMAPI int QuaternionEquals(Quaternion p, Quaternion q) +{ +#if !defined(EPSILON) + #define EPSILON 0.000001f +#endif + + int result = (((fabsf(p.x - q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y - q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z - q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w - q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))) || + (((fabsf(p.x + q.x)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.x), fabsf(q.x))))) && + ((fabsf(p.y + q.y)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.y), fabsf(q.y))))) && + ((fabsf(p.z + q.z)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.z), fabsf(q.z))))) && + ((fabsf(p.w + q.w)) <= (EPSILON*fmaxf(1.0f, fmaxf(fabsf(p.w), fabsf(q.w)))))); + + return result; +} + +// Decompose a transformation matrix into its rotational, translational and scaling components +RMAPI void MatrixDecompose(Matrix mat, Vector3 *translation, Quaternion *rotation, Vector3 *scale) +{ + // Extract translation. + translation->x = mat.m12; + translation->y = mat.m13; + translation->z = mat.m14; + + // Extract upper-left for determinant computation + const float a = mat.m0; + const float b = mat.m4; + const float c = mat.m8; + const float d = mat.m1; + const float e = mat.m5; + const float f = mat.m9; + const float g = mat.m2; + const float h = mat.m6; + const float i = mat.m10; + const float A = e*i - f*h; + const float B = f*g - d*i; + const float C = d*h - e*g; + + // Extract scale + const float det = a*A + b*B + c*C; + Vector3 abc = { a, b, c }; + Vector3 def = { d, e, f }; + Vector3 ghi = { g, h, i }; + + float scalex = Vector3Length(abc); + float scaley = Vector3Length(def); + float scalez = Vector3Length(ghi); + Vector3 s = { scalex, scaley, scalez }; + + if (det < 0) s = Vector3Negate(s); + + *scale = s; + + // Remove scale from the matrix if it is not close to zero + Matrix clone = mat; + if (!FloatEquals(det, 0)) + { + clone.m0 /= s.x; + clone.m4 /= s.x; + clone.m8 /= s.x; + clone.m1 /= s.y; + clone.m5 /= s.y; + clone.m9 /= s.y; + clone.m2 /= s.z; + clone.m6 /= s.z; + clone.m10 /= s.z; + + // Extract rotation + *rotation = QuaternionFromMatrix(clone); + } + else + { + // Set to identity if close to zero + *rotation = QuaternionIdentity(); + } +} + +#if defined(__cplusplus) && !defined(RAYMATH_DISABLE_CPP_OPERATORS) + +// Optional C++ math operators +//------------------------------------------------------------------------------- + +// Vector2 operators +static constexpr Vector2 Vector2Zeros = { 0, 0 }; +static constexpr Vector2 Vector2Ones = { 1, 1 }; +static constexpr Vector2 Vector2UnitX = { 1, 0 }; +static constexpr Vector2 Vector2UnitY = { 0, 1 }; + +inline Vector2 operator + (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Add(lhs, rhs); +} + +inline const Vector2& operator += (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Add(lhs, rhs); + return lhs; +} + +inline Vector2 operator - (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Subtract(lhs, rhs); +} + +inline const Vector2& operator -= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Subtract(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Multiply(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Multiply(lhs, rhs); + return lhs; +} + +inline Vector2 operator * (const Vector2& lhs, const Matrix& rhs) +{ + return Vector2Transform(lhs, rhs); +} + +inline const Vector2& operator *= (Vector2& lhs, const Matrix& rhs) +{ + lhs = Vector2Transform(lhs, rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const float& rhs) +{ + return Vector2Scale(lhs, 1.0f / rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const float& rhs) +{ + lhs = Vector2Scale(lhs, 1.0f / rhs); + return lhs; +} + +inline Vector2 operator / (const Vector2& lhs, const Vector2& rhs) +{ + return Vector2Divide(lhs, rhs); +} + +inline const Vector2& operator /= (Vector2& lhs, const Vector2& rhs) +{ + lhs = Vector2Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector2& lhs, const Vector2& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y); +} + +inline bool operator != (const Vector2& lhs, const Vector2& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y); +} + +// Vector3 operators +static constexpr Vector3 Vector3Zeros = { 0, 0, 0 }; +static constexpr Vector3 Vector3Ones = { 1, 1, 1 }; +static constexpr Vector3 Vector3UnitX = { 1, 0, 0 }; +static constexpr Vector3 Vector3UnitY = { 0, 1, 0 }; +static constexpr Vector3 Vector3UnitZ = { 0, 0, 1 }; + +inline Vector3 operator + (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Add(lhs, rhs); +} + +inline const Vector3& operator += (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Add(lhs, rhs); + return lhs; +} + +inline Vector3 operator - (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Subtract(lhs, rhs); +} + +inline const Vector3& operator -= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Subtract(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Multiply(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Multiply(lhs, rhs); + return lhs; +} + +inline Vector3 operator * (const Vector3& lhs, const Matrix& rhs) +{ + return Vector3Transform(lhs, rhs); +} + +inline const Vector3& operator *= (Vector3& lhs, const Matrix& rhs) +{ + lhs = Vector3Transform(lhs, rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const float& rhs) +{ + return Vector3Scale(lhs, 1.0f / rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const float& rhs) +{ + lhs = Vector3Scale(lhs, 1.0f / rhs); + return lhs; +} + +inline Vector3 operator / (const Vector3& lhs, const Vector3& rhs) +{ + return Vector3Divide(lhs, rhs); +} + +inline const Vector3& operator /= (Vector3& lhs, const Vector3& rhs) +{ + lhs = Vector3Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector3& lhs, const Vector3& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z); +} + +inline bool operator != (const Vector3& lhs, const Vector3& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z); +} + +// Vector4 operators +static constexpr Vector4 Vector4Zeros = { 0, 0, 0, 0 }; +static constexpr Vector4 Vector4Ones = { 1, 1, 1, 1 }; +static constexpr Vector4 Vector4UnitX = { 1, 0, 0, 0 }; +static constexpr Vector4 Vector4UnitY = { 0, 1, 0, 0 }; +static constexpr Vector4 Vector4UnitZ = { 0, 0, 1, 0 }; +static constexpr Vector4 Vector4UnitW = { 0, 0, 0, 1 }; + +inline Vector4 operator + (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Add(lhs, rhs); +} + +inline const Vector4& operator += (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Add(lhs, rhs); + return lhs; +} + +inline Vector4 operator - (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Subtract(lhs, rhs); +} + +inline const Vector4& operator -= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Subtract(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, rhs); + return lhs; +} + +inline Vector4 operator * (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Multiply(lhs, rhs); +} + +inline const Vector4& operator *= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Multiply(lhs, rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const float& rhs) +{ + return Vector4Scale(lhs, 1.0f / rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const float& rhs) +{ + lhs = Vector4Scale(lhs, 1.0f / rhs); + return lhs; +} + +inline Vector4 operator / (const Vector4& lhs, const Vector4& rhs) +{ + return Vector4Divide(lhs, rhs); +} + +inline const Vector4& operator /= (Vector4& lhs, const Vector4& rhs) +{ + lhs = Vector4Divide(lhs, rhs); + return lhs; +} + +inline bool operator == (const Vector4& lhs, const Vector4& rhs) +{ + return FloatEquals(lhs.x, rhs.x) && FloatEquals(lhs.y, rhs.y) && FloatEquals(lhs.z, rhs.z) && FloatEquals(lhs.w, rhs.w); +} + +inline bool operator != (const Vector4& lhs, const Vector4& rhs) +{ + return !FloatEquals(lhs.x, rhs.x) || !FloatEquals(lhs.y, rhs.y) || !FloatEquals(lhs.z, rhs.z) || !FloatEquals(lhs.w, rhs.w); +} + +// Quaternion operators +static constexpr Quaternion QuaternionZeros = { 0, 0, 0, 0 }; +static constexpr Quaternion QuaternionOnes = { 1, 1, 1, 1 }; +static constexpr Quaternion QuaternionUnitX = { 0, 0, 0, 1 }; + +inline Quaternion operator + (const Quaternion& lhs, const float& rhs) +{ + return QuaternionAddValue(lhs, rhs); +} + +inline const Quaternion& operator += (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionAddValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator - (const Quaternion& lhs, const float& rhs) +{ + return QuaternionSubtractValue(lhs, rhs); +} + +inline const Quaternion& operator -= (Quaternion& lhs, const float& rhs) +{ + lhs = QuaternionSubtractValue(lhs, rhs); + return lhs; +} + +inline Quaternion operator * (const Quaternion& lhs, const Matrix& rhs) +{ + return QuaternionTransform(lhs, rhs); +} + +inline const Quaternion& operator *= (Quaternion& lhs, const Matrix& rhs) +{ + lhs = QuaternionTransform(lhs, rhs); + return lhs; +} + +// Matrix operators +inline Matrix operator + (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixAdd(lhs, rhs); +} + +inline const Matrix& operator += (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixAdd(lhs, rhs); + return lhs; +} + +inline Matrix operator - (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixSubtract(lhs, rhs); +} + +inline const Matrix& operator -= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixSubtract(lhs, rhs); + return lhs; +} + +inline Matrix operator * (const Matrix& lhs, const Matrix& rhs) +{ + return MatrixMultiply(lhs, rhs); +} + +inline const Matrix& operator *= (Matrix& lhs, const Matrix& rhs) +{ + lhs = MatrixMultiply(lhs, rhs); + return lhs; +} +//------------------------------------------------------------------------------- +#endif // C++ operators + +#endif // RAYMATH_H diff --git a/third_party/raylib/include/rlgl.h b/third_party/raylib/include/rlgl.h new file mode 100644 index 00000000000..92971df6277 --- /dev/null +++ b/third_party/raylib/include/rlgl.h @@ -0,0 +1,5262 @@ +/********************************************************************************************** +* +* rlgl v5.0 - A multi-OpenGL abstraction layer with an immediate-mode style API +* +* DESCRIPTION: +* An abstraction layer for multiple OpenGL versions (1.1, 2.1, 3.3 Core, 4.3 Core, ES 2.0, ES 3.0) +* that provides a pseudo-OpenGL 1.1 immediate-mode style API (rlVertex, rlTranslate, rlRotate...) +* +* ADDITIONAL NOTES: +* When choosing an OpenGL backend different than OpenGL 1.1, some internal buffer are +* initialized on rlglInit() to accumulate vertex data +* +* When an internal state change is required all the stored vertex data is renderer in batch, +* additionally, rlDrawRenderBatchActive() could be called to force flushing of the batch +* +* Some resources are also loaded for convenience, here the complete list: +* - Default batch (RLGL.defaultBatch): RenderBatch system to accumulate vertex data +* - Default texture (RLGL.defaultTextureId): 1x1 white pixel R8G8B8A8 +* - Default shader (RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs) +* +* Internal buffer (and resources) must be manually unloaded calling rlglClose() +* +* CONFIGURATION: +* #define GRAPHICS_API_OPENGL_11 +* #define GRAPHICS_API_OPENGL_21 +* #define GRAPHICS_API_OPENGL_33 +* #define GRAPHICS_API_OPENGL_43 +* #define GRAPHICS_API_OPENGL_ES2 +* #define GRAPHICS_API_OPENGL_ES3 +* Use selected OpenGL graphics backend, should be supported by platform +* Those preprocessor defines are only used on rlgl module, if OpenGL version is +* required by any other module, use rlGetVersion() to check it +* +* #define RLGL_IMPLEMENTATION +* Generates the implementation of the library into the included file +* If not defined, the library is in header only mode and can be included in other headers +* or source files without problems. But only ONE file should hold the implementation +* +* #define RLGL_RENDER_TEXTURES_HINT +* Enable framebuffer objects (fbo) support (enabled by default) +* Some GPUs could not support them despite the OpenGL version +* +* #define RLGL_SHOW_GL_DETAILS_INFO +* Show OpenGL extensions and capabilities detailed logs on init +* +* #define RLGL_ENABLE_OPENGL_DEBUG_CONTEXT +* Enable debug context (only available on OpenGL 4.3) +* +* rlgl capabilities could be customized just defining some internal +* values before library inclusion (default values listed): +* +* #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 // Default internal render batch elements limits +* #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +* #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +* #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +* +* #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of internal Matrix stack +* #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +* #define RL_CULL_DISTANCE_NEAR 0.01 // Default projection matrix near cull distance +* #define RL_CULL_DISTANCE_FAR 1000.0 // Default projection matrix far cull distance +* +* When loading a shader, the following vertex attributes and uniform +* location names are tried to be set automatically: +* +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS +* #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView))) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +* #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +* #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +* +* DEPENDENCIES: +* - OpenGL libraries (depending on platform and OpenGL version selected) +* - GLAD OpenGL extensions loading library (only for OpenGL 3.3 Core, 4.3 Core) +* +* +* LICENSE: zlib/libpng +* +* Copyright (c) 2014-2024 Ramon Santamaria (@raysan5) +* +* This software is provided "as-is", without any express or implied warranty. In no event +* will the authors be held liable for any damages arising from the use of this software. +* +* Permission is granted to anyone to use this software for any purpose, including commercial +* applications, and to alter it and redistribute it freely, subject to the following restrictions: +* +* 1. The origin of this software must not be misrepresented; you must not claim that you +* wrote the original software. If you use this software in a product, an acknowledgment +* in the product documentation would be appreciated but is not required. +* +* 2. Altered source versions must be plainly marked as such, and must not be misrepresented +* as being the original software. +* +* 3. This notice may not be removed or altered from any source distribution. +* +**********************************************************************************************/ + +#ifndef RLGL_H +#define RLGL_H + +#define RLGL_VERSION "5.0" + +// Function specifiers in case library is build/used as a shared library +// NOTE: Microsoft specifiers to tell compiler that symbols are imported/exported from a .dll +// NOTE: visibility(default) attribute makes symbols "visible" when compiled with -fvisibility=hidden +#if defined(_WIN32) && defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __declspec(dllexport) // We are building the library as a Win32 shared library (.dll) +#elif defined(BUILD_LIBTYPE_SHARED) + #define RLAPI __attribute__((visibility("default"))) // We are building the library as a Unix shared library (.so/.dylib) +#elif defined(_WIN32) && defined(USE_LIBTYPE_SHARED) + #define RLAPI __declspec(dllimport) // We are using the library as a Win32 shared library (.dll) +#endif + +// Function specifiers definition +#ifndef RLAPI + #define RLAPI // Functions defined as 'extern' by default (implicit specifiers) +#endif + +// Support TRACELOG macros +#ifndef TRACELOG + #define TRACELOG(level, ...) (void)0 + #define TRACELOGD(...) (void)0 +#endif + +// Allow custom memory allocators +#ifndef RL_MALLOC + #define RL_MALLOC(sz) malloc(sz) +#endif +#ifndef RL_CALLOC + #define RL_CALLOC(n,sz) calloc(n,sz) +#endif +#ifndef RL_REALLOC + #define RL_REALLOC(n,sz) realloc(n,sz) +#endif +#ifndef RL_FREE + #define RL_FREE(p) free(p) +#endif + +// Security check in case no GRAPHICS_API_OPENGL_* defined +#if !defined(GRAPHICS_API_OPENGL_11) && \ + !defined(GRAPHICS_API_OPENGL_21) && \ + !defined(GRAPHICS_API_OPENGL_33) && \ + !defined(GRAPHICS_API_OPENGL_43) && \ + !defined(GRAPHICS_API_OPENGL_ES2) && \ + !defined(GRAPHICS_API_OPENGL_ES3) + #define GRAPHICS_API_OPENGL_33 +#endif + +// Security check in case multiple GRAPHICS_API_OPENGL_* defined +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_21) + #undef GRAPHICS_API_OPENGL_21 + #endif + #if defined(GRAPHICS_API_OPENGL_33) + #undef GRAPHICS_API_OPENGL_33 + #endif + #if defined(GRAPHICS_API_OPENGL_43) + #undef GRAPHICS_API_OPENGL_43 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + #undef GRAPHICS_API_OPENGL_ES2 + #endif +#endif + +// OpenGL 2.1 uses most of OpenGL 3.3 Core functionality +// WARNING: Specific parts are checked with #if defines +#if defined(GRAPHICS_API_OPENGL_21) + #define GRAPHICS_API_OPENGL_33 +#endif + +// OpenGL 4.3 uses OpenGL 3.3 Core functionality +#if defined(GRAPHICS_API_OPENGL_43) + #define GRAPHICS_API_OPENGL_33 +#endif + +// OpenGL ES 3.0 uses OpenGL ES 2.0 functionality (and more) +#if defined(GRAPHICS_API_OPENGL_ES3) + #define GRAPHICS_API_OPENGL_ES2 +#endif + +// Support framebuffer objects by default +// NOTE: Some driver implementation do not support it, despite they should +#define RLGL_RENDER_TEXTURES_HINT + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- + +// Default internal render batch elements limits +#ifndef RL_DEFAULT_BATCH_BUFFER_ELEMENTS + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // This is the maximum amount of elements (quads) per batch + // NOTE: Be careful with text, every letter maps to a quad + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 8192 + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + // We reduce memory sizes for embedded systems (RPI and HTML5) + // NOTE: On HTML5 (emscripten) this is allocated on heap, + // by default it's only 16MB!...just take care... + #define RL_DEFAULT_BATCH_BUFFER_ELEMENTS 2048 + #endif +#endif +#ifndef RL_DEFAULT_BATCH_BUFFERS + #define RL_DEFAULT_BATCH_BUFFERS 1 // Default number of batch buffers (multi-buffering) +#endif +#ifndef RL_DEFAULT_BATCH_DRAWCALLS + #define RL_DEFAULT_BATCH_DRAWCALLS 256 // Default number of batch draw calls (by state changes: mode, texture) +#endif +#ifndef RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS + #define RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS 4 // Maximum number of textures units that can be activated on batch drawing (SetShaderValueTexture()) +#endif + +// Internal Matrix stack +#ifndef RL_MAX_MATRIX_STACK_SIZE + #define RL_MAX_MATRIX_STACK_SIZE 32 // Maximum size of Matrix stack +#endif + +// Shader limits +#ifndef RL_MAX_SHADER_LOCATIONS + #define RL_MAX_SHADER_LOCATIONS 32 // Maximum number of shader locations supported +#endif + +// Projection matrix culling +#ifndef RL_CULL_DISTANCE_NEAR + #define RL_CULL_DISTANCE_NEAR 0.01 // Default near cull distance +#endif +#ifndef RL_CULL_DISTANCE_FAR + #define RL_CULL_DISTANCE_FAR 1000.0 // Default far cull distance +#endif + +// Texture parameters (equivalent to OpenGL defines) +#define RL_TEXTURE_WRAP_S 0x2802 // GL_TEXTURE_WRAP_S +#define RL_TEXTURE_WRAP_T 0x2803 // GL_TEXTURE_WRAP_T +#define RL_TEXTURE_MAG_FILTER 0x2800 // GL_TEXTURE_MAG_FILTER +#define RL_TEXTURE_MIN_FILTER 0x2801 // GL_TEXTURE_MIN_FILTER + +#define RL_TEXTURE_FILTER_NEAREST 0x2600 // GL_NEAREST +#define RL_TEXTURE_FILTER_LINEAR 0x2601 // GL_LINEAR +#define RL_TEXTURE_FILTER_MIP_NEAREST 0x2700 // GL_NEAREST_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_NEAREST_MIP_LINEAR 0x2702 // GL_NEAREST_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_LINEAR_MIP_NEAREST 0x2701 // GL_LINEAR_MIPMAP_NEAREST +#define RL_TEXTURE_FILTER_MIP_LINEAR 0x2703 // GL_LINEAR_MIPMAP_LINEAR +#define RL_TEXTURE_FILTER_ANISOTROPIC 0x3000 // Anisotropic filter (custom identifier) +#define RL_TEXTURE_MIPMAP_BIAS_RATIO 0x4000 // Texture mipmap bias, percentage ratio (custom identifier) + +#define RL_TEXTURE_WRAP_REPEAT 0x2901 // GL_REPEAT +#define RL_TEXTURE_WRAP_CLAMP 0x812F // GL_CLAMP_TO_EDGE +#define RL_TEXTURE_WRAP_MIRROR_REPEAT 0x8370 // GL_MIRRORED_REPEAT +#define RL_TEXTURE_WRAP_MIRROR_CLAMP 0x8742 // GL_MIRROR_CLAMP_EXT + +// Matrix modes (equivalent to OpenGL) +#define RL_MODELVIEW 0x1700 // GL_MODELVIEW +#define RL_PROJECTION 0x1701 // GL_PROJECTION +#define RL_TEXTURE 0x1702 // GL_TEXTURE + +// Primitive assembly draw modes +#define RL_LINES 0x0001 // GL_LINES +#define RL_TRIANGLES 0x0004 // GL_TRIANGLES +#define RL_QUADS 0x0007 // GL_QUADS + +// GL equivalent data types +#define RL_UNSIGNED_BYTE 0x1401 // GL_UNSIGNED_BYTE +#define RL_FLOAT 0x1406 // GL_FLOAT + +// GL buffer usage hint +#define RL_STREAM_DRAW 0x88E0 // GL_STREAM_DRAW +#define RL_STREAM_READ 0x88E1 // GL_STREAM_READ +#define RL_STREAM_COPY 0x88E2 // GL_STREAM_COPY +#define RL_STATIC_DRAW 0x88E4 // GL_STATIC_DRAW +#define RL_STATIC_READ 0x88E5 // GL_STATIC_READ +#define RL_STATIC_COPY 0x88E6 // GL_STATIC_COPY +#define RL_DYNAMIC_DRAW 0x88E8 // GL_DYNAMIC_DRAW +#define RL_DYNAMIC_READ 0x88E9 // GL_DYNAMIC_READ +#define RL_DYNAMIC_COPY 0x88EA // GL_DYNAMIC_COPY + +// GL Shader type +#define RL_FRAGMENT_SHADER 0x8B30 // GL_FRAGMENT_SHADER +#define RL_VERTEX_SHADER 0x8B31 // GL_VERTEX_SHADER +#define RL_COMPUTE_SHADER 0x91B9 // GL_COMPUTE_SHADER + +// GL blending factors +#define RL_ZERO 0 // GL_ZERO +#define RL_ONE 1 // GL_ONE +#define RL_SRC_COLOR 0x0300 // GL_SRC_COLOR +#define RL_ONE_MINUS_SRC_COLOR 0x0301 // GL_ONE_MINUS_SRC_COLOR +#define RL_SRC_ALPHA 0x0302 // GL_SRC_ALPHA +#define RL_ONE_MINUS_SRC_ALPHA 0x0303 // GL_ONE_MINUS_SRC_ALPHA +#define RL_DST_ALPHA 0x0304 // GL_DST_ALPHA +#define RL_ONE_MINUS_DST_ALPHA 0x0305 // GL_ONE_MINUS_DST_ALPHA +#define RL_DST_COLOR 0x0306 // GL_DST_COLOR +#define RL_ONE_MINUS_DST_COLOR 0x0307 // GL_ONE_MINUS_DST_COLOR +#define RL_SRC_ALPHA_SATURATE 0x0308 // GL_SRC_ALPHA_SATURATE +#define RL_CONSTANT_COLOR 0x8001 // GL_CONSTANT_COLOR +#define RL_ONE_MINUS_CONSTANT_COLOR 0x8002 // GL_ONE_MINUS_CONSTANT_COLOR +#define RL_CONSTANT_ALPHA 0x8003 // GL_CONSTANT_ALPHA +#define RL_ONE_MINUS_CONSTANT_ALPHA 0x8004 // GL_ONE_MINUS_CONSTANT_ALPHA + +// GL blending functions/equations +#define RL_FUNC_ADD 0x8006 // GL_FUNC_ADD +#define RL_MIN 0x8007 // GL_MIN +#define RL_MAX 0x8008 // GL_MAX +#define RL_FUNC_SUBTRACT 0x800A // GL_FUNC_SUBTRACT +#define RL_FUNC_REVERSE_SUBTRACT 0x800B // GL_FUNC_REVERSE_SUBTRACT +#define RL_BLEND_EQUATION 0x8009 // GL_BLEND_EQUATION +#define RL_BLEND_EQUATION_RGB 0x8009 // GL_BLEND_EQUATION_RGB // (Same as BLEND_EQUATION) +#define RL_BLEND_EQUATION_ALPHA 0x883D // GL_BLEND_EQUATION_ALPHA +#define RL_BLEND_DST_RGB 0x80C8 // GL_BLEND_DST_RGB +#define RL_BLEND_SRC_RGB 0x80C9 // GL_BLEND_SRC_RGB +#define RL_BLEND_DST_ALPHA 0x80CA // GL_BLEND_DST_ALPHA +#define RL_BLEND_SRC_ALPHA 0x80CB // GL_BLEND_SRC_ALPHA +#define RL_BLEND_COLOR 0x8005 // GL_BLEND_COLOR + +#define RL_READ_FRAMEBUFFER 0x8CA8 // GL_READ_FRAMEBUFFER +#define RL_DRAW_FRAMEBUFFER 0x8CA9 // GL_DRAW_FRAMEBUFFER + +// Default shader vertex attribute locations +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION 0 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD 1 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL 2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR 3 +#endif + #ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT +#define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT 4 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2 5 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_INDICES 6 +#endif +#ifdef RL_SUPPORT_MESH_GPU_SKINNING +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS 7 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS 8 +#endif +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if (defined(__STDC__) && __STDC_VERSION__ >= 199901L) || (defined(_MSC_VER) && _MSC_VER >= 1800) + #include +#elif !defined(__cplusplus) && !defined(bool) && !defined(RL_BOOL_TYPE) + // Boolean type +typedef enum bool { false = 0, true = !false } bool; +#endif + +#if !defined(RL_MATRIX_TYPE) +// Matrix, 4x4 components, column major, OpenGL style, right handed +typedef struct Matrix { + float m0, m4, m8, m12; // Matrix first row (4 components) + float m1, m5, m9, m13; // Matrix second row (4 components) + float m2, m6, m10, m14; // Matrix third row (4 components) + float m3, m7, m11, m15; // Matrix fourth row (4 components) +} Matrix; +#define RL_MATRIX_TYPE +#endif + +// Dynamic vertex buffers (position + texcoords + colors + indices arrays) +typedef struct rlVertexBuffer { + int elementCount; // Number of elements in the buffer (QUADS) + + float *vertices; // Vertex position (XYZ - 3 components per vertex) (shader-location = 0) + float *texcoords; // Vertex texture coordinates (UV - 2 components per vertex) (shader-location = 1) + float *normals; // Vertex normal (XYZ - 3 components per vertex) (shader-location = 2) + unsigned char *colors; // Vertex colors (RGBA - 4 components per vertex) (shader-location = 3) +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + unsigned int *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + unsigned short *indices; // Vertex indices (in case vertex data comes indexed) (6 indices per quad) +#endif + unsigned int vaoId; // OpenGL Vertex Array Object id + unsigned int vboId[5]; // OpenGL Vertex Buffer Objects id (5 types of vertex data) +} rlVertexBuffer; + +// Draw call type +// NOTE: Only texture changes register a new draw, other state-change-related elements are not +// used at this moment (vaoId, shaderId, matrices), raylib just forces a batch draw call if any +// of those state-change happens (this is done in core module) +typedef struct rlDrawCall { + int mode; // Drawing mode: LINES, TRIANGLES, QUADS + int vertexCount; // Number of vertex of the draw + int vertexAlignment; // Number of vertex required for index alignment (LINES, TRIANGLES) + //unsigned int vaoId; // Vertex array id to be used on the draw -> Using RLGL.currentBatch->vertexBuffer.vaoId + //unsigned int shaderId; // Shader id to be used on the draw -> Using RLGL.currentShaderId + unsigned int textureId; // Texture id to be used on the draw -> Use to create new draw call if changes + + //Matrix projection; // Projection matrix for this draw -> Using RLGL.projection by default + //Matrix modelview; // Modelview matrix for this draw -> Using RLGL.modelview by default +} rlDrawCall; + +// rlRenderBatch type +typedef struct rlRenderBatch { + int bufferCount; // Number of vertex buffers (multi-buffering support) + int currentBuffer; // Current buffer tracking in case of multi-buffering + rlVertexBuffer *vertexBuffer; // Dynamic buffer(s) for vertex data + + rlDrawCall *draws; // Draw calls array, depends on textureId + int drawCounter; // Draw calls counter + float currentDepth; // Current depth value for next draw +} rlRenderBatch; + +// OpenGL version +typedef enum { + RL_OPENGL_11 = 1, // OpenGL 1.1 + RL_OPENGL_21, // OpenGL 2.1 (GLSL 120) + RL_OPENGL_33, // OpenGL 3.3 (GLSL 330) + RL_OPENGL_43, // OpenGL 4.3 (using GLSL 330) + RL_OPENGL_ES_20, // OpenGL ES 2.0 (GLSL 100) + RL_OPENGL_ES_30 // OpenGL ES 3.0 (GLSL 300 es) +} rlGlVersion; + +// Trace log level +// NOTE: Organized by priority level +typedef enum { + RL_LOG_ALL = 0, // Display all logs + RL_LOG_TRACE, // Trace logging, intended for internal use only + RL_LOG_DEBUG, // Debug logging, used for internal debugging, it should be disabled on release builds + RL_LOG_INFO, // Info logging, used for program execution info + RL_LOG_WARNING, // Warning logging, used on recoverable failures + RL_LOG_ERROR, // Error logging, used on unrecoverable failures + RL_LOG_FATAL, // Fatal logging, used to abort program: exit(EXIT_FAILURE) + RL_LOG_NONE // Disable logging +} rlTraceLogLevel; + +// Texture pixel formats +// NOTE: Support depends on OpenGL version +typedef enum { + RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE = 1, // 8 bit per pixel (no alpha) + RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA, // 8*2 bpp (2 channels) + RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5, // 16 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8, // 24 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1, // 16 bpp (1 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4, // 16 bpp (4 bit alpha) + RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, // 32 bpp + RL_PIXELFORMAT_UNCOMPRESSED_R32, // 32 bpp (1 channel - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32, // 32*3 bpp (3 channels - float) + RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32, // 32*4 bpp (4 channels - float) + RL_PIXELFORMAT_UNCOMPRESSED_R16, // 16 bpp (1 channel - half float) + RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16, // 16*3 bpp (3 channels - half float) + RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16, // 16*4 bpp (4 channels - half float) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGB, // 4 bpp (no alpha) + RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA, // 4 bpp (1 bit alpha) + RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ETC1_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGB, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA, // 4 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA, // 8 bpp + RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA // 2 bpp +} rlPixelFormat; + +// Texture parameters: filter mode +// NOTE 1: Filtering considers mipmaps if available in the texture +// NOTE 2: Filter is accordingly set for minification and magnification +typedef enum { + RL_TEXTURE_FILTER_POINT = 0, // No filter, just pixel approximation + RL_TEXTURE_FILTER_BILINEAR, // Linear filtering + RL_TEXTURE_FILTER_TRILINEAR, // Trilinear filtering (linear with mipmaps) + RL_TEXTURE_FILTER_ANISOTROPIC_4X, // Anisotropic filtering 4x + RL_TEXTURE_FILTER_ANISOTROPIC_8X, // Anisotropic filtering 8x + RL_TEXTURE_FILTER_ANISOTROPIC_16X, // Anisotropic filtering 16x +} rlTextureFilter; + +// Color blending modes (pre-defined) +typedef enum { + RL_BLEND_ALPHA = 0, // Blend textures considering alpha (default) + RL_BLEND_ADDITIVE, // Blend textures adding colors + RL_BLEND_MULTIPLIED, // Blend textures multiplying colors + RL_BLEND_ADD_COLORS, // Blend textures adding colors (alternative) + RL_BLEND_SUBTRACT_COLORS, // Blend textures subtracting colors (alternative) + RL_BLEND_ALPHA_PREMULTIPLY, // Blend premultiplied textures considering alpha + RL_BLEND_CUSTOM, // Blend textures using custom src/dst factors (use rlSetBlendFactors()) + RL_BLEND_CUSTOM_SEPARATE // Blend textures using custom src/dst factors (use rlSetBlendFactorsSeparate()) +} rlBlendMode; + +// Shader location point type +typedef enum { + RL_SHADER_LOC_VERTEX_POSITION = 0, // Shader location: vertex attribute: position + RL_SHADER_LOC_VERTEX_TEXCOORD01, // Shader location: vertex attribute: texcoord01 + RL_SHADER_LOC_VERTEX_TEXCOORD02, // Shader location: vertex attribute: texcoord02 + RL_SHADER_LOC_VERTEX_NORMAL, // Shader location: vertex attribute: normal + RL_SHADER_LOC_VERTEX_TANGENT, // Shader location: vertex attribute: tangent + RL_SHADER_LOC_VERTEX_COLOR, // Shader location: vertex attribute: color + RL_SHADER_LOC_MATRIX_MVP, // Shader location: matrix uniform: model-view-projection + RL_SHADER_LOC_MATRIX_VIEW, // Shader location: matrix uniform: view (camera transform) + RL_SHADER_LOC_MATRIX_PROJECTION, // Shader location: matrix uniform: projection + RL_SHADER_LOC_MATRIX_MODEL, // Shader location: matrix uniform: model (transform) + RL_SHADER_LOC_MATRIX_NORMAL, // Shader location: matrix uniform: normal + RL_SHADER_LOC_VECTOR_VIEW, // Shader location: vector uniform: view + RL_SHADER_LOC_COLOR_DIFFUSE, // Shader location: vector uniform: diffuse color + RL_SHADER_LOC_COLOR_SPECULAR, // Shader location: vector uniform: specular color + RL_SHADER_LOC_COLOR_AMBIENT, // Shader location: vector uniform: ambient color + RL_SHADER_LOC_MAP_ALBEDO, // Shader location: sampler2d texture: albedo (same as: RL_SHADER_LOC_MAP_DIFFUSE) + RL_SHADER_LOC_MAP_METALNESS, // Shader location: sampler2d texture: metalness (same as: RL_SHADER_LOC_MAP_SPECULAR) + RL_SHADER_LOC_MAP_NORMAL, // Shader location: sampler2d texture: normal + RL_SHADER_LOC_MAP_ROUGHNESS, // Shader location: sampler2d texture: roughness + RL_SHADER_LOC_MAP_OCCLUSION, // Shader location: sampler2d texture: occlusion + RL_SHADER_LOC_MAP_EMISSION, // Shader location: sampler2d texture: emission + RL_SHADER_LOC_MAP_HEIGHT, // Shader location: sampler2d texture: height + RL_SHADER_LOC_MAP_CUBEMAP, // Shader location: samplerCube texture: cubemap + RL_SHADER_LOC_MAP_IRRADIANCE, // Shader location: samplerCube texture: irradiance + RL_SHADER_LOC_MAP_PREFILTER, // Shader location: samplerCube texture: prefilter + RL_SHADER_LOC_MAP_BRDF // Shader location: sampler2d texture: brdf +} rlShaderLocationIndex; + +#define RL_SHADER_LOC_MAP_DIFFUSE RL_SHADER_LOC_MAP_ALBEDO +#define RL_SHADER_LOC_MAP_SPECULAR RL_SHADER_LOC_MAP_METALNESS + +// Shader uniform data type +typedef enum { + RL_SHADER_UNIFORM_FLOAT = 0, // Shader uniform type: float + RL_SHADER_UNIFORM_VEC2, // Shader uniform type: vec2 (2 float) + RL_SHADER_UNIFORM_VEC3, // Shader uniform type: vec3 (3 float) + RL_SHADER_UNIFORM_VEC4, // Shader uniform type: vec4 (4 float) + RL_SHADER_UNIFORM_INT, // Shader uniform type: int + RL_SHADER_UNIFORM_IVEC2, // Shader uniform type: ivec2 (2 int) + RL_SHADER_UNIFORM_IVEC3, // Shader uniform type: ivec3 (3 int) + RL_SHADER_UNIFORM_IVEC4, // Shader uniform type: ivec4 (4 int) + RL_SHADER_UNIFORM_UINT, // Shader uniform type: unsigned int + RL_SHADER_UNIFORM_UIVEC2, // Shader uniform type: uivec2 (2 unsigned int) + RL_SHADER_UNIFORM_UIVEC3, // Shader uniform type: uivec3 (3 unsigned int) + RL_SHADER_UNIFORM_UIVEC4, // Shader uniform type: uivec4 (4 unsigned int) + RL_SHADER_UNIFORM_SAMPLER2D // Shader uniform type: sampler2d +} rlShaderUniformDataType; + +// Shader attribute data types +typedef enum { + RL_SHADER_ATTRIB_FLOAT = 0, // Shader attribute type: float + RL_SHADER_ATTRIB_VEC2, // Shader attribute type: vec2 (2 float) + RL_SHADER_ATTRIB_VEC3, // Shader attribute type: vec3 (3 float) + RL_SHADER_ATTRIB_VEC4 // Shader attribute type: vec4 (4 float) +} rlShaderAttributeDataType; + +// Framebuffer attachment type +// NOTE: By default up to 8 color channels defined, but it can be more +typedef enum { + RL_ATTACHMENT_COLOR_CHANNEL0 = 0, // Framebuffer attachment type: color 0 + RL_ATTACHMENT_COLOR_CHANNEL1 = 1, // Framebuffer attachment type: color 1 + RL_ATTACHMENT_COLOR_CHANNEL2 = 2, // Framebuffer attachment type: color 2 + RL_ATTACHMENT_COLOR_CHANNEL3 = 3, // Framebuffer attachment type: color 3 + RL_ATTACHMENT_COLOR_CHANNEL4 = 4, // Framebuffer attachment type: color 4 + RL_ATTACHMENT_COLOR_CHANNEL5 = 5, // Framebuffer attachment type: color 5 + RL_ATTACHMENT_COLOR_CHANNEL6 = 6, // Framebuffer attachment type: color 6 + RL_ATTACHMENT_COLOR_CHANNEL7 = 7, // Framebuffer attachment type: color 7 + RL_ATTACHMENT_DEPTH = 100, // Framebuffer attachment type: depth + RL_ATTACHMENT_STENCIL = 200, // Framebuffer attachment type: stencil +} rlFramebufferAttachType; + +// Framebuffer texture attachment type +typedef enum { + RL_ATTACHMENT_CUBEMAP_POSITIVE_X = 0, // Framebuffer texture attachment type: cubemap, +X side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_X = 1, // Framebuffer texture attachment type: cubemap, -X side + RL_ATTACHMENT_CUBEMAP_POSITIVE_Y = 2, // Framebuffer texture attachment type: cubemap, +Y side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Y = 3, // Framebuffer texture attachment type: cubemap, -Y side + RL_ATTACHMENT_CUBEMAP_POSITIVE_Z = 4, // Framebuffer texture attachment type: cubemap, +Z side + RL_ATTACHMENT_CUBEMAP_NEGATIVE_Z = 5, // Framebuffer texture attachment type: cubemap, -Z side + RL_ATTACHMENT_TEXTURE2D = 100, // Framebuffer texture attachment type: texture2d + RL_ATTACHMENT_RENDERBUFFER = 200, // Framebuffer texture attachment type: renderbuffer +} rlFramebufferAttachTextureType; + +// Face culling mode +typedef enum { + RL_CULL_FACE_FRONT = 0, + RL_CULL_FACE_BACK +} rlCullMode; + +//------------------------------------------------------------------------------------ +// Functions Declaration - Matrix operations +//------------------------------------------------------------------------------------ + +#if defined(__cplusplus) +extern "C" { // Prevents name mangling of functions +#endif + +RLAPI void rlMatrixMode(int mode); // Choose the current matrix to be transformed +RLAPI void rlPushMatrix(void); // Push the current matrix to stack +RLAPI void rlPopMatrix(void); // Pop latest inserted matrix from stack +RLAPI void rlLoadIdentity(void); // Reset current matrix to identity matrix +RLAPI void rlTranslatef(float x, float y, float z); // Multiply the current matrix by a translation matrix +RLAPI void rlRotatef(float angle, float x, float y, float z); // Multiply the current matrix by a rotation matrix +RLAPI void rlScalef(float x, float y, float z); // Multiply the current matrix by a scaling matrix +RLAPI void rlMultMatrixf(const float *matf); // Multiply the current matrix by another matrix +RLAPI void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar); +RLAPI void rlViewport(int x, int y, int width, int height); // Set the viewport area +RLAPI void rlSetClipPlanes(double nearPlane, double farPlane); // Set clip planes distances +RLAPI double rlGetCullDistanceNear(void); // Get cull plane distance near +RLAPI double rlGetCullDistanceFar(void); // Get cull plane distance far + +//------------------------------------------------------------------------------------ +// Functions Declaration - Vertex level operations +//------------------------------------------------------------------------------------ +RLAPI void rlBegin(int mode); // Initialize drawing mode (how to organize vertex) +RLAPI void rlEnd(void); // Finish vertex providing +RLAPI void rlVertex2i(int x, int y); // Define one vertex (position) - 2 int +RLAPI void rlVertex2f(float x, float y); // Define one vertex (position) - 2 float +RLAPI void rlVertex3f(float x, float y, float z); // Define one vertex (position) - 3 float +RLAPI void rlTexCoord2f(float x, float y); // Define one vertex (texture coordinate) - 2 float +RLAPI void rlNormal3f(float x, float y, float z); // Define one vertex (normal) - 3 float +RLAPI void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Define one vertex (color) - 4 byte +RLAPI void rlColor3f(float x, float y, float z); // Define one vertex (color) - 3 float +RLAPI void rlColor4f(float x, float y, float z, float w); // Define one vertex (color) - 4 float + +//------------------------------------------------------------------------------------ +// Functions Declaration - OpenGL style functions (common to 1.1, 3.3+, ES2) +// NOTE: This functions are used to completely abstract raylib code from OpenGL layer, +// some of them are direct wrappers over OpenGL calls, some others are custom +//------------------------------------------------------------------------------------ + +// Vertex buffers state +RLAPI bool rlEnableVertexArray(unsigned int vaoId); // Enable vertex array (VAO, if supported) +RLAPI void rlDisableVertexArray(void); // Disable vertex array (VAO, if supported) +RLAPI void rlEnableVertexBuffer(unsigned int id); // Enable vertex buffer (VBO) +RLAPI void rlDisableVertexBuffer(void); // Disable vertex buffer (VBO) +RLAPI void rlEnableVertexBufferElement(unsigned int id); // Enable vertex buffer element (VBO element) +RLAPI void rlDisableVertexBufferElement(void); // Disable vertex buffer element (VBO element) +RLAPI void rlEnableVertexAttribute(unsigned int index); // Enable vertex attribute index +RLAPI void rlDisableVertexAttribute(unsigned int index); // Disable vertex attribute index +#if defined(GRAPHICS_API_OPENGL_11) +RLAPI void rlEnableStatePointer(int vertexAttribType, void *buffer); // Enable attribute state pointer +RLAPI void rlDisableStatePointer(int vertexAttribType); // Disable attribute state pointer +#endif + +// Textures state +RLAPI void rlActiveTextureSlot(int slot); // Select and active a texture slot +RLAPI void rlEnableTexture(unsigned int id); // Enable texture +RLAPI void rlDisableTexture(void); // Disable texture +RLAPI void rlEnableTextureCubemap(unsigned int id); // Enable texture cubemap +RLAPI void rlDisableTextureCubemap(void); // Disable texture cubemap +RLAPI void rlTextureParameters(unsigned int id, int param, int value); // Set texture parameters (filter, wrap) +RLAPI void rlCubemapParameters(unsigned int id, int param, int value); // Set cubemap parameters (filter, wrap) + +// Shader state +RLAPI void rlEnableShader(unsigned int id); // Enable shader program +RLAPI void rlDisableShader(void); // Disable shader program + +// Framebuffer state +RLAPI void rlEnableFramebuffer(unsigned int id); // Enable render texture (fbo) +RLAPI void rlDisableFramebuffer(void); // Disable render texture (fbo), return to default framebuffer +RLAPI unsigned int rlGetActiveFramebuffer(void); // Get the currently active render texture (fbo), 0 for default framebuffer +RLAPI void rlActiveDrawBuffers(int count); // Activate multiple draw color buffers +RLAPI void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask); // Blit active framebuffer to main framebuffer +RLAPI void rlBindFramebuffer(unsigned int target, unsigned int framebuffer); // Bind framebuffer (FBO) + +// General render state +RLAPI void rlEnableColorBlend(void); // Enable color blending +RLAPI void rlDisableColorBlend(void); // Disable color blending +RLAPI void rlEnableDepthTest(void); // Enable depth test +RLAPI void rlDisableDepthTest(void); // Disable depth test +RLAPI void rlEnableDepthMask(void); // Enable depth write +RLAPI void rlDisableDepthMask(void); // Disable depth write +RLAPI void rlEnableBackfaceCulling(void); // Enable backface culling +RLAPI void rlDisableBackfaceCulling(void); // Disable backface culling +RLAPI void rlColorMask(bool r, bool g, bool b, bool a); // Color mask control +RLAPI void rlSetCullFace(int mode); // Set face culling mode +RLAPI void rlEnableScissorTest(void); // Enable scissor test +RLAPI void rlDisableScissorTest(void); // Disable scissor test +RLAPI void rlScissor(int x, int y, int width, int height); // Scissor test +RLAPI void rlEnableWireMode(void); // Enable wire mode +RLAPI void rlEnablePointMode(void); // Enable point mode +RLAPI void rlDisableWireMode(void); // Disable wire (and point) mode +RLAPI void rlSetLineWidth(float width); // Set the line drawing width +RLAPI float rlGetLineWidth(void); // Get the line drawing width +RLAPI void rlEnableSmoothLines(void); // Enable line aliasing +RLAPI void rlDisableSmoothLines(void); // Disable line aliasing +RLAPI void rlEnableStereoRender(void); // Enable stereo rendering +RLAPI void rlDisableStereoRender(void); // Disable stereo rendering +RLAPI bool rlIsStereoRenderEnabled(void); // Check if stereo render is enabled + +RLAPI void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a); // Clear color buffer with color +RLAPI void rlClearScreenBuffers(void); // Clear used screen buffers (color and depth) +RLAPI void rlCheckErrors(void); // Check and log OpenGL error codes +RLAPI void rlSetBlendMode(int mode); // Set blending mode +RLAPI void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation); // Set blending mode factor and equation (using OpenGL factors) +RLAPI void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha); // Set blending mode factors and equations separately (using OpenGL factors) + +//------------------------------------------------------------------------------------ +// Functions Declaration - rlgl functionality +//------------------------------------------------------------------------------------ +// rlgl initialization functions +RLAPI void rlglInit(int width, int height); // Initialize rlgl (buffers, shaders, textures, states) +RLAPI void rlglClose(void); // De-initialize rlgl (buffers, shaders, textures) +RLAPI void rlLoadExtensions(void *loader); // Load OpenGL extensions (loader function required) +RLAPI int rlGetVersion(void); // Get current OpenGL version +RLAPI void rlSetFramebufferWidth(int width); // Set current framebuffer width +RLAPI int rlGetFramebufferWidth(void); // Get default framebuffer width +RLAPI void rlSetFramebufferHeight(int height); // Set current framebuffer height +RLAPI int rlGetFramebufferHeight(void); // Get default framebuffer height + +RLAPI unsigned int rlGetTextureIdDefault(void); // Get default texture id +RLAPI unsigned int rlGetShaderIdDefault(void); // Get default shader id +RLAPI int *rlGetShaderLocsDefault(void); // Get default shader locations + +// Render batch management +// NOTE: rlgl provides a default render batch to behave like OpenGL 1.1 immediate mode +// but this render batch API is exposed in case of custom batches are required +RLAPI rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements); // Load a render batch system +RLAPI void rlUnloadRenderBatch(rlRenderBatch batch); // Unload render batch system +RLAPI void rlDrawRenderBatch(rlRenderBatch *batch); // Draw render batch data (Update->Draw->Reset) +RLAPI void rlSetRenderBatchActive(rlRenderBatch *batch); // Set the active render batch for rlgl (NULL for default internal) +RLAPI void rlDrawRenderBatchActive(void); // Update and draw internal render batch +RLAPI bool rlCheckRenderBatchLimit(int vCount); // Check internal buffer overflow for a given number of vertex + +RLAPI void rlSetTexture(unsigned int id); // Set current texture for render batch and check buffers limits + +//------------------------------------------------------------------------------------------------------------------------ + +// Vertex buffers management +RLAPI unsigned int rlLoadVertexArray(void); // Load vertex array (vao) if supported +RLAPI unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic); // Load a vertex buffer object +RLAPI unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic); // Load vertex buffer elements object +RLAPI void rlUpdateVertexBuffer(unsigned int bufferId, const void *data, int dataSize, int offset); // Update vertex buffer object data on GPU buffer +RLAPI void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset); // Update vertex buffer elements data on GPU buffer +RLAPI void rlUnloadVertexArray(unsigned int vaoId); // Unload vertex array (vao) +RLAPI void rlUnloadVertexBuffer(unsigned int vboId); // Unload vertex buffer object +RLAPI void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset); // Set vertex attribute data configuration +RLAPI void rlSetVertexAttributeDivisor(unsigned int index, int divisor); // Set vertex attribute data divisor +RLAPI void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count); // Set vertex attribute default value, when attribute to provided +RLAPI void rlDrawVertexArray(int offset, int count); // Draw vertex array (currently active vao) +RLAPI void rlDrawVertexArrayElements(int offset, int count, const void *buffer); // Draw vertex array elements +RLAPI void rlDrawVertexArrayInstanced(int offset, int count, int instances); // Draw vertex array (currently active vao) with instancing +RLAPI void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances); // Draw vertex array elements with instancing + +// Textures management +RLAPI unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount); // Load texture data +RLAPI unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer); // Load depth texture/renderbuffer (to be attached to fbo) +RLAPI unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount); // Load texture cubemap data +RLAPI void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data); // Update texture with new data on GPU +RLAPI void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType); // Get OpenGL internal formats +RLAPI const char *rlGetPixelFormatName(unsigned int format); // Get name string for pixel format +RLAPI void rlUnloadTexture(unsigned int id); // Unload texture from GPU memory +RLAPI void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps); // Generate mipmap data for selected texture +RLAPI void *rlReadTexturePixels(unsigned int id, int width, int height, int format); // Read texture pixel data +RLAPI unsigned char *rlReadScreenPixels(int width, int height); // Read screen pixel data (color buffer) + +// Framebuffer management (fbo) +RLAPI unsigned int rlLoadFramebuffer(void); // Load an empty framebuffer +RLAPI void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel); // Attach texture/renderbuffer to a framebuffer +RLAPI bool rlFramebufferComplete(unsigned int id); // Verify framebuffer is complete +RLAPI void rlUnloadFramebuffer(unsigned int id); // Delete framebuffer from GPU + +// Shaders management +RLAPI unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode); // Load shader from code strings +RLAPI unsigned int rlCompileShader(const char *shaderCode, int type); // Compile custom shader and return shader id (type: RL_VERTEX_SHADER, RL_FRAGMENT_SHADER, RL_COMPUTE_SHADER) +RLAPI unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId); // Load custom shader program +RLAPI void rlUnloadShaderProgram(unsigned int id); // Unload shader program +RLAPI int rlGetLocationUniform(unsigned int shaderId, const char *uniformName); // Get shader location uniform +RLAPI int rlGetLocationAttrib(unsigned int shaderId, const char *attribName); // Get shader location attribute +RLAPI void rlSetUniform(int locIndex, const void *value, int uniformType, int count); // Set shader value uniform +RLAPI void rlSetUniformMatrix(int locIndex, Matrix mat); // Set shader value matrix +RLAPI void rlSetUniformMatrices(int locIndex, const Matrix *mat, int count); // Set shader value matrices +RLAPI void rlSetUniformSampler(int locIndex, unsigned int textureId); // Set shader value sampler +RLAPI void rlSetShader(unsigned int id, int *locs); // Set shader currently active (id and locations) + +// Compute shader management +RLAPI unsigned int rlLoadComputeShaderProgram(unsigned int shaderId); // Load compute shader program +RLAPI void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ); // Dispatch compute shader (equivalent to *draw* for graphics pipeline) + +// Shader buffer storage object management (ssbo) +RLAPI unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint); // Load shader storage buffer object (SSBO) +RLAPI void rlUnloadShaderBuffer(unsigned int ssboId); // Unload shader storage buffer object (SSBO) +RLAPI void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset); // Update SSBO buffer data +RLAPI void rlBindShaderBuffer(unsigned int id, unsigned int index); // Bind SSBO buffer +RLAPI void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset); // Read SSBO buffer data (GPU->CPU) +RLAPI void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count); // Copy SSBO data between buffers +RLAPI unsigned int rlGetShaderBufferSize(unsigned int id); // Get SSBO buffer size + +// Buffer management +RLAPI void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly); // Bind image texture + +// Matrix state management +RLAPI Matrix rlGetMatrixModelview(void); // Get internal modelview matrix +RLAPI Matrix rlGetMatrixProjection(void); // Get internal projection matrix +RLAPI Matrix rlGetMatrixTransform(void); // Get internal accumulated transform matrix +RLAPI Matrix rlGetMatrixProjectionStereo(int eye); // Get internal projection matrix for stereo render (selected eye) +RLAPI Matrix rlGetMatrixViewOffsetStereo(int eye); // Get internal view offset matrix for stereo render (selected eye) +RLAPI void rlSetMatrixProjection(Matrix proj); // Set a custom projection matrix (replaces internal projection matrix) +RLAPI void rlSetMatrixModelview(Matrix view); // Set a custom modelview matrix (replaces internal modelview matrix) +RLAPI void rlSetMatrixProjectionStereo(Matrix right, Matrix left); // Set eyes projection matrices for stereo rendering +RLAPI void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left); // Set eyes view offsets matrices for stereo rendering + +// Quick and dirty cube/quad buffers load->draw->unload +RLAPI void rlLoadDrawCube(void); // Load and draw a cube +RLAPI void rlLoadDrawQuad(void); // Load and draw a quad + +#if defined(__cplusplus) +} +#endif + +#endif // RLGL_H + +/*********************************************************************************** +* +* RLGL IMPLEMENTATION +* +************************************************************************************/ + +#if defined(RLGL_IMPLEMENTATION) + +// Expose OpenGL functions from glad in raylib +#if defined(BUILD_LIBTYPE_SHARED) + #define GLAD_API_CALL_EXPORT + #define GLAD_API_CALL_EXPORT_BUILD +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #if defined(__APPLE__) + #include // OpenGL 1.1 library for OSX + #include // OpenGL extensions library + #else + // APIENTRY for OpenGL function pointer declarations is required + #if !defined(APIENTRY) + #if defined(_WIN32) + #define APIENTRY __stdcall + #else + #define APIENTRY + #endif + #endif + // WINGDIAPI definition. Some Windows OpenGL headers need it + #if !defined(WINGDIAPI) && defined(_WIN32) + #define WINGDIAPI __declspec(dllimport) + #endif + + #include // OpenGL 1.1 library + #endif +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + #define GLAD_MALLOC RL_MALLOC + #define GLAD_FREE RL_FREE + + #define GLAD_GL_IMPLEMENTATION + #include "external/glad.h" // GLAD extensions loading library, includes OpenGL headers +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + #include // OpenGL ES 3.0 library + #define GL_GLEXT_PROTOTYPES + #include // OpenGL ES 2.0 extensions library +#elif defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 can be enabled on Desktop platforms, + // in that case, functions are loaded from a custom glad for OpenGL ES 2.0 + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) + #define GLAD_GLES2_IMPLEMENTATION + #include "external/glad_gles2.h" + #else + #define GL_GLEXT_PROTOTYPES + //#include // EGL library -> not required, platform layer + #include // OpenGL ES 2.0 library + #include // OpenGL ES 2.0 extensions library + #endif + + // It seems OpenGL ES 2.0 instancing entry points are not defined on Raspberry Pi + // provided headers (despite being defined in official Khronos GLES2 headers) + #if defined(PLATFORM_DRM) + typedef void (GL_APIENTRYP PFNGLDRAWARRAYSINSTANCEDEXTPROC) (GLenum mode, GLint start, GLsizei count, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLDRAWELEMENTSINSTANCEDEXTPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount); + typedef void (GL_APIENTRYP PFNGLVERTEXATTRIBDIVISOREXTPROC) (GLuint index, GLuint divisor); + #endif +#endif + +#include // Required for: malloc(), free() +#include // Required for: strcmp(), strlen() [Used in rlglInit(), on extensions loading] +#include // Required for: sqrtf(), sinf(), cosf(), floor(), log() + +//---------------------------------------------------------------------------------- +// Defines and Macros +//---------------------------------------------------------------------------------- +#ifndef PI + #define PI 3.14159265358979323846f +#endif +#ifndef DEG2RAD + #define DEG2RAD (PI/180.0f) +#endif +#ifndef RAD2DEG + #define RAD2DEG (180.0f/PI) +#endif + +#ifndef GL_SHADING_LANGUAGE_VERSION + #define GL_SHADING_LANGUAGE_VERSION 0x8B8C +#endif + +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT + #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_ETC1_RGB8_OES + #define GL_ETC1_RGB8_OES 0x8D64 +#endif +#ifndef GL_COMPRESSED_RGB8_ETC2 + #define GL_COMPRESSED_RGB8_ETC2 0x9274 +#endif +#ifndef GL_COMPRESSED_RGBA8_ETC2_EAC + #define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#endif +#ifndef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG 0x8C00 +#endif +#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG + #define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_4x4_KHR + #define GL_COMPRESSED_RGBA_ASTC_4x4_KHR 0x93b0 +#endif +#ifndef GL_COMPRESSED_RGBA_ASTC_8x8_KHR + #define GL_COMPRESSED_RGBA_ASTC_8x8_KHR 0x93b7 +#endif + +#ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF +#endif +#ifndef GL_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE +#endif + +#ifndef GL_PROGRAM_POINT_SIZE + #define GL_PROGRAM_POINT_SIZE 0x8642 +#endif + +#ifndef GL_LINE_WIDTH + #define GL_LINE_WIDTH 0x0B21 +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + #define GL_UNSIGNED_SHORT_5_6_5 0x8363 + #define GL_UNSIGNED_SHORT_5_5_5_1 0x8034 + #define GL_UNSIGNED_SHORT_4_4_4_4 0x8033 +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + #define GL_LUMINANCE 0x1909 + #define GL_LUMINANCE_ALPHA 0x190A +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + #define glClearDepth glClearDepthf + #if !defined(GRAPHICS_API_OPENGL_ES3) + #define GL_READ_FRAMEBUFFER GL_FRAMEBUFFER + #define GL_DRAW_FRAMEBUFFER GL_FRAMEBUFFER + #endif +#endif + +// Default shader vertex attribute names to set location points +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION + #define RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION "vertexPosition" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD "vertexTexCoord" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL + #define RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL "vertexNormal" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR + #define RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR "vertexColor" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT "vertexTangent" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 + #define RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 "vertexTexCoord2" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2 +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS "vertexBoneIds" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS +#endif +#ifndef RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS + #define RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS "vertexBoneWeights" // Bound by default to shader location: RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS +#endif + +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MVP + #define RL_DEFAULT_SHADER_UNIFORM_NAME_MVP "mvp" // model-view-projection matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW + #define RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW "matView" // view matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION + #define RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION "matProjection" // projection matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL + #define RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL "matModel" // model matrix +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL + #define RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL "matNormal" // normal matrix (transpose(inverse(matModelView)) +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR + #define RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR "colDiffuse" // color diffuse (base tint color, multiplied by texture color) +#endif +#ifndef RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES + #define RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES "boneMatrices" // bone matrices +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0 "texture0" // texture0 (texture slot active 0) +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1 "texture1" // texture1 (texture slot active 1) +#endif +#ifndef RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 + #define RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2 "texture2" // texture2 (texture slot active 2) +#endif + +//---------------------------------------------------------------------------------- +// Types and Structures Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +typedef struct rlglData { + rlRenderBatch *currentBatch; // Current render batch + rlRenderBatch defaultBatch; // Default internal render batch + + struct { + int vertexCounter; // Current active render batch vertex counter (generic, used for all batches) + float texcoordx, texcoordy; // Current active texture coordinate (added on glVertex*()) + float normalx, normaly, normalz; // Current active normal (added on glVertex*()) + unsigned char colorr, colorg, colorb, colora; // Current active color (added on glVertex*()) + + int currentMatrixMode; // Current matrix mode + Matrix *currentMatrix; // Current matrix pointer + Matrix modelview; // Default modelview matrix + Matrix projection; // Default projection matrix + Matrix transform; // Transform matrix to be used with rlTranslate, rlRotate, rlScale + bool transformRequired; // Require transform matrix application to current draw-call vertex (if required) + Matrix stack[RL_MAX_MATRIX_STACK_SIZE];// Matrix stack for push/pop + int stackCounter; // Matrix stack counter + + unsigned int defaultTextureId; // Default texture used on shapes/poly drawing (required by shader) + unsigned int activeTextureId[RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS]; // Active texture ids to be enabled on batch drawing (0 active by default) + unsigned int defaultVShaderId; // Default vertex shader id (used by default shader program) + unsigned int defaultFShaderId; // Default fragment shader id (used by default shader program) + unsigned int defaultShaderId; // Default shader program id, supports vertex color and diffuse texture + int *defaultShaderLocs; // Default shader locations pointer to be used on rendering + unsigned int currentShaderId; // Current shader id to be used on rendering (by default, defaultShaderId) + int *currentShaderLocs; // Current shader locations pointer to be used on rendering (by default, defaultShaderLocs) + + bool stereoRender; // Stereo rendering flag + Matrix projectionStereo[2]; // VR stereo rendering eyes projection matrices + Matrix viewOffsetStereo[2]; // VR stereo rendering eyes view offset matrices + + // Blending variables + int currentBlendMode; // Blending mode active + int glBlendSrcFactor; // Blending source factor + int glBlendDstFactor; // Blending destination factor + int glBlendEquation; // Blending equation + int glBlendSrcFactorRGB; // Blending source RGB factor + int glBlendDestFactorRGB; // Blending destination RGB factor + int glBlendSrcFactorAlpha; // Blending source alpha factor + int glBlendDestFactorAlpha; // Blending destination alpha factor + int glBlendEquationRGB; // Blending equation for RGB + int glBlendEquationAlpha; // Blending equation for alpha + bool glCustomBlendModeModified; // Custom blending factor and equation modification status + + int framebufferWidth; // Current framebuffer width + int framebufferHeight; // Current framebuffer height + + } State; // Renderer state + struct { + bool vao; // VAO support (OpenGL ES2 could not support VAO extension) (GL_ARB_vertex_array_object) + bool instancing; // Instancing supported (GL_ANGLE_instanced_arrays, GL_EXT_draw_instanced + GL_EXT_instanced_arrays) + bool texNPOT; // NPOT textures full support (GL_ARB_texture_non_power_of_two, GL_OES_texture_npot) + bool texDepth; // Depth textures supported (GL_ARB_depth_texture, GL_OES_depth_texture) + bool texDepthWebGL; // Depth textures supported WebGL specific (GL_WEBGL_depth_texture) + bool texFloat32; // float textures support (32 bit per channel) (GL_OES_texture_float) + bool texFloat16; // half float textures support (16 bit per channel) (GL_OES_texture_half_float) + bool texCompDXT; // DDS texture compression support (GL_EXT_texture_compression_s3tc, GL_WEBGL_compressed_texture_s3tc, GL_WEBKIT_WEBGL_compressed_texture_s3tc) + bool texCompETC1; // ETC1 texture compression support (GL_OES_compressed_ETC1_RGB8_texture, GL_WEBGL_compressed_texture_etc1) + bool texCompETC2; // ETC2/EAC texture compression support (GL_ARB_ES3_compatibility) + bool texCompPVRT; // PVR texture compression support (GL_IMG_texture_compression_pvrtc) + bool texCompASTC; // ASTC texture compression support (GL_KHR_texture_compression_astc_hdr, GL_KHR_texture_compression_astc_ldr) + bool texMirrorClamp; // Clamp mirror wrap mode supported (GL_EXT_texture_mirror_clamp) + bool texAnisoFilter; // Anisotropic texture filtering support (GL_EXT_texture_filter_anisotropic) + bool computeShader; // Compute shaders support (GL_ARB_compute_shader) + bool ssbo; // Shader storage buffer object support (GL_ARB_shader_storage_buffer_object) + + float maxAnisotropyLevel; // Maximum anisotropy level supported (minimum is 2.0f) + int maxDepthBits; // Maximum bits for depth component + + } ExtSupported; // Extensions supported flags +} rlglData; + +typedef void *(*rlglLoadProc)(const char *name); // OpenGL extension functions loader signature (same as GLADloadproc) + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +//---------------------------------------------------------------------------------- +// Global Variables Definition +//---------------------------------------------------------------------------------- +static double rlCullDistanceNear = RL_CULL_DISTANCE_NEAR; +static double rlCullDistanceFar = RL_CULL_DISTANCE_FAR; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static rlglData RLGL = { 0 }; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +#if defined(GRAPHICS_API_OPENGL_ES2) && !defined(GRAPHICS_API_OPENGL_ES3) +// NOTE: VAO functionality is exposed through extensions (OES) +static PFNGLGENVERTEXARRAYSOESPROC glGenVertexArrays = NULL; +static PFNGLBINDVERTEXARRAYOESPROC glBindVertexArray = NULL; +static PFNGLDELETEVERTEXARRAYSOESPROC glDeleteVertexArrays = NULL; + +// NOTE: Instancing functionality could also be available through extension +static PFNGLDRAWARRAYSINSTANCEDEXTPROC glDrawArraysInstanced = NULL; +static PFNGLDRAWELEMENTSINSTANCEDEXTPROC glDrawElementsInstanced = NULL; +static PFNGLVERTEXATTRIBDIVISOREXTPROC glVertexAttribDivisor = NULL; +#endif + +//---------------------------------------------------------------------------------- +// Module specific Functions Declaration +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +static void rlLoadShaderDefault(void); // Load default shader +static void rlUnloadShaderDefault(void); // Unload default shader +#if defined(RLGL_SHOW_GL_DETAILS_INFO) +static const char *rlGetCompressedFormatName(int format); // Get compressed format official GL identifier name +#endif // RLGL_SHOW_GL_DETAILS_INFO +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +static int rlGetPixelDataSize(int width, int height, int format); // Get pixel data size in bytes (image or texture) + +// Auxiliar matrix math functions +typedef struct rl_float16 { + float v[16]; +} rl_float16; +static rl_float16 rlMatrixToFloatV(Matrix mat); // Get float array of matrix data +#define rlMatrixToFloat(mat) (rlMatrixToFloatV(mat).v) // Get float vector for Matrix +static Matrix rlMatrixIdentity(void); // Get identity matrix +static Matrix rlMatrixMultiply(Matrix left, Matrix right); // Multiply two matrices +static Matrix rlMatrixTranspose(Matrix mat); // Transposes provided matrix +static Matrix rlMatrixInvert(Matrix mat); // Invert provided matrix + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Matrix operations +//---------------------------------------------------------------------------------- + +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlMatrixMode(int mode) +{ + switch (mode) + { + case RL_PROJECTION: glMatrixMode(GL_PROJECTION); break; + case RL_MODELVIEW: glMatrixMode(GL_MODELVIEW); break; + case RL_TEXTURE: glMatrixMode(GL_TEXTURE); break; + default: break; + } +} + +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + glFrustum(left, right, bottom, top, znear, zfar); +} + +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + glOrtho(left, right, bottom, top, znear, zfar); +} + +void rlPushMatrix(void) { glPushMatrix(); } +void rlPopMatrix(void) { glPopMatrix(); } +void rlLoadIdentity(void) { glLoadIdentity(); } +void rlTranslatef(float x, float y, float z) { glTranslatef(x, y, z); } +void rlRotatef(float angle, float x, float y, float z) { glRotatef(angle, x, y, z); } +void rlScalef(float x, float y, float z) { glScalef(x, y, z); } +void rlMultMatrixf(const float *matf) { glMultMatrixf(matf); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Choose the current matrix to be transformed +void rlMatrixMode(int mode) +{ + if (mode == RL_PROJECTION) RLGL.State.currentMatrix = &RLGL.State.projection; + else if (mode == RL_MODELVIEW) RLGL.State.currentMatrix = &RLGL.State.modelview; + //else if (mode == RL_TEXTURE) // Not supported + + RLGL.State.currentMatrixMode = mode; +} + +// Push the current matrix into RLGL.State.stack +void rlPushMatrix(void) +{ + if (RLGL.State.stackCounter >= RL_MAX_MATRIX_STACK_SIZE) TRACELOG(RL_LOG_ERROR, "RLGL: Matrix stack overflow (RL_MAX_MATRIX_STACK_SIZE)"); + + if (RLGL.State.currentMatrixMode == RL_MODELVIEW) + { + RLGL.State.transformRequired = true; + RLGL.State.currentMatrix = &RLGL.State.transform; + } + + RLGL.State.stack[RLGL.State.stackCounter] = *RLGL.State.currentMatrix; + RLGL.State.stackCounter++; +} + +// Pop lattest inserted matrix from RLGL.State.stack +void rlPopMatrix(void) +{ + if (RLGL.State.stackCounter > 0) + { + Matrix mat = RLGL.State.stack[RLGL.State.stackCounter - 1]; + *RLGL.State.currentMatrix = mat; + RLGL.State.stackCounter--; + } + + if ((RLGL.State.stackCounter == 0) && (RLGL.State.currentMatrixMode == RL_MODELVIEW)) + { + RLGL.State.currentMatrix = &RLGL.State.modelview; + RLGL.State.transformRequired = false; + } +} + +// Reset current matrix to identity matrix +void rlLoadIdentity(void) +{ + *RLGL.State.currentMatrix = rlMatrixIdentity(); +} + +// Multiply the current matrix by a translation matrix +void rlTranslatef(float x, float y, float z) +{ + Matrix matTranslation = { + 1.0f, 0.0f, 0.0f, x, + 0.0f, 1.0f, 0.0f, y, + 0.0f, 0.0f, 1.0f, z, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matTranslation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a rotation matrix +// NOTE: The provided angle must be in degrees +void rlRotatef(float angle, float x, float y, float z) +{ + Matrix matRotation = rlMatrixIdentity(); + + // Axis vector (x, y, z) normalization + float lengthSquared = x*x + y*y + z*z; + if ((lengthSquared != 1.0f) && (lengthSquared != 0.0f)) + { + float inverseLength = 1.0f/sqrtf(lengthSquared); + x *= inverseLength; + y *= inverseLength; + z *= inverseLength; + } + + // Rotation matrix generation + float sinres = sinf(DEG2RAD*angle); + float cosres = cosf(DEG2RAD*angle); + float t = 1.0f - cosres; + + matRotation.m0 = x*x*t + cosres; + matRotation.m1 = y*x*t + z*sinres; + matRotation.m2 = z*x*t - y*sinres; + matRotation.m3 = 0.0f; + + matRotation.m4 = x*y*t - z*sinres; + matRotation.m5 = y*y*t + cosres; + matRotation.m6 = z*y*t + x*sinres; + matRotation.m7 = 0.0f; + + matRotation.m8 = x*z*t + y*sinres; + matRotation.m9 = y*z*t - x*sinres; + matRotation.m10 = z*z*t + cosres; + matRotation.m11 = 0.0f; + + matRotation.m12 = 0.0f; + matRotation.m13 = 0.0f; + matRotation.m14 = 0.0f; + matRotation.m15 = 1.0f; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matRotation, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a scaling matrix +void rlScalef(float x, float y, float z) +{ + Matrix matScale = { + x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + // NOTE: We transpose matrix with multiplication order + *RLGL.State.currentMatrix = rlMatrixMultiply(matScale, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by another matrix +void rlMultMatrixf(const float *matf) +{ + // Matrix creation from array + Matrix mat = { matf[0], matf[4], matf[8], matf[12], + matf[1], matf[5], matf[9], matf[13], + matf[2], matf[6], matf[10], matf[14], + matf[3], matf[7], matf[11], matf[15] }; + + *RLGL.State.currentMatrix = rlMatrixMultiply(mat, *RLGL.State.currentMatrix); +} + +// Multiply the current matrix by a perspective matrix generated by parameters +void rlFrustum(double left, double right, double bottom, double top, double znear, double zfar) +{ + Matrix matFrustum = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matFrustum.m0 = ((float) znear*2.0f)/rl; + matFrustum.m1 = 0.0f; + matFrustum.m2 = 0.0f; + matFrustum.m3 = 0.0f; + + matFrustum.m4 = 0.0f; + matFrustum.m5 = ((float) znear*2.0f)/tb; + matFrustum.m6 = 0.0f; + matFrustum.m7 = 0.0f; + + matFrustum.m8 = ((float)right + (float)left)/rl; + matFrustum.m9 = ((float)top + (float)bottom)/tb; + matFrustum.m10 = -((float)zfar + (float)znear)/fn; + matFrustum.m11 = -1.0f; + + matFrustum.m12 = 0.0f; + matFrustum.m13 = 0.0f; + matFrustum.m14 = -((float)zfar*(float)znear*2.0f)/fn; + matFrustum.m15 = 0.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matFrustum); +} + +// Multiply the current matrix by an orthographic matrix generated by parameters +void rlOrtho(double left, double right, double bottom, double top, double znear, double zfar) +{ + // NOTE: If left-right and top-botton values are equal it could create a division by zero, + // response to it is platform/compiler dependant + Matrix matOrtho = { 0 }; + + float rl = (float)(right - left); + float tb = (float)(top - bottom); + float fn = (float)(zfar - znear); + + matOrtho.m0 = 2.0f/rl; + matOrtho.m1 = 0.0f; + matOrtho.m2 = 0.0f; + matOrtho.m3 = 0.0f; + matOrtho.m4 = 0.0f; + matOrtho.m5 = 2.0f/tb; + matOrtho.m6 = 0.0f; + matOrtho.m7 = 0.0f; + matOrtho.m8 = 0.0f; + matOrtho.m9 = 0.0f; + matOrtho.m10 = -2.0f/fn; + matOrtho.m11 = 0.0f; + matOrtho.m12 = -((float)left + (float)right)/rl; + matOrtho.m13 = -((float)top + (float)bottom)/tb; + matOrtho.m14 = -((float)zfar + (float)znear)/fn; + matOrtho.m15 = 1.0f; + + *RLGL.State.currentMatrix = rlMatrixMultiply(*RLGL.State.currentMatrix, matOrtho); +} +#endif + +// Set the viewport area (transformation from normalized device coordinates to window coordinates) +// NOTE: We store current viewport dimensions +void rlViewport(int x, int y, int width, int height) +{ + glViewport(x, y, width, height); +} + +// Set clip planes distances +void rlSetClipPlanes(double nearPlane, double farPlane) +{ + rlCullDistanceNear = nearPlane; + rlCullDistanceFar = farPlane; +} + +// Get cull plane distance near +double rlGetCullDistanceNear(void) +{ + return rlCullDistanceNear; +} + +// Get cull plane distance far +double rlGetCullDistanceFar(void) +{ + return rlCullDistanceFar; +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - Vertex level operations +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_11) +// Fallback to OpenGL 1.1 function calls +//--------------------------------------- +void rlBegin(int mode) +{ + switch (mode) + { + case RL_LINES: glBegin(GL_LINES); break; + case RL_TRIANGLES: glBegin(GL_TRIANGLES); break; + case RL_QUADS: glBegin(GL_QUADS); break; + default: break; + } +} + +void rlEnd(void) { glEnd(); } +void rlVertex2i(int x, int y) { glVertex2i(x, y); } +void rlVertex2f(float x, float y) { glVertex2f(x, y); } +void rlVertex3f(float x, float y, float z) { glVertex3f(x, y, z); } +void rlTexCoord2f(float x, float y) { glTexCoord2f(x, y); } +void rlNormal3f(float x, float y, float z) { glNormal3f(x, y, z); } +void rlColor4ub(unsigned char r, unsigned char g, unsigned char b, unsigned char a) { glColor4ub(r, g, b, a); } +void rlColor3f(float x, float y, float z) { glColor3f(x, y, z); } +void rlColor4f(float x, float y, float z, float w) { glColor4f(x, y, z, w); } +#endif +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Initialize drawing mode (how to organize vertex) +void rlBegin(int mode) +{ + // Draw mode can be RL_LINES, RL_TRIANGLES and RL_QUADS + // NOTE: In all three cases, vertex are accumulated over default internal vertex buffer + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode != mode) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + RLGL.currentBatch->drawCounter++; + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = mode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = RLGL.State.defaultTextureId; + } +} + +// Finish vertex providing +void rlEnd(void) +{ + // NOTE: Depth increment is dependant on rlOrtho(): z-near and z-far values, + // as well as depth buffer bit-depth (16bit or 24bit or 32bit) + // Correct increment formula would be: depthInc = (zfar - znear)/pow(2, bits) + RLGL.currentBatch->currentDepth += (1.0f/20000.0f); +} + +// Define one vertex (position) +// NOTE: Vertex position data is the basic information required for drawing +void rlVertex3f(float x, float y, float z) +{ + float tx = x; + float ty = y; + float tz = z; + + // Transform provided vector if required + if (RLGL.State.transformRequired) + { + tx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z + RLGL.State.transform.m12; + ty = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z + RLGL.State.transform.m13; + tz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z + RLGL.State.transform.m14; + } + + // WARNING: We can't break primitives when launching a new batch + // RL_LINES comes in pairs, RL_TRIANGLES come in groups of 3 vertices and RL_QUADS come in groups of 4 vertices + // We must check current draw.mode when a new vertex is required and finish the batch only if the draw.mode draw.vertexCount is %2, %3 or %4 + if (RLGL.State.vertexCounter > (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4 - 4)) + { + if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%2 == 0)) + { + // Reached the maximum number of vertices for RL_LINES drawing + // Launch a draw call but keep current state for next vertices comming + // NOTE: We add +1 vertex to the check for security + rlCheckRenderBatchLimit(2 + 1); + } + else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%3 == 0)) + { + rlCheckRenderBatchLimit(3 + 1); + } + else if ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_QUADS) && + (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4 == 0)) + { + rlCheckRenderBatchLimit(4 + 1); + } + } + + // Add vertices + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter] = tx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 1] = ty; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].vertices[3*RLGL.State.vertexCounter + 2] = tz; + + // Add current texcoord + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter] = RLGL.State.texcoordx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].texcoords[2*RLGL.State.vertexCounter + 1] = RLGL.State.texcoordy; + + // Add current normal + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter] = RLGL.State.normalx; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 1] = RLGL.State.normaly; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].normals[3*RLGL.State.vertexCounter + 2] = RLGL.State.normalz; + + // Add current color + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter] = RLGL.State.colorr; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 1] = RLGL.State.colorg; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 2] = RLGL.State.colorb; + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].colors[4*RLGL.State.vertexCounter + 3] = RLGL.State.colora; + + RLGL.State.vertexCounter++; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount++; +} + +// Define one vertex (position) +void rlVertex2f(float x, float y) +{ + rlVertex3f(x, y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (position) +void rlVertex2i(int x, int y) +{ + rlVertex3f((float)x, (float)y, RLGL.currentBatch->currentDepth); +} + +// Define one vertex (texture coordinate) +// NOTE: Texture coordinates are limited to QUADS only +void rlTexCoord2f(float x, float y) +{ + RLGL.State.texcoordx = x; + RLGL.State.texcoordy = y; +} + +// Define one vertex (normal) +// NOTE: Normals limited to TRIANGLES only? +void rlNormal3f(float x, float y, float z) +{ + float normalx = x; + float normaly = y; + float normalz = z; + if (RLGL.State.transformRequired) + { + normalx = RLGL.State.transform.m0*x + RLGL.State.transform.m4*y + RLGL.State.transform.m8*z; + normaly = RLGL.State.transform.m1*x + RLGL.State.transform.m5*y + RLGL.State.transform.m9*z; + normalz = RLGL.State.transform.m2*x + RLGL.State.transform.m6*y + RLGL.State.transform.m10*z; + } + float length = sqrtf(normalx*normalx + normaly*normaly + normalz*normalz); + if (length != 0.0f) + { + float ilength = 1.0f/length; + normalx *= ilength; + normaly *= ilength; + normalz *= ilength; + } + RLGL.State.normalx = normalx; + RLGL.State.normaly = normaly; + RLGL.State.normalz = normalz; +} + +// Define one vertex (color) +void rlColor4ub(unsigned char x, unsigned char y, unsigned char z, unsigned char w) +{ + RLGL.State.colorr = x; + RLGL.State.colorg = y; + RLGL.State.colorb = z; + RLGL.State.colora = w; +} + +// Define one vertex (color) +void rlColor4f(float r, float g, float b, float a) +{ + rlColor4ub((unsigned char)(r*255), (unsigned char)(g*255), (unsigned char)(b*255), (unsigned char)(a*255)); +} + +// Define one vertex (color) +void rlColor3f(float x, float y, float z) +{ + rlColor4ub((unsigned char)(x*255), (unsigned char)(y*255), (unsigned char)(z*255), 255); +} + +#endif + +//-------------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL style functions (common to 1.1, 3.3+, ES2) +//-------------------------------------------------------------------------------------- + +// Set current texture to use +void rlSetTexture(unsigned int id) +{ + if (id == 0) + { +#if defined(GRAPHICS_API_OPENGL_11) + rlDisableTexture(); +#else + // NOTE: If quads batch limit is reached, we force a draw call and next batch starts + if (RLGL.State.vertexCounter >= + RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4) + { + rlDrawRenderBatch(RLGL.currentBatch); + } +#endif + } + else + { +#if defined(GRAPHICS_API_OPENGL_11) + rlEnableTexture(id); +#else + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId != id) + { + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount > 0) + { + // Make sure current RLGL.currentBatch->draws[i].vertexCount is aligned a multiple of 4, + // that way, following QUADS drawing will keep aligned with index processing + // It implies adding some extra alignment vertex at the end of the draw, + // those vertex are not processed but they are considered as an additional offset + // for the next set of vertex to be drawn + if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_LINES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount : RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4); + else if (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode == RL_TRIANGLES) RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = ((RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount < 4)? 1 : (4 - (RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount%4))); + else RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment = 0; + + if (!rlCheckRenderBatchLimit(RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment)) + { + RLGL.State.vertexCounter += RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexAlignment; + + RLGL.currentBatch->drawCounter++; + } + } + + if (RLGL.currentBatch->drawCounter >= RL_DEFAULT_BATCH_DRAWCALLS) rlDrawRenderBatch(RLGL.currentBatch); + + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = id; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].vertexCount = 0; + } +#endif + } +} + +// Select and active a texture slot +void rlActiveTextureSlot(int slot) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glActiveTexture(GL_TEXTURE0 + slot); +#endif +} + +// Enable texture +void rlEnableTexture(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, id); +} + +// Disable texture +void rlDisableTexture(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_TEXTURE_2D); +#endif + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Enable texture cubemap +void rlEnableTextureCubemap(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, id); +#endif +} + +// Disable texture cubemap +void rlDisableTextureCubemap(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Set texture parameters (wrap mode/filter mode) +void rlTextureParameters(unsigned int id, int param, int value) +{ + glBindTexture(GL_TEXTURE_2D, id); + +#if !defined(GRAPHICS_API_OPENGL_11) + // Reset anisotropy filter, in case it was set + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); +#endif + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_2D, param, value); + else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); +#endif + } + else glTexParameteri(GL_TEXTURE_2D, param, value); + + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_2D, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { +#if !defined(GRAPHICS_API_OPENGL_11) + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); +#endif + } break; +#if defined(GRAPHICS_API_OPENGL_33) + case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, value/100.0f); +#endif + default: break; + } + + glBindTexture(GL_TEXTURE_2D, 0); +} + +// Set cubemap parameters (wrap mode/filter mode) +void rlCubemapParameters(unsigned int id, int param, int value) +{ +#if !defined(GRAPHICS_API_OPENGL_11) + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + // Reset anisotropy filter, in case it was set + glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f); + + switch (param) + { + case RL_TEXTURE_WRAP_S: + case RL_TEXTURE_WRAP_T: + { + if (value == RL_TEXTURE_WRAP_MIRROR_CLAMP) + { + if (RLGL.ExtSupported.texMirrorClamp) glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); + else TRACELOG(RL_LOG_WARNING, "GL: Clamp mirror wrap mode not supported (GL_MIRROR_CLAMP_EXT)"); + } + else glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); + + } break; + case RL_TEXTURE_MAG_FILTER: + case RL_TEXTURE_MIN_FILTER: glTexParameteri(GL_TEXTURE_CUBE_MAP, param, value); break; + case RL_TEXTURE_FILTER_ANISOTROPIC: + { + if (value <= RLGL.ExtSupported.maxAnisotropyLevel) glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + else if (RLGL.ExtSupported.maxAnisotropyLevel > 0.0f) + { + TRACELOG(RL_LOG_WARNING, "GL: Maximum anisotropic filter level supported is %iX", id, (int)RLGL.ExtSupported.maxAnisotropyLevel); + glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAX_ANISOTROPY_EXT, (float)value); + } + else TRACELOG(RL_LOG_WARNING, "GL: Anisotropic filtering not supported"); + } break; +#if defined(GRAPHICS_API_OPENGL_33) + case RL_TEXTURE_MIPMAP_BIAS_RATIO: glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_LOD_BIAS, value/100.0f); +#endif + default: break; + } + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif +} + +// Enable shader program +void rlEnableShader(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(id); +#endif +} + +// Disable shader program +void rlDisableShader(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + glUseProgram(0); +#endif +} + +// Enable rendering to texture (fbo) +void rlEnableFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); +#endif +} + +// return the active render texture (fbo) +unsigned int rlGetActiveFramebuffer(void) +{ + GLint fboId = 0; +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &fboId); +#endif + return fboId; +} + +// Disable rendering to texture +void rlDisableFramebuffer(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Blit active framebuffer to main framebuffer +void rlBlitFramebuffer(int srcX, int srcY, int srcWidth, int srcHeight, int dstX, int dstY, int dstWidth, int dstHeight, int bufferMask) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBlitFramebuffer(srcX, srcY, srcWidth, srcHeight, dstX, dstY, dstWidth, dstHeight, bufferMask, GL_NEAREST); +#endif +} + +// Bind framebuffer object (fbo) +void rlBindFramebuffer(unsigned int target, unsigned int framebuffer) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(target, framebuffer); +#endif +} + +// Activate multiple draw color buffers +// NOTE: One color buffer is always active by default +void rlActiveDrawBuffers(int count) +{ +#if ((defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES3)) && defined(RLGL_RENDER_TEXTURES_HINT)) + // NOTE: Maximum number of draw buffers supported is implementation dependant, + // it can be queried with glGet*() but it must be at least 8 + //GLint maxDrawBuffers = 0; + //glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers); + + if (count > 0) + { + if (count > 8) TRACELOG(LOG_WARNING, "GL: Max color buffers limited to 8"); + else + { + unsigned int buffers[8] = { +#if defined(GRAPHICS_API_OPENGL_ES3) + GL_COLOR_ATTACHMENT0_EXT, + GL_COLOR_ATTACHMENT1_EXT, + GL_COLOR_ATTACHMENT2_EXT, + GL_COLOR_ATTACHMENT3_EXT, + GL_COLOR_ATTACHMENT4_EXT, + GL_COLOR_ATTACHMENT5_EXT, + GL_COLOR_ATTACHMENT6_EXT, + GL_COLOR_ATTACHMENT7_EXT, +#else + GL_COLOR_ATTACHMENT0, + GL_COLOR_ATTACHMENT1, + GL_COLOR_ATTACHMENT2, + GL_COLOR_ATTACHMENT3, + GL_COLOR_ATTACHMENT4, + GL_COLOR_ATTACHMENT5, + GL_COLOR_ATTACHMENT6, + GL_COLOR_ATTACHMENT7, +#endif + }; + +#if defined(GRAPHICS_API_OPENGL_ES3) + glDrawBuffersEXT(count, buffers); +#else + glDrawBuffers(count, buffers); +#endif + } + } + else TRACELOG(LOG_WARNING, "GL: One color buffer active by default"); +#endif +} + +//---------------------------------------------------------------------------------- +// General render state configuration +//---------------------------------------------------------------------------------- + +// Enable color blending +void rlEnableColorBlend(void) { glEnable(GL_BLEND); } + +// Disable color blending +void rlDisableColorBlend(void) { glDisable(GL_BLEND); } + +// Enable depth test +void rlEnableDepthTest(void) { glEnable(GL_DEPTH_TEST); } + +// Disable depth test +void rlDisableDepthTest(void) { glDisable(GL_DEPTH_TEST); } + +// Enable depth write +void rlEnableDepthMask(void) { glDepthMask(GL_TRUE); } + +// Disable depth write +void rlDisableDepthMask(void) { glDepthMask(GL_FALSE); } + +// Enable backface culling +void rlEnableBackfaceCulling(void) { glEnable(GL_CULL_FACE); } + +// Disable backface culling +void rlDisableBackfaceCulling(void) { glDisable(GL_CULL_FACE); } + +// Set color mask active for screen read/draw +void rlColorMask(bool r, bool g, bool b, bool a) { glColorMask(r, g, b, a); } + +// Set face culling mode +void rlSetCullFace(int mode) +{ + switch (mode) + { + case RL_CULL_FACE_BACK: glCullFace(GL_BACK); break; + case RL_CULL_FACE_FRONT: glCullFace(GL_FRONT); break; + default: break; + } +} + +// Enable scissor test +void rlEnableScissorTest(void) { glEnable(GL_SCISSOR_TEST); } + +// Disable scissor test +void rlDisableScissorTest(void) { glDisable(GL_SCISSOR_TEST); } + +// Scissor test +void rlScissor(int x, int y, int width, int height) { glScissor(x, y, width, height); } + +// Enable wire mode +void rlEnableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); +#endif +} + +// Enable point mode +void rlEnablePointMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); + glEnable(GL_PROGRAM_POINT_SIZE); +#endif +} + +// Disable wire mode +void rlDisableWireMode(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + // NOTE: glPolygonMode() not available on OpenGL ES + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); +#endif +} + +// Set the line drawing width +void rlSetLineWidth(float width) { glLineWidth(width); } + +// Get the line drawing width +float rlGetLineWidth(void) +{ + float width = 0; + glGetFloatv(GL_LINE_WIDTH, &width); + return width; +} + +// Enable line aliasing +void rlEnableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glEnable(GL_LINE_SMOOTH); +#endif +} + +// Disable line aliasing +void rlDisableSmoothLines(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_11) + glDisable(GL_LINE_SMOOTH); +#endif +} + +// Enable stereo rendering +void rlEnableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = true; +#endif +} + +// Disable stereo rendering +void rlDisableStereoRender(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + RLGL.State.stereoRender = false; +#endif +} + +// Check if stereo render is enabled +bool rlIsStereoRenderEnabled(void) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) + return RLGL.State.stereoRender; +#else + return false; +#endif +} + +// Clear color buffer with color +void rlClearColor(unsigned char r, unsigned char g, unsigned char b, unsigned char a) +{ + // Color values clamp to 0.0f(0) and 1.0f(255) + float cr = (float)r/255; + float cg = (float)g/255; + float cb = (float)b/255; + float ca = (float)a/255; + + glClearColor(cr, cg, cb, ca); +} + +// Clear used screen buffers (color and depth) +void rlClearScreenBuffers(void) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear used buffers: Color and Depth (Depth is used for 3D) + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); // Stencil buffer not used... +} + +// Check and log OpenGL error codes +void rlCheckErrors(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int check = 1; + while (check) + { + const GLenum err = glGetError(); + switch (err) + { + case GL_NO_ERROR: check = 0; break; + case 0x0500: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_ENUM"); break; + case 0x0501: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_VALUE"); break; + case 0x0502: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_OPERATION"); break; + case 0x0503: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_OVERFLOW"); break; + case 0x0504: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_STACK_UNDERFLOW"); break; + case 0x0505: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_OUT_OF_MEMORY"); break; + case 0x0506: TRACELOG(RL_LOG_WARNING, "GL: Error detected: GL_INVALID_FRAMEBUFFER_OPERATION"); break; + default: TRACELOG(RL_LOG_WARNING, "GL: Error detected: Unknown error code: %x", err); break; + } + } +#endif +} + +// Set blend mode +void rlSetBlendMode(int mode) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.currentBlendMode != mode) || ((mode == RL_BLEND_CUSTOM || mode == RL_BLEND_CUSTOM_SEPARATE) && RLGL.State.glCustomBlendModeModified)) + { + rlDrawRenderBatch(RLGL.currentBatch); + + switch (mode) + { + case RL_BLEND_ALPHA: glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADDITIVE: glBlendFunc(GL_SRC_ALPHA, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_MULTIPLIED: glBlendFunc(GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_ADD_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_SUBTRACT_COLORS: glBlendFunc(GL_ONE, GL_ONE); glBlendEquation(GL_FUNC_SUBTRACT); break; + case RL_BLEND_ALPHA_PREMULTIPLY: glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); break; + case RL_BLEND_CUSTOM: + { + // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactors() + glBlendFunc(RLGL.State.glBlendSrcFactor, RLGL.State.glBlendDstFactor); glBlendEquation(RLGL.State.glBlendEquation); + + } break; + case RL_BLEND_CUSTOM_SEPARATE: + { + // NOTE: Using GL blend src/dst factors and GL equation configured with rlSetBlendFactorsSeparate() + glBlendFuncSeparate(RLGL.State.glBlendSrcFactorRGB, RLGL.State.glBlendDestFactorRGB, RLGL.State.glBlendSrcFactorAlpha, RLGL.State.glBlendDestFactorAlpha); + glBlendEquationSeparate(RLGL.State.glBlendEquationRGB, RLGL.State.glBlendEquationAlpha); + + } break; + default: break; + } + + RLGL.State.currentBlendMode = mode; + RLGL.State.glCustomBlendModeModified = false; + } +#endif +} + +// Set blending mode factor and equation +void rlSetBlendFactors(int glSrcFactor, int glDstFactor, int glEquation) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.glBlendSrcFactor != glSrcFactor) || + (RLGL.State.glBlendDstFactor != glDstFactor) || + (RLGL.State.glBlendEquation != glEquation)) + { + RLGL.State.glBlendSrcFactor = glSrcFactor; + RLGL.State.glBlendDstFactor = glDstFactor; + RLGL.State.glBlendEquation = glEquation; + + RLGL.State.glCustomBlendModeModified = true; + } +#endif +} + +// Set blending mode factor and equation separately for RGB and alpha +void rlSetBlendFactorsSeparate(int glSrcRGB, int glDstRGB, int glSrcAlpha, int glDstAlpha, int glEqRGB, int glEqAlpha) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.glBlendSrcFactorRGB != glSrcRGB) || + (RLGL.State.glBlendDestFactorRGB != glDstRGB) || + (RLGL.State.glBlendSrcFactorAlpha != glSrcAlpha) || + (RLGL.State.glBlendDestFactorAlpha != glDstAlpha) || + (RLGL.State.glBlendEquationRGB != glEqRGB) || + (RLGL.State.glBlendEquationAlpha != glEqAlpha)) + { + RLGL.State.glBlendSrcFactorRGB = glSrcRGB; + RLGL.State.glBlendDestFactorRGB = glDstRGB; + RLGL.State.glBlendSrcFactorAlpha = glSrcAlpha; + RLGL.State.glBlendDestFactorAlpha = glDstAlpha; + RLGL.State.glBlendEquationRGB = glEqRGB; + RLGL.State.glBlendEquationAlpha = glEqAlpha; + + RLGL.State.glCustomBlendModeModified = true; + } +#endif +} + +//---------------------------------------------------------------------------------- +// Module Functions Definition - OpenGL Debug +//---------------------------------------------------------------------------------- +#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) +static void GLAPIENTRY rlDebugMessageCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar *message, const void *userParam) +{ + // Ignore non-significant error/warning codes (NVidia drivers) + // NOTE: Here there are the details with a sample output: + // - #131169 - Framebuffer detailed info: The driver allocated storage for renderbuffer 2. (severity: low) + // - #131185 - Buffer detailed info: Buffer object 1 (bound to GL_ELEMENT_ARRAY_BUFFER_ARB, usage hint is GL_ENUM_88e4) + // will use VIDEO memory as the source for buffer object operations. (severity: low) + // - #131218 - Program/shader state performance warning: Vertex shader in program 7 is being recompiled based on GL state. (severity: medium) + // - #131204 - Texture state usage warning: The texture object (0) bound to texture image unit 0 does not have + // a defined base level and cannot be used for texture mapping. (severity: low) + if ((id == 131169) || (id == 131185) || (id == 131218) || (id == 131204)) return; + + const char *msgSource = NULL; + switch (source) + { + case GL_DEBUG_SOURCE_API: msgSource = "API"; break; + case GL_DEBUG_SOURCE_WINDOW_SYSTEM: msgSource = "WINDOW_SYSTEM"; break; + case GL_DEBUG_SOURCE_SHADER_COMPILER: msgSource = "SHADER_COMPILER"; break; + case GL_DEBUG_SOURCE_THIRD_PARTY: msgSource = "THIRD_PARTY"; break; + case GL_DEBUG_SOURCE_APPLICATION: msgSource = "APPLICATION"; break; + case GL_DEBUG_SOURCE_OTHER: msgSource = "OTHER"; break; + default: break; + } + + const char *msgType = NULL; + switch (type) + { + case GL_DEBUG_TYPE_ERROR: msgType = "ERROR"; break; + case GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR: msgType = "DEPRECATED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR: msgType = "UNDEFINED_BEHAVIOR"; break; + case GL_DEBUG_TYPE_PORTABILITY: msgType = "PORTABILITY"; break; + case GL_DEBUG_TYPE_PERFORMANCE: msgType = "PERFORMANCE"; break; + case GL_DEBUG_TYPE_MARKER: msgType = "MARKER"; break; + case GL_DEBUG_TYPE_PUSH_GROUP: msgType = "PUSH_GROUP"; break; + case GL_DEBUG_TYPE_POP_GROUP: msgType = "POP_GROUP"; break; + case GL_DEBUG_TYPE_OTHER: msgType = "OTHER"; break; + default: break; + } + + const char *msgSeverity = "DEFAULT"; + switch (severity) + { + case GL_DEBUG_SEVERITY_LOW: msgSeverity = "LOW"; break; + case GL_DEBUG_SEVERITY_MEDIUM: msgSeverity = "MEDIUM"; break; + case GL_DEBUG_SEVERITY_HIGH: msgSeverity = "HIGH"; break; + case GL_DEBUG_SEVERITY_NOTIFICATION: msgSeverity = "NOTIFICATION"; break; + default: break; + } + + TRACELOG(LOG_WARNING, "GL: OpenGL debug message: %s", message); + TRACELOG(LOG_WARNING, " > Type: %s", msgType); + TRACELOG(LOG_WARNING, " > Source = %s", msgSource); + TRACELOG(LOG_WARNING, " > Severity = %s", msgSeverity); +} +#endif + +//---------------------------------------------------------------------------------- +// Module Functions Definition - rlgl functionality +//---------------------------------------------------------------------------------- + +// Initialize rlgl: OpenGL extensions, default buffers/shaders/textures, OpenGL states +void rlglInit(int width, int height) +{ + // Enable OpenGL debug context if required +#if defined(RLGL_ENABLE_OPENGL_DEBUG_CONTEXT) && defined(GRAPHICS_API_OPENGL_43) + if ((glDebugMessageCallback != NULL) && (glDebugMessageControl != NULL)) + { + glDebugMessageCallback(rlDebugMessageCallback, 0); + // glDebugMessageControl(GL_DEBUG_SOURCE_API, GL_DEBUG_TYPE_ERROR, GL_DEBUG_SEVERITY_HIGH, 0, 0, GL_TRUE); + + // Debug context options: + // - GL_DEBUG_OUTPUT - Faster version but not useful for breakpoints + // - GL_DEBUG_OUTPUT_SYNCHRONUS - Callback is in sync with errors, so a breakpoint can be placed on the callback in order to get a stacktrace for the GL error + glEnable(GL_DEBUG_OUTPUT); + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); + } +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Init default white texture + unsigned char pixels[4] = { 255, 255, 255, 255 }; // 1 pixel RGBA (4 bytes) + RLGL.State.defaultTextureId = rlLoadTexture(pixels, 1, 1, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8, 1); + + if (RLGL.State.defaultTextureId != 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture loaded successfully", RLGL.State.defaultTextureId); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load default texture"); + + // Init default Shader (customized for GL 3.3 and ES2) + // Loaded: RLGL.State.defaultShaderId + RLGL.State.defaultShaderLocs + rlLoadShaderDefault(); + RLGL.State.currentShaderId = RLGL.State.defaultShaderId; + RLGL.State.currentShaderLocs = RLGL.State.defaultShaderLocs; + + // Init default vertex arrays buffers + // Simulate that the default shader has the location RL_SHADER_LOC_VERTEX_NORMAL to bind the normal buffer for the default render batch + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL; + RLGL.defaultBatch = rlLoadRenderBatch(RL_DEFAULT_BATCH_BUFFERS, RL_DEFAULT_BATCH_BUFFER_ELEMENTS); + RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL] = -1; + RLGL.currentBatch = &RLGL.defaultBatch; + + // Init stack matrices (emulating OpenGL 1.1) + for (int i = 0; i < RL_MAX_MATRIX_STACK_SIZE; i++) RLGL.State.stack[i] = rlMatrixIdentity(); + + // Init internal matrices + RLGL.State.transform = rlMatrixIdentity(); + RLGL.State.projection = rlMatrixIdentity(); + RLGL.State.modelview = rlMatrixIdentity(); + RLGL.State.currentMatrix = &RLGL.State.modelview; +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + + // Initialize OpenGL default states + //---------------------------------------------------------- + // Init state: Depth test + glDepthFunc(GL_LEQUAL); // Type of depth testing to apply + glDisable(GL_DEPTH_TEST); // Disable depth testing for 2D (only used for 3D) + + // Init state: Blending mode + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Color blending function (how colors are mixed) + glEnable(GL_BLEND); // Enable color blending (required to work with transparencies) + + // Init state: Culling + // NOTE: All shapes/models triangles are drawn CCW + glCullFace(GL_BACK); // Cull the back face (default) + glFrontFace(GL_CCW); // Front face are defined counter clockwise (default) + glEnable(GL_CULL_FACE); // Enable backface culling + + // Init state: Cubemap seamless +#if defined(GRAPHICS_API_OPENGL_33) + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); // Seamless cubemaps (not supported on OpenGL ES 2.0) +#endif + +#if defined(GRAPHICS_API_OPENGL_11) + // Init state: Color hints (deprecated in OpenGL 3.0+) + glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); // Improve quality of color and texture coordinate interpolation + glShadeModel(GL_SMOOTH); // Smooth shading between vertex (vertex colors interpolation) +#endif + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Store screen size into global variables + RLGL.State.framebufferWidth = width; + RLGL.State.framebufferHeight = height; + + TRACELOG(RL_LOG_INFO, "RLGL: Default OpenGL state initialized successfully"); + //---------------------------------------------------------- +#endif + + // Init state: Color/Depth buffers clear + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set clear color (black) + glClearDepth(1.0f); // Set clear depth value (default) + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear color and depth buffers (depth buffer required for 3D) +} + +// Vertex Buffer Object deinitialization (memory free) +void rlglClose(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlUnloadRenderBatch(RLGL.defaultBatch); + + rlUnloadShaderDefault(); // Unload default shader + + glDeleteTextures(1, &RLGL.State.defaultTextureId); // Unload default texture + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Default texture unloaded successfully", RLGL.State.defaultTextureId); +#endif +} + +// Load OpenGL extensions +// NOTE: External loader function must be provided +void rlLoadExtensions(void *loader) +{ +#if defined(GRAPHICS_API_OPENGL_33) // Also defined for GRAPHICS_API_OPENGL_21 + // NOTE: glad is generated and contains only required OpenGL 3.3 Core extensions (and lower versions) + if (gladLoadGL((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL extensions"); + else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL extensions loaded successfully"); + + // Get number of supported extensions + GLint numExt = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &numExt); + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + // Get supported extensions list + // WARNING: glGetStringi() not available on OpenGL 2.1 + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", glGetStringi(GL_EXTENSIONS, i)); +#endif + +#if defined(GRAPHICS_API_OPENGL_21) + // Register supported extensions flags + // Optional OpenGL 2.1 extensions + RLGL.ExtSupported.vao = GLAD_GL_ARB_vertex_array_object; + RLGL.ExtSupported.instancing = (GLAD_GL_EXT_draw_instanced && GLAD_GL_ARB_instanced_arrays); + RLGL.ExtSupported.texNPOT = GLAD_GL_ARB_texture_non_power_of_two; + RLGL.ExtSupported.texFloat32 = GLAD_GL_ARB_texture_float; + RLGL.ExtSupported.texFloat16 = GLAD_GL_ARB_texture_float; + RLGL.ExtSupported.texDepth = GLAD_GL_ARB_depth_texture; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = GLAD_GL_EXT_texture_filter_anisotropic; + RLGL.ExtSupported.texMirrorClamp = GLAD_GL_EXT_texture_mirror_clamp; +#else + // Register supported extensions flags + // OpenGL 3.3 extensions supported by default (core) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texFloat16 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.maxDepthBits = 32; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; +#endif + + // Optional OpenGL 3.3 extensions + RLGL.ExtSupported.texCompASTC = GLAD_GL_KHR_texture_compression_astc_hdr && GLAD_GL_KHR_texture_compression_astc_ldr; + RLGL.ExtSupported.texCompDXT = GLAD_GL_EXT_texture_compression_s3tc; // Texture compression: DXT + RLGL.ExtSupported.texCompETC2 = GLAD_GL_ARB_ES3_compatibility; // Texture compression: ETC2/EAC + #if defined(GRAPHICS_API_OPENGL_43) + RLGL.ExtSupported.computeShader = GLAD_GL_ARB_compute_shader; + RLGL.ExtSupported.ssbo = GLAD_GL_ARB_shader_storage_buffer_object; + #endif + +#endif // GRAPHICS_API_OPENGL_33 + +#if defined(GRAPHICS_API_OPENGL_ES3) + // Register supported extensions flags + // OpenGL ES 3.0 extensions supported by default (or it should be) + RLGL.ExtSupported.vao = true; + RLGL.ExtSupported.instancing = true; + RLGL.ExtSupported.texNPOT = true; + RLGL.ExtSupported.texFloat32 = true; + RLGL.ExtSupported.texFloat16 = true; + RLGL.ExtSupported.texDepth = true; + RLGL.ExtSupported.texDepthWebGL = true; + RLGL.ExtSupported.maxDepthBits = 24; + RLGL.ExtSupported.texAnisoFilter = true; + RLGL.ExtSupported.texMirrorClamp = true; + // TODO: Check for additional OpenGL ES 3.0 supported extensions: + //RLGL.ExtSupported.texCompDXT = true; + //RLGL.ExtSupported.texCompETC1 = true; + //RLGL.ExtSupported.texCompETC2 = true; + //RLGL.ExtSupported.texCompPVRT = true; + //RLGL.ExtSupported.texCompASTC = true; + //RLGL.ExtSupported.maxAnisotropyLevel = true; + //RLGL.ExtSupported.computeShader = true; + //RLGL.ExtSupported.ssbo = true; + +#elif defined(GRAPHICS_API_OPENGL_ES2) + + #if defined(PLATFORM_DESKTOP_GLFW) || defined(PLATFORM_DESKTOP_SDL) + // TODO: Support GLAD loader for OpenGL ES 3.0 + if (gladLoadGLES2((GLADloadfunc)loader) == 0) TRACELOG(RL_LOG_WARNING, "GLAD: Cannot load OpenGL ES2.0 functions"); + else TRACELOG(RL_LOG_INFO, "GLAD: OpenGL ES 2.0 loaded successfully"); + #endif + + // Get supported extensions list + GLint numExt = 0; + const char **extList = RL_MALLOC(512*sizeof(const char *)); // Allocate 512 strings pointers (2 KB) + const char *extensions = (const char *)glGetString(GL_EXTENSIONS); // One big const string + + // NOTE: We have to duplicate string because glGetString() returns a const string + int size = strlen(extensions) + 1; // Get extensions string size in bytes + char *extensionsDup = (char *)RL_CALLOC(size, sizeof(char)); + strcpy(extensionsDup, extensions); + extList[numExt] = extensionsDup; + + for (int i = 0; i < size; i++) + { + if (extensionsDup[i] == ' ') + { + extensionsDup[i] = '\0'; + numExt++; + extList[numExt] = &extensionsDup[i + 1]; + } + } + + TRACELOG(RL_LOG_INFO, "GL: Supported extensions count: %i", numExt); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + TRACELOG(RL_LOG_INFO, "GL: OpenGL extensions:"); + for (int i = 0; i < numExt; i++) TRACELOG(RL_LOG_INFO, " %s", extList[i]); +#endif + + // Check required extensions + for (int i = 0; i < numExt; i++) + { + // Check VAO support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has VAO support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_vertex_array_object") == 0) + { + // The extension is supported by our hardware and driver, try to get related functions pointers + // NOTE: emscripten does not support VAOs natively, it uses emulation and it reduces overall performance... + glGenVertexArrays = (PFNGLGENVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glGenVertexArraysOES"); + glBindVertexArray = (PFNGLBINDVERTEXARRAYOESPROC)((rlglLoadProc)loader)("glBindVertexArrayOES"); + glDeleteVertexArrays = (PFNGLDELETEVERTEXARRAYSOESPROC)((rlglLoadProc)loader)("glDeleteVertexArraysOES"); + //glIsVertexArray = (PFNGLISVERTEXARRAYOESPROC)loader("glIsVertexArrayOES"); // NOTE: Fails in WebGL, omitted + + if ((glGenVertexArrays != NULL) && (glBindVertexArray != NULL) && (glDeleteVertexArrays != NULL)) RLGL.ExtSupported.vao = true; + } + + // Check instanced rendering support + if (strstr(extList[i], (const char*)"instanced_arrays") != NULL) // Broad check for instanced_arrays + { + // Specific check + if (strcmp(extList[i], (const char *)"GL_ANGLE_instanced_arrays") == 0) // ANGLE + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedANGLE"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedANGLE"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorANGLE"); + } + else if (strcmp(extList[i], (const char *)"GL_EXT_instanced_arrays") == 0) // EXT + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorEXT"); + } + else if (strcmp(extList[i], (const char *)"GL_NV_instanced_arrays") == 0) // NVIDIA GLES + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + glVertexAttribDivisor = (PFNGLVERTEXATTRIBDIVISOREXTPROC)((rlglLoadProc)loader)("glVertexAttribDivisorNV"); + } + + // The feature will only be marked as supported if the elements from GL_XXX_instanced_arrays are present + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + else if (strstr(extList[i], (const char *)"draw_instanced") != NULL) + { + // GL_ANGLE_draw_instanced doesn't exist + if (strcmp(extList[i], (const char *)"GL_EXT_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedEXT"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedEXT"); + } + else if (strcmp(extList[i], (const char*)"GL_NV_draw_instanced") == 0) + { + glDrawArraysInstanced = (PFNGLDRAWARRAYSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawArraysInstancedNV"); + glDrawElementsInstanced = (PFNGLDRAWELEMENTSINSTANCEDEXTPROC)((rlglLoadProc)loader)("glDrawElementsInstancedNV"); + } + + // But the functions will at least be loaded if only GL_XX_EXT_draw_instanced exist + if ((glDrawArraysInstanced != NULL) && (glDrawElementsInstanced != NULL) && (glVertexAttribDivisor != NULL)) RLGL.ExtSupported.instancing = true; + } + + // Check NPOT textures support + // NOTE: Only check on OpenGL ES, OpenGL 3.3 has NPOT textures full support as core feature + if (strcmp(extList[i], (const char *)"GL_OES_texture_npot") == 0) RLGL.ExtSupported.texNPOT = true; + + // Check texture float support + if (strcmp(extList[i], (const char *)"GL_OES_texture_float") == 0) RLGL.ExtSupported.texFloat32 = true; + if (strcmp(extList[i], (const char *)"GL_OES_texture_half_float") == 0) RLGL.ExtSupported.texFloat16 = true; + + // Check depth texture support + if (strcmp(extList[i], (const char *)"GL_OES_depth_texture") == 0) RLGL.ExtSupported.texDepth = true; + if (strcmp(extList[i], (const char *)"GL_WEBGL_depth_texture") == 0) RLGL.ExtSupported.texDepthWebGL = true; // WebGL requires unsized internal format + if (RLGL.ExtSupported.texDepthWebGL) RLGL.ExtSupported.texDepth = true; + + if (strcmp(extList[i], (const char *)"GL_OES_depth24") == 0) RLGL.ExtSupported.maxDepthBits = 24; // Not available on WebGL + if (strcmp(extList[i], (const char *)"GL_OES_depth32") == 0) RLGL.ExtSupported.maxDepthBits = 32; // Not available on WebGL + + // Check texture compression support: DXT + if ((strcmp(extList[i], (const char *)"GL_EXT_texture_compression_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_s3tc") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBKIT_WEBGL_compressed_texture_s3tc") == 0)) RLGL.ExtSupported.texCompDXT = true; + + // Check texture compression support: ETC1 + if ((strcmp(extList[i], (const char *)"GL_OES_compressed_ETC1_RGB8_texture") == 0) || + (strcmp(extList[i], (const char *)"GL_WEBGL_compressed_texture_etc1") == 0)) RLGL.ExtSupported.texCompETC1 = true; + + // Check texture compression support: ETC2/EAC + if (strcmp(extList[i], (const char *)"GL_ARB_ES3_compatibility") == 0) RLGL.ExtSupported.texCompETC2 = true; + + // Check texture compression support: PVR + if (strcmp(extList[i], (const char *)"GL_IMG_texture_compression_pvrtc") == 0) RLGL.ExtSupported.texCompPVRT = true; + + // Check texture compression support: ASTC + if (strcmp(extList[i], (const char *)"GL_KHR_texture_compression_astc_hdr") == 0) RLGL.ExtSupported.texCompASTC = true; + + // Check anisotropic texture filter support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_filter_anisotropic") == 0) RLGL.ExtSupported.texAnisoFilter = true; + + // Check clamp mirror wrap mode support + if (strcmp(extList[i], (const char *)"GL_EXT_texture_mirror_clamp") == 0) RLGL.ExtSupported.texMirrorClamp = true; + } + + // Free extensions pointers + RL_FREE(extList); + RL_FREE(extensionsDup); // Duplicated string must be deallocated +#endif // GRAPHICS_API_OPENGL_ES2 + + // Check OpenGL information and capabilities + //------------------------------------------------------------------------------ + // Show current OpenGL and GLSL version + TRACELOG(RL_LOG_INFO, "GL: OpenGL device information:"); + TRACELOG(RL_LOG_INFO, " > Vendor: %s", glGetString(GL_VENDOR)); + TRACELOG(RL_LOG_INFO, " > Renderer: %s", glGetString(GL_RENDERER)); + TRACELOG(RL_LOG_INFO, " > Version: %s", glGetString(GL_VERSION)); + TRACELOG(RL_LOG_INFO, " > GLSL: %s", glGetString(GL_SHADING_LANGUAGE_VERSION)); + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Anisotropy levels capability is an extension + #ifndef GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT + #define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF + #endif + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &RLGL.ExtSupported.maxAnisotropyLevel); + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) + // Show some OpenGL GPU capabilities + TRACELOG(RL_LOG_INFO, "GL: OpenGL capabilities:"); + GLint capability = 0; + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_CUBE_MAP_TEXTURE_SIZE: %i", capability); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_IMAGE_UNITS: %i", capability); + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIBS: %i", capability); + #if !defined(GRAPHICS_API_OPENGL_ES2) + glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_BLOCK_SIZE: %i", capability); + glGetIntegerv(GL_MAX_DRAW_BUFFERS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_DRAW_BUFFERS: %i", capability); + if (RLGL.ExtSupported.texAnisoFilter) TRACELOG(RL_LOG_INFO, " GL_MAX_TEXTURE_MAX_ANISOTROPY: %.0f", RLGL.ExtSupported.maxAnisotropyLevel); + #endif + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &capability); + TRACELOG(RL_LOG_INFO, " GL_NUM_COMPRESSED_TEXTURE_FORMATS: %i", capability); + GLint *compFormats = (GLint *)RL_CALLOC(capability, sizeof(GLint)); + glGetIntegerv(GL_COMPRESSED_TEXTURE_FORMATS, compFormats); + for (int i = 0; i < capability; i++) TRACELOG(RL_LOG_INFO, " %s", rlGetCompressedFormatName(compFormats[i])); + RL_FREE(compFormats); + +#if defined(GRAPHICS_API_OPENGL_43) + glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_VERTEX_ATTRIB_BINDINGS: %i", capability); + glGetIntegerv(GL_MAX_UNIFORM_LOCATIONS, &capability); + TRACELOG(RL_LOG_INFO, " GL_MAX_UNIFORM_LOCATIONS: %i", capability); +#endif // GRAPHICS_API_OPENGL_43 +#else // RLGL_SHOW_GL_DETAILS_INFO + + // Show some basic info about GL supported features + if (RLGL.ExtSupported.vao) TRACELOG(RL_LOG_INFO, "GL: VAO extension detected, VAO functions loaded successfully"); + else TRACELOG(RL_LOG_WARNING, "GL: VAO extension not found, VAO not supported"); + if (RLGL.ExtSupported.texNPOT) TRACELOG(RL_LOG_INFO, "GL: NPOT textures extension detected, full NPOT textures supported"); + else TRACELOG(RL_LOG_WARNING, "GL: NPOT textures extension not found, limited NPOT support (no-mipmaps, no-repeat)"); + if (RLGL.ExtSupported.texCompDXT) TRACELOG(RL_LOG_INFO, "GL: DXT compressed textures supported"); + if (RLGL.ExtSupported.texCompETC1) TRACELOG(RL_LOG_INFO, "GL: ETC1 compressed textures supported"); + if (RLGL.ExtSupported.texCompETC2) TRACELOG(RL_LOG_INFO, "GL: ETC2/EAC compressed textures supported"); + if (RLGL.ExtSupported.texCompPVRT) TRACELOG(RL_LOG_INFO, "GL: PVRT compressed textures supported"); + if (RLGL.ExtSupported.texCompASTC) TRACELOG(RL_LOG_INFO, "GL: ASTC compressed textures supported"); + if (RLGL.ExtSupported.computeShader) TRACELOG(RL_LOG_INFO, "GL: Compute shaders supported"); + if (RLGL.ExtSupported.ssbo) TRACELOG(RL_LOG_INFO, "GL: Shader storage buffer objects supported"); +#endif // RLGL_SHOW_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 +} + +// Get current OpenGL version +int rlGetVersion(void) +{ + int glVersion = 0; +#if defined(GRAPHICS_API_OPENGL_11) + glVersion = RL_OPENGL_11; +#endif +#if defined(GRAPHICS_API_OPENGL_21) + glVersion = RL_OPENGL_21; +#elif defined(GRAPHICS_API_OPENGL_43) + glVersion = RL_OPENGL_43; +#elif defined(GRAPHICS_API_OPENGL_33) + glVersion = RL_OPENGL_33; +#endif +#if defined(GRAPHICS_API_OPENGL_ES3) + glVersion = RL_OPENGL_ES_30; +#elif defined(GRAPHICS_API_OPENGL_ES2) + glVersion = RL_OPENGL_ES_20; +#endif + + return glVersion; +} + +// Set current framebuffer width +void rlSetFramebufferWidth(int width) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.framebufferWidth = width; +#endif +} + +// Set current framebuffer height +void rlSetFramebufferHeight(int height) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.framebufferHeight = height; +#endif +} + +// Get default framebuffer width +int rlGetFramebufferWidth(void) +{ + int width = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + width = RLGL.State.framebufferWidth; +#endif + return width; +} + +// Get default framebuffer height +int rlGetFramebufferHeight(void) +{ + int height = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + height = RLGL.State.framebufferHeight; +#endif + return height; +} + +// Get default internal texture (white texture) +// NOTE: Default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 +unsigned int rlGetTextureIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultTextureId; +#endif + return id; +} + +// Get default shader id +unsigned int rlGetShaderIdDefault(void) +{ + unsigned int id = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + id = RLGL.State.defaultShaderId; +#endif + return id; +} + +// Get default shader locs +int *rlGetShaderLocsDefault(void) +{ + int *locs = NULL; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + locs = RLGL.State.defaultShaderLocs; +#endif + return locs; +} + +// Render batch management +//------------------------------------------------------------------------------------------------ +// Load render batch +rlRenderBatch rlLoadRenderBatch(int numBuffers, int bufferElements) +{ + rlRenderBatch batch = { 0 }; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Initialize CPU (RAM) vertex buffers (position, texcoord, color data and indexes) + //-------------------------------------------------------------------------------------------- + batch.vertexBuffer = (rlVertexBuffer *)RL_MALLOC(numBuffers*sizeof(rlVertexBuffer)); + + for (int i = 0; i < numBuffers; i++) + { + batch.vertexBuffer[i].elementCount = bufferElements; + + batch.vertexBuffer[i].vertices = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].texcoords = (float *)RL_MALLOC(bufferElements*2*4*sizeof(float)); // 2 float by texcoord, 4 texcoord by quad + batch.vertexBuffer[i].normals = (float *)RL_MALLOC(bufferElements*3*4*sizeof(float)); // 3 float by vertex, 4 vertex by quad + batch.vertexBuffer[i].colors = (unsigned char *)RL_MALLOC(bufferElements*4*4*sizeof(unsigned char)); // 4 float by color, 4 colors by quad +#if defined(GRAPHICS_API_OPENGL_33) + batch.vertexBuffer[i].indices = (unsigned int *)RL_MALLOC(bufferElements*6*sizeof(unsigned int)); // 6 int by quad (indices) +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + batch.vertexBuffer[i].indices = (unsigned short *)RL_MALLOC(bufferElements*6*sizeof(unsigned short)); // 6 int by quad (indices) +#endif + + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].vertices[j] = 0.0f; + for (int j = 0; j < (2*4*bufferElements); j++) batch.vertexBuffer[i].texcoords[j] = 0.0f; + for (int j = 0; j < (3*4*bufferElements); j++) batch.vertexBuffer[i].normals[j] = 0.0f; + for (int j = 0; j < (4*4*bufferElements); j++) batch.vertexBuffer[i].colors[j] = 0; + + int k = 0; + + // Indices can be initialized right now + for (int j = 0; j < (6*bufferElements); j += 6) + { + batch.vertexBuffer[i].indices[j] = 4*k; + batch.vertexBuffer[i].indices[j + 1] = 4*k + 1; + batch.vertexBuffer[i].indices[j + 2] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 3] = 4*k; + batch.vertexBuffer[i].indices[j + 4] = 4*k + 2; + batch.vertexBuffer[i].indices[j + 5] = 4*k + 3; + + k++; + } + + RLGL.State.vertexCounter = 0; + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in RAM (CPU)"); + //-------------------------------------------------------------------------------------------- + + // Upload to GPU (VRAM) vertex data and initialize VAOs/VBOs + //-------------------------------------------------------------------------------------------- + for (int i = 0; i < numBuffers; i++) + { + if (RLGL.ExtSupported.vao) + { + // Initialize Quads VAO + glGenVertexArrays(1, &batch.vertexBuffer[i].vaoId); + glBindVertexArray(batch.vertexBuffer[i].vaoId); + } + + // Quads - Vertex buffers binding and attributes enable + // Vertex position buffer (shader-location = 0) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[0]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].vertices, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + + // Vertex texcoord buffer (shader-location = 1) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[1]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*2*4*sizeof(float), batch.vertexBuffer[i].texcoords, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + + // Vertex normal buffer (shader-location = 2) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[2]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*3*4*sizeof(float), batch.vertexBuffer[i].normals, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + + // Vertex color buffer (shader-location = 3) + glGenBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glBindBuffer(GL_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[3]); + glBufferData(GL_ARRAY_BUFFER, bufferElements*4*4*sizeof(unsigned char), batch.vertexBuffer[i].colors, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + + // Fill index buffer + glGenBuffers(1, &batch.vertexBuffer[i].vboId[4]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch.vertexBuffer[i].vboId[4]); +#if defined(GRAPHICS_API_OPENGL_33) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(int), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif +#if defined(GRAPHICS_API_OPENGL_ES2) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, bufferElements*6*sizeof(short), batch.vertexBuffer[i].indices, GL_STATIC_DRAW); +#endif + } + + TRACELOG(RL_LOG_INFO, "RLGL: Render batch vertex buffers loaded successfully in VRAM (GPU)"); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + //-------------------------------------------------------------------------------------------- + + // Init draw calls tracking system + //-------------------------------------------------------------------------------------------- + batch.draws = (rlDrawCall *)RL_MALLOC(RL_DEFAULT_BATCH_DRAWCALLS*sizeof(rlDrawCall)); + + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch.draws[i].mode = RL_QUADS; + batch.draws[i].vertexCount = 0; + batch.draws[i].vertexAlignment = 0; + //batch.draws[i].vaoId = 0; + //batch.draws[i].shaderId = 0; + batch.draws[i].textureId = RLGL.State.defaultTextureId; + //batch.draws[i].RLGL.State.projection = rlMatrixIdentity(); + //batch.draws[i].RLGL.State.modelview = rlMatrixIdentity(); + } + + batch.bufferCount = numBuffers; // Record buffer count + batch.drawCounter = 1; // Reset draws counter + batch.currentDepth = -1.0f; // Reset depth value + //-------------------------------------------------------------------------------------------- +#endif + + return batch; +} + +// Unload default internal buffers vertex data from CPU and GPU +void rlUnloadRenderBatch(rlRenderBatch batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Unbind everything + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + // Unload all vertex buffers data + for (int i = 0; i < batch.bufferCount; i++) + { + // Unbind VAO attribs data + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(batch.vertexBuffer[i].vaoId); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glDisableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR); + glBindVertexArray(0); + } + + // Delete VBOs from GPU (VRAM) + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[0]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[1]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[2]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[3]); + glDeleteBuffers(1, &batch.vertexBuffer[i].vboId[4]); + + // Delete VAOs from GPU (VRAM) + if (RLGL.ExtSupported.vao) glDeleteVertexArrays(1, &batch.vertexBuffer[i].vaoId); + + // Free vertex arrays memory from CPU (RAM) + RL_FREE(batch.vertexBuffer[i].vertices); + RL_FREE(batch.vertexBuffer[i].texcoords); + RL_FREE(batch.vertexBuffer[i].normals); + RL_FREE(batch.vertexBuffer[i].colors); + RL_FREE(batch.vertexBuffer[i].indices); + } + + // Unload arrays + RL_FREE(batch.vertexBuffer); + RL_FREE(batch.draws); +#endif +} + +// Draw render batch +// NOTE: We require a pointer to reset batch and increase current buffer (multi-buffer) +void rlDrawRenderBatch(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Update batch vertex buffers + //------------------------------------------------------------------------------------------------------------ + // NOTE: If there is not vertex data, buffers doesn't need to be updated (vertexCount > 0) + // TODO: If no data changed on the CPU arrays --> No need to re-update GPU arrays (use a change detector flag?) + if (RLGL.State.vertexCounter > 0) + { + // Activate elements VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + + // Vertex positions buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].vertices); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].vertices, GL_DYNAMIC_DRAW); // Update all buffer + + // Texture coordinates buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*2*sizeof(float), batch->vertexBuffer[batch->currentBuffer].texcoords); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*2*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].texcoords, GL_DYNAMIC_DRAW); // Update all buffer + + // Normals buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*3*sizeof(float), batch->vertexBuffer[batch->currentBuffer].normals); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*3*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].normals, GL_DYNAMIC_DRAW); // Update all buffer + + // Colors buffer + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + glBufferSubData(GL_ARRAY_BUFFER, 0, RLGL.State.vertexCounter*4*sizeof(unsigned char), batch->vertexBuffer[batch->currentBuffer].colors); + //glBufferData(GL_ARRAY_BUFFER, sizeof(float)*4*4*batch->vertexBuffer[batch->currentBuffer].elementCount, batch->vertexBuffer[batch->currentBuffer].colors, GL_DYNAMIC_DRAW); // Update all buffer + + // NOTE: glMapBuffer() causes sync issue + // If GPU is working with this buffer, glMapBuffer() will wait(stall) until GPU to finish its job + // To avoid waiting (idle), you can call first glBufferData() with NULL pointer before glMapBuffer() + // If you do that, the previous data in PBO will be discarded and glMapBuffer() returns a new + // allocated pointer immediately even if GPU is still working with the previous data + + // Another option: map the buffer object into client's memory + // Probably this code could be moved somewhere else... + // batch->vertexBuffer[batch->currentBuffer].vertices = (float *)glMapBuffer(GL_ARRAY_BUFFER, GL_READ_WRITE); + // if (batch->vertexBuffer[batch->currentBuffer].vertices) + // { + // Update vertex data + // } + // glUnmapBuffer(GL_ARRAY_BUFFER); + + // Unbind the current VAO + if (RLGL.ExtSupported.vao) glBindVertexArray(0); + } + //------------------------------------------------------------------------------------------------------------ + + // Draw batch vertex buffers (considering VR stereo if required) + //------------------------------------------------------------------------------------------------------------ + Matrix matProjection = RLGL.State.projection; + Matrix matModelView = RLGL.State.modelview; + + int eyeCount = 1; + if (RLGL.State.stereoRender) eyeCount = 2; + + for (int eye = 0; eye < eyeCount; eye++) + { + if (eyeCount == 2) + { + // Setup current eye viewport (half screen width) + rlViewport(eye*RLGL.State.framebufferWidth/2, 0, RLGL.State.framebufferWidth/2, RLGL.State.framebufferHeight); + + // Set current eye view offset to modelview matrix + rlSetMatrixModelview(rlMatrixMultiply(matModelView, RLGL.State.viewOffsetStereo[eye])); + // Set current eye projection matrix + rlSetMatrixProjection(RLGL.State.projectionStereo[eye]); + } + + // Draw buffers + if (RLGL.State.vertexCounter > 0) + { + // Set current shader and upload current MVP matrix + glUseProgram(RLGL.State.currentShaderId); + + // Create modelview-projection matrix and upload to shader + Matrix matMVP = rlMatrixMultiply(RLGL.State.modelview, RLGL.State.projection); + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MVP], 1, false, rlMatrixToFloat(matMVP)); + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_PROJECTION], 1, false, rlMatrixToFloat(RLGL.State.projection)); + } + + // WARNING: For the following setup of the view, model, and normal matrices, it is expected that + // transformations and rendering occur between rlPushMatrix() and rlPopMatrix() + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_VIEW], 1, false, rlMatrixToFloat(RLGL.State.modelview)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_MODEL], 1, false, rlMatrixToFloat(RLGL.State.transform)); + } + + if (RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL] != -1) + { + glUniformMatrix4fv(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MATRIX_NORMAL], 1, false, rlMatrixToFloat(rlMatrixTranspose(rlMatrixInvert(RLGL.State.transform)))); + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(batch->vertexBuffer[batch->currentBuffer].vaoId); + else + { + // Bind vertex attrib: position (shader-location = 0) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[0]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_POSITION]); + + // Bind vertex attrib: texcoord (shader-location = 1) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[1]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01], 2, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01]); + + // Bind vertex attrib: normal (shader-location = 2) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[2]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL], 3, GL_FLOAT, 0, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_NORMAL]); + + // Bind vertex attrib: color (shader-location = 3) + glBindBuffer(GL_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[3]); + glVertexAttribPointer(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR], 4, GL_UNSIGNED_BYTE, GL_TRUE, 0, 0); + glEnableVertexAttribArray(RLGL.State.currentShaderLocs[RL_SHADER_LOC_VERTEX_COLOR]); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, batch->vertexBuffer[batch->currentBuffer].vboId[4]); + } + + // Setup some default shader values + glUniform4f(RLGL.State.currentShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE], 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1i(RLGL.State.currentShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE], 0); // Active default sampler2D: texture0 + + // Activate additional sampler textures + // Those additional textures will be common for all draw calls of the batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] > 0) + { + glActiveTexture(GL_TEXTURE0 + 1 + i); + glBindTexture(GL_TEXTURE_2D, RLGL.State.activeTextureId[i]); + } + } + + // Activate default sampler2D texture0 (one texture is always active for default batch shader) + // NOTE: Batch system accumulates calls by texture0 changes, additional textures are enabled for all the draw calls + glActiveTexture(GL_TEXTURE0); + + for (int i = 0, vertexOffset = 0; i < batch->drawCounter; i++) + { + // Bind current draw call texture, activated as GL_TEXTURE0 and Bound to sampler2D texture0 by default + glBindTexture(GL_TEXTURE_2D, batch->draws[i].textureId); + + if ((batch->draws[i].mode == RL_LINES) || (batch->draws[i].mode == RL_TRIANGLES)) glDrawArrays(batch->draws[i].mode, vertexOffset, batch->draws[i].vertexCount); + else + { + #if defined(GRAPHICS_API_OPENGL_33) + // We need to define the number of indices to be processed: elementCount*6 + // NOTE: The final parameter tells the GPU the offset in bytes from the + // start of the index buffer to the location of the first index to process + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_INT, (GLvoid *)(vertexOffset/4*6*sizeof(GLuint))); + #endif + #if defined(GRAPHICS_API_OPENGL_ES2) + glDrawElements(GL_TRIANGLES, batch->draws[i].vertexCount/4*6, GL_UNSIGNED_SHORT, (GLvoid *)(vertexOffset/4*6*sizeof(GLushort))); + #endif + } + + vertexOffset += (batch->draws[i].vertexCount + batch->draws[i].vertexAlignment); + } + + if (!RLGL.ExtSupported.vao) + { + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } + + glBindTexture(GL_TEXTURE_2D, 0); // Unbind textures + } + + if (RLGL.ExtSupported.vao) glBindVertexArray(0); // Unbind VAO + + glUseProgram(0); // Unbind shader program + } + + // Restore viewport to default measures + if (eyeCount == 2) rlViewport(0, 0, RLGL.State.framebufferWidth, RLGL.State.framebufferHeight); + //------------------------------------------------------------------------------------------------------------ + + // Reset batch buffers + //------------------------------------------------------------------------------------------------------------ + // Reset vertex counter for next frame + RLGL.State.vertexCounter = 0; + + // Reset depth for next draw + batch->currentDepth = -1.0f; + + // Restore projection/modelview matrices + RLGL.State.projection = matProjection; + RLGL.State.modelview = matModelView; + + // Reset RLGL.currentBatch->draws array + for (int i = 0; i < RL_DEFAULT_BATCH_DRAWCALLS; i++) + { + batch->draws[i].mode = RL_QUADS; + batch->draws[i].vertexCount = 0; + batch->draws[i].textureId = RLGL.State.defaultTextureId; + } + + // Reset active texture units for next batch + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) RLGL.State.activeTextureId[i] = 0; + + // Reset draws counter to one draw for the batch + batch->drawCounter = 1; + //------------------------------------------------------------------------------------------------------------ + + // Change to next buffer in the list (in case of multi-buffering) + batch->currentBuffer++; + if (batch->currentBuffer >= batch->bufferCount) batch->currentBuffer = 0; +#endif +} + +// Set the active render batch for rlgl +void rlSetRenderBatchActive(rlRenderBatch *batch) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); + + if (batch != NULL) RLGL.currentBatch = batch; + else RLGL.currentBatch = &RLGL.defaultBatch; +#endif +} + +// Update and draw internal render batch +void rlDrawRenderBatchActive(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside +#endif +} + +// Check internal buffer overflow for a given number of vertex +// and force a rlRenderBatch draw call if required +bool rlCheckRenderBatchLimit(int vCount) +{ + bool overflow = false; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((RLGL.State.vertexCounter + vCount) >= + (RLGL.currentBatch->vertexBuffer[RLGL.currentBatch->currentBuffer].elementCount*4)) + { + overflow = true; + + // Store current primitive drawing mode and texture id + int currentMode = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode; + int currentTexture = RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId; + + rlDrawRenderBatch(RLGL.currentBatch); // NOTE: Stereo rendering is checked inside + + // Restore state of last batch so we can continue adding vertices + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].mode = currentMode; + RLGL.currentBatch->draws[RLGL.currentBatch->drawCounter - 1].textureId = currentTexture; + } +#endif + + return overflow; +} + +// Textures data management +//----------------------------------------------------------------------------------------- +// Convert image data to OpenGL texture (returns OpenGL valid Id) +unsigned int rlLoadTexture(const void *data, int width, int height, int format, int mipmapCount) +{ + unsigned int id = 0; + + glBindTexture(GL_TEXTURE_2D, 0); // Free any old binding + + // Check texture format support by OpenGL 1.1 (compressed textures not supported) +#if defined(GRAPHICS_API_OPENGL_11) + if (format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + TRACELOG(RL_LOG_WARNING, "GL: OpenGL 1.1 does not support GPU compressed texture formats"); + return id; + } +#else + if ((!RLGL.ExtSupported.texCompDXT) && ((format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA) || + (format == RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: DXT compressed texture format not supported"); + return id; + } +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if ((!RLGL.ExtSupported.texCompETC1) && (format == RL_PIXELFORMAT_COMPRESSED_ETC1_RGB)) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC1 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompETC2) && ((format == RL_PIXELFORMAT_COMPRESSED_ETC2_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ETC2 compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompPVRT) && ((format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGB) || (format == RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: PVRT compressed texture format not supported"); + return id; + } + + if ((!RLGL.ExtSupported.texCompASTC) && ((format == RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA) || (format == RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA))) + { + TRACELOG(RL_LOG_WARNING, "GL: ASTC compressed texture format not supported"); + return id; + } +#endif +#endif // GRAPHICS_API_OPENGL_11 + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + + glGenTextures(1, &id); // Generate texture id + + glBindTexture(GL_TEXTURE_2D, id); + + int mipWidth = width; + int mipHeight = height; + int mipOffset = 0; // Mipmap data offset, only used for tracelog + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + + // Load the different mipmap levels + for (int i = 0; i < mipmapCount; i++) + { + unsigned int mipSize = rlGetPixelDataSize(mipWidth, mipHeight, format); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + TRACELOGD("TEXTURE: Load mipmap level %i (%i x %i), size: %i, offset: %i", i, mipWidth, mipHeight, mipSize, mipOffset); + + if (glInternalFormat != 0) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, glFormat, glType, dataPtr); +#if !defined(GRAPHICS_API_OPENGL_11) + else glCompressedTexImage2D(GL_TEXTURE_2D, i, glInternalFormat, mipWidth, mipHeight, 0, mipSize, dataPtr); +#endif + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + } + + mipWidth /= 2; + mipHeight /= 2; + mipOffset += mipSize; // Increment offset position to next mipmap + if (data != NULL) dataPtr += mipSize; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipWidth < 1) mipWidth = 1; + if (mipHeight < 1) mipHeight = 1; + } + + // Texture parameters configuration + // NOTE: glTexParameteri does NOT affect texture uploading, just the way it's used +#if defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: OpenGL ES 2.0 with no GL_OES_texture_npot support (i.e. WebGL) has limited NPOT support, so CLAMP_TO_EDGE must be used + if (RLGL.ExtSupported.texNPOT) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis + } + else + { + // NOTE: If using negative texture coordinates (LoadOBJ()), it does not work! + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // Set texture to clamp on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Set texture to clamp on y-axis + } +#else + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // Set texture to repeat on x-axis + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Set texture to repeat on y-axis +#endif + + // Magnification and minification filters + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // Alternative: GL_LINEAR + +#if defined(GRAPHICS_API_OPENGL_33) + if (mipmapCount > 1) + { + // Activate Trilinear filtering if mipmaps are available + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } +#endif + + // At this point we have the texture loaded in GPU and texture parameters configured + + // NOTE: If mipmaps were not in data, they are not generated automatically + + // Unbind current texture + glBindTexture(GL_TEXTURE_2D, 0); + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Texture loaded successfully (%ix%i | %s | %i mipmaps)", id, width, height, rlGetPixelFormatName(format), mipmapCount); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load texture"); + + return id; +} + +// Load depth texture/renderbuffer (to be attached to fbo) +// WARNING: OpenGL ES 2.0 requires GL_OES_depth_texture and WebGL requires WEBGL_depth_texture extensions +unsigned int rlLoadTextureDepth(int width, int height, bool useRenderBuffer) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // In case depth textures not supported, we force renderbuffer usage + if (!RLGL.ExtSupported.texDepth) useRenderBuffer = true; + + // NOTE: We let the implementation to choose the best bit-depth + // Possible formats: GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT32 and GL_DEPTH_COMPONENT32F + unsigned int glInternalFormat = GL_DEPTH_COMPONENT; + +#if (defined(GRAPHICS_API_OPENGL_ES2) || defined(GRAPHICS_API_OPENGL_ES3)) + // WARNING: WebGL platform requires unsized internal format definition (GL_DEPTH_COMPONENT) + // while other platforms using OpenGL ES 2.0 require/support sized internal formats depending on the GPU capabilities + if (!RLGL.ExtSupported.texDepthWebGL || useRenderBuffer) + { + if (RLGL.ExtSupported.maxDepthBits == 32) glInternalFormat = GL_DEPTH_COMPONENT32_OES; + else if (RLGL.ExtSupported.maxDepthBits == 24) glInternalFormat = GL_DEPTH_COMPONENT24_OES; + else glInternalFormat = GL_DEPTH_COMPONENT16; + } +#endif + + if (!useRenderBuffer && RLGL.ExtSupported.texDepth) + { + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, NULL); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(GL_TEXTURE_2D, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: Depth texture loaded successfully"); + } + else + { + // Create the renderbuffer that will serve as the depth attachment for the framebuffer + // NOTE: A renderbuffer is simpler than a texture and could offer better performance on embedded devices + glGenRenderbuffers(1, &id); + glBindRenderbuffer(GL_RENDERBUFFER, id); + glRenderbufferStorage(GL_RENDERBUFFER, glInternalFormat, width, height); + + glBindRenderbuffer(GL_RENDERBUFFER, 0); + + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Depth renderbuffer loaded successfully (%i bits)", id, (RLGL.ExtSupported.maxDepthBits >= 24)? RLGL.ExtSupported.maxDepthBits : 16); + } +#endif + + return id; +} + +// Load texture cubemap +// NOTE: Cubemap data is expected to be 6 images in a single data array (one after the other), +// expected the following convention: +X, -X, +Y, -Y, +Z, -Z +unsigned int rlLoadTextureCubemap(const void *data, int size, int format, int mipmapCount) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + int mipSize = size; + + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned char *dataPtr = NULL; + if (data != NULL) dataPtr = (unsigned char *)data; + + unsigned int dataSize = rlGetPixelDataSize(size, size, format); + + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if (glInternalFormat != 0) + { + // Load cubemap faces/mipmaps + for (int i = 0; i < 6*mipmapCount; i++) + { + int mipmapLevel = i/6; + int face = i%6; + + if (data == NULL) + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) + { + if ((format == RL_PIXELFORMAT_UNCOMPRESSED_R32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16) || + (format == RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16)) TRACELOG(RL_LOG_WARNING, "TEXTURES: Cubemap requested format not supported"); + else glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, NULL); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURES: Empty cubemap creation does not support compressed format"); + } + else + { + if (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, glFormat, glType, (unsigned char *)dataPtr + face*dataSize); + else glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, mipmapLevel, glInternalFormat, mipSize, mipSize, 0, dataSize, (unsigned char *)dataPtr + face*dataSize); + } + +#if defined(GRAPHICS_API_OPENGL_33) + if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE) + { + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ONE }; + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } + else if (format == RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA) + { +#if defined(GRAPHICS_API_OPENGL_21) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_ALPHA }; +#elif defined(GRAPHICS_API_OPENGL_33) + GLint swizzleMask[] = { GL_RED, GL_RED, GL_RED, GL_GREEN }; +#endif + glTexParameteriv(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_SWIZZLE_RGBA, swizzleMask); + } +#endif + if (face == 5) + { + mipSize /= 2; + if (data != NULL) dataPtr += dataSize*6; // Increment data pointer to next mipmap + + // Security check for NPOT textures + if (mipSize < 1) mipSize = 1; + + dataSize = rlGetPixelDataSize(mipSize, mipSize, format); + } + } + } + + // Set cubemap texture sampling parameters + if (mipmapCount > 1) glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + else glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); +#if defined(GRAPHICS_API_OPENGL_33) + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // Flag not supported on OpenGL ES 2.0 +#endif + + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +#endif + + if (id > 0) TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Cubemap texture loaded successfully (%ix%i)", id, size, size); + else TRACELOG(RL_LOG_WARNING, "TEXTURE: Failed to load cubemap texture"); + + return id; +} + +// Update already loaded texture in GPU with new data +// NOTE: We don't know safely if internal texture format is the expected one... +void rlUpdateTexture(unsigned int id, int offsetX, int offsetY, int width, int height, int format, const void *data) +{ + glBindTexture(GL_TEXTURE_2D, id); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + + if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + glTexSubImage2D(GL_TEXTURE_2D, 0, offsetX, offsetY, width, height, glFormat, glType, data); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to update for current texture format (%i)", id, format); +} + +// Get OpenGL internal formats and data type from raylib PixelFormat +void rlGetGlTextureFormats(int format, unsigned int *glInternalFormat, unsigned int *glFormat, unsigned int *glType) +{ + *glInternalFormat = 0; + *glFormat = 0; + *glType = 0; + + switch (format) + { + #if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_21) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: on OpenGL ES 2.0 (WebGL), internalFormat must match format and options allowed are: GL_LUMINANCE, GL_RGB, GL_RGBA + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_LUMINANCE_ALPHA; *glFormat = GL_LUMINANCE_ALPHA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + #if !defined(GRAPHICS_API_OPENGL_11) + #if defined(GRAPHICS_API_OPENGL_ES3) + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F_EXT; *glFormat = GL_RED_EXT; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F_EXT; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F_EXT; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F_EXT; *glFormat = GL_RED_EXT; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F_EXT; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F_EXT; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; + #else + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; // NOTE: Requires extension OES_texture_float + #if defined(GRAPHICS_API_OPENGL_21) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_ARB; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_ARB; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_ARB; break; + #else // defined(GRAPHICS_API_OPENGL_ES2) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_LUMINANCE; *glFormat = GL_LUMINANCE; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT_OES; break; // NOTE: Requires extension OES_texture_half_float + #endif + #endif + #endif + #elif defined(GRAPHICS_API_OPENGL_33) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: *glInternalFormat = GL_R8; *glFormat = GL_RED; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: *glInternalFormat = GL_RG8; *glFormat = GL_RG; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: *glInternalFormat = GL_RGB565; *glFormat = GL_RGB; *glType = GL_UNSIGNED_SHORT_5_6_5; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: *glInternalFormat = GL_RGB8; *glFormat = GL_RGB; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: *glInternalFormat = GL_RGB5_A1; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_5_5_5_1; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: *glInternalFormat = GL_RGBA4; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_SHORT_4_4_4_4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: *glInternalFormat = GL_RGBA8; *glFormat = GL_RGBA; *glType = GL_UNSIGNED_BYTE; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_R32F; *glFormat = GL_RED; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGB32F; *glFormat = GL_RGB; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: if (RLGL.ExtSupported.texFloat32) *glInternalFormat = GL_RGBA32F; *glFormat = GL_RGBA; *glType = GL_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_R16F; *glFormat = GL_RED; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGB16F; *glFormat = GL_RGB; *glType = GL_HALF_FLOAT; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: if (RLGL.ExtSupported.texFloat16) *glInternalFormat = GL_RGBA16F; *glFormat = GL_RGBA; *glType = GL_HALF_FLOAT; break; + #endif + #if !defined(GRAPHICS_API_OPENGL_11) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGB_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: if (RLGL.ExtSupported.texCompDXT) *glInternalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; break; + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: if (RLGL.ExtSupported.texCompETC1) *glInternalFormat = GL_ETC1_RGB8_OES; break; // NOTE: Requires OpenGL ES 2.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGB8_ETC2; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: if (RLGL.ExtSupported.texCompETC2) *glInternalFormat = GL_COMPRESSED_RGBA8_ETC2_EAC; break; // NOTE: Requires OpenGL ES 3.0 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: if (RLGL.ExtSupported.texCompPVRT) *glInternalFormat = GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; break; // NOTE: Requires PowerVR GPU + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_4x4_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: if (RLGL.ExtSupported.texCompASTC) *glInternalFormat = GL_COMPRESSED_RGBA_ASTC_8x8_KHR; break; // NOTE: Requires OpenGL ES 3.1 or OpenGL 4.3 + #endif + default: TRACELOG(RL_LOG_WARNING, "TEXTURE: Current format not supported (%i)", format); break; + } +} + +// Unload texture from GPU memory +void rlUnloadTexture(unsigned int id) +{ + glDeleteTextures(1, &id); +} + +// Generate mipmap data for selected texture +// NOTE: Only supports GPU mipmap generation +void rlGenTextureMipmaps(unsigned int id, int width, int height, int format, int *mipmaps) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindTexture(GL_TEXTURE_2D, id); + + // Check if texture is power-of-two (POT) + bool texIsPOT = false; + + if (((width > 0) && ((width & (width - 1)) == 0)) && + ((height > 0) && ((height & (height - 1)) == 0))) texIsPOT = true; + + if ((texIsPOT) || (RLGL.ExtSupported.texNPOT)) + { + //glHint(GL_GENERATE_MIPMAP_HINT, GL_DONT_CARE); // Hint for mipmaps generation algorithm: GL_FASTEST, GL_NICEST, GL_DONT_CARE + glGenerateMipmap(GL_TEXTURE_2D); // Generate mipmaps automatically + + #define MIN(a,b) (((a)<(b))? (a):(b)) + #define MAX(a,b) (((a)>(b))? (a):(b)) + + *mipmaps = 1 + (int)floor(log(MAX(width, height))/log(2)); + TRACELOG(RL_LOG_INFO, "TEXTURE: [ID %i] Mipmaps generated automatically, total: %i", id, *mipmaps); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Failed to generate mipmaps", id); + + glBindTexture(GL_TEXTURE_2D, 0); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] GPU mipmap generation not supported", id); +#endif +} + +// Read texture pixel data +void *rlReadTexturePixels(unsigned int id, int width, int height, int format) +{ + void *pixels = NULL; + +#if defined(GRAPHICS_API_OPENGL_11) || defined(GRAPHICS_API_OPENGL_33) + glBindTexture(GL_TEXTURE_2D, id); + + // NOTE: Using texture id, we can retrieve some texture info (but not on OpenGL ES 2.0) + // Possible texture info: GL_TEXTURE_RED_SIZE, GL_TEXTURE_GREEN_SIZE, GL_TEXTURE_BLUE_SIZE, GL_TEXTURE_ALPHA_SIZE + //int width, height, format; + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height); + //glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format); + + // NOTE: Each row written to or read from by OpenGL pixel operations like glGetTexImage are aligned to a 4 byte boundary by default, which may add some padding + // Use glPixelStorei to modify padding with the GL_[UN]PACK_ALIGNMENT setting + // GL_PACK_ALIGNMENT affects operations that read from OpenGL memory (glReadPixels, glGetTexImage, etc.) + // GL_UNPACK_ALIGNMENT affects operations that write to OpenGL memory (glTexImage, etc.) + glPixelStorei(GL_PACK_ALIGNMENT, 1); + + unsigned int glInternalFormat, glFormat, glType; + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + unsigned int size = rlGetPixelDataSize(width, height, format); + + if ((glInternalFormat != 0) && (format < RL_PIXELFORMAT_COMPRESSED_DXT1_RGB)) + { + pixels = RL_MALLOC(size); + glGetTexImage(GL_TEXTURE_2D, 0, glFormat, glType, pixels); + } + else TRACELOG(RL_LOG_WARNING, "TEXTURE: [ID %i] Data retrieval not suported for pixel format (%i)", id, format); + + glBindTexture(GL_TEXTURE_2D, 0); +#endif + +#if defined(GRAPHICS_API_OPENGL_ES2) + // glGetTexImage() is not available on OpenGL ES 2.0 + // Texture width and height are required on OpenGL ES 2.0, there is no way to get it from texture id + // Two possible Options: + // 1 - Bind texture to color fbo attachment and glReadPixels() + // 2 - Create an fbo, activate it, render quad with texture, glReadPixels() + // We are using Option 1, just need to care for texture format on retrieval + // NOTE: This behaviour could be conditioned by graphic driver... + unsigned int fboId = rlLoadFramebuffer(); + + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + glBindTexture(GL_TEXTURE_2D, 0); + + // Attach our texture to FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, id, 0); + + // We read data as RGBA because FBO texture is configured as RGBA, despite binding another texture format + pixels = (unsigned char *)RL_MALLOC(rlGetPixelDataSize(width, height, RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8)); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // Clean up temporal fbo + rlUnloadFramebuffer(fboId); +#endif + + return pixels; +} + +// Read screen pixel data (color buffer) +unsigned char *rlReadScreenPixels(int width, int height) +{ + unsigned char *screenData = (unsigned char *)RL_CALLOC(width*height*4, sizeof(unsigned char)); + + // NOTE 1: glReadPixels returns image flipped vertically -> (0,0) is the bottom left corner of the framebuffer + // NOTE 2: We are getting alpha channel! Be careful, it can be transparent if not cleared properly! + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, screenData); + + // Flip image vertically! + unsigned char *imgData = (unsigned char *)RL_MALLOC(width*height*4*sizeof(unsigned char)); + + for (int y = height - 1; y >= 0; y--) + { + for (int x = 0; x < (width*4); x++) + { + imgData[((height - 1) - y)*width*4 + x] = screenData[(y*width*4) + x]; // Flip line + + // Set alpha component value to 255 (no trasparent image retrieval) + // NOTE: Alpha value has already been applied to RGB in framebuffer, we don't need it! + if (((x + 1)%4) == 0) imgData[((height - 1) - y)*width*4 + x] = 255; + } + } + + RL_FREE(screenData); + + return imgData; // NOTE: image data should be freed +} + +// Framebuffer management (fbo) +//----------------------------------------------------------------------------------------- +// Load a framebuffer to be used for rendering +// NOTE: No textures attached +unsigned int rlLoadFramebuffer(void) +{ + unsigned int fboId = 0; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glGenFramebuffers(1, &fboId); // Create the framebuffer object + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind any framebuffer +#endif + + return fboId; +} + +// Attach color buffer texture to an fbo (unloads previous attachment) +// NOTE: Attach type: 0-Color, 1-Depth renderbuffer, 2-Depth texture +void rlFramebufferAttach(unsigned int fboId, unsigned int texId, int attachType, int texType, int mipLevel) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, fboId); + + switch (attachType) + { + case RL_ATTACHMENT_COLOR_CHANNEL0: + case RL_ATTACHMENT_COLOR_CHANNEL1: + case RL_ATTACHMENT_COLOR_CHANNEL2: + case RL_ATTACHMENT_COLOR_CHANNEL3: + case RL_ATTACHMENT_COLOR_CHANNEL4: + case RL_ATTACHMENT_COLOR_CHANNEL5: + case RL_ATTACHMENT_COLOR_CHANNEL6: + case RL_ATTACHMENT_COLOR_CHANNEL7: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_RENDERBUFFER, texId); + else if (texType >= RL_ATTACHMENT_CUBEMAP_POSITIVE_X) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachType, GL_TEXTURE_CUBE_MAP_POSITIVE_X + texType, texId, mipLevel); + + } break; + case RL_ATTACHMENT_DEPTH: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + case RL_ATTACHMENT_STENCIL: + { + if (texType == RL_ATTACHMENT_TEXTURE2D) glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texId, mipLevel); + else if (texType == RL_ATTACHMENT_RENDERBUFFER) glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, texId); + + } break; + default: break; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif +} + +// Verify render texture is complete +bool rlFramebufferComplete(unsigned int id) +{ + bool result = false; + +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + glBindFramebuffer(GL_FRAMEBUFFER, id); + + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + + if (status != GL_FRAMEBUFFER_COMPLETE) + { + switch (status) + { + case GL_FRAMEBUFFER_UNSUPPORTED: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer is unsupported", id); break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete attachment", id); break; +#if defined(GRAPHICS_API_OPENGL_ES2) + case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has incomplete dimensions", id); break; +#endif + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: TRACELOG(RL_LOG_WARNING, "FBO: [ID %i] Framebuffer has a missing attachment", id); break; + default: break; + } + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + result = (status == GL_FRAMEBUFFER_COMPLETE); +#endif + + return result; +} + +// Unload framebuffer from GPU memory +// NOTE: All attached textures/cubemaps/renderbuffers are also deleted +void rlUnloadFramebuffer(unsigned int id) +{ +#if (defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2)) && defined(RLGL_RENDER_TEXTURES_HINT) + // Query depth attachment to automatically delete texture/renderbuffer + int depthType = 0, depthId = 0; + glBindFramebuffer(GL_FRAMEBUFFER, id); // Bind framebuffer to query depth texture type + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE, &depthType); + + // TODO: Review warning retrieving object name in WebGL + // WARNING: WebGL: INVALID_ENUM: getFramebufferAttachmentParameter: invalid parameter name + // https://registry.khronos.org/webgl/specs/latest/1.0/ + glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME, &depthId); + + unsigned int depthIdU = (unsigned int)depthId; + if (depthType == GL_RENDERBUFFER) glDeleteRenderbuffers(1, &depthIdU); + else if (depthType == GL_TEXTURE) glDeleteTextures(1, &depthIdU); + + // NOTE: If a texture object is deleted while its image is attached to the *currently bound* framebuffer, + // the texture image is automatically detached from the currently bound framebuffer + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glDeleteFramebuffers(1, &id); + + TRACELOG(RL_LOG_INFO, "FBO: [ID %i] Unloaded framebuffer from VRAM (GPU)", id); +#endif +} + +// Vertex data management +//----------------------------------------------------------------------------------------- +// Load a new attributes buffer +unsigned int rlLoadVertexBuffer(const void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferData(GL_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Load a new attributes element buffer +unsigned int rlLoadVertexBufferElement(const void *buffer, int size, bool dynamic) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glGenBuffers(1, &id); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, size, buffer, dynamic? GL_DYNAMIC_DRAW : GL_STATIC_DRAW); +#endif + + return id; +} + +// Enable vertex buffer (VBO) +void rlEnableVertexBuffer(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer (VBO) +void rlDisableVertexBuffer(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, 0); +#endif +} + +// Enable vertex buffer element (VBO element) +void rlEnableVertexBufferElement(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); +#endif +} + +// Disable vertex buffer element (VBO element) +void rlDisableVertexBufferElement(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); +#endif +} + +// Update vertex buffer with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBuffer(unsigned int id, const void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ARRAY_BUFFER, id); + glBufferSubData(GL_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Update vertex buffer elements with new data +// NOTE: dataSize and offset must be provided in bytes +void rlUpdateVertexBufferElements(unsigned int id, const void *data, int dataSize, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, id); + glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, dataSize, data); +#endif +} + +// Enable vertex array object (VAO) +bool rlEnableVertexArray(unsigned int vaoId) +{ + bool result = false; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(vaoId); + result = true; + } +#endif + return result; +} + +// Disable vertex array object (VAO) +void rlDisableVertexArray(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) glBindVertexArray(0); +#endif +} + +// Enable vertex attribute index +void rlEnableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glEnableVertexAttribArray(index); +#endif +} + +// Disable vertex attribute index +void rlDisableVertexAttribute(unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDisableVertexAttribArray(index); +#endif +} + +// Draw vertex array +void rlDrawVertexArray(int offset, int count) +{ + glDrawArrays(GL_TRIANGLES, offset, count); +} + +// Draw vertex array elements +void rlDrawVertexArrayElements(int offset, int count, const void *buffer) +{ + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned short *bufferPtr = (unsigned short *)buffer; + if (offset > 0) bufferPtr += offset; + + glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr); +} + +// Draw vertex array instanced +void rlDrawVertexArrayInstanced(int offset, int count, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDrawArraysInstanced(GL_TRIANGLES, 0, count, instances); +#endif +} + +// Draw vertex array elements instanced +void rlDrawVertexArrayElementsInstanced(int offset, int count, const void *buffer, int instances) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Added pointer math separately from function to avoid UBSAN complaining + unsigned short *bufferPtr = (unsigned short *)buffer; + if (offset > 0) bufferPtr += offset; + + glDrawElementsInstanced(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, (const unsigned short *)bufferPtr, instances); +#endif +} + +#if defined(GRAPHICS_API_OPENGL_11) +// Enable vertex state pointer +void rlEnableStatePointer(int vertexAttribType, void *buffer) +{ + if (buffer != NULL) glEnableClientState(vertexAttribType); + switch (vertexAttribType) + { + case GL_VERTEX_ARRAY: glVertexPointer(3, GL_FLOAT, 0, buffer); break; + case GL_TEXTURE_COORD_ARRAY: glTexCoordPointer(2, GL_FLOAT, 0, buffer); break; + case GL_NORMAL_ARRAY: if (buffer != NULL) glNormalPointer(GL_FLOAT, 0, buffer); break; + case GL_COLOR_ARRAY: if (buffer != NULL) glColorPointer(4, GL_UNSIGNED_BYTE, 0, buffer); break; + //case GL_INDEX_ARRAY: if (buffer != NULL) glIndexPointer(GL_SHORT, 0, buffer); break; // Indexed colors + default: break; + } +} + +// Disable vertex state pointer +void rlDisableStatePointer(int vertexAttribType) +{ + glDisableClientState(vertexAttribType); +} +#endif + +// Load vertex array object (VAO) +unsigned int rlLoadVertexArray(void) +{ + unsigned int vaoId = 0; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glGenVertexArrays(1, &vaoId); + } +#endif + return vaoId; +} + +// Set vertex attribute +void rlSetVertexAttribute(unsigned int index, int compSize, int type, bool normalized, int stride, int offset) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // NOTE: Data type could be: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT + // Additional types (depends on OpenGL version or extensions): + // - GL_HALF_FLOAT, GL_FLOAT, GL_DOUBLE, GL_FIXED, + // - GL_INT_2_10_10_10_REV, GL_UNSIGNED_INT_2_10_10_10_REV, GL_UNSIGNED_INT_10F_11F_11F_REV + + size_t offsetNative = offset; + glVertexAttribPointer(index, compSize, type, normalized, stride, (void *)offsetNative); +#endif +} + +// Set vertex attribute divisor +void rlSetVertexAttributeDivisor(unsigned int index, int divisor) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glVertexAttribDivisor(index, divisor); +#endif +} + +// Unload vertex array object (VAO) +void rlUnloadVertexArray(unsigned int vaoId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.ExtSupported.vao) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &vaoId); + TRACELOG(RL_LOG_INFO, "VAO: [ID %i] Unloaded vertex array data from VRAM (GPU)", vaoId); + } +#endif +} + +// Unload vertex buffer (VBO) +void rlUnloadVertexBuffer(unsigned int vboId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteBuffers(1, &vboId); + //TRACELOG(RL_LOG_INFO, "VBO: Unloaded vertex data from VRAM (GPU)"); +#endif +} + +// Shaders management +//----------------------------------------------------------------------------------------------- +// Load shader from code strings +// NOTE: If shader string is NULL, using default vertex/fragment shaders +unsigned int rlLoadShaderCode(const char *vsCode, const char *fsCode) +{ + unsigned int id = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int vertexShaderId = 0; + unsigned int fragmentShaderId = 0; + + // Compile vertex shader (if provided) + // NOTE: If not vertex shader is provided, use default one + if (vsCode != NULL) vertexShaderId = rlCompileShader(vsCode, GL_VERTEX_SHADER); + else vertexShaderId = RLGL.State.defaultVShaderId; + + // Compile fragment shader (if provided) + // NOTE: If not vertex shader is provided, use default one + if (fsCode != NULL) fragmentShaderId = rlCompileShader(fsCode, GL_FRAGMENT_SHADER); + else fragmentShaderId = RLGL.State.defaultFShaderId; + + // In case vertex and fragment shader are the default ones, no need to recompile, we can just assign the default shader program id + if ((vertexShaderId == RLGL.State.defaultVShaderId) && (fragmentShaderId == RLGL.State.defaultFShaderId)) id = RLGL.State.defaultShaderId; + else if ((vertexShaderId > 0) && (fragmentShaderId > 0)) + { + // One of or both shader are new, we need to compile a new shader program + id = rlLoadShaderProgram(vertexShaderId, fragmentShaderId); + + // We can detach and delete vertex/fragment shaders (if not default ones) + // NOTE: We detach shader before deletion to make sure memory is freed + if (vertexShaderId != RLGL.State.defaultVShaderId) + { + // WARNING: Shader program linkage could fail and returned id is 0 + if (id > 0) glDetachShader(id, vertexShaderId); + glDeleteShader(vertexShaderId); + } + if (fragmentShaderId != RLGL.State.defaultFShaderId) + { + // WARNING: Shader program linkage could fail and returned id is 0 + if (id > 0) glDetachShader(id, fragmentShaderId); + glDeleteShader(fragmentShaderId); + } + + // In case shader program loading failed, we assign default shader + if (id == 0) + { + // In case shader loading fails, we return the default shader + TRACELOG(RL_LOG_WARNING, "SHADER: Failed to load custom shader code, using default shader"); + id = RLGL.State.defaultShaderId; + } + /* + else + { + // Get available shader uniforms + // NOTE: This information is useful for debug... + int uniformCount = -1; + glGetProgramiv(id, GL_ACTIVE_UNIFORMS, &uniformCount); + + for (int i = 0; i < uniformCount; i++) + { + int namelen = -1; + int num = -1; + char name[256] = { 0 }; // Assume no variable names longer than 256 + GLenum type = GL_ZERO; + + // Get the name of the uniforms + glGetActiveUniform(id, i, sizeof(name) - 1, &namelen, &num, &type, name); + + name[namelen] = 0; + TRACELOGD("SHADER: [ID %i] Active uniform (%s) set at location: %i", id, name, glGetUniformLocation(id, name)); + } + } + */ + } +#endif + + return id; +} + +// Compile custom shader and return shader id +unsigned int rlCompileShader(const char *shaderCode, int type) +{ + unsigned int shader = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + shader = glCreateShader(type); + glShaderSource(shader, 1, &shaderCode, NULL); + + GLint success = 0; + glCompileShader(shader); + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + + if (success == GL_FALSE) + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile vertex shader code", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile fragment shader code", shader); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to compile compute shader code", shader); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; + #endif + default: break; + } + + int maxLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetShaderInfoLog(shader, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Compile error: %s", shader, log); + RL_FREE(log); + } + + shader = 0; + } + else + { + switch (type) + { + case GL_VERTEX_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Vertex shader compiled successfully", shader); break; + case GL_FRAGMENT_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Fragment shader compiled successfully", shader); break; + //case GL_GEOMETRY_SHADER: + #if defined(GRAPHICS_API_OPENGL_43) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader compiled successfully", shader); break; + #elif defined(GRAPHICS_API_OPENGL_33) + case GL_COMPUTE_SHADER: TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43", shader); break; + #endif + default: break; + } + } +#endif + + return shader; +} + +// Load custom shader strings and return program id +unsigned int rlLoadShaderProgram(unsigned int vShaderId, unsigned int fShaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + GLint success = 0; + program = glCreateProgram(); + + glAttachShader(program, vShaderId); + glAttachShader(program, fShaderId); + + // NOTE: Default attribute shader locations must be Bound before linking + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_COLOR, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TANGENT, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD2, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); + +#ifdef RL_SUPPORT_MESH_GPU_SKINNING + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEIDS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); + glBindAttribLocation(program, RL_DEFAULT_SHADER_ATTRIB_LOCATION_BONEWEIGHTS, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); +#endif + + // NOTE: If some attrib name is no found on the shader, it locations becomes -1 + + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Program shader loaded successfully", program); + } +#endif + return program; +} + +// Unload shader program +void rlUnloadShaderProgram(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + glDeleteProgram(id); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Unloaded shader program data from VRAM (GPU)", id); +#endif +} + +// Get shader location uniform +int rlGetLocationUniform(unsigned int shaderId, const char *uniformName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetUniformLocation(shaderId, uniformName); + + //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader uniform: %s", shaderId, uniformName); + //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader uniform (%s) set at location: %i", shaderId, uniformName, location); +#endif + return location; +} + +// Get shader location attribute +int rlGetLocationAttrib(unsigned int shaderId, const char *attribName) +{ + int location = -1; +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + location = glGetAttribLocation(shaderId, attribName); + + //if (location == -1) TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to find shader attribute: %s", shaderId, attribName); + //else TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Shader attribute (%s) set at location: %i", shaderId, attribName, location); +#endif + return location; +} + +// Set shader value uniform +void rlSetUniform(int locIndex, const void *value, int uniformType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (uniformType) + { + case RL_SHADER_UNIFORM_FLOAT: glUniform1fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC2: glUniform2fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC3: glUniform3fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_VEC4: glUniform4fv(locIndex, count, (float *)value); break; + case RL_SHADER_UNIFORM_INT: glUniform1iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC2: glUniform2iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC3: glUniform3iv(locIndex, count, (int *)value); break; + case RL_SHADER_UNIFORM_IVEC4: glUniform4iv(locIndex, count, (int *)value); break; + #if !defined(GRAPHICS_API_OPENGL_ES2) + case RL_SHADER_UNIFORM_UINT: glUniform1uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC2: glUniform2uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC3: glUniform3uiv(locIndex, count, (unsigned int *)value); break; + case RL_SHADER_UNIFORM_UIVEC4: glUniform4uiv(locIndex, count, (unsigned int *)value); break; + #endif + case RL_SHADER_UNIFORM_SAMPLER2D: glUniform1iv(locIndex, count, (int *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set uniform value, data type not recognized"); + + // TODO: Support glUniform1uiv(), glUniform2uiv(), glUniform3uiv(), glUniform4uiv() + } +#endif +} + +// Set shader value attribute +void rlSetVertexAttributeDefault(int locIndex, const void *value, int attribType, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + switch (attribType) + { + case RL_SHADER_ATTRIB_FLOAT: if (count == 1) glVertexAttrib1fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC2: if (count == 2) glVertexAttrib2fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC3: if (count == 3) glVertexAttrib3fv(locIndex, (float *)value); break; + case RL_SHADER_ATTRIB_VEC4: if (count == 4) glVertexAttrib4fv(locIndex, (float *)value); break; + default: TRACELOG(RL_LOG_WARNING, "SHADER: Failed to set attrib default value, data type not recognized"); + } +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrix(int locIndex, Matrix mat) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + float matfloat[16] = { + mat.m0, mat.m1, mat.m2, mat.m3, + mat.m4, mat.m5, mat.m6, mat.m7, + mat.m8, mat.m9, mat.m10, mat.m11, + mat.m12, mat.m13, mat.m14, mat.m15 + }; + glUniformMatrix4fv(locIndex, 1, false, matfloat); +#endif +} + +// Set shader value uniform matrix +void rlSetUniformMatrices(int locIndex, const Matrix *matrices, int count) +{ +#if defined(GRAPHICS_API_OPENGL_33) + glUniformMatrix4fv(locIndex, count, true, (const float *)matrices); +#elif defined(GRAPHICS_API_OPENGL_ES2) + // WARNING: WebGL does not support Matrix transpose ("true" parameter) + // REF: https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniformMatrix + glUniformMatrix4fv(locIndex, count, false, (const float *)matrices); +#endif +} + +// Set shader value uniform sampler +void rlSetUniformSampler(int locIndex, unsigned int textureId) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // Check if texture is already active + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == textureId) + { + glUniform1i(locIndex, 1 + i); + return; + } + } + + // Register a new active texture for the internal batch system + // NOTE: Default texture is always activated as GL_TEXTURE0 + for (int i = 0; i < RL_DEFAULT_BATCH_MAX_TEXTURE_UNITS; i++) + { + if (RLGL.State.activeTextureId[i] == 0) + { + glUniform1i(locIndex, 1 + i); // Activate new texture unit + RLGL.State.activeTextureId[i] = textureId; // Save texture id for binding on drawing + break; + } + } +#endif +} + +// Set shader currently active (id and locations) +void rlSetShader(unsigned int id, int *locs) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + if (RLGL.State.currentShaderId != id) + { + rlDrawRenderBatch(RLGL.currentBatch); + RLGL.State.currentShaderId = id; + RLGL.State.currentShaderLocs = locs; + } +#endif +} + +// Load compute shader program +unsigned int rlLoadComputeShaderProgram(unsigned int shaderId) +{ + unsigned int program = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + GLint success = 0; + program = glCreateProgram(); + glAttachShader(program, shaderId); + glLinkProgram(program); + + // NOTE: All uniform variables are intitialised to 0 when a program links + + glGetProgramiv(program, GL_LINK_STATUS, &success); + + if (success == GL_FALSE) + { + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to link compute shader program", program); + + int maxLength = 0; + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength); + + if (maxLength > 0) + { + int length = 0; + char *log = (char *)RL_CALLOC(maxLength, sizeof(char)); + glGetProgramInfoLog(program, maxLength, &length, log); + TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Link error: %s", program, log); + RL_FREE(log); + } + + glDeleteProgram(program); + + program = 0; + } + else + { + // Get the size of compiled shader program (not available on OpenGL ES 2.0) + // NOTE: If GL_LINK_STATUS is GL_FALSE, program binary length is zero + //GLint binarySize = 0; + //glGetProgramiv(id, GL_PROGRAM_BINARY_LENGTH, &binarySize); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Compute shader program loaded successfully", program); + } +#else + TRACELOG(RL_LOG_WARNING, "SHADER: Compute shaders not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + + return program; +} + +// Dispatch compute shader (equivalent to *draw* for graphics pilepine) +void rlComputeShaderDispatch(unsigned int groupX, unsigned int groupY, unsigned int groupZ) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDispatchCompute(groupX, groupY, groupZ); +#endif +} + +// Load shader storage buffer object (SSBO) +unsigned int rlLoadShaderBuffer(unsigned int size, const void *data, int usageHint) +{ + unsigned int ssbo = 0; + +#if defined(GRAPHICS_API_OPENGL_43) + glGenBuffers(1, &ssbo); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); + glBufferData(GL_SHADER_STORAGE_BUFFER, size, data, usageHint? usageHint : RL_STREAM_COPY); + if (data == NULL) glClearBufferData(GL_SHADER_STORAGE_BUFFER, GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE, NULL); // Clear buffer data to 0 + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + + return ssbo; +} + +// Unload shader storage buffer object (SSBO) +void rlUnloadShaderBuffer(unsigned int ssboId) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glDeleteBuffers(1, &ssboId); +#else + TRACELOG(RL_LOG_WARNING, "SSBO: SSBO not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif + +} + +// Update SSBO buffer data +void rlUpdateShaderBuffer(unsigned int id, const void *data, unsigned int dataSize, unsigned int offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, dataSize, data); +#endif +} + +// Get SSBO buffer size +unsigned int rlGetShaderBufferSize(unsigned int id) +{ +#if defined(GRAPHICS_API_OPENGL_43) + GLint64 size = 0; + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetBufferParameteri64v(GL_SHADER_STORAGE_BUFFER, GL_BUFFER_SIZE, &size); + return (size > 0)? (unsigned int)size : 0; +#else + return 0; +#endif +} + +// Read SSBO buffer data (GPU->CPU) +void rlReadShaderBuffer(unsigned int id, void *dest, unsigned int count, unsigned int offset) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_SHADER_STORAGE_BUFFER, id); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, offset, count, dest); +#endif +} + +// Bind SSBO buffer +void rlBindShaderBuffer(unsigned int id, unsigned int index) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, id); +#endif +} + +// Copy SSBO buffer data +void rlCopyShaderBuffer(unsigned int destId, unsigned int srcId, unsigned int destOffset, unsigned int srcOffset, unsigned int count) +{ +#if defined(GRAPHICS_API_OPENGL_43) + glBindBuffer(GL_COPY_READ_BUFFER, srcId); + glBindBuffer(GL_COPY_WRITE_BUFFER, destId); + glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, srcOffset, destOffset, count); +#endif +} + +// Bind image texture +void rlBindImageTexture(unsigned int id, unsigned int index, int format, bool readonly) +{ +#if defined(GRAPHICS_API_OPENGL_43) + unsigned int glInternalFormat = 0, glFormat = 0, glType = 0; + + rlGetGlTextureFormats(format, &glInternalFormat, &glFormat, &glType); + glBindImageTexture(index, id, 0, 0, 0, readonly? GL_READ_ONLY : GL_READ_WRITE, glInternalFormat); +#else + TRACELOG(RL_LOG_WARNING, "TEXTURE: Image texture binding not enabled. Define GRAPHICS_API_OPENGL_43"); +#endif +} + +// Matrix state management +//----------------------------------------------------------------------------------------- +// Get internal modelview matrix +Matrix rlGetMatrixModelview(void) +{ + Matrix matrix = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_MODELVIEW_MATRIX, mat); + matrix.m0 = mat[0]; + matrix.m1 = mat[1]; + matrix.m2 = mat[2]; + matrix.m3 = mat[3]; + matrix.m4 = mat[4]; + matrix.m5 = mat[5]; + matrix.m6 = mat[6]; + matrix.m7 = mat[7]; + matrix.m8 = mat[8]; + matrix.m9 = mat[9]; + matrix.m10 = mat[10]; + matrix.m11 = mat[11]; + matrix.m12 = mat[12]; + matrix.m13 = mat[13]; + matrix.m14 = mat[14]; + matrix.m15 = mat[15]; +#else + matrix = RLGL.State.modelview; +#endif + return matrix; +} + +// Get internal projection matrix +Matrix rlGetMatrixProjection(void) +{ +#if defined(GRAPHICS_API_OPENGL_11) + float mat[16]; + glGetFloatv(GL_PROJECTION_MATRIX,mat); + Matrix m; + m.m0 = mat[0]; + m.m1 = mat[1]; + m.m2 = mat[2]; + m.m3 = mat[3]; + m.m4 = mat[4]; + m.m5 = mat[5]; + m.m6 = mat[6]; + m.m7 = mat[7]; + m.m8 = mat[8]; + m.m9 = mat[9]; + m.m10 = mat[10]; + m.m11 = mat[11]; + m.m12 = mat[12]; + m.m13 = mat[13]; + m.m14 = mat[14]; + m.m15 = mat[15]; + return m; +#else + return RLGL.State.projection; +#endif +} + +// Get internal accumulated transform matrix +Matrix rlGetMatrixTransform(void) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + // TODO: Consider possible transform matrices in the RLGL.State.stack + // Is this the right order? or should we start with the first stored matrix instead of the last one? + //Matrix matStackTransform = rlMatrixIdentity(); + //for (int i = RLGL.State.stackCounter; i > 0; i--) matStackTransform = rlMatrixMultiply(RLGL.State.stack[i], matStackTransform); + mat = RLGL.State.transform; +#endif + return mat; +} + +// Get internal projection matrix for stereo render (selected eye) +Matrix rlGetMatrixProjectionStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.projectionStereo[eye]; +#endif + return mat; +} + +// Get internal view offset matrix for stereo render (selected eye) +Matrix rlGetMatrixViewOffsetStereo(int eye) +{ + Matrix mat = rlMatrixIdentity(); +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + mat = RLGL.State.viewOffsetStereo[eye]; +#endif + return mat; +} + +// Set a custom modelview matrix (replaces internal modelview matrix) +void rlSetMatrixModelview(Matrix view) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.modelview = view; +#endif +} + +// Set a custom projection matrix (replaces internal projection matrix) +void rlSetMatrixProjection(Matrix projection) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projection = projection; +#endif +} + +// Set eyes projection matrices for stereo rendering +void rlSetMatrixProjectionStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.projectionStereo[0] = right; + RLGL.State.projectionStereo[1] = left; +#endif +} + +// Set eyes view offsets matrices for stereo rendering +void rlSetMatrixViewOffsetStereo(Matrix right, Matrix left) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + RLGL.State.viewOffsetStereo[0] = right; + RLGL.State.viewOffsetStereo[1] = left; +#endif +} + +// Load and draw a quad in NDC +void rlLoadDrawQuad(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int quadVAO = 0; + unsigned int quadVBO = 0; + + float vertices[] = { + // Positions Texcoords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &quadVBO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), &vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, texcoords) + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void *)(3*sizeof(float))); // Texcoords + + // Draw quad + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + + // Delete buffers (VBO and VAO) + glDeleteBuffers(1, &quadVBO); + glDeleteVertexArrays(1, &quadVAO); +#endif +} + +// Load and draw a cube in NDC +void rlLoadDrawCube(void) +{ +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) + unsigned int cubeVAO = 0; + unsigned int cubeVBO = 0; + + float vertices[] = { + // Positions Normals Texcoords + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, -1.0f, 1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, -1.0f, -1.0f, 0.0f, -1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, + -1.0f, 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f + }; + + // Gen VAO to contain VBO + glGenVertexArrays(1, &cubeVAO); + glBindVertexArray(cubeVAO); + + // Gen and fill vertex buffer (VBO) + glGenBuffers(1, &cubeVBO); + glBindBuffer(GL_ARRAY_BUFFER, cubeVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + // Bind vertex attributes (position, normals, texcoords) + glBindVertexArray(cubeVAO); + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0); // Positions + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float))); // Normals + glEnableVertexAttribArray(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD); + glVertexAttribPointer(RL_DEFAULT_SHADER_ATTRIB_LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float))); // Texcoords + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + + // Draw cube + glBindVertexArray(cubeVAO); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + + // Delete VBO and VAO + glDeleteBuffers(1, &cubeVBO); + glDeleteVertexArrays(1, &cubeVAO); +#endif +} + +// Get name string for pixel format +const char *rlGetPixelFormatName(unsigned int format) +{ + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: return "GRAYSCALE"; break; // 8 bit per pixel (no alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: return "GRAY_ALPHA"; break; // 8*2 bpp (2 channels) + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: return "R5G6B5"; break; // 16 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: return "R8G8B8"; break; // 24 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: return "R5G5B5A1"; break; // 16 bpp (1 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: return "R4G4B4A4"; break; // 16 bpp (4 bit alpha) + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: return "R8G8B8A8"; break; // 32 bpp + case RL_PIXELFORMAT_UNCOMPRESSED_R32: return "R32"; break; // 32 bpp (1 channel - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: return "R32G32B32"; break; // 32*3 bpp (3 channels - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: return "R32G32B32A32"; break; // 32*4 bpp (4 channels - float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16: return "R16"; break; // 16 bpp (1 channel - half float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: return "R16G16B16"; break; // 16*3 bpp (3 channels - half float) + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: return "R16G16B16A16"; break; // 16*4 bpp (4 channels - half float) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: return "DXT1_RGB"; break; // 4 bpp (no alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: return "DXT1_RGBA"; break; // 4 bpp (1 bit alpha) + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: return "DXT3_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: return "DXT5_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: return "ETC1_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: return "ETC2_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: return "ETC2_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: return "PVRT_RGB"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: return "PVRT_RGBA"; break; // 4 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: return "ASTC_4x4_RGBA"; break; // 8 bpp + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: return "ASTC_8x8_RGBA"; break; // 2 bpp + default: return "UNKNOWN"; break; + } +} + +//---------------------------------------------------------------------------------- +// Module specific Functions Definition +//---------------------------------------------------------------------------------- +#if defined(GRAPHICS_API_OPENGL_33) || defined(GRAPHICS_API_OPENGL_ES2) +// Load default shader (just vertex positioning and texture coloring) +// NOTE: This shader program is used for internal buffers +// NOTE: Loaded: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlLoadShaderDefault(void) +{ + RLGL.State.defaultShaderLocs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); + + // NOTE: All locations must be reseted to -1 (no location) + for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) RLGL.State.defaultShaderLocs[i] = -1; + + // Vertex shader directly defined, no external file required + const char *defaultVShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) (on some browsers) + "in vec3 vertexPosition; \n" + "in vec2 vertexTexCoord; \n" + "in vec4 vertexColor; \n" + "out vec2 fragTexCoord; \n" + "out vec4 fragColor; \n" +#elif defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) (on some browsers) + "attribute vec3 vertexPosition; \n" + "attribute vec2 vertexTexCoord; \n" + "attribute vec4 vertexColor; \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" +#endif + + "uniform mat4 mvp; \n" + "void main() \n" + "{ \n" + " fragTexCoord = vertexTexCoord; \n" + " fragColor = vertexColor; \n" + " gl_Position = mvp*vec4(vertexPosition, 1.0); \n" + "} \n"; + + // Fragment shader directly defined, no external file required + const char *defaultFShaderCode = +#if defined(GRAPHICS_API_OPENGL_21) + "#version 120 \n" + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_33) + "#version 330 \n" + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + +#if defined(GRAPHICS_API_OPENGL_ES3) + "#version 300 es \n" + "precision mediump float; \n" // Precision required for OpenGL ES3 (WebGL 2) + "in vec2 fragTexCoord; \n" + "in vec4 fragColor; \n" + "out vec4 finalColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture(texture0, fragTexCoord); \n" + " finalColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#elif defined(GRAPHICS_API_OPENGL_ES2) + "#version 100 \n" + "precision mediump float; \n" // Precision required for OpenGL ES2 (WebGL) + "varying vec2 fragTexCoord; \n" + "varying vec4 fragColor; \n" + "uniform sampler2D texture0; \n" + "uniform vec4 colDiffuse; \n" + "void main() \n" + "{ \n" + " vec4 texelColor = texture2D(texture0, fragTexCoord); \n" + " gl_FragColor = texelColor*colDiffuse*fragColor; \n" + "} \n"; +#endif + + // NOTE: Compiled vertex/fragment shaders are not deleted, + // they are kept for re-use as default shaders in case some shader loading fails + RLGL.State.defaultVShaderId = rlCompileShader(defaultVShaderCode, GL_VERTEX_SHADER); // Compile default vertex shader + RLGL.State.defaultFShaderId = rlCompileShader(defaultFShaderCode, GL_FRAGMENT_SHADER); // Compile default fragment shader + + RLGL.State.defaultShaderId = rlLoadShaderProgram(RLGL.State.defaultVShaderId, RLGL.State.defaultFShaderId); + + if (RLGL.State.defaultShaderId > 0) + { + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader loaded successfully", RLGL.State.defaultShaderId); + + // Set default shader locations: attributes locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_POSITION] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_TEXCOORD01] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_VERTEX_COLOR] = glGetAttribLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); + + // Set default shader locations: uniform locations + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MATRIX_MVP] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_COLOR_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); + RLGL.State.defaultShaderLocs[RL_SHADER_LOC_MAP_DIFFUSE] = glGetUniformLocation(RLGL.State.defaultShaderId, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); + } + else TRACELOG(RL_LOG_WARNING, "SHADER: [ID %i] Failed to load default shader", RLGL.State.defaultShaderId); +} + +// Unload default shader +// NOTE: Unloads: RLGL.State.defaultShaderId, RLGL.State.defaultShaderLocs +static void rlUnloadShaderDefault(void) +{ + glUseProgram(0); + + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultVShaderId); + glDetachShader(RLGL.State.defaultShaderId, RLGL.State.defaultFShaderId); + glDeleteShader(RLGL.State.defaultVShaderId); + glDeleteShader(RLGL.State.defaultFShaderId); + + glDeleteProgram(RLGL.State.defaultShaderId); + + RL_FREE(RLGL.State.defaultShaderLocs); + + TRACELOG(RL_LOG_INFO, "SHADER: [ID %i] Default shader unloaded successfully", RLGL.State.defaultShaderId); +} + +#if defined(RLGL_SHOW_GL_DETAILS_INFO) +// Get compressed format official GL identifier name +static const char *rlGetCompressedFormatName(int format) +{ + switch (format) + { + // GL_EXT_texture_compression_s3tc + case 0x83F0: return "GL_COMPRESSED_RGB_S3TC_DXT1_EXT"; break; + case 0x83F1: return "GL_COMPRESSED_RGBA_S3TC_DXT1_EXT"; break; + case 0x83F2: return "GL_COMPRESSED_RGBA_S3TC_DXT3_EXT"; break; + case 0x83F3: return "GL_COMPRESSED_RGBA_S3TC_DXT5_EXT"; break; + // GL_3DFX_texture_compression_FXT1 + case 0x86B0: return "GL_COMPRESSED_RGB_FXT1_3DFX"; break; + case 0x86B1: return "GL_COMPRESSED_RGBA_FXT1_3DFX"; break; + // GL_IMG_texture_compression_pvrtc + case 0x8C00: return "GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG"; break; + case 0x8C01: return "GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG"; break; + case 0x8C02: return "GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG"; break; + case 0x8C03: return "GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG"; break; + // GL_OES_compressed_ETC1_RGB8_texture + case 0x8D64: return "GL_ETC1_RGB8_OES"; break; + // GL_ARB_texture_compression_rgtc + case 0x8DBB: return "GL_COMPRESSED_RED_RGTC1"; break; + case 0x8DBC: return "GL_COMPRESSED_SIGNED_RED_RGTC1"; break; + case 0x8DBD: return "GL_COMPRESSED_RG_RGTC2"; break; + case 0x8DBE: return "GL_COMPRESSED_SIGNED_RG_RGTC2"; break; + // GL_ARB_texture_compression_bptc + case 0x8E8C: return "GL_COMPRESSED_RGBA_BPTC_UNORM_ARB"; break; + case 0x8E8D: return "GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM_ARB"; break; + case 0x8E8E: return "GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT_ARB"; break; + case 0x8E8F: return "GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT_ARB"; break; + // GL_ARB_ES3_compatibility + case 0x9274: return "GL_COMPRESSED_RGB8_ETC2"; break; + case 0x9275: return "GL_COMPRESSED_SRGB8_ETC2"; break; + case 0x9276: return "GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9277: return "GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2"; break; + case 0x9278: return "GL_COMPRESSED_RGBA8_ETC2_EAC"; break; + case 0x9279: return "GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC"; break; + case 0x9270: return "GL_COMPRESSED_R11_EAC"; break; + case 0x9271: return "GL_COMPRESSED_SIGNED_R11_EAC"; break; + case 0x9272: return "GL_COMPRESSED_RG11_EAC"; break; + case 0x9273: return "GL_COMPRESSED_SIGNED_RG11_EAC"; break; + // GL_KHR_texture_compression_astc_hdr + case 0x93B0: return "GL_COMPRESSED_RGBA_ASTC_4x4_KHR"; break; + case 0x93B1: return "GL_COMPRESSED_RGBA_ASTC_5x4_KHR"; break; + case 0x93B2: return "GL_COMPRESSED_RGBA_ASTC_5x5_KHR"; break; + case 0x93B3: return "GL_COMPRESSED_RGBA_ASTC_6x5_KHR"; break; + case 0x93B4: return "GL_COMPRESSED_RGBA_ASTC_6x6_KHR"; break; + case 0x93B5: return "GL_COMPRESSED_RGBA_ASTC_8x5_KHR"; break; + case 0x93B6: return "GL_COMPRESSED_RGBA_ASTC_8x6_KHR"; break; + case 0x93B7: return "GL_COMPRESSED_RGBA_ASTC_8x8_KHR"; break; + case 0x93B8: return "GL_COMPRESSED_RGBA_ASTC_10x5_KHR"; break; + case 0x93B9: return "GL_COMPRESSED_RGBA_ASTC_10x6_KHR"; break; + case 0x93BA: return "GL_COMPRESSED_RGBA_ASTC_10x8_KHR"; break; + case 0x93BB: return "GL_COMPRESSED_RGBA_ASTC_10x10_KHR"; break; + case 0x93BC: return "GL_COMPRESSED_RGBA_ASTC_12x10_KHR"; break; + case 0x93BD: return "GL_COMPRESSED_RGBA_ASTC_12x12_KHR"; break; + case 0x93D0: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR"; break; + case 0x93D1: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR"; break; + case 0x93D2: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR"; break; + case 0x93D3: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR"; break; + case 0x93D4: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR"; break; + case 0x93D5: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR"; break; + case 0x93D6: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR"; break; + case 0x93D7: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR"; break; + case 0x93D8: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR"; break; + case 0x93D9: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR"; break; + case 0x93DA: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR"; break; + case 0x93DB: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR"; break; + case 0x93DC: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR"; break; + case 0x93DD: return "GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR"; break; + default: return "GL_COMPRESSED_UNKNOWN"; break; + } +} +#endif // RLGL_SHOW_GL_DETAILS_INFO + +#endif // GRAPHICS_API_OPENGL_33 || GRAPHICS_API_OPENGL_ES2 + +// Get pixel data size in bytes (image or texture) +// NOTE: Size depends on pixel format +static int rlGetPixelDataSize(int width, int height, int format) +{ + int dataSize = 0; // Size in bytes + int bpp = 0; // Bits per pixel + + switch (format) + { + case RL_PIXELFORMAT_UNCOMPRESSED_GRAYSCALE: bpp = 8; break; + case RL_PIXELFORMAT_UNCOMPRESSED_GRAY_ALPHA: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G6B5: + case RL_PIXELFORMAT_UNCOMPRESSED_R5G5B5A1: + case RL_PIXELFORMAT_UNCOMPRESSED_R4G4B4A4: bpp = 16; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8A8: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R8G8B8: bpp = 24; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32: bpp = 32; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32: bpp = 32*3; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R32G32B32A32: bpp = 32*4; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16: bpp = 16; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16: bpp = 16*3; break; + case RL_PIXELFORMAT_UNCOMPRESSED_R16G16B16A16: bpp = 16*4; break; + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGB: + case RL_PIXELFORMAT_COMPRESSED_DXT1_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC1_RGB: + case RL_PIXELFORMAT_COMPRESSED_ETC2_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGB: + case RL_PIXELFORMAT_COMPRESSED_PVRT_RGBA: bpp = 4; break; + case RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA: + case RL_PIXELFORMAT_COMPRESSED_DXT5_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ETC2_EAC_RGBA: + case RL_PIXELFORMAT_COMPRESSED_ASTC_4x4_RGBA: bpp = 8; break; + case RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA: bpp = 2; break; + default: break; + } + + double bytesPerPixel = (double)bpp/8.0; + dataSize = (int)(bytesPerPixel*width*height); // Total data size in bytes + + // Most compressed formats works on 4x4 blocks, + // if texture is smaller, minimum dataSize is 8 or 16 + if ((width < 4) && (height < 4)) + { + if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT1_RGB) && (format < RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA)) dataSize = 8; + else if ((format >= RL_PIXELFORMAT_COMPRESSED_DXT3_RGBA) && (format < RL_PIXELFORMAT_COMPRESSED_ASTC_8x8_RGBA)) dataSize = 16; + } + + return dataSize; +} + +// Auxiliar math functions + +// Get float array of matrix data +static rl_float16 rlMatrixToFloatV(Matrix mat) +{ + rl_float16 result = { 0 }; + + result.v[0] = mat.m0; + result.v[1] = mat.m1; + result.v[2] = mat.m2; + result.v[3] = mat.m3; + result.v[4] = mat.m4; + result.v[5] = mat.m5; + result.v[6] = mat.m6; + result.v[7] = mat.m7; + result.v[8] = mat.m8; + result.v[9] = mat.m9; + result.v[10] = mat.m10; + result.v[11] = mat.m11; + result.v[12] = mat.m12; + result.v[13] = mat.m13; + result.v[14] = mat.m14; + result.v[15] = mat.m15; + + return result; +} + +// Get identity matrix +static Matrix rlMatrixIdentity(void) +{ + Matrix result = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + return result; +} + +// Get two matrix multiplication +// NOTE: When multiplying matrices... the order matters! +static Matrix rlMatrixMultiply(Matrix left, Matrix right) +{ + Matrix result = { 0 }; + + result.m0 = left.m0*right.m0 + left.m1*right.m4 + left.m2*right.m8 + left.m3*right.m12; + result.m1 = left.m0*right.m1 + left.m1*right.m5 + left.m2*right.m9 + left.m3*right.m13; + result.m2 = left.m0*right.m2 + left.m1*right.m6 + left.m2*right.m10 + left.m3*right.m14; + result.m3 = left.m0*right.m3 + left.m1*right.m7 + left.m2*right.m11 + left.m3*right.m15; + result.m4 = left.m4*right.m0 + left.m5*right.m4 + left.m6*right.m8 + left.m7*right.m12; + result.m5 = left.m4*right.m1 + left.m5*right.m5 + left.m6*right.m9 + left.m7*right.m13; + result.m6 = left.m4*right.m2 + left.m5*right.m6 + left.m6*right.m10 + left.m7*right.m14; + result.m7 = left.m4*right.m3 + left.m5*right.m7 + left.m6*right.m11 + left.m7*right.m15; + result.m8 = left.m8*right.m0 + left.m9*right.m4 + left.m10*right.m8 + left.m11*right.m12; + result.m9 = left.m8*right.m1 + left.m9*right.m5 + left.m10*right.m9 + left.m11*right.m13; + result.m10 = left.m8*right.m2 + left.m9*right.m6 + left.m10*right.m10 + left.m11*right.m14; + result.m11 = left.m8*right.m3 + left.m9*right.m7 + left.m10*right.m11 + left.m11*right.m15; + result.m12 = left.m12*right.m0 + left.m13*right.m4 + left.m14*right.m8 + left.m15*right.m12; + result.m13 = left.m12*right.m1 + left.m13*right.m5 + left.m14*right.m9 + left.m15*right.m13; + result.m14 = left.m12*right.m2 + left.m13*right.m6 + left.m14*right.m10 + left.m15*right.m14; + result.m15 = left.m12*right.m3 + left.m13*right.m7 + left.m14*right.m11 + left.m15*right.m15; + + return result; +} + +// Transposes provided matrix +static Matrix rlMatrixTranspose(Matrix mat) +{ + Matrix result = { 0 }; + + result.m0 = mat.m0; + result.m1 = mat.m4; + result.m2 = mat.m8; + result.m3 = mat.m12; + result.m4 = mat.m1; + result.m5 = mat.m5; + result.m6 = mat.m9; + result.m7 = mat.m13; + result.m8 = mat.m2; + result.m9 = mat.m6; + result.m10 = mat.m10; + result.m11 = mat.m14; + result.m12 = mat.m3; + result.m13 = mat.m7; + result.m14 = mat.m11; + result.m15 = mat.m15; + + return result; +} + +// Invert provided matrix +static Matrix rlMatrixInvert(Matrix mat) +{ + Matrix result = { 0 }; + + // Cache the matrix values (speed optimization) + float a00 = mat.m0, a01 = mat.m1, a02 = mat.m2, a03 = mat.m3; + float a10 = mat.m4, a11 = mat.m5, a12 = mat.m6, a13 = mat.m7; + float a20 = mat.m8, a21 = mat.m9, a22 = mat.m10, a23 = mat.m11; + float a30 = mat.m12, a31 = mat.m13, a32 = mat.m14, a33 = mat.m15; + + float b00 = a00*a11 - a01*a10; + float b01 = a00*a12 - a02*a10; + float b02 = a00*a13 - a03*a10; + float b03 = a01*a12 - a02*a11; + float b04 = a01*a13 - a03*a11; + float b05 = a02*a13 - a03*a12; + float b06 = a20*a31 - a21*a30; + float b07 = a20*a32 - a22*a30; + float b08 = a20*a33 - a23*a30; + float b09 = a21*a32 - a22*a31; + float b10 = a21*a33 - a23*a31; + float b11 = a22*a33 - a23*a32; + + // Calculate the invert determinant (inlined to avoid double-caching) + float invDet = 1.0f/(b00*b11 - b01*b10 + b02*b09 + b03*b08 - b04*b07 + b05*b06); + + result.m0 = (a11*b11 - a12*b10 + a13*b09)*invDet; + result.m1 = (-a01*b11 + a02*b10 - a03*b09)*invDet; + result.m2 = (a31*b05 - a32*b04 + a33*b03)*invDet; + result.m3 = (-a21*b05 + a22*b04 - a23*b03)*invDet; + result.m4 = (-a10*b11 + a12*b08 - a13*b07)*invDet; + result.m5 = (a00*b11 - a02*b08 + a03*b07)*invDet; + result.m6 = (-a30*b05 + a32*b02 - a33*b01)*invDet; + result.m7 = (a20*b05 - a22*b02 + a23*b01)*invDet; + result.m8 = (a10*b10 - a11*b08 + a13*b06)*invDet; + result.m9 = (-a00*b10 + a01*b08 - a03*b06)*invDet; + result.m10 = (a30*b04 - a31*b02 + a33*b00)*invDet; + result.m11 = (-a20*b04 + a21*b02 - a23*b00)*invDet; + result.m12 = (-a10*b09 + a11*b07 - a12*b06)*invDet; + result.m13 = (a00*b09 - a01*b07 + a02*b06)*invDet; + result.m14 = (-a30*b03 + a31*b01 - a32*b00)*invDet; + result.m15 = (a20*b03 - a21*b01 + a22*b00)*invDet; + + return result; +} + +#endif // RLGL_IMPLEMENTATION diff --git a/third_party/raylib/larch64/libraylib.a b/third_party/raylib/larch64/libraylib.a new file mode 100644 index 00000000000..fa538e52143 --- /dev/null +++ b/third_party/raylib/larch64/libraylib.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f760af8b4693cf60e3760341e5275890d78d933da2354c4bad0572ec575b970a +size 2001860 diff --git a/third_party/raylib/x86_64/libraylib.a b/third_party/raylib/x86_64/libraylib.a new file mode 100644 index 00000000000..ea124c1bcfe --- /dev/null +++ b/third_party/raylib/x86_64/libraylib.a @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c928e849b51b04d8e3603cd649184299efed0e9e0fb02201612b967b31efd73 +size 2771092 diff --git a/uv.lock b/uv.lock index e12842ac5b5..f5c128fcb76 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "casadi" @@ -381,7 +381,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "execnet" @@ -395,7 +395,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "fonttools" @@ -442,7 +442,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "ghp-import" @@ -459,7 +459,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "google-crc32c" @@ -577,7 +577,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "libusb1" @@ -593,7 +593,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "markdown" @@ -745,7 +745,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "numpy" @@ -912,7 +912,7 @@ requires-dist = [ { name = "python3-dev", git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases" }, { name = "pyzmq" }, { name = "qrcode" }, - { name = "raylib", git = "https://github.com/commaai/dependencies.git?subdirectory=raylib&rev=releases" }, + { name = "raylib", specifier = ">5.5.0.3" }, { name = "requests" }, { name = "ruff", marker = "extra == 'testing'" }, { name = "scons" }, @@ -935,7 +935,7 @@ provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "openssl3" version = "3.4.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "packaging" @@ -1306,7 +1306,7 @@ wheels = [ [[package]] name = "python3-dev" version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "pyyaml" @@ -1374,10 +1374,20 @@ wheels = [ [[package]] name = "raylib" version = "5.5.0.4" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=raylib&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, ] +sdist = { url = "https://files.pythonhosted.org/packages/7c/4b/858958762c075c54058ee3b0771838fd505ca908871e6a0397b01086e526/raylib-5.5.0.4.tar.gz", hash = "sha256:996506e8a533cd7a6a3ef6c44ec11f9d6936698f2c394a991af8022be33079a0", size = 184413, upload-time = "2025-12-11T15:32:12.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/21/9117d7013997a65f6d51c6f56145b2c583eeba8f7c1af71a60776eaae9b9/raylib-5.5.0.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:31f64f71e42fed10e8f3629028c9f5700906e0e573b915cfc2244d7a3f3b2ed9", size = 1635486, upload-time = "2025-12-11T15:27:31.05Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a3/e55039c8f49856c5a194f2b81f27ca6ba2d5900024f09435587e177bfaf2/raylib-5.5.0.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:80bfa053e765d47a9f58d59e321a999184b5a5190e369dd015c12fcfd08d6217", size = 1554132, upload-time = "2025-12-11T15:27:33.291Z" }, + { url = "https://files.pythonhosted.org/packages/58/1c/86bee75ecaa577214da16b374f8de70b45885452703f622c63e06baa0b8e/raylib-5.5.0.4-cp312-cp312-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:033240c61c1a1fc06fecff747a183671431a4ce63a0c8aafec59217845f86888", size = 2039888, upload-time = "2025-12-11T15:27:36.059Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/00763899bb8a178a927b5dda90aca692c80ff6cec5f51e6fee88db3f45c2/raylib-5.5.0.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:ba87ca50c5748cab75de37a991b7f3f836ce500efbb2d737a923a5f464169088", size = 2198926, upload-time = "2025-12-11T18:50:08.813Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e9/0123385e369904335985ebd59157f7a10c89c3a706dffcf6dace863a1fa2/raylib-5.5.0.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:788830bc371ce067c4930ff46a1b6eca0c9cf27bac88f81b035e4b73cc6bf197", size = 2205629, upload-time = "2025-12-11T15:27:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fa/c25087b39d2db2d833a52b4056ae62db74e64b4be677f816e0b368e65453/raylib-5.5.0.4-cp312-cp312-win32.whl", hash = "sha256:e09f395035484337776c90e6c9955c5876b988db7e13168dcadb6ed11974f8ee", size = 1457266, upload-time = "2025-12-11T15:27:43.798Z" }, + { url = "https://files.pythonhosted.org/packages/2c/66/a307e61c953ace906ba68ba1174ed8f1e90e68d5fc3e3af9fb7dc46d68d1/raylib-5.5.0.4-cp312-cp312-win_amd64.whl", hash = "sha256:553043a050a31f2ef072f26d3a70373f838a04733f7c5b26a4e9ee3f8caf06ec", size = 1708354, upload-time = "2025-12-11T15:27:45.979Z" }, +] [[package]] name = "requests" @@ -1664,7 +1674,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } [[package]] name = "zstandard" @@ -1694,4 +1704,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#de7c914a461f68a5fb80d57d534181b6350afe8f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } From 56ed3771970ea92d4ba4b33e86802208416f7e99 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Mar 2026 17:23:48 -0800 Subject: [PATCH 450/685] Zipapp fixes (#37538) * zip app fixes * add nl * rename * emoji was brok * bytes --- release/pack.py | 2 +- .../mici/layouts/settings/network/__init__.py | 155 +----------------- .../settings/network/network_layout.py | 154 +++++++++++++++++ .../ui/mici/layouts/settings/settings.py | 2 +- system/ui/lib/emoji.py | 12 +- system/ui/lib/multilang.py | 2 +- system/ui/mici_setup.py | 3 +- 7 files changed, 168 insertions(+), 162 deletions(-) create mode 100644 selfdrive/ui/mici/layouts/settings/network/network_layout.py diff --git a/release/pack.py b/release/pack.py index 92ff68fe761..8979e0e618d 100755 --- a/release/pack.py +++ b/release/pack.py @@ -12,7 +12,7 @@ DIRS = ['cereal', 'openpilot'] -EXTS = ['.png', '.py', '.ttf', '.capnp', '.json', '.fnt', '.mo'] +EXTS = ['.png', '.py', '.ttf', '.capnp', '.json', '.fnt', '.mo', '.po'] INTERPRETER = '/usr/bin/env python3' diff --git a/selfdrive/ui/mici/layouts/settings/network/__init__.py b/selfdrive/ui/mici/layouts/settings/network/__init__.py index 553a74fc601..ddbab4b478c 100644 --- a/selfdrive/ui/mici/layouts/settings/network/__init__.py +++ b/selfdrive/ui/mici/layouts/settings/network/__init__.py @@ -1,13 +1,9 @@ import pyray as rl -from openpilot.system.ui.widgets.scroller import NavScroller -from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici, WifiIcon -from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog -from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.lib.prime_state import PrimeType +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiIcon +from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.system.ui.lib.application import gui_app -from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType, ConnectStatus, SecurityType, normalize_ssid +from openpilot.system.ui.lib.wifi_manager import WifiManager, ConnectStatus, SecurityType, normalize_ssid class WifiNetworkButton(BigButton): @@ -62,148 +58,3 @@ def _draw_content(self, btn_y: float): lock_x = icon_x + self._txt_icon.width - self._lock_txt.width + 7 lock_y = icon_y + self._txt_icon.height - self._lock_txt.height + 8 rl.draw_texture_ex(self._lock_txt, (lock_x, lock_y), 0.0, 1.0, rl.WHITE) - - -class NetworkLayoutMici(NavScroller): - def __init__(self): - super().__init__() - - self._wifi_manager = WifiManager() - self._wifi_manager.set_active(False) - self._wifi_ui = WifiUIMici(self._wifi_manager) - - self._wifi_manager.add_callbacks( - networks_updated=self._on_network_updated, - ) - - # ******** Tethering ******** - def tethering_toggle_callback(checked: bool): - self._tethering_toggle_btn.set_enabled(False) - self._tethering_password_btn.set_enabled(False) - self._network_metered_btn.set_enabled(False) - self._wifi_manager.set_tethering_active(checked) - - self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) - - def tethering_password_callback(password: str): - if password: - self._tethering_toggle_btn.set_enabled(False) - self._tethering_password_btn.set_enabled(False) - self._wifi_manager.set_tethering_password(password) - - def tethering_password_clicked(): - tethering_password = self._wifi_manager.tethering_password - dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, - confirm_callback=tethering_password_callback) - gui_app.push_widget(dlg) - - txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) - self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) - self._tethering_password_btn.set_click_callback(tethering_password_clicked) - - # ******** Network Metered ******** - def network_metered_callback(value: str): - self._network_metered_btn.set_enabled(False) - metered = { - 'default': MeteredType.UNKNOWN, - 'metered': MeteredType.YES, - 'unmetered': MeteredType.NO - }.get(value, MeteredType.UNKNOWN) - self._wifi_manager.set_current_network_metered(metered) - - # TODO: signal for current network metered type when changing networks, this is wrong until you press it once - # TODO: disable when not connected - self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) - self._network_metered_btn.set_enabled(False) - - self._wifi_button = WifiNetworkButton(self._wifi_manager) - self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) - - # ******** Advanced settings ******** - # ******** Roaming toggle ******** - self._roaming_btn = BigParamControl("enable roaming", "GsmRoaming", toggle_callback=self._toggle_roaming) - - # ******** APN settings ******** - self._apn_btn = BigButton("apn settings", "edit") - self._apn_btn.set_click_callback(self._edit_apn) - - # ******** Cellular metered toggle ******** - self._cellular_metered_btn = BigParamControl("cellular metered", "GsmMetered", toggle_callback=self._toggle_cellular_metered) - - # Main scroller ---------------------------------- - self._scroller.add_widgets([ - self._wifi_button, - self._network_metered_btn, - self._tethering_toggle_btn, - self._tethering_password_btn, - # /* Advanced settings - self._roaming_btn, - self._apn_btn, - self._cellular_metered_btn, - # */ - ]) - - # Set initial config - roaming_enabled = ui_state.params.get_bool("GsmRoaming") - metered = ui_state.params.get_bool("GsmMetered") - self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) - - def _update_state(self): - super()._update_state() - - # If not using prime SIM, show GSM settings and enable IPv4 forwarding - show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) - self._wifi_manager.set_ipv4_forward(show_cell_settings) - self._roaming_btn.set_visible(show_cell_settings) - self._apn_btn.set_visible(show_cell_settings) - self._cellular_metered_btn.set_visible(show_cell_settings) - - def show_event(self): - super().show_event() - self._wifi_manager.set_active(True) - - # Process wifi callbacks while at any point in the nav stack - gui_app.add_nav_stack_tick(self._wifi_manager.process_callbacks) - - def hide_event(self): - super().hide_event() - self._wifi_manager.set_active(False) - - gui_app.remove_nav_stack_tick(self._wifi_manager.process_callbacks) - - def _toggle_roaming(self, checked: bool): - self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) - - def _edit_apn(self): - def update_apn(apn: str): - apn = apn.strip() - if apn == "": - ui_state.params.remove("GsmApn") - else: - ui_state.params.put("GsmApn", apn) - - self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), apn, ui_state.params.get_bool("GsmMetered")) - - current_apn = ui_state.params.get("GsmApn") or "" - dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn) - gui_app.push_widget(dlg) - - def _toggle_cellular_metered(self, checked: bool): - self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked) - - def _on_network_updated(self, networks: list[Network]): - # Update tethering state - tethering_active = self._wifi_manager.is_tethering_active() - # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons - self._tethering_toggle_btn.set_enabled(True) - self._tethering_password_btn.set_enabled(True) - self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) - self._tethering_toggle_btn.set_checked(tethering_active) - - # Update network metered - self._network_metered_btn.set_value( - { - MeteredType.UNKNOWN: 'default', - MeteredType.YES: 'metered', - MeteredType.NO: 'unmetered' - }.get(self._wifi_manager.current_network_metered, 'default')) diff --git a/selfdrive/ui/mici/layouts/settings/network/network_layout.py b/selfdrive/ui/mici/layouts/settings/network/network_layout.py new file mode 100644 index 00000000000..9f6fae4b5f8 --- /dev/null +++ b/selfdrive/ui/mici/layouts/settings/network/network_layout.py @@ -0,0 +1,154 @@ +from openpilot.system.ui.widgets.scroller import NavScroller +from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigMultiToggle, BigParamControl, BigToggle +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.lib.prime_state import PrimeType +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.wifi_manager import WifiManager, Network, MeteredType + + +class NetworkLayoutMici(NavScroller): + def __init__(self): + super().__init__() + + self._wifi_manager = WifiManager() + self._wifi_manager.set_active(False) + self._wifi_ui = WifiUIMici(self._wifi_manager) + + self._wifi_manager.add_callbacks( + networks_updated=self._on_network_updated, + ) + + # ******** Tethering ******** + def tethering_toggle_callback(checked: bool): + self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) + self._network_metered_btn.set_enabled(False) + self._wifi_manager.set_tethering_active(checked) + + self._tethering_toggle_btn = BigToggle("enable tethering", "", toggle_callback=tethering_toggle_callback) + + def tethering_password_callback(password: str): + if password: + self._tethering_toggle_btn.set_enabled(False) + self._tethering_password_btn.set_enabled(False) + self._wifi_manager.set_tethering_password(password) + + def tethering_password_clicked(): + tethering_password = self._wifi_manager.tethering_password + dlg = BigInputDialog("enter password...", tethering_password, minimum_length=8, + confirm_callback=tethering_password_callback) + gui_app.push_widget(dlg) + + txt_tethering = gui_app.texture("icons_mici/settings/network/tethering.png", 64, 54) + self._tethering_password_btn = BigButton("tethering password", "", txt_tethering) + self._tethering_password_btn.set_click_callback(tethering_password_clicked) + + # ******** Network Metered ******** + def network_metered_callback(value: str): + self._network_metered_btn.set_enabled(False) + metered = { + 'default': MeteredType.UNKNOWN, + 'metered': MeteredType.YES, + 'unmetered': MeteredType.NO + }.get(value, MeteredType.UNKNOWN) + self._wifi_manager.set_current_network_metered(metered) + + # TODO: signal for current network metered type when changing networks, this is wrong until you press it once + # TODO: disable when not connected + self._network_metered_btn = BigMultiToggle("network usage", ["default", "metered", "unmetered"], select_callback=network_metered_callback) + self._network_metered_btn.set_enabled(False) + + self._wifi_button = WifiNetworkButton(self._wifi_manager) + self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) + + # ******** Advanced settings ******** + # ******** Roaming toggle ******** + self._roaming_btn = BigParamControl("enable roaming", "GsmRoaming", toggle_callback=self._toggle_roaming) + + # ******** APN settings ******** + self._apn_btn = BigButton("apn settings", "edit") + self._apn_btn.set_click_callback(self._edit_apn) + + # ******** Cellular metered toggle ******** + self._cellular_metered_btn = BigParamControl("cellular metered", "GsmMetered", toggle_callback=self._toggle_cellular_metered) + + # Main scroller ---------------------------------- + self._scroller.add_widgets([ + self._wifi_button, + self._network_metered_btn, + self._tethering_toggle_btn, + self._tethering_password_btn, + # /* Advanced settings + self._roaming_btn, + self._apn_btn, + self._cellular_metered_btn, + # */ + ]) + + # Set initial config + roaming_enabled = ui_state.params.get_bool("GsmRoaming") + metered = ui_state.params.get_bool("GsmMetered") + self._wifi_manager.update_gsm_settings(roaming_enabled, ui_state.params.get("GsmApn") or "", metered) + + def _update_state(self): + super()._update_state() + + # If not using prime SIM, show GSM settings and enable IPv4 forwarding + show_cell_settings = ui_state.prime_state.get_type() in (PrimeType.NONE, PrimeType.LITE) + self._wifi_manager.set_ipv4_forward(show_cell_settings) + self._roaming_btn.set_visible(show_cell_settings) + self._apn_btn.set_visible(show_cell_settings) + self._cellular_metered_btn.set_visible(show_cell_settings) + + def show_event(self): + super().show_event() + self._wifi_manager.set_active(True) + + # Process wifi callbacks while at any point in the nav stack + gui_app.add_nav_stack_tick(self._wifi_manager.process_callbacks) + + def hide_event(self): + super().hide_event() + self._wifi_manager.set_active(False) + + gui_app.remove_nav_stack_tick(self._wifi_manager.process_callbacks) + + def _toggle_roaming(self, checked: bool): + self._wifi_manager.update_gsm_settings(checked, ui_state.params.get("GsmApn") or "", ui_state.params.get_bool("GsmMetered")) + + def _edit_apn(self): + def update_apn(apn: str): + apn = apn.strip() + if apn == "": + ui_state.params.remove("GsmApn") + else: + ui_state.params.put("GsmApn", apn) + + self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), apn, ui_state.params.get_bool("GsmMetered")) + + current_apn = ui_state.params.get("GsmApn") or "" + dlg = BigInputDialog("enter APN...", current_apn, minimum_length=0, confirm_callback=update_apn) + gui_app.push_widget(dlg) + + def _toggle_cellular_metered(self, checked: bool): + self._wifi_manager.update_gsm_settings(ui_state.params.get_bool("GsmRoaming"), ui_state.params.get("GsmApn") or "", checked) + + def _on_network_updated(self, networks: list[Network]): + # Update tethering state + tethering_active = self._wifi_manager.is_tethering_active() + # TODO: use real signals (like activated/settings changed, etc.) to speed up re-enabling buttons + self._tethering_toggle_btn.set_enabled(True) + self._tethering_password_btn.set_enabled(True) + self._network_metered_btn.set_enabled(lambda: not tethering_active and bool(self._wifi_manager.ipv4_address)) + self._tethering_toggle_btn.set_checked(tethering_active) + + # Update network metered + self._network_metered_btn.set_value( + { + MeteredType.UNKNOWN: 'default', + MeteredType.YES: 'metered', + MeteredType.NO: 'unmetered' + }.get(self._wifi_manager.current_network_metered, 'default')) diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index c7fb3201f56..8e037ccf88c 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -2,7 +2,7 @@ from openpilot.system.ui.widgets.scroller import NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton from openpilot.selfdrive.ui.mici.layouts.settings.toggles import TogglesLayoutMici -from openpilot.selfdrive.ui.mici.layouts.settings.network import NetworkLayoutMici +from openpilot.selfdrive.ui.mici.layouts.settings.network.network_layout import NetworkLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici, PairBigButton from openpilot.selfdrive.ui.mici.layouts.settings.developer import DeveloperLayoutMici from openpilot.selfdrive.ui.mici.layouts.settings.firehose import FirehoseLayout diff --git a/system/ui/lib/emoji.py b/system/ui/lib/emoji.py index 37228e2d45f..ad4c272c8de 100644 --- a/system/ui/lib/emoji.py +++ b/system/ui/lib/emoji.py @@ -1,12 +1,13 @@ import io import re +import functools +from importlib.resources import as_file from PIL import Image, ImageDraw, ImageFont import pyray as rl from openpilot.system.ui.lib.application import FONT_DIR -_emoji_font: ImageFont.FreeTypeFont | None = None _cache: dict[str, rl.Texture] = {} EMOJI_REGEX = re.compile( @@ -33,11 +34,10 @@ flags=re.UNICODE ) -def _load_emoji_font() -> ImageFont.FreeTypeFont | None: - global _emoji_font - if _emoji_font is None: - _emoji_font = ImageFont.truetype(str(FONT_DIR.joinpath("NotoColorEmoji.ttf")), 109) - return _emoji_font +@functools.cache +def _load_emoji_font() -> ImageFont.FreeTypeFont: + with as_file(FONT_DIR.joinpath("NotoColorEmoji.ttf")) as font_path: + return ImageFont.truetype(io.BytesIO(font_path.read_bytes()), 109) def find_emoji(text): return [(m.start(), m.end(), m.group()) for m in EMOJI_REGEX.finditer(text)] diff --git a/system/ui/lib/multilang.py b/system/ui/lib/multilang.py index 7f191bc75e2..3c6a6b85643 100644 --- a/system/ui/lib/multilang.py +++ b/system/ui/lib/multilang.py @@ -74,7 +74,7 @@ def load_translations(path) -> tuple[dict[str, str], dict[str, list[str]]]: translations: msgid -> msgstr plurals: msgid -> [msgstr[0], msgstr[1], ...] """ - with open(str(path), encoding='utf-8') as f: + with path.open(encoding='utf-8') as f: lines = f.readlines() translations: dict[str, str] = {} diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 22ba8c40329..5dc9905ddf8 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -25,7 +25,8 @@ from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPACING from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider -from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton, WifiUIMici +from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton +from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog from openpilot.selfdrive.ui.mici.widgets.button import BigButton From 0c452dbafe223cfe971803b80d3d2e20009536f5 Mon Sep 17 00:00:00 2001 From: royjr Date: Tue, 3 Mar 2026 23:12:53 -0500 Subject: [PATCH 451/685] cabana: fix right pane width limitation (#37527) Update chartswidget.cc --- tools/cabana/chart/chartswidget.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/chart/chartswidget.cc b/tools/cabana/chart/chartswidget.cc index acdb6d064d3..44dca42152e 100644 --- a/tools/cabana/chart/chartswidget.cc +++ b/tools/cabana/chart/chartswidget.cc @@ -427,7 +427,7 @@ void ChartsWidget::doAutoScroll() { } QSize ChartsWidget::minimumSizeHint() const { - return QSize(CHART_MIN_WIDTH * 1.5 * qApp->devicePixelRatio(), QWidget::minimumSizeHint().height()); + return QSize(CHART_MIN_WIDTH * 1.5, QWidget::minimumSizeHint().height()); } void ChartsWidget::newChart() { From 7e1a8d41a1b83e53ef483956513f7e2974ac042d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 3 Mar 2026 21:45:49 -0800 Subject: [PATCH 452/685] steering arc: enable for angle cars (#37078) * enable for angle cars * use carparams * less roll at low speed, it's too pronounced * clean up --- selfdrive/ui/mici/onroad/hud_renderer.py | 3 +-- selfdrive/ui/mici/onroad/torque_bar.py | 20 +++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 3b056c3e0f6..21e30e74263 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -172,8 +172,7 @@ def _update_state(self) -> None: def _render(self, rect: rl.Rectangle) -> None: """Render HUD elements to the screen.""" - if ui_state.sm['controlsState'].lateralControlState.which() != 'angleState': - self._torque_bar.render(rect) + self._torque_bar.render(rect) if self.is_cruise_set: self._draw_set_speed(rect) diff --git a/selfdrive/ui/mici/onroad/torque_bar.py b/selfdrive/ui/mici/onroad/torque_bar.py index d7c9f27a92d..1338c8dfb35 100644 --- a/selfdrive/ui/mici/onroad/torque_bar.py +++ b/selfdrive/ui/mici/onroad/torque_bar.py @@ -145,6 +145,9 @@ def get_cap(left: bool, a_deg: float): return pts +DEFAULT_MAX_LAT_ACCEL = 3.0 # m/s^2 + + class TorqueBar(Widget): def __init__(self, demo: bool = False): super().__init__() @@ -165,16 +168,23 @@ def _update_state(self): controls_state = ui_state.sm['controlsState'] car_state = ui_state.sm['carState'] live_parameters = ui_state.sm['liveParameters'] - lateral_acceleration = controls_state.curvature * car_state.vEgo ** 2 - live_parameters.roll * ACCELERATION_DUE_TO_GRAVITY - # TODO: pull from carparams - max_lateral_acceleration = 3 + car_control = ui_state.sm['carControl'] - # from selfdrived + # Include lateral accel error in estimated torque utilization actual_lateral_accel = controls_state.curvature * car_state.vEgo ** 2 desired_lateral_accel = controls_state.desiredCurvature * car_state.vEgo ** 2 accel_diff = (desired_lateral_accel - actual_lateral_accel) - self._torque_filter.update(min(max(lateral_acceleration / max_lateral_acceleration + accel_diff, -1), 1)) + # Include road roll in estimated torque utilization + # Roll is less accurate near standstill, so reduce its effect at low speed + roll_compensation = live_parameters.roll * ACCELERATION_DUE_TO_GRAVITY * np.interp(car_state.vEgo, [5, 15], [0.0, 1.0]) + lateral_acceleration = actual_lateral_accel - roll_compensation + max_lateral_acceleration = ui_state.CP.maxLateralAccel if ui_state.CP else DEFAULT_MAX_LAT_ACCEL + + if not car_control.latActive: + self._torque_filter.update(0.0) + else: + self._torque_filter.update(np.clip((lateral_acceleration + accel_diff) / max_lateral_acceleration, -1, 1)) else: self._torque_filter.update(-ui_state.sm['carOutput'].actuatorsOutput.torque) From 20d484c7cb0791de581dc59955e92a166f996106 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 01:23:56 -0800 Subject: [PATCH 453/685] reset: recover needs to reboot (#37546) fix not rebooting --- system/ui/mici_reset.py | 1 + 1 file changed, 1 insertion(+) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index a459927eeb4..5ad959badbb 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -91,6 +91,7 @@ def _render(self, rect: rl.Rectangle): if self._mode == ResetMode.RECOVER: self._cancel_button.set_text("reboot") + self._cancel_button.set_click_callback(self._do_reboot) self._cancel_button.render(rl.Rectangle( rect.x + 8, rect.y + rect.height - self._cancel_button.rect.height, From 6795b09d0a84f460d2ce673dff454c2c8c8bacd1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 03:16:29 -0800 Subject: [PATCH 454/685] file_downloader: stream downloads in a single HTTP request (#37549) The Python file downloader was making a separate HTTP Range request per 1MB chunk via URLFile.read(), causing massive latency overhead. Use a single streaming GET request instead, matching the old C++ behavior. Co-authored-by: Claude Opus 4.6 --- tools/lib/file_downloader.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/tools/lib/file_downloader.py b/tools/lib/file_downloader.py index c9c26bb3070..5b31a5894cf 100755 --- a/tools/lib/file_downloader.py +++ b/tools/lib/file_downloader.py @@ -60,8 +60,16 @@ def cmd_download(args): return try: - uf = URLFile(url, cache=False) - total = uf.get_length() + # Stream the file in a single HTTP request instead of making + # a separate Range request per chunk (which was very slow). + pool = URLFile.pool_manager() + r = pool.request("GET", url, preload_content=False) + if r.status not in (200, 206): + sys.stderr.write(f"ERROR:HTTP {r.status}\n") + sys.stderr.flush() + sys.exit(1) + + total = int(r.headers.get('content-length', 0)) if total <= 0: sys.stderr.write("ERROR:File not found or empty\n") sys.stderr.flush() @@ -73,8 +81,7 @@ def cmd_download(args): downloaded = 0 chunk_size = 1024 * 1024 with os.fdopen(tmp_fd, 'wb') as f: - while downloaded < total: - data = uf.read(min(chunk_size, total - downloaded)) + for data in r.stream(chunk_size): f.write(data) downloaded += len(data) sys.stderr.write(f"PROGRESS:{downloaded}:{total}\n") @@ -91,6 +98,8 @@ def cmd_download(args): except OSError: pass raise + finally: + r.release_conn() except Exception as e: sys.stderr.write(f"ERROR:{e}\n") From e97a1d1a44d2b7e1b56e2368524094f8c9108b12 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 04:34:48 -0800 Subject: [PATCH 455/685] updater: zipapp and additional fixes (#37550) * new updater zipapp * fix deadlock from agnos.py throwing timeout errors, never hitting failed screen! + try catch the whole process for errors while starting process * add todo * set core affinity like setup in updater * fix import * rezip --- system/hardware/tici/updater_magic | 4 ++-- system/ui/mici_updater.py | 23 +++++++++++++++++++---- system/ui/tici_updater.py | 11 ++++++++--- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index ec586dbcb3c..9674d85f00a 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c44fb88b3b1643b6b44ae8ac9880348bd0257ff90f4084cbe889de91d71653fe -size 25111329 +oid sha256:d815a9140e69242d85e57f74a14c6372809eebefe8958be9152efa7874928ccc +size 71215510 diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 50ea4129252..cd1f99062ce 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -5,7 +5,9 @@ import pyray as rl from enum import IntEnum -from openpilot.system.hardware import HARDWARE +from openpilot.common.realtime import config_realtime_process, set_core_affinity +from openpilot.system.hardware import HARDWARE, TICI +from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel @@ -34,6 +36,7 @@ def __init__(self, updater_path, manifest_path): self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() + # TODO: network page is rendered inline, not pushed on nav stack, so auto-dismiss on internet connect doesn't work self._network_setup_page = NetworkSetupPageBase(self._network_monitor, self._network_setup_continue_callback, disable_connect_hint=True) self._network_setup_page.set_is_updater() @@ -93,9 +96,13 @@ def install_update(self): def _run_update_process(self): # TODO: just import it and run in a thread without a subprocess - cmd = [self.updater, "--swap", self.manifest] - self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True, bufsize=1, universal_newlines=True) + try: + cmd = [self.updater, "--swap", self.manifest] + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, + text=True, bufsize=1, universal_newlines=True) + except Exception: + self.set_current_screen(Screen.FAILED) + return if self.process.stdout is not None: for line in self.process.stdout: @@ -169,6 +176,14 @@ def close(self): def main(): + config_realtime_process(0, 51) + # attempt to affine. AGNOS will start setup with all cores, should only fail when manually launching with screen off + if TICI: + try: + set_core_affinity([5]) + except OSError: + cloudlog.exception("Failed to set core affinity for updater process") + if len(sys.argv) < 3: print("Usage: updater.py ") sys.exit(1) diff --git a/system/ui/tici_updater.py b/system/ui/tici_updater.py index 9824638cd06..3a3b0987d0e 100755 --- a/system/ui/tici_updater.py +++ b/system/ui/tici_updater.py @@ -67,9 +67,14 @@ def install_update(self): def _run_update_process(self): # TODO: just import it and run in a thread without a subprocess - cmd = [self.updater, "--swap", self.manifest] - self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True, bufsize=1, universal_newlines=True) + try: + cmd = [self.updater, "--swap", self.manifest] + self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, + text=True, bufsize=1, universal_newlines=True) + except Exception: + self.progress_text = "Update failed" + self.show_reboot_button = True + return if self.process.stdout is not None: for line in self.process.stdout: From cd22ee3327fcb5c6b286db2c67fded7931bfc8f7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 09:50:23 -0800 Subject: [PATCH 456/685] rm openssl3 package (#37551) * rm openssl3 package * upgrade * lil more --- SConstruct | 3 +- pyproject.toml | 4 +- uv.lock | 131 +++++++++++++++++++++---------------------------- 3 files changed, 59 insertions(+), 79 deletions(-) diff --git a/SConstruct b/SConstruct index da70bc39247..18bf040cf07 100644 --- a/SConstruct +++ b/SConstruct @@ -46,11 +46,10 @@ if arch != "larch64": import libjpeg import libyuv import ncurses - import openssl3 import python3_dev import zeromq import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, openssl3, zeromq, zstd] + pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, zeromq, zstd] py_include = python3_dev.INCLUDE_DIR else: # TODO: remove when AGNOS has our new vendor pkgs diff --git a/pyproject.toml b/pyproject.toml index 6516c8cd5bc..c581dbfd8b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,12 +32,12 @@ dependencies = [ "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", "libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv", - "openssl3 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=openssl3", "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", + "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", # body / webrtcd "av", @@ -103,12 +103,10 @@ testing = [ dev = [ "matplotlib", "opencv-python-headless", - "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", ] tools = [ "metadrive-simulator @ git+https://github.com/commaai/metadrive.git@minimal ; (platform_machine != 'aarch64')", - "dearpygui>=2.1.0; (sys_platform != 'linux' or platform_machine != 'aarch64')", # not vended for linux aarch64 ] [project.urls] diff --git a/uv.lock b/uv.lock index f5c128fcb76..4f04da00c65 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "casadi" @@ -359,16 +359,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/fa/d3c15189f7c52aaefbaea76fb012119b04b9013f4bf446cb4eb4c26c4e6b/cython-3.2.4-py3-none-any.whl", hash = "sha256:732fc93bc33ae4b14f6afaca663b916c2fdd5dcbfad7114e17fb2434eeaea45c", size = 1257078, upload-time = "2026-01-04T14:14:12.373Z" }, ] -[[package]] -name = "dearpygui" -version = "2.2" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/17/c8/b4afdac89c7bf458513366af3143f7383d7b09721637989c95788d93e24c/dearpygui-2.2-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:34ceae1ca1b65444e49012d6851312e44f08713da1b8cc0150cf41f1c207af9c", size = 1931443, upload-time = "2026-02-17T14:21:54.394Z" }, - { url = "https://files.pythonhosted.org/packages/43/93/a2d083b2e0edb095be815662cc41e40cf9ea7b65d6323e47bb30df7eb284/dearpygui-2.2-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:e1fae9ae59fec0e41773df64c80311a6ba67696219dde5506a2a4c013e8bcdfa", size = 2592645, upload-time = "2026-02-17T14:22:02.869Z" }, - { url = "https://files.pythonhosted.org/packages/80/ba/eae13acaad479f522db853e8b1ccd695a7bc8da2b9685c1d70a3b318df89/dearpygui-2.2-cp312-cp312-win_amd64.whl", hash = "sha256:7d399543b5a26ab6426ef3bbd776e55520b491b3e169647bde5e6b2de3701b35", size = 1830531, upload-time = "2026-02-17T14:21:43.386Z" }, -] - [[package]] name = "dnspython" version = "2.8.0" @@ -381,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "execnet" @@ -395,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "fonttools" @@ -442,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "ghp-import" @@ -459,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "google-crc32c" @@ -577,7 +567,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "libusb1" @@ -593,7 +583,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "markdown" @@ -745,7 +735,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "numpy" @@ -800,6 +790,7 @@ dependencies = [ { name = "cython" }, { name = "eigen" }, { name = "ffmpeg" }, + { name = "gcc-arm-none-eabi" }, { name = "git-lfs" }, { name = "inputs" }, { name = "jeepney" }, @@ -809,7 +800,6 @@ dependencies = [ { name = "libyuv" }, { name = "ncurses" }, { name = "numpy" }, - { name = "openssl3" }, { name = "psutil" }, { name = "pycapnp" }, { name = "pycryptodome" }, @@ -837,7 +827,6 @@ dependencies = [ [package.optional-dependencies] dev = [ - { name = "gcc-arm-none-eabi" }, { name = "matplotlib" }, { name = "opencv-python-headless" }, ] @@ -860,7 +849,6 @@ testing = [ { name = "ty" }, ] tools = [ - { name = "dearpygui", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64'" }, ] @@ -877,10 +865,9 @@ requires-dist = [ { name = "coverage", marker = "extra == 'testing'" }, { name = "crcmod-plus" }, { name = "cython" }, - { name = "dearpygui", marker = "(platform_machine != 'aarch64' and extra == 'tools') or (sys_platform != 'linux' and extra == 'tools')", specifier = ">=2.1.0" }, { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases" }, { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases" }, - { name = "gcc-arm-none-eabi", marker = "extra == 'dev'", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, + { name = "gcc-arm-none-eabi", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, @@ -896,7 +883,6 @@ requires-dist = [ { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, { name = "numpy", specifier = ">=2.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, - { name = "openssl3", git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, { name = "pycapnp" }, @@ -932,11 +918,6 @@ requires-dist = [ ] provides-extras = ["docs", "testing", "dev", "tools"] -[[package]] -name = "openssl3" -version = "3.4.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=openssl3&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } - [[package]] name = "packaging" version = "26.0" @@ -1306,7 +1287,7 @@ wheels = [ [[package]] name = "python3-dev" version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "pyyaml" @@ -1449,15 +1430,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.53.0" +version = "2.54.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/06/66c8b705179bc54087845f28fd1b72f83751b6e9a195628e2e9af9926505/sentry_sdk-2.53.0.tar.gz", hash = "sha256:6520ef2c4acd823f28efc55e43eb6ce2e6d9f954a95a3aa96b6fd14871e92b77", size = 412369, upload-time = "2026-02-16T11:11:14.743Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c8/e9/2e3a46c304e7fa21eaa70612f60354e32699c7102eb961f67448e222ad7c/sentry_sdk-2.54.0.tar.gz", hash = "sha256:2620c2575128d009b11b20f7feb81e4e4e8ae08ec1d36cbc845705060b45cc1b", size = 413813, upload-time = "2026-03-02T15:12:41.355Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/d4/2fdf854bc3b9c7f55219678f812600a20a138af2dd847d99004994eada8f/sentry_sdk-2.53.0-py2.py3-none-any.whl", hash = "sha256:46e1ed8d84355ae54406c924f6b290c3d61f4048625989a723fd622aab838899", size = 437908, upload-time = "2026-02-16T11:11:13.227Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/be412cc86bc6247b8f69e9383d7950711bd86f8d0a4a4b0fe8fad685bc21/sentry_sdk-2.54.0-py2.py3-none-any.whl", hash = "sha256:fd74e0e281dcda63afff095d23ebcd6e97006102cdc8e78a29f19ecdf796a0de", size = 439198, upload-time = "2026-03-02T15:12:39.546Z" }, ] [[package]] @@ -1553,26 +1534,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.19" +version = "0.0.20" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/84/5e/da108b9eeb392e02ff0478a34e9651490b36af295881cb56575b83f0cc3a/ty-0.0.19.tar.gz", hash = "sha256:ee3d9ed4cb586e77f6efe3d0fe5a855673ca438a3d533a27598e1d3502a2948a", size = 5220026, upload-time = "2026-02-26T12:13:15.215Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/95/8de69bb98417227b01f1b1d743c819d6456c9fd140255b6124b05b17dfd6/ty-0.0.20.tar.gz", hash = "sha256:ebba6be7974c14efbb2a9adda6ac59848f880d7259f089dfa72a093039f1dcc6", size = 5262529, upload-time = "2026-03-02T15:51:36.587Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/31/fd8c6067abb275bea11523d21ecf64e1d870b1ce80cac529cf6636df1471/ty-0.0.19-py3-none-linux_armv6l.whl", hash = "sha256:29bed05d34c8a7597567b8e327c53c1aed4a07dcfbe6c81e6d60c7444936ad77", size = 10268470, upload-time = "2026-02-26T12:13:42.881Z" }, - { url = "https://files.pythonhosted.org/packages/15/de/16a11bbf7d98c75849fc41f5d008b89bb5d080a4b10dc8ea851ee2bd371b/ty-0.0.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79140870c688c97ec68e723c28935ddef9d91a76d48c68e665fe7c851e628b8a", size = 10098562, upload-time = "2026-02-26T12:13:31.618Z" }, - { url = "https://files.pythonhosted.org/packages/e7/4f/086d6ff6686eadf903913c45b53ab96694b62bbfee1d8cf3e55a9b5aa4b2/ty-0.0.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6e9c1f9cfa6a26f7881d14d75cf963af743f6c4189e6aa3e3b4056a65f22e730", size = 9604073, upload-time = "2026-02-26T12:13:24.645Z" }, - { url = "https://files.pythonhosted.org/packages/95/13/888a6b6c7ed4a880fee91bec997f775153ce86215ee4c56b868516314734/ty-0.0.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbca43b050edf1db2e64ae7b79add233c2aea2855b8a876081bbd032edcd0610", size = 10106295, upload-time = "2026-02-26T12:13:40.584Z" }, - { url = "https://files.pythonhosted.org/packages/cb/e8/05a372cae8da482de73b8246fb43236bf11e24ac28c879804568108759db/ty-0.0.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8acaa88ab1955ca6b15a0ccc274011c4961377fe65c3948e5d2b212f2517b87c", size = 10098234, upload-time = "2026-02-26T12:13:33.725Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f1/5b0958e9e9576e7662192fe689bbb3dc88e631a4e073db3047793a547d58/ty-0.0.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a901b6a6dd9d17d5b3b2e7bafc3057294e88da3f5de507347316687d7f191a1", size = 10607218, upload-time = "2026-02-26T12:13:17.576Z" }, - { url = "https://files.pythonhosted.org/packages/fb/ab/358c78b77844f58ff5aca368550ab16c719f1ab0ec892ceb1114d7500f4e/ty-0.0.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8deafdaaaee65fd121c66064da74a922d8501be4a2d50049c71eab521a23eff7", size = 11160593, upload-time = "2026-02-26T12:13:36.008Z" }, - { url = "https://files.pythonhosted.org/packages/95/59/827fc346d66a59fe48e9689a5ceb67dbbd5b4de2e8d4625371af39a2e8b7/ty-0.0.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e56071af280897441018f74f921b97d53aec0856f8af85f4f949df8eda07d", size = 10822392, upload-time = "2026-02-26T12:13:29.415Z" }, - { url = "https://files.pythonhosted.org/packages/81/f9/3bbfbbe35478de9bcd63848f4bc9bffda72278dd9732dbad3efc3978432e/ty-0.0.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdf5885130393ce74501dba792f48ce0a515756ec81c33a4b324bdf3509df6e", size = 10707139, upload-time = "2026-02-26T12:13:20.148Z" }, - { url = "https://files.pythonhosted.org/packages/12/9e/597023b183ec4ade83a36a0cea5c103f3bffa34f70813d46386c61447fb8/ty-0.0.19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:877e89005c8f9d1dbff5ad14cbac9f35c528406fde38926f9b44f24830de8d6a", size = 10096933, upload-time = "2026-02-26T12:13:45.266Z" }, - { url = "https://files.pythonhosted.org/packages/1e/76/d0d2f6e674db2a17c8efa5e26682b9dfa8d34774705f35902a7b45ebd3bd/ty-0.0.19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:39bd1da051c1e4d316efaf79dbed313255633f7c6ad6e24d29f4d9c6ffaf4de6", size = 10109547, upload-time = "2026-02-26T12:13:22.17Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b0/76026c06b852a3aa4fdb5bd329fdc2175aaf3c64a3fafece9cc4df167cee/ty-0.0.19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87df8415a6c9cb27b8f1382fcdc6052e59f5b9f50f78bc14663197eb5c8d3699", size = 10289110, upload-time = "2026-02-26T12:13:38.29Z" }, - { url = "https://files.pythonhosted.org/packages/14/6c/f3b3a189816b4f079b20fe5d0d7ee38e38a472f53cc6770bb6571147e3de/ty-0.0.19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:89b6bb23c332ed5c38dd859eb5793f887abcc936f681a40d4ea68e35eac1af33", size = 10796479, upload-time = "2026-02-26T12:13:10.992Z" }, - { url = "https://files.pythonhosted.org/packages/3d/18/caee33d1ce9dd50bd94c26cde7cda4f6971e22e474e7d72a5c86d745ad58/ty-0.0.19-py3-none-win32.whl", hash = "sha256:19b33df3aa7af7b1a9eaa4e1175c3b4dec0f5f2e140243e3492c8355c37418f3", size = 9677215, upload-time = "2026-02-26T12:13:08.519Z" }, - { url = "https://files.pythonhosted.org/packages/81/41/18fc0771d0b1da7d7cc2fc9af278d3122b754fe8b521a748734f4e16ecfd/ty-0.0.19-py3-none-win_amd64.whl", hash = "sha256:b9052c61464cdd76bc8e6796f2588c08700f25d0dcbc225bb165e390ea9d96a4", size = 10651252, upload-time = "2026-02-26T12:13:13.035Z" }, - { url = "https://files.pythonhosted.org/packages/8b/8c/26f7ce8863eb54510082747b3dfb1046ba24f16fc11de18c0e5feb36ff18/ty-0.0.19-py3-none-win_arm64.whl", hash = "sha256:9329804b66dcbae8e7af916ef4963221ed53b8ec7d09b0793591c5ae8a0f3270", size = 10093195, upload-time = "2026-02-26T12:13:26.816Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2c/718abe48393e521bf852cd6b0f984766869b09c258d6e38a118768a91731/ty-0.0.20-py3-none-linux_armv6l.whl", hash = "sha256:7cc12769c169c9709a829c2248ee2826b7aae82e92caeac813d856f07c021eae", size = 10333656, upload-time = "2026-03-02T15:51:56.461Z" }, + { url = "https://files.pythonhosted.org/packages/41/0e/eb1c4cc4a12862e2327b72657bcebb10b7d9f17046f1bdcd6457a0211615/ty-0.0.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b777c1bf13bc0a95985ebb8a324b8668a4a9b2e514dde5ccf09e4d55d2ff232", size = 10168505, upload-time = "2026-03-02T15:51:51.895Z" }, + { url = "https://files.pythonhosted.org/packages/89/7f/10230798e673f0dd3094dfd16e43bfd90e9494e7af6e8e7db516fb431ddf/ty-0.0.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b2a4a7db48bf8cba30365001bc2cad7fd13c1a5aacdd704cc4b7925de8ca5eb3", size = 9678510, upload-time = "2026-03-02T15:51:48.451Z" }, + { url = "https://files.pythonhosted.org/packages/7a/3d/59d9159577494edd1728f7db77b51bb07884bd21384f517963114e3ab5f6/ty-0.0.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6846427b8b353a43483e9c19936dc6a25612573b44c8f7d983dfa317e7f00d4c", size = 10162926, upload-time = "2026-03-02T15:51:40.558Z" }, + { url = "https://files.pythonhosted.org/packages/9c/a8/b7273eec3e802f78eb913fbe0ce0c16ef263723173e06a5776a8359b2c66/ty-0.0.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245ceef5bd88df366869385cf96411cb14696334f8daa75597cf7e41c3012eb8", size = 10171702, upload-time = "2026-03-02T15:51:44.069Z" }, + { url = "https://files.pythonhosted.org/packages/9f/32/5f1144f2f04a275109db06e3498450c4721554215b80ae73652ef412eeab/ty-0.0.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4d21d1cdf67a444d3c37583c17291ddba9382a9871021f3f5d5735e09e85efe", size = 10682552, upload-time = "2026-03-02T15:51:33.102Z" }, + { url = "https://files.pythonhosted.org/packages/6a/db/9f1f637310792f12bd6ed37d5fc8ab39ba1a9b0c6c55a33865e9f1cad840/ty-0.0.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd4ffd907d1bd70e46af9e9a2f88622f215e1bf44658ea43b32c2c0b357299e4", size = 11242605, upload-time = "2026-03-02T15:51:34.895Z" }, + { url = "https://files.pythonhosted.org/packages/1a/68/cc9cae2e732fcfd20ccdffc508407905a023fc8493b8771c392d915528dc/ty-0.0.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6594b58d8b0e9d16a22b3045fc1305db4b132c8d70c17784ab8c7a7cc986807", size = 10974655, upload-time = "2026-03-02T15:51:46.011Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c1/b9e3e3f28fe63486331e653f6aeb4184af8b1fe80542fcf74d2dda40a93d/ty-0.0.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3662f890518ce6cf4d7568f57d03906912d2afbf948a01089a28e325b1ef198c", size = 10761325, upload-time = "2026-03-02T15:51:26.818Z" }, + { url = "https://files.pythonhosted.org/packages/39/9e/67db935bdedf219a00fb69ec5437ba24dab66e0f2e706dd54a4eca234b84/ty-0.0.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e3ffbae58f9f0d17cdc4ac6d175ceae560b7ed7d54f9ddfb1c9f31054bcdc2c", size = 10145793, upload-time = "2026-03-02T15:51:38.562Z" }, + { url = "https://files.pythonhosted.org/packages/c7/de/b0eb815d4dc5a819c7e4faddc2a79058611169f7eef07ccc006531ce228c/ty-0.0.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:176e52bc8bb00b0e84efd34583962878a447a3a0e34ecc45fd7097a37554261b", size = 10189640, upload-time = "2026-03-02T15:51:50.202Z" }, + { url = "https://files.pythonhosted.org/packages/b8/71/63734923965cbb70df1da3e93e4b8875434e326b89e9f850611122f279bf/ty-0.0.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2bc73025418e976ca4143dde71fb9025a90754a08ac03e6aa9b80d4bed1294b", size = 10370568, upload-time = "2026-03-02T15:51:42.295Z" }, + { url = "https://files.pythonhosted.org/packages/32/a0/a532c2048533347dff48e9ca98bd86d2c224356e101688a8edaf8d6973fb/ty-0.0.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d52f7c9ec6e363e094b3c389c344d5a140401f14a77f0625e3f28c21918552f5", size = 10853999, upload-time = "2026-03-02T15:51:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/48/88/36c652c658fe96658043e4abc8ea97801de6fb6e63ab50aaa82807bff1d8/ty-0.0.20-py3-none-win32.whl", hash = "sha256:c7d32bfe93f8fcaa52b6eef3f1b930fd7da410c2c94e96f7412c30cfbabf1d17", size = 9744206, upload-time = "2026-03-02T15:51:54.183Z" }, + { url = "https://files.pythonhosted.org/packages/ff/a7/a4a13bed1d7fd9d97aaa3c5bb5e6d3e9a689e6984806cbca2ab4c9233cac/ty-0.0.20-py3-none-win_amd64.whl", hash = "sha256:a5e10f40fc4a0a1cbcb740a4aad5c7ce35d79f030836ea3183b7a28f43170248", size = 10711999, upload-time = "2026-03-02T15:51:29.212Z" }, + { url = "https://files.pythonhosted.org/packages/8d/7e/6bfd748a9f4ff9267ed3329b86a0f02cdf6ab49f87bc36c8a164852f99fc/ty-0.0.20-py3-none-win_arm64.whl", hash = "sha256:53f7a5c12c960e71f160b734f328eff9a35d578af4b67a36b0bb5990ac5cdc27", size = 10150143, upload-time = "2026-03-02T15:51:31.283Z" }, ] [[package]] @@ -1643,38 +1624,40 @@ wheels = [ [[package]] name = "yarl" -version = "1.22.0" +version = "1.23.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "multidict" }, { name = "propcache" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/63/0c6ebca57330cd313f6102b16dd57ffaf3ec4c83403dcb45dbd15c6f3ea1/yarl-1.22.0.tar.gz", hash = "sha256:bebf8557577d4401ba8bd9ff33906f1376c877aa78d1fe216ad01b4d6745af71", size = 187169, upload-time = "2025-10-06T14:12:55.963Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/ff/46736024fee3429b80a165a732e38e5d5a238721e634ab41b040d49f8738/yarl-1.22.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e340382d1afa5d32b892b3ff062436d592ec3d692aeea3bef3a5cfe11bbf8c6f", size = 142000, upload-time = "2025-10-06T14:09:44.631Z" }, - { url = "https://files.pythonhosted.org/packages/5a/9a/b312ed670df903145598914770eb12de1bac44599549b3360acc96878df8/yarl-1.22.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f1e09112a2c31ffe8d80be1b0988fa6a18c5d5cad92a9ffbb1c04c91bfe52ad2", size = 94338, upload-time = "2025-10-06T14:09:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f5/0601483296f09c3c65e303d60c070a5c19fcdbc72daa061e96170785bc7d/yarl-1.22.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:939fe60db294c786f6b7c2d2e121576628468f65453d86b0fe36cb52f987bd74", size = 94909, upload-time = "2025-10-06T14:09:48.648Z" }, - { url = "https://files.pythonhosted.org/packages/60/41/9a1fe0b73dbcefce72e46cf149b0e0a67612d60bfc90fb59c2b2efdfbd86/yarl-1.22.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1651bf8e0398574646744c1885a41198eba53dc8a9312b954073f845c90a8df", size = 372940, upload-time = "2025-10-06T14:09:50.089Z" }, - { url = "https://files.pythonhosted.org/packages/17/7a/795cb6dfee561961c30b800f0ed616b923a2ec6258b5def2a00bf8231334/yarl-1.22.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b8a0588521a26bf92a57a1705b77b8b59044cdceccac7151bd8d229e66b8dedb", size = 345825, upload-time = "2025-10-06T14:09:52.142Z" }, - { url = "https://files.pythonhosted.org/packages/d7/93/a58f4d596d2be2ae7bab1a5846c4d270b894958845753b2c606d666744d3/yarl-1.22.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:42188e6a615c1a75bcaa6e150c3fe8f3e8680471a6b10150c5f7e83f47cc34d2", size = 386705, upload-time = "2025-10-06T14:09:54.128Z" }, - { url = "https://files.pythonhosted.org/packages/61/92/682279d0e099d0e14d7fd2e176bd04f48de1484f56546a3e1313cd6c8e7c/yarl-1.22.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f6d2cb59377d99718913ad9a151030d6f83ef420a2b8f521d94609ecc106ee82", size = 396518, upload-time = "2025-10-06T14:09:55.762Z" }, - { url = "https://files.pythonhosted.org/packages/db/0f/0d52c98b8a885aeda831224b78f3be7ec2e1aa4a62091f9f9188c3c65b56/yarl-1.22.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:50678a3b71c751d58d7908edc96d332af328839eea883bb554a43f539101277a", size = 377267, upload-time = "2025-10-06T14:09:57.958Z" }, - { url = "https://files.pythonhosted.org/packages/22/42/d2685e35908cbeaa6532c1fc73e89e7f2efb5d8a7df3959ea8e37177c5a3/yarl-1.22.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e8fbaa7cec507aa24ea27a01456e8dd4b6fab829059b69844bd348f2d467124", size = 365797, upload-time = "2025-10-06T14:09:59.527Z" }, - { url = "https://files.pythonhosted.org/packages/a2/83/cf8c7bcc6355631762f7d8bdab920ad09b82efa6b722999dfb05afa6cfac/yarl-1.22.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:433885ab5431bc3d3d4f2f9bd15bfa1614c522b0f1405d62c4f926ccd69d04fa", size = 365535, upload-time = "2025-10-06T14:10:01.139Z" }, - { url = "https://files.pythonhosted.org/packages/25/e1/5302ff9b28f0c59cac913b91fe3f16c59a033887e57ce9ca5d41a3a94737/yarl-1.22.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b790b39c7e9a4192dc2e201a282109ed2985a1ddbd5ac08dc56d0e121400a8f7", size = 382324, upload-time = "2025-10-06T14:10:02.756Z" }, - { url = "https://files.pythonhosted.org/packages/bf/cd/4617eb60f032f19ae3a688dc990d8f0d89ee0ea378b61cac81ede3e52fae/yarl-1.22.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31f0b53913220599446872d757257be5898019c85e7971599065bc55065dc99d", size = 383803, upload-time = "2025-10-06T14:10:04.552Z" }, - { url = "https://files.pythonhosted.org/packages/59/65/afc6e62bb506a319ea67b694551dab4a7e6fb7bf604e9bd9f3e11d575fec/yarl-1.22.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a49370e8f711daec68d09b821a34e1167792ee2d24d405cbc2387be4f158b520", size = 374220, upload-time = "2025-10-06T14:10:06.489Z" }, - { url = "https://files.pythonhosted.org/packages/e7/3d/68bf18d50dc674b942daec86a9ba922d3113d8399b0e52b9897530442da2/yarl-1.22.0-cp312-cp312-win32.whl", hash = "sha256:70dfd4f241c04bd9239d53b17f11e6ab672b9f1420364af63e8531198e3f5fe8", size = 81589, upload-time = "2025-10-06T14:10:09.254Z" }, - { url = "https://files.pythonhosted.org/packages/c8/9a/6ad1a9b37c2f72874f93e691b2e7ecb6137fb2b899983125db4204e47575/yarl-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:8884d8b332a5e9b88e23f60bb166890009429391864c685e17bd73a9eda9105c", size = 87213, upload-time = "2025-10-06T14:10:11.369Z" }, - { url = "https://files.pythonhosted.org/packages/44/c5/c21b562d1680a77634d748e30c653c3ca918beb35555cff24986fff54598/yarl-1.22.0-cp312-cp312-win_arm64.whl", hash = "sha256:ea70f61a47f3cc93bdf8b2f368ed359ef02a01ca6393916bc8ff877427181e74", size = 81330, upload-time = "2025-10-06T14:10:13.112Z" }, - { url = "https://files.pythonhosted.org/packages/73/ae/b48f95715333080afb75a4504487cbe142cae1268afc482d06692d605ae6/yarl-1.22.0-py3-none-any.whl", hash = "sha256:1380560bdba02b6b6c90de54133c81c9f2a453dee9912fe58c1dcced1edb7cff", size = 46814, upload-time = "2025-10-06T14:12:53.872Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/23/6e/beb1beec874a72f23815c1434518bfc4ed2175065173fb138c3705f658d4/yarl-1.23.0.tar.gz", hash = "sha256:53b1ea6ca88ebd4420379c330aea57e258408dd0df9af0992e5de2078dc9f5d5", size = 194676, upload-time = "2026-03-01T22:07:53.373Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/8a/94615bc31022f711add374097ad4144d569e95ff3c38d39215d07ac153a0/yarl-1.23.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1932b6b8bba8d0160a9d1078aae5838a66039e8832d41d2992daa9a3a08f7860", size = 124737, upload-time = "2026-03-01T22:05:12.897Z" }, + { url = "https://files.pythonhosted.org/packages/e3/6f/c6554045d59d64052698add01226bc867b52fe4a12373415d7991fdca95d/yarl-1.23.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:411225bae281f114067578891bc75534cfb3d92a3b4dfef7a6ca78ba354e6069", size = 87029, upload-time = "2026-03-01T22:05:14.376Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/725ecc166d53438bc88f76822ed4b1e3b10756e790bafd7b523fe97c322d/yarl-1.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13a563739ae600a631c36ce096615fe307f131344588b0bc0daec108cdb47b25", size = 86310, upload-time = "2026-03-01T22:05:15.71Z" }, + { url = "https://files.pythonhosted.org/packages/99/30/58260ed98e6ff7f90ba84442c1ddd758c9170d70327394a6227b310cd60f/yarl-1.23.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9cbf44c5cb4a7633d078788e1b56387e3d3cf2b8139a3be38040b22d6c3221c8", size = 97587, upload-time = "2026-03-01T22:05:17.384Z" }, + { url = "https://files.pythonhosted.org/packages/76/0a/8b08aac08b50682e65759f7f8dde98ae8168f72487e7357a5d684c581ef9/yarl-1.23.0-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:53ad387048f6f09a8969631e4de3f1bf70c50e93545d64af4f751b2498755072", size = 92528, upload-time = "2026-03-01T22:05:18.804Z" }, + { url = "https://files.pythonhosted.org/packages/52/07/0b7179101fe5f8385ec6c6bb5d0cb9f76bd9fb4a769591ab6fb5cdbfc69a/yarl-1.23.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4a59ba56f340334766f3a4442e0efd0af895fae9e2b204741ef885c446b3a1a8", size = 105339, upload-time = "2026-03-01T22:05:20.235Z" }, + { url = "https://files.pythonhosted.org/packages/d3/8a/36d82869ab5ec829ca8574dfcb92b51286fcfb1e9c7a73659616362dc880/yarl-1.23.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:803a3c3ce4acc62eaf01eaca1208dcf0783025ef27572c3336502b9c232005e7", size = 105061, upload-time = "2026-03-01T22:05:22.268Z" }, + { url = "https://files.pythonhosted.org/packages/66/3e/868e5c3364b6cee19ff3e1a122194fa4ce51def02c61023970442162859e/yarl-1.23.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3d2bff8f37f8d0f96c7ec554d16945050d54462d6e95414babaa18bfafc7f51", size = 100132, upload-time = "2026-03-01T22:05:23.638Z" }, + { url = "https://files.pythonhosted.org/packages/cf/26/9c89acf82f08a52cb52d6d39454f8d18af15f9d386a23795389d1d423823/yarl-1.23.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c75eb09e8d55bceb4367e83496ff8ef2bc7ea6960efb38e978e8073ea59ecb67", size = 99289, upload-time = "2026-03-01T22:05:25.749Z" }, + { url = "https://files.pythonhosted.org/packages/6f/54/5b0db00d2cb056922356104468019c0a132e89c8d3ab67d8ede9f4483d2a/yarl-1.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877b0738624280e34c55680d6054a307aa94f7d52fa0e3034a9cc6e790871da7", size = 96950, upload-time = "2026-03-01T22:05:27.318Z" }, + { url = "https://files.pythonhosted.org/packages/f6/40/10fa93811fd439341fad7e0718a86aca0de9548023bbb403668d6555acab/yarl-1.23.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b5405bb8f0e783a988172993cfc627e4d9d00432d6bbac65a923041edacf997d", size = 93960, upload-time = "2026-03-01T22:05:28.738Z" }, + { url = "https://files.pythonhosted.org/packages/bc/d2/8ae2e6cd77d0805f4526e30ec43b6f9a3dfc542d401ac4990d178e4bf0cf/yarl-1.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1c3a3598a832590c5a3ce56ab5576361b5688c12cb1d39429cf5dba30b510760", size = 104703, upload-time = "2026-03-01T22:05:30.438Z" }, + { url = "https://files.pythonhosted.org/packages/2f/0c/b3ceacf82c3fe21183ce35fa2acf5320af003d52bc1fcf5915077681142e/yarl-1.23.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8419ebd326430d1cbb7efb5292330a2cf39114e82df5cc3d83c9a0d5ebeaf2f2", size = 98325, upload-time = "2026-03-01T22:05:31.835Z" }, + { url = "https://files.pythonhosted.org/packages/9d/e0/12900edd28bdab91a69bd2554b85ad7b151f64e8b521fe16f9ad2f56477a/yarl-1.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:be61f6fff406ca40e3b1d84716fde398fc08bc63dd96d15f3a14230a0973ed86", size = 105067, upload-time = "2026-03-01T22:05:33.358Z" }, + { url = "https://files.pythonhosted.org/packages/15/61/74bb1182cf79c9bbe4eb6b1f14a57a22d7a0be5e9cedf8e2d5c2086474c3/yarl-1.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ceb13c5c858d01321b5d9bb65e4cf37a92169ea470b70fec6f236b2c9dd7e34", size = 100285, upload-time = "2026-03-01T22:05:35.4Z" }, + { url = "https://files.pythonhosted.org/packages/69/7f/cd5ef733f2550de6241bd8bd8c3febc78158b9d75f197d9c7baa113436af/yarl-1.23.0-cp312-cp312-win32.whl", hash = "sha256:fffc45637bcd6538de8b85f51e3df3223e4ad89bccbfca0481c08c7fc8b7ed7d", size = 82359, upload-time = "2026-03-01T22:05:36.811Z" }, + { url = "https://files.pythonhosted.org/packages/f5/be/25216a49daeeb7af2bec0db22d5e7df08ed1d7c9f65d78b14f3b74fd72fc/yarl-1.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:f69f57305656a4852f2a7203efc661d8c042e6cc67f7acd97d8667fb448a426e", size = 87674, upload-time = "2026-03-01T22:05:38.171Z" }, + { url = "https://files.pythonhosted.org/packages/d2/35/aeab955d6c425b227d5b7247eafb24f2653fedc32f95373a001af5dfeb9e/yarl-1.23.0-cp312-cp312-win_arm64.whl", hash = "sha256:6e87a6e8735b44816e7db0b2fbc9686932df473c826b0d9743148432e10bb9b9", size = 81879, upload-time = "2026-03-01T22:05:40.006Z" }, + { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, ] [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } [[package]] name = "zstandard" @@ -1704,4 +1687,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#12581f30b45b570dd0bbc36055fe1532f5a8ef60" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } From fc372e2ae1741399cac662abbf4dc5f68eae5598 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 12:36:40 -0800 Subject: [PATCH 457/685] ui needs pillow --- pyproject.toml | 1 + uv.lock | 2 ++ 2 files changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index c581dbfd8b9..5b77eb76d8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,6 +76,7 @@ dependencies = [ "raylib > 5.5.0.3", "qrcode", "jeepney", + "pillow", ] [project.optional-dependencies] diff --git a/uv.lock b/uv.lock index 4f04da00c65..743521fb2f1 100644 --- a/uv.lock +++ b/uv.lock @@ -800,6 +800,7 @@ dependencies = [ { name = "libyuv" }, { name = "ncurses" }, { name = "numpy" }, + { name = "pillow" }, { name = "psutil" }, { name = "pycapnp" }, { name = "pycryptodome" }, @@ -883,6 +884,7 @@ requires-dist = [ { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, { name = "numpy", specifier = ">=2.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, + { name = "pillow" }, { name = "pre-commit-hooks", marker = "extra == 'testing'" }, { name = "psutil" }, { name = "pycapnp" }, From fef89d1039bb1e9526d4880aa3520ad940c7678d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 14:18:35 -0800 Subject: [PATCH 458/685] op adb: find free port --- tools/scripts/adb_ssh.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tools/scripts/adb_ssh.sh b/tools/scripts/adb_ssh.sh index 4527a0296d5..b9668e7e0b4 100755 --- a/tools/scripts/adb_ssh.sh +++ b/tools/scripts/adb_ssh.sh @@ -31,8 +31,12 @@ for name, port in sorted(ports): PY ) -# Forward SSH port first for interactive shell access. -adb forward tcp:2222 tcp:22 +# Forward SSH port, finding a free local port if 2222 is taken. +SSH_PORT=2222 +while ss -tln | grep -q ":${SSH_PORT} "; do + SSH_PORT=$((SSH_PORT + 1)) +done +adb forward tcp:${SSH_PORT} tcp:22 # SSH! -ssh comma@localhost -p 2222 "$@" +ssh comma@localhost -p ${SSH_PORT} "$@" From e264b4269f7a556a4f588d25fd29bb7e1277f750 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 14:39:11 -0800 Subject: [PATCH 459/685] reset: don't timeout if partition is corrupt --- system/ui/mici_reset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 5ad959badbb..de22dea5d61 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -73,7 +73,7 @@ def _update_state(self): if self._reset_state != self._previous_reset_state: self._previous_reset_state = self._reset_state self._timeout_st = time.monotonic() - elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: + elif self._mode != ResetMode.RECOVER and self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: exit(0) def _render(self, rect: rl.Rectangle): From 2c4e114b51ff1526c295bcb7ee49ecc52103191a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 17:35:24 -0800 Subject: [PATCH 460/685] updater: new scroller style (#37556) * good start * reset on push * clean up * why tf it remove comments * no more base unnav * repack --- system/hardware/tici/updater_magic | 4 +- system/ui/mici_setup.py | 14 +-- system/ui/mici_updater.py | 180 ++++++++++++----------------- 3 files changed, 80 insertions(+), 118 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index 9674d85f00a..b4f776565d0 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d815a9140e69242d85e57f74a14c6372809eebefe8958be9152efa7874928ccc -size 71215510 +oid sha256:d376e7aa4bb44bf78db9965ff55a5362ac823335b8d2a81dd3aebc5c8c2f239f +size 71214189 diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 5dc9905ddf8..8a72712527a 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -216,9 +216,11 @@ def _render(self, rect: rl.Rectangle): )) -class FailedPageBase(Widget): +class FailedPage(NavWidget): def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): super().__init__() + self.set_back_callback(retry_callback) + self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), @@ -269,12 +271,6 @@ def _render(self, rect: rl.Rectangle): )) -class FailedPage(FailedPageBase, NavWidget): - def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): - super().__init__(reboot_callback, retry_callback, title) - self.set_back_callback(retry_callback) - - class GreyBigButton(BigButton): """Users should manage newlines with this class themselves""" @@ -426,10 +422,6 @@ def set_custom_software(self, custom_software: bool): self._continue_button.set_text("install openpilot" if not custom_software else "choose software") self._continue_button.set_green(not custom_software) - def set_is_updater(self): - self._continue_button.set_text("download\n& install") - self._continue_button.set_green(False) - def _update_state(self): super()._update_state() diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index cd1f99062ce..8e6f0a195ca 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -3,95 +3,116 @@ import subprocess import threading import pyray as rl -from enum import IntEnum from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.button import FullRoundedButton -from openpilot.system.ui.mici_setup import NetworkSetupPageBase, FailedPageBase, NetworkConnectivityMonitor +from openpilot.system.ui.mici_setup import (NetworkSetupPage, FailedPage, NetworkConnectivityMonitor, + GreyBigButton, BigPillButton) -class Screen(IntEnum): - PROMPT = 0 - WIFI = 1 - PROGRESS = 2 - FAILED = 3 +class UpdaterNetworkSetupPage(NetworkSetupPage): + def __init__(self, network_monitor, continue_callback): + super().__init__(network_monitor, continue_callback, back_callback=None) + self._continue_button.set_text("download\n& install") + self._continue_button.set_green(False) -class Updater(Widget): +class ProgressPage(Widget): + def __init__(self): + super().__init__() + + self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), + font_weight=FontWeight.DISPLAY, line_height=0.8) + self._progress_percent_label = UnifiedLabel("", 132, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), + font_weight=FontWeight.ROMAN, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + + def set_progress(self, text: str, value: int): + self._progress_title_label.set_text(text.replace("_", "_\n") + "...") + self._progress_percent_label.set_text(f"{value}%") + + def show_event(self): + super().show_event() + self.set_progress("downloading", 0) + + def _render(self, rect: rl.Rectangle): + rl.draw_rectangle_rec(rect, rl.BLACK) + self._progress_title_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 2, + rect.width, + self._progress_title_label.get_content_height(int(rect.width - 20)), + )) + + self._progress_percent_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 18, + rect.width, + rect.height, + )) + + +class Updater(Scroller): def __init__(self, updater_path, manifest_path): super().__init__() self.updater = updater_path self.manifest = manifest_path - self.current_screen = Screen.PROMPT self.progress_value = 0 self.progress_text = "loading" self.process = None self.update_thread = None + self._update_failed = False + self._network_monitor = NetworkConnectivityMonitor() self._network_monitor.start() - # TODO: network page is rendered inline, not pushed on nav stack, so auto-dismiss on internet connect doesn't work - self._network_setup_page = NetworkSetupPageBase(self._network_monitor, self._network_setup_continue_callback, - disable_connect_hint=True) - self._network_setup_page.set_is_updater() - self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack + self._network_setup_page = UpdaterNetworkSetupPage(self._network_monitor, self._network_setup_continue_callback) - # Buttons - self._continue_button = FullRoundedButton("continue") - self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI)) + self._progress_page = ProgressPage() - self._title_label = UnifiedLabel("update required", 48, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.DISPLAY) - self._subtitle_label = UnifiedLabel("The download size is approximately 1GB.", 36, - text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) + self._failed_page = FailedPage(HARDWARE.reboot, self._retry, title="update failed") - self._update_failed_page = FailedPageBase(HARDWARE.reboot, self._update_failed_retry_callback, - title="update failed") + self._continue_button = BigPillButton("next") + self._continue_button.set_click_callback(lambda: gui_app.push_widget(self._network_setup_page)) - self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.DISPLAY, line_height=0.8) - self._progress_percent_label = UnifiedLabel("", 132, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), - font_weight=FontWeight.ROMAN, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + self._scroller.add_widgets([ + GreyBigButton("update required", "The download size is\napproximately 1 GB", + gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 64, 64)), + self._continue_button, + ]) + + gui_app.add_nav_stack_tick(self._nav_stack_tick) def _network_setup_continue_callback(self, _): self.install_update() - def _update_failed_retry_callback(self): - self.set_current_screen(Screen.PROMPT) - - def set_current_screen(self, screen: Screen): - if self.current_screen != screen: - if screen == Screen.PROGRESS: - if self._network_setup_page: - self._network_setup_page.hide_event() - elif screen == Screen.WIFI: - if self._network_setup_page: - self._network_setup_page.show_event() - elif screen == Screen.PROMPT: - if self._network_setup_page: - self._network_setup_page.hide_event() - elif screen == Screen.FAILED: - if self._network_setup_page: - self._network_setup_page.hide_event() - - self.current_screen = screen + def _retry(self): + gui_app.pop_widgets_to(self) + + def _nav_stack_tick(self): + self._progress_page.set_progress(self.progress_text, self.progress_value) + + if self._update_failed: + self._update_failed = False + self.show_event() + gui_app.pop_widgets_to(self, instant=True) + gui_app.push_widget(self._failed_page) def install_update(self): - self.set_current_screen(Screen.PROGRESS) self.progress_value = 0 self.progress_text = "downloading" + gui_app.pop_widgets_to(self, instant=True) + gui_app.push_widget(self._progress_page) + # Start the update process in a separate thread - self.update_thread = threading.Thread(target=self._run_update_process) - self.update_thread.daemon = True + self.update_thread = threading.Thread(target=self._run_update_process, daemon=True) self.update_thread.start() def _run_update_process(self): @@ -101,7 +122,7 @@ def _run_update_process(self): self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) except Exception: - self.set_current_screen(Screen.FAILED) + self._update_failed = True return if self.process.stdout is not None: @@ -118,58 +139,7 @@ def _run_update_process(self): if exit_code == 0: HARDWARE.reboot() else: - self.set_current_screen(Screen.FAILED) - - def render_prompt_screen(self, rect: rl.Rectangle): - self._title_label.render(rl.Rectangle( - rect.x + 8, - rect.y - 5, - rect.width, - 48, - )) - - subtitle_width = rect.width - 16 - subtitle_height = self._subtitle_label.get_content_height(int(subtitle_width)) - self._subtitle_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 48, - subtitle_width, - subtitle_height, - )) - - self._continue_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) - - def render_progress_screen(self, rect: rl.Rectangle): - self._progress_title_label.set_text(self.progress_text.replace("_", "_\n") + "...") - self._progress_title_label.render(rl.Rectangle( - rect.x + 12, - rect.y + 2, - rect.width, - self._progress_title_label.get_content_height(int(rect.width - 20)), - )) - - self._progress_percent_label.set_text(f"{self.progress_value}%") - self._progress_percent_label.render(rl.Rectangle( - rect.x + 12, - rect.y + 18, - rect.width, - rect.height, - )) - - def _render(self, rect: rl.Rectangle): - if self.current_screen == Screen.PROMPT: - self.render_prompt_screen(rect) - elif self.current_screen == Screen.WIFI: - self._network_setup_page.render(rect) - elif self.current_screen == Screen.PROGRESS: - self.render_progress_screen(rect) - elif self.current_screen == Screen.FAILED: - self._update_failed_page.render(rect) + self._update_failed = True def close(self): self._network_monitor.stop() From 6330a9c53a317b2ad3ad70477c57bc234c17a955 Mon Sep 17 00:00:00 2001 From: Jacob Pfeifer Date: Wed, 4 Mar 2026 21:59:57 -0500 Subject: [PATCH 461/685] add explicit include for cstdint instead of relying on leaky include (#37559) --- third_party/json11/json11.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/third_party/json11/json11.cpp b/third_party/json11/json11.cpp index bc4045f07d1..3bd4fde2f2c 100644 --- a/third_party/json11/json11.cpp +++ b/third_party/json11/json11.cpp @@ -25,6 +25,7 @@ #include #include #include +#include namespace json11 { From 055b29b22625627a3f129ef52d550a654fa77901 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 19:37:24 -0800 Subject: [PATCH 462/685] updater: better flow (#37560) * better update flow * clean up * clean up * cmt * clean up * todo --- common/filter_simple.py | 2 +- system/ui/mici_updater.py | 24 ++++++++++++++---------- system/ui/widgets/nav_widget.py | 9 +++++++++ 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/common/filter_simple.py b/common/filter_simple.py index 212e1a8f409..b28c3d68f59 100644 --- a/common/filter_simple.py +++ b/common/filter_simple.py @@ -28,7 +28,7 @@ def update(self, x): scale = self.dt / (1.0 / 60.0) # tuned at 60 fps self.velocity.x += (x - self.x) * self.bounce * scale * self.dt self.velocity.update(0.0) - if abs(self.velocity.x) < 1e-5: + if abs(self.velocity.x) < 1e-3: self.velocity.x = 0.0 self.x += self.velocity.x return self.x diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 8e6f0a195ca..42aa1090bcd 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -8,7 +8,7 @@ from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.mici_setup import (NetworkSetupPage, FailedPage, NetworkConnectivityMonitor, @@ -22,7 +22,7 @@ def __init__(self, network_monitor, continue_callback): self._continue_button.set_green(False) -class ProgressPage(Widget): +class ProgressPage(NavWidget): def __init__(self): super().__init__() @@ -32,12 +32,16 @@ def __init__(self): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + def _back_enabled(self) -> bool: + return False + def set_progress(self, text: str, value: int): self._progress_title_label.set_text(text.replace("_", "_\n") + "...") self._progress_percent_label.set_text(f"{value}%") def show_event(self): super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable self.set_progress("downloading", 0) def _render(self, rect: rl.Rectangle): @@ -82,7 +86,7 @@ def __init__(self, updater_path, manifest_path): self._continue_button.set_click_callback(lambda: gui_app.push_widget(self._network_setup_page)) self._scroller.add_widgets([ - GreyBigButton("update required", "The download size is\napproximately 1 GB", + GreyBigButton("update required", "the download size\nis approximately 1 GB", gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 64, 64)), self._continue_button, ]) @@ -101,19 +105,19 @@ def _nav_stack_tick(self): if self._update_failed: self._update_failed = False self.show_event() - gui_app.pop_widgets_to(self, instant=True) - gui_app.push_widget(self._failed_page) + gui_app.pop_widgets_to(self, lambda: gui_app.push_widget(self._failed_page)) def install_update(self): self.progress_value = 0 self.progress_text = "downloading" - gui_app.pop_widgets_to(self, instant=True) - gui_app.push_widget(self._progress_page) + def start_update(): + self.update_thread = threading.Thread(target=self._run_update_process, daemon=True) + self.update_thread.start() - # Start the update process in a separate thread - self.update_thread = threading.Thread(target=self._run_update_process, daemon=True) - self.update_thread.start() + # Start the update process in a separate thread *after* show animation completes + self._progress_page.set_shown_callback(start_update) + gui_app.push_widget(self._progress_page) def _run_update_process(self): # TODO: just import it and run in a thread without a subprocess diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 3292c53ce8b..5cf8715c0c1 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -65,6 +65,8 @@ def __init__(self): self._back_callback: Callable[[], None] | None = None # persistent callback for user-initiated back navigation self._dismiss_callback: Callable[[], None] | None = None # transient callback for programmatic dismiss + # TODO: add this functionality to push_widget + self._shown_callback: Callable[[], None] | None = None # transient callback fired after show animation completes # TODO: move this state into NavBar self._nav_bar = NavBar() @@ -79,6 +81,9 @@ def _back_enabled(self) -> bool: def set_back_callback(self, callback: Callable[[], None]) -> None: self._back_callback = callback + def set_shown_callback(self, callback: Callable[[], None] | None) -> None: + self._shown_callback = callback + def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: super()._handle_mouse_event(mouse_event) @@ -147,6 +152,10 @@ def _update_state(self): if abs(new_y) < 1 and self._y_pos_filter.velocity.x == 0.0: new_y = self._y_pos_filter.x = 0.0 + if self._shown_callback is not None: + self._shown_callback() + self._shown_callback = None + if new_y > self._rect.height + DISMISS_PUSH_OFFSET - 10: gui_app.pop_widget() From 0274b737607571048008a044991d132390d7634d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 4 Mar 2026 20:20:07 -0800 Subject: [PATCH 463/685] jenkins: always run pandad tests --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index c5ebf6162bd..5c785f1d982 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -218,14 +218,14 @@ node { 'camerad OX03C10': { deviceStage("OX03C10", "tizi-ox03c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"), step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, 'camerad OS04C10': { deviceStage("OS04C10", "tici-os04c10", ["UNSAFE=1"], [ step("build", "cd system/manager && ./build.py"), - step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py", [diffPaths: ["panda", "selfdrive/pandad/"]]), + step("test pandad", "pytest selfdrive/pandad/tests/test_pandad.py"), step("test camerad", "pytest system/camerad/test/test_camerad.py", [timeout: 90]), ]) }, From 5beae930e458521b08df20a2d665ff3929fa5be5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 20:44:29 -0800 Subject: [PATCH 464/685] setup: new scroller failed screen (#37561) * better update flow * clean up * clean up * cmt * clean up * todo * failed scroller * fix for setup * show wrong url * setup failed is red not orange * clean up and fix all flashing in setup --- system/ui/mici_setup.py | 106 ++++++++++++++++---------------------- system/ui/mici_updater.py | 2 +- 2 files changed, 46 insertions(+), 62 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 8a72712527a..3265a325e22 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -21,14 +21,13 @@ from openpilot.system.ui.lib.wifi_manager import WifiManager from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.button import SmallButton from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPACING -from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider +from openpilot.system.ui.widgets.slider import LargerSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog -from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton NetworkType = log.DeviceState.NetworkType @@ -181,7 +180,8 @@ def __init__(self, continue_callback: Callable, back_callback: Callable): ]) -class DownloadingPage(Widget): +# TODO: unifi with updater's progress page +class DownloadingPage(NavWidget): def __init__(self): super().__init__() @@ -191,8 +191,12 @@ def __init__(self): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) self._progress = 0 + def _back_enabled(self) -> bool: + return False + def show_event(self): super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable self.set_progress(0) def set_progress(self, progress: int): @@ -216,59 +220,37 @@ def _render(self, rect: rl.Rectangle): )) -class FailedPage(NavWidget): - def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): +class FailedPage(NavScroller): + def __init__(self, retry_callback: Callable, title: str = "download failed", red_icon: bool = False): super().__init__() self.set_back_callback(retry_callback) - self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.DISPLAY) - self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), - font_weight=FontWeight.ROMAN) - - self._reboot_slider = SmallSlider("reboot", reboot_callback) - self._reboot_slider.set_enabled(lambda: self.enabled) # for nav stack - - self._retry_button = SmallButton("retry") - self._retry_button.set_click_callback(retry_callback) - self._retry_button.set_enabled(lambda: self.enabled) # for nav stack - - def set_reason(self, reason: str): - self._reason_label.set_text(reason) + def show_reboot_dialog(): + dialog = BigConfirmationDialogV2("slide to reboot", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + gui_app.push_widget(dialog) - def show_event(self): - super().show_event() - self._reboot_slider.reset() + reboot_button = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) + reboot_button.set_click_callback(show_reboot_dialog) - def _render(self, rect: rl.Rectangle): - self._title_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 10, - rect.width, - 64, - )) + self._reason_card = GreyBigButton("", "") + self._reason_card.set_visible(False) - self._reason_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 10 + 64, - rect.width, - 36, - )) + warning_icon = "icons_mici/setup/red_warning.png" if red_icon else "icons_mici/setup/warning.png" - self._retry_button.set_opacity(1 - self._reboot_slider.slider_percentage) - self._retry_button.render(rl.Rectangle( - self._rect.x + 8, - self._rect.y + self._rect.height - self._retry_button.rect.height, - self._retry_button.rect.width, - self._retry_button.rect.height, - )) + self._scroller.add_widgets([ + GreyBigButton(title, "swipe down to go\nback and try again", + gui_app.texture(warning_icon, 64, 58)), + self._reason_card, + reboot_button, + ]) - self._reboot_slider.render(rl.Rectangle( - self._rect.x + self._rect.width - self._reboot_slider.rect.width, - self._rect.y + self._rect.height - self._reboot_slider.rect.height, - self._reboot_slider.rect.width, - self._reboot_slider.rect.height, - )) + def set_reason(self, reason: str): + if reason: + self._reason_card.set_value(reason) + self._reason_card.set_visible(True) + else: + self._reason_card.set_visible(False) class GreyBigButton(BigButton): @@ -300,6 +282,9 @@ def LABEL_VERTICAL_PADDING(self): def _width_hint(self) -> int: return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2) + def _get_label_font_size(self): + return 36 + def _render(self, _): rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15))) self._draw_content(self._rect.y) @@ -474,7 +459,7 @@ def getting_started_button_callback(): self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) - self._download_failed_page = FailedPage(HARDWARE.reboot, self._pop_to_software_selection) + self._download_failed_page = FailedPage(self._pop_to_software_selection, red_icon=True) self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection) @@ -489,8 +474,7 @@ def _nav_stack_tick(self): reason = self._download_failed_reason self._download_failed_reason = None self._download_failed_page.set_reason(reason) - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders - gui_app.push_widget(self._download_failed_page) + gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._download_failed_page)) def _render(self, rect: rl.Rectangle): self._start_page.render(rect) @@ -524,13 +508,11 @@ def _push_network_setup(self, custom_software: bool = False): def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(OPENPILOT_URL) else: def handle_keyboard_result(text): url = text.strip() if url: - gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders self._download(url) keyboard = BigInputDialog("custom software URL...", confirm_callback=handle_keyboard_result, auto_return_to_letters="./") @@ -545,10 +527,12 @@ def _download(self, url: str): self.download_url = (urlparse(f"https://{url}") if not parsed.netloc else parsed).geturl() self.download_progress = 0 - gui_app.push_widget(self._downloading_page) + def start_download(): + self.download_thread = threading.Thread(target=self._download_thread, daemon=True) + self.download_thread.start() - self.download_thread = threading.Thread(target=self._download_thread, daemon=True) - self.download_thread.start() + self._downloading_page.set_shown_callback(start_download) + gui_app.push_widget(self._downloading_page) def _download_thread(self): try: @@ -583,7 +567,7 @@ def _download_thread(self): is_elf = header == b'\x7fELF' if not is_elf: - self._download_failed_reason = "No custom software found at this URL." + self._download_failed_reason = "No custom software found at this URL: " + self.download_url.replace("https://", "", 1) return # AGNOS might try to execute the installer before this process exits. @@ -600,9 +584,9 @@ def _download_thread(self): except urllib.error.HTTPError as e: if e.code == 409: - self._download_failed_reason = "Incompatible openpilot version" + self._download_failed_reason = "Incompatible openpilot version." except Exception: - self._download_failed_reason = "Invalid URL" + self._download_failed_reason = "Invalid URL: " + self.download_url.replace("https://", "", 1) def main(): diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index 42aa1090bcd..8437e6fa60c 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -80,7 +80,7 @@ def __init__(self, updater_path, manifest_path): self._progress_page = ProgressPage() - self._failed_page = FailedPage(HARDWARE.reboot, self._retry, title="update failed") + self._failed_page = FailedPage(self._retry, title="update failed") self._continue_button = BigPillButton("next") self._continue_button.set_click_callback(lambda: gui_app.push_widget(self._network_setup_page)) From e59f675715cff7df8406cff741fbe95921e393bb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 22:36:25 -0800 Subject: [PATCH 465/685] new reset (#37563) * start new reset w navwidgets * full port * clean up * clean up * clean up * fixes * rm --- .../assets/icons_mici/setup/factory_reset.png | 3 + .../assets/icons_mici/setup/reset_failed.png | 3 + system/ui/mici_reset.py | 176 +++++++++--------- system/ui/mici_setup.py | 11 +- 4 files changed, 103 insertions(+), 90 deletions(-) create mode 100644 selfdrive/assets/icons_mici/setup/factory_reset.png create mode 100644 selfdrive/assets/icons_mici/setup/reset_failed.png diff --git a/selfdrive/assets/icons_mici/setup/factory_reset.png b/selfdrive/assets/icons_mici/setup/factory_reset.png new file mode 100644 index 00000000000..bcb3ea92cb1 --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/factory_reset.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:122a614d1aa26187507951f932160eebfddfebcb4293e78f8d23e350fc97bc0f +size 11489 diff --git a/selfdrive/assets/icons_mici/setup/reset_failed.png b/selfdrive/assets/icons_mici/setup/reset_failed.png new file mode 100644 index 00000000000..680df97cbcd --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/reset_failed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d5b8f76e5f47e77e5af3016ebdbe548ad3bc9af83a1111b3214bf4017c95a28 +size 11792 diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index de22dea5d61..5cbba5e8efd 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 import os import sys -import threading import time from enum import IntEnum import pyray as rl -from openpilot.system.hardware import PC -from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.slider import SmallSlider -from openpilot.system.ui.widgets.button import SmallButton, FullRoundedButton -from openpilot.system.ui.widgets.label import gui_label, gui_text_box +from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.nav_widget import NavWidget +from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" TIMEOUT = 3*60 @@ -24,32 +24,87 @@ class ResetMode(IntEnum): FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata -class ResetState(IntEnum): - NONE = 0 - RESETTING = 1 - FAILED = 2 +class ResetFailedPage(FailedPage): + def __init__(self): + super().__init__(None, "reset failed", "reboot to try again", icon="icons_mici/setup/reset_failed.png") + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable -class Reset(Widget): + def _back_enabled(self) -> bool: + return False + + +class ResettingPage(NavWidget): + def __init__(self): + super().__init__() + + self._resetting_card = GreyBigButton("resetting device", "this may take up to\na minute...", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable + + def _back_enabled(self) -> bool: + return False + + def _render(self, _): + self._resetting_card.render(rl.Rectangle( + self._rect.x + self._rect.width / 2 - self._resetting_card.rect.width / 2, + self._rect.y + self._rect.height / 2 - self._resetting_card.rect.height / 2, + self._resetting_card.rect.width, + self._resetting_card.rect.height, + )) + + +class Reset(Scroller): def __init__(self, mode): super().__init__() self._mode = mode - self._previous_reset_state = None - self._reset_state = ResetState.NONE + self._previous_active_widget = None + self._reset_failed = False + self._timeout_st = time.monotonic() - self._cancel_button = SmallButton("cancel") - self._cancel_button.set_click_callback(gui_app.request_close) + self._resetting_page = ResettingPage() + self._reset_failed_page = ResetFailedPage() - self._reboot_button = FullRoundedButton("reboot") - self._reboot_button.set_click_callback(self._do_reboot) + def show_confirm_dialog(): + dialog = BigConfirmationDialogV2("erase\ndevice", "icons_mici/settings/device/uninstall.png", red=True, + confirm_callback=self.start_reset) + gui_app.push_widget(dialog) - self._confirm_slider = SmallSlider("reset", self._confirm) + def show_cancel_dialog(): + dialog = BigConfirmationDialogV2("normal\nstartup", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=gui_app.request_close) + gui_app.push_widget(dialog) - def _do_reboot(self): - if PC: - return + def show_reboot_dialog(): + dialog = BigConfirmationDialogV2("reboot\ndevice", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + gui_app.push_widget(dialog) - os.system("sudo reboot") + self._reset_button = BigCircleButton("icons_mici/settings/device/uninstall.png", red=True) + self._reset_button.set_click_callback(show_confirm_dialog) + + self._cancel_button = BigCircleButton("icons_mici/settings/device/reboot.png") + self._cancel_button.set_click_callback(show_cancel_dialog) + + main_card = GreyBigButton("factory reset", "all content and\nsettings will be erased", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + + # cancel button becomes reboot button + if mode == ResetMode.RECOVER: + main_card.set_text("unable to mount\ndata partition") + main_card.set_value("it may be corrupted") + self._cancel_button.set_click_callback(show_reboot_dialog) + + self._scroller.add_widgets([ + main_card, + self._reset_button, + self._cancel_button, + ]) def _do_erase(self): if PC: @@ -63,72 +118,26 @@ def _do_erase(self): if rm == 0 or fmt == 0: os.system("sudo reboot") else: - self._reset_state = ResetState.FAILED + self._reset_failed = True def start_reset(self): - self._reset_state = ResetState.RESETTING - threading.Timer(0.1, self._do_erase).start() + self._resetting_page.set_shown_callback(self._do_erase) + gui_app.push_widget(self._resetting_page) def _update_state(self): - if self._reset_state != self._previous_reset_state: - self._previous_reset_state = self._reset_state + super()._update_state() + + if self._reset_failed: + self._reset_failed = False + gui_app.pop_widgets_to(self, lambda: gui_app.push_widget(self._reset_failed_page)) + + active_widget = gui_app.get_active_widget() + if active_widget != self._previous_active_widget: + self._previous_active_widget = active_widget self._timeout_st = time.monotonic() - elif self._mode != ResetMode.RECOVER and self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: + elif self._mode != ResetMode.RECOVER and active_widget != self._resetting_page and (time.monotonic() - self._timeout_st) > TIMEOUT: exit(0) - def _render(self, rect: rl.Rectangle): - label_rect = rl.Rectangle(rect.x + 8, rect.y + 8, rect.width, 50) - gui_label(label_rect, "factory reset", 48, font_weight=FontWeight.BOLD, - color=rl.Color(255, 255, 255, int(255 * 0.9))) - - text_rect = rl.Rectangle(rect.x + 8, rect.y + 56, rect.width - 8 * 2, rect.height - 80) - gui_text_box(text_rect, self._get_body_text(), 36, font_weight=FontWeight.ROMAN, line_scale=0.9) - - if self._reset_state != ResetState.RESETTING: - # fade out cancel button as slider is moved, set visible to prevent pressing invisible cancel - self._cancel_button.set_opacity(1.0 - self._confirm_slider.slider_percentage) - self._cancel_button.set_visible(self._confirm_slider.slider_percentage < 0.8) - - if self._mode == ResetMode.RECOVER: - self._cancel_button.set_text("reboot") - self._cancel_button.set_click_callback(self._do_reboot) - self._cancel_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._cancel_button.rect.height, - self._cancel_button.rect.width, - self._cancel_button.rect.height)) - elif self._mode == ResetMode.USER_RESET and self._reset_state != ResetState.FAILED: - self._cancel_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._cancel_button.rect.height, - self._cancel_button.rect.width, - self._cancel_button.rect.height)) - - if self._reset_state != ResetState.FAILED: - self._confirm_slider.render(rl.Rectangle( - rect.x + rect.width - self._confirm_slider.rect.width, - rect.y + rect.height - self._confirm_slider.rect.height, - self._confirm_slider.rect.width, - self._confirm_slider.rect.height)) - else: - self._reboot_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._reboot_button.rect.height, - self._reboot_button.rect.width, - self._reboot_button.rect.height)) - - def _confirm(self): - self.start_reset() - - def _get_body_text(self): - if self._reset_state == ResetState.RESETTING: - return "Resetting device... This may take up to a minute." - if self._reset_state == ResetState.FAILED: - return "Reset failed. Reboot to try again." - if self._mode == ResetMode.RECOVER: - return "Unable to mount data partition. It may be corrupted." - return "All content and settings will be erased." - def main(): mode = ResetMode.USER_RESET @@ -140,12 +149,11 @@ def main(): gui_app.init_window("System Reset") reset = Reset(mode) + gui_app.push_widget(reset) if mode == ResetMode.FORMAT: reset.start_reset() - gui_app.push_widget(reset) - for _ in gui_app.render(): pass diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 3265a325e22..92ac1df44e1 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -221,7 +221,8 @@ def _render(self, rect: rl.Rectangle): class FailedPage(NavScroller): - def __init__(self, retry_callback: Callable, title: str = "download failed", red_icon: bool = False): + def __init__(self, retry_callback: Callable | None, title: str = "download failed", + description: str | None = None, icon: str = "icons_mici/setup/warning.png"): super().__init__() self.set_back_callback(retry_callback) @@ -236,11 +237,9 @@ def show_reboot_dialog(): self._reason_card = GreyBigButton("", "") self._reason_card.set_visible(False) - warning_icon = "icons_mici/setup/red_warning.png" if red_icon else "icons_mici/setup/warning.png" - self._scroller.add_widgets([ - GreyBigButton(title, "swipe down to go\nback and try again", - gui_app.texture(warning_icon, 64, 58)), + GreyBigButton(title, description or "swipe down to go\nback and try again", + gui_app.texture(icon, 64, 58)), self._reason_card, reboot_button, ]) @@ -459,7 +458,7 @@ def getting_started_button_callback(): self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) - self._download_failed_page = FailedPage(self._pop_to_software_selection, red_icon=True) + self._download_failed_page = FailedPage(self._pop_to_software_selection, icon="icons_mici/setup/red_warning.png") self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection) From 3cc9d89d4578b1b42f1ecfee87aa38bff7ecb3d6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 23:07:37 -0800 Subject: [PATCH 466/685] mici ui: wifi scanning card (#37564) * start * yes * no more show * clean up --- .../mici/layouts/settings/network/wifi_ui.py | 76 +++++++++---------- 1 file changed, 35 insertions(+), 41 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 22d3d1d0da9..9cd4db7bf0d 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -3,7 +3,6 @@ import pyray as rl from collections.abc import Callable -from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR @@ -14,39 +13,26 @@ class LoadingAnimation(Widget): - HIDE_TIME = 4 + RADIUS = 8 + SPACING = 24 # center-to-center: diameter (16) + gap (8) + Y_MAG = 11.2 def __init__(self): super().__init__() - self._opacity_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) - self._opacity_target = 1.0 - self._hide_time = 0.0 - - def show_event(self): - self._opacity_target = 1.0 - self._hide_time = rl.get_time() + w = self.SPACING * 2 + self.RADIUS * 2 + h = self.RADIUS * 2 + int(self.Y_MAG) + self.set_rect(rl.Rectangle(0, 0, w, h)) def _render(self, _): - if rl.get_time() - self._hide_time > self.HIDE_TIME: - self._opacity_target = 0.0 - - self._opacity_filter.update(self._opacity_target) - - if self._opacity_filter.x < 0.01: - return - - cx = int(self._rect.x + self._rect.width / 2) - cy = int(self._rect.y + self._rect.height / 2) - - y_mag = 7 - anim_scale = 4 - spacing = 14 + # Balls rest at bottom center; bounce upward + base_x = int(self._rect.x + self._rect.width / 2) + base_y = int(self._rect.y + self._rect.height - self.RADIUS) for i in range(3): - x = cx - spacing + i * spacing - y = int(cy + min(math.sin((rl.get_time() - i * 0.2) * anim_scale) * y_mag, 0)) - alpha = int(np.interp(cy - y, [0, y_mag], [255 * 0.45, 255 * 0.9]) * self._opacity_filter.x) - rl.draw_circle(x, y, 5, rl.Color(255, 255, 255, alpha)) + x = base_x + (i - 1) * self.SPACING + y = int(base_y + min(math.sin((rl.get_time() - i * 0.2) * 4) * self.Y_MAG, 0)) + alpha = int(np.interp(base_y - y, [0, self.Y_MAG], [255 * 0.45, 255 * 0.9])) + rl.draw_circle(x, y, self.RADIUS, rl.Color(255, 255, 255, alpha)) class WifiIcon(Widget): @@ -270,11 +256,26 @@ def _render(self, _): rl.draw_texture_ex(self._trash_txt, (trash_x, trash_y), 0, 1.0, rl.WHITE) +class ScanningButton(BigButton): + def __init__(self): + super().__init__("", "searching for networks") + self.set_enabled(False) + self._loading_animation = LoadingAnimation() + + def _draw_content(self, btn_y: float): + super()._draw_content(btn_y) + anim = self._loading_animation + x = self._rect.x + self._rect.width - anim.rect.width - 40 + y = btn_y + self._rect.height - anim.rect.height - 30 + anim.set_position(x, y) + anim.render() + + class WifiUIMici(NavScroller): def __init__(self, wifi_manager: WifiManager): super().__init__() - self._loading_animation = LoadingAnimation() + self._scanning_btn = ScanningButton() self._wifi_manager = wifi_manager self._networks: dict[str, Network] = {} @@ -288,9 +289,9 @@ def __init__(self, wifi_manager: WifiManager): def show_event(self): # Clear scroller items and update from latest scan results super().show_event() - self._loading_animation.show_event() self._wifi_manager.set_active(True) self._scroller.items.clear() + self._scroller.add_widget(self._scanning_btn) # trigger button update on latest sorted networks self._on_network_updated(self._wifi_manager.networks) @@ -310,6 +311,11 @@ def _update_buttons(self): btn.set_click_callback(lambda ssid=network.ssid: self._connect_to_network(ssid)) self._scroller.add_widget(btn) + # Keep scanning button at the end + items = self._scroller.items + if self._scanning_btn in items: + items.append(items.pop(items.index(self._scanning_btn))) + # Mark networks no longer in scan results (display handled by _update_state) for btn in self._scroller.items: if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: @@ -371,16 +377,4 @@ def _update_state(self): self._move_network_to_front(self._wifi_manager.wifi_state.ssid) - # Show loading animation near end - max_scroll = max(self._scroller.content_size - self._scroller.rect.width, 1) - progress = -self._scroller.scroll_panel.get_offset() / max_scroll - if progress > 0.8 or len(self._scroller.items) <= 1: - self._loading_animation.show_event() - - def _render(self, _): - super()._render(self._rect) - anim_w = 90 - anim_x = self._rect.x + self._rect.width - anim_w - anim_y = self._rect.y + self._rect.height - 25 + 2 - self._loading_animation.render(rl.Rectangle(anim_x, anim_y, anim_w, 20)) From 4f5df6589d5c87f8b554f726096817208dd6662a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 23:47:34 -0800 Subject: [PATCH 467/685] mici setup: set WifiManager active on network setup page show (#37566) * set active * cmt --- system/ui/mici_setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 92ac1df44e1..c1474728c7b 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -371,6 +371,8 @@ def on_waiting_click(): def show_event(self): super().show_event() + # make sure we populate strength and ip immediately if already have wifi + self._wifi_manager.set_active(True) self._show_time = rl.get_time() self._prev_has_internet = False self._pending_has_internet_scroll = False From dcc166343fbd6b8eb5800ba4ba65a6c05e3003b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 00:25:09 -0800 Subject: [PATCH 468/685] mici setup: get time immediately after internet (#37565) * should be instant * guard on disconnect * just time fix --- system/ui/mici_setup.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index c1474728c7b..5a0b751d3b2 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os import re +import ssl import threading import time import urllib.request @@ -16,6 +17,7 @@ from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.common.swaglog import cloudlog +from openpilot.common.time_helpers import system_time_valid from openpilot.common.utils import run_cmd from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.wifi_manager import WifiManager @@ -55,6 +57,7 @@ def __init__(self, should_check: Callable[[], bool] | None = None): self.wifi_connected = threading.Event() self._should_check = should_check or (lambda: True) self._stop_event = threading.Event() + self._last_timesyncd_restart = 0.0 self._thread: threading.Thread | None = None def start(self): @@ -82,6 +85,13 @@ def _run(self): self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() + except urllib.error.URLError as e: + if (isinstance(e.reason, ssl.SSLCertVerificationError) and + not system_time_valid() and + time.monotonic() - self._last_timesyncd_restart > 5): + self._last_timesyncd_restart = time.monotonic() + run_cmd(["sudo", "systemctl", "restart", "systemd-timesyncd"]) + self.reset() except Exception: self.reset() else: From 3a19f85512d4dff7f9c5bc824fda2105a4d33fa7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 01:04:16 -0800 Subject: [PATCH 469/685] WifiManager: guard AP paths failure --- system/ui/lib/wifi_manager.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/system/ui/lib/wifi_manager.py b/system/ui/lib/wifi_manager.py index 4820c7aaba7..d3c855d9bca 100644 --- a/system/ui/lib/wifi_manager.py +++ b/system/ui/lib/wifi_manager.py @@ -854,8 +854,12 @@ def worker(): # NOTE: AccessPoints property may exclude hidden APs (use GetAllAccessPoints method if needed) wifi_addr = DBusAddress(self._wifi_device, NM, interface=NM_WIRELESS_IFACE) - wifi_props = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()).body[0] - ap_paths = wifi_props.get('AccessPoints', ('ao', []))[1] + wifi_props_reply = self._router_main.send_and_get_reply(Properties(wifi_addr).get_all()) + if wifi_props_reply.header.message_type == MessageType.error: + cloudlog.warning(f"Failed to get WiFi properties: {wifi_props_reply}") + return + + ap_paths = wifi_props_reply.body[0].get('AccessPoints', ('ao', []))[1] aps: dict[str, list[AccessPoint]] = {} From d801cebb2e6e9a1d0931522cb2e86ba9d481b8a7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 01:23:29 -0800 Subject: [PATCH 470/685] mici setup: guard continue button when forgetting/connecting (#37568) * test * fix * test * too much * simple to ship * revert * bug free * simpler * fix * even safer guard --- .../mici/layouts/settings/network/wifi_ui.py | 9 ++++++ system/ui/mici_setup.py | 31 +++++++++++++------ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 9cd4db7bf0d..02b561d81e7 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -110,6 +110,10 @@ def update_network(self, network: Network): if self._is_connected or self._is_connecting: self._wrong_password = False + @property + def network_forgetting(self) -> bool: + return self._network_forgetting + def _forget_network(self): if self._network_forgetting: return @@ -286,6 +290,11 @@ def __init__(self, wifi_manager: WifiManager): networks_updated=self._on_network_updated, ) + @property + def any_network_forgetting(self) -> bool: + # TODO: deactivate before forget and add DISCONNECTING state + return any(btn.network_forgetting for btn in self._scroller.items if isinstance(btn, WifiButton)) + def show_event(self): # Clear scroller items and update from latest scan results super().show_event() diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 5a0b751d3b2..27022877060 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -20,7 +20,7 @@ from openpilot.common.time_helpers import system_time_valid from openpilot.common.utils import run_cmd from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.lib.wifi_manager import WifiManager +from openpilot.system.ui.lib.wifi_manager import WifiManager, ConnectStatus from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import UnifiedLabel @@ -55,6 +55,7 @@ class NetworkConnectivityMonitor: def __init__(self, should_check: Callable[[], bool] | None = None): self.network_connected = threading.Event() self.wifi_connected = threading.Event() + self.recheck_event = threading.Event() self._should_check = should_check or (lambda: True) self._stop_event = threading.Event() self._last_timesyncd_restart = 0.0 @@ -76,12 +77,21 @@ def reset(self): self.network_connected.clear() self.wifi_connected.clear() + def invalidate(self): + self.recheck_event.set() + def _run(self): while not self._stop_event.is_set(): if self._should_check(): try: request = urllib.request.Request(OPENPILOT_URL, method="HEAD") urllib.request.urlopen(request, timeout=2.0) + + # Discard stale result if invalidated during request + if self.recheck_event.is_set(): + self.recheck_event.clear() + continue + self.network_connected.set() if HARDWARE.get_network_type() == NetworkType.wifi: self.wifi_connected.set() @@ -392,7 +402,17 @@ def show_event(self): def _nav_stack_tick(self): self._wifi_manager.process_callbacks() - has_internet = self._network_monitor.network_connected.is_set() + # Discard stale poll results while network state is changing + network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING + if network_changing: + self._network_monitor.invalidate() + + has_internet = (self._network_monitor.network_connected.is_set() and + not network_changing and + not self._network_monitor.recheck_event.is_set()) + self._continue_button.set_visible(has_internet) + self._waiting_button.set_visible(not has_internet) + if has_internet and not self._prev_has_internet: self._pending_has_internet_scroll = True self._prev_has_internet = has_internet @@ -432,13 +452,6 @@ def _update_state(self): self._pending_wifi_grow_animation = False self._wifi_button.trigger_grow_animation() - if self._network_monitor.network_connected.is_set(): - self._continue_button.set_visible(True) - self._waiting_button.set_visible(False) - else: - self._continue_button.set_visible(False) - self._waiting_button.set_visible(True) - class NetworkSetupPage(NetworkSetupPageBase, NavScroller): def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callback: Callable[[bool], None], From 41bba2b55ae7b915a6ce4b356ae2837fbf4d7da2 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 02:11:23 -0800 Subject: [PATCH 471/685] mici setup: fix race on disconnect guard --- system/ui/mici_setup.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 27022877060..cc65fc4bb08 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -79,6 +79,7 @@ def reset(self): def invalidate(self): self.recheck_event.set() + self.reset() def _run(self): while not self._stop_event.is_set(): @@ -400,9 +401,8 @@ def show_event(self): self._pending_wifi_grow_animation = False def _nav_stack_tick(self): - self._wifi_manager.process_callbacks() - - # Discard stale poll results while network state is changing + # Check network state before processing callbacks so forgetting flag + # is still set on the frame the forgotten callback fires network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING if network_changing: self._network_monitor.invalidate() @@ -413,6 +413,8 @@ def _nav_stack_tick(self): self._continue_button.set_visible(has_internet) self._waiting_button.set_visible(not has_internet) + self._wifi_manager.process_callbacks() + if has_internet and not self._prev_has_internet: self._pending_has_internet_scroll = True self._prev_has_internet = has_internet From 4a1101c032abc8c49ad6e67d998af09a3493e12c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 02:54:24 -0800 Subject: [PATCH 472/685] mici setup: don't run network tick while not in network setup page --- system/ui/mici_setup.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index cc65fc4bb08..186f8e58e7a 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -401,6 +401,11 @@ def show_event(self): self._pending_wifi_grow_animation = False def _nav_stack_tick(self): + # Only run tick when this page or its WiFi UI is on the stack + if gui_app.get_active_widget() is not self and not gui_app.widget_in_stack(self._wifi_ui): + self._wifi_manager.process_callbacks() + return + # Check network state before processing callbacks so forgetting flag # is still set on the frame the forgotten callback fires network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING @@ -425,7 +430,7 @@ def _nav_stack_tick(self): if elapsed > 0.5: self._pending_has_internet_scroll = False - def scroll_to_download(): + def scroll_to_end(): self._scroller._layout() end_offset = -(self._scroller.content_size - self._rect.width) remaining = self._scroller.scroll_panel.get_offset() - end_offset @@ -433,7 +438,7 @@ def scroll_to_download(): self._pending_continue_grow_animation = True # Animate WifiUi down first before scroll - gui_app.pop_widgets_to(self, scroll_to_download) + gui_app.pop_widgets_to(self, scroll_to_end) def set_custom_software(self, custom_software: bool): self._custom_software = custom_software From 2d53f4cf019df478542493a6f92722187947c3a1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 03:36:37 -0800 Subject: [PATCH 473/685] WifiUi: re-sort buttons on show (#37570) sort --- .../mici/layouts/settings/network/wifi_ui.py | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 02b561d81e7..54834debc8f 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -296,19 +296,17 @@ def any_network_forgetting(self) -> bool: return any(btn.network_forgetting for btn in self._scroller.items if isinstance(btn, WifiButton)) def show_event(self): - # Clear scroller items and update from latest scan results + # Re-sort scroller items and update from latest scan results super().show_event() self._wifi_manager.set_active(True) - self._scroller.items.clear() - self._scroller.add_widget(self._scanning_btn) - # trigger button update on latest sorted networks - self._on_network_updated(self._wifi_manager.networks) + self._networks = {n.ssid: n for n in self._wifi_manager.networks} + self._update_buttons(re_sort=True) def _on_network_updated(self, networks: list[Network]): self._networks = {network.ssid: network for network in networks} self._update_buttons() - def _update_buttons(self): + def _update_buttons(self, re_sort: bool = False): # Update existing buttons, add new ones to the end existing = {btn.network.ssid: btn for btn in self._scroller.items if isinstance(btn, WifiButton)} @@ -320,15 +318,22 @@ def _update_buttons(self): btn.set_click_callback(lambda ssid=network.ssid: self._connect_to_network(ssid)) self._scroller.add_widget(btn) + if re_sort: + # Remove stale buttons and sort to match scan order, preserving eager state + btn_map = {btn.network.ssid: btn for btn in self._scroller.items if isinstance(btn, WifiButton)} + self._scroller.items[:] = [btn_map[ssid] for ssid in self._networks if ssid in btn_map] + else: + # Mark networks no longer in scan results (display handled by _update_state) + for btn in self._scroller.items: + if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: + btn.set_network_missing(True) + # Keep scanning button at the end items = self._scroller.items if self._scanning_btn in items: items.append(items.pop(items.index(self._scanning_btn))) - - # Mark networks no longer in scan results (display handled by _update_state) - for btn in self._scroller.items: - if isinstance(btn, WifiButton) and btn.network.ssid not in self._networks: - btn.set_network_missing(True) + else: + self._scroller.add_widget(self._scanning_btn) def _connect_with_password(self, ssid: str, password: str): self._wifi_manager.connect_to_network(ssid, password) From b4b747e5cb005b136aefedd1030c62536981db48 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 04:48:30 -0800 Subject: [PATCH 474/685] mici scroller: fix scroll bar direction with less content than viewport (#37571) fix --- system/ui/widgets/scroller.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index c48be6b80b1..5becef79395 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -47,7 +47,7 @@ def _render(self, _): # position based on scroll ratio slide_range = self._viewport.width - indicator_w max_scroll = self._content_size - self._viewport.width - scroll_ratio = -self._scroll_offset / max_scroll + scroll_ratio = (-self._scroll_offset / abs(max_scroll)) if abs(max_scroll) > 1e-3 else 0.0 x = self._viewport.x + scroll_ratio * slide_range # don't bounce up when NavWidget shows y = max(self._viewport.y, 0) + self._viewport.height - self._txt_scroll_indicator.height / 2 From 6922d587626ec80db1ce1ae5515738164b15fd39 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 04:58:18 -0800 Subject: [PATCH 475/685] mici setup: swipe down on wifi connect, then wait for internet (#37569) * try this * try this * fix * delay hide on wifi/internet * 0.5 * fix flash on forgetting * also reset * fix * todo * dupl * wifi after * bring back cmts * fix spotty internet check while downloading! * cmt * cmt * todo * resort * more delay * redundtant * nl * scroll over for wifi (waiting) OR internet (continue) * fix scroll * fix scroll * show_event fully manages its scroll over, not some weiird delay mixed with other triggers via fake rising edge * instant if not popping * cmt --- system/ui/mici_setup.py | 72 +++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 186f8e58e7a..c2034ffda73 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -355,7 +355,6 @@ def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callbac self._wifi_manager.set_active(True) self._network_monitor = network_monitor self._custom_software = False - self._prev_has_internet = False self._wifi_ui = WifiUIMici(self._wifi_manager) self._connect_button = GreyBigButton("connect to\ninternet", "swipe down to go back", @@ -365,8 +364,9 @@ def __init__(self, network_monitor: NetworkConnectivityMonitor, continue_callbac self._wifi_button = WifiNetworkButton(self._wifi_manager) self._wifi_button.set_click_callback(lambda: gui_app.push_widget(self._wifi_ui)) - self._show_time = 0.0 - self._pending_has_internet_scroll = False + self._prev_has_internet = False + self._prev_wifi_connected = False + self._pending_has_internet_scroll: float | None = None # stores time to use as delay self._pending_continue_grow_animation = False self._pending_wifi_grow_animation = False @@ -394,20 +394,17 @@ def show_event(self): super().show_event() # make sure we populate strength and ip immediately if already have wifi self._wifi_manager.set_active(True) - self._show_time = rl.get_time() - self._prev_has_internet = False - self._pending_has_internet_scroll = False + self._prev_has_internet = self._has_internet + self._prev_wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED + self._pending_has_internet_scroll = None self._pending_continue_grow_animation = False self._pending_wifi_grow_animation = False - def _nav_stack_tick(self): - # Only run tick when this page or its WiFi UI is on the stack - if gui_app.get_active_widget() is not self and not gui_app.widget_in_stack(self._wifi_ui): - self._wifi_manager.process_callbacks() - return + if self._prev_has_internet or self._prev_wifi_connected: + self.set_shown_callback(lambda: self._scroll_to_end_and_grow()) - # Check network state before processing callbacks so forgetting flag - # is still set on the frame the forgotten callback fires + @property + def _has_internet(self) -> bool: network_changing = self._wifi_ui.any_network_forgetting or self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTING if network_changing: self._network_monitor.invalidate() @@ -415,30 +412,49 @@ def _nav_stack_tick(self): has_internet = (self._network_monitor.network_connected.is_set() and not network_changing and not self._network_monitor.recheck_event.is_set()) + return has_internet + + def _nav_stack_tick(self): + # Only run tick when this page or its WiFi UI is on the stack + if gui_app.get_active_widget() is not self and not gui_app.widget_in_stack(self._wifi_ui): + self._wifi_manager.process_callbacks() + return + + # Check network state before processing callbacks so forgetting flag + # is still set on the frame the forgotten callback fires + has_internet = self._has_internet self._continue_button.set_visible(has_internet) self._waiting_button.set_visible(not has_internet) + # TODO: fire show/hide events on visibility changes + if not has_internet: + self._pending_continue_grow_animation = False + self._wifi_manager.process_callbacks() - if has_internet and not self._prev_has_internet: - self._pending_has_internet_scroll = True + # Dismiss WiFi UI and scroll on WiFi connect or internet gain + wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED + if (has_internet and not self._prev_has_internet) or (wifi_connected and not self._prev_wifi_connected): + # TODO: cancel if connect is transient + self._pending_has_internet_scroll = rl.get_time() + self._prev_has_internet = has_internet + self._prev_wifi_connected = wifi_connected - if self._pending_has_internet_scroll: + if self._pending_has_internet_scroll is not None: # Scrolls over to continue button, then grows once in view - elapsed = rl.get_time() - self._show_time - if elapsed > 0.5: - self._pending_has_internet_scroll = False - - def scroll_to_end(): - self._scroller._layout() - end_offset = -(self._scroller.content_size - self._rect.width) - remaining = self._scroller.scroll_panel.get_offset() - end_offset - self._scroller.scroll_to(remaining, smooth=True, block_interaction=True) - self._pending_continue_grow_animation = True - + elapsed = rl.get_time() - self._pending_has_internet_scroll + if elapsed > 0.7 or gui_app.get_active_widget() is self: # instant scroll + grow if not popping # Animate WifiUi down first before scroll - gui_app.pop_widgets_to(self, scroll_to_end) + self._pending_has_internet_scroll = None + gui_app.pop_widgets_to(self, self._scroll_to_end_and_grow) + + def _scroll_to_end_and_grow(self): + self._scroller._layout() + end_offset = -(self._scroller.content_size - self._rect.width) + remaining = self._scroller.scroll_panel.get_offset() - end_offset + self._scroller.scroll_to(remaining, smooth=True, block_interaction=True) + self._pending_continue_grow_animation = True def set_custom_software(self, custom_software: bool): self._custom_software = custom_software From 93eb8418b72d45dcf13f867548345980a3e5ecc6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 05:54:44 -0800 Subject: [PATCH 476/685] Zip app updater (#37572) replace --- system/hardware/tici/updater_magic | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index b4f776565d0..8bf150744aa 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d376e7aa4bb44bf78db9965ff55a5362ac823335b8d2a81dd3aebc5c8c2f239f -size 71214189 +oid sha256:3236a08d318c26022e536fa76627ca15c0517f050f4c4e91a1aafc5bfefb44d0 +size 71240680 From 118d903e2dd04f146261d2fe1d775b3a89bcd39d Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 06:04:01 -0800 Subject: [PATCH 477/685] mici ui: slim review terms (#37573) * replace * fix --- selfdrive/ui/mici/layouts/onboarding.py | 9 ++++++--- selfdrive/ui/mici/layouts/settings/device.py | 8 ++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index cf633192ea9..557a8de8b2b 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -337,13 +337,16 @@ def show_decline_dialog(): self._decline_button = BigCircleButton("icons_mici/setup/cancel.png", red=True) self._decline_button.set_click_callback(show_decline_dialog) + self._terms_header = GreyBigButton("terms and\nconditions", "scroll to continue", + gui_app.texture("icons_mici/setup/green_info.png", 64, 64)) + self._must_accept_card = GreyBigButton("", "You must accept the Terms & Conditions to use openpilot.") + self._scroller.add_widgets([ - GreyBigButton("terms and\nconditions", "scroll to continue", - gui_app.texture("icons_mici/setup/green_info.png", 64, 64)), + self._terms_header, GreyBigButton("swipe for QR code", "or go to https://comma.ai/terms", gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 56, flip_x=True)), QRCodeWidget("https://comma.ai/terms"), - GreyBigButton("", "You must accept the Terms & Conditions to use openpilot."), + self._must_accept_card, self._accept_button, self._decline_button, ]) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index ed29a6a84ac..78ec67ccb9d 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -13,7 +13,6 @@ from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage -from openpilot.system.ui.mici_setup import BigPillButton from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget @@ -27,13 +26,11 @@ class ReviewTermsPage(TermsPage, NavScroller): """TermsPage with NavWidget swipe-to-dismiss for reviewing in device settings.""" def __init__(self): super().__init__(on_accept=self.dismiss, on_decline=self.dismiss) + self._terms_header.set_visible(False) + self._must_accept_card.set_visible(False) self._accept_button.set_visible(False) self._decline_button.set_visible(False) - close_button = BigPillButton("close") - close_button.set_click_callback(self.dismiss) - self._scroller.add_widget(close_button) - class ReviewTrainingGuide(TrainingGuide): def show_event(self): @@ -340,7 +337,6 @@ def uninstall_openpilot_callback(): terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png") terms_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTermsPage())) - terms_btn.set_enabled(lambda: ui_state.is_offroad()) self._scroller.add_widgets([ DeviceInfoLayoutMici(), From 5303afb0dcc9c9a599ccca3abf93a959e68f00e3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 5 Mar 2026 07:20:50 -0800 Subject: [PATCH 478/685] mici installer: bring back finishing setup (#37574) need this :( --- selfdrive/ui/installer/installer.cc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 9c35b0465e7..75994541946 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -81,14 +81,16 @@ void run(const char* cmd) { } void finishInstall() { - if (tici_device) { - BeginDrawing(); - ClearBackground(BLACK); + BeginDrawing(); + ClearBackground(BLACK); + if (tici_device) { const char *m = "Finishing install..."; int text_width = MeasureText(m, FONT_SIZE); DrawTextEx(font_display, m, (Vector2){(float)(GetScreenWidth() - text_width)/2 + FONT_SIZE, (float)(GetScreenHeight() - FONT_SIZE)/2}, FONT_SIZE, 0, WHITE); - EndDrawing(); - } + } else { + DrawTextEx(font_display, "finishing setup", (Vector2){12, 0}, 77, 0, (Color){255, 255, 255, (unsigned char)(255 * 0.9)}); + } + EndDrawing(); util::sleep_for(60 * 1000); } From 363735f7ce936755c3c39c35a5e131c280a6fa0f Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Thu, 5 Mar 2026 09:38:51 -0800 Subject: [PATCH 479/685] Update RELEASES.md --- RELEASES.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index 895dcbba7a2..d6d22d48913 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,8 @@ Version 0.10.4 (2026-02-17) ======================== +* New driving model #36798 + * Fully trained using a learned simulator + * Improved longitudinal performance in experimental mode * Kia K7 2017 support thanks to royjr! * Lexus LS 2018 support thanks to Hacheoy! * Reduce comma four standby power usage by 77% to 52 mW From ac1dd692af83febeaf30ae2f4f66d4e430315f72 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 6 Mar 2026 17:18:41 -0800 Subject: [PATCH 480/685] ui: fix BigButton shake on startup (#37577) _shake_start defaults to None, but `None or 0.0` treated it as time zero, so any button rendered within 0.5s of window creation would play the shake animation. Co-authored-by: Claude Opus 4.6 --- selfdrive/ui/mici/widgets/button.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 5559a1181c9..18413dbc3aa 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -194,7 +194,9 @@ def _shake_offset(self) -> float: SHAKE_DURATION = 0.5 SHAKE_AMPLITUDE = 24.0 SHAKE_FREQUENCY = 32.0 - t = rl.get_time() - (self._shake_start or 0.0) + if self._shake_start is None: + return 0.0 + t = rl.get_time() - self._shake_start if t > SHAKE_DURATION: return 0.0 decay = 1.0 - t / SHAKE_DURATION From 4651bc6a1f57886294bbbdb3a10912aef82d7c41 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 17:33:50 -0800 Subject: [PATCH 481/685] ui: rename BigConfirmationDialogV2 (#37578) * ui: rename BigConfirmationDialogV2 * clean up --- selfdrive/ui/mici/layouts/onboarding.py | 16 ++++++++-------- selfdrive/ui/mici/layouts/settings/device.py | 8 ++++---- .../ui/mici/layouts/settings/network/wifi_ui.py | 8 +++----- selfdrive/ui/mici/tests/test_widget_leaks.py | 6 +++--- selfdrive/ui/mici/widgets/dialog.py | 2 +- system/ui/mici_reset.py | 14 +++++++------- system/ui/mici_setup.py | 6 +++--- system/ui/widgets/slider.py | 2 +- 8 files changed, 30 insertions(+), 32 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 557a8de8b2b..3a3625d714b 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -15,7 +15,7 @@ from openpilot.system.version import terms_version, training_version from openpilot.selfdrive.ui.ui_state import ui_state, device from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog @@ -220,15 +220,15 @@ def on_accept(): ui_state.params.put_bool_nonblocking("RecordFront", True) continue_callback() - gui_app.push_widget(BigConfirmationDialogV2("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", exit_on_confirm=False, - confirm_callback=on_accept)) + gui_app.push_widget(BigConfirmationDialog("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", exit_on_confirm=False, + confirm_callback=on_accept)) def show_decline_dialog(): def on_decline(): ui_state.params.put_bool_nonblocking("RecordFront", False) continue_callback() - gui_app.push_widget(BigConfirmationDialogV2("no, don't upload", "icons_mici/setup/cancel.png", exit_on_confirm=False, confirm_callback=on_decline)) + gui_app.push_widget(BigConfirmationDialog("no, don't upload", "icons_mici/setup/cancel.png", exit_on_confirm=False, confirm_callback=on_decline)) self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") self._accept_button.set_click_callback(show_accept_dialog) @@ -324,12 +324,12 @@ def __init__(self, on_accept, on_decline): super().__init__() def show_accept_dialog(): - gui_app.push_widget(BigConfirmationDialogV2("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", - confirm_callback=on_accept)) + gui_app.push_widget(BigConfirmationDialog("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", + confirm_callback=on_accept)) def show_decline_dialog(): - gui_app.push_widget(BigConfirmationDialogV2("decline &\nuninstall", "icons_mici/setup/cancel.png", - red=True, exit_on_confirm=False, confirm_callback=on_decline)) + gui_app.push_widget(BigConfirmationDialog("decline &\nuninstall", "icons_mici/setup/cancel.png", + red=True, exit_on_confirm=False, confirm_callback=on_decline)) self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") self._accept_button.set_click_callback(show_accept_dialog) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 78ec67ccb9d..0bc1ac2958e 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -9,7 +9,7 @@ from openpilot.common.time_helpers import system_time_valid from openpilot.system.ui.widgets.scroller import NavRawScrollPanel, NavScroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton -from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage @@ -85,9 +85,9 @@ def confirm_callback(): # TODO: check icon = "icons_mici/settings/comma_icon.png" - dlg: BigConfirmationDialogV2 | BigDialog = BigConfirmationDialogV2(f"slide to\n{action_text.lower()}", icon, red=red, - exit_on_confirm=action_text == "reset", - confirm_callback=confirm_callback) + dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, red=red, + exit_on_confirm=action_text == "reset", + confirm_callback=confirm_callback) gui_app.push_widget(dlg) else: dlg = BigDialog(f"Disengage to {action_text}", "") diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 54834debc8f..cda3cb365ac 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -4,7 +4,7 @@ from collections.abc import Callable from openpilot.common.swaglog import cloudlog -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.button import BigButton, LABEL_COLOR from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.widgets import Widget @@ -246,8 +246,8 @@ def __init__(self, forget_network: Callable): def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) - dlg = BigConfirmationDialogV2("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, - confirm_callback=self._forget_network) + dlg = BigConfirmationDialog("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, + confirm_callback=self._forget_network) gui_app.push_widget(dlg) def _render(self, _): @@ -390,5 +390,3 @@ def _update_state(self): super()._update_state() self._move_network_to_front(self._wifi_manager.wifi_state.ssid) - - diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index ea7af84293a..c1065659f1c 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -10,7 +10,7 @@ from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide as MiciTrainingGuide, OnboardingWindow as MiciOnboardingWindow from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog as MiciDriverCameraDialog from openpilot.selfdrive.ui.mici.widgets.pairing_dialog import PairingDialog as MiciPairingDialog -from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialogV2, BigInputDialog +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationDialog, BigInputDialog from openpilot.selfdrive.ui.mici.layouts.settings.device import MiciFccModal # tici dialogs @@ -44,7 +44,7 @@ "openpilot.system.ui.widgets.scroller_tici.Scroller", "openpilot.system.ui.widgets.label.UnifiedLabel", "openpilot.system.ui.widgets.mici_keyboard.MiciKeyboard", - "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialogV2", + "openpilot.selfdrive.ui.mici.widgets.dialog.BigConfirmationDialog", "openpilot.system.ui.widgets.keyboard.Keyboard", "openpilot.system.ui.widgets.slider.BigSlider", "openpilot.selfdrive.ui.mici.widgets.dialog.BigInputDialog", @@ -72,7 +72,7 @@ def test_dialogs_do_not_leak(): lambda: MiciTrainingGuide(lambda: None), lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), - lambda: BigConfirmationDialogV2("test", "icons_mici/settings/network/new/trash.png"), + lambda: BigConfirmationDialog("test", "icons_mici/settings/network/new/trash.png"), lambda: BigInputDialog("test"), lambda: MiciFccModal(text="test"), # tici diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index b2662d8a3bf..387f4c02d07 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -63,7 +63,7 @@ def _render(self, _): alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) -class BigConfirmationDialogV2(BigDialogBase): +class BigConfirmationDialog(BigDialogBase): def __init__(self, title: str, icon: str, red: bool = False, exit_on_confirm: bool = True, confirm_callback: Callable | None = None): diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 5cbba5e8efd..92cd7e7cb89 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -11,7 +11,7 @@ from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" @@ -71,18 +71,18 @@ def __init__(self, mode): self._reset_failed_page = ResetFailedPage() def show_confirm_dialog(): - dialog = BigConfirmationDialogV2("erase\ndevice", "icons_mici/settings/device/uninstall.png", red=True, - confirm_callback=self.start_reset) + dialog = BigConfirmationDialog("erase\ndevice", "icons_mici/settings/device/uninstall.png", red=True, + confirm_callback=self.start_reset) gui_app.push_widget(dialog) def show_cancel_dialog(): - dialog = BigConfirmationDialogV2("normal\nstartup", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=gui_app.request_close) + dialog = BigConfirmationDialog("normal\nstartup", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=gui_app.request_close) gui_app.push_widget(dialog) def show_reboot_dialog(): - dialog = BigConfirmationDialogV2("reboot\ndevice", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + dialog = BigConfirmationDialog("reboot\ndevice", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=HARDWARE.reboot) gui_app.push_widget(dialog) self._reset_button = BigCircleButton("icons_mici/settings/device/uninstall.png", red=True) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index c2034ffda73..5e9d34c658d 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -28,7 +28,7 @@ from openpilot.system.ui.widgets.slider import LargerSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialog from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton NetworkType = log.DeviceState.NetworkType @@ -248,8 +248,8 @@ def __init__(self, retry_callback: Callable | None, title: str = "download faile self.set_back_callback(retry_callback) def show_reboot_dialog(): - dialog = BigConfirmationDialogV2("slide to reboot", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + dialog = BigConfirmationDialog("slide to reboot", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=HARDWARE.reboot) gui_app.push_widget(dialog) reboot_button = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 8f4bbfc0112..57786565e29 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -13,7 +13,7 @@ class SmallSlider(Widget): CONFIRM_DELAY = 0.2 def __init__(self, title: str, confirm_callback: Callable | None = None): - # TODO: unify this with BigConfirmationDialogV2 + # TODO: unify this with BigConfirmationDialog super().__init__() self._confirm_callback = confirm_callback From af1fb2644ebb7d4a7ba825f36f8c0f46ff9ffd57 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 18:17:26 -0800 Subject: [PATCH 482/685] mici ui: remove unused widgets (#37579) * remove small buttons! * remove those assets --- .../icons_mici/setup/medium_button_bg.png | 3 - .../setup/medium_button_pressed_bg.png | 3 - .../icons_mici/setup/reset/small_button.png | 3 - .../setup/reset/small_button_pressed.png | 3 - .../icons_mici/setup/reset/wide_button.png | 3 - .../setup/reset/wide_button_pressed.png | 3 - .../icons_mici/setup/small_red_pill.png | 3 - .../setup/small_red_pill_pressed.png | 3 - .../icons_mici/setup/smaller_button.png | 3 - .../setup/smaller_button_disabled.png | 3 - .../setup/smaller_button_pressed.png | 3 - .../assets/icons_mici/setup/widish_button.png | 3 - .../setup/widish_button_disabled.png | 3 - .../setup/widish_button_pressed.png | 3 - system/ui/widgets/button.py | 81 +------------------ 15 files changed, 1 insertion(+), 122 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/setup/medium_button_bg.png delete mode 100644 selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png delete mode 100644 selfdrive/assets/icons_mici/setup/reset/small_button.png delete mode 100644 selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png delete mode 100644 selfdrive/assets/icons_mici/setup/reset/wide_button.png delete mode 100644 selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_red_pill.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png delete mode 100644 selfdrive/assets/icons_mici/setup/smaller_button.png delete mode 100644 selfdrive/assets/icons_mici/setup/smaller_button_disabled.png delete mode 100644 selfdrive/assets/icons_mici/setup/smaller_button_pressed.png delete mode 100644 selfdrive/assets/icons_mici/setup/widish_button.png delete mode 100644 selfdrive/assets/icons_mici/setup/widish_button_disabled.png delete mode 100644 selfdrive/assets/icons_mici/setup/widish_button_pressed.png diff --git a/selfdrive/assets/icons_mici/setup/medium_button_bg.png b/selfdrive/assets/icons_mici/setup/medium_button_bg.png deleted file mode 100644 index e79dc2eb588..00000000000 --- a/selfdrive/assets/icons_mici/setup/medium_button_bg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9e363a79dc35ca4c4e9efaa6a843d37ad219efa5299d3e538d8249affa230096 -size 7935 diff --git a/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png b/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png deleted file mode 100644 index e52fb0c17d0..00000000000 --- a/selfdrive/assets/icons_mici/setup/medium_button_pressed_bg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cc6fb48520143b6fa1f060d8212e6d929917ab616ce943b5fab5a60665f00da5 -size 18225 diff --git a/selfdrive/assets/icons_mici/setup/reset/small_button.png b/selfdrive/assets/icons_mici/setup/reset/small_button.png deleted file mode 100644 index e3f58b1078c..00000000000 --- a/selfdrive/assets/icons_mici/setup/reset/small_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7a198f13f30b3dbc09f30d7fd8033a0bc07a0da9b010b7ca6ed2678430c9e5b4 -size 6949 diff --git a/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png b/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png deleted file mode 100644 index 5b502e00aa9..00000000000 --- a/selfdrive/assets/icons_mici/setup/reset/small_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:75289d004709def2a2d6101a0330ec867895068ec3807aefc2a26d423d907a13 -size 13437 diff --git a/selfdrive/assets/icons_mici/setup/reset/wide_button.png b/selfdrive/assets/icons_mici/setup/reset/wide_button.png deleted file mode 100644 index 3892f6eb8cc..00000000000 --- a/selfdrive/assets/icons_mici/setup/reset/wide_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2452aaf59da18be1b74b475851d66e5c73c50aa49820419a288b1fdb7b42dee1 -size 9071 diff --git a/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png b/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png deleted file mode 100644 index 3a34af88467..00000000000 --- a/selfdrive/assets/icons_mici/setup/reset/wide_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6478f7c1c5ef2013e94fc4218ab370889883c5c12231ba3e0975874cb0b6fec9 -size 21893 diff --git a/selfdrive/assets/icons_mici/setup/small_red_pill.png b/selfdrive/assets/icons_mici/setup/small_red_pill.png deleted file mode 100644 index 4a7db930a0b..00000000000 --- a/selfdrive/assets/icons_mici/setup/small_red_pill.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3a336afddad80dc91caca91d54bd29897ce491f180374edf9a5ba517cbc00e9 -size 8765 diff --git a/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png b/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png deleted file mode 100644 index a8d51960c41..00000000000 --- a/selfdrive/assets/icons_mici/setup/small_red_pill_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:8eee9f10ca80a4e6100c00c02bb46aa5f253b14b086ab9982cfa85ee94eec162 -size 22512 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button.png b/selfdrive/assets/icons_mici/setup/smaller_button.png deleted file mode 100644 index 9b4851c5689..00000000000 --- a/selfdrive/assets/icons_mici/setup/smaller_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:89ca7e6bb01dfa78300126ce828cb2a64e7a2e68e1e9152de242f57a36d0e57a -size 8604 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png b/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png deleted file mode 100644 index 6514791de75..00000000000 --- a/selfdrive/assets/icons_mici/setup/smaller_button_disabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b3242a411b559f1d0308f189fe0d25b81d6c7d964ca418a0c599a1bab4bffcbb -size 5341 diff --git a/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png b/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png deleted file mode 100644 index 64235b3a2f6..00000000000 --- a/selfdrive/assets/icons_mici/setup/smaller_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d354651c0c8107dcc5f599777d260f53ef1901123315785ed8190466166cdce8 -size 17554 diff --git a/selfdrive/assets/icons_mici/setup/widish_button.png b/selfdrive/assets/icons_mici/setup/widish_button.png deleted file mode 100644 index 529b7c80cca..00000000000 --- a/selfdrive/assets/icons_mici/setup/widish_button.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:74fc21132b1e761ea54ce64617730c6ee79d01668244ab555b3b89870cfea181 -size 7112 diff --git a/selfdrive/assets/icons_mici/setup/widish_button_disabled.png b/selfdrive/assets/icons_mici/setup/widish_button_disabled.png deleted file mode 100644 index 5028a8cd21e..00000000000 --- a/selfdrive/assets/icons_mici/setup/widish_button_disabled.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:9728423bd5e3197ef02d62e4bae415e6694aab875ca8630ffc9f188c38e18e5f -size 4141 diff --git a/selfdrive/assets/icons_mici/setup/widish_button_pressed.png b/selfdrive/assets/icons_mici/setup/widish_button_pressed.png deleted file mode 100644 index 1095d4fc239..00000000000 --- a/selfdrive/assets/icons_mici/setup/widish_button_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0ff179f93f421edcb503ca5c22a12b37e3a2aaabc414bf90f57e20ff5255dd75 -size 15572 diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index a608344710a..60f9e60735b 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -5,7 +5,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.label import Label, UnifiedLabel +from openpilot.system.ui.widgets.label import Label from openpilot.common.filter_simple import FirstOrderFilter @@ -223,82 +223,3 @@ def _render(self, _): icon_x = self.rect.x + (self.rect.width - self._icon_txt.width) / 2 icon_y = self.rect.y + (self.rect.height - self._icon_txt.height) / 2 rl.draw_texture(self._icon_txt, int(icon_x), int(icon_y), icon_white) - - -class SmallButton(Widget): - def __init__(self, text: str): - super().__init__() - self._click_delay = 0.075 - self._opacity_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) - - self._load_assets() - - self._label = UnifiedLabel(text, 36, font_weight=FontWeight.SEMI_BOLD, - text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) - - self._bg_disabled_txt = None - - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 194, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/reset/small_button.png", 194, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/small_button_pressed.png", 194, 100) - - def set_text(self, text: str): - self._label.set_text(text) - - def set_opacity(self, opacity: float, smooth: bool = False): - if smooth: - self._opacity_filter.update(opacity) - else: - self._opacity_filter.x = opacity - - def _render(self, _): - if not self.enabled and self._bg_disabled_txt is not None: - rl.draw_texture(self._bg_disabled_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) - elif self.is_pressed: - rl.draw_texture(self._bg_pressed_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) - else: - rl.draw_texture(self._bg_txt, int(self.rect.x), int(self.rect.y), rl.Color(255, 255, 255, int(255 * self._opacity_filter.x))) - - opacity = 0.9 if self.enabled else 0.35 - self._label.set_color(rl.Color(255, 255, 255, int(255 * opacity * self._opacity_filter.x))) - self._label.render(self._rect) - - -class SmallRedPillButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 194, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/small_red_pill.png", 194, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/small_red_pill_pressed.png", 194, 100) - - -class SmallerRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 150, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/smaller_button.png", 150, 100) - self._bg_disabled_txt = gui_app.texture("icons_mici/setup/smaller_button_disabled.png", 150, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/smaller_button_pressed.png", 150, 100) - - -class WideRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 316, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/medium_button_bg.png", 316, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/medium_button_pressed_bg.png", 316, 100) - - -class WidishRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 250, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/widish_button.png", 250, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/widish_button_pressed.png", 250, 100) - self._bg_disabled_txt = gui_app.texture("icons_mici/setup/widish_button_disabled.png", 250, 100) - - -class FullRoundedButton(SmallButton): - def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 520, 100)) - self._bg_txt = gui_app.texture("icons_mici/setup/reset/wide_button.png", 520, 100) - self._bg_pressed_txt = gui_app.texture("icons_mici/setup/reset/wide_button_pressed.png", 520, 100) From 60ec7dc7b623bb057bdaf2a369a759ea106ec1a5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 18:33:26 -0800 Subject: [PATCH 483/685] Remove unused icons --- selfdrive/assets/icons_mici/onroad/bookmark_fill.png | 3 --- selfdrive/assets/icons_mici/settings/device/language.png | 3 --- selfdrive/assets/icons_mici/setup/back_new.png | 3 --- selfdrive/assets/icons_mici/setup/scroll_down_indicator.png | 3 --- 4 files changed, 12 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/onroad/bookmark_fill.png delete mode 100644 selfdrive/assets/icons_mici/settings/device/language.png delete mode 100644 selfdrive/assets/icons_mici/setup/back_new.png delete mode 100644 selfdrive/assets/icons_mici/setup/scroll_down_indicator.png diff --git a/selfdrive/assets/icons_mici/onroad/bookmark_fill.png b/selfdrive/assets/icons_mici/onroad/bookmark_fill.png deleted file mode 100644 index 531d5db1cfb..00000000000 --- a/selfdrive/assets/icons_mici/onroad/bookmark_fill.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f3f57346a1cf9a66f9fd746f87bcebb23b7a403e9d6e4fd7701b126abcdd47ea -size 18476 diff --git a/selfdrive/assets/icons_mici/settings/device/language.png b/selfdrive/assets/icons_mici/settings/device/language.png deleted file mode 100644 index d2ef27de36b..00000000000 --- a/selfdrive/assets/icons_mici/settings/device/language.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:f646263b26de46f79cac836ef6865b0f25ddc91e386b99311723b68bd06693c9 -size 3304 diff --git a/selfdrive/assets/icons_mici/setup/back_new.png b/selfdrive/assets/icons_mici/setup/back_new.png deleted file mode 100644 index 20e7fe3b88b..00000000000 --- a/selfdrive/assets/icons_mici/setup/back_new.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d29a9c295b33b3164c37a68ad77795595e6ac877a5b308d28112b0315ecd498f -size 1687 diff --git a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png b/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png deleted file mode 100644 index 3cd26e51810..00000000000 --- a/selfdrive/assets/icons_mici/setup/scroll_down_indicator.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a733c425113a7f6ff5ec3dc50ef94b5481c0f2d306e33d1485be8ee6b2798532 -size 1136 From 44ec08c112fb2c3f1491287d592c5c3ec05b632a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 18:36:12 -0800 Subject: [PATCH 484/685] sliders: clean up (#37580) * remove small buttons! * remove those assets * clean up sliders * fix * abc * base --- .../setup/small_slider/slider_bg.png | 3 --- .../setup/small_slider/slider_red_circle.png | 3 --- .../slider_red_circle_pressed.png | 3 --- system/ui/widgets/slider.py | 23 +++++++++---------- 4 files changed, 11 insertions(+), 21 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png delete mode 100644 selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png deleted file mode 100644 index 43c10a54ad8..00000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_bg.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:94a86fac6ffe8a8179812cf55350ab9ca6935f36244c6f679c1cf521a842316b -size 5723 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png deleted file mode 100644 index 541433be763..00000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6ccb5f2298389ae36df87de84d85440ee5a82c50e803c9bd362c9b89ea45aa69 -size 6611 diff --git a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png b/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png deleted file mode 100644 index eea6eded86c..00000000000 --- a/selfdrive/assets/icons_mici/setup/small_slider/slider_red_circle_pressed.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a804da77b268f0a625f93949642ae74cdfe5b5caa5baea1c52c4605ae25c80e4 -size 12916 diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 57786565e29..203c9eb4a00 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -1,3 +1,4 @@ +import abc from collections.abc import Callable import pyray as rl @@ -8,17 +9,19 @@ from openpilot.common.filter_simple import FirstOrderFilter -class SmallSlider(Widget): +class SliderBase(Widget, abc.ABC): HORIZONTAL_PADDING = 8 CONFIRM_DELAY = 0.2 + _bg_txt: rl.Texture + _circle_bg_txt: rl.Texture + _circle_bg_pressed_txt: rl.Texture + _circle_arrow_txt: rl.Texture + def __init__(self, title: str, confirm_callback: Callable | None = None): - # TODO: unify this with BigConfirmationDialog super().__init__() self._confirm_callback = confirm_callback - self._font = gui_app.font(FontWeight.DISPLAY) - self._load_assets() self._drag_threshold = -self._rect.width // 2 @@ -37,13 +40,9 @@ def __init__(self, title: str, confirm_callback: Callable | None = None): alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9) + @abc.abstractmethod def _load_assets(self): - self.set_rect(rl.Rectangle(0, 0, 316 + self.HORIZONTAL_PADDING * 2, 100)) - - self._bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_bg.png", 316, 100) - self._circle_bg_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle.png", 100, 100) - self._circle_bg_pressed_txt = gui_app.texture("icons_mici/setup/small_slider/slider_red_circle_pressed.png", 100, 100) - self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 37, 32) + ... @property def confirmed(self) -> bool: @@ -149,7 +148,7 @@ def _render(self, _): rl.draw_texture_ex(self._circle_arrow_txt, rl.Vector2(arrow_x, arrow_y), 0.0, 1.0, white) -class LargerSlider(SmallSlider): +class LargerSlider(SliderBase): def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True): self._green = green super().__init__(title, confirm_callback=confirm_callback) @@ -164,7 +163,7 @@ def _load_assets(self): self._circle_arrow_txt = gui_app.texture("icons_mici/setup/small_slider/slider_arrow.png", 64, 55) -class BigSlider(SmallSlider): +class BigSlider(SliderBase): def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable | None = None): self._icon = icon super().__init__(title, confirm_callback=confirm_callback) From 5e2a5b53553dab1fbacf964aac695c9c4c7e2df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 6 Mar 2026 19:00:15 -0800 Subject: [PATCH 485/685] lagd: smooth lat accel + min lat accel range (#37424) * Smooth * Min lat accel range * Make the moving average masked * Bring back the range * Update test * Smooth desired signal too * Diff * Gaussian * Fix fmt * Remove newline --- selfdrive/locationd/lagd.py | 22 ++++++++++++++++++++-- selfdrive/locationd/test/test_lagd.py | 4 ++-- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index 361bb79cce5..8bbee6604b0 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -28,10 +28,25 @@ MAX_LAG_STD = 0.1 MAX_LAT_ACCEL = 2.0 MAX_LAT_ACCEL_DIFF = 0.6 +MIN_LAT_ACCEL_RANGE = 0.5 MIN_CONFIDENCE = 0.7 CORR_BORDER_OFFSET = 5 LAG_CANDIDATE_CORR_THRESHOLD = 0.9 - +SMOOTH_K = 5 +SMOOTH_SIGMA = 1.0 + + +def masked_symmetric_moving_average(x: np.ndarray, mask: np.ndarray, k: int, sigma: float) -> np.ndarray: + assert k >= 1 and k % 2 == 1, "k must be positive and odd" + pad = k // 2 + i = np.arange(k) - pad + w = np.exp(-0.5 * (i / sigma) ** 2) + w /= w.sum() + xp = np.pad(x * mask, pad, mode="edge") + mp = np.pad(mask, pad, mode="edge") + num = np.convolve(xp, w, mode="valid") + den = np.convolve(mp, w, mode="valid") + return np.divide(num, den, out=np.full_like(num, np.nan, dtype=np.float64), where=den != 0) def masked_normalized_cross_correlation(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, n: int): """ @@ -294,11 +309,14 @@ def update_estimate(self): times, desired, actual, okay = self.points.get() # check if there are any new valid data points since the last update - is_valid = self.points_valid() + is_valid = self.points_valid() and (actual.max() - actual.min() >= MIN_LAT_ACCEL_RANGE) if self.last_estimate_t != 0 and times[0] <= self.last_estimate_t: new_values_start_idx = next(-i for i, t in enumerate(reversed(times)) if t <= self.last_estimate_t) is_valid = is_valid and not (new_values_start_idx == 0 or not np.any(okay[new_values_start_idx:])) + desired = masked_symmetric_moving_average(desired, okay, SMOOTH_K, SMOOTH_SIGMA) + actual = masked_symmetric_moving_average(actual, okay, SMOOTH_K, SMOOTH_SIGMA) + delay, corr, confidence = self.actuator_delay(desired, actual, okay, self.dt, MIN_LAG, MAX_LAG) if corr < self.min_ncc or confidence < self.min_confidence or not is_valid: return diff --git a/selfdrive/locationd/test/test_lagd.py b/selfdrive/locationd/test/test_lagd.py index 4728413d9dd..6249e6b04b8 100644 --- a/selfdrive/locationd/test/test_lagd.py +++ b/selfdrive/locationd/test/test_lagd.py @@ -19,8 +19,8 @@ def process_messages(estimator, lag_frames, n_frames, vego=20.0, rejection_threshold=0.0): for i in range(n_frames): t = i * estimator.dt - desired_la = np.cos(10 * t) * 0.1 - actual_la = np.cos(10 * (t - lag_frames * estimator.dt)) * 0.1 + desired_la = np.cos(10 * t) * 0.3 + actual_la = np.cos(10 * (t - lag_frames * estimator.dt)) * 0.3 # if sample is masked out, set it to desired value (no lag) rejected = random.uniform(0, 1) < rejection_threshold From 4cc68f57cf5387842790da633d8591726b3e0f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 6 Mar 2026 20:17:26 -0800 Subject: [PATCH 486/685] lagd: change lag candidate threshold range (#37581) * Use extended_roi_ncc instead of roi_ncc * It doesnt make sense to use non-positive lags in thresholding --- selfdrive/locationd/lagd.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/selfdrive/locationd/lagd.py b/selfdrive/locationd/lagd.py index 8bbee6604b0..6232404c30d 100755 --- a/selfdrive/locationd/lagd.py +++ b/selfdrive/locationd/lagd.py @@ -328,16 +328,16 @@ def update_estimate(self): def actuator_delay(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.ndarray, dt: float, min_lag: float, max_lag: float) -> tuple[float, float, float]: assert len(expected_sig) == len(actual_sig) - min_lag_samples, max_lag_samples = int(round(min_lag / dt)), int(round(max_lag / dt)) - padded_size = fft_next_good_size(len(expected_sig) + max_lag_samples) + min_lag_samples, max_lag_samples, one_sec_samples = int(round(min_lag / dt)), int(round(max_lag / dt)), int(round(1.0 / dt)) + padded_size = fft_next_good_size(len(expected_sig) + max(max_lag_samples, one_sec_samples)) ncc = masked_normalized_cross_correlation(expected_sig, actual_sig, mask, padded_size) - # only consider lags from min_lag to max_lag - roi = np.s_[len(expected_sig) - 1 + min_lag_samples: len(expected_sig) - 1 + max_lag_samples] - extended_roi = np.s_[roi.start - CORR_BORDER_OFFSET: roi.stop + CORR_BORDER_OFFSET] - roi_ncc = ncc[roi] - extended_roi_ncc = ncc[extended_roi] + # only consider lags from ranges: + roi = np.s_[len(expected_sig) - 1 + min_lag_samples: len(expected_sig) - 1 + max_lag_samples] # min_lag - max_lag range + threshold_roi = np.s_[len(expected_sig) - 1: len(expected_sig) - 1 + one_sec_samples] # 0 - 1 second range + confidence_roi = np.s_[threshold_roi.start - CORR_BORDER_OFFSET: threshold_roi.stop + CORR_BORDER_OFFSET] # threshold range +/- border + roi_ncc, confidence_roi_ncc, threshold_roi_ncc = ncc[roi], ncc[confidence_roi], ncc[threshold_roi] max_corr_index = np.argmax(roi_ncc) corr = roi_ncc[max_corr_index] @@ -345,8 +345,8 @@ def actuator_delay(expected_sig: np.ndarray, actual_sig: np.ndarray, mask: np.nd # to estimate lag confidence, gather all high-correlation candidates and see how spread they are # if e.g. 0.8 and 0.4 are both viable, this is an ambiguous case - ncc_thresh = (roi_ncc.max() - roi_ncc.min()) * LAG_CANDIDATE_CORR_THRESHOLD + roi_ncc.min() - good_lag_candidate_mask = extended_roi_ncc >= ncc_thresh + ncc_thresh = (threshold_roi_ncc.max() - threshold_roi_ncc.min()) * LAG_CANDIDATE_CORR_THRESHOLD + threshold_roi_ncc.min() + good_lag_candidate_mask = confidence_roi_ncc >= ncc_thresh good_lag_candidate_edges = np.diff(good_lag_candidate_mask.astype(int), prepend=0, append=0) starts, ends = np.where(good_lag_candidate_edges == 1)[0], np.where(good_lag_candidate_edges == -1)[0] - 1 run_idx = np.searchsorted(starts, max_corr_index + CORR_BORDER_OFFSET, side='right') - 1 From 2f1a58f9912c4207360da933c5765c5f2813592f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 20:45:39 -0800 Subject: [PATCH 487/685] mici setup: connect to continue (#37583) * connect to continue * fix --- system/ui/mici_setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 5e9d34c658d..77d0fc56e74 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -376,7 +376,7 @@ def on_waiting_click(): # trigger grow when wifi button in view self._pending_wifi_grow_animation = True - self._waiting_button = BigPillButton("waiting for\ninternet...", disabled_background=True) + self._waiting_button = BigPillButton("connect to\ncontinue", disabled_background=True) self._waiting_button.set_click_callback(on_waiting_click) self._continue_button = BigPillButton("install openpilot", green=True) self._continue_button.set_click_callback(lambda: continue_callback(self._custom_software)) @@ -423,17 +423,19 @@ def _nav_stack_tick(self): # Check network state before processing callbacks so forgetting flag # is still set on the frame the forgotten callback fires has_internet = self._has_internet + wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED + self._continue_button.set_visible(has_internet) self._waiting_button.set_visible(not has_internet) # TODO: fire show/hide events on visibility changes if not has_internet: self._pending_continue_grow_animation = False + self._waiting_button.set_text("waiting for\ninternet..." if wifi_connected else "connect to\ncontinue") self._wifi_manager.process_callbacks() # Dismiss WiFi UI and scroll on WiFi connect or internet gain - wifi_connected = self._wifi_manager.wifi_state.status == ConnectStatus.CONNECTED if (has_internet and not self._prev_has_internet) or (wifi_connected and not self._prev_wifi_connected): # TODO: cancel if connect is transient self._pending_has_internet_scroll = rl.get_time() From fd98db72abad00fbe317f478e9adbcf3c434f570 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 21:36:43 -0800 Subject: [PATCH 488/685] ui: make confirm callback required for confirmation dialog (#37585) * always required! * reoreder * reorder again * make required so better order * not clear better --- selfdrive/ui/mici/layouts/onboarding.py | 12 +++++------- selfdrive/ui/mici/layouts/settings/device.py | 5 ++--- .../ui/mici/layouts/settings/network/wifi_ui.py | 3 +-- selfdrive/ui/mici/tests/test_widget_leaks.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 5 ++--- system/ui/mici_reset.py | 9 +++------ system/ui/mici_setup.py | 3 +-- 7 files changed, 15 insertions(+), 24 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 3a3625d714b..3b9b36f7016 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -220,15 +220,14 @@ def on_accept(): ui_state.params.put_bool_nonblocking("RecordFront", True) continue_callback() - gui_app.push_widget(BigConfirmationDialog("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", exit_on_confirm=False, - confirm_callback=on_accept)) + gui_app.push_widget(BigConfirmationDialog("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept, exit_on_confirm=False)) def show_decline_dialog(): def on_decline(): ui_state.params.put_bool_nonblocking("RecordFront", False) continue_callback() - gui_app.push_widget(BigConfirmationDialog("no, don't upload", "icons_mici/setup/cancel.png", exit_on_confirm=False, confirm_callback=on_decline)) + gui_app.push_widget(BigConfirmationDialog("no, don't upload", "icons_mici/setup/cancel.png", on_decline, exit_on_confirm=False)) self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") self._accept_button.set_click_callback(show_accept_dialog) @@ -324,12 +323,11 @@ def __init__(self, on_accept, on_decline): super().__init__() def show_accept_dialog(): - gui_app.push_widget(BigConfirmationDialog("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", - confirm_callback=on_accept)) + gui_app.push_widget(BigConfirmationDialog("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept)) def show_decline_dialog(): - gui_app.push_widget(BigConfirmationDialog("decline &\nuninstall", "icons_mici/setup/cancel.png", - red=True, exit_on_confirm=False, confirm_callback=on_decline)) + gui_app.push_widget(BigConfirmationDialog("decline &\nuninstall", "icons_mici/setup/cancel.png", on_decline, + red=True, exit_on_confirm=False)) self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") self._accept_button.set_click_callback(show_accept_dialog) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 0bc1ac2958e..bee8cbce5fc 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -85,9 +85,8 @@ def confirm_callback(): # TODO: check icon = "icons_mici/settings/comma_icon.png" - dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, red=red, - exit_on_confirm=action_text == "reset", - confirm_callback=confirm_callback) + dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, + red=red, exit_on_confirm=action_text == "reset") gui_app.push_widget(dlg) else: dlg = BigDialog(f"Disengage to {action_text}", "") diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index cda3cb365ac..0f5ac977b93 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -246,8 +246,7 @@ def __init__(self, forget_network: Callable): def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) - dlg = BigConfirmationDialog("slide to forget", "icons_mici/settings/network/new/trash.png", red=True, - confirm_callback=self._forget_network) + dlg = BigConfirmationDialog("slide to forget", "icons_mici/settings/network/new/trash.png", self._forget_network, red=True) gui_app.push_widget(dlg) def _render(self, _): diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index c1065659f1c..43b2cf79f67 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -72,7 +72,7 @@ def test_dialogs_do_not_leak(): lambda: MiciTrainingGuide(lambda: None), lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), - lambda: BigConfirmationDialog("test", "icons_mici/settings/network/new/trash.png"), + lambda: BigConfirmationDialog("test", "icons_mici/settings/network/new/trash.png", lambda: None), lambda: BigInputDialog("test"), lambda: MiciFccModal(text="test"), # tici diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 387f4c02d07..fc4e3edabea 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -64,9 +64,8 @@ def _render(self, _): class BigConfirmationDialog(BigDialogBase): - def __init__(self, title: str, icon: str, red: bool = False, - exit_on_confirm: bool = True, - confirm_callback: Callable | None = None): + def __init__(self, title: str, icon: str, confirm_callback: Callable[[], None], + exit_on_confirm: bool = True, red: bool = False): super().__init__() self._confirm_callback = confirm_callback self._exit_on_confirm = exit_on_confirm diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 92cd7e7cb89..b56e38941ab 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -71,18 +71,15 @@ def __init__(self, mode): self._reset_failed_page = ResetFailedPage() def show_confirm_dialog(): - dialog = BigConfirmationDialog("erase\ndevice", "icons_mici/settings/device/uninstall.png", red=True, - confirm_callback=self.start_reset) + dialog = BigConfirmationDialog("erase\ndevice", "icons_mici/settings/device/uninstall.png", self.start_reset, red=True) gui_app.push_widget(dialog) def show_cancel_dialog(): - dialog = BigConfirmationDialog("normal\nstartup", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=gui_app.request_close) + dialog = BigConfirmationDialog("normal\nstartup", "icons_mici/settings/device/reboot.png", gui_app.request_close, exit_on_confirm=False) gui_app.push_widget(dialog) def show_reboot_dialog(): - dialog = BigConfirmationDialog("reboot\ndevice", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + dialog = BigConfirmationDialog("reboot\ndevice", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) gui_app.push_widget(dialog) self._reset_button = BigCircleButton("icons_mici/settings/device/uninstall.png", red=True) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 77d0fc56e74..31edbe86e8d 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -248,8 +248,7 @@ def __init__(self, retry_callback: Callable | None, title: str = "download faile self.set_back_callback(retry_callback) def show_reboot_dialog(): - dialog = BigConfirmationDialog("slide to reboot", "icons_mici/settings/device/reboot.png", - exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + dialog = BigConfirmationDialog("slide to reboot", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) gui_app.push_widget(dialog) reboot_button = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) From 5e1a576f3d88d76df6230eec931073419a2fe870 Mon Sep 17 00:00:00 2001 From: Lukas Heintz <61192133+lukasloetkolben@users.noreply.github.com> Date: Sat, 7 Mar 2026 07:13:16 +0100 Subject: [PATCH 489/685] cabana: exclude SocketCAN on macOS (#37553) fix cabana on macos Co-authored-by: Adeeb Shihadeh --- tools/cabana/SConscript | 15 +++++++++------ tools/cabana/cabana.cc | 6 ++++++ tools/cabana/streamselector.cc | 5 ++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 098a6351b76..1f7ba3eaef7 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -88,12 +88,15 @@ assets_src = "assets/assets.qrc" cabana_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"])) -cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/socketcanstream.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', - 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', - 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', - 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', - 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', - 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_srcs = ['mainwin.cc', 'streams/pandastream.cc', 'streams/devicestream.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'historylog.cc', 'videowidget.cc', 'signalview.cc', + 'streams/routes.cc', 'dbc/dbc.cc', 'dbc/dbcfile.cc', 'dbc/dbcmanager.cc', + 'utils/export.cc', 'utils/util.cc', 'utils/elidedlabel.cc', + 'chart/chartswidget.cc', 'chart/chart.cc', 'chart/signalselector.cc', 'chart/tiplabel.cc', 'chart/sparkline.cc', + 'commands.cc', 'messageswidget.cc', 'streamselector.cc', 'settings.cc', 'panda.cc', + 'cameraview.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc', 'tools/findsignal.cc', 'tools/routeinfo.cc'] +if arch != "Darwin": + cabana_srcs += ['streams/socketcanstream.cc'] +cabana_lib = cabana_env.Library("cabana_lib", cabana_srcs, LIBS=cabana_libs, FRAMEWORKS=base_frameworks) cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 9e4cfc4a51c..db26b4067ac 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -5,7 +5,9 @@ #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#ifdef __linux__ #include "tools/cabana/streams/socketcanstream.h" +#endif int main(int argc, char *argv[]) { QCoreApplication::setApplicationName("Cabana"); @@ -29,9 +31,11 @@ int main(int argc, char *argv[]) { cmd_parser.addOption({"msgq", "read can messages from the msgq"}); cmd_parser.addOption({"panda", "read can messages from panda"}); cmd_parser.addOption({"panda-serial", "read can messages from panda with given serial", "panda-serial"}); +#ifdef __linux__ if (SocketCanStream::available()) { cmd_parser.addOption({"socketcan", "read can messages from given SocketCAN device", "socketcan"}); } +#endif cmd_parser.addOption({"zmq", "read can messages from zmq at the specified ip-address", "ip-address"}); cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); cmd_parser.addOption({"no-vipc", "do not output video"}); @@ -51,8 +55,10 @@ int main(int argc, char *argv[]) { qWarning() << e.what(); return 0; } +#ifdef __linux__ } else if (SocketCanStream::available() && cmd_parser.isSet("socketcan")) { stream = new SocketCanStream(&app, {.device = cmd_parser.value("socketcan").toStdString()}); +#endif } else { uint32_t replay_flags = REPLAY_FLAG_NONE; if (cmd_parser.isSet("ecam")) replay_flags |= REPLAY_FLAG_ECAM; diff --git a/tools/cabana/streamselector.cc b/tools/cabana/streamselector.cc index efd00d39857..4ad552d4b4c 100644 --- a/tools/cabana/streamselector.cc +++ b/tools/cabana/streamselector.cc @@ -4,11 +4,12 @@ #include #include -#include "streams/socketcanstream.h" #include "tools/cabana/streams/devicestream.h" #include "tools/cabana/streams/pandastream.h" #include "tools/cabana/streams/replaystream.h" +#ifdef __linux__ #include "tools/cabana/streams/socketcanstream.h" +#endif StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) { setWindowTitle(tr("Open stream")); @@ -35,9 +36,11 @@ StreamSelector::StreamSelector(QWidget *parent) : QDialog(parent) { addStreamWidget(new OpenReplayWidget, tr("&Replay")); addStreamWidget(new OpenPandaWidget, tr("&Panda")); +#ifdef __linux__ if (SocketCanStream::available()) { addStreamWidget(new OpenSocketCanWidget, tr("&SocketCAN")); } +#endif addStreamWidget(new OpenDeviceWidget, tr("&Device")); QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); From 793f8fee328fc4ce2ef4b1685af9ea0d9755bd3d Mon Sep 17 00:00:00 2001 From: Utkarsh Gill <46515280+utkarshgill@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:44:31 +0530 Subject: [PATCH 490/685] fix(sim): use getRamImageAs for correct channel order (#37528) getRamImage() returns panda3d's internal BGRA format. on macOS this produces swapped red/blue channels in the sim camera feed. getRamImageAs("RGBA") requests explicit RGBA reordering from panda3d, correct on all platforms. no-op where internal format is already RGBA. ref: https://docs.panda3d.org/1.10/python/reference/panda3d.core.Texture#panda3d.core.Texture.getRamImageAs fixes #37526 --- tools/sim/bridge/metadrive/metadrive_common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/sim/bridge/metadrive/metadrive_common.py b/tools/sim/bridge/metadrive/metadrive_common.py index 42a7eb60dd4..079d933d1a8 100644 --- a/tools/sim/bridge/metadrive/metadrive_common.py +++ b/tools/sim/bridge/metadrive/metadrive_common.py @@ -13,9 +13,9 @@ def __init__(self, *args, **kwargs): def get_rgb_array_cpu(self): origin_img = self.cpu_texture - img = np.frombuffer(origin_img.getRamImage().getData(), dtype=np.uint8) - img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), -1)) - img = img[:,:,:3] # RGBA to RGB + img = np.frombuffer(origin_img.getRamImageAs("RGBA").getData(), dtype=np.uint8) + img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), 4)) + img = img[:, :, :3] # img = np.swapaxes(img, 1, 0) img = img[::-1] # Flip on vertical axis return img From 0557283e3d506cd1f92e6fcbae17771ae9fcdecf Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 22:38:00 -0800 Subject: [PATCH 491/685] ui: add confirmation circle button (#37586) * try this * clean up and use it * clean up * simpler * do this later * do onboarding & reset * do setup * temp * Revert "temp" This reverts commit 22fbbf5c813b4915e784b9ee235ed3bde2229048. * simpler again * missing size * fix * Revert "fix" This reverts commit 53c4e29e614181029dc8e9a2baea7694957dc8fb. * nl --- selfdrive/ui/mici/layouts/onboarding.py | 42 +++++++------------------ selfdrive/ui/mici/widgets/dialog.py | 14 ++++++++- system/ui/mici_reset.py | 28 +++++------------ system/ui/mici_setup.py | 14 +++------ 4 files changed, 37 insertions(+), 61 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 3b9b36f7016..f145a477340 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -14,8 +14,7 @@ from openpilot.system.ui.lib.multilang import tr from openpilot.system.version import terms_version, training_version from openpilot.selfdrive.ui.ui_state import ui_state, device -from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import BaseDriverCameraDialog @@ -215,25 +214,18 @@ class TrainingGuideRecordFront(NavScroller): def __init__(self, continue_callback: Callable[[], None]): super().__init__() - def show_accept_dialog(): - def on_accept(): - ui_state.params.put_bool_nonblocking("RecordFront", True) - continue_callback() + def on_accept(): + ui_state.params.put_bool_nonblocking("RecordFront", True) + continue_callback() - gui_app.push_widget(BigConfirmationDialog("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept, exit_on_confirm=False)) + def on_decline(): + ui_state.params.put_bool_nonblocking("RecordFront", False) + continue_callback() - def show_decline_dialog(): - def on_decline(): - ui_state.params.put_bool_nonblocking("RecordFront", False) - continue_callback() + self._accept_button = BigConfirmationCircleButton("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", + on_accept, exit_on_confirm=False) - gui_app.push_widget(BigConfirmationDialog("no, don't upload", "icons_mici/setup/cancel.png", on_decline, exit_on_confirm=False)) - - self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") - self._accept_button.set_click_callback(show_accept_dialog) - - self._decline_button = BigCircleButton("icons_mici/setup/cancel.png") - self._decline_button.set_click_callback(show_decline_dialog) + self._decline_button = BigConfirmationCircleButton("no, don't upload", "icons_mici/setup/cancel.png", on_decline, exit_on_confirm=False) self._scroller.add_widgets([ GreyBigButton("driver camera data", "do you want to share video data for training?", @@ -322,18 +314,8 @@ class TermsPage(Scroller): def __init__(self, on_accept, on_decline): super().__init__() - def show_accept_dialog(): - gui_app.push_widget(BigConfirmationDialog("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept)) - - def show_decline_dialog(): - gui_app.push_widget(BigConfirmationDialog("decline &\nuninstall", "icons_mici/setup/cancel.png", on_decline, - red=True, exit_on_confirm=False)) - - self._accept_button = BigCircleButton("icons_mici/setup/driver_monitoring/dm_check.png") - self._accept_button.set_click_callback(show_accept_dialog) - - self._decline_button = BigCircleButton("icons_mici/setup/cancel.png", red=True) - self._decline_button.set_click_callback(show_decline_dialog) + self._accept_button = BigConfirmationCircleButton("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept) + self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", "icons_mici/setup/cancel.png", on_decline, red=True, exit_on_confirm=False) self._terms_header = GreyBigButton("terms and\nconditions", "scroll to continue", gui_app.texture("icons_mici/setup/green_info.png", 64, 64)) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index fc4e3edabea..53c419b4148 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -11,7 +11,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton DEBUG = False @@ -253,3 +253,15 @@ def _handle_mouse_release(self, mouse_pos: MousePos): dlg = BigDialog(self.text, self._description) gui_app.push_widget(dlg) + + +class BigConfirmationCircleButton(BigCircleButton): + def __init__(self, title: str, icon: str, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, + red: bool = False, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, red, icon_size, icon_offset) + + def show_confirm_dialog(): + gui_app.push_widget(BigConfirmationDialog(title, icon, confirm_callback, + exit_on_confirm=exit_on_confirm, red=red)) + + self.set_click_callback(show_confirm_dialog) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index b56e38941ab..e88f5f029eb 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -11,8 +11,7 @@ from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog -from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" TIMEOUT = 3*60 @@ -70,37 +69,26 @@ def __init__(self, mode): self._resetting_page = ResettingPage() self._reset_failed_page = ResetFailedPage() - def show_confirm_dialog(): - dialog = BigConfirmationDialog("erase\ndevice", "icons_mici/settings/device/uninstall.png", self.start_reset, red=True) - gui_app.push_widget(dialog) + self._reset_button = BigConfirmationCircleButton("erase\ndevice", "icons_mici/settings/device/uninstall.png", self.start_reset, red=True) + self._cancel_button = BigConfirmationCircleButton("normal\nstartup", "icons_mici/settings/device/reboot.png", gui_app.request_close, exit_on_confirm=False) + self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) - def show_cancel_dialog(): - dialog = BigConfirmationDialog("normal\nstartup", "icons_mici/settings/device/reboot.png", gui_app.request_close, exit_on_confirm=False) - gui_app.push_widget(dialog) - - def show_reboot_dialog(): - dialog = BigConfirmationDialog("reboot\ndevice", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) - gui_app.push_widget(dialog) - - self._reset_button = BigCircleButton("icons_mici/settings/device/uninstall.png", red=True) - self._reset_button.set_click_callback(show_confirm_dialog) - - self._cancel_button = BigCircleButton("icons_mici/settings/device/reboot.png") - self._cancel_button.set_click_callback(show_cancel_dialog) + # show reboot button if in recover mode + self._cancel_button.set_visible(mode != ResetMode.RECOVER) + self._reboot_button.set_visible(mode == ResetMode.RECOVER) main_card = GreyBigButton("factory reset", "all content and\nsettings will be erased", gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) - # cancel button becomes reboot button if mode == ResetMode.RECOVER: main_card.set_text("unable to mount\ndata partition") main_card.set_value("it may be corrupted") - self._cancel_button.set_click_callback(show_reboot_dialog) self._scroller.add_widgets([ main_card, self._reset_button, self._cancel_button, + self._reboot_button, ]) def _do_erase(self): diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 31edbe86e8d..2f409f2d549 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -28,8 +28,8 @@ from openpilot.system.ui.widgets.slider import LargerSlider from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici -from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialog -from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationCircleButton +from openpilot.selfdrive.ui.mici.widgets.button import BigButton NetworkType = log.DeviceState.NetworkType @@ -247,13 +247,6 @@ def __init__(self, retry_callback: Callable | None, title: str = "download faile super().__init__() self.set_back_callback(retry_callback) - def show_reboot_dialog(): - dialog = BigConfirmationDialog("slide to reboot", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) - gui_app.push_widget(dialog) - - reboot_button = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) - reboot_button.set_click_callback(show_reboot_dialog) - self._reason_card = GreyBigButton("", "") self._reason_card.set_visible(False) @@ -261,7 +254,8 @@ def show_reboot_dialog(): GreyBigButton(title, description or "swipe down to go\nback and try again", gui_app.texture(icon, 64, 58)), self._reason_card, - reboot_button, + BigConfirmationCircleButton("slide to reboot", "icons_mici/settings/device/reboot.png", + HARDWARE.reboot, exit_on_confirm=False, icon_size=(64, 70)), ]) def set_reason(self, reason: str): From 1f9ec135a46df4d3524a620f83989953484ce976 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 6 Mar 2026 23:40:42 -0800 Subject: [PATCH 492/685] BigButton: take icon texture and fix image sizes (#37590) * more explicit pass texture like everything else, esp since sizes are not all same * fix some confirmation dialog images * fix image sizes * do bigbutton * fix * static --- selfdrive/ui/mici/layouts/onboarding.py | 10 ++++--- .../ui/mici/layouts/settings/developer.py | 4 +-- selfdrive/ui/mici/layouts/settings/device.py | 28 +++++++++---------- .../mici/layouts/settings/network/wifi_ui.py | 2 +- .../ui/mici/layouts/settings/settings.py | 10 +++---- selfdrive/ui/mici/tests/test_widget_leaks.py | 2 +- selfdrive/ui/mici/widgets/button.py | 24 ++++++++-------- selfdrive/ui/mici/widgets/dialog.py | 13 ++++----- system/ui/mici_reset.py | 9 ++++-- system/ui/mici_setup.py | 4 +-- 10 files changed, 54 insertions(+), 52 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index f145a477340..ac196287388 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -222,10 +222,11 @@ def on_decline(): ui_state.params.put_bool_nonblocking("RecordFront", False) continue_callback() - self._accept_button = BigConfirmationCircleButton("allow data uploading", "icons_mici/setup/driver_monitoring/dm_check.png", + self._accept_button = BigConfirmationCircleButton("allow data uploading", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept, exit_on_confirm=False) - self._decline_button = BigConfirmationCircleButton("no, don't upload", "icons_mici/setup/cancel.png", on_decline, exit_on_confirm=False) + self._decline_button = BigConfirmationCircleButton("no, don't upload", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, + exit_on_confirm=False) self._scroller.add_widgets([ GreyBigButton("driver camera data", "do you want to share video data for training?", @@ -314,8 +315,9 @@ class TermsPage(Scroller): def __init__(self, on_accept, on_decline): super().__init__() - self._accept_button = BigConfirmationCircleButton("accept\nterms", "icons_mici/setup/driver_monitoring/dm_check.png", on_accept) - self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", "icons_mici/setup/cancel.png", on_decline, red=True, exit_on_confirm=False) + self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept) + self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, + red=True, exit_on_confirm=False) self._terms_header = GreyBigButton("terms and\nconditions", "scroll to continue", gui_app.texture("icons_mici/setup/green_info.png", 64, 64)) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 4e7796814ee..eccaa3ec093 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -42,8 +42,8 @@ def ssh_keys_callback(): # adb, ssh, ssh keys, debug mode, joystick debug mode, longitudinal maneuver mode, ip address # ******** Main Scroller ******** - self._adb_toggle = BigCircleParamControl("icons_mici/adb_short.png", "AdbEnabled", icon_size=(82, 82), icon_offset=(0, 12)) - self._ssh_toggle = BigCircleParamControl("icons_mici/ssh_short.png", "SshEnabled", icon_size=(82, 82), icon_offset=(0, 12)) + self._adb_toggle = BigCircleParamControl(gui_app.texture("icons_mici/adb_short.png", 82, 82), "AdbEnabled", icon_offset=(0, 12)) + self._ssh_toggle = BigCircleParamControl(gui_app.texture("icons_mici/ssh_short.png", 82, 82), "SshEnabled", icon_offset=(0, 12)) self._joystick_toggle = BigToggle("joystick debug mode", initial_state=ui_state.params.get_bool("JoystickDebugMode"), toggle_callback=self._on_joystick_debug_mode) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index bee8cbce5fc..d4c8fda5cef 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -73,17 +73,17 @@ def confirm_callback(): red = False if action_text == "power off": - icon = "icons_mici/settings/device/power.png" + icon = gui_app.texture("icons_mici/settings/device/power.png", 64, 66) red = True elif action_text == "reboot": - icon = "icons_mici/settings/device/reboot.png" + icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70) elif action_text == "reset": - icon = "icons_mici/settings/device/lkas.png" + icon = gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64) elif action_text == "uninstall": - icon = "icons_mici/settings/device/uninstall.png" + icon = gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64) else: # TODO: check - icon = "icons_mici/settings/comma_icon.png" + icon = gui_app.texture("icons_mici/settings/comma_icon.png", 36, 64) dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, red=red, exit_on_confirm=action_text == "reset") @@ -131,7 +131,7 @@ class UpdaterState(IntEnum): class PairBigButton(BigButton): def __init__(self): - super().__init__("pair", "connect.comma.ai", "icons_mici/settings/comma_icon.png", icon_size=(33, 60)) + super().__init__("pair", "connect.comma.ai", gui_app.texture("icons_mici/settings/comma_icon.png", 33, 60)) def _get_label_font_size(self): return 64 @@ -310,31 +310,31 @@ def reset_calibration_callback(): def uninstall_openpilot_callback(): ui_state.params.put_bool("DoUninstall", True) - reset_calibration_btn = BigButton("reset calibration", "", "icons_mici/settings/device/lkas.png", icon_size=(114, 60)) + reset_calibration_btn = BigButton("reset calibration", "", gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64)) reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset")) - uninstall_openpilot_btn = BigButton("uninstall openpilot", "", "icons_mici/settings/device/uninstall.png") + uninstall_openpilot_btn = BigButton("uninstall openpilot", "", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64)) uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall")) - reboot_btn = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70)) + reboot_btn = BigCircleButton(gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70)) reboot_btn.set_click_callback(lambda: _engaged_confirmation_callback(reboot_callback, "reboot")) - self._power_off_btn = BigCircleButton("icons_mici/settings/device/power.png", red=True, icon_size=(64, 66)) + self._power_off_btn = BigCircleButton(gui_app.texture("icons_mici/settings/device/power.png", 64, 66), red=True) self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) self._power_off_btn.set_visible(lambda: not ui_state.ignition) - regulatory_btn = BigButton("regulatory info", "", "icons_mici/settings/device/info.png") + regulatory_btn = BigButton("regulatory info", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) regulatory_btn.set_click_callback(self._on_regulatory) - driver_cam_btn = BigButton("driver\ncamera preview", "", "icons_mici/settings/device/cameras.png") + driver_cam_btn = BigButton("driver\ncamera preview", "", gui_app.texture("icons_mici/settings/device/cameras.png", 64, 64)) driver_cam_btn.set_click_callback(lambda: gui_app.push_widget(DriverCameraDialog())) driver_cam_btn.set_enabled(lambda: ui_state.is_offroad()) - review_training_guide_btn = BigButton("review\ntraining guide", "", "icons_mici/settings/device/info.png") + review_training_guide_btn = BigButton("review\ntraining guide", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) review_training_guide_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTrainingGuide(completed_callback=lambda: gui_app.pop_widgets_to(self)))) review_training_guide_btn.set_enabled(lambda: ui_state.is_offroad()) - terms_btn = BigButton("terms &\nconditions", "", "icons_mici/settings/device/info.png") + terms_btn = BigButton("terms &\nconditions", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) terms_btn.set_click_callback(lambda: gui_app.push_widget(ReviewTermsPage())) self._scroller.add_widgets([ diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 0f5ac977b93..10d828fbd8a 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -246,7 +246,7 @@ def __init__(self, forget_network: Callable): def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) - dlg = BigConfirmationDialog("slide to forget", "icons_mici/settings/network/new/trash.png", self._forget_network, red=True) + dlg = BigConfirmationDialog("slide to forget", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), self._forget_network, red=True) gui_app.push_widget(dlg) def _render(self, _): diff --git a/selfdrive/ui/mici/layouts/settings/settings.py b/selfdrive/ui/mici/layouts/settings/settings.py index 8e037ccf88c..4ccc5ba139a 100644 --- a/selfdrive/ui/mici/layouts/settings/settings.py +++ b/selfdrive/ui/mici/layouts/settings/settings.py @@ -20,23 +20,23 @@ def __init__(self): self._params = Params() toggles_panel = TogglesLayoutMici() - toggles_btn = SettingsBigButton("toggles", "", "icons_mici/settings.png") + toggles_btn = SettingsBigButton("toggles", "", gui_app.texture("icons_mici/settings.png", 64, 64)) toggles_btn.set_click_callback(lambda: gui_app.push_widget(toggles_panel)) network_panel = NetworkLayoutMici() - network_btn = SettingsBigButton("network", "", "icons_mici/settings/network/wifi_strength_full.png", icon_size=(76, 56)) + network_btn = SettingsBigButton("network", "", gui_app.texture("icons_mici/settings/network/wifi_strength_full.png", 76, 56)) network_btn.set_click_callback(lambda: gui_app.push_widget(network_panel)) device_panel = DeviceLayoutMici() - device_btn = SettingsBigButton("device", "", "icons_mici/settings/device_icon.png", icon_size=(74, 60)) + device_btn = SettingsBigButton("device", "", gui_app.texture("icons_mici/settings/device_icon.png", 72, 58)) device_btn.set_click_callback(lambda: gui_app.push_widget(device_panel)) developer_panel = DeveloperLayoutMici() - developer_btn = SettingsBigButton("developer", "", "icons_mici/settings/developer_icon.png", icon_size=(64, 60)) + developer_btn = SettingsBigButton("developer", "", gui_app.texture("icons_mici/settings/developer_icon.png", 64, 60)) developer_btn.set_click_callback(lambda: gui_app.push_widget(developer_panel)) firehose_panel = FirehoseLayout() - firehose_btn = SettingsBigButton("firehose", "", "icons_mici/settings/firehose.png", icon_size=(52, 62)) + firehose_btn = SettingsBigButton("firehose", "", gui_app.texture("icons_mici/settings/firehose.png", 52, 62)) firehose_btn.set_click_callback(lambda: gui_app.push_widget(firehose_panel)) self._scroller.add_widgets([ diff --git a/selfdrive/ui/mici/tests/test_widget_leaks.py b/selfdrive/ui/mici/tests/test_widget_leaks.py index 43b2cf79f67..e35cb447768 100755 --- a/selfdrive/ui/mici/tests/test_widget_leaks.py +++ b/selfdrive/ui/mici/tests/test_widget_leaks.py @@ -72,7 +72,7 @@ def test_dialogs_do_not_leak(): lambda: MiciTrainingGuide(lambda: None), lambda: MiciOnboardingWindow(lambda: None), lambda: BigDialog("test", "test"), - lambda: BigConfirmationDialog("test", "icons_mici/settings/network/new/trash.png", lambda: None), + lambda: BigConfirmationDialog("test", gui_app.texture("icons_mici/settings/network/new/trash.png", 54, 64), lambda: None), lambda: BigInputDialog("test"), lambda: MiciFccModal(text="test"), # tici diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 18413dbc3aa..9724a18192f 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -28,7 +28,7 @@ class ScrollState(Enum): class BigCircleButton(Widget): - def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): + def __init__(self, icon: rl.Texture, red: bool = False, icon_offset: tuple[int, int] = (0, 0)): super().__init__() self._red = red self._icon_offset = icon_offset @@ -39,7 +39,7 @@ def __init__(self, icon: str, red: bool = False, icon_size: tuple[int, int] = (6 self._click_delay = 0.075 # Icons - self._txt_icon = gui_app.texture(icon, *icon_size) + self._txt_icon = icon self._txt_btn_disabled_bg = gui_app.texture("icons_mici/buttons/button_circle_disabled.png", 180, 180) self._txt_btn_bg = gui_app.texture("icons_mici/buttons/button_circle.png", 180, 180) @@ -71,8 +71,8 @@ def _render(self, _): class BigCircleToggle(BigCircleButton): - def __init__(self, icon: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): - super().__init__(icon, False, icon_size=icon_size, icon_offset=icon_offset) + def __init__(self, icon: rl.Texture, toggle_callback: Callable | None = None, icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, False, icon_offset=icon_offset) self._toggle_callback = toggle_callback # State @@ -107,15 +107,13 @@ class BigButton(Widget): """A lightweight stand-in for the Qt BigButton, drawn & updated each frame.""" - def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "", icon_size: tuple[int, int] = (64, 64), - scroll: bool = False): + def __init__(self, text: str, value: str = "", icon: Union[rl.Texture, None] = None, scroll: bool = False): super().__init__() self.set_rect(rl.Rectangle(0, 0, 402, 180)) self.text = text self.value = value - self._icon_size = icon_size + self._txt_icon = icon self._scroll = scroll - self.set_icon(icon) self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) self._click_delay = 0.075 @@ -133,8 +131,8 @@ def __init__(self, text: str, value: str = "", icon: Union[str, rl.Texture] = "" self._load_images() - def set_icon(self, icon: Union[str, rl.Texture]): - self._txt_icon = gui_app.texture(icon, *self._icon_size) if isinstance(icon, str) and len(icon) else icon + def set_icon(self, icon: Union[rl.Texture, None]): + self._txt_icon = icon def set_rotate_icon(self, rotate: bool): if rotate and self._rotate_icon_t is not None: @@ -151,7 +149,7 @@ def set_touch_valid_callback(self, touch_callback: Callable[[], bool]) -> None: def _width_hint(self) -> int: # Single line if scrolling, so hide behind icon if exists - icon_size = self._icon_size[0] if self._txt_icon and self._scroll and self.value else 0 + icon_size = self._txt_icon.width if self._txt_icon and self._scroll and self.value else 0 return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2 - icon_size) def _get_label_font_size(self): @@ -372,9 +370,9 @@ def refresh(self): # TODO: param control base class class BigCircleParamControl(BigCircleToggle): - def __init__(self, icon: str, param: str, toggle_callback: Callable | None = None, icon_size: tuple[int, int] = (64, 53), + def __init__(self, icon: rl.Texture, param: str, toggle_callback: Callable | None = None, icon_offset: tuple[int, int] = (0, 0)): - super().__init__(icon, toggle_callback, icon_size=icon_size, icon_offset=icon_offset) + super().__init__(icon, toggle_callback, icon_offset=icon_offset) self._param = param self.params = Params() self.set_checked(self.params.get_bool(self._param, False)) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 53c419b4148..94091b92692 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -64,18 +64,17 @@ def _render(self, _): class BigConfirmationDialog(BigDialogBase): - def __init__(self, title: str, icon: str, confirm_callback: Callable[[], None], + def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, red: bool = False): super().__init__() self._confirm_callback = confirm_callback self._exit_on_confirm = exit_on_confirm - icon_txt = gui_app.texture(icon, 64, 53) self._slider: BigSlider | RedBigSlider if red: - self._slider = RedBigSlider(title, icon_txt, confirm_callback=self._on_confirm) + self._slider = RedBigSlider(title, icon, confirm_callback=self._on_confirm) else: - self._slider = BigSlider(title, icon_txt, confirm_callback=self._on_confirm) + self._slider = BigSlider(title, icon, confirm_callback=self._on_confirm) self._slider.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget def _on_confirm(self): @@ -256,9 +255,9 @@ def _handle_mouse_release(self, mouse_pos: MousePos): class BigConfirmationCircleButton(BigCircleButton): - def __init__(self, title: str, icon: str, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, - red: bool = False, icon_size: tuple[int, int] = (64, 53), icon_offset: tuple[int, int] = (0, 0)): - super().__init__(icon, red, icon_size, icon_offset) + def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable[[], None], exit_on_confirm: bool = True, + red: bool = False, icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, red, icon_offset) def show_confirm_dialog(): gui_app.push_widget(BigConfirmationDialog(title, icon, confirm_callback, diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index e88f5f029eb..49fbf71b76e 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -69,9 +69,12 @@ def __init__(self, mode): self._resetting_page = ResettingPage() self._reset_failed_page = ResetFailedPage() - self._reset_button = BigConfirmationCircleButton("erase\ndevice", "icons_mici/settings/device/uninstall.png", self.start_reset, red=True) - self._cancel_button = BigConfirmationCircleButton("normal\nstartup", "icons_mici/settings/device/reboot.png", gui_app.request_close, exit_on_confirm=False) - self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", "icons_mici/settings/device/reboot.png", HARDWARE.reboot, exit_on_confirm=False) + self._reset_button = BigConfirmationCircleButton("erase\ndevice", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64), + self.start_reset, red=True) + self._cancel_button = BigConfirmationCircleButton("normal\nstartup", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + gui_app.request_close, exit_on_confirm=False) + self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + HARDWARE.reboot, exit_on_confirm=False) # show reboot button if in recover mode self._cancel_button.set_visible(mode != ResetMode.RECOVER) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 2f409f2d549..40b4c7a8819 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -254,8 +254,8 @@ def __init__(self, retry_callback: Callable | None, title: str = "download faile GreyBigButton(title, description or "swipe down to go\nback and try again", gui_app.texture(icon, 64, 58)), self._reason_card, - BigConfirmationCircleButton("slide to reboot", "icons_mici/settings/device/reboot.png", - HARDWARE.reboot, exit_on_confirm=False, icon_size=(64, 70)), + BigConfirmationCircleButton("slide to reboot", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + HARDWARE.reboot, exit_on_confirm=False), ]) def set_reason(self, reason: str): From c36c30e74be7510a8b3f8e44d9470b28b2237910 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 00:14:01 -0800 Subject: [PATCH 493/685] reset: rm --format (#37591) * reset: rm --format * same for tici --- system/ui/mici_reset.py | 10 ++-------- system/ui/tici_reset.py | 10 ++-------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 49fbf71b76e..03593b99002 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -20,7 +20,6 @@ class ResetMode(IntEnum): USER_RESET = 0 # user initiated a factory reset from openpilot RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover - FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata class ResetFailedPage(FailedPage): @@ -70,7 +69,7 @@ def __init__(self, mode): self._reset_failed_page = ResetFailedPage() self._reset_button = BigConfirmationCircleButton("erase\ndevice", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64), - self.start_reset, red=True) + self._start_reset, red=True) self._cancel_button = BigConfirmationCircleButton("normal\nstartup", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), @@ -108,7 +107,7 @@ def _do_erase(self): else: self._reset_failed = True - def start_reset(self): + def _start_reset(self): self._resetting_page.set_shown_callback(self._do_erase) gui_app.push_widget(self._resetting_page) @@ -132,16 +131,11 @@ def main(): if len(sys.argv) > 1: if sys.argv[1] == '--recover': mode = ResetMode.RECOVER - elif sys.argv[1] == "--format": - mode = ResetMode.FORMAT gui_app.init_window("System Reset") reset = Reset(mode) gui_app.push_widget(reset) - if mode == ResetMode.FORMAT: - reset.start_reset() - for _ in gui_app.render(): pass diff --git a/system/ui/tici_reset.py b/system/ui/tici_reset.py index 23f6b344ec6..a6603d547e6 100755 --- a/system/ui/tici_reset.py +++ b/system/ui/tici_reset.py @@ -20,7 +20,6 @@ class ResetMode(IntEnum): USER_RESET = 0 # user initiated a factory reset from openpilot RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover - FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata class ResetState(IntEnum): @@ -54,7 +53,7 @@ def _do_erase(self): else: self._reset_state = ResetState.FAILED - def start_reset(self): + def _start_reset(self): self._reset_state = ResetState.RESETTING threading.Timer(0.1, self._do_erase).start() @@ -92,7 +91,7 @@ def _render(self, _): def _confirm(self): if self._reset_state == ResetState.CONFIRM: - self.start_reset() + self._start_reset() else: self._reset_state = ResetState.CONFIRM @@ -113,15 +112,10 @@ def main(): if len(sys.argv) > 1: if sys.argv[1] == '--recover': mode = ResetMode.RECOVER - elif sys.argv[1] == "--format": - mode = ResetMode.FORMAT gui_app.init_window("System Reset", 20) reset = Reset(mode) - if mode == ResetMode.FORMAT: - reset.start_reset() - gui_app.push_widget(reset) for _ in gui_app.render(): From 7061c18ceedbfba058cc4d9e48d8cf3e96ab2122 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 01:45:46 -0800 Subject: [PATCH 494/685] ui: antialias text (#37592) aa --- system/ui/lib/application.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 8bb919cfe1a..ddcde014968 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -656,7 +656,8 @@ def _load_fonts(self): fnt_path = fspath / font_weight_file font = rl.load_font(fnt_path.as_posix()) if font_weight_file != FontWeight.UNIFONT: - rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) + rl.gen_texture_mipmaps(font.texture) + rl.set_texture_filter(font.texture, rl.TextureFilter.TEXTURE_FILTER_TRILINEAR) self._fonts[font_weight_file] = font rl.gui_set_font(self._fonts[FontWeight.NORMAL]) From 08162be765a1cf88c53d2fd0098ba0b0ad5c4ae3 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 01:53:41 -0800 Subject: [PATCH 495/685] mici reset: new flow (#37584) * copy * add back * stash * fix * more * dot animation * fix anim * 0.6 * fix --- system/ui/mici_reset.py | 30 ++++++++++++++++++++++-------- system/ui/mici_setup.py | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 03593b99002..2f89b6d62a3 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -20,6 +20,7 @@ class ResetMode(IntEnum): USER_RESET = 0 # user initiated a factory reset from openpilot RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover + TAP_RESET = 2 # user initiated a factory reset by tapping the screen during boot class ResetFailedPage(FailedPage): @@ -35,8 +36,11 @@ def _back_enabled(self) -> bool: class ResettingPage(NavWidget): + DOT_STEP = 0.6 + def __init__(self): super().__init__() + self._show_time = 0.0 self._resetting_card = GreyBigButton("resetting device", "this may take up to\na minute...", gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) @@ -44,11 +48,15 @@ def __init__(self): def show_event(self): super().show_event() self._nav_bar._alpha = 0.0 # not dismissable + self._show_time = rl.get_time() def _back_enabled(self) -> bool: return False def _render(self, _): + t = (rl.get_time() - self._show_time) % (self.DOT_STEP * 2) + dots = "." * min(int(t / (self.DOT_STEP / 4)), 3) + self._resetting_card.set_value(f"this may take up to\na minute{dots}") self._resetting_card.render(rl.Rectangle( self._rect.x + self._rect.width / 2 - self._resetting_card.rect.width / 2, self._rect.y + self._rect.height / 2 - self._resetting_card.rect.height / 2, @@ -68,9 +76,9 @@ def __init__(self, mode): self._resetting_page = ResettingPage() self._reset_failed_page = ResetFailedPage() - self._reset_button = BigConfirmationCircleButton("erase\ndevice", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64), + self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70), self._start_reset, red=True) - self._cancel_button = BigConfirmationCircleButton("normal\nstartup", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), HARDWARE.reboot, exit_on_confirm=False) @@ -79,18 +87,22 @@ def __init__(self, mode): self._cancel_button.set_visible(mode != ResetMode.RECOVER) self._reboot_button.set_visible(mode == ResetMode.RECOVER) - main_card = GreyBigButton("factory reset", "all content and\nsettings will be erased", + main_card = GreyBigButton("factory reset", "resetting erases\nall user content & data", gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + self._scroller.add_widget(main_card) - if mode == ResetMode.RECOVER: - main_card.set_text("unable to mount\ndata partition") - main_card.set_value("it may be corrupted") + if mode != ResetMode.USER_RESET: + self._scroller.add_widget(GreyBigButton("", "Resetting erases all user content & data.")) + if mode == ResetMode.RECOVER: + main_card.set_value("user data partition\ncould not be mounted") + elif mode == ResetMode.TAP_RESET: + main_card.set_value("reset triggered by\ntapping the screen") self._scroller.add_widgets([ - main_card, - self._reset_button, + GreyBigButton("", "For a deeper reset, go to\nhttps://flash.comma.ai"), self._cancel_button, self._reboot_button, + self._reset_button, ]) def _do_erase(self): @@ -131,6 +143,8 @@ def main(): if len(sys.argv) > 1: if sys.argv[1] == '--recover': mode = ResetMode.RECOVER + elif sys.argv[1] == '--tap-reset': + mode = ResetMode.TAP_RESET gui_app.init_window("System Reset") reset = Reset(mode) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 40b4c7a8819..9a339d1a7df 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -254,7 +254,7 @@ def __init__(self, retry_callback: Callable | None, title: str = "download faile GreyBigButton(title, description or "swipe down to go\nback and try again", gui_app.texture(icon, 64, 58)), self._reason_card, - BigConfirmationCircleButton("slide to reboot", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), HARDWARE.reboot, exit_on_confirm=False), ]) From 6607283cec77308886714ae8a0a6d49b23c9b270 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 02:17:36 -0800 Subject: [PATCH 496/685] mici ui: engaged confirmation buttons (#37589) * do deviec * clean up * clean up * todo * action text * back --- selfdrive/ui/mici/layouts/settings/device.py | 55 ++++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index d4c8fda5cef..2be32d66bd1 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -64,33 +64,31 @@ def _render(self, rect: rl.Rectangle): rl.draw_texture_ex(self._fcc_logo, fcc_pos, 0.0, 1.0, rl.WHITE) -def _engaged_confirmation_callback(callback: Callable, action_text: str): +def _engaged_confirmation_click(callback: Callable, action_text: str, icon: rl.Texture, exit_on_confirm: bool = True, red: bool = False): if not ui_state.engaged: def confirm_callback(): # Check engaged again in case it changed while the dialog was open + # TODO: if true, we stay on the dialog if not exit_on_confirm until normal onroad timeout if not ui_state.engaged: callback() - red = False - if action_text == "power off": - icon = gui_app.texture("icons_mici/settings/device/power.png", 64, 66) - red = True - elif action_text == "reboot": - icon = gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70) - elif action_text == "reset": - icon = gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64) - elif action_text == "uninstall": - icon = gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64) - else: - # TODO: check - icon = gui_app.texture("icons_mici/settings/comma_icon.png", 36, 64) - - dlg: BigConfirmationDialog | BigDialog = BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, - red=red, exit_on_confirm=action_text == "reset") - gui_app.push_widget(dlg) + gui_app.push_widget(BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, exit_on_confirm=exit_on_confirm, red=red)) else: - dlg = BigDialog(f"Disengage to {action_text}", "") - gui_app.push_widget(dlg) + gui_app.push_widget(BigDialog(f"Disengage to {action_text}", "")) + + +class EngagedConfirmationCircleButton(BigCircleButton): + def __init__(self, title: str, icon: rl.Texture, callback: Callable[[], None], exit_on_confirm: bool = True, + red: bool = False, icon_offset: tuple[int, int] = (0, 0)): + super().__init__(icon, red, icon_offset) + self.set_click_callback(lambda: _engaged_confirmation_click(callback, title, icon, exit_on_confirm=exit_on_confirm, red=red)) + + +class EngagedConfirmationButton(BigButton): + def __init__(self, text: str, action_text: str, icon: rl.Texture, callback: Callable[[], None], + exit_on_confirm: bool = True, red: bool = False): + super().__init__(text, "", icon) + self.set_click_callback(lambda: _engaged_confirmation_click(callback, action_text, icon, exit_on_confirm=exit_on_confirm, red=red)) class DeviceInfoLayoutMici(Widget): @@ -310,17 +308,18 @@ def reset_calibration_callback(): def uninstall_openpilot_callback(): ui_state.params.put_bool("DoUninstall", True) - reset_calibration_btn = BigButton("reset calibration", "", gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64)) - reset_calibration_btn.set_click_callback(lambda: _engaged_confirmation_callback(reset_calibration_callback, "reset")) + reset_calibration_btn = EngagedConfirmationButton("reset calibration", "reset", gui_app.texture("icons_mici/settings/device/lkas.png", 122, 64), + reset_calibration_callback) - uninstall_openpilot_btn = BigButton("uninstall openpilot", "", gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64)) - uninstall_openpilot_btn.set_click_callback(lambda: _engaged_confirmation_callback(uninstall_openpilot_callback, "uninstall")) + uninstall_openpilot_btn = EngagedConfirmationButton("uninstall openpilot", "uninstall", + gui_app.texture("icons_mici/settings/device/uninstall.png", 64, 64), + uninstall_openpilot_callback, exit_on_confirm=False) - reboot_btn = BigCircleButton(gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70)) - reboot_btn.set_click_callback(lambda: _engaged_confirmation_callback(reboot_callback, "reboot")) + reboot_btn = EngagedConfirmationCircleButton("reboot", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), + reboot_callback, exit_on_confirm=False) - self._power_off_btn = BigCircleButton(gui_app.texture("icons_mici/settings/device/power.png", 64, 66), red=True) - self._power_off_btn.set_click_callback(lambda: _engaged_confirmation_callback(power_off_callback, "power off")) + self._power_off_btn = EngagedConfirmationCircleButton("power off", gui_app.texture("icons_mici/settings/device/power.png", 64, 66), + power_off_callback, exit_on_confirm=False, red=True) self._power_off_btn.set_visible(lambda: not ui_state.ignition) regulatory_btn = BigButton("regulatory info", "", gui_app.texture("icons_mici/settings/device/info.png", 64, 64)) From e35513afc458e4c7b970274ef30ecc8f0cc58629 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 02:55:10 -0800 Subject: [PATCH 497/685] ui: fix 1px overshoot on NavWidget show (#37593) fix --- system/ui/widgets/nav_widget.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 5cf8715c0c1..d397fe441ad 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -149,8 +149,9 @@ def _update_state(self): new_y = self._rect.height + DISMISS_PUSH_OFFSET new_y = round(self._y_pos_filter.update(new_y)) - if abs(new_y) < 1 and self._y_pos_filter.velocity.x == 0.0: + if abs(new_y) < 1 and abs(self._y_pos_filter.velocity.x) < 0.5: new_y = self._y_pos_filter.x = 0.0 + self._y_pos_filter.velocity.x = 0.0 if self._shown_callback is not None: self._shown_callback() @@ -223,6 +224,7 @@ def show_event(self): # Start NavWidget off-screen, no matter how tall it is self._y_pos_filter.update_alpha(0.1) self._y_pos_filter.x = gui_app.height + self._y_pos_filter.velocity.x = 0.0 self._nav_bar_y_filter.x = -NAV_BAR_MARGIN - NAV_BAR_HEIGHT self._nav_bar_show_time = rl.get_time() From 024e2af2692b77033f7d04c46d3b3bda3fbe9c73 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 03:10:29 -0800 Subject: [PATCH 498/685] slider: use self.confirmed --- system/ui/widgets/slider.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 203c9eb4a00..43fb97be802 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -100,7 +100,7 @@ def _update_state(self): activated_pos = int(-self._bg_txt.width + self._circle_bg_txt.width) self._scroll_x_circle = max(min(self._scroll_x_circle, 0), activated_pos) - if self._confirmed_time > 0: + if self.confirmed: # swiped left to confirm self._scroll_x_circle_filter.update(activated_pos) @@ -129,7 +129,7 @@ def _render(self, _): btn_x = bg_txt_x + self._bg_txt.width - self._circle_bg_txt.width + self._scroll_x_circle_filter.x btn_y = self._rect.y + (self._rect.height - self._circle_bg_txt.height) / 2 - if self._confirmed_time == 0.0 or self._scroll_x_circle > 0: + if not self.confirmed: self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.65 * (1.0 - self.slider_percentage) * self._opacity_filter.x))) label_rect = rl.Rectangle( self._rect.x + 20, @@ -140,7 +140,7 @@ def _render(self, _): self._label.render(label_rect) # circle and arrow - circle_bg_txt = self._circle_bg_pressed_txt if self._is_dragging_circle or self._confirmed_time > 0 else self._circle_bg_txt + circle_bg_txt = self._circle_bg_pressed_txt if self._is_dragging_circle or self.confirmed else self._circle_bg_txt rl.draw_texture_ex(circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) arrow_x = btn_x + (self._circle_bg_txt.width - self._circle_arrow_txt.width) / 2 From 797b769478e2b1fc2d640da3d50ad69c123647b1 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 04:32:47 -0800 Subject: [PATCH 499/685] ui: sliders bounce (#37595) * sliders bounce * start page should bounce too * clean up * bouncy sliders * bouncy everything * tiny bounce * clean up * no scroll bounce --- system/ui/mici_setup.py | 4 ++-- system/ui/widgets/slider.py | 17 ++++++++++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 9a339d1a7df..33dfe98f37e 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -13,7 +13,7 @@ import pyray as rl from cereal import log -from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.filter_simple import BounceFilter from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.common.swaglog import cloudlog @@ -122,7 +122,7 @@ def __init__(self): self._start_bg_txt = gui_app.texture("icons_mici/setup/start_button.png", 500, 224, keep_aspect_ratio=False) self._start_bg_pressed_txt = gui_app.texture("icons_mici/setup/start_button_pressed.png", 500, 224, keep_aspect_ratio=False) - self._scale_filter = FirstOrderFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) self._click_delay = 0.075 def _render(self, rect: rl.Rectangle): diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 43fb97be802..900efed73af 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -6,12 +6,13 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.common.filter_simple import FirstOrderFilter, BounceFilter class SliderBase(Widget, abc.ABC): HORIZONTAL_PADDING = 8 CONFIRM_DELAY = 0.2 + PRESSED_SCALE = 1.07 _bg_txt: rl.Texture _circle_bg_txt: rl.Texture @@ -33,6 +34,8 @@ def __init__(self, title: str, confirm_callback: Callable | None = None): self._start_x_circle = 0.0 self._scroll_x_circle = 0.0 self._scroll_x_circle_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) + self._circle_scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) + self._click_delay = 0.075 self._is_dragging_circle = False @@ -139,12 +142,16 @@ def _render(self, _): ) self._label.render(label_rect) - # circle and arrow - circle_bg_txt = self._circle_bg_pressed_txt if self._is_dragging_circle or self.confirmed else self._circle_bg_txt - rl.draw_texture_ex(circle_bg_txt, rl.Vector2(btn_x, btn_y), 0.0, 1.0, white) + # circle and arrow with grow animation + circle_pressed = self._is_dragging_circle or self.confirmed or self.is_pressed + circle_bg_txt = self._circle_bg_pressed_txt if circle_pressed else self._circle_bg_txt + scale = self._circle_scale_filter.update(self.PRESSED_SCALE if circle_pressed else 1.0) + scaled_btn_x = btn_x + (self._circle_bg_txt.width * (1 - scale)) / 2 + scaled_btn_y = btn_y + (self._circle_bg_txt.height * (1 - scale)) / 2 + rl.draw_texture_ex(circle_bg_txt, rl.Vector2(scaled_btn_x, scaled_btn_y), 0.0, scale, white) arrow_x = btn_x + (self._circle_bg_txt.width - self._circle_arrow_txt.width) / 2 - arrow_y = btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2 + arrow_y = scaled_btn_y + (self._circle_bg_txt.height - self._circle_arrow_txt.height) / 2 rl.draw_texture_ex(self._circle_arrow_txt, rl.Vector2(arrow_x, arrow_y), 0.0, 1.0, white) From 4bf2bfb122f86ef68659f9dad27693f4f3451e8b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:07:03 -0800 Subject: [PATCH 500/685] ui: child widget support (#37594) * child widgets! * cmt * missing * group * add debug flag * use in scroller * not clean yet * restore --- selfdrive/ui/mici/layouts/home.py | 1 + system/ui/widgets/__init__.py | 43 +++++++++++++++++++++++++++---- system/ui/widgets/nav_widget.py | 3 +-- system/ui/widgets/scroller.py | 10 +------ 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 730a7ca7b75..e87e62503e7 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -111,6 +111,7 @@ def __init__(self): self._version_commit_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) def show_event(self): + super().show_event() self._version_text = self._get_version_text() self._update_params() diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 568f58b9854..2a0dc7be2d0 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -3,6 +3,7 @@ import abc import pyray as rl from enum import IntEnum +from typing import TypeVar from collections.abc import Callable from openpilot.system.ui.lib.application import gui_app, MousePos, MAX_TOUCH_SLOTS, MouseEvent @@ -13,6 +14,10 @@ class Device: awake = True device = Device() +W = TypeVar('W', bound='Widget') + +DEBUG = True + class DialogResult(IntEnum): CANCEL = 0 @@ -24,11 +29,14 @@ class Widget(abc.ABC): def __init__(self): self._rect: rl.Rectangle = rl.Rectangle(0, 0, 0, 0) self._parent_rect: rl.Rectangle | None = None + self._children: list[Widget] = [] + + self._enabled: bool | Callable[[], bool] = True + self._is_visible: bool | Callable[[], bool] = True + self.__is_pressed = [False] * MAX_TOUCH_SLOTS # if current mouse/touch down started within the widget's rectangle self.__tracking_is_pressed = [False] * MAX_TOUCH_SLOTS - self._enabled: bool | Callable[[], bool] = True - self._is_visible: bool | Callable[[], bool] = True self._touch_valid_callback: Callable[[], bool] | None = None self._click_delay: float | None = None # seconds to hold is_pressed after release self._click_release_time: float | None = None @@ -197,12 +205,37 @@ def _handle_mouse_event(self, mouse_event: MouseEvent) -> None: """Optionally handle mouse events. This is called before rendering.""" # Default implementation does nothing, can be overridden by subclasses + def _child(self, widget: W) -> W: + """ + Register a widget as a child. Lifecycle events (show/hide) propagate to registered children. + - If the widget is pushed onto the nav stack, do NOT register it (gui_app manages its lifecycle). + - If the widget is rendered inline in _render(), register it. + """ + assert widget not in self._children, f"{type(widget).__name__} already a child of {type(self).__name__}" + self._children.append(widget) + return widget + + _show_hide_depth = 0 + def show_event(self): - """Optionally handle show event. Parent must manually call this""" - # TODO: iterate through all child objects, check for subclassing from Widget/Layout (Scroller) + """Called when widget becomes visible. Propagates to registered children.""" + if DEBUG: + print(f"{' ' * Widget._show_hide_depth}show_event: {type(self).__name__}") + Widget._show_hide_depth += 1 + for child in self._children: + child.show_event() + if DEBUG: + Widget._show_hide_depth -= 1 def hide_event(self): - """Optionally handle hide event. Parent must manually call this""" + """Called when widget is hidden. Propagates to registered children.""" + if DEBUG: + print(f"{' ' * Widget._show_hide_depth}hide_event: {type(self).__name__}") + Widget._show_hide_depth += 1 + for child in self._children: + child.hide_event() + if DEBUG: + Widget._show_hide_depth -= 1 def dismiss(self, callback: Callable[[], None] | None = None): """Immediately dismiss the widget, firing the callback after.""" diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index d397fe441ad..6f2bbd025b8 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -69,7 +69,7 @@ def __init__(self): self._shown_callback: Callable[[], None] | None = None # transient callback fired after show animation completes # TODO: move this state into NavBar - self._nav_bar = NavBar() + self._nav_bar = self._child(NavBar()) self._nav_bar_show_time = 0.0 self._nav_bar_y_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) @@ -214,7 +214,6 @@ def dismiss(self, callback: Callable[[], None] | None = None): def show_event(self): super().show_event() - self._nav_bar.show_event() # Reset state self._drag_start_pos = None diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 5becef79395..65f23739c1f 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -422,18 +422,10 @@ class Scroller(Widget): """Wrapper for _Scroller so that children do not need to call events or pass down enabled for nav stack.""" def __init__(self, **kwargs): super().__init__() - self._scroller = _Scroller([], **kwargs) + self._scroller = self._child(_Scroller([], **kwargs)) # pass down enabled to child widget for nav stack self._scroller.set_enabled(lambda: self.enabled) - def show_event(self): - super().show_event() - self._scroller.show_event() - - def hide_event(self): - super().hide_event() - self._scroller.hide_event() - def _render(self, _): self._scroller.render(self._rect) From 4742bf0230112dfbd12af50fa2f27b82885d4081 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:08:44 -0800 Subject: [PATCH 501/685] HBoxLayout: use children --- system/ui/widgets/layouts.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/system/ui/widgets/layouts.py b/system/ui/widgets/layouts.py index 6f97fe5ed85..6fd3bffd8c9 100644 --- a/system/ui/widgets/layouts.py +++ b/system/ui/widgets/layouts.py @@ -21,7 +21,6 @@ class HBoxLayout(Widget): def __init__(self, widgets: list[Widget] | None = None, spacing: int = 0, alignment: Alignment = Alignment.LEFT | Alignment.V_CENTER): super().__init__() - self._widgets: list[Widget] = [] self._spacing = spacing self._alignment = alignment @@ -31,13 +30,13 @@ def __init__(self, widgets: list[Widget] | None = None, spacing: int = 0, @property def widgets(self) -> list[Widget]: - return self._widgets + return self._children def add_widget(self, widget: Widget) -> None: - self._widgets.append(widget) + self._child(widget) def _render(self, _): - visible_widgets = [w for w in self._widgets if w.is_visible] + visible_widgets = [w for w in self._children if w.is_visible] cur_offset_x = 0 From 7a5d8a813b5dafcb9d5420b7807e2aacd7de2856 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:08:58 -0800 Subject: [PATCH 502/685] Turn off Widget debug mode --- system/ui/widgets/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/widgets/__init__.py b/system/ui/widgets/__init__.py index 2a0dc7be2d0..4ce1c1b694d 100644 --- a/system/ui/widgets/__init__.py +++ b/system/ui/widgets/__init__.py @@ -16,7 +16,7 @@ class Device: W = TypeVar('W', bound='Widget') -DEBUG = True +DEBUG = False class DialogResult(IntEnum): From 6e851ff886eb100b89f0ac726681ef284d82c87f Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:21:06 -0800 Subject: [PATCH 503/685] ui: missing super show event (#37597) missing --- selfdrive/ui/layouts/home.py | 1 + selfdrive/ui/layouts/settings/developer.py | 1 + selfdrive/ui/layouts/settings/device.py | 1 + selfdrive/ui/layouts/settings/software.py | 1 + selfdrive/ui/layouts/settings/toggles.py | 1 + selfdrive/ui/widgets/exp_mode_button.py | 1 + selfdrive/ui/widgets/offroad_alerts.py | 1 + system/ui/widgets/list_view.py | 1 + system/ui/widgets/network.py | 4 ++++ 9 files changed, 12 insertions(+) diff --git a/selfdrive/ui/layouts/home.py b/selfdrive/ui/layouts/home.py index a8404a20c3b..183c2d45888 100644 --- a/selfdrive/ui/layouts/home.py +++ b/selfdrive/ui/layouts/home.py @@ -62,6 +62,7 @@ def __init__(self): self._setup_callbacks() def show_event(self): + super().show_event() self._exp_mode_button.show_event() self.last_refresh = time.monotonic() self._refresh() diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index 17ab60172a5..bd064ab834b 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -100,6 +100,7 @@ def _render(self, rect): self._scroller.render(rect) def show_event(self): + super().show_event() self._scroller.show_event() self._update_toggles() diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 751373dba63..5c3dae869be 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -72,6 +72,7 @@ def _offroad_transition(self): self._power_off_btn.action_item.right_button.set_visible(ui_state.is_offroad()) def show_event(self): + super().show_event() self._scroller.show_event() def _render(self, rect): diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py index c197b45453e..83a66ef3bd0 100644 --- a/selfdrive/ui/layouts/settings/software.py +++ b/selfdrive/ui/layouts/settings/software.py @@ -80,6 +80,7 @@ def __init__(self): ], line_separator=True, spacing=0) def show_event(self): + super().show_event() self._scroller.show_event() def _render(self, rect): diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index dbe5e241aac..711392bdb02 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -148,6 +148,7 @@ def _update_state(self): ui_state.personality = personality def show_event(self): + super().show_event() self._scroller.show_event() self._update_toggles() diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index faa3bf877f0..0b5bff7da46 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -20,6 +20,7 @@ def __init__(self): self.experimental_pixmap = gui_app.texture("icons/experimental_grey.png", self.img_width, self.img_width) def show_event(self): + super().show_event() self.experimental_mode = self.params.get_bool("ExperimentalMode") def _get_gradient_colors(self): diff --git a/selfdrive/ui/widgets/offroad_alerts.py b/selfdrive/ui/widgets/offroad_alerts.py index a87727e27f9..110ca714a9d 100644 --- a/selfdrive/ui/widgets/offroad_alerts.py +++ b/selfdrive/ui/widgets/offroad_alerts.py @@ -118,6 +118,7 @@ def excessive_actuation_callback(): self.scroll_panel = GuiScrollPanel() def show_event(self): + super().show_event() self.scroll_panel.set_offset(0) def set_dismiss_callback(self, callback: Callable): diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 32bf01cfc86..1cf530a66aa 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -293,6 +293,7 @@ def __init__(self, title: str | Callable[[], str] = "", icon: str | None = None, self._prev_description: str | None = self.description def show_event(self): + super().show_event() self._set_description_visible(False) def set_description_opened_callback(self, callback: Callable) -> None: diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 668565a033a..9027dc4c2ac 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -75,10 +75,12 @@ def __init__(self, wifi_manager: WifiManager): self._nav_button.set_click_callback(self._cycle_panel) def show_event(self): + super().show_event() self._set_current_panel(PanelType.WIFI) self._wifi_panel.show_event() def hide_event(self): + super().hide_event() self._wifi_panel.hide_event() def _cycle_panel(self): @@ -299,10 +301,12 @@ def __init__(self, wifi_manager: WifiManager): disconnected=self._on_disconnected) def show_event(self): + super().show_event() # start/stop scanning when widget is visible self._wifi_manager.set_active(True) def hide_event(self): + super().hide_event() self._wifi_manager.set_active(False) def _load_icons(self): From 6a3dcc74e8c9357f632ac63d64815aff975cfd59 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 05:28:51 -0800 Subject: [PATCH 504/685] ui: mark more child widgets (#37596) * do onboarding * do tici * clean * hide event reset state :( --- selfdrive/ui/mici/layouts/onboarding.py | 5 +---- system/ui/widgets/network.py | 11 +++-------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index ac196287388..781f60ee216 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -266,12 +266,9 @@ def __init__(self, completed_callback: Callable[[], None]): TrainingGuideRecordFront(continue_callback=completed_callback), ] + self._child(self._steps[0]) self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack - def show_event(self): - super().show_event() - self._steps[0].show_event() - def _render(self, _): self._steps[0].render(self._rect) diff --git a/system/ui/widgets/network.py b/system/ui/widgets/network.py index 9027dc4c2ac..e739eef63d7 100644 --- a/system/ui/widgets/network.py +++ b/system/ui/widgets/network.py @@ -69,19 +69,14 @@ def __init__(self, wifi_manager: WifiManager): super().__init__() self._wifi_manager = wifi_manager self._current_panel: PanelType = PanelType.WIFI - self._wifi_panel = WifiManagerUI(wifi_manager) - self._advanced_panel = AdvancedNetworkSettings(wifi_manager) - self._nav_button = NavButton(tr("Advanced")) + self._wifi_panel = self._child(WifiManagerUI(wifi_manager)) + self._advanced_panel = self._child(AdvancedNetworkSettings(wifi_manager)) + self._nav_button = self._child(NavButton(tr("Advanced"))) self._nav_button.set_click_callback(self._cycle_panel) def show_event(self): super().show_event() self._set_current_panel(PanelType.WIFI) - self._wifi_panel.show_event() - - def hide_event(self): - super().hide_event() - self._wifi_panel.hide_event() def _cycle_panel(self): if self._current_panel == PanelType.WIFI: From acec60d19e5b08a397453512a153d55330dd21fd Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:23:20 -0600 Subject: [PATCH 505/685] docs: update WSL2 hardware acceleration note (#37603) * docs: update WSL2 hardware acceleration note for improved UI performance * space * clarify --- tools/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/README.md b/tools/README.md index d52c8f45225..90696ab4e6b 100644 --- a/tools/README.md +++ b/tools/README.md @@ -38,7 +38,7 @@ scons -u -j$(nproc) Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install) to setup the WSL and install the `Ubuntu-24.04` distribution. Once your Ubuntu WSL environment is setup, follow the Linux setup instructions to finish setting up your environment. See [these instructions](https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) for running GUI apps. -**NOTE**: If you are running WSL and any GUIs are failing (segfaulting or other strange issues) even after following the steps above, you may need to enable software rendering with `LIBGL_ALWAYS_SOFTWARE=1`, e.g. `LIBGL_ALWAYS_SOFTWARE=1 selfdrive/ui/ui`. +**NOTE**: If you are running WSL 2 and experiencing performance issues with the UI or simulator, you may need to explicitly enable hardware acceleration by setting `GALLIUM_DRIVER=d3d12` before commands. Add `export GALLIUM_DRIVER=d3d12` to your `~/.bashrc` file to make it automatic for future sessions. ## CTF Learn about the openpilot ecosystem and tools by playing our [CTF](/tools/CTF.md). From 9d7edbf57a52059adc2230553437031571849fd9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 7 Mar 2026 23:11:38 -0800 Subject: [PATCH 506/685] ui: remove MiciLabel (#37599) * unified * newl * do home too * pairing * match style * delete micilabel! * default color --- selfdrive/ui/mici/layouts/home.py | 18 +- selfdrive/ui/mici/layouts/settings/device.py | 13 +- selfdrive/ui/mici/widgets/pairing_dialog.py | 7 +- system/ui/widgets/label.py | 170 +------------------ 4 files changed, 28 insertions(+), 180 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index e87e62503e7..da5b0ac5e05 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -7,7 +7,7 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.layouts import HBoxLayout from openpilot.system.ui.widgets.icon_widget import IconWidget -from openpilot.system.ui.widgets.label import MiciLabel, UnifiedLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.version import RELEASE_BRANCHES @@ -103,12 +103,12 @@ def __init__(self): self._mic_icon, ], spacing=18) - self._openpilot_label = MiciLabel("openpilot", font_size=96, color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) - self._version_label = MiciLabel("", font_size=36, font_weight=FontWeight.ROMAN) - self._large_version_label = MiciLabel("", font_size=64, color=rl.GRAY, font_weight=FontWeight.ROMAN) - self._date_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._openpilot_label = UnifiedLabel("openpilot", font_size=96, font_weight=FontWeight.DISPLAY, max_width=480, wrap_text=False) + self._version_label = UnifiedLabel("", font_size=36, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) + self._large_version_label = UnifiedLabel("", font_size=64, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) + self._date_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) self._branch_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, scroll=True) - self._version_commit_label = MiciLabel("", font_size=36, color=rl.GRAY, font_weight=FontWeight.ROMAN) + self._version_commit_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False) def show_event(self): super().show_event() @@ -183,12 +183,12 @@ def _render(self, _): self._version_label.render() self._date_label.set_text(" " + self._version_text[3]) - self._date_label.set_position(version_pos.x + self._version_label.rect.width + 10, version_pos.y) + self._date_label.set_position(version_pos.x + self._version_label.text_width + 10, version_pos.y) self._date_label.render() - self._branch_label.set_max_width(gui_app.width - self._version_label.rect.width - self._date_label.rect.width - 32) + self._branch_label.set_max_width(gui_app.width - self._version_label.text_width - self._date_label.text_width - 32) self._branch_label.set_text(" " + ("release" if release_branch else self._version_text[1])) - self._branch_label.set_position(version_pos.x + self._version_label.rect.width + self._date_label.rect.width + 20, version_pos.y) + self._branch_label.set_position(version_pos.x + self._version_label.text_width + self._date_label.text_width + 20, version_pos.y) self._branch_label.render() if not release_branch: diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 2be32d66bd1..909e30ac719 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -17,7 +17,7 @@ from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.ui_state import device, ui_state -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID @@ -98,14 +98,15 @@ def __init__(self): self.set_rect(rl.Rectangle(0, 0, 360, 180)) params = Params() - header_color = rl.Color(255, 255, 255, int(255 * 0.9)) subheader_color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)) max_width = int(self._rect.width - 20) - self._dongle_id_label = MiciLabel("device ID", 48, width=max_width, color=header_color, font_weight=FontWeight.DISPLAY) - self._dongle_id_text_label = MiciLabel(params.get("DongleId") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + self._dongle_id_label = UnifiedLabel("device ID", 48, max_width=max_width, font_weight=FontWeight.DISPLAY, wrap_text=False) + self._dongle_id_text_label = UnifiedLabel(params.get("DongleId") or 'N/A', 32, max_width=max_width, text_color=subheader_color, + font_weight=FontWeight.ROMAN, wrap_text=False) - self._serial_number_label = MiciLabel("serial", 48, color=header_color, font_weight=FontWeight.DISPLAY) - self._serial_number_text_label = MiciLabel(params.get("HardwareSerial") or 'N/A', 32, width=max_width, color=subheader_color, font_weight=FontWeight.ROMAN) + self._serial_number_label = UnifiedLabel("serial", 48, max_width=max_width, font_weight=FontWeight.DISPLAY, wrap_text=False) + self._serial_number_text_label = UnifiedLabel(params.get("HardwareSerial") or 'N/A', 32, max_width=max_width, text_color=subheader_color, + font_weight=FontWeight.ROMAN, wrap_text=False) def _render(self, _): self._dongle_id_label.set_position(self._rect.x + 20, self._rect.y - 10) diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 991cb05a8cc..8421b516d24 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -9,7 +9,7 @@ from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.lib.application import FontWeight, gui_app -from openpilot.system.ui.widgets.label import MiciLabel +from openpilot.system.ui.widgets.label import UnifiedLabel class PairingDialog(NavWidget): @@ -24,8 +24,7 @@ def __init__(self): self._last_qr_generation = float("-inf") self._txt_pair = gui_app.texture("icons_mici/settings/device/pair.png", 33, 60) - self._pair_label = MiciLabel("pair with comma connect", 48, font_weight=FontWeight.BOLD, - color=rl.Color(255, 255, 255, int(255 * 0.9)), line_height=40, wrap_text=True) + self._pair_label = UnifiedLabel("pair with comma connect", font_size=48, font_weight=FontWeight.BOLD, line_height=0.8) def _get_pairing_url(self) -> str: try: @@ -77,7 +76,7 @@ def _render(self, rect: rl.Rectangle): self._render_qr_code() label_x = self._rect.x + 8 + self._rect.height + 24 - self._pair_label.set_width(int(self._rect.width - label_x)) + self._pair_label.set_max_width(int(self._rect.width - label_x)) self._pair_label.set_position(label_x, self._rect.y + 16) self._pair_label.render() diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 8ed9ec62f5c..0b2444ec65d 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -26,166 +26,6 @@ class ScrollState(IntEnum): SCROLLING = 1 -# TODO: merge anything new here to master -class MiciLabel(Widget): - def __init__(self, - text: str, - font_size: int = DEFAULT_TEXT_SIZE, - width: int | None = None, - color: rl.Color = DEFAULT_TEXT_COLOR, - font_weight: FontWeight = FontWeight.NORMAL, - alignment: int = rl.GuiTextAlignment.TEXT_ALIGN_LEFT, - alignment_vertical: int = rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, - spacing: int = 0, - line_height: int | None = None, - elide_right: bool = True, - wrap_text: bool = False, - scroll: bool = False): - super().__init__() - self.text = text - self.wrapped_text: list[str] = [] - self.font_size = font_size - self.width = width - self.color = color - self.font_weight = font_weight - self.alignment = alignment - self.alignment_vertical = alignment_vertical - self.spacing = spacing - self.line_height = line_height if line_height is not None else font_size - self.elide_right = elide_right - self.wrap_text = wrap_text - self._height = 0 - - # Scroll state - self.scroll = scroll - self._needs_scroll = False - self._scroll_offset = 0 - self._scroll_pause_t: float | None = None - self._scroll_state: ScrollState = ScrollState.STARTING - - assert not (self.scroll and self.wrap_text), "Cannot enable both scroll and wrap_text" - assert not (self.scroll and self.elide_right), "Cannot enable both scroll and elide_right" - - self.set_text(text) - - @property - def text_height(self): - return self._height - - def set_font_size(self, font_size: int): - self.font_size = font_size - self.set_text(self.text) - - def set_width(self, width: int): - self.width = width - self._rect.width = width - self.set_text(self.text) - - def set_text(self, txt: str): - self.text = txt - text_size = measure_text_cached(gui_app.font(self.font_weight), self.text, self.font_size, self.spacing) - if self.width is not None: - self._rect.width = self.width - else: - self._rect.width = text_size.x - - if self.wrap_text: - self.wrapped_text = wrap_text(gui_app.font(self.font_weight), self.text, self.font_size, int(self._rect.width)) - self._height = len(self.wrapped_text) * self.line_height - elif self.scroll: - self._needs_scroll = self.scroll and text_size.x > self._rect.width - self._rect.height = text_size.y - - def set_color(self, color: rl.Color): - self.color = color - - def set_font_weight(self, font_weight: FontWeight): - self.font_weight = font_weight - self.set_text(self.text) - - def _render(self, rect: rl.Rectangle): - # Only scissor when we know there is a single scrolling line - if self._needs_scroll: - rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height)) - - font = gui_app.font(self.font_weight) - - text_y_offset = 0 - # Draw the text in the specified rectangle - lines = self.wrapped_text or [self.text] - if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: - lines = lines[::-1] - - for display_text in lines: - text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) - - # Elide text to fit within the rectangle - if self.elide_right and text_size.x > rect.width: - ellipsis = "..." - left, right = 0, len(display_text) - while left < right: - mid = (left + right) // 2 - candidate = display_text[:mid] + ellipsis - candidate_size = measure_text_cached(font, candidate, self.font_size, self.spacing) - if candidate_size.x <= rect.width: - left = mid + 1 - else: - right = mid - display_text = display_text[: left - 1] + ellipsis if left > 0 else ellipsis - text_size = measure_text_cached(font, display_text, self.font_size, self.spacing) - - # Handle scroll state - elif self.scroll and self._needs_scroll: - if self._scroll_state == ScrollState.STARTING: - if self._scroll_pause_t is None: - self._scroll_pause_t = rl.get_time() + 2.0 - if rl.get_time() >= self._scroll_pause_t: - self._scroll_state = ScrollState.SCROLLING - self._scroll_pause_t = None - - elif self._scroll_state == ScrollState.SCROLLING: - self._scroll_offset -= 0.8 / 60. * gui_app.target_fps - # don't fully hide - if self._scroll_offset <= -text_size.x - self._rect.width / 3: - self._scroll_offset = 0 - self._scroll_state = ScrollState.STARTING - self._scroll_pause_t = None - - # Calculate horizontal position based on alignment - text_x = rect.x + { - rl.GuiTextAlignment.TEXT_ALIGN_LEFT: 0, - rl.GuiTextAlignment.TEXT_ALIGN_CENTER: (rect.width - text_size.x) / 2, - rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: rect.width - text_size.x, - }.get(self.alignment, 0) + self._scroll_offset - - # Calculate vertical position based on alignment - text_y = rect.y + { - rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP: 0, - rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE: (rect.height - text_size.y) / 2, - rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: rect.height - text_size.y, - }.get(self.alignment_vertical, 0) - text_y += text_y_offset - - rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x), text_y), self.font_size, self.spacing, self.color) - # Draw 2nd instance for scrolling - if self._needs_scroll and self._scroll_state != ScrollState.STARTING: - text2_scroll_offset = text_size.x + self._rect.width / 3 - rl.draw_text_ex(font, display_text, rl.Vector2(round(text_x + text2_scroll_offset), text_y), self.font_size, self.spacing, self.color) - if self.alignment_vertical == rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM: - text_y_offset -= self.line_height - else: - text_y_offset += self.line_height - - if self._needs_scroll: - # draw black fade on left and right - fade_width = 20 - rl.draw_rectangle_gradient_h(int(rect.x + rect.width - fade_width), int(rect.y), fade_width, int(rect.height), rl.BLANK, rl.BLACK) - if self._scroll_state != ScrollState.STARTING: - rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), fade_width, int(rect.height), rl.BLACK, rl.BLANK) - - rl.end_scissor_mode() - - # TODO: This should be a Widget class def gui_label( rect: rl.Rectangle, @@ -392,7 +232,7 @@ def _render(self, _): class UnifiedLabel(Widget): """ - Unified label widget that combines functionality from gui_label, gui_text_box, Label, and MiciLabel. + Unified label widget that combines functionality from gui_label, gui_text_box, and Label. Supports: - Emoji rendering @@ -467,6 +307,14 @@ def text(self) -> str: """Get the current text content.""" return str(_resolve_value(self._text)) + @property + def font_size(self) -> int: + return self._font_size + + @property + def text_width(self) -> float: + return max((s.x for s in self._cached_line_sizes), default=0.0) + def set_text_color(self, color: rl.Color): """Update the text color.""" self._text_color = color From 1197ea9ab92223672ea3e9487ecc199a49113e1b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sun, 8 Mar 2026 00:13:08 -0800 Subject: [PATCH 507/685] sliders: fix clicking anywhere activates press (#37605) * fix * finish * fix --- system/ui/widgets/slider.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 900efed73af..7a10548acc7 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -35,7 +35,7 @@ def __init__(self, title: str, confirm_callback: Callable | None = None): self._scroll_x_circle = 0.0 self._scroll_x_circle_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) self._circle_scale_filter = BounceFilter(1.0, 0.1, 1 / gui_app.target_fps) - self._click_delay = 0.075 + self._circle_press_time: float | None = None self._is_dragging_circle = False @@ -54,6 +54,7 @@ def confirmed(self) -> bool: def reset(self): # reset all slider state self._is_dragging_circle = False + self._circle_press_time = None self._confirmed_time = 0.0 self._confirm_callback_called = False @@ -86,6 +87,7 @@ def _handle_mouse_event(self, mouse_event): if rl.check_collision_point_rec(mouse_event.pos, circle_button_rect): self._start_x_circle = mouse_event.pos.x self._is_dragging_circle = True + self._circle_press_time = rl.get_time() elif mouse_event.left_released: # swiped to left @@ -143,7 +145,7 @@ def _render(self, _): self._label.render(label_rect) # circle and arrow with grow animation - circle_pressed = self._is_dragging_circle or self.confirmed or self.is_pressed + circle_pressed = self._is_dragging_circle or self.confirmed or (self._circle_press_time is not None and rl.get_time() - self._circle_press_time < 0.075) circle_bg_txt = self._circle_bg_pressed_txt if circle_pressed else self._circle_bg_txt scale = self._circle_scale_filter.update(self.PRESSED_SCALE if circle_pressed else 1.0) scaled_btn_x = btn_x + (self._circle_bg_txt.width * (1 - scale)) / 2 From 6e87e66bc53dac055d49b9a72bcca13c3772b569 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 11:54:15 -0700 Subject: [PATCH 508/685] 0.11 time --- RELEASES.md | 6 +++--- common/version.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index d6d22d48913..bf4e0c75aac 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,11 +1,11 @@ -Version 0.10.4 (2026-02-17) +Version 0.11.0 (2026-03-17) ======================== * New driving model #36798 * Fully trained using a learned simulator - * Improved longitudinal performance in experimental mode + * Improved longitudinal performance in Experimental mode +* Reduce comma four standby power usage by 77% to 52 mW * Kia K7 2017 support thanks to royjr! * Lexus LS 2018 support thanks to Hacheoy! -* Reduce comma four standby power usage by 77% to 52 mW Version 0.10.3 (2025-12-17) ======================== diff --git a/common/version.h b/common/version.h index 7e78d64b223..78264e074cd 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.10.4" +#define COMMA_VERSION "0.11.0" From 9510e05dc0012757f6156f012fc37f87f6340d06 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 18:07:05 -0700 Subject: [PATCH 509/685] setup & reset tuneups (#37611) * period * no exit there * fasle * edit those * swipe down to go back * fix weird animation --- system/ui/mici_reset.py | 2 +- system/ui/mici_setup.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 2f89b6d62a3..c547d9185db 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -77,7 +77,7 @@ def __init__(self, mode): self._reset_failed_page = ResetFailedPage() self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70), - self._start_reset, red=True) + self._start_reset, exit_on_confirm=False, red=True) self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 33dfe98f37e..4e340335b98 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -144,7 +144,7 @@ def __init__(self, use_openpilot_callback: Callable, self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to install\nother software", use_custom_software_callback, green=False) + self._custom_software_slider = LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -190,11 +190,11 @@ def __init__(self, continue_callback: Callable, back_callback: Callable): self._continue_button.set_click_callback(continue_callback) self._scroller.add_widgets([ - GreyBigButton("use caution", "when installing\n3rd party software", + GreyBigButton("caution: installing\n3rd party software", "swipe down to go back", gui_app.texture("icons_mici/setup/warning.png", 64, 58)), - GreyBigButton("", "• It has not been tested by comma"), - GreyBigButton("", "• It may not comply with relevant safety standards."), - GreyBigButton("", "• It may cause damage to your device and/or vehicle."), + GreyBigButton("", "• It has not been tested by comma."), + GreyBigButton("", "• It may not comply with safety standards."), + GreyBigButton("", "• It may damage your device and/or vehicle."), GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai", gui_app.texture("icons_mici/setup/restore.png", 64, 64)), self._continue_button, @@ -546,7 +546,7 @@ def _use_openpilot(self): def _push_network_setup(self, custom_software: bool = False): # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) - gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._network_setup_page)) + gui_app.push_widget(self._network_setup_page) def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: From e42ee228c2dce761bb339fdb74d690ceec783a4c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 18:31:11 -0700 Subject: [PATCH 510/685] gitignore cleanups (#37615) * gitignore cleanups * lil more * one more --- .gitignore | 36 ++++++++---------------- common/.gitignore | 1 - common/transformations/.gitignore | 2 -- conftest.py | 1 - selfdrive/assets/.gitignore | 2 -- selfdrive/car/tests/.gitignore | 1 - selfdrive/controls/.gitignore | 2 -- selfdrive/locationd/.gitignore | 2 -- selfdrive/locationd/test/.gitignore | 1 - selfdrive/test/.gitignore | 2 +- selfdrive/test/process_replay/.gitignore | 1 - selfdrive/ui/.gitignore | 3 ++ selfdrive/ui/tests/.gitignore | 2 -- selfdrive/ui/tests/diff/.gitignore | 2 -- third_party/.gitignore | 1 - tools/bodyteleop/.gitignore | 4 --- tools/cabana/assets/.gitignore | 1 - tools/plotjuggler/.gitignore | 3 -- tools/replay/.gitignore | 3 -- 19 files changed, 15 insertions(+), 55 deletions(-) delete mode 100644 common/.gitignore delete mode 100644 common/transformations/.gitignore delete mode 100644 selfdrive/car/tests/.gitignore delete mode 100644 selfdrive/controls/.gitignore delete mode 100644 selfdrive/locationd/.gitignore delete mode 100644 selfdrive/locationd/test/.gitignore delete mode 100644 selfdrive/test/process_replay/.gitignore delete mode 100644 selfdrive/ui/tests/.gitignore delete mode 100644 selfdrive/ui/tests/diff/.gitignore delete mode 100644 third_party/.gitignore delete mode 100644 tools/bodyteleop/.gitignore delete mode 100644 tools/cabana/assets/.gitignore delete mode 100644 tools/plotjuggler/.gitignore diff --git a/.gitignore b/.gitignore index 062801d7874..1f58a371e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,13 +13,13 @@ venv/ a.out .hypothesis .cache/ - -/docs_site/ +bin/ *.mp4 *.dylib *.DSYM *.d +*.pem *.pyc *.pyo .*.swp @@ -39,11 +39,13 @@ a.out *.mo *_pyx.cpp *.stats +*.pkl +*.pkl* config.json -clcache compile_commands.json compare_runtime*.html +# build artifacts selfdrive/pandad/pandad cereal/services.h cereal/gen @@ -56,46 +58,30 @@ system/camerad/test/ae_gray_test .coverage* coverage.xml htmlcov -pandaextra - -.mypy_cache/ -flycheck_* - -cppcheck_report.txt -comma*.sh - -selfdrive/modeld/models/*.pkl* # openpilot log files *.bz2 *.zst +*.rlog build/ !**/.gitkeep -poetry.toml -Pipfile ### VisualStudioCode ### +*.vsix +.history +.ionide .vscode/* +.history/ !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json !.vscode/*.code-snippets -# Local History for Visual Studio Code -.history/ - -# Built Visual Studio Code Extensions -*.vsix - -### VisualStudioCode Patch ### -# Ignore all local history of files -.history -.ionide - +# agents .claude/ .context/ PLAN.md diff --git a/common/.gitignore b/common/.gitignore deleted file mode 100644 index ce1da4c53c0..00000000000 --- a/common/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.cpp diff --git a/common/transformations/.gitignore b/common/transformations/.gitignore deleted file mode 100644 index a67290f09ae..00000000000 --- a/common/transformations/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -transformations -transformations.cpp diff --git a/conftest.py b/conftest.py index 7e40ec3ed7a..a01ddc2f6bd 100644 --- a/conftest.py +++ b/conftest.py @@ -10,7 +10,6 @@ # TODO: pytest-cpp doesn't support FAIL, and we need to create test translations in sessionstart # pending https://github.com/pytest-dev/pytest-cpp/pull/147 collect_ignore = [ - "selfdrive/ui/tests/test_translations", "selfdrive/test/process_replay/test_processes.py", "selfdrive/test/process_replay/test_regen.py", ] diff --git a/selfdrive/assets/.gitignore b/selfdrive/assets/.gitignore index fffd4b4ed9c..2d97f8b111f 100644 --- a/selfdrive/assets/.gitignore +++ b/selfdrive/assets/.gitignore @@ -1,4 +1,2 @@ -*.cc fonts/*.fnt fonts/*.png -translations_assets.qrc diff --git a/selfdrive/car/tests/.gitignore b/selfdrive/car/tests/.gitignore deleted file mode 100644 index 192fb0945ec..00000000000 --- a/selfdrive/car/tests/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.bz2 diff --git a/selfdrive/controls/.gitignore b/selfdrive/controls/.gitignore deleted file mode 100644 index 22a371d8ffd..00000000000 --- a/selfdrive/controls/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -calibration_param -traces diff --git a/selfdrive/locationd/.gitignore b/selfdrive/locationd/.gitignore deleted file mode 100644 index 1a8c72388ad..00000000000 --- a/selfdrive/locationd/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -params_learner -paramsd diff --git a/selfdrive/locationd/test/.gitignore b/selfdrive/locationd/test/.gitignore deleted file mode 100644 index 89f9ac04aac..00000000000 --- a/selfdrive/locationd/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -out/ diff --git a/selfdrive/test/.gitignore b/selfdrive/test/.gitignore index 5801faadf4e..b8c6bebd953 100644 --- a/selfdrive/test/.gitignore +++ b/selfdrive/test/.gitignore @@ -3,7 +3,7 @@ docker_out/ process_replay/diff.txt process_replay/model_diff.txt +process_replay/fakedata/ valgrind_logs.txt -*.bz2 *.hevc diff --git a/selfdrive/test/process_replay/.gitignore b/selfdrive/test/process_replay/.gitignore deleted file mode 100644 index a35cd58d415..00000000000 --- a/selfdrive/test/process_replay/.gitignore +++ /dev/null @@ -1 +0,0 @@ -fakedata/ diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 945928f6178..30ae77d885e 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -1 +1,4 @@ installer/installers/* + +tests/diff/report +.coverage diff --git a/selfdrive/ui/tests/.gitignore b/selfdrive/ui/tests/.gitignore deleted file mode 100644 index 74ab2675dbf..00000000000 --- a/selfdrive/ui/tests/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -test -test_translations diff --git a/selfdrive/ui/tests/diff/.gitignore b/selfdrive/ui/tests/diff/.gitignore deleted file mode 100644 index e21a8d896e9..00000000000 --- a/selfdrive/ui/tests/diff/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -report -.coverage diff --git a/third_party/.gitignore b/third_party/.gitignore deleted file mode 100644 index 0d20b6487c6..00000000000 --- a/third_party/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.pyc diff --git a/tools/bodyteleop/.gitignore b/tools/bodyteleop/.gitignore deleted file mode 100644 index adeab99a95e..00000000000 --- a/tools/bodyteleop/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -av -av-10.0.0/* -key.pem -cert.pem \ No newline at end of file diff --git a/tools/cabana/assets/.gitignore b/tools/cabana/assets/.gitignore deleted file mode 100644 index 283034ca8b4..00000000000 --- a/tools/cabana/assets/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.cc diff --git a/tools/plotjuggler/.gitignore b/tools/plotjuggler/.gitignore deleted file mode 100644 index 45559d0b091..00000000000 --- a/tools/plotjuggler/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -bin/ -bin -*.rlog diff --git a/tools/replay/.gitignore b/tools/replay/.gitignore index 83f0e99a8b9..aa615770a24 100644 --- a/tools/replay/.gitignore +++ b/tools/replay/.gitignore @@ -1,5 +1,2 @@ -moc_* -*.moc - replay tests/test_replay From 71290f38054197421aecae24748a1be993a19464 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 19:16:38 -0700 Subject: [PATCH 511/685] cabana: gitignore assets.cc --- tools/cabana/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 3d64f832046..1ee6c922366 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -1,6 +1,8 @@ moc_* *.moc +assets.cc + _cabana dbc/car_fingerprint_to_dbc.json tests/test_cabana From ad181ba501a36164cc891b8ff1dbdc6daf2647ed Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 8 Mar 2026 20:54:31 -0700 Subject: [PATCH 512/685] agnos 17 (#37552) --- SConstruct | 21 ++-------- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 22 +++++------ system/hardware/tici/all-partitions.json | 49 +++++++++--------------- system/qcomgpsd/qcomgpsd.py | 40 ++++++++++++++----- system/qcomgpsd/tests/test_qcomgpsd.py | 22 +++++------ 6 files changed, 77 insertions(+), 79 deletions(-) diff --git a/SConstruct b/SConstruct index 18bf040cf07..3f974f09c16 100644 --- a/SConstruct +++ b/SConstruct @@ -4,6 +4,7 @@ import sys import sysconfig import platform import shlex +import importlib import numpy as np import SCons.Errors @@ -38,23 +39,9 @@ assert arch in [ "Darwin", # macOS arm64 (x86 not supported) ] -if arch != "larch64": - import bzip2 - import capnproto - import eigen - import ffmpeg as ffmpeg_pkg - import libjpeg - import libyuv - import ncurses - import python3_dev - import zeromq - import zstd - pkgs = [bzip2, capnproto, eigen, ffmpeg_pkg, libjpeg, libyuv, ncurses, zeromq, zstd] - py_include = python3_dev.INCLUDE_DIR -else: - # TODO: remove when AGNOS has our new vendor pkgs - pkgs = [] - py_include = sysconfig.get_paths()['include'] +pkg_names = ['bzip2', 'capnproto', 'eigen', 'ffmpeg', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd'] +pkgs = [importlib.import_module(name) for name in pkg_names] +py_include = importlib.import_module('python3_dev').INCLUDE_DIR env = Environment( ENV={ diff --git a/launch_env.sh b/launch_env.sh index 314366f429a..097f5fbe948 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="16" + export AGNOS_VERSION="17" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index e33a26bb2e4..ec5f43fa7b9 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", - "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "size": 17496064, + "url": "https://commadist.azureedge.net/agnosupdate/boot-1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0.img.xz", + "hash": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "hash_raw": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" + "ondevice_hash": "a64fc06e2508dbb2af4fa20808c10009bb5a31517ff39b0fb1dad882c9bec808" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", - "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", - "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img.xz", + "hash": "ef7ad7d290d74285e9e73f30442d2adc8ff3a616e1d7ddcceee6ba5420aa2fb7", + "hash_raw": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", + "ondevice_hash": "bfe5187bb754fd0df5069d0b2b12f79f5938588f97bb87063532da5d6e7a1cfb", "alt": { - "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", + "hash": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index b6718fe97db..8b4c0d43125 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,62 +339,51 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756.img.xz", - "hash": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "hash_raw": "a0185fa5ffc860de2179e4d0fec703fef6d560eacd730f79f60891ca79c72756", - "size": 17496064, + "url": "https://commadist.azureedge.net/agnosupdate/boot-1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0.img.xz", + "hash": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "hash_raw": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "0ee1ab104bb46d0f72e7d0b7d3e94629a7644a368896c6d4c558554fb955a08a" + "ondevice_hash": "a64fc06e2508dbb2af4fa20808c10009bb5a31517ff39b0fb1dad882c9bec808" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img.xz", - "hash": "7c58308be461126677ba02e9c9739556520ee02958934733867d86ecfe2e58e9", - "hash_raw": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img.xz", + "hash": "ef7ad7d290d74285e9e73f30442d2adc8ff3a616e1d7ddcceee6ba5420aa2fb7", + "hash_raw": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "826790516410c325aa30265846946d06a556f0a7b23c957f65fd11c055a663da", + "ondevice_hash": "bfe5187bb754fd0df5069d0b2b12f79f5938588f97bb87063532da5d6e7a1cfb", "alt": { - "hash": "0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd", - "url": "https://commadist.azureedge.net/agnosupdate/system-0cf8cb01e40d05d6d325afe68b934a6c0dda3a56703b2ef3e3de637d754ae5dd.img", + "hash": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", + "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb.img.xz", - "hash": "aa0f0fe32187493e6135aee9e984d3f9705fc58560d537b34687bb6b51a38428", - "hash_raw": "ec31b8116125a95755adb32853c401c462a14a74f538535532bf2c34d72c60eb", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-3711c021ee7a6512c93452857893325a6ed845e4cfad52398b531eaede22f913.img.xz", + "hash": "67e0066cc5d2b7173f6280a7a8836d6681e2cd55b0f4a79eafac5b9fdd4fd7c8", + "hash_raw": "3711c021ee7a6512c93452857893325a6ed845e4cfad52398b531eaede22f913", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "9c916b7d05543d4608b0401bc867639f44ce9671639a1a6da83b6d58b4eaa1b4" + "ondevice_hash": "31fe9e6e1b4ae1fe267868daff1b56660c9b4345e6a6272fce161b30ba70ed4c" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11.img.xz", - "hash": "fa83d4b7096857136820b0b0a8785c90677256b054c5c14039cd7b9b1065a90b", - "hash_raw": "7f092cc841124c10300e43574e90e3367e983bfbe4faa0969024e79e5ce90b11", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-8fcd15c164625c81199d7d3cca11009c862efa3ae19112f57fb10b4202474363.img.xz", + "hash": "ccb3bde6f31b340816d8499a5a205ba0e9984c189451807501a76f05820c3f4c", + "hash_raw": "8fcd15c164625c81199d7d3cca11009c862efa3ae19112f57fb10b4202474363", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "1699e38de769eb32c21dfa6a5ac21eb3ad620a362c7b8abf1a2c0afe0f717530" - }, - { - "name": "userdata_30", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_30-3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed.img.xz", - "hash": "890910f20b1ad88a728ee822a47b1234eb3d70cab28ca8a935679c8c2d33cbe9", - "hash_raw": "3df2dcd5e1f426c90b090fdbcd1a95b035d96a4bdaf88d5517245db5ee84f5ed", - "size": 32212254720, - "sparse": true, - "full_check": true, - "has_ab": false, - "ondevice_hash": "8e7cb392dd6e49c7d59fa850be7d1f44901314c86ba9c88be5bb27a0cd1123c9" + "ondevice_hash": "4167c69304e8038050793dbde70ff230614146836d9d31e5eb83aa03b6ed787f" } ] \ No newline at end of file diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 59f5ac0b506..82b9ea927fd 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -7,7 +7,7 @@ import time import requests import shutil -import subprocess +from serial import Serial import datetime from multiprocessing import Process, Event from typing import NoReturn @@ -90,9 +90,24 @@ def try_setup_logs(diag, logs): return setup_logs(diag, logs) -@retry(attempts=3, delay=1.0) -def at_cmd(cmd: str) -> str | None: - return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') +AT_PORT = "/dev/modem_at0" + +@retry(attempts=5, delay=1.0) +def at_cmd(cmd: str) -> str: + with Serial(AT_PORT, baudrate=115200, timeout=5) as ser: + ser.reset_input_buffer() + ser.write(f"{cmd}\r".encode()) + lines = [] + while True: + line = ser.readline() + if not line: + raise RuntimeError(f"AT command timeout: {cmd}") + line = line.decode('utf-8', errors='replace').strip() + if line in ("OK", "ERROR") or line.startswith("+CME ERROR"): + break + if line and line != cmd: + lines.append(line) + return '\n'.join(lines) def gps_enabled() -> bool: return "QGPS: 1" in at_cmd("AT+QGPS?") @@ -131,6 +146,7 @@ def downloader_loop(event): @retry(attempts=5, delay=0.2, ignore_failure=True) def inject_assistance(): + import subprocess cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}" subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) cloudlog.info("successfully loaded assistance data") @@ -207,13 +223,19 @@ def teardown_quectel(diag): try_setup_logs(diag, []) -def wait_for_modem(cmd="AT+QGPS?"): +def wait_for_modem(): cloudlog.warning("waiting for modem to come up") + while not os.path.exists(AT_PORT): + time.sleep(0.5) + # wait until the modem GNSS subsystem responds while True: - ret = subprocess.call(f"mmcli -m any --timeout 10 --command=\"{cmd}\"", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) - if ret == 0: - return - time.sleep(0.1) + try: + resp = at_cmd("AT+QGPS?") + if "+QGPS:" in resp: + return + except Exception: + pass + time.sleep(0.5) def main() -> NoReturn: diff --git a/system/qcomgpsd/tests/test_qcomgpsd.py b/system/qcomgpsd/tests/test_qcomgpsd.py index 2fc6205ea4e..75e87075910 100644 --- a/system/qcomgpsd/tests/test_qcomgpsd.py +++ b/system/qcomgpsd/tests/test_qcomgpsd.py @@ -1,9 +1,7 @@ import os import pytest -import json import time import datetime -import subprocess import cereal.messaging as messaging from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem @@ -44,15 +42,13 @@ def _wait_for_output(self, t): return self.sm.updated['qcomGnss'] def test_no_crash_double_command(self): + wait_for_modem() at_cmd("AT+QGPSDEL=0") at_cmd("AT+QGPSDEL=0") def test_wait_for_modem(self): os.system("sudo systemctl stop ModemManager") managed_processes['qcomgpsd'].start() - assert not self._wait_for_output(5) - - os.system("sudo systemctl restart ModemManager") assert self._wait_for_output(30) def test_startup_time(self, subtests): @@ -61,7 +57,7 @@ def test_startup_time(self, subtests): os.system("sudo systemctl stop systemd-resolved") with subtests.test(internet=internet): managed_processes['qcomgpsd'].start() - assert self._wait_for_output(7) + assert self._wait_for_output(30) managed_processes['qcomgpsd'].stop() def test_turns_off_gnss(self, subtests): @@ -71,14 +67,15 @@ def test_turns_off_gnss(self, subtests): time.sleep(s) managed_processes['qcomgpsd'].stop() - ls = subprocess.check_output("mmcli -m any --location-status --output-json", shell=True, encoding='utf-8') - loc_status = json.loads(ls) - assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} + wait_for_modem() + resp = at_cmd("AT+QGPS?") + assert "+QGPS: 0" in resp def check_assistance(self, should_be_loaded): # after QGPSDEL: '+QGPSXTRADATA: 0,"1980/01/05,19:00:00"' # after loading: '+QGPSXTRADATA: 10080,"2023/06/24,19:00:00"' + wait_for_modem() out = at_cmd("AT+QGPSXTRADATA?") out = out.split("+QGPSXTRADATA:")[1].split("'")[0].strip() valid_duration, injected_time_str = out.split(",", 1) @@ -92,20 +89,23 @@ def check_assistance(self, should_be_loaded): assert injected_time_str[:] == '1980/01/05,19:00:00'[:] assert valid_duration == '0' + @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") def test_assistance_loading(self): managed_processes['qcomgpsd'].start() - assert self._wait_for_output(10) + assert self._wait_for_output(30) managed_processes['qcomgpsd'].stop() self.check_assistance(True) + @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") def test_no_assistance_loading(self): os.system("sudo systemctl stop systemd-resolved") managed_processes['qcomgpsd'].start() - assert self._wait_for_output(10) + assert self._wait_for_output(30) managed_processes['qcomgpsd'].stop() self.check_assistance(False) + @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") def test_late_assistance_loading(self): os.system("sudo systemctl stop systemd-resolved") From 76458d175fe34c0e8610914a413c23e9be257850 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 9 Mar 2026 09:33:00 -0700 Subject: [PATCH 513/685] [bot] Update translations (#37530) Update translations Co-authored-by: Vehicle Researcher --- selfdrive/ui/translations/app.pot | 965 +++++++++++------------- selfdrive/ui/translations/app_de.po | 737 +++++++----------- selfdrive/ui/translations/app_en.po | 715 +++++++----------- selfdrive/ui/translations/app_es.po | 749 +++++++----------- selfdrive/ui/translations/app_fr.po | 765 +++++++------------ selfdrive/ui/translations/app_ja.po | 712 +++++++---------- selfdrive/ui/translations/app_ko.po | 697 +++++++---------- selfdrive/ui/translations/app_pt-BR.po | 738 +++++++----------- selfdrive/ui/translations/app_th.po | 619 +++++++-------- selfdrive/ui/translations/app_tr.po | 726 +++++++----------- selfdrive/ui/translations/app_uk.po | 754 +++++++----------- selfdrive/ui/translations/app_zh-CHS.po | 670 +++++++--------- selfdrive/ui/translations/app_zh-CHT.po | 669 +++++++--------- 13 files changed, 3694 insertions(+), 5822 deletions(-) diff --git a/selfdrive/ui/translations/app.pot b/selfdrive/ui/translations/app.pot index abb6940a549..468ff35fd9d 100644 --- a/selfdrive/ui/translations/app.pot +++ b/selfdrive/ui/translations/app.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:51-0700\n" +"POT-Creation-Date: 2026-03-09 14:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,1113 +18,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format -msgid "OK" +msgid "Advanced" msgstr "" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format -msgid "Cancel" +msgid "Back" msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/network.py:201 #, python-format -msgid "Select" +msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:201 #, python-format -msgid "Advanced" +msgid "leave blank for automatic configuration" msgstr "" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:243 #, python-format -msgid "Back" +msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:257 +#, python-format +msgid "Enter new tethering password" +msgstr "" + +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: system/ui/widgets/network.py:135 -#, python-format -msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" - -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:141 #, python-format -msgid "default" +msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format -msgid "metered" +msgid "Enter password" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:316 #, python-format -msgid "unmetered" +msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:376 #, python-format -msgid "Wi-Fi Network Metered" +msgid "CONNECTING..." msgstr "" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format -msgid "Prevent large data uploads when on a metered Wi-Fi connection" +msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:132 #, python-format -msgid "IP Address" +msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:139 #, python-format -msgid "Hidden Network" +msgid "default" msgstr "" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:139 #, python-format -msgid "CONNECT" +msgid "metered" msgstr "" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:139 #, python-format -msgid "Enter APN" +msgid "unmetered" msgstr "" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:141 #, python-format -msgid "leave blank for automatic configuration" +msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:147 #, python-format -msgid "Enter password" +msgid "IP Address" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:152 #, python-format -msgid "for \"{}\"" +msgid "Hidden Network" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format -msgid "Enter SSID" +msgid "CONNECT" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:320 #, python-format -msgid "Enter new tethering password" +msgid "Wrong password" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format -msgid "Scanning Wi-Fi networks..." +msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:380 #, python-format -msgid "Wrong password" +msgid "FORGETTING..." msgstr "" -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format -msgid "Forget" +msgid "for \"{}\"" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format -msgid "CONNECTING..." +msgid "OK" msgstr "" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/option_dialog.py:37 #, python-format -msgid "FORGETTING..." +msgid "Select" msgstr "" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:29 -msgid "LOADING" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:30 -msgid "ADD" -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:31 -msgid "REMOVE" +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 +#, python-format +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "Enter your GitHub username" +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format -msgid "No SSH keys found" +msgid "Finish Setup" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format -msgid "Request timed out" +msgid "Pair device" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format -msgid "No SSH keys found for user '{}'" +msgid "Open" msgstr "" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format -msgid "Upgrade Now" +msgid "🔥 Firehose Mode 🔥" msgstr "" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format -msgid "Become a comma prime member at connect.comma.ai" +msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "" -#: selfdrive/ui/widgets/prime.py:44 -#, python-format -msgid "PRIME FEATURES:" +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +msgid "LOADING" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "Remote access" +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +msgid "ADD" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 -#, python-format -msgid "24/7 LTE connectivity" +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +msgid "REMOVE" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format -msgid "1 year of drive storage" +msgid "Enter your GitHub username" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format -msgid "Remote snapshots" +msgid "Request timed out" msgstr "" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format -msgid "✓ SUBSCRIBED" +msgid "No SSH keys found" msgstr "" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format -msgid "comma prime" +msgid "No SSH keys found for user '{}'" msgstr "" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format -msgid "EXPERIMENTAL MODE ON" +msgid "Upgrade Now" msgstr "" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format -msgid "CHILL MODE ON" +msgid "PRIME FEATURES:" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format -msgid "Close" +msgid "Remote access" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format -msgid "Snooze Update" +msgid "24/7 LTE connectivity" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format -msgid "Acknowledge Excessive Actuation" +msgid "1 year of drive storage" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format -msgid "Reboot and Update" +msgid "Remote snapshots" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format -msgid "No release notes available." +msgid "✓ SUBSCRIBED" msgstr "" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format -msgid "Pair device" +msgid "comma prime" msgstr "" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format -msgid "Open" +msgid "Become a comma prime member at connect.comma.ai" msgstr "" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format -msgid "🔥 Firehose Mode 🔥" +msgid "EXPERIMENTAL MODE ON" msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format -msgid "Finish Setup" +msgid "CHILL MODE ON" msgstr "" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." +msgid "Close" msgstr "" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Snooze Update" msgstr "" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format -msgid "Please connect to Wi-Fi to complete initial pairing" +msgid "Acknowledge Excessive Actuation" msgstr "" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format -msgid "UPDATE" +msgid "Reboot and Update" msgstr "" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format -msgid "{} ALERT" -msgid_plural "{} ALERTS" -msgstr[0] "" -msgstr[1] "" +msgid "No release notes available." +msgstr "" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "NO" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +msgid "PANDA" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "NO" +#: openpilot/selfdrive/ui/layouts/home.py:155 +#, python-format +msgid "UPDATE" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 -msgid "PANDA" -msgstr "" +#: openpilot/selfdrive/ui/layouts/home.py:169 +#, python-format +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "" +msgstr[1] "" -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:18 -msgid "Firehose Mode" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" - -#: selfdrive/ui/layouts/settings/firehose.py:111 -#, python-format -msgid "{} segment of your driving is in the training dataset so far." -msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -msgstr[1] "" - -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +msgid "Display speed in km/h instead of mph." msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 +#, python-format +msgid "Driving Personality" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 +#, python-format +msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "Enable ADB" +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format -msgid "Enable SSH" +msgid "Enable" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format -msgid "SSH Keys" +msgid "Enable openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format -msgid "Joystick Debug Mode" +msgid "Experimental Mode" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format -msgid "Longitudinal Maneuver Mode" +msgid "Disengage on Accelerator Pedal" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format -msgid "openpilot Longitudinal Control (Alpha)" +msgid "Enable Lane Departure Warnings" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format -msgid "Enable" +msgid "Always-On Driver Monitoring" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format -msgid "never" +msgid "Record and Upload Driver Camera" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format -msgid "now" +msgid "Record and Upload Microphone Audio" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format -msgid "{} minute ago" -msgid_plural "{} minutes ago" -msgstr[0] "" -msgstr[1] "" +msgid "Use Metric System" +msgstr "" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format -msgid "{} hour ago" -msgid_plural "{} hours ago" -msgstr[0] "" -msgstr[1] "" +msgid "openpilot longitudinal control may come in a future update." +msgstr "" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format -msgid "{} day ago" -msgid_plural "{} days ago" -msgstr[0] "" -msgstr[1] "" +msgid "Aggressive" +msgstr "" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format -msgid "Updates are only downloaded while the car is off." +msgid "Standard" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format -msgid "Current Version" +msgid "Relaxed" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "Download" +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 -#, python-format -msgid "CHECK" +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +msgid "Device" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 -#, python-format -msgid "Install Update" +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +msgid "Network" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 -#, python-format -msgid "INSTALL" +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +msgid "Toggles" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 -#, python-format -msgid "Target Branch" +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +msgid "Software" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 -#, python-format -msgid "SELECT" +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +msgid "Firehose" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 -#, python-format -msgid "Uninstall" +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +msgid "Developer" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 -#, python-format -msgid "UNINSTALL" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:106 -#, python-format -msgid "failed to check for update" +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "" -#: selfdrive/ui/layouts/settings/software.py:109 -#, python-format -msgid "update available" +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +msgid "Review the rules, features, and limitations of openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format -msgid "DOWNLOAD" +msgid "Select a language" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format -msgid "up to date, last checked {}" +msgid "Are you sure you want to reset calibration?" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format -msgid "up to date, last checked never" +msgid "Reset" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format -msgid "Are you sure you want to uninstall?" +msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format -msgid "Select a branch" +msgid "Are you sure you want to reboot?" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 +#, python-format +msgid "Reboot" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#, python-format +msgid "Are you sure you want to power off?" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:27 -msgid "Review the rules, features, and limitations of openpilot" +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 +#, python-format +msgid "Power Off" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 -#, python-format -msgid "Reboot" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 -#, python-format -msgid "Power Off" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 -#, python-format -msgid "N/A" -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format -msgid "Select a language" +msgid "Disengage to Reset Calibration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format -msgid "Disengage to Reset Calibration" +msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format -msgid "Are you sure you want to reset calibration?" +msgid "Disengage to Reboot" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format -msgid "Reset" +msgid "Disengage to Power Off" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format -msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." +msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format -msgid "down" +msgid " Steering torque response calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format -msgid "up" +msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format -msgid "left" +msgid "down" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format -msgid "right" +msgid "up" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format -msgid "

Steering lag calibration is {}% complete." +msgid "left" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format -msgid "

Steering lag calibration is complete." +msgid "right" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:160 -#, python-format -msgid " Steering torque response calibration is complete." +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +msgid "Firehose Mode" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:165 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" +msgid "{} segment of your driving is in the training dataset so far." +msgid_plural "{} segments of your driving is in the training dataset so far." +msgstr[0] "" +msgstr[1] "" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 #, python-format -msgid "Disengage to Reboot" +msgid "checking..." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format -msgid "Are you sure you want to reboot?" +msgid "downloading..." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 #, python-format -msgid "Disengage to Power Off" +msgid "finalizing update..." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format -msgid "Are you sure you want to power off?" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:62 -msgid "Device" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:63 -msgid "Network" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:64 -msgid "Toggles" -msgstr "" - -#: selfdrive/ui/layouts/settings/settings.py:65 -msgid "Software" +msgid "never" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:66 -msgid "Firehose" +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 +#, python-format +msgid "now" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:67 -msgid "Developer" +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 +#, python-format +msgid "CHECK" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#, python-format +msgid "Are you sure you want to uninstall?" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:20 -msgid "When enabled, pressing the accelerator pedal will disengage openpilot." +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 +#, python-format +msgid "Uninstall" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 +#, python-format +msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 +#, python-format +msgid "{} minute ago" +msgid_plural "{} minutes ago" +msgstr[0] "" +msgstr[1] "" -#: selfdrive/ui/layouts/settings/toggles.py:30 -msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 +#, python-format +msgid "{} hour ago" +msgid_plural "{} hours ago" +msgstr[0] "" +msgstr[1] "" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 +#, python-format +msgid "{} day ago" +msgid_plural "{} days ago" +msgstr[0] "" +msgstr[1] "" -#: selfdrive/ui/layouts/settings/toggles.py:32 -msgid "Display speed in km/h instead of mph." +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 +#, python-format +msgid "Updates are only downloaded while the car is off." msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 +#, python-format +msgid "Current Version" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format -msgid "Enable openpilot" +msgid "Download" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format -msgid "Experimental Mode" +msgid "Install Update" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format -msgid "Disengage on Accelerator Pedal" +msgid "INSTALL" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format -msgid "Enable Lane Departure Warnings" +msgid "Target Branch" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format -msgid "Always-On Driver Monitoring" +msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format -msgid "Record and Upload Driver Camera" +msgid "failed to check for update" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format -msgid "Record and Upload Microphone Audio" +msgid "UNINSTALL" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format -msgid "Use Metric System" +msgid "update available" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format -msgid "Driving Personality" +msgid "DOWNLOAD" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format -msgid "Aggressive" +msgid "up to date, last checked never" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format -msgid "Standard" +msgid "up to date, last checked {}" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format -msgid "Relaxed" +msgid "Enable ADB" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format -msgid "Changing this setting will restart openpilot if the car is powered on." +msgid "Enable SSH" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:158 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." +msgid "SSH Keys" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Joystick Debug Mode" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format -msgid "openpilot longitudinal control may come in a future update." +msgid "Longitudinal Maneuver Mode" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:186 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." +msgid "openpilot Longitudinal Control (Alpha)" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "UI Debug Mode" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format -msgid "MAX" +msgid "openpilot Unavailable" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format -msgid "km/h" +msgid "Waiting to start" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format -msgid "mph" +msgid "TAKE CONTROL IMMEDIATELY" msgstr "" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format -msgid "camera starting" +msgid "System Unresponsive" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format -msgid "openpilot Unavailable" +msgid "Reboot Device" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format -msgid "Waiting to start" +msgid "camera starting" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format -msgid "TAKE CONTROL IMMEDIATELY" +msgid "MAX" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format -msgid "System Unresponsive" +msgid "km/h" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format -msgid "Reboot Device" +msgid "mph" msgstr "" + diff --git a/selfdrive/ui/translations/app_de.po b/selfdrive/ui/translations/app_de.po index f32c27a9efb..9888bb718e7 100644 --- a/selfdrive/ui/translations/app_de.po +++ b/selfdrive/ui/translations/app_de.po @@ -17,1205 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist abgeschlossen." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist zu {}% abgeschlossen." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ihr Gerät ist um {:.1f}° {} und {:.1f}° {} ausgerichtet." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 Jahr Fahrtdatenspeicherung" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24/7 LTE‑Verbindung" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"WARNUNG: Die Längsregelung von openpilot befindet sich für dieses " -"Fahrzeug in der Alpha-Phase und deaktiviert das automatische Notbremssystem " -"(AEB).

Auf diesem Fahrzeug verwendet openpilot standardmäßig den " -"integrierten ACC statt der openpilot-Längsregelung. Aktivieren Sie dies, um " -"auf die openpilot-Längsregelung umzuschalten. Das Aktivieren des " -"Experimentalmodus wird empfohlen, wenn Sie die openpilot-Längsregelung " -"(Alpha) aktivieren." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Kalibrierung der Lenkverzögerung abgeschlossen." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Kalibrierung der Lenkverzögerung zu {}% abgeschlossen." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "AKTIV" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) ermöglicht die Verbindung mit Ihrem Gerät über " -"USB oder über das Netzwerk. Siehe https://docs.comma.ai/how-to/connect-to-" -"comma für weitere Informationen." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "HINZUFÜGEN" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN‑Einstellung" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Übermäßige Betätigung bestätigen" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Erweitert" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Aggressiv" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Zustimmen" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Immer aktive Fahrerüberwachung" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Eine Alpha-Version der openpilot-Längsregelung kann zusammen mit dem " -"Experimentalmodus auf Nicht-Release-Zweigen getestet werden." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Sind Sie sicher, dass Sie ausschalten möchten?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Sind Sie sicher, dass Sie neu starten möchten?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Sind Sie sicher, dass Sie die Kalibrierung zurücksetzen möchten?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Sind Sie sicher, dass Sie deinstallieren möchten?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Zurück" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Werden Sie comma prime Mitglied auf connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Fügen Sie connect.comma.ai Ihrem Startbildschirm hinzu, um es wie eine App " -"zu verwenden" +msgstr "Fügen Sie connect.comma.ai Ihrem Startbildschirm hinzu, um es wie eine App zu verwenden" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "ÄNDERN" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "PRÜFEN" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "CHILL‑MODUS AKTIV" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "VERBINDUNG" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "VERBINDUNG" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Abbrechen" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Getaktete Mobilfunkverbindung" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Sprache ändern" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -" Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " -"eingeschaltet ist." +msgstr " Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto eingeschaltet ist." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Klicken Sie auf \"add new device\" und scannen Sie den QR‑Code rechts" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Schließen" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Aktuelle Version" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "HERUNTERLADEN" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Ablehnen" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Ablehnen, openpilot deinstallieren" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Entwickler" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Gerät" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Beim Gaspedal deaktivieren" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Zum Ausschalten deaktivieren" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Zum Neustart deaktivieren" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Zum Zurücksetzen der Kalibrierung deaktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Geschwindigkeit in km/h statt mph anzeigen." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle-ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Herunterladen" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Fahrerkamera" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Fahrstil" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "BEARBEITEN" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "FEHLER" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTALMODUS AKTIV" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Aktivieren" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB aktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Spurverlassenswarnungen aktivieren" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "openpilot aktivieren" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH aktivieren" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Spurverlassenswarnungen aktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Fahrerüberwachung auch aktivieren, wenn openpilot nicht aktiv ist." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot aktivieren" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den " -"Experimentalmodus zu erlauben." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den Experimentalmodus zu erlauben." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "APN eingeben" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "SSID eingeben" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Neues Tethering‑Passwort eingeben" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Passwort eingeben" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Geben Sie Ihren GitHub‑Benutzernamen ein" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Fehler" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Experimentalmodus" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da " -"der serienmäßige ACC für die Längsregelung verwendet wird." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da der serienmäßige ACC für die Längsregelung verwendet wird." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "WIRD VERGESSEN..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Einrichtung abschließen" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose‑Modus" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Für maximale Wirksamkeit bringen Sie Ihr Gerät regelmäßig ins Haus und " -"verbinden es wöchentlich mit einem guten USB‑C‑Adapter und WLAN.\n" -"\n" -"Der Firehose‑Modus kann auch während der Fahrt funktionieren, wenn eine " -"Verbindung zu einem Hotspot oder einer unbegrenzten SIM besteht.\n" -"\n" -"\n" -"Häufig gestellte Fragen\n" -"\n" -"Spielt es eine Rolle, wie oder wo ich fahre? Nein, fahren Sie einfach wie " -"gewöhnlich.\n" -"\n" -"Werden alle meine Segmente im Firehose‑Modus abgeholt? Nein, wir ziehen " -"selektiv eine Teilmenge Ihrer Segmente.\n" -"\n" -"Was ist ein guter USB‑C‑Adapter? Jeder schnelle Telefon‑ oder Laptoplader " -"sollte ausreichen.\n" -"\n" -"Spielt es eine Rolle, welche Software ich verwende? Ja, nur " -"Upstream‑openpilot (und bestimmte Forks) können für das Training verwendet " -"werden." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Vergessen" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "WLAN‑Netz „{}“ vergessen?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "GUT" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Gehen Sie auf Ihrem Telefon zu https://connect.comma.ai" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "HOCH" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Netzwerk" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INAKTIV: Mit einem unlimitierten Netzwerk verbinden" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALLIEREN" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP‑Adresse" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Update installieren" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick‑Debugmodus" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "LADEN" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Längsmanövermodus" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximieren Sie Ihre Trainingsdaten‑Uploads, um die Fahrmodelle von openpilot " -"zu verbessern." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximieren Sie Ihre Trainingsdaten‑Uploads, um die Fahrmodelle von openpilot zu verbessern." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "k. A." -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "KEIN" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Netzwerk" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Keine SSH‑Schlüssel gefunden" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Keine SSH‑Schlüssel für Benutzer '{username}' gefunden" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Keine Versionshinweise verfügbar." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ONLINE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Öffnen" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "KOPPELN" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "VORSCHAU" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME‑FUNKTIONEN:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Gerät koppeln" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Gerät koppeln" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Koppeln Sie Ihr Gerät mit Ihrem comma‑Konto" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Koppeln Sie Ihr Gerät mit comma connect (connect.comma.ai) und lösen Sie Ihr " -"comma‑prime‑Angebot ein." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Koppeln Sie Ihr Gerät mit comma connect (connect.comma.ai) und lösen Sie Ihr comma‑prime‑Angebot ein." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Bitte mit WLAN verbinden, um das erste Koppeln abzuschließen" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Ausschalten" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung " -"gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR‑Code‑Fehler" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ENTFERNEN" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "ZURÜCKSETZEN" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "ANSEHEN" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Neustart" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Gerät neu starten" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Neustarten und aktualisieren" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Erhalten Sie Warnungen, um zurück in die Spur zu lenken, wenn Ihr Fahrzeug " -"ohne Blinker über eine erkannte Spurlinie driftet und über 31 mph (50 km/h) " -"fährt." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Fahrerkamera aufzeichnen und hochladen" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Mikrofonton aufzeichnen und hochladen" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Mikrofonton während der Fahrt aufzeichnen und speichern. Die Audiospur wird " -"im Dashcam‑Video in comma connect enthalten sein." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Mikrofonton während der Fahrt aufzeichnen und speichern. Die Audiospur wird im Dashcam‑Video in comma connect enthalten sein." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Vorschriften" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Entspannt" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Fernzugriff" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Remote‑Schnappschüsse" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Zeitüberschreitung bei der Anfrage" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Zurücksetzen" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Kalibrierung zurücksetzen" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Trainingsanleitung ansehen" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" -msgstr "" -"Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" +msgstr "Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH‑Schlüssel" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "WLAN‑Netzwerke werden gesucht..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Auswählen" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Sprache auswählen" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Seriennummer" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Update verschieben" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Standard wird empfohlen. Im aggressiven Modus folgt openpilot " -"vorausfahrenden Fahrzeugen näher und ist beim Gasgeben und Bremsen " -"aggressiver. Im entspannten Modus bleibt openpilot weiter entfernt. Bei " -"unterstützten Fahrzeugen können Sie mit der Abstandstaste am Lenkrad " -"zwischen diesen Profilen wechseln." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "System reagiert nicht" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "SOFORT DIE KONTROLLE ÜBERNEHMEN" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Tethering‑Passwort" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Schalter" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DEINSTALLIEREN" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "UPDATE" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Deinstallieren" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Unbekannt" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates werden nur heruntergeladen, wenn das Auto aus ist." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Jetzt abonnieren" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus " -"verbessern." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus verbessern." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Metersystem verwenden" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Verwenden Sie openpilot für adaptive Geschwindigkeitsregelung und " -"Spurhalteassistenz. Ihre Aufmerksamkeit ist jederzeit erforderlich, um diese " -"Funktion zu nutzen." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "FAHRZEUG" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "ANSEHEN" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Warten auf Start" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Warnung: Dies gewährt SSH‑Zugriff auf alle öffentlichen Schlüssel in Ihren " -"GitHub‑Einstellungen. Geben Sie niemals einen anderen GitHub‑Benutzernamen " -"als Ihren eigenen ein. Ein comma‑Mitarbeiter wird Sie NIEMALS bitten, seinen " -"GitHub‑Benutzernamen hinzuzufügen." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Willkommen bei openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Wenn aktiviert, deaktiviert das Drücken des Gaspedals openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "WLAN" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Getaktetes WLAN‑Netzwerk" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Falsches Passwort" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." -msgstr "" -"Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden." +msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. " -"Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie " -"fortfahren." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie fortfahren." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "Kamera startet" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "Standard" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "unten" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "Überprüfung auf Updates fehlgeschlagen" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "für „{}“" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "für automatische Konfiguration leer lassen" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "links" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "getaktet" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "nie" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "jetzt" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Längsregelung (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot nicht verfügbar" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot fährt standardmäßig im Chill‑Modus. Der Experimentalmodus " -"aktiviert Funktionen im Alpha‑Status, die für den Chill‑Modus noch nicht " -"bereit sind. Die experimentellen Funktionen sind unten aufgeführt:" -"

End-to‑End‑Längsregelung


Das Fahrmodell steuert Gas und " -"Bremse. openpilot fährt so, wie es einen Menschen einschätzt, einschließlich " -"Anhalten an roten Ampeln und Stoppschildern. Da das Modell die " -"Geschwindigkeit bestimmt, dient die eingestellte Geschwindigkeit nur als " -"Obergrenze. Dies ist eine Alpha‑Funktion; Fehler sind zu erwarten." -"

Neue Fahrvisualisierung


Die Visualisierung wechselt bei " -"niedriger Geschwindigkeit auf die nach vorn gerichtete Weitwinkelkamera, um " -"manche Kurven besser zu zeigen. Das Experimentalmodus‑Logo wird außerdem " -"oben rechts angezeigt." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -" Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto " -"eingeschaltet ist." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot lernt das Fahren, indem es Menschen wie Sie beobachtet.\n" -"\n" -"Der Firehose‑Modus ermöglicht es Ihnen, Ihre Trainingsdaten‑Uploads zu " -"maximieren, um die Fahrmodelle von openpilot zu verbessern. Mehr Daten " -"bedeuten größere Modelle – und damit einen besseren Experimentalmodus." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "Die openpilot‑Längsregelung könnte in einem zukünftigen Update kommen." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts " -"und innerhalb von 5° nach oben oder 9° nach unten montiert ist." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts und innerhalb von 5° nach oben oder 9° nach unten montiert ist." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "rechts" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "unbegrenzt" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "oben" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "Aktuell, zuletzt geprüft: nie" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "Aktuell, zuletzt geprüft: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "Update verfügbar" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} WARNUNG" msgstr[1] "{} WARNUNGEN" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "vor {} Tag" msgstr[1] "vor {} Tagen" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "vor {} Stunde" msgstr[1] "vor {} Stunden" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "vor {} Minute" msgstr[1] "vor {} Minuten" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} Segment Ihrer Fahrten ist bisher im Trainingsdatensatz." msgstr[1] "{} Segmente Ihrer Fahrten sind bisher im Trainingsdatensatz." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONNIERT" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose‑Modus 🔥" + diff --git a/selfdrive/ui/translations/app_en.po b/selfdrive/ui/translations/app_en.po index 6fbb537aff4..37440962266 100644 --- a/selfdrive/ui/translations/app_en.po +++ b/selfdrive/ui/translations/app_en.po @@ -17,1191 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Steering torque response calibration is complete." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Steering torque response calibration is {}% complete." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Your device is pointed {:.1f}° {} and {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 year of drive storage" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24/7 LTE connectivity" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Steering lag calibration is complete." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Steering lag calibration is {}% complete." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ACTIVE" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ADD" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN Setting" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Acknowledge Excessive Actuation" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Advanced" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Aggressive" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Agree" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Always-On Driver Monitoring" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Are you sure you want to power off?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Are you sure you want to reboot?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Are you sure you want to reset calibration?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Are you sure you want to uninstall?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Back" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Become a comma prime member at connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Bookmark connect.comma.ai to your home screen to use it like an app" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "CHANGE" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "CHECK" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "CHILL MODE ON" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONNECTING..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Cancel" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Cellular Metered" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Change Language" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "Changing this setting will restart openpilot if the car is powered on." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Click \"add new device\" and scan the QR code on the right" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Close" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Current Version" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "DOWNLOAD" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Decline" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Decline, uninstall openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Developer" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Device" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Disengage on Accelerator Pedal" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Disengage to Power Off" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Disengage to Reboot" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Disengage to Reset Calibration" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Display speed in km/h instead of mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Download" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Driver Camera" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Driving Personality" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "EDIT" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERROR" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTAL MODE ON" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Enable" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Enable ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Enable Lane Departure Warnings" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Enable Roaming" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Enable SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Enable Tethering" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Enable driver monitoring even when openpilot is not engaged." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Enable openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Enter APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Enter SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Enter new tethering password" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Enter password" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Enter your GitHub username" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Error" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Experimental Mode" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "FORGETTING..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Finish Setup" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose Mode" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Forget" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Forget Wi-Fi Network \"{}\"?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "GOOD" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Go to https://connect.comma.ai on your phone" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "HIGH" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Hidden Network" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INACTIVE: connect to an unmetered network" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALL" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP Address" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Install Update" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick Debug Mode" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "LOADING" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Longitudinal Maneuver Mode" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximize your training data uploads to improve openpilot's driving models." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "N/A" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NO" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Network" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "No SSH keys found" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "No SSH keys found for user '{}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "No release notes available." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ONLINE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Open" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "PAIR" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "PREVIEW" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME FEATURES:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Pair Device" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Pair device" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Pair your device to your comma account" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Please connect to Wi-Fi to complete initial pairing" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Power Off" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Prevent large data uploads when on a metered Wi-Fi connection" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Prevent large data uploads when on a metered cellular connection" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR Code Error" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "REMOVE" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "RESET" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "REVIEW" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Reboot" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reboot Device" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reboot and Update" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Record and Upload Driver Camera" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Record and Upload Microphone Audio" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Regulatory" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relaxed" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Remote access" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Remote snapshots" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Request timed out" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Reset" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Reset Calibration" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Review Training Guide" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Review the rules, features, and limitations of openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH Keys" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Scanning Wi-Fi networks..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Select" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Select a language" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Serial" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Snooze Update" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "System Unresponsive" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "TAKE CONTROL IMMEDIATELY" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Tethering Password" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Toggles" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "UNINSTALL" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "UPDATE" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Uninstall" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Unknown" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Updates are only downloaded while the car is off." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Upgrade Now" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Use Metric System" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEHICLE" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VIEW" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Waiting to start" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Welcome to openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "When enabled, pressing the accelerator pedal will disengage openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi-Fi Network Metered" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Wrong password" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "You must accept the Terms and Conditions in order to use openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "camera starting" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "default" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "down" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "failed to check for update" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "for \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "leave blank for automatic configuration" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "left" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "metered" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "never" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "now" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Longitudinal Control (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Unavailable" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot longitudinal control may come in a future update." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "right" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "unmetered" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "up" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "up to date, last checked never" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "up to date, last checked {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "update available" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERT" msgstr[1] "{} ALERTS" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} day ago" msgstr[1] "{} days ago" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hour ago" msgstr[1] "{} hours ago" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minute ago" msgstr[1] "{} minutes ago" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment of your driving is in the training dataset so far." msgstr[1] "{} segments of your driving is in the training dataset so far." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ SUBSCRIBED" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Mode 🔥" + diff --git a/selfdrive/ui/translations/app_es.po b/selfdrive/ui/translations/app_es.po index 59b9e6dfdbe..35188fe2fc9 100644 --- a/selfdrive/ui/translations/app_es.po +++ b/selfdrive/ui/translations/app_es.po @@ -17,1209 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " La calibración de respuesta de par de dirección está completa." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " La calibración de respuesta de par de dirección está {}% completa." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Tu dispositivo está orientado {:.1f}° {} y {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 año de almacenamiento de conducción" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Conectividad LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"ADVERTENCIA: el control longitudinal de openpilot está en alpha para este " -"coche y deshabilitará el Frenado Automático de Emergencia (AEB).

En este coche, openpilot usa por defecto el ACC integrado del " -"coche en lugar del control longitudinal de openpilot. Activa esto para " -"cambiar al control longitudinal de openpilot. Se recomienda activar el modo " -"Experimental al habilitar el control longitudinal de openpilot (alpha)." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ACTIVO" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) permite conectar tu dispositivo por USB o por la " -"red. Consulta https://docs.comma.ai/how-to/connect-to-comma para más " -"información." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "AÑADIR" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Reconocer actuación excesiva" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agresivo" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Aceptar" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Supervisión del conductor siempre activa" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Se puede probar una versión alpha del control longitudinal de openpilot, " -"junto con el modo Experimental, en ramas que no son de lanzamiento." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "¿Seguro que quieres apagar?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "¿Seguro que quieres reiniciar?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "¿Seguro que quieres restablecer la calibración?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "¿Seguro que quieres desinstalar?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Atrás" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Hazte miembro de comma prime en connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Añade connect.comma.ai a tu pantalla de inicio para usarlo como una app" +msgstr "Añade connect.comma.ai a tu pantalla de inicio para usarlo como una app" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "CAMBIAR" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "COMPROBAR" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "MODO CHILL ACTIVADO" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONECTAR" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Cambiar idioma" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -" Cambiar esta configuración reiniciará openpilot si el coche está encendido." +msgstr " Cambiar esta configuración reiniciará openpilot si el coche está encendido." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" -msgstr "" -"Haz clic en \"añadir nuevo dispositivo\" y escanea el código QR de la derecha" +msgstr "Haz clic en \"añadir nuevo dispositivo\" y escanea el código QR de la derecha" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Cerrar" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Versión actual" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "DESCARGAR" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Rechazar" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Rechazar, desinstalar openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Desarrollador" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Dispositivo" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Desactivar con el pedal del acelerador" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Desactivar para apagar" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Desactivar para reiniciar" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desactivar para restablecer la calibración" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Mostrar la velocidad en km/h en lugar de mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID del dongle" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Descargar" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Cámara del conductor" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Estilo de conducción" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERROR" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ACTIVADO" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Activar" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Activar ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Activar advertencias de salida de carril" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Activar openpilot" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Activar SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Activar advertencias de salida de carril" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" -"Activar la supervisión del conductor incluso cuando openpilot no esté " -"activado." +msgstr "Activar la supervisión del conductor incluso cuando openpilot no esté activado." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Activar openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Activa el interruptor de control longitudinal de openpilot (alpha) para " -"permitir el modo Experimental." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Activa el interruptor de control longitudinal de openpilot (alpha) para permitir el modo Experimental." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Introduce tu nombre de usuario de GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Modo experimental" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"El modo experimental no está disponible actualmente en este coche, ya que se " -"usa el ACC de fábrica para el control longitudinal." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "El modo experimental no está disponible actualmente en este coche, ya que se usa el ACC de fábrica para el control longitudinal." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Finalizar configuración" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Modo Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Para la máxima efectividad, lleva tu dispositivo al interior y conéctalo " -"semanalmente a un buen adaptador USB‑C y Wi‑Fi.\n" -"\n" -"El Modo Firehose también puede funcionar mientras conduces si está conectado " -"a un hotspot o a una SIM ilimitada.\n" -"\n" -"\n" -"Preguntas frecuentes\n" -"\n" -"¿Importa cómo o dónde conduzco? No, conduce como normalmente lo harías.\n" -"\n" -"¿Se suben todos mis segmentos en el Modo Firehose? No, seleccionamos un " -"subconjunto de tus segmentos.\n" -"\n" -"¿Qué es un buen adaptador USB‑C? Cualquier cargador rápido de teléfono o " -"laptop sirve.\n" -"\n" -"¿Importa qué software ejecuto? Sí, solo openpilot upstream (y forks " -"particulares) pueden usarse para entrenamiento." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BUENO" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Ve a https://connect.comma.ai en tu teléfono" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Red" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INACTIVO: conéctate a una red sin límites" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Instalar actualización" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Modo de depuración de joystick" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CARGANDO" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Modo de maniobra longitudinal" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MÁX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximiza tus cargas de datos de entrenamiento para mejorar los modelos de " -"conducción de openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximiza tus cargas de datos de entrenamiento para mejorar los modelos de conducción de openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NO" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Red" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "No se encontraron claves SSH" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "No se encontraron claves SSH para el usuario '{username}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "No hay notas de versión disponibles." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "SIN CONEXIÓN" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "EN LÍNEA" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Abrir" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "EMPAREJAR" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "VISTA PREVIA" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "FUNCIONES PRIME:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Emparejar dispositivo" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Emparejar dispositivo" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Empareja tu dispositivo con tu cuenta de comma" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu " -"oferta de comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu oferta de comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conéctate a Wi‑Fi para completar el emparejamiento inicial" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Apagar" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Previsualiza la cámara hacia el conductor para asegurarte de que la " -"supervisión del conductor tenga buena visibilidad. (el vehículo debe estar " -"apagado)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Previsualiza la cámara hacia el conductor para asegurarte de que la supervisión del conductor tenga buena visibilidad. (el vehículo debe estar apagado)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Error de código QR" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ELIMINAR" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "RESTABLECER" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "REVISAR" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Reiniciar" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reiniciar dispositivo" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reiniciar y actualizar" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Recibe alertas para volver al carril cuando tu vehículo se desvíe sobre una " -"línea de carril detectada sin la direccional activada mientras conduces a " -"más de 31 mph (50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Grabar y subir cámara del conductor" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Grabar y subir audio del micrófono" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Grabar y almacenar audio del micrófono mientras conduces. El audio se " -"incluirá en el video de la dashcam en comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Grabar y almacenar audio del micrófono mientras conduces. El audio se incluirá en el video de la dashcam en comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Reglamentario" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relajado" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Acceso remoto" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Capturas remotas" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Se agotó el tiempo de espera de la solicitud" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Restablecer" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Restablecer calibración" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Revisar guía de entrenamiento" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Revisa las reglas, funciones y limitaciones de openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Selecciona un idioma" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Número de serie" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Posponer actualización" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Estándar" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Se recomienda Estándar. En modo agresivo, openpilot seguirá más de cerca a " -"los coches delanteros y será más agresivo con el acelerador y el freno. En " -"modo relajado, openpilot se mantendrá más lejos de los coches delanteros. En " -"coches compatibles, puedes cambiar entre estas personalidades con el botón " -"de distancia del volante." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistema sin respuesta" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "TOME EL CONTROL INMEDIATAMENTE" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Interruptores" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ACTUALIZAR" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Desinstalar" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Desconocido" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Las actualizaciones solo se descargan cuando el coche está apagado." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Mejorar ahora" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Sube datos de la cámara orientada al conductor y ayuda a mejorar el " -"algoritmo de supervisión del conductor." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Sube datos de la cámara orientada al conductor y ayuda a mejorar el algoritmo de supervisión del conductor." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Usar sistema métrico" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Usa el sistema openpilot para control de crucero adaptativo y asistencia de " -"mantenimiento de carril. Tu atención se requiere en todo momento para usar " -"esta función." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEHÍCULO" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VER" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Esperando para iniciar" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Advertencia: Esto otorga acceso SSH a todas las claves públicas en tu " -"configuración de GitHub. Nunca introduzcas un nombre de usuario de GitHub " -"que no sea el tuyo. Un empleado de comma NUNCA te pedirá que agregues su " -"nombre de usuario de GitHub." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Bienvenido a openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Cuando está activado, al presionar el pedal del acelerador se desactivará " -"openpilot." +msgstr "Cuando está activado, al presionar el pedal del acelerador se desactivará openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Debes aceptar los Términos y Condiciones para poder usar openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Debes aceptar los Términos y Condiciones para usar openpilot. Lee los " -"términos más recientes en https://comma.ai/terms antes de continuar." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Debes aceptar los Términos y Condiciones para usar openpilot. Lee los términos más recientes en https://comma.ai/terms antes de continuar." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "iniciando cámara" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "abajo" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "Error al buscar actualizaciones" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "izquierda" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "nunca" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "ahora" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Control longitudinal de openpilot (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot no disponible" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot conduce por defecto en modo chill. El modo Experimental habilita " -"funciones de nivel alpha que no están listas para el modo chill. Las " -"funciones experimentales se enumeran a continuación:

Control " -"longitudinal de extremo a extremo


Deja que el modelo de conducción " -"controle el acelerador y los frenos. openpilot conducirá como piensa que lo " -"haría un humano, incluyendo detenerse en luces rojas y señales de alto. Dado " -"que el modelo decide la velocidad a la que conducir, la velocidad " -"establecida solo actuará como límite superior. Esta es una función de " -"calidad alpha; se deben esperar errores.

Nueva visualización de " -"conducción


La visualización de conducción hará la transición a la " -"cámara gran angular orientada a la carretera a bajas velocidades para " -"mostrar mejor algunos giros. El logotipo del modo Experimental también se " -"mostrará en la esquina superior derecha." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -" Cambiar esta configuración reiniciará openpilot si el coche está encendido." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot aprende a conducir observando a humanos, como tú, conducir.\n" -"\n" -"El Modo Firehose te permite maximizar tus cargas de datos de entrenamiento " -"para mejorar los modelos de conducción de openpilot. Más datos significan " -"modelos más grandes, lo que significa un mejor Modo Experimental." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." -msgstr "" -"El control longitudinal de openpilot podría llegar en una actualización " -"futura." +msgstr "El control longitudinal de openpilot podría llegar en una actualización futura." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda " -"o derecha y dentro de 5° hacia arriba o 9° hacia abajo." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda o derecha y dentro de 5° hacia arriba o 9° hacia abajo." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "derecha" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "arriba" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "actualizado, última comprobación: nunca" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "actualizado, última comprobación: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "actualización disponible" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "hace {} día" msgstr[1] "hace {} días" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "hace {} hora" msgstr[1] "hace {} horas" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "hace {} minuto" msgstr[1] "hace {} minutos" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} segmento de tu conducción está en el conjunto de entrenamiento hasta " -"ahora." -msgstr[1] "" -"{} segmentos de tu conducción están en el conjunto de entrenamiento hasta " -"ahora." +msgstr[0] "{} segmento de tu conducción está en el conjunto de entrenamiento hasta ahora." +msgstr[1] "{} segmentos de tu conducción están en el conjunto de entrenamiento hasta ahora." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ SUSCRITO" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" + diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po index 409761588e9..d3ce386bdcb 100644 --- a/selfdrive/ui/translations/app_fr.po +++ b/selfdrive/ui/translations/app_fr.po @@ -18,1219 +18,1018 @@ msgstr "" "Plural-Forms: nplurals=2; plural=(n > 1);\n" "X-Generator: Poedit 3.8\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " L'étalonnage de la réponse du couple de direction est terminé." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " L'étalonnage de la réponse du couple de direction est terminé à {}%." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Votre appareil est orienté {:.1f}° {} et {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 an de stockage de trajets" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Connexion LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"ATTENTION : le contrôle longitudinal openpilot est en alpha pour cette " -"voiture et désactivera le freinage d'urgence automatique (AEB).

Sur cette voiture, openpilot utilise par défaut le régulateur de " -"vitesse adaptatif intégré au véhicule plutôt que le contrôle longitudinal " -"d'openpilot. Activez ceci pour passer au contrôle longitudinal openpilot. Il " -"est recommandé d'activer le mode expérimental lors de l'activation du " -"contrôle longitudinal openpilot alpha." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

L'étalonnage du délai de réponse de la direction est terminé." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." -msgstr "" -"

L'étalonnage du délai de réponse de la direction est terminé à {}%." - -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ACTIF" +msgstr "

L'étalonnage du délai de réponse de la direction est terminé à {}%." -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) permet de connecter votre appareil via USB ou via " -"le réseau. Voir https://docs.comma.ai/how-to/connect-to-comma pour plus " -"d'informations." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "AJOUTER" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "Paramètres APN" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Accuser réception d'actionnement excessif" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Avancé" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agressif" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Accepter" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Surveillance continue du conducteur" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Une version alpha du contrôle longitudinal openpilot peut être testée, avec " -"le mode expérimental, sur des branches non publiées." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Êtes-vous sûr de vouloir éteindre ?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Êtes-vous sûr de vouloir redémarrer ?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Êtes-vous sûr de vouloir réinitialiser la calibration ?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Êtes-vous sûr de vouloir désinstaller ?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Retour" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Devenez membre comma prime sur connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Ajoutez connect.comma.ai à votre écran d'accueil pour l'utiliser comme une " -"application" +msgstr "Ajoutez connect.comma.ai à votre écran d'accueil pour l'utiliser comme une application" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "CHANGER" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "VÉRIFIER" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "MODE CHILL ACTIVÉ" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECTER" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONNECTER..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Annuler" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Données cellulaire limitées" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Changer la langue" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"La modification de ce réglage redémarrera openpilot si la voiture est sous " -"tension." +msgstr "La modification de ce réglage redémarrera openpilot si la voiture est sous tension." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Cliquez sur \"add new device\" et scannez le code QR à droite" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Fermer" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Version actuelle" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "TÉLÉCHARGER" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Refuser" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Refuser, désinstaller openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Développeur" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Appareil" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Désengager à l'appui sur l'accélérateur" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Désengager pour éteindre" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Désengager pour redémarrer" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Désengager pour réinitialiser la calibration" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Afficher la vitesse en km/h au lieu de mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID du dongle" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Télécharger" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Caméra conducteur" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Personnalité de conduite" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "EDITER" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERREUR" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODE EXPÉRIMENTAL ACTIVÉ" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Activer" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Activer ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Activer les alertes de sortie de voie" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Activer openpilot" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Activer SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Activer les alertes de sortie de voie" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" -"Activer la surveillance du conducteur même lorsque openpilot n'est pas " -"engagé." +msgstr "Activer la surveillance du conducteur même lorsque openpilot n'est pas engagé." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Activer openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Activez l'option de contrôle longitudinal openpilot (alpha) pour autoriser " -"le mode expérimental." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Activez l'option de contrôle longitudinal openpilot (alpha) pour autoriser le mode expérimental." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Saisir l'APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Entrer le SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Saisir le mot de passe du partage de connexion" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Saisir le mot de passe" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Entrez votre nom d'utilisateur GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Erreur" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Mode expérimental" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Le mode expérimental est actuellement indisponible sur cette voiture car " -"l'ACC d'origine est utilisé pour le contrôle longitudinal." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Le mode expérimental est actuellement indisponible sur cette voiture car l'ACC d'origine est utilisé pour le contrôle longitudinal." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "OUBLIER..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Terminer la configuration" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Mode Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Pour une efficacité maximale, rentrez votre appareil et connectez-le chaque " -"semaine à un bon adaptateur USB-C et au Wi‑Fi.\n" -"\n" -"Le Mode Firehose peut aussi fonctionner pendant que vous conduisez si vous " -"êtes connecté à un hotspot ou à une carte SIM illimitée.\n" -"\n" -"\n" -"Foire aux questions\n" -"\n" -"Est-ce que la manière ou l'endroit où je conduis compte ? Non, conduisez " -"normalement.\n" -"\n" -"Tous mes segments sont-ils récupérés en Mode Firehose ? Non, nous récupérons " -"de façon sélective un sous-ensemble de vos segments.\n" -"\n" -"Quel est un bon adaptateur USB-C ? Tout chargeur rapide de téléphone ou " -"d'ordinateur portable convient.\n" -"\n" -"Le logiciel utilisé importe-t-il ? Oui, seul openpilot amont (et certains " -"forks) peut être utilisé pour l'entraînement." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Oublier" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Oublier le réseau Wi-Fi \"{}\" ?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BON" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Allez sur https://connect.comma.ai sur votre téléphone" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ÉLEVÉ" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Réseau" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INACTIF : connectez-vous à un réseau non limité" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALLER" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "Adresse IP" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Installer la mise à jour" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Mode débogage joystick" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CHARGEMENT" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Mode de manœuvre longitudinale" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximisez vos envois de données d'entraînement pour améliorer les modèles de " -"conduite d'openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximisez vos envois de données d'entraînement pour améliorer les modèles de conduite d'openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "NC" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NON" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Réseau" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Aucune clé SSH trouvée" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Aucune clé SSH trouvée pour l'utilisateur '{}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Aucune note de version disponible." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "HORS LIGNE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "EN LIGNE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Ouvrir" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "ASSOCIER" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "APERÇU" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "FONCTIONNALITÉS PRIME :" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Associer l'appareil" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Associer l'appareil" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Associez votre appareil à votre compte comma" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Associez votre appareil à comma connect (connect.comma.ai) et réclamez votre " -"offre comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Associez votre appareil à comma connect (connect.comma.ai) et réclamez votre offre comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Veuillez vous connecter au Wi‑Fi pour terminer l'association initiale" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Éteindre" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" -"Eviter les transferts de données volumineux lorsque vous êtes connecté à un " -"réseau Wi-Fi limité" +msgstr "Eviter les transferts de données volumineux lorsque vous êtes connecté à un réseau Wi-Fi limité" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" -"Eviter les transferts de données volumineux lors d'une connexion à un réseau " -"cellulaire limité" +msgstr "Eviter les transferts de données volumineux lors d'une connexion à un réseau cellulaire limité" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Prévisualisez la caméra orientée conducteur pour vous assurer que la " -"surveillance du conducteur a une bonne visibilité. (le véhicule doit être " -"éteint)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Prévisualisez la caméra orientée conducteur pour vous assurer que la surveillance du conducteur a une bonne visibilité. (le véhicule doit être éteint)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Erreur de code QR" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "SUPPRIMER" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "RÉINITIALISER" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "CONSULTER" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Redémarrer" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Redémarrer l'appareil" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Redémarrer et mettre à jour" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Recevez des alertes pour revenir dans la voie lorsque votre véhicule dépasse " -"une ligne de voie détectée sans clignotant activé en roulant au-delà de 31 " -"mph (50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Enregistrer et téléverser la caméra conducteur" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Enregistrer et téléverser l'audio du microphone" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Enregistrer et stocker l'audio du microphone pendant la conduite. L'audio " -"sera inclus dans la vidéo dashcam dans comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Enregistrer et stocker l'audio du microphone pendant la conduite. L'audio sera inclus dans la vidéo dashcam dans comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Réglementaire" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Détendu" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Accès à distance" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Captures à distance" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Délai de la requête dépassé" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Réinitialiser" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Réinitialiser la calibration" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Consulter le guide d'entraînement" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Consultez les règles, fonctionnalités et limitations d'openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "SELECTIONNER" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "Clefs SSH" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Analyse des réseaux Wi-Fi..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Sélectionner" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "Sélectionner une branche" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Sélectionner un langage" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Numéro de série" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Reporter la mise à jour" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Logiciel" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standard" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Le mode standard est recommandé. En mode agressif, openpilot suivra les " -"véhicules de tête de plus près et sera plus agressif avec l'accélérateur et " -"le frein. En mode détendu, openpilot restera plus éloigné des véhicules de " -"tête. Sur les voitures compatibles, vous pouvez parcourir ces personnalités " -"avec le bouton de distance du volant." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Système non réactif" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "REPRENEZ IMMÉDIATEMENT LE CONTRÔLE" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMPÉRATURE" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "Branche cible" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Mot de passe du partage de connexion" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Options" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DÉSINSTALLER" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "METTRE À JOUR" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Désinstaller" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Inconnu" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." -msgstr "" -"Les mises à jour ne sont téléchargées que lorsque la voiture est éteinte." +msgstr "Les mises à jour ne sont téléchargées que lorsque la voiture est éteinte." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Mettre à niveau maintenant" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Téléverser les données de la caméra orientée conducteur et aider à améliorer " -"l'algorithme de surveillance du conducteur." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Téléverser les données de la caméra orientée conducteur et aider à améliorer l'algorithme de surveillance du conducteur." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Utiliser le système métrique" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Utilisez le système openpilot pour l'ACC et l'assistance au maintien de " -"voie. Votre attention est requise en permanence pour utiliser cette " -"fonctionnalité." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VÉHICULE" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VOIR" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "En attente de démarrage" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Avertissement : Ceci accorde un accès SSH à toutes les clés publiques dans " -"vos paramètres GitHub. N'entrez jamais un nom d'utilisateur GitHub autre que " -"le vôtre. Un employé comma ne vous demandera JAMAIS d'ajouter son nom " -"d'utilisateur GitHub." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Bienvenue sur openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Lorsque activé, appuyer sur la pédale d'accélérateur désengagera openpilot." +msgstr "Lorsque activé, appuyer sur la pédale d'accélérateur désengagera openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Réseau Wi-Fi limité" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Mauvais mot de passe" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Vous devez accepter les conditions générales pour utiliser openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Vous devez accepter les conditions générales pour utiliser openpilot. Lisez " -"les dernières conditions sur https://comma.ai/terms avant de continuer." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Vous devez accepter les conditions générales pour utiliser openpilot. Lisez les dernières conditions sur https://comma.ai/terms avant de continuer." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "démarrage de la caméra" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "défaut" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "bas" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "échec de la vérification de mise à jour" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "pour \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "ne pas remplir pour une configuration automatique" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "gauche" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "limité" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "jamais" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "maintenant" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Contrôle longitudinal openpilot (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot indisponible" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot roule par défaut en mode chill. Le mode expérimental active des " -"fonctionnalités de niveau alpha qui ne sont pas prêtes pour le mode chill. " -"Les fonctionnalités expérimentales sont listées ci‑dessous:

Contrôle " -"longitudinal de bout en bout


Laissez le modèle de conduite contrôler " -"l'accélérateur et les freins. openpilot conduira comme il pense qu'un humain " -"le ferait, y compris s'arrêter aux feux rouges et aux panneaux stop. Comme " -"le modèle décide de la vitesse à adopter, la vitesse réglée n'agira que " -"comme une limite supérieure. C'est une fonctionnalité de qualité alpha ; des " -"erreurs sont à prévoir.

Nouvelle visualisation de conduite


La " -"visualisation passera à la caméra grand angle orientée route à basse vitesse " -"pour mieux montrer certains virages. Le logo du mode expérimental sera " -"également affiché en haut à droite." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"La modification de ce réglage redémarrera openpilot si la voiture est sous " -"tension." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot apprend à conduire en regardant des humains, comme vous, " -"conduire.\n" -"\n" -"Le Mode Firehose vous permet de maximiser vos envois de données " -"d'entraînement pour améliorer les modèles de conduite d'openpilot. Plus de " -"données signifie des modèles plus grands, ce qui signifie un meilleur Mode " -"expérimental." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." -msgstr "" -"Le contrôle longitudinal openpilot pourra arriver dans une future mise à " -"jour." +msgstr "Le contrôle longitudinal openpilot pourra arriver dans une future mise à jour." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite " -"et à moins de 5° vers le haut ou 9° vers le bas." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite et à moins de 5° vers le haut ou 9° vers le bas." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "droite" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "non limité" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "haut" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "à jour, dernière vérification jamais" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "à jour, dernière vérification {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "mise à jour disponible" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTE" msgstr[1] "{} ALERTES" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "il y a {} jour" msgstr[1] "il y a {} jours" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "il y a {} heure" msgstr[1] "il y a {} heures" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "il y a {} minute" msgstr[1] "il y a {} minutes" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} segment de votre conduite est dans l'ensemble d'entraînement jusqu'à " -"présent." -msgstr[1] "" -"{} segments de votre conduite sont dans l'ensemble d'entraînement jusqu'à " -"présent." +msgstr[0] "{} segment de votre conduite est dans l'ensemble d'entraînement jusqu'à présent." +msgstr[1] "{} segments de votre conduite sont dans l'ensemble d'entraînement jusqu'à présent." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONNÉ" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Mode Firehose 🔥" + diff --git a/selfdrive/ui/translations/app_ja.po b/selfdrive/ui/translations/app_ja.po index ca8aac1515a..41eb91dd58b 100644 --- a/selfdrive/ui/translations/app_ja.po +++ b/selfdrive/ui/translations/app_ja.po @@ -17,1181 +17,1013 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " ステアリングトルク応答のキャリブレーションが完了しました。" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " ステアリングトルク応答のキャリブレーションは{}%完了しました。" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " デバイスは{:.1f}°{}、{:.1f}°{}の向きです。" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "走行データを1年間保存" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "24時間365日のLTE接続" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"警告: この車におけるopenpilotの縦制御はアルファ版であり、自動緊急ブレーキ" -"(AEB)を無効にします。

この車では、openpilotは縦制御として" -"openpilotではなく車両の内蔵ACCを既定で使用します。openpilotの縦制御に切り替え" -"るにはこの設定を有効にしてください。openpilot縦制御アルファを有効にする場合は" -"実験モードの有効化を推奨します。この設定を変更すると、車が起動中の場合は" -"openpilotが再起動します。" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

ステアリング遅延のキャリブレーションが完了しました。" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

ステアリング遅延のキャリブレーションは{}%完了しました。" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "アクティブ" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB(Android Debug Bridge)を使用すると、USBまたはネットワーク経由でデバイス" -"に接続できます。詳しくは https://docs.comma.ai/how-to/connect-to-comma を参照" -"してください。" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "追加" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN設定" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "過度な作動を承認" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "詳細設定" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "アグレッシブ" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "同意する" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "常時ドライバーモニタリング" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"openpilotの縦制御アルファ版は、実験モードと併せて非リリースブランチでテストで" -"きます。" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "本当に電源をオフにしますか?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "本当に再起動しますか?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "本当にキャリブレーションをリセットしますか?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "本当にアンインストールしますか?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "戻る" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.aiで comma prime に加入" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.aiをホーム画面に追加してアプリのように使いましょう" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "変更" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "確認" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "チルモードON" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "接続" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "接続中..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "キャンセル" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "従量課金の携帯回線" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "言語を変更" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "車が起動中の場合、この設定を変更するとopenpilotが再起動します。" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"を押して右側のQRコードをスキャン" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "閉じる" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "現在のバージョン" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "ダウンロード" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "拒否する" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒否してopenpilotをアンインストール" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "開発者" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "デバイス" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "アクセルで解除" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "解除して電源オフ" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "解除して再起動" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "解除してキャリブレーションをリセット" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "速度をmphではなくkm/hで表示します。" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ドングルID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "ダウンロード" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "ドライバーカメラ" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "走行性格" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "編集" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "エラー" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "実験モードON" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "有効化" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADBを有効化" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "車線逸脱警報を有効化" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "ローミングを有効化" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSHを有効化" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "テザリングを有効化" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilotが未作動でもドライバーモニタリングを有効にします。" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilotを有効化" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "APNを入力" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "SSIDを入力" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "新しいテザリングのパスワードを入力" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "パスワードを入力" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHubユーザー名を入力" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "エラー" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "実験モード" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "削除中..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "セットアップを完了" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehoseモード" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"最大限の効果を得るため、デバイスを屋内に持ち込み、週に一度は品質の良いUSB-Cア" -"ダプターとWi‑Fiに接続してください。\n" -"\n" -"Firehoseモードは、ホットスポットや無制限SIMに接続していれば走行中でも動作しま" -"す。\n" -"\n" -"\n" -"よくある質問\n" -"\n" -"運転の仕方や場所は関係ありますか? いいえ。普段どおりに運転してください。\n" -"\n" -"Firehoseモードではすべてのセグメントが取得されますか? いいえ。セグメントの一" -"部を選択的に取得します。\n" -"\n" -"良いUSB‑Cアダプターとは? 高速なスマホまたはノートPC用充電器で問題ありませ" -"ん。\n" -"\n" -"どのソフトウェアを使うかは重要ですか? はい。学習に使えるのは上流のopenpilot" -"(および特定のフォーク)のみです。" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "削除" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fiネットワーク「{}」を削除しますか?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "スマートフォンで https://connect.comma.ai にアクセス" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高温" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "非公開ネットワーク" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "非アクティブ:非従量のネットワークに接続してください" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "インストール" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IPアドレス" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "アップデートをインストール" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "ジョイスティックデバッグモード" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "読み込み中" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "縦制御マヌーバーモード" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"学習データのアップロードを最大化してopenpilotの運転モデルを改善しましょう。" +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "学習データのアップロードを最大化してopenpilotの運転モデルを改善しましょう。" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "該当なし" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "いいえ" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "ネットワーク" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "SSH鍵が見つかりません" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "ユーザー'{}'のSSH鍵が見つかりません" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "リリースノートはありません。" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "オフライン" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "オンライン" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "開く" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "ペアリング" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "プレビュー" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "prime の特典:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "デバイスをペアリング" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "デバイスをペアリング" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "デバイスをあなたの comma アカウントにペアリング" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"デバイスを comma connect(connect.comma.ai)とペアリングして、comma prime 特" -"典を受け取りましょう。" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "デバイスを comma connect(connect.comma.ai)とペアリングして、comma prime 特典を受け取りましょう。" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "初回ペアリングを完了するにはWi‑Fiに接続してください" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "電源オフ" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "従量課金のWi‑Fi接続時は大きなデータのアップロードを抑制" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "従量課金の携帯回線接続時は大きなデータのアップロードを抑制" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停" -"止状態である必要があります)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停止状態である必要があります)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QRコードエラー" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "削除" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "リセット" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "確認" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "再起動" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "デバイスを再起動" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "再起動して更新" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"時速31mph(50km/h)を超えて走行中にウインカーを出さず検出された車線を外れた場" -"合、車線内に戻るよう警告を受け取ります。" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "ドライバーカメラを記録してアップロード" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "マイク音声を記録してアップロード" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"走行中にマイク音声を記録・保存します。音声は comma connect のドライブレコー" -"ダー動画に含まれます。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "走行中にマイク音声を記録・保存します。音声は comma connect のドライブレコーダー動画に含まれます。" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "規制情報" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "リラックス" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "リモートアクセス" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "リモートスナップショット" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "リクエストがタイムアウトしました" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "リセット" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "キャリブレーションをリセット" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "トレーニングガイドを確認" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilotのルール、機能、制限を確認" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "選択" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH鍵" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fiネットワークを検索中..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "選択" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "ブランチを選択" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "言語を選択" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "シリアル" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "更新を後で通知" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "ソフトウェア" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "スタンダード" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"標準を推奨します。アグレッシブでは前走車に近づき、加減速も積極的になります。" -"リラックスでは前走車との距離を保ちます。対応車種ではステアリングの車間ボタン" -"でこれらの性格を切り替えられます。" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "システムが応答しません" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "すぐに手動介入してください" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "温度" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "対象ブランチ" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "テザリングのパスワード" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "トグル" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "アンインストール" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "アンインストール" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "不明" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "アップデートは車両の電源が切れている間のみダウンロードされます。" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "今すぐアップグレード" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善" -"に協力してください。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善に協力してください。" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "メートル法を使用" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"ACCと車線維持支援にopenpilotを使用します。本機能の使用中は常に注意が必要で" -"す。" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "車両" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "表示" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "開始待機中" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"警告: これはGitHub設定内のすべての公開鍵にSSHアクセスを与えます。自分以外の" -"GitHubユーザー名を絶対に入力しないでください。comma の従業員が自分のGitHub" -"ユーザー名を追加するよう求めることは決してありません。" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "openpilotへようこそ" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "有効にすると、アクセルを踏むとopenpilotが解除されます。" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fiネットワーク(従量課金)" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "パスワードが違います" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilotを使用するには、利用規約に同意する必要があります。" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"openpilotを使用するには利用規約に同意する必要があります。続行する前に " -"https://comma.ai/terms の最新の規約をお読みください。" +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "openpilotを使用するには利用規約に同意する必要があります。続行する前に https://comma.ai/terms の最新の規約をお読みください。" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "カメラを起動中" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "既定" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "下" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "アップデートの確認に失敗しました" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "「{}」向け" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "自動設定の場合は空欄のままにしてください" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "左" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "従量" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "なし" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "今" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縦制御(アルファ)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilotは利用できません" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilotは既定でチルモードで走行します。実験モードでは、チルモードにはまだ準" -"備ができていないアルファレベルの機能が有効になります。実験的な機能は以下のと" -"おりです:

エンドツーエンド縦制御


運転モデルがアクセルとブレー" -"キを制御します。openpilotは人間のように走行し、赤信号や一時停止でも停止しま" -"す。走行速度は運転モデルが決めるため、設定速度は上限としてのみ機能します。こ" -"れはアルファ品質の機能であり、誤動作が発生する可能性があります。

新し" -"い運転ビジュアライゼーション


低速時には道路向きの広角カメラに切り替わ" -"り、一部の曲がりをより良く表示します。画面右上には実験モードのロゴも表示され" -"ます。" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilotは継続的にキャリブレーションを行っており、リセットが必要になることは" -"稀です。車が起動中にキャリブレーションをリセットするとopenpilotが再起動しま" -"す。" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilotは、あなたのような人間の運転を見て運転を学習します。\n" -"\n" -"Firehoseモードを使うと、学習データのアップロードを最大化してopenpilotの運転モ" -"デルを改善できます。データが増えるほどモデルが大きくなり、実験モードがより良" -"くなります。" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilotの縦制御は将来のアップデートで提供される可能性があります。" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilotでは、デバイスの取り付け角度が左右±4°、上方向5°以内、下方向9°以内で" -"ある必要があります。" +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilotでは、デバイスの取り付け角度が左右±4°、上方向5°以内、下方向9°以内である必要があります。" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "右" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "非従量" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "上" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "最新です。最終確認: なし" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "最新です。最終確認: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "更新があります" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}件のアラート" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}日前" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}時間前" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}分前" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" +msgstr[0] "これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 登録済み" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehoseモード 🔥" + diff --git a/selfdrive/ui/translations/app_ko.po b/selfdrive/ui/translations/app_ko.po index f12aebaeb3b..9b73a22387a 100644 --- a/selfdrive/ui/translations/app_ko.po +++ b/selfdrive/ui/translations/app_ko.po @@ -17,1174 +17,1013 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " 스티어링 토크 응답 보정이 완료되었습니다." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 스티어링 토크 응답 보정이 {}% 완료되었습니다." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 장치는 {:.1f}° {} 및 {:.1f}° {} 방향을 가리키고 있습니다." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "주행 데이터 1년 보관" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "연중무휴 LTE 연결" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"경고: 이 차량에서 openpilot의 롱컨 제어는 알파 버전이며 자동 긴급 제동" -"(AEB)을 비활성화합니다.

이 차량에서는 openpilot 롱컨 제어 대신 " -"차량 내장 ACC가 기본으로 사용됩니다. openpilot 롱컨 제어로 전환하려면 이 설" -"정을 켜세요. 롱컨 제어 알파를 켤 때는 실험 모드 사용을 권장합니다. 차량 전" -"원이 켜져 있는 경우 이 설정을 변경하면 openpilot이 재시작됩니다." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

스티어링 지연 보정이 완료되었습니다." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

스티어링 지연 보정이 {}% 완료되었습니다." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "활성" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB(Android Debug Bridge)를 사용하면 USB 또는 네트워크로 장치에 연결할 수 있" -"습니다. 자세한 내용은 https://docs.comma.ai/how-to/connect-to-comma 를 참고하" -"세요." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "추가" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN 설정" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "과도한 작동을 확인" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "고급" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "공격적" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "동의" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "운전자 모니터링 항상 켜짐" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"openpilot 롱컨 제어 알파 버전은 실험 모드와 함께 비릴리스 브랜치에서 테스트" -"할 수 있습니다." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "정말 전원을 끄시겠습니까?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "정말 재시작하시겠습니까?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "정말 보정을 재설정하시겠습니까?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "정말 제거하시겠습니까?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "뒤로" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai에서 comma prime 회원이 되세요" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.ai를 홈 화면에 추가하여 앱처럼 사용하세요" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "변경" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "확인" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "안정적 모드 켜짐" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "연결" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "연결 중..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "취소" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "종량제 셀룰러" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "언어 변경" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "차량 전원이 켜져 있으면 이 설정을 변경할 때 openpilot이 재시작됩니다." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"를 눌러 오른쪽의 QR 코드를 스캔하세요" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "닫기" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "현재 버전" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "다운로드" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "거부" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "거부하고 openpilot 제거" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "개발자" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "장치" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "가속 페달로 해제" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "해제 후 전원 끄기" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "해제 후 재시작" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "해제 후 캘리브레이션 재설정" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "속도를 mph 대신 km/h로 표시합니다." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "동글 ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "다운로드" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "운전자 카메라" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "주행 성향" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "편집" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "오류" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "실험 모드 켜짐" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "사용" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB 사용" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "차선 이탈 경고 사용" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "로밍 사용" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH 사용" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "테더링 사용" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot이 작동 중이 아닐 때도 운전자 모니터링을 사용합니다." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot 사용" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "실험 모드를 사용하려면 openpilot 롱컨 제어(알파) 토글을 켜세요." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "APN 입력" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "SSID 입력" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "새 테더링 비밀번호 입력" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "비밀번호 입력" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHub 사용자 이름 입력" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "오류" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "실험 모드" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"이 차량은 롱컨 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습" -"니다." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "이 차량은 롱컨 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습니다." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "삭제 중..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "설정 완료" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "파이어호스" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "파이어호스 모드" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"최대의 효과를 위해 주 1회는 장치를 실내로 가져와 품질 좋은 USB‑C 어댑터와 " -"Wi‑Fi에 연결하세요.\n" -"\n" -"핫스팟이나 무제한 SIM에 연결되어 있다면 주행 중에도 파이어호스 모드가 동작합니" -"다.\n" -"\n" -"\n" -"자주 묻는 질문\n" -"\n" -"어떻게, 어디서 운전하는지가 중요한가요? 아니요. 평소처럼 운전하세요.\n" -"\n" -"파이어호스 모드에서 모든 구간을 가져가지나요? 아니요. 일부 구간만 선택" -"적으로 가져갑니다.\n" -"\n" -"좋은 USB‑C 어댑터는 무엇인가요? 빠른 휴대폰 또는 노트북 충전기면 충분합니" -"다.\n" -"\n" -"어떤 소프트웨어를 실행하는지가 중요한가요? 예. 학습에는 업스트림 " -"openpilot(및 일부 포크)만 사용할 수 있습니다." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "삭제" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fi 네트워크 \"{}\"를 삭제하시겠습니까?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "양호" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "휴대폰에서 https://connect.comma.ai 에 접속하세요" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "높음" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "숨겨진 네트워크" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "비활성: 비종량제 네트워크에 연결하세요" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "설치" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP 주소" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "업데이트 설치" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "조이스틱 디버그 모드" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "로딩 중" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "롱컨 기동 모드" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "최대" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선하세요." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "해당 없음" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "아니오" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "네트워크" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "SSH 키를 찾을 수 없습니다" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "사용자 '{}'의 SSH 키를 찾을 수 없습니다" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "릴리스 노트가 없습니다." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "오프라인" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "확인" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "온라인" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "열기" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "페어링" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "미리보기" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "프라임 기능:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "장치 페어링" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "장치 페어링" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "장치를 귀하의 comma 계정에 페어링하세요" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"장치를 comma connect(connect.comma.ai)와 페어링하고 comma 프라임 혜택을 받으세" -"요." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "장치를 comma connect(connect.comma.ai)와 페어링하고 comma 프라임 혜택을 받으세요." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "초기 페어링을 완료하려면 Wi‑Fi에 연결하세요" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "전원 끄기" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "종량제 Wi‑Fi 연결 시 대용량 업로드 방지" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "종량제 셀룰러 연결 시 대용량 업로드 방지" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량" -"은 꺼져 있어야 합니다)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량은 꺼져 있어야 합니다)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR 코드 오류" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "제거" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "재설정" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "검토" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "재시작" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "장치 재시작" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "재시작 및 업데이트" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"시속 31mph(50km/h) 이상에서 방향지시등 없이 감지된 차선 밖으로 벗어나면 차선" -"으로 복귀하라는 경고를 받습니다." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "운전자 카메라 기록 및 업로드" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "마이크 오디오 기록 및 업로드" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"주행 중 마이크 오디오를 기록하고 저장합니다. 오디오는 comma connect의 대시캠 " -"영상에 포함됩니다." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "주행 중 마이크 오디오를 기록하고 저장합니다. 오디오는 comma connect의 대시캠 영상에 포함됩니다." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "규제 정보" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "편안한" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "원격 액세스" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "원격 스냅샷" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "요청 시간이 초과되었습니다" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "재설정" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "캘리브레이션 재설정" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "학습 가이드 검토" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilot의 규칙, 기능 및 제한을 검토" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "선택" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 키" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fi 네트워크 검색 중..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "선택" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "브랜치 선택" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "언어 선택" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "시리얼" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "업데이트 나중에 알림" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "소프트웨어" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "표준" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"표준을 권장합니다. 공격적 모드에서는 앞차를 더 가깝게 따라가고 가감속이 더 적" -"극적입니다. 편안한 모드에서는 앞차와 거리를 더 둡니다. 지원 차량에서는 스티어" -"링의 차간 버튼으로 이 성향들을 전환할 수 있습니다." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "시스템 응답 없음" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "즉시 수동 조작하세요" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "온도" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "대상 브랜치" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "테더링 비밀번호" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "토글" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "제거" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "업데이트" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "제거" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "알수없음" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "업데이트는 차량 전원이 꺼져 있을 때만 다운로드됩니다." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "지금 업그레이드" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움" -"을 주세요." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움을 주세요." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "미터법 사용" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"ACC 및 차선 유지 보조에 openpilot을 사용합니다. 이 기능을 사용할 때는 항상 주" -"의가 필요합니다." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "차량" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "보기" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "시작 대기 중" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"경고: 이는 GitHub 설정의 모든 공개 키에 SSH 액세스를 부여합니다. 자신의 것이 " -"아닌 GitHub 사용자 이름을 절대 입력하지 마세요. comma 직원이 본인의 GitHub 사" -"용자 이름 추가를 요구하는 일은 결코 없습니다." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "openpilot에 오신 것을 환영합니다" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "이 옵션을 켜면 가속 페달을 밟을 때 openpilot이 해제됩니다." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 네트워크 종량제" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "비밀번호가 올바르지 않습니다" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot을 사용하려면 약관에 동의해야 합니다." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma." -"ai/terms 에서 최신 약관을 읽어주세요." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma.ai/terms 에서 최신 약관을 읽어주세요." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "카메라 시작 중" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma 프라임" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "기본값" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "아래" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "업데이트 확인 실패" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "\"{}\"용" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "자동 구성을 사용하려면 비워 두세요" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "왼쪽" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "종량제" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "없음" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "지금" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 롱컨 제어(알파)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 사용 불가" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot은 기본적으로 안정적 모드로 주행합니다. 실험 모드를 사용하면 안정적 모드에 " -"아직 준비되지 않은 알파 수준의 기능이 활성화됩니다. 실험 기능은 아래와 같습니" -"다:

엔드투엔드 롱컨 제어


주행 모델이 가속과 제동을 제어합니" -"다. openpilot은 빨간 신호 및 정지 표지에서의 정지를 포함해 사람이 운전한다고 " -"판단하는 방식으로 주행합니다. 주행 속도는 모델이 결정하므로 설정 속도는 상한" -"으로만 동작합니다. 알파 품질 기능이므로 오작동이 발생할 수 있습니다.

" -"새로운 주행 시각화


저속에서는 도로 방향의 광각 카메라로 전환되어 일" -"부 회전을 더 잘 보여줍니다. 화면 오른쪽 위에는 실험 모드 로고도 표시됩니다." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot은 지속적으로 보정을 진행하므로 재설정이 필요한 경우는 드뭅니다. 차" -"량 전원이 켜져 있을 때 보정을 재설정하면 openpilot이 재시작됩니다." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot은 당신과 같은 사람의 운전을 보며 운전을 학습합니다.\n" -"\n" -"Firehose 모드는 학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선" -"할 수 있게 해줍니다. 데이터가 많을수록 모델은 커지고, 실험 모드는 더 좋아집니" -"다." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 롱컨 제어는 향후 업데이트에서 제공될 수 있습니다." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot은 장치를 좌우 4°, 위쪽 5°, 아래쪽 9° 이내로 장착해야 합니다." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "오른쪽" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "비종량제" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "위" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "최신입니다. 마지막 확인: 없음" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "최신입니다. 마지막 확인: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "업데이트 가능" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}건의 알림" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}일 전" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}시간 전" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}분 전" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "현재까지 귀하의 주행 {}구간이 학습 데이터셋에 포함되었습니다." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 구독됨" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 파이어호스 모드 🔥" + diff --git a/selfdrive/ui/translations/app_pt-BR.po b/selfdrive/ui/translations/app_pt-BR.po index 84b53c6e8d9..1adb797c887 100644 --- a/selfdrive/ui/translations/app_pt-BR.po +++ b/selfdrive/ui/translations/app_pt-BR.po @@ -19,1202 +19,1018 @@ msgstr "" "X-Language: pt_BR\n" "X-Source-Language: C\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " A calibração da resposta de torque da direção foi concluída." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " A calibração da resposta de torque da direção está {}% concluída." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Seu dispositivo está apontado {:.1f}° {} e {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 ano de armazenamento de condução" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Conectividade LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"AVISO: o controle longitudinal do openpilot está em alpha para este carro " -"e desativará a Frenagem Automática de Emergência (AEB).

Neste " -"carro, o openpilot usa por padrão o ACC integrado do carro em vez do " -"controle longitudinal do openpilot. Ative isto para alternar para o controle " -"longitudinal do openpilot. Recomenda-se ativar o Modo Experimental ao ativar " -"o controle longitudinal do openpilot em alpha." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

A calibração da latência da direção está concluída." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

A calibração da latência da direção está {}% concluída." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "ATIVO" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) permite conectar ao seu dispositivo via USB ou " -"pela rede. Veja https://docs.comma.ai/how-to/connect-to-comma para mais " -"informações." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ADICIONAR" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "Configuração de APN" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Reconhecer Atuação Excessiva" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Avançado" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agressivo" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Concordo" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Monitoramento de Motorista Sempre Ativo" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Uma versão alpha do controle longitudinal do openpilot pode ser testada, " -"junto com o Modo Experimental, em ramificações fora de release." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Tem certeza de que deseja desligar?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Tem certeza de que deseja reiniciar?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Tem certeza de que deseja redefinir a calibração?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Tem certeza de que deseja desinstalar?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Voltar" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Torne-se membro comma prime em connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Adicione connect.comma.ai à tela inicial para usá-lo como um app" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "ALTERAR" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "VERIFICAR" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "MODO CHILL ATIVO" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "CONECTANDO..." -#: system/ui/widgets/confirm_dialog.py:23 -#: system/ui/widgets/option_dialog.py:35 system/ui/widgets/keyboard.py:81 -#: system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Cancelar" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Dados móveis limitados" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Alterar Idioma" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." +msgstr "Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Toque em \"adicionar novo dispositivo\" e escaneie o QR code à direita" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Fechar" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Versão Atual" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "BAIXAR" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Recusar" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Recusar, desinstalar o openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Desenvolv" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Dispositivo" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Desativar ao pressionar o acelerador" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Desativar para Desligar" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Desativar para Reiniciar" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Desativar para Redefinir Calibração" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Exibir velocidade em km/h em vez de mph." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID do Dongle" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Baixar" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Câmera do Motorista" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Personalidade" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "EDITAR" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ERRO" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ATIVO" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Ativar" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Ativar ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Ativar alertas de saída de faixa" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Ativar openpilot" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Ativar SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Ativar alertas de saída de faixa" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" -"Ativar monitoramento do motorista mesmo quando o openpilot não está engajado." +msgstr "Ativar monitoramento do motorista mesmo quando o openpilot não está engajado." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Ativar openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Ative a opção de controle longitudinal do openpilot (alpha) para permitir o " -"Modo Experimental." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Ative a opção de controle longitudinal do openpilot (alpha) para permitir o Modo Experimental." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Digite APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Digite SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Digite nova senha tethering" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Digite a senha" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Digite seu nome de usuário do GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Erro" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Modo Experimental" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"O Modo Experimental está indisponível neste carro pois o ACC original do " -"carro é usado para controle longitudinal." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "O Modo Experimental está indisponível neste carro pois o ACC original do carro é usado para controle longitudinal." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "ESQUECENDO..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Configure" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Modo Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Para máxima efetividade, leve seu dispositivo para dentro e conecte a um bom " -"adaptador USB-C e Wi‑Fi semanalmente.\n" -"\n" -"O Modo Firehose também pode funcionar enquanto você dirige se estiver " -"conectado a um hotspot ou a um SIM ilimitado.\n" -"\n" -"\n" -"Perguntas Frequentes\n" -"\n" -"Importa como ou onde eu dirijo? Não, apenas dirija como normalmente.\n" -"\n" -"Todos os meus segmentos são puxados no Modo Firehose? Não, puxamos " -"seletivamente um subconjunto dos seus segmentos.\n" -"\n" -"Qual é um bom adaptador USB‑C? Qualquer carregador rápido de telefone ou " -"laptop serve.\n" -"\n" -"Importa qual software eu executo? Sim, apenas o openpilot upstream (e forks " -"específicos) podem ser usados para treinamento." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Esquecer" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Esquecer rede Wi-Fi \"{}\"?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "BOM" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Acesse https://connect.comma.ai no seu telefone" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Rede" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "INATIVO: conecte a uma rede sem franquia" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "Endereço IP" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Instalar Atualização" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Modo de Depuração do Joystick" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "CARREGANDO" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Modo de Manobra Longitudinal" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MÁX" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Maximize seus envios de dados de treinamento para melhorar os modelos de " -"condução do openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Maximize seus envios de dados de treinamento para melhorar os modelos de condução do openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "N/A" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "NÃO" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Rede" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Nenhuma chave SSH encontrada" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Nenhuma chave SSH encontrada para o usuário '{username}'" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Sem notas de versão disponíveis." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ONLINE" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Abrir" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "EMPARELHAR" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "PRÉVIA" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "RECURSOS PRIME:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Emparelhar Dispositivo" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Emparelhar dispositivo" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Emparelhe seu dispositivo à sua conta comma" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Emparelhe seu dispositivo com o comma connect (connect.comma.ai) e resgate " -"sua oferta comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Emparelhe seu dispositivo com o comma connect (connect.comma.ai) e resgate sua oferta comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conecte-se ao Wi‑Fi para concluir o emparelhamento inicial" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Desligar" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Evitar uploads grandes de dados em conexões Wi-Fi limitadas" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Evitar uploads grandes de dados em conexões móveis limitadas" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Pré-visualize a câmera voltada para o motorista para garantir que o " -"monitoramento do motorista tenha boa visibilidade. (veículo deve estar " -"desligado)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Pré-visualize a câmera voltada para o motorista para garantir que o monitoramento do motorista tenha boa visibilidade. (veículo deve estar desligado)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Erro no QR Code" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "REMOVER" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "REDEFINIR" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "REVISAR" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Reiniciar" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Reiniciar Dispositivo" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Reiniciar e Atualizar" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Receba alertas para voltar à faixa quando seu veículo cruzar uma linha de " -"faixa detectada sem seta ativada ao dirigir acima de 31 mph (50 km/h)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Gravar e Enviar Câmera do Motorista" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Gravar e Enviar Áudio do Microfone" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Grave e armazene o áudio do microfone enquanto dirige. O áudio será incluído " -"no vídeo da dashcam no comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Grave e armazene o áudio do microfone enquanto dirige. O áudio será incluído no vídeo da dashcam no comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Regulatório" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Relaxado" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Acesso remoto" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Capturas remotas" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Tempo da solicitação esgotado" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Redefinir" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Redefinir Calibração" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Revisar Guia de Treinamento" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Revise as regras, recursos e limitações do openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "SELECIONAR" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "Chaves SSH" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Procurando redes Wi-Fi..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Selecione" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "Selecione uma branch" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Selecione um idioma" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Serial" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Adiar Atualização" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Software" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Padrão" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Padrão é recomendado. No modo agressivo, o openpilot seguirá veículos à " -"frente mais de perto e será mais agressivo com acelerador e freio. No modo " -"relaxado, o openpilot ficará mais longe dos veículos à frente. Em carros " -"compatíveis, você pode alternar essas personalidades com o botão de " -"distância do volante." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistema sem resposta" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "ASSUMA O CONTROLE IMEDIATAMENTE" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "Branch Alvo" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Senha Tethering" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Toggles" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "DESINSTALAR" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ATUALIZAR" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Desinstalar" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Desconhecido" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Atualizações são baixadas apenas com o carro desligado." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Atualizar Agora" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Envie dados da câmera voltada para o motorista e ajude a melhorar o " -"algoritmo de monitoramento do motorista." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Envie dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramento do motorista." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Usar Sistema Métrico" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Use o sistema openpilot para controle de cruzeiro adaptativo e assistência " -"de permanência em faixa. Sua atenção é necessária o tempo todo para usar " -"este recurso." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "VEÍCULO" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "VER" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Aguardando para iniciar" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Aviso: Isso concede acesso SSH a todas as chaves públicas nas suas " -"configurações do GitHub. Nunca informe um nome de usuário do GitHub que não " -"seja o seu. Um funcionário da comma NUNCA pedirá para você adicionar o nome " -"de usuário do GitHub dele." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Bem-vindo ao openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Quando ativado, pressionar o pedal do acelerador desengajará o openpilot." +msgstr "Quando ativado, pressionar o pedal do acelerador desengajará o openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Rede Wi-Fi limitada" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Senha errada" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Você deve aceitar os Termos e Condições para usar o openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Você deve aceitar os Termos e Condições para usar o openpilot. Leia os " -"termos mais recentes em https://comma.ai/terms antes de continuar." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Você deve aceitar os Termos e Condições para usar o openpilot. Leia os termos mais recentes em https://comma.ai/terms antes de continuar." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "câmera iniciando" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "default" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "para baixo" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "falha ao verificar atualização" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "para \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "deixe em branco para configuração automática" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "à esquerda" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "limitados" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "nunca" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "agora" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Controle Longitudinal do openpilot (Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Indisponível" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables " -"alpha-level features that aren't ready for chill mode. Experimental features " -"are listed below:

End-to-End Longitudinal Control


Let the " -"driving model control the gas and brakes. openpilot will drive as it thinks " -"a human would, including stopping for red lights and stop signs. Since the " -"driving model decides the speed to drive, the set speed will only act as an " -"upper bound. This is an alpha quality feature; mistakes should be " -"expected.

New Driving Visualization


The driving visualization " -"will transition to the road-facing wide-angle camera at low speeds to better " -"show some turns. The Experimental mode logo will also be shown in the top " -"right corner." -msgstr "" -"o openpilot dirige por padrão no modo chill. O Modo Experimental habilita " -"recursos em nível alpha que não estão prontos para o modo chill. Os recursos " -"experimentais são listados abaixo:

Controle Longitudinal " -"End-to-End


Permita que o modelo de condução controle o acelerador e " -"os freios. O openpilot dirigirá como acha que um humano faria, incluindo " -"parar em sinais e semáforos vermelhos. Como o modelo decide a velocidade, a " -"velocidade definida atuará apenas como limite superior. Este é um recurso de " -"qualidade alpha; erros devem ser esperados.

Nova Visualização de " -"Condução


A visualização de condução mudará para a câmera " -"grande-angular voltada para a estrada em baixas velocidades para mostrar " -"melhor algumas curvas. O logotipo do Modo Experimental também será exibido " -"no canto superior direito." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"O openpilot está continuamente calibrando, resetar é raramente solicitado. " -"Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"o openpilot aprende a dirigir observando humanos, como você, dirigirem.\n" -"\n" -"O Modo Firehose permite maximizar seus envios de dados de treinamento para " -"melhorar os modelos de condução do openpilot. Mais dados significam modelos " -"maiores, o que significa um Modo Experimental melhor." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." -msgstr "" -"o controle longitudinal do openpilot pode vir em uma atualização futura." +msgstr "o controle longitudinal do openpilot pode vir em uma atualização futura." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"o openpilot requer que o dispositivo seja montado dentro de 4° para a " -"esquerda ou direita e dentro de 5° para cima ou 9° para baixo." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "o openpilot requer que o dispositivo seja montado dentro de 4° para a esquerda ou direita e dentro de 5° para cima ou 9° para baixo." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "à direita" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "ilimitados" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "para cima" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "atualizado, última verificação: nunca" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "atualizado, última verificação: {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "atualização disponível" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} dia atrás" msgstr[1] "{} dias atrás" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hora atrás" msgstr[1] "{} horas atrás" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minuto atrás" msgstr[1] "{} minutos atrás" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} segmento da sua condução está no conjunto de treinamento até agora." -msgstr[1] "" -"{} segmentos da sua condução estão no conjunto de treinamento até agora." +msgstr[0] "{} segmento da sua condução está no conjunto de treinamento até agora." +msgstr[1] "{} segmentos da sua condução estão no conjunto de treinamento até agora." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ASSINADO" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" + diff --git a/selfdrive/ui/translations/app_th.po b/selfdrive/ui/translations/app_th.po index f2e56f2882c..facf52d9228 100644 --- a/selfdrive/ui/translations/app_th.po +++ b/selfdrive/ui/translations/app_th.po @@ -17,1113 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "" msgstr[1] "" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "" + diff --git a/selfdrive/ui/translations/app_tr.po b/selfdrive/ui/translations/app_tr.po index 10191234a1f..137d350c2a0 100644 --- a/selfdrive/ui/translations/app_tr.po +++ b/selfdrive/ui/translations/app_tr.po @@ -17,1194 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Direksiyon tork tepkisi kalibrasyonu tamamlandı." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " Direksiyon tork tepkisi kalibrasyonu {}% tamamlandı." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Cihazınız {:.1f}° {} ve {:.1f}° {} yönünde konumlandırılmış." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 yıl sürüş depolaması" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "7/24 LTE bağlantısı" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"UYARI: Bu araç için openpilot boylamsal kontrolü alfa aşamasındadır ve " -"Otomatik Acil Frenlemeyi (AEB) devre dışı bırakacaktır.

Bu araçta " -"openpilot, openpilot'un boylamsal kontrolü yerine aracın yerleşik ACC'sini " -"varsayılan olarak kullanır. openpilot boylamsal kontrolüne geçmek için bunu " -"etkinleştirin. openpilot boylamsal kontrol alfayı etkinleştirirken Deneysel " -"modu etkinleştirmeniz önerilir." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "AKTİF" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge), cihazınıza USB veya ağ üzerinden bağlanmayı " -"sağlar. Daha fazla bilgi için https://docs.comma.ai/how-to/connect-to-comma " -"adresine bakın." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "EKLE" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Aşırı Müdahaleyi Onayla" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Agresif" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Kabul et" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Sürekli Sürücü İzleme" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"openpilot boylamsal kontrolünün alfa sürümü, Deneysel mod ile birlikte, " -"yayın dışı dallarda test edilebilir." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Kapatmak istediğinizden emin misiniz?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Yeniden başlatmak istediğinizden emin misiniz?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Kalibrasyonu sıfırlamak istediğinizden emin misiniz?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Kaldırmak istediğinizden emin misiniz?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Geri" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai adresinde comma prime üyesi olun" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"connect.comma.ai'yi ana ekranınıza ekleyerek bir uygulama gibi kullanın" +msgstr "connect.comma.ai'yi ana ekranınıza ekleyerek bir uygulama gibi kullanın" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "DEĞİŞTİR" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "KONTROL ET" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "CHILL MODU AÇIK" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "BAĞLAN" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "BAĞLAN" -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Dili Değiştir" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -" Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." +msgstr " Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"yeni cihaz ekle\"ye tıklayın ve sağdaki QR kodunu tarayın" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Kapat" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Geçerli Sürüm" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "İNDİR" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Reddet" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Reddet, openpilot'u kaldır" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Geliştirici" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Cihaz" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Gaz Pedalında Devreden Çık" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Kapatmak için Devreden Çıkın" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Yeniden Başlatmak için Devreden Çıkın" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Kalibrasyonu Sıfırlamak için Devreden Çıkın" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Hızı mph yerine km/h olarak göster." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "İndir" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Sürücü Kamerası" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Sürüş Kişiliği" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "HATA" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "DENEYSEL MOD AÇIK" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Etkinleştir" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "ADB'yi Etkinleştir" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "openpilot'u etkinleştir" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "SSH'yi Etkinleştir" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot devrede değilken bile sürücü izlemesini etkinleştir." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "openpilot'u etkinleştir" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "GitHub kullanıcı adınızı girin" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Deneysel Mod" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel " -"mod kullanılamıyor." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel mod kullanılamıyor." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "" -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Kurulumu Bitir" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose Modu" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Maksimum verim için cihazınızı içeri alın ve haftalık olarak iyi bir USB-C " -"adaptörüne ve Wi‑Fi'a bağlayın.\n" -"\n" -"Firehose Modu, bir hotspot'a veya sınırsız SIM karta bağlıyken sürüş " -"sırasında da çalışabilir.\n" -"\n" -"\n" -"Sıkça Sorulan Sorular\n" -"\n" -"Nasıl veya nerede sürdüğüm önemli mi? Hayır, normalde nasıl sürüyorsanız " -"öyle sürün.\n" -"\n" -"Firehose Modu'nda tüm segmentlerim çekiliyor mu? Hayır, segmentlerinizin bir " -"alt kümesini seçerek çekiyoruz.\n" -"\n" -"İyi bir USB‑C adaptörü nedir? Hızlı bir telefon veya dizüstü şarj cihazı " -"uygundur.\n" -"\n" -"Hangi yazılımı çalıştırdığım önemli mi? Evet, yalnızca upstream openpilot " -"(ve bazı fork'lar) eğitim için kullanılabilir." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "İYİ" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Telefonunuzda https://connect.comma.ai adresine gidin" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "YÜKSEK" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Ağ" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "PASİF: sınırsız bir ağa bağlanın" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "YÜKLE" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Güncellemeyi Yükle" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Joystick Hata Ayıklama Modu" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "YÜKLENİYOR" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Boylamsal Manevra Modu" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "MAKS" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"openpilot'un sürüş modellerini iyileştirmek için eğitim veri yüklemelerinizi " -"en üst düzeye çıkarın." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "openpilot'un sürüş modellerini iyileştirmek için eğitim veri yüklemelerinizi en üst düzeye çıkarın." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "HAYIR" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Ağ" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "SSH anahtarı bulunamadı" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "'{username}' için SSH anahtarı bulunamadı" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Sürüm notu mevcut değil." -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "ÇEVRİMDIŞI" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ÇEVRİMİÇİ" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "Aç" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "EŞLE" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "ÖNİZLEME" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME ÖZELLİKLERİ:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Cihazı Eşle" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Cihazı eşle" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Cihazınızı comma hesabınızla eşleştirin" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime " -"teklifinizi alın." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime teklifinizi alın." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "İlk eşleştirmeyi tamamlamak için lütfen Wi‑Fi'a bağlanın" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Kapat" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan " -"kamerayı önizleyin. (araç kapalı olmalıdır)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan kamerayı önizleyin. (araç kapalı olmalıdır)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR Kod Hatası" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "KALDIR" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "SIFIRLA" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "GÖZDEN GEÇİR" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Yeniden Başlat" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Cihazı Yeniden Başlat" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Yeniden Başlat ve Güncelle" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Araç 31 mph (50 km/h) üzerindeyken sinyal verilmeden algılanan şerit " -"çizgisini aştığınızda şeride geri dönmeniz için uyarılar alın." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Sürücü Kamerasını Kaydet ve Yükle" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Mikrofon Sesini Kaydet ve Yükle" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Sürüş sırasında mikrofon sesini kaydedip saklayın. Ses, comma connect'teki " -"ön kamera videosuna dahil edilecektir." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Sürüş sırasında mikrofon sesini kaydedip saklayın. Ses, comma connect'teki ön kamera videosuna dahil edilecektir." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Mevzuat" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Rahat" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Uzaktan erişim" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Uzaktan anlık görüntüler" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "İstek zaman aşımına uğradı" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Sıfırla" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Kalibrasyonu Sıfırla" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Eğitim Kılavuzunu İncele" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" -msgstr "" -"openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" +msgstr "openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Bir dil seçin" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Seri" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Güncellemeyi Ertele" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Yazılım" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Standart" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Standart önerilir. Agresif modda openpilot öndeki aracı daha yakından takip " -"eder ve gaz/fren kullanımında daha ataktır. Rahat modda openpilot öndeki " -"araçlardan daha uzak durur. Desteklenen araçlarda bu kişilikler arasında " -"direksiyon mesafe düğmesiyle geçiş yapabilirsiniz." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Sistem Yanıt Vermiyor" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "HEMEN KONTROLÜ DEVRALIN" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "TEMP" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Seçenekler" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "KALDIR" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "GÜNCELLE" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Kaldır" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Bilinmiyor" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Güncellemeler yalnızca araç kapalıyken indirilir." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Şimdi Yükselt" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını " -"geliştirmeye yardımcı olun." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını geliştirmeye yardımcı olun." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Metrik Sistemi Kullan" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Uyarlanabilir hız sabitleyici ve şerit koruma sürücü yardımında openpilot " -"sistemini kullanın. Bu özelliği kullanırken her zaman dikkatli olmanız " -"gerekir." - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "ARAÇ" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "GÖRÜNTÜLE" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Başlatma bekleniyor" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Uyarı: Bu, GitHub ayarlarınızdaki tüm açık anahtarlara SSH erişimi verir. " -"Kendi adınız dışında asla bir GitHub kullanıcı adı girmeyin. Bir comma " -"çalışanı sizden asla GitHub kullanıcı adlarını eklemenizi İSTEMEZ." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "openpilot'a hoş geldiniz" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" -"Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıkarır." +msgstr "Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıkarır." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam " -"etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "kamera başlatılıyor" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "aşağı" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "güncelleme kontrolü başarısız" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "sol" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "asla" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "şimdi" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Boylamsal Kontrol (Alfa)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Kullanılamıyor" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot varsayılan olarak chill modunda sürer. Deneysel mod, chill moduna " -"hazır olmayan alfa seviyesindeki özellikleri etkinleştirir. Deneysel " -"özellikler aşağıda listelenmiştir:

Uçtan Uca Boylamsal Kontrol
Sürüş modelinin gaz ve frenleri kontrol etmesine izin verin. " -"openpilot, kırmızı ışıklarda ve dur işaretlerinde durmak dahil, bir insan " -"nasıl sürer diye düşündüğüne göre sürer. Hızı sürüş modeli belirlediğinden, " -"ayarlanan hız yalnızca üst sınır olarak işlev görür. Bu bir alfa kalitesinde " -"özelliktir; hatalar beklenmelidir.

Yeni Sürüş Görselleştirmesi
Sürüş görselleştirmesi, düşük hızlarda bazı dönüşleri daha iyi " -"göstermek için yola bakan geniş açılı kameraya geçer. Deneysel mod logosu " -"sağ üst köşede de gösterilecektir." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -" Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot, sizin gibi insanların nasıl sürdüğünü izleyerek sürmeyi öğrenir.\n" -"\n" -"Firehose Modu, openpilot'un sürüş modellerini geliştirmek için eğitim veri " -"yüklemelerinizi en üst düzeye çıkarmanıza olanak tanır. Daha fazla veri, " -"daha büyük modeller demektir; bu da daha iyi Deneysel Mod anlamına gelir." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot boylamsal kontrolü gelecekteki bir güncellemede gelebilir." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte " -"edilmesini gerektirir." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte edilmesini gerektirir." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "sağ" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "yukarı" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "güncel, son kontrol asla" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "güncel, son kontrol {}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "güncelleme mevcut" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} UYARI" msgstr[1] "{} UYARILAR" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} gün önce" msgstr[1] "{} gün önce" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} saat önce" msgstr[1] "{} saat önce" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} dakika önce" msgstr[1] "{} dakika önce" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment sürüşünüz eğitim veri setinde." msgstr[1] "{} segment sürüşünüz eğitim veri setinde." -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ABONE" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Modu 🔥" + diff --git a/selfdrive/ui/translations/app_uk.po b/selfdrive/ui/translations/app_uk.po index cf78fb5a330..e36ceac2ceb 100644 --- a/selfdrive/ui/translations/app_uk.po +++ b/selfdrive/ui/translations/app_uk.po @@ -15,1193 +15,980 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " -"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" "X-Generator: Poedit 3.8\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " Калібрування реакції крутного моменту керма завершено." -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr "Калібрування реакції крутного моменту керма завершено на {}%." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ваш пристрій нахилено на {:.1f}° {} та {:.1f}° {}." -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 рік зберігання поїздок" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "Підключення LTE 24/7" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"ПОПЕРЕДЖЕННЯ: поздовжнє керування openpilot для цього автомобіля знаходиться " -"в стадії альфа-тестування і вимкне автоматичне екстрене гальмування (AEB)." - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

Калібрування затримки кермування завершено." -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

Калібрування затримки кермування завершено на {}%." -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "АКТИВНИЙ" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) дозволяє підключатися до вашого пристрою через " -"USB або мережу. Дивіться https://docs.comma.ai/how-to/connect-to-comma для " -"отримання додаткової інформації." - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "ДОДАТИ" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "Налаштування APN" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "Визнайте надмірне спрацьовування" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "Розширені" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "Агресивн." -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "Погодитися" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "Постійний моніторинг водія" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "" -"Альфа-версію поздовжнього керування openpilot можна протестувати разом з " -"експериментальним режимом на нерелізних гілках." - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "Ви впевнені, що хочете вимкнути?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "Ви впевнені, що хочете перезавантажити?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "Ви впевнені, що хочете скинути калібрування?" -#: selfdrive/ui/layouts/settings/software.py:171 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "Ви впевнені, що хочете видалити?" -#: system/ui/widgets/network.py:99 -#: selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "Назад" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "Станьте членом comma prime на connect.comma.ai" -#: selfdrive/ui/widgets/pairing_dialog.py:119 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" -"Додайте connect.comma.ai до головного екрану, щоб використовувати його як " -"додаток." +msgstr "Додайте connect.comma.ai до головного екрану, щоб використовувати його як додаток." -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "ЗМІНИТИ" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:115 -#: selfdrive/ui/layouts/settings/software.py:126 -#: selfdrive/ui/layouts/settings/software.py:155 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "ПЕРЕВІРИТИ" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "СПОКІЙНИЙ РЕЖИМ" -#: system/ui/widgets/network.py:155 -#: selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 -#: selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "ПІДКЛЮЧА..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/network.py:318 system/ui/widgets/keyboard.py:81 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "Скасувати" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "Лімітне стільникове з'єднання" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "Змінити мову" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" -"Зміна цього параметра призведе до перезапуску openpilot, якщо автомобіль " -"увімкнено." +msgstr "Зміна цього параметра призведе до перезапуску openpilot, якщо автомобіль увімкнено." -#: selfdrive/ui/widgets/pairing_dialog.py:118 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Натисніть «додати новий пристрій» і відскануйте QR-код праворуч." -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "Закрити" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "Поточна версія" -#: selfdrive/ui/layouts/settings/software.py:118 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "ВАНТАЖ" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "Відхилити" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "Відхилити, видалити openpilot" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "Розробник" -#: selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "Пристрій" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "Вимкнення при натисканні на педаль газу" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "Вимкніть openpilot, щоб вимкнути пристрій" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "Вимкніть openpilot, щоб перезавантажити" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "Деактивуйте для скидання калібрування" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "Відображати швидкість у км/год замість миль/год." -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "ID ключа" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "Завантажити" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "Камера водія" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "Стиль водіння" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "РЕДАГ." -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "ПОМИЛКА" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "ЕКСПЕРИМЕНТ. РЕЖИМ" -#: selfdrive/ui/layouts/settings/toggles.py:228 -#: selfdrive/ui/layouts/settings/developer.py:166 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "Увімкнути" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "Увімкнути ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "Увімкнути попередження про виїзд зі смуги" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "Увімкнути роумінг" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "Увімкнути SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "Увімкнути точку доступу" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Увімкнути моніторинг водія, навіть коли openpilot не ввімкнено." -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "Увімкнути openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." -msgstr "" -"Увімкніть перемикач поздовжнього керування openpilot (альфа), щоб увімкнути " -"експериментальний режим." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "Увімкніть перемикач поздовжнього керування openpilot (альфа), щоб увімкнути експериментальний режим." -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "Введіть APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "Введіть SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "Введіть новий пароль для модему" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "Введіть пароль" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "Введіть ваш логін GitHub" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "Помилка" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "Експериментальний режим" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." -msgstr "" -"Експериментальний режим наразі недоступний для цього автомобіля, оскільки " -"для поздовжнього керування використовується штатний адаптивний круїз-" -"контроль (ACC)." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "Експериментальний режим наразі недоступний для цього автомобіля, оскільки для поздовжнього керування використовується штатний адаптивний круїз-контроль (ACC)." -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "ЗАБУВАЮ..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "Завершити налаштування" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Злива" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Режим зливи" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"Для максимальної ефективності щотижня заносьте пристрій у приміщення та " -"підключайте його до якісного адаптера USB-C і Wi-Fi.\n" -"\n" -"Режим Зливи також може працювати під час руху, якщо пристрій підключено до " -"точки доступу або SIM-картки з необмеженим трафіком.\n" -"\n" -"\n" -"Поширені запитання\n" -"\n" -"Чи має значення, як і де я їду? Ні, просто їдьте, як зазвичай.\n" -"\n" -"Чи всі мої сегменти потрапляють у режим Зливи? Ні, ми вибірково вибираємо " -"підмножину ваших сегментів.\n" -"\n" -"Що таке хороший адаптер USB-C? Будь-який швидкий зарядний пристрій для " -"телефону або ноутбука підійде.\n" -"\n" -"Чи має значення, яке програмне забезпечення я використовую? Так, для " -"навчання можна використовувати тільки upstream openpilot (і певні його " -"форки)." - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "Заб-и" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Забути мережу Wi-Fi \"{}\"?" -#: selfdrive/ui/layouts/sidebar.py:71 -#: selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "ДОБРА" -#: selfdrive/ui/widgets/pairing_dialog.py:117 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "Перейдіть на сайт https://connect.comma.ai на своєму телефоні." -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "ВИСОКА" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "Прихована мережа" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "НЕАКТИВНО: підключення до мережі без ліміту трафіку" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:144 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "ВСТАНОВ." -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP-адреса" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "Встановити оновлення" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "Режим зневадження джойстика" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "ЗАВАНТАЖЕННЯ" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "Режим поздовжнього маневрування" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "МАКС" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." -msgstr "" -"Максимізуйте завантаження навчальних даних, щоб поліпшити моделі openpilot." +msgid "Maximize your training data uploads to improve openpilot's driving models." +msgstr "Максимізуйте завантаження навчальних даних, щоб поліпшити моделі openpilot." -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "Н/Д" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "НЕМАЄ" -#: selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "Мережа" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "Не знайдено ключів SSH" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "Користувач '{}' не має ключів на GitHub" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "Інформація про випуск відсутня." -#: selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "ОФЛАЙН" -#: system/ui/widgets/confirm_dialog.py:93 system/ui/widgets/html_render.py:263 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "OK" -#: selfdrive/ui/layouts/sidebar.py:72 -#: selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "ОНЛАЙН" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "ВІДКРИТИ" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "ПІДКЛЮЧИТИ" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "ПОКАЖИ" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "XАРАКТЕРИСТИКИ PRIME:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "Підключити пристрій" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "Підключити пристрій" -#: selfdrive/ui/widgets/pairing_dialog.py:92 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "Підключіть свій пристрій до обліковки comma connect" -#: selfdrive/ui/widgets/setup.py:48 -#: selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"Підключіть свій пристрій до comma connect (connect.comma.ai) і отримайте " -"свою пропозицію comma prime." +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "Підключіть свій пристрій до comma connect (connect.comma.ai) і отримайте свою пропозицію comma prime." -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Будь ласка, підключіться до Wi-Fi, щоб завершити початкове сполучення." -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "Вимкнути" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" -"Запобігайте завантаженню великих обсягів даних під час використання Wi-Fi-" -"з'єднання з обмеженим трафіком" +msgstr "Запобігайте завантаженню великих обсягів даних під час використання Wi-Fi-з'єднання з обмеженим трафіком" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" -"Запобігати великим завантаженням даних під час лімітного стільникового " -"з'єднання" +msgstr "Запобігати великим завантаженням даних під час лімітного стільникового з'єднання" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" -msgstr "" -"Попередньо перегляньте камеру, спрямовану на водія, щоб переконатися, що " -"система моніторингу водія має добру видимість. (автомобіль повинен бути " -"вимкнений)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" +msgstr "Попередньо перегляньте камеру, спрямовану на водія, щоб переконатися, що система моніторингу водія має добру видимість. (автомобіль повинен бути вимкнений)" -#: selfdrive/ui/widgets/pairing_dialog.py:150 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "Помилка QR-коду" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "ВИДАЛИТИ" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "Скинути" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "ДИВИТИСЬ" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "Перезавантажити" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "Перезавантажте пристрій" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "Перезавантажити та оновити" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"Отримувати попередження про необхідність повернутися в смугу, коли ваш " -"автомобіль перетинає виявлену лінію розмітки без увімкненого сигналу " -"повороту під час руху зі швидкістю понад 31 миль/год (50 км/год)." - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "Писати та вантажити відео з камери водія" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "Запис та завантаження аудіо з мікрофона" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"Записуйте та зберігайте аудіо з мікрофона під час руху. Аудіо буде включено " -"до відео з відеореєстратора в comma connect." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "Записуйте та зберігайте аудіо з мікрофона під час руху. Аудіо буде включено до відео з відеореєстратора в comma connect." -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "Нормативні документи" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "Спокійний" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "Віддалений доступ" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "Віддалені знімки" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "Час запиту вичерпано" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "Скинути" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "Скинути калібрування" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "Переглянути посібник з навчання" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "Перегляньте правила, функції та обмеження openpilot" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "ВИБРАТИ" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH ключі" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "Пошук мереж..." -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "Вибрати" -#: selfdrive/ui/layouts/settings/software.py:191 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "Виберіть гілку" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "Виберіть мову" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "Серійний номер" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "Відкласти оновлення" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "Програма" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "Стандарт" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"Рекомендується стандартний режим. В агресивному режимі openpilot буде " -"триматися ближче до автомобілів попереду і більш агресивно використовувати " -"газ і гальма. У спокійному режимі openpilot буде триматися на більшій " -"відстані від автомобілів попереду. На підтримуваних автомобілях ви можете " -"перемикатися між цими режимами за допомогою кнопки дистанції на кермі." - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "Система не реагує" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "КЕРМУЙТЕ НЕГАЙНО" -#: selfdrive/ui/layouts/sidebar.py:71 -#: selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "ТЕМП" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "Цільова гілка" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "Пароль для точки доступу" -#: selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "Перемикачі" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "ВИДАЛИТИ" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "ОНОВИТИ" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:171 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "Видалити" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "Невідомо" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "Оновлення завантажуються лише тоді, коли автомобіль вимкнено." -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "Оновити зараз" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." -msgstr "" -"Завантажуйте дані з камери, спрямованої на водія, та допоможіть покращити " -"алгоритм моніторингу водія." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." +msgstr "Завантажуйте дані з камери, спрямованої на водія, та допоможіть покращити алгоритм моніторингу водія." -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "Використовувати метричну систему" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"Використовуйте систему openpilot для адаптивного круїз-контролю та допомоги " -"в утриманні смуги руху. Ваша увага потрібна постійно при використанні цієї " -"функції. Зміна цього налаштування набуває чинності після вимкнення живлення " -"автомобіля." - -#: selfdrive/ui/layouts/sidebar.py:72 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "АВТО" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "ДИВИСЬ" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "Очікування початку" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"Попередження: це надає доступ по SSH до всіх публічних ключів у ваших " -"налаштуваннях GitHub. Ніколи не вводьте ім'я користувача GitHub, окрім " -"вашого власного. Співробітник comma НІКОЛИ не попросить вас додати його ім'я " -"користувача GitHub." - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "Ласкаво просимо до openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Якщо увімкнено, натискання на педаль акселератора вимкне openpilot." -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Трафік Wi-Fi" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "Невірний пароль" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Ви повинні прийняти Умови та положення, щоб користуватися openpilot." -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"Ви повинні прийняти Умови використання, щоб користуватися openpilot. Перед " -"тим, як продовжити, ознайомтеся з останніми умовами на сайті https://" -"comma.ai/terms." +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "Ви повинні прийняти Умови використання, щоб користуватися openpilot. Перед тим, як продовжити, ознайомтеся з останніми умовами на сайті https://comma.ai/terms." -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "запуск камери" -#: selfdrive/ui/layouts/settings/software.py:105 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 #, python-format msgid "checking..." msgstr "перевіряю..." -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "замовч." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "вниз" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 #, python-format msgid "downloading..." msgstr "завантажую..." -#: selfdrive/ui/layouts/settings/software.py:114 +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "не вдалося перевірити оновлення" -#: selfdrive/ui/layouts/settings/software.py:107 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 #, python-format msgid "finalizing update..." msgstr "завершую..." -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "для \"{}\"" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "км/год" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "залиште порожнім для автоматичного налаштування" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "вліво" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "обмеж." -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "миль/год" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "ніколи" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "зараз" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "Поздовжнє керування openpilot (Альфа)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot Недоступний" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot за замовчуванням працює в режимі спокій. Експериментальний режим " -"увімкне функції альфа-рівня, які ще не готові для режиму спокій. " -"Експериментальні функції перелічені нижче:

Кінцевий поздовжній " -"контроль


Дозвольте моделі водіння контролювати газ і гальма. " -"openpilot буде керувати автомобілем так, як це робив би людина, включаючи " -"зупинку на червоне світло і знаки зупинки. Оскільки модель водіння визначає " -"швидкість руху, задана швидкість буде діяти лише як верхня межа. Це функція " -"альфа-рівня; слід очікувати помилок.

Нова візуалізація водіння
Візуалізація водіння перейде на ширококутну камеру, спрямовану на " -"дорогу, при низьких швидкостях, щоб краще показувати деякі повороти. Логотип " -"експериментального режиму також буде показаний у верхньому правому куті." - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot постійно калібрується, скидання рідко потрібне. Скидання " -"калібрування призведе до перезапуску openpilot, якщо автомобіль увімкнено." - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot вчиться керувати автомобілем, спостерігаючи за тим, як це роблять " -"люди, такі як ви.\n" -"\n" -"Режим зливи дозволяє максимально збільшити обсяг завантажуваних навчальних " -"даних, щоб поліпшити моделі керування автомобілем openpilot. Більше даних " -"означає більші моделі, а це означає кращий експериментальний режим." - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "Поздовжнє керування openpilot може з'явитися в майбутньому оновленні." -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." -msgstr "" -"Для роботи openpilot потрібно, щоб пристрій був встановлений з нахилом не " -"більше 4° вліво або вправо та не більше 5° вгору або 9° вниз. openpilot " -"постійно калібрується, тому скидання калібрування потрібне рідко." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." +msgstr "Для роботи openpilot потрібно, щоб пристрій був встановлений з нахилом не більше 4° вліво або вправо та не більше 5° вгору або 9° вниз. openpilot постійно калібрується, тому скидання калібрування потрібне рідко." -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "вправо" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "необмеж." -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "вгору" -#: selfdrive/ui/layouts/settings/software.py:125 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "оновлено, ніколи не перевірялось" -#: selfdrive/ui/layouts/settings/software.py:123 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "оновлено, перевірив {}" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "доступне оновлення" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" @@ -1209,7 +996,7 @@ msgstr[0] "{} СПОВІЩЕННЯ" msgstr[1] "{} СПОВІЩЕННЯ" msgstr[2] "{} СПОВІЩЕНЬ" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" @@ -1217,7 +1004,7 @@ msgstr[0] "{} день тому" msgstr[1] "{} дні тому" msgstr[2] "{} днів тому" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" @@ -1225,7 +1012,7 @@ msgstr[0] "{} година тому" msgstr[1] "{} години тому" msgstr[2] "{} годин тому" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" @@ -1233,26 +1020,21 @@ msgstr[0] "{} хвилина тому" msgstr[1] "{} хвилини тому" msgstr[2] "{} хвилин тому" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -"{} сегмент вашого водіння на даний момент містяться в тренувальному наборі " -"даних." -msgstr[1] "" -"{} сегменти вашого водіння на даний момент містяться в тренувальному наборі " -"даних." -msgstr[2] "" -"{} сегментів вашого водіння на даний момент містяться в тренувальному наборі " -"даних." - -#: selfdrive/ui/widgets/prime.py:62 +msgstr[0] "{} сегмент вашого водіння на даний момент містяться в тренувальному наборі даних." +msgstr[1] "{} сегменти вашого водіння на даний момент містяться в тренувальному наборі даних." +msgstr[2] "{} сегментів вашого водіння на даний момент містяться в тренувальному наборі даних." + +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ ПІДПИСАНО" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🌧️ Режим зливи 🌧️" + diff --git a/selfdrive/ui/translations/app_zh-CHS.po b/selfdrive/ui/translations/app_zh-CHS.po index 2400b6f44aa..4d9a5b78e27 100644 --- a/selfdrive/ui/translations/app_zh-CHS.po +++ b/selfdrive/ui/translations/app_zh-CHS.po @@ -17,1158 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " 转向扭矩响应校准完成。" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 转向扭矩响应校准已完成 {}%。" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的设备朝向 {:.1f}° {} 与 {:.1f}° {}。" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 年行驶数据存储" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "全天候 LTE 连接" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"警告:此车型的 openpilot 纵向控制仍为 alpha,将会停用自动紧急制动 (AEB)。" -"

在此车型上,openpilot 默认使用车载 ACC,而非 openpilot 的纵向控" -"制。启用此选项可切换为 openpilot 纵向控制。建议同时启用实验模式。若车辆通电," -"更改此设置将会重启 openpilot。" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

转向延迟校准完成。" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

转向延迟校准已完成 {}%。" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "已启用" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB(Android 调试桥)可通过 USB 或网络连接到您的设备。详见 https://docs." -"comma.ai/how-to/connect-to-comma。" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "添加" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN 设置" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "确认过度作动" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "高级" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "激进" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "同意" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "始终启用驾驶员监控" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "openpilot 纵向控制的 alpha 版本可在非发布分支搭配实验模式进行测试。" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "确定要关机吗?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "确定要重启吗?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "确定要重置校准吗?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "确定要卸载吗?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "返回" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成为 comma prime 会员" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "将 connect.comma.ai 添加到主屏幕,像应用一样使用" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "更改" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "检查" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "安稳模式已开启" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "连接中..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "蜂窝计量" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "更改语言" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若车辆通电,更改此设置将重启 openpilot。" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "点击“添加新设备”,扫描右侧二维码" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "关闭" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "当前版本" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "下载" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "拒绝" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒绝并卸载 openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "开发者" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "设备" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板时脱离" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "脱离以关机" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "脱离以重启" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "脱离以重置校准" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "以 km/h 显示速度(非 mph)。" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "下载" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "车内摄像头" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "驾驶风格" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "编辑" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "错误" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "实验模式已开启" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "启用" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "启用 ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "启用车道偏离警示" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "启用漫游" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "启用 SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "启用网络共享" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未启用 openpilot 也启用驾驶员监控。" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "启用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "启用 openpilot 纵向控制(alpha)开关,以使用实验模式。" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "输入 APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "输入 SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "输入新的网络共享密码" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "输入密码" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "输入您的 GitHub 用户名" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "错误" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "实验模式" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此车型当前无法使用实验模式,因为纵向控制使用的是原厂 ACC。" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "正在遗忘..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "完成设置" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose 模式" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"为达到最佳效果,请将设备带到室内,并每周连接优质 USB‑C 充电器与 Wi‑Fi。\n" -"\n" -"若连接热点或不限流量卡,行车中也可使用 Firehose 模式。\n" -"\n" -"\n" -"常见问题\n" -"\n" -"我怎么开、在哪开有区别吗?没有,平常怎么开就怎么开。\n" -"\n" -"Firehose 模式会拉取我所有片段吗?不会,我们会选择性拉取部分片段。\n" -"\n" -"什么是好的 USB‑C 充电器?任何快速的手机或笔电充电器都可以。\n" -"\n" -"我跑什么软件有区别吗?有,只有上游 openpilot(及特定分支)可用于训练。" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "忘记" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘记 Wi‑Fi 网络“{}”吗?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "在手机上前往 https://connect.comma.ai" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "隐藏网络" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "未启用:请连接不限流量网络" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "安装" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP 地址" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "安装更新" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "摇杆调试模式" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "加载中" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "纵向操作模式" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上传训练数据,以改进 openpilot 的驾驶模型。" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "无" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "否" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "网络" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "未找到 SSH 密钥" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "未找到用户“{}”的 SSH 密钥" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "暂无发行说明。" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "离线" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "确定" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "在线" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "打开" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "配对" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "预览" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "配对设备" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "配对设备" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "将设备配对到您的 comma 账号" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"将设备与 comma connect(connect.comma.ai)配对,领取您的 comma prime 优惠。" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "将设备与 comma connect(connect.comma.ai)配对,领取您的 comma prime 优惠。" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "请连接 Wi‑Fi 以完成初始配对" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "关机" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在计量制 Wi‑Fi 连接时避免大量上传" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在计量制蜂窝网络时避免大量上传" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "预览车内摄像头以确保驾驶员监控视野良好。(车辆必须熄火)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "二维码错误" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "移除" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "重置" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "查看" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "重启" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "重启设备" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "重启并更新" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"当车辆以超过 31 mph(50 km/h)行驶且未打转向灯越过检测到的车道线时,接收引导" -"回车道的警报。" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "录制并上传车内摄像头" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "录制并上传麦克风音频" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"行驶时录制并保存麦克风音频。音频将包含在 comma connect 的行车记录视频中。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "行驶时录制并保存麦克风音频。音频将包含在 comma connect 的行车记录视频中。" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "法规" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "从容" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "远程访问" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "远程快照" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "请求超时" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "重置" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "重置校准" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "查看训练指南" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "查看 openpilot 的规则、功能与限制" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "选择" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 密钥" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "正在扫描 Wi‑Fi 网络…" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "选择" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "选择分支" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "选择语言" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "序列号" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "延后更新" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "软件" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "标准" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"建议使用标准模式。激进模式下,openpilot 会更贴近前车,油门与刹车更为激进;从" -"容模式下,会与前车保持更远距离。在支持的车型上,可用方向盘距离按钮切换这些风" -"格。" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "系统无响应" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "请立即接管控制" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "温度" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "目标分支" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "网络共享密码" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "切换" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "卸载" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "卸载" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "未知" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "仅在车辆熄火时下载更新。" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "立即升级" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "上传车内摄像头数据,帮助改进驾驶员监控算法。" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "使用公制" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"使用 openpilot 进行自适应巡航与车道保持辅助。使用此功能时,您必须始终保持专" -"注。" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "车辆" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "查看" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "等待开始" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"警告:这将授予对您 GitHub 设置中所有公钥的 SSH 访问权限。请勿输入非您本人的 " -"GitHub 用户名。comma 员工绝不会要求您添加他们的用户名。" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "欢迎使用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "启用后,踩下加速踏板将会脱离 openpilot。" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 计量网络" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "密码错误" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必须接受条款与条件才能使用 openpilot。" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms " -"上的最新条款。" +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms 上的最新条款。" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "相机启动中" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "默认" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "下" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "检查更新失败" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "用于“{}”" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "留空以自动配置" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "左" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "计量" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "从不" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "现在" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 纵向控制(Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 无法使用" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot 默认以安稳模式行驶。实验模式会启用尚未准备好用于安稳模式的 Alpha 级" -"功能。实验功能如下:

端到端纵向控制


让驾驶模型控制油门与刹车。" -"openpilot 会像人类一样驾驶,包括在红灯与停牌前停车。由于驾驶模型决定行驶速" -"度,设定速度仅作为上限。这是 Alpha 质量功能;预期会有错误。

全新驾驶可" -"视化


在低速时,驾驶可视化将切换至面向道路的广角摄像头以更好显示部分转" -"弯。右上角也会显示实验模式图标。" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot 持续进行校准,通常无需重置。若车辆通电,重置校准将会重启 " -"openpilot。" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot 通过观察人类(例如您)的驾驶来学习。\n" -"\n" -"Firehose 模式可让您最大化上传训练数据,以改进 openpilot 的驾驶模型。更多数据" -"意味着更大的模型,也意味着更好的实验模式。" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 纵向控制可能会在未来更新中提供。" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot 要求设备安装在左右 4°、上 5° 或下 9° 以内。" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "右" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "不限流量" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "上" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "已是最新,最后检查:从未" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "已是最新,最后检查:{}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "有可用更新" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 条警报" msgstr[1] "{} 条警报" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小时前" msgstr[1] "{} 小时前" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分钟前" msgstr[1] "{} 分钟前" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" msgstr[1] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 已订阅" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" + diff --git a/selfdrive/ui/translations/app_zh-CHT.po b/selfdrive/ui/translations/app_zh-CHT.po index f4d5e0a4ed2..1c2fd065633 100644 --- a/selfdrive/ui/translations/app_zh-CHT.po +++ b/selfdrive/ui/translations/app_zh-CHT.po @@ -17,1157 +17,1018 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: selfdrive/ui/layouts/settings/device.py:160 +#: openpilot/selfdrive/ui/layouts/settings/device.py:152 #, python-format msgid " Steering torque response calibration is complete." msgstr " 轉向扭矩回應校正完成。" -#: selfdrive/ui/layouts/settings/device.py:158 +#: openpilot/selfdrive/ui/layouts/settings/device.py:150 #, python-format msgid " Steering torque response calibration is {}% complete." msgstr " 轉向扭矩回應校正已完成 {}%。" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的裝置朝向 {:.1f}° {} 與 {:.1f}° {}。" -#: selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py:43 msgid "--" msgstr "--" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "1 year of drive storage" msgstr "1 年行駛資料儲存" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "24/7 LTE connectivity" msgstr "全年無休 LTE 連線" -#: selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py:46 msgid "2G" msgstr "2G" -#: selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py:47 msgid "3G" msgstr "3G" -#: selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py:49 msgid "5G" msgstr "5G" -#: selfdrive/ui/layouts/settings/developer.py:23 -msgid "" -"WARNING: openpilot longitudinal control is in alpha for this car and will " -"disable Automatic Emergency Braking (AEB).

On this car, openpilot " -"defaults to the car's built-in ACC instead of openpilot's longitudinal " -"control. Enable this to switch to openpilot longitudinal control. Enabling " -"Experimental mode is recommended when enabling openpilot longitudinal " -"control alpha. Changing this setting will restart openpilot if the car is " -"powered on." -msgstr "" -"警告:此車款的 openpilot 縱向控制仍為 alpha,將會停用自動緊急煞車 (AEB)。" -"

在此車款上,openpilot 預設使用車載 ACC,而非 openpilot 的縱向控" -"制。啟用此選項可切換為 openpilot 縱向控制。建議同時啟用實驗模式。若車輛通電," -"變更此設定將會重新啟動 openpilot。" - -#: selfdrive/ui/layouts/settings/device.py:148 +#: openpilot/selfdrive/ui/layouts/settings/device.py:140 #, python-format msgid "

Steering lag calibration is complete." msgstr "

轉向延遲校正完成。" -#: selfdrive/ui/layouts/settings/device.py:146 +#: openpilot/selfdrive/ui/layouts/settings/device.py:138 #, python-format msgid "

Steering lag calibration is {}% complete." msgstr "

轉向延遲校正已完成 {}%。" -#: selfdrive/ui/layouts/settings/firehose.py:138 -#, python-format -msgid "ACTIVE" -msgstr "啟用" - -#: selfdrive/ui/layouts/settings/developer.py:15 -msgid "" -"ADB (Android Debug Bridge) allows connecting to your device over USB or over " -"the network. See https://docs.comma.ai/how-to/connect-to-comma for more info." -msgstr "" -"ADB (Android Debug Bridge) 可透過 USB 或網路連線至您的裝置。詳見 https://" -"docs.comma.ai/how-to/connect-to-comma。" - -#: selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 msgid "ADD" msgstr "新增" -#: system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:136 #, python-format msgid "APN Setting" msgstr "APN 設定" -#: selfdrive/ui/widgets/offroad_alerts.py:109 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 #, python-format msgid "Acknowledge Excessive Actuation" msgstr "確認過度作動" -#: system/ui/widgets/network.py:74 system/ui/widgets/network.py:95 +#: system/ui/widgets/network.py:92 +#: system/ui/widgets/network.py:74 #, python-format msgid "Advanced" msgstr "進階" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Aggressive" msgstr "積極" -#: selfdrive/ui/layouts/onboarding.py:116 +#: openpilot/selfdrive/ui/layouts/onboarding.py:120 #, python-format msgid "Agree" msgstr "同意" -#: selfdrive/ui/layouts/settings/toggles.py:70 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 #, python-format msgid "Always-On Driver Monitoring" msgstr "持續啟用駕駛監控" -#: selfdrive/ui/layouts/settings/toggles.py:186 -#, python-format -msgid "" -"An alpha version of openpilot longitudinal control can be tested, along with " -"Experimental mode, on non-release branches." -msgstr "openpilot 縱向控制的 alpha 版本可於非發行分支搭配實驗模式進行測試。" - -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 #, python-format msgid "Are you sure you want to power off?" msgstr "確定要關機嗎?" -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 #, python-format msgid "Are you sure you want to reboot?" msgstr "確定要重新啟動嗎?" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Are you sure you want to reset calibration?" msgstr "確定要重設校正嗎?" -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 #, python-format msgid "Are you sure you want to uninstall?" msgstr "確定要解除安裝嗎?" -#: system/ui/widgets/network.py:99 selfdrive/ui/layouts/onboarding.py:147 +#: system/ui/widgets/network.py:96 +#: openpilot/selfdrive/ui/layouts/onboarding.py:151 #, python-format msgid "Back" msgstr "返回" -#: selfdrive/ui/widgets/prime.py:38 +#: openpilot/selfdrive/ui/widgets/prime.py:38 #, python-format msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成為 comma prime 會員" -#: selfdrive/ui/widgets/pairing_dialog.py:130 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 #, python-format msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "將 connect.comma.ai 加到主畫面,像 App 一樣使用" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "CHANGE" msgstr "變更" -#: selfdrive/ui/layouts/settings/software.py:50 -#: selfdrive/ui/layouts/settings/software.py:107 -#: selfdrive/ui/layouts/settings/software.py:118 -#: selfdrive/ui/layouts/settings/software.py:147 +#: openpilot/selfdrive/ui/layouts/settings/software.py:157 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 +#: openpilot/selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:128 #, python-format msgid "CHECK" msgstr "檢查" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "CHILL MODE ON" msgstr "安穩模式已開啟" -#: system/ui/widgets/network.py:155 selfdrive/ui/layouts/sidebar.py:73 -#: selfdrive/ui/layouts/sidebar.py:134 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:138 +#: system/ui/widgets/network.py:152 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 #, python-format msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:369 +#: system/ui/widgets/network.py:376 #, python-format msgid "CONNECTING..." msgstr "連線中..." -#: system/ui/widgets/confirm_dialog.py:23 system/ui/widgets/option_dialog.py:35 -#: system/ui/widgets/keyboard.py:81 system/ui/widgets/network.py:318 +#: system/ui/widgets/network.py:326 +#: system/ui/widgets/confirm_dialog.py:24 +#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/keyboard.py:83 #, python-format msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py:134 +#: system/ui/widgets/network.py:131 #, python-format msgid "Cellular Metered" msgstr "行動網路計量" -#: selfdrive/ui/layouts/settings/device.py:68 +#: openpilot/selfdrive/ui/layouts/settings/device.py:66 #, python-format msgid "Change Language" msgstr "變更語言" -#: selfdrive/ui/layouts/settings/toggles.py:125 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 #, python-format msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若車輛通電,變更此設定將重新啟動 openpilot。" -#: selfdrive/ui/widgets/pairing_dialog.py:129 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 #, python-format msgid "Click \"add new device\" and scan the QR code on the right" msgstr "點選「新增裝置」,掃描右側 QR 碼" -#: selfdrive/ui/widgets/offroad_alerts.py:104 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 #, python-format msgid "Close" msgstr "關閉" -#: selfdrive/ui/layouts/settings/software.py:49 +#: openpilot/selfdrive/ui/layouts/settings/software.py:56 #, python-format msgid "Current Version" msgstr "目前版本" -#: selfdrive/ui/layouts/settings/software.py:110 +#: openpilot/selfdrive/ui/layouts/settings/software.py:120 #, python-format msgid "DOWNLOAD" msgstr "下載" -#: selfdrive/ui/layouts/onboarding.py:115 +#: openpilot/selfdrive/ui/layouts/onboarding.py:119 #, python-format msgid "Decline" msgstr "拒絕" -#: selfdrive/ui/layouts/onboarding.py:148 +#: openpilot/selfdrive/ui/layouts/onboarding.py:152 #, python-format msgid "Decline, uninstall openpilot" msgstr "拒絕並解除安裝 openpilot" -#: selfdrive/ui/layouts/settings/settings.py:67 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 msgid "Developer" msgstr "開發人員" -#: selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 msgid "Device" msgstr "裝置" -#: selfdrive/ui/layouts/settings/toggles.py:58 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 #, python-format msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板時脫離" -#: selfdrive/ui/layouts/settings/device.py:184 +#: openpilot/selfdrive/ui/layouts/settings/device.py:176 #, python-format msgid "Disengage to Power Off" msgstr "脫離以關機" -#: selfdrive/ui/layouts/settings/device.py:172 +#: openpilot/selfdrive/ui/layouts/settings/device.py:164 #, python-format msgid "Disengage to Reboot" msgstr "脫離以重新啟動" -#: selfdrive/ui/layouts/settings/device.py:103 +#: openpilot/selfdrive/ui/layouts/settings/device.py:95 #, python-format msgid "Disengage to Reset Calibration" msgstr "脫離以重設校正" -#: selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 msgid "Display speed in km/h instead of mph." msgstr "以 km/h 顯示速度(非 mph)。" -#: selfdrive/ui/layouts/settings/device.py:59 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 #, python-format msgid "Dongle ID" msgstr "Dongle ID" -#: selfdrive/ui/layouts/settings/software.py:50 +#: openpilot/selfdrive/ui/layouts/settings/software.py:57 #, python-format msgid "Download" msgstr "下載" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "Driver Camera" msgstr "車內鏡頭" -#: selfdrive/ui/layouts/settings/toggles.py:96 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 #, python-format msgid "Driving Personality" msgstr "駕駛風格" -#: system/ui/widgets/network.py:123 system/ui/widgets/network.py:139 +#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:136 #, python-format msgid "EDIT" msgstr "編輯" -#: selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py:138 msgid "ERROR" msgstr "錯誤" -#: selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py:45 msgid "ETH" msgstr "ETH" -#: selfdrive/ui/widgets/exp_mode_button.py:50 +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 #, python-format msgid "EXPERIMENTAL MODE ON" msgstr "實驗模式已開啟" -#: selfdrive/ui/layouts/settings/developer.py:166 -#: selfdrive/ui/layouts/settings/toggles.py:228 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 #, python-format msgid "Enable" msgstr "啟用" -#: selfdrive/ui/layouts/settings/developer.py:39 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 #, python-format msgid "Enable ADB" msgstr "啟用 ADB" -#: selfdrive/ui/layouts/settings/toggles.py:64 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 #, python-format msgid "Enable Lane Departure Warnings" msgstr "啟用偏離車道警示" -#: system/ui/widgets/network.py:129 +#: system/ui/widgets/network.py:126 #, python-format msgid "Enable Roaming" msgstr "啟用漫遊" -#: selfdrive/ui/layouts/settings/developer.py:48 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 #, python-format msgid "Enable SSH" msgstr "啟用 SSH" -#: system/ui/widgets/network.py:120 +#: system/ui/widgets/network.py:117 #, python-format msgid "Enable Tethering" msgstr "啟用網路共享" -#: selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未啟動 openpilot 亦啟用駕駛監控。" -#: selfdrive/ui/layouts/settings/toggles.py:46 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 #, python-format msgid "Enable openpilot" msgstr "啟用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:189 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 #, python-format -msgid "" -"Enable the openpilot longitudinal control (alpha) toggle to allow " -"Experimental mode." +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "啟用 openpilot 縱向控制(alpha)切換,以使用實驗模式。" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "Enter APN" msgstr "輸入 APN" -#: system/ui/widgets/network.py:241 +#: system/ui/widgets/network.py:243 #, python-format msgid "Enter SSID" msgstr "輸入 SSID" -#: system/ui/widgets/network.py:254 +#: system/ui/widgets/network.py:257 #, python-format msgid "Enter new tethering password" msgstr "輸入新的網路共享密碼" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:320 #, python-format msgid "Enter password" msgstr "輸入密碼" -#: selfdrive/ui/widgets/ssh_key.py:89 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 #, python-format msgid "Enter your GitHub username" msgstr "輸入您的 GitHub 使用者名稱" -#: system/ui/widgets/list_view.py:123 system/ui/widgets/list_view.py:160 +#: system/ui/widgets/list_view.py:123 +#: system/ui/widgets/list_view.py:160 #, python-format msgid "Error" msgstr "錯誤" -#: selfdrive/ui/layouts/settings/toggles.py:52 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 #, python-format msgid "Experimental Mode" msgstr "實驗模式" -#: selfdrive/ui/layouts/settings/toggles.py:181 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 #, python-format -msgid "" -"Experimental mode is currently unavailable on this car since the car's stock " -"ACC is used for longitudinal control." +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此車款目前無法使用實驗模式,因為縱向控制使用的是原廠 ACC。" -#: system/ui/widgets/network.py:373 +#: system/ui/widgets/network.py:380 #, python-format msgid "FORGETTING..." msgstr "正在遺忘..." -#: selfdrive/ui/widgets/setup.py:44 +#: openpilot/selfdrive/ui/widgets/setup.py:43 #, python-format msgid "Finish Setup" msgstr "完成設定" -#: selfdrive/ui/layouts/settings/settings.py:66 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 msgid "Firehose" msgstr "Firehose" -#: selfdrive/ui/layouts/settings/firehose.py:18 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 msgid "Firehose Mode" msgstr "Firehose 模式" -#: selfdrive/ui/layouts/settings/firehose.py:25 -msgid "" -"For maximum effectiveness, bring your device inside and connect to a good " -"USB-C adapter and Wi-Fi weekly.\n" -"\n" -"Firehose Mode can also work while you're driving if connected to a hotspot " -"or unlimited SIM card.\n" -"\n" -"\n" -"Frequently Asked Questions\n" -"\n" -"Does it matter how or where I drive? Nope, just drive as you normally " -"would.\n" -"\n" -"Do all of my segments get pulled in Firehose Mode? No, we selectively pull a " -"subset of your segments.\n" -"\n" -"What's a good USB-C adapter? Any fast phone or laptop charger should be " -"fine.\n" -"\n" -"Does it matter which software I run? Yes, only upstream openpilot (and " -"particular forks) are able to be used for training." -msgstr "" -"為達最佳效果,請將裝置帶到室內,並每週連接優質 USB‑C 充電器與 Wi‑Fi。\n" -"\n" -"若連上熱點或吃到飽門號,行車中也可使用 Firehose 模式。\n" -"\n" -"\n" -"常見問題\n" -"\n" -"我怎麼開、在哪裡開有差嗎?沒有,平常怎麼開就怎麼開。\n" -"\n" -"Firehose 模式會拉取我所有片段嗎?不會,我們會選擇性拉取部分片段。\n" -"\n" -"什麼是好的 USB‑C 充電器?任何快速的手機或筆電充電器都可以。\n" -"\n" -"我跑什麼軟體有差嗎?有,只有上游 openpilot(及特定分支)可用於訓練。" - -#: system/ui/widgets/network.py:318 system/ui/widgets/network.py:451 +#: system/ui/widgets/network.py:458 +#: system/ui/widgets/network.py:326 #, python-format msgid "Forget" msgstr "忘記" -#: system/ui/widgets/network.py:319 +#: system/ui/widgets/network.py:327 #, python-format msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘記 Wi‑Fi 網路「{}」嗎?" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 msgid "GOOD" msgstr "良好" -#: selfdrive/ui/widgets/pairing_dialog.py:128 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 #, python-format msgid "Go to https://connect.comma.ai on your phone" msgstr "在手機上前往 https://connect.comma.ai" -#: selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py:155 +#: system/ui/widgets/network.py:152 #, python-format msgid "Hidden Network" msgstr "隱藏網路" -#: selfdrive/ui/layouts/settings/firehose.py:140 -#, python-format -msgid "INACTIVE: connect to an unmetered network" -msgstr "未啟用:請連接不限流量網路" - -#: selfdrive/ui/layouts/settings/software.py:53 -#: selfdrive/ui/layouts/settings/software.py:136 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 +#: openpilot/selfdrive/ui/layouts/settings/software.py:146 #, python-format msgid "INSTALL" msgstr "安裝" -#: system/ui/widgets/network.py:150 +#: system/ui/widgets/network.py:147 #, python-format msgid "IP Address" msgstr "IP 位址" -#: selfdrive/ui/layouts/settings/software.py:53 +#: openpilot/selfdrive/ui/layouts/settings/software.py:60 #, python-format msgid "Install Update" msgstr "安裝更新" -#: selfdrive/ui/layouts/settings/developer.py:56 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 #, python-format msgid "Joystick Debug Mode" msgstr "搖桿除錯模式" -#: selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 msgid "LOADING" msgstr "載入中" -#: selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py:48 msgid "LTE" msgstr "LTE" -#: selfdrive/ui/layouts/settings/developer.py:64 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 #, python-format msgid "Longitudinal Maneuver Mode" msgstr "縱向操作模式" -#: selfdrive/ui/onroad/hud_renderer.py:148 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 #, python-format msgid "MAX" msgstr "最大" -#: selfdrive/ui/widgets/setup.py:75 +#: openpilot/selfdrive/ui/widgets/setup.py:74 #, python-format -msgid "" -"Maximize your training data uploads to improve openpilot's driving models." +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上傳訓練資料,以改進 openpilot 的駕駛模型。" -#: selfdrive/ui/layouts/settings/device.py:59 -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:57 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "N/A" msgstr "無" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "NO" msgstr "否" -#: selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 msgid "Network" msgstr "網路" -#: selfdrive/ui/widgets/ssh_key.py:114 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 #, python-format msgid "No SSH keys found" msgstr "找不到 SSH 金鑰" -#: selfdrive/ui/widgets/ssh_key.py:126 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 #, python-format msgid "No SSH keys found for user '{}'" msgstr "找不到使用者 '{}' 的 SSH 金鑰" -#: selfdrive/ui/widgets/offroad_alerts.py:320 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 #, python-format msgid "No release notes available." msgstr "無可用發行說明。" -#: selfdrive/ui/layouts/sidebar.py:73 selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py:73 +#: openpilot/selfdrive/ui/layouts/sidebar.py:134 msgid "OFFLINE" msgstr "離線" -#: system/ui/widgets/html_render.py:263 system/ui/widgets/confirm_dialog.py:93 -#: selfdrive/ui/layouts/sidebar.py:127 +#: system/ui/widgets/confirm_dialog.py:93 +#: system/ui/widgets/html_render.py:263 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 #, python-format msgid "OK" msgstr "確定" -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:136 -#: selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:136 msgid "ONLINE" msgstr "線上" -#: selfdrive/ui/widgets/setup.py:20 +#: openpilot/selfdrive/ui/widgets/setup.py:19 #, python-format msgid "Open" msgstr "開啟" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "PAIR" msgstr "配對" -#: selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py:142 msgid "PANDA" msgstr "PANDA" -#: selfdrive/ui/layouts/settings/device.py:62 +#: openpilot/selfdrive/ui/layouts/settings/device.py:60 #, python-format msgid "PREVIEW" msgstr "預覽" -#: selfdrive/ui/widgets/prime.py:44 +#: openpilot/selfdrive/ui/widgets/prime.py:44 #, python-format msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: selfdrive/ui/layouts/settings/device.py:48 +#: openpilot/selfdrive/ui/layouts/settings/device.py:45 #, python-format msgid "Pair Device" msgstr "配對裝置" -#: selfdrive/ui/widgets/setup.py:19 +#: openpilot/selfdrive/ui/widgets/setup.py:18 #, python-format msgid "Pair device" msgstr "配對裝置" -#: selfdrive/ui/widgets/pairing_dialog.py:103 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 #, python-format msgid "Pair your device to your comma account" msgstr "將裝置配對至您的 comma 帳號" -#: selfdrive/ui/widgets/setup.py:48 selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/widgets/setup.py:47 +#: openpilot/selfdrive/ui/layouts/settings/device.py:23 #, python-format -msgid "" -"Pair your device with comma connect (connect.comma.ai) and claim your comma " -"prime offer." -msgstr "" -"將裝置與 comma connect(connect.comma.ai)配對,領取您的 comma prime 優惠。" +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +msgstr "將裝置與 comma connect(connect.comma.ai)配對,領取您的 comma prime 優惠。" -#: selfdrive/ui/widgets/setup.py:91 +#: openpilot/selfdrive/ui/widgets/setup.py:91 #, python-format msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "請連線至 Wi‑Fi 以完成初始化配對" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:187 +#: openpilot/selfdrive/ui/layouts/settings/device.py:183 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Power Off" msgstr "關機" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在計量制 Wi‑Fi 連線時避免大量上傳" -#: system/ui/widgets/network.py:135 +#: system/ui/widgets/network.py:132 #, python-format msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在計量制行動網路時避免大量上傳" -#: selfdrive/ui/layouts/settings/device.py:25 -msgid "" -"Preview the driver facing camera to ensure that driver monitoring has good " -"visibility. (vehicle must be off)" +#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "預覽車內鏡頭以確保駕駛監控視野良好。(車輛須熄火)" -#: selfdrive/ui/widgets/pairing_dialog.py:161 +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 #, python-format msgid "QR Code Error" msgstr "QR 碼錯誤" -#: selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 msgid "REMOVE" msgstr "移除" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "RESET" msgstr "重設" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "REVIEW" msgstr "檢視" -#: selfdrive/ui/layouts/settings/device.py:55 -#: selfdrive/ui/layouts/settings/device.py:175 +#: openpilot/selfdrive/ui/layouts/settings/device.py:171 +#: openpilot/selfdrive/ui/layouts/settings/device.py:53 #, python-format msgid "Reboot" msgstr "重新啟動" -#: selfdrive/ui/onroad/alert_renderer.py:66 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 #, python-format msgid "Reboot Device" msgstr "重新啟動裝置" -#: selfdrive/ui/widgets/offroad_alerts.py:112 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 #, python-format msgid "Reboot and Update" msgstr "重新啟動並更新" -#: selfdrive/ui/layouts/settings/toggles.py:27 -msgid "" -"Receive alerts to steer back into the lane when your vehicle drifts over a " -"detected lane line without a turn signal activated while driving over 31 mph " -"(50 km/h)." -msgstr "" -"當車輛以超過 31 mph(50 km/h)行駛且未打方向燈越過偵測到的車道線時,接收轉向" -"回車道的警示。" - -#: selfdrive/ui/layouts/settings/toggles.py:76 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 #, python-format msgid "Record and Upload Driver Camera" msgstr "錄製並上傳車內鏡頭" -#: selfdrive/ui/layouts/settings/toggles.py:82 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 #, python-format msgid "Record and Upload Microphone Audio" msgstr "錄製並上傳麥克風音訊" -#: selfdrive/ui/layouts/settings/toggles.py:33 -msgid "" -"Record and store microphone audio while driving. The audio will be included " -"in the dashcam video in comma connect." -msgstr "" -"行車時錄製並儲存麥克風音訊。音訊將包含在 comma connect 的行車紀錄影片中。" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." +msgstr "行車時錄製並儲存麥克風音訊。音訊將包含在 comma connect 的行車紀錄影片中。" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "Regulatory" msgstr "法規" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Relaxed" msgstr "從容" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote access" msgstr "遠端存取" -#: selfdrive/ui/widgets/prime.py:47 +#: openpilot/selfdrive/ui/widgets/prime.py:47 #, python-format msgid "Remote snapshots" msgstr "遠端擷圖" -#: selfdrive/ui/widgets/ssh_key.py:123 +#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 #, python-format msgid "Request timed out" msgstr "要求逾時" -#: selfdrive/ui/layouts/settings/device.py:119 +#: openpilot/selfdrive/ui/layouts/settings/device.py:111 #, python-format msgid "Reset" msgstr "重設" -#: selfdrive/ui/layouts/settings/device.py:51 +#: openpilot/selfdrive/ui/layouts/settings/device.py:49 #, python-format msgid "Reset Calibration" msgstr "重設校正" -#: selfdrive/ui/layouts/settings/device.py:65 +#: openpilot/selfdrive/ui/layouts/settings/device.py:63 #, python-format msgid "Review Training Guide" msgstr "檢視訓練指南" -#: selfdrive/ui/layouts/settings/device.py:27 +#: openpilot/selfdrive/ui/layouts/settings/device.py:26 msgid "Review the rules, features, and limitations of openpilot" msgstr "檢視 openpilot 的規則、功能與限制" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "SELECT" msgstr "選取" -#: selfdrive/ui/layouts/settings/developer.py:53 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 #, python-format msgid "SSH Keys" msgstr "SSH 金鑰" -#: system/ui/widgets/network.py:310 +#: system/ui/widgets/network.py:316 #, python-format msgid "Scanning Wi-Fi networks..." msgstr "正在掃描 Wi‑Fi 網路…" -#: system/ui/widgets/option_dialog.py:36 +#: system/ui/widgets/option_dialog.py:37 #, python-format msgid "Select" msgstr "選取" -#: selfdrive/ui/layouts/settings/software.py:183 +#: openpilot/selfdrive/ui/layouts/settings/software.py:203 #, python-format msgid "Select a branch" msgstr "選取分支" -#: selfdrive/ui/layouts/settings/device.py:91 +#: openpilot/selfdrive/ui/layouts/settings/device.py:89 #, python-format msgid "Select a language" msgstr "選取語言" -#: selfdrive/ui/layouts/settings/device.py:60 +#: openpilot/selfdrive/ui/layouts/settings/device.py:58 #, python-format msgid "Serial" msgstr "序號" -#: selfdrive/ui/widgets/offroad_alerts.py:106 +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 #, python-format msgid "Snooze Update" msgstr "延後更新" -#: selfdrive/ui/layouts/settings/settings.py:65 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 msgid "Software" msgstr "軟體" -#: selfdrive/ui/layouts/settings/toggles.py:98 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 #, python-format msgid "Standard" msgstr "標準" -#: selfdrive/ui/layouts/settings/toggles.py:22 -msgid "" -"Standard is recommended. In aggressive mode, openpilot will follow lead cars " -"closer and be more aggressive with the gas and brake. In relaxed mode " -"openpilot will stay further away from lead cars. On supported cars, you can " -"cycle through these personalities with your steering wheel distance button." -msgstr "" -"建議使用標準模式。積極模式下,openpilot 會更貼近前車,油門與煞車反應更積極;" -"從容模式下,會與前車保持更遠距離。於支援車款,可用方向盤距離按鈕切換這些風" -"格。" - -#: selfdrive/ui/onroad/alert_renderer.py:59 -#: selfdrive/ui/onroad/alert_renderer.py:65 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 #, python-format msgid "System Unresponsive" msgstr "系統無回應" -#: selfdrive/ui/onroad/alert_renderer.py:58 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 #, python-format msgid "TAKE CONTROL IMMEDIATELY" msgstr "請立刻接手控制" -#: selfdrive/ui/layouts/sidebar.py:71 selfdrive/ui/layouts/sidebar.py:125 -#: selfdrive/ui/layouts/sidebar.py:127 selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py:71 +#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py:127 +#: openpilot/selfdrive/ui/layouts/sidebar.py:129 msgid "TEMP" msgstr "溫度" -#: selfdrive/ui/layouts/settings/software.py:61 +#: openpilot/selfdrive/ui/layouts/settings/software.py:68 #, python-format msgid "Target Branch" msgstr "目標分支" -#: system/ui/widgets/network.py:124 +#: system/ui/widgets/network.py:121 #, python-format msgid "Tethering Password" msgstr "網路共享密碼" -#: selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 msgid "Toggles" msgstr "切換" -#: selfdrive/ui/layouts/settings/software.py:72 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 +#, python-format +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "UNINSTALL" msgstr "解除安裝" -#: selfdrive/ui/layouts/home.py:155 +#: openpilot/selfdrive/ui/layouts/home.py:155 #, python-format msgid "UPDATE" msgstr "更新" -#: selfdrive/ui/layouts/settings/software.py:72 -#: selfdrive/ui/layouts/settings/software.py:163 +#: openpilot/selfdrive/ui/layouts/settings/software.py:173 +#: openpilot/selfdrive/ui/layouts/settings/software.py:79 #, python-format msgid "Uninstall" msgstr "解除安裝" -#: selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py:117 msgid "Unknown" msgstr "未知" -#: selfdrive/ui/layouts/settings/software.py:48 +#: openpilot/selfdrive/ui/layouts/settings/software.py:55 #, python-format msgid "Updates are only downloaded while the car is off." msgstr "僅在車輛熄火時下載更新。" -#: selfdrive/ui/widgets/prime.py:33 +#: openpilot/selfdrive/ui/widgets/prime.py:33 #, python-format msgid "Upgrade Now" msgstr "立即升級" -#: selfdrive/ui/layouts/settings/toggles.py:31 -msgid "" -"Upload data from the driver facing camera and help improve the driver " -"monitoring algorithm." +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "上傳車內鏡頭資料,協助改善駕駛監控演算法。" -#: selfdrive/ui/layouts/settings/toggles.py:88 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 #, python-format msgid "Use Metric System" msgstr "使用公制" -#: selfdrive/ui/layouts/settings/toggles.py:17 -msgid "" -"Use the openpilot system for adaptive cruise control and lane keep driver " -"assistance. Your attention is required at all times to use this feature." -msgstr "" -"使用 openpilot 進行 ACC 與車道維持輔助。使用此功能時,您必須始終保持專注。" - -#: selfdrive/ui/layouts/sidebar.py:72 selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py:72 +#: openpilot/selfdrive/ui/layouts/sidebar.py:144 msgid "VEHICLE" msgstr "車輛" -#: selfdrive/ui/layouts/settings/device.py:67 +#: openpilot/selfdrive/ui/layouts/settings/device.py:65 #, python-format msgid "VIEW" msgstr "檢視" -#: selfdrive/ui/onroad/alert_renderer.py:52 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 #, python-format msgid "Waiting to start" msgstr "等待開始" -#: selfdrive/ui/layouts/settings/developer.py:19 -msgid "" -"Warning: This grants SSH access to all public keys in your GitHub settings. " -"Never enter a GitHub username other than your own. A comma employee will " -"NEVER ask you to add their GitHub username." -msgstr "" -"警告:這將授予對您 GitHub 設定中所有公開金鑰的 SSH 存取權。請勿輸入非您本人" -"的 GitHub 帳號。comma 員工絕不會要求您新增他們的帳號。" - -#: selfdrive/ui/layouts/onboarding.py:111 +#: openpilot/selfdrive/ui/layouts/onboarding.py:115 #, python-format msgid "Welcome to openpilot" msgstr "歡迎使用 openpilot" -#: selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "啟用後,踩下加速踏板將會脫離 openpilot。" -#: selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py:44 msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:144 +#: system/ui/widgets/network.py:141 #, python-format msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 計量網路" -#: system/ui/widgets/network.py:314 +#: system/ui/widgets/network.py:320 #, python-format msgid "Wrong password" msgstr "密碼錯誤" -#: selfdrive/ui/layouts/onboarding.py:145 +#: openpilot/selfdrive/ui/layouts/onboarding.py:149 #, python-format msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必須接受條款與細則才能使用 openpilot。" -#: selfdrive/ui/layouts/onboarding.py:112 +#: openpilot/selfdrive/ui/layouts/onboarding.py:116 #, python-format -msgid "" -"You must accept the Terms and Conditions to use openpilot. Read the latest " -"terms at https://comma.ai/terms before continuing." -msgstr "" -"您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms " -"上的最新條款。" +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." +msgstr "您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms 上的最新條款。" -#: selfdrive/ui/onroad/driver_camera_dialog.py:34 +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 #, python-format msgid "camera starting" msgstr "相機啟動中" -#: selfdrive/ui/widgets/prime.py:63 +#: openpilot/selfdrive/ui/layouts/settings/software.py:19 +#, python-format +msgid "checking..." +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py:63 #, python-format msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "default" msgstr "預設" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "down" msgstr "下" -#: selfdrive/ui/layouts/settings/software.py:106 +#: openpilot/selfdrive/ui/layouts/settings/software.py:20 +#, python-format +msgid "downloading..." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py:116 #, python-format msgid "failed to check for update" msgstr "檢查更新失敗" -#: system/ui/widgets/network.py:237 system/ui/widgets/network.py:314 +#: openpilot/selfdrive/ui/layouts/settings/software.py:21 +#, python-format +msgid "finalizing update..." +msgstr "" + +#: system/ui/widgets/network.py:238 +#: system/ui/widgets/network.py:321 #, python-format msgid "for \"{}\"" msgstr "適用於「{}」" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:204 +#: system/ui/widgets/network.py:201 #, python-format msgid "leave blank for automatic configuration" msgstr "留空以自動設定" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "left" msgstr "左" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "metered" msgstr "計量" -#: selfdrive/ui/onroad/hud_renderer.py:177 +#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 #, python-format msgid "mph" msgstr "mph" -#: selfdrive/ui/layouts/settings/software.py:20 +#: openpilot/selfdrive/ui/layouts/settings/software.py:27 #, python-format msgid "never" msgstr "從不" -#: selfdrive/ui/layouts/settings/software.py:31 +#: openpilot/selfdrive/ui/layouts/settings/software.py:38 #, python-format msgid "now" msgstr "現在" -#: selfdrive/ui/layouts/settings/developer.py:71 +#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 #, python-format msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縱向控制(Alpha)" -#: selfdrive/ui/onroad/alert_renderer.py:51 +#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 #, python-format msgid "openpilot Unavailable" msgstr "openpilot 無法使用" -#: selfdrive/ui/layouts/settings/toggles.py:158 -#, python-format -msgid "" -"openpilot defaults to driving in chill mode. Experimental mode enables alpha-" -"level features that aren't ready for chill mode. Experimental features are " -"listed below:

End-to-End Longitudinal Control


Let the driving " -"model control the gas and brakes. openpilot will drive as it thinks a human " -"would, including stopping for red lights and stop signs. Since the driving " -"model decides the speed to drive, the set speed will only act as an upper " -"bound. This is an alpha quality feature; mistakes should be expected." -"

New Driving Visualization


The driving visualization will " -"transition to the road-facing wide-angle camera at low speeds to better show " -"some turns. The Experimental mode logo will also be shown in the top right " -"corner." -msgstr "" -"openpilot 預設以安穩模式行駛。實驗模式啟用尚未準備好進入安穩模式的 Alpha 等級" -"功能。實驗功能如下:

端到端縱向控制


讓駕駛模型控制油門與煞車。" -"openpilot 會如同人類駕駛般行駛,包括在紅燈與停車標誌前停車。由於駕駛模型決定" -"行駛速度,設定速度僅作為上限。此為 Alpha 品質功能;預期會有失誤。

全新" -"駕駛視覺化


在低速時,駕駛視覺化將切換至面向道路的廣角鏡頭以更好呈現部" -"分轉彎。右上角亦會顯示實驗模式圖示。" - -#: selfdrive/ui/layouts/settings/device.py:165 -#, python-format -msgid "" -"openpilot is continuously calibrating, resetting is rarely required. " -"Resetting calibration will restart openpilot if the car is powered on." -msgstr "" -"openpilot 會持續校正,通常不需重設。若車輛通電,重設校正將重新啟動 " -"openpilot。" - -#: selfdrive/ui/layouts/settings/firehose.py:20 -msgid "" -"openpilot learns to drive by watching humans, like you, drive.\n" -"\n" -"Firehose Mode allows you to maximize your training data uploads to improve " -"openpilot's driving models. More data means bigger models, which means " -"better Experimental Mode." -msgstr "" -"openpilot 透過觀察人類(也就是您)的駕駛方式來學習。\n" -"\n" -"Firehose 模式可讓您最大化上傳訓練資料,以改進 openpilot 的駕駛模型。更多資料" -"代表更大的模型,也就代表更好的實驗模式。" - -#: selfdrive/ui/layouts/settings/toggles.py:183 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 #, python-format msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 縱向控制可能於未來更新提供。" -#: selfdrive/ui/layouts/settings/device.py:26 -msgid "" -"openpilot requires the device to be mounted within 4° left or right and " -"within 5° up or 9° down." +#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot 要求裝置安裝在左右 4°、上 5° 或下 9° 以內。" -#: selfdrive/ui/layouts/settings/device.py:134 +#: openpilot/selfdrive/ui/layouts/settings/device.py:126 #, python-format msgid "right" msgstr "右" -#: system/ui/widgets/network.py:142 +#: system/ui/widgets/network.py:139 #, python-format msgid "unmetered" msgstr "不限流量" -#: selfdrive/ui/layouts/settings/device.py:133 +#: openpilot/selfdrive/ui/layouts/settings/device.py:125 #, python-format msgid "up" msgstr "上" -#: selfdrive/ui/layouts/settings/software.py:117 +#: openpilot/selfdrive/ui/layouts/settings/software.py:127 #, python-format msgid "up to date, last checked never" msgstr "已為最新,最後檢查:從未" -#: selfdrive/ui/layouts/settings/software.py:115 +#: openpilot/selfdrive/ui/layouts/settings/software.py:125 #, python-format msgid "up to date, last checked {}" msgstr "已為最新,最後檢查:{}" -#: selfdrive/ui/layouts/settings/software.py:109 +#: openpilot/selfdrive/ui/layouts/settings/software.py:119 #, python-format msgid "update available" msgstr "有可用更新" -#: selfdrive/ui/layouts/home.py:169 +#: openpilot/selfdrive/ui/layouts/home.py:169 #, python-format msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 則警示" msgstr[1] "{} 則警示" -#: selfdrive/ui/layouts/settings/software.py:40 +#: openpilot/selfdrive/ui/layouts/settings/software.py:47 #, python-format msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: selfdrive/ui/layouts/settings/software.py:37 +#: openpilot/selfdrive/ui/layouts/settings/software.py:44 #, python-format msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小時前" msgstr[1] "{} 小時前" -#: selfdrive/ui/layouts/settings/software.py:34 +#: openpilot/selfdrive/ui/layouts/settings/software.py:41 #, python-format msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分鐘前" msgstr[1] "{} 分鐘前" -#: selfdrive/ui/layouts/settings/firehose.py:111 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 #, python-format msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 個您的駕駛片段納入訓練資料集。" msgstr[1] "目前已有 {} 個您的駕駛片段納入訓練資料集。" -#: selfdrive/ui/widgets/prime.py:62 +#: openpilot/selfdrive/ui/widgets/prime.py:62 #, python-format msgid "✓ SUBSCRIBED" msgstr "✓ 已訂閱" -#: selfdrive/ui/widgets/setup.py:22 +#: openpilot/selfdrive/ui/widgets/setup.py:21 #, python-format msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" + From 1dbae159a8aff24d7325c91bb875ed90472c4313 Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Mon, 9 Mar 2026 14:02:03 -0700 Subject: [PATCH 514/685] op switch: sync submodules (#37618) --- tools/op.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/op.sh b/tools/op.sh index 7c20403a27f..29f0b630202 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -405,6 +405,7 @@ function op_switch() { git submodule deinit --all --force git reset --hard "${REMOTE}/${BRANCH}" git clean -df + git submodule sync --recursive git submodule update --init --recursive git submodule foreach git reset --hard git submodule foreach git clean -df From 56d196162528b053d8da3441edc83608931dc66a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 14:09:13 -0700 Subject: [PATCH 515/685] Revert "setup & reset tuneups" (#37619) Revert "setup & reset tuneups (#37611)" This reverts commit 9510e05dc0012757f6156f012fc37f87f6340d06. --- system/ui/mici_reset.py | 2 +- system/ui/mici_setup.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index c547d9185db..2f89b6d62a3 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -77,7 +77,7 @@ def __init__(self, mode): self._reset_failed_page = ResetFailedPage() self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70), - self._start_reset, exit_on_confirm=False, red=True) + self._start_reset, red=True) self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 4e340335b98..33dfe98f37e 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -144,7 +144,7 @@ def __init__(self, use_openpilot_callback: Callable, self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False) + self._custom_software_slider = LargerSlider("slide to install\nother software", use_custom_software_callback, green=False) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -190,11 +190,11 @@ def __init__(self, continue_callback: Callable, back_callback: Callable): self._continue_button.set_click_callback(continue_callback) self._scroller.add_widgets([ - GreyBigButton("caution: installing\n3rd party software", "swipe down to go back", + GreyBigButton("use caution", "when installing\n3rd party software", gui_app.texture("icons_mici/setup/warning.png", 64, 58)), - GreyBigButton("", "• It has not been tested by comma."), - GreyBigButton("", "• It may not comply with safety standards."), - GreyBigButton("", "• It may damage your device and/or vehicle."), + GreyBigButton("", "• It has not been tested by comma"), + GreyBigButton("", "• It may not comply with relevant safety standards."), + GreyBigButton("", "• It may cause damage to your device and/or vehicle."), GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai", gui_app.texture("icons_mici/setup/restore.png", 64, 64)), self._continue_button, @@ -546,7 +546,7 @@ def _use_openpilot(self): def _push_network_setup(self, custom_software: bool = False): # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) - gui_app.push_widget(self._network_setup_page) + gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._network_setup_page)) def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: From d6c85abcd3f80e27ebe56c526a5aef3515bbc314 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 14:11:01 -0700 Subject: [PATCH 516/685] setup: copy changes from https://github.com/commaai/openpilot/pull/37611 --- system/ui/mici_setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 33dfe98f37e..7571139361b 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -144,7 +144,7 @@ def __init__(self, use_openpilot_callback: Callable, self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to install\nother software", use_custom_software_callback, green=False) + self._custom_software_slider = LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -190,11 +190,11 @@ def __init__(self, continue_callback: Callable, back_callback: Callable): self._continue_button.set_click_callback(continue_callback) self._scroller.add_widgets([ - GreyBigButton("use caution", "when installing\n3rd party software", + GreyBigButton("caution: installing\n3rd party software", "swipe down to go back", gui_app.texture("icons_mici/setup/warning.png", 64, 58)), - GreyBigButton("", "• It has not been tested by comma"), - GreyBigButton("", "• It may not comply with relevant safety standards."), - GreyBigButton("", "• It may cause damage to your device and/or vehicle."), + GreyBigButton("", "• It has not been tested by comma."), + GreyBigButton("", "• It may not comply with safety standards."), + GreyBigButton("", "• It may damage your device and/or vehicle."), GreyBigButton("how to restore to a\nfactory state later", "https://flash.comma.ai", gui_app.texture("icons_mici/setup/restore.png", 64, 64)), self._continue_button, From dd8aa4a21ef5cab0dec44ee642293d52eb87ca28 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 14:20:16 -0700 Subject: [PATCH 517/685] setup: don't swipe down custom fork screen --- system/ui/mici_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 7571139361b..4e340335b98 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -546,7 +546,7 @@ def _use_openpilot(self): def _push_network_setup(self, custom_software: bool = False): # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) - gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._network_setup_page)) + gui_app.push_widget(self._network_setup_page) def _network_setup_continue_callback(self, custom_software: bool): if not custom_software: From 0208d26845db2dabb0f7a49bd69f694fb4a6c771 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 15:39:06 -0700 Subject: [PATCH 518/685] reset: don't swipe down confirm slider (#37620) * test and broke * fix * clean up --- system/ui/mici_reset.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 2f89b6d62a3..2eed4db11e1 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -77,7 +77,7 @@ def __init__(self, mode): self._reset_failed_page = ResetFailedPage() self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70), - self._start_reset, red=True) + self._start_reset, exit_on_confirm=False, red=True) self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), gui_app.request_close, exit_on_confirm=False) self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70), @@ -105,6 +105,8 @@ def __init__(self, mode): self._reset_button, ]) + gui_app.add_nav_stack_tick(self._nav_stack_tick) + def _do_erase(self): if PC: return @@ -123,9 +125,7 @@ def _start_reset(self): self._resetting_page.set_shown_callback(self._do_erase) gui_app.push_widget(self._resetting_page) - def _update_state(self): - super()._update_state() - + def _nav_stack_tick(self): if self._reset_failed: self._reset_failed = False gui_app.pop_widgets_to(self, lambda: gui_app.push_widget(self._reset_failed_page)) From acace97ef89c3893322f690bc621118c05a1b258 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 17:18:40 -0700 Subject: [PATCH 519/685] add warning to pack.py (#37624) * start * works! * can't check ls-files because we need built files too >:( * add print --- release/pack.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/release/pack.py b/release/pack.py index 8979e0e618d..a617a883507 100755 --- a/release/pack.py +++ b/release/pack.py @@ -28,6 +28,8 @@ def copy(src, dest): parser.add_argument('module', help="the module to target, e.g. 'openpilot.system.ui.spinner'") args = parser.parse_args() + print('WARNING: copying all files! make sure to run scons and git tree is clean') + if not args.output: args.output = args.module From a17a8daad56f99fccf6e366e96bb693ac943b47c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 17:32:33 -0700 Subject: [PATCH 520/685] pack.py: exclude large unused folderrs --- release/pack.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/release/pack.py b/release/pack.py index a617a883507..8831a0b34dc 100755 --- a/release/pack.py +++ b/release/pack.py @@ -13,11 +13,12 @@ DIRS = ['cereal', 'openpilot'] EXTS = ['.png', '.py', '.ttf', '.capnp', '.json', '.fnt', '.mo', '.po'] +EXCLUDE = ['selfdrive/assets/training', 'third_party/raylib/raylib_repo/examples'] INTERPRETER = '/usr/bin/env python3' def copy(src, dest): - if any(src.endswith(ext) for ext in EXTS): + if any(src.endswith(ext) for ext in EXTS) and not any(exc in src for exc in EXCLUDE): shutil.copy2(src, dest, follow_symlinks=True) From 2ca6f893df17bc98b9e4dca0e854c26b95ce8a5c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 17:34:16 -0700 Subject: [PATCH 521/685] New updater_magic --- system/hardware/tici/updater_magic | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index 8bf150744aa..f0689d50724 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3236a08d318c26022e536fa76627ca15c0517f050f4c4e91a1aafc5bfefb44d0 -size 71240680 +oid sha256:238d750f12c8b6758fd47998891d0cfa4bc52d72662801553c7d03952f8166b2 +size 24744419 From 095d96fbe07cb28b81bde9102208a0ff1f3cba77 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 18:43:42 -0700 Subject: [PATCH 522/685] reset: erase in thread (#37627) erase in thread --- system/ui/mici_reset.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index 2eed4db11e1..af9d866d544 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -2,6 +2,7 @@ import os import sys import time +import threading from enum import IntEnum import pyray as rl @@ -122,7 +123,10 @@ def _do_erase(self): self._reset_failed = True def _start_reset(self): - self._resetting_page.set_shown_callback(self._do_erase) + def do_erase_thread(): + threading.Thread(target=self._do_erase, daemon=True).start() + + self._resetting_page.set_shown_callback(do_erase_thread) gui_app.push_widget(self._resetting_page) def _nav_stack_tick(self): From 1777d548bf0023297d8a685ff8dc6917a1e1db44 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 9 Mar 2026 20:11:26 -0700 Subject: [PATCH 523/685] stagger driver camera SOF (#37628) --- panda | 2 +- selfdrive/test/test_onroad.py | 7 ++++++- system/camerad/cameras/hw.h | 4 ++++ system/camerad/cameras/spectra.cc | 20 ++++++++++++++------ system/camerad/cameras/spectra.h | 3 ++- system/camerad/test/test_camerad.py | 12 +++++++++--- 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/panda b/panda index d1410f7f7b0..c10b82f8ff0 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit d1410f7f7b061171c3702d84d975a3da3afce109 +Subproject commit c10b82f8ff03c7d677a9420c21c0c50126a5071e diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 008b8ebe7f1..1129a1a2ff1 100644 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -342,10 +342,15 @@ def test_camera_sync(self, subtests): start, end = min(first_fid), min(last_fid) for i in range(end-start): - ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams} + # road and wide cameras (first two) should be synced within 2ms + ts = {c: round(self.ts[c]['timestampSof'][i]/1e6, 1) for c in cams[:2]} diff = (max(ts.values()) - min(ts.values())) assert diff < 2, f"Cameras not synced properly: frame_id={start+i}, {diff=:.1f}ms, {ts=}" + # driver camera should be staggered ~25ms from road camera + offset_ms = abs(self.ts[cams[2]]['timestampSof'][i] - self.ts[cams[0]]['timestampSof'][i]) / 1e6 + assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {start+i}: {offset_ms:.1f}ms" + def test_camera_encoder_matches(self, subtests): # sanity check that the frame metadata is consistent with the encoded frames pairs = [('roadCameraState', 'roadEncodeIdx'), diff --git a/system/camerad/cameras/hw.h b/system/camerad/cameras/hw.h index f20a1b3adec..defe878e89a 100644 --- a/system/camerad/cameras/hw.h +++ b/system/camerad/cameras/hw.h @@ -25,6 +25,7 @@ struct CameraConfig { uint32_t phy; bool vignetting_correction; SpectraOutputType output_type; + bool staggered_sof; // SOF is staggered (half-period offset) from other cameras }; // NOTE: to be able to disable road and wide road, we still have to configure the sensor over i2c @@ -39,6 +40,7 @@ const CameraConfig WIDE_ROAD_CAMERA_CONFIG = { .phy = CAM_ISP_IFE_IN_RES_PHY_0, .vignetting_correction = false, .output_type = ISP_IFE_PROCESSED, + .staggered_sof = false, }; const CameraConfig ROAD_CAMERA_CONFIG = { @@ -51,6 +53,7 @@ const CameraConfig ROAD_CAMERA_CONFIG = { .phy = CAM_ISP_IFE_IN_RES_PHY_1, .vignetting_correction = true, .output_type = ISP_IFE_PROCESSED, + .staggered_sof = false, }; const CameraConfig DRIVER_CAMERA_CONFIG = { @@ -63,6 +66,7 @@ const CameraConfig DRIVER_CAMERA_CONFIG = { .phy = CAM_ISP_IFE_IN_RES_PHY_2, .vignetting_correction = false, .output_type = ISP_BPS_PROCESSED, + .staggered_sof = true, }; const CameraConfig ALL_CAMERA_CONFIGS[] = {WIDE_ROAD_CAMERA_CONFIG, ROAD_CAMERA_CONFIG, DRIVER_CAMERA_CONFIG}; diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index 73e0a78da30..ab9d8e069af 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -1436,7 +1436,7 @@ bool SpectraCamera::waitForFrameReady(uint64_t request_id) { } bool SpectraCamera::processFrame(int buf_idx, uint64_t request_id, uint64_t frame_id_raw, uint64_t timestamp) { - if (!syncFirstFrame(cc.camera_num, request_id, frame_id_raw, timestamp)) { + if (!syncFirstFrame(cc.camera_num, request_id, frame_id_raw, timestamp, cc.staggered_sof)) { return false; } @@ -1455,23 +1455,31 @@ bool SpectraCamera::processFrame(int buf_idx, uint64_t request_id, uint64_t fram return true; } -bool SpectraCamera::syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp) { +bool SpectraCamera::syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp, bool staggered) { if (first_frame_synced) return true; // Store the frame data for this camera - camera_sync_data[camera_id] = SyncData{timestamp, raw_id + 1}; + camera_sync_data[camera_id] = SyncData{timestamp, raw_id + 1, staggered}; // Ensure all cameras are up int enabled_camera_count = std::count_if(std::begin(ALL_CAMERA_CONFIGS), std::end(ALL_CAMERA_CONFIGS), [](const auto &config) { return config.enabled; }); bool all_cams_up = camera_sync_data.size() == enabled_camera_count; - // Wait until the timestamps line up + // Check that camera timestamps are properly aligned: + // - non-staggered cameras should be within 0.2ms of each other + // - staggered cameras should be within 0.2ms of a 25ms offset from non-staggered cameras + const uint64_t half_period_ns = 25 * 1000000ULL; // 25ms + const uint64_t tolerance_ns = 200000ULL; // 0.2ms bool all_cams_synced = true; - for (const auto &[_, sync_data] : camera_sync_data) { + for (const auto &[cam, sync_data] : camera_sync_data) { + if (cam == camera_id) continue; uint64_t diff = std::max(timestamp, sync_data.timestamp) - std::min(timestamp, sync_data.timestamp); - if (diff > 0.2*1e6) { // milliseconds + bool pair_staggered = staggered != sync_data.staggered; + uint64_t expected_offset = pair_staggered ? half_period_ns : 0; + uint64_t error = (diff > expected_offset) ? diff - expected_offset : expected_offset - diff; + if (error > tolerance_ns) { all_cams_synced = false; } } diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index a02b8a6cac7..7dd11352540 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -194,10 +194,11 @@ class SpectraCamera { bool validateEvent(uint64_t request_id, uint64_t frame_id_raw); bool waitForFrameReady(uint64_t request_id); bool processFrame(int buf_idx, uint64_t request_id, uint64_t frame_id_raw, uint64_t timestamp); - static bool syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp); + static bool syncFirstFrame(int camera_id, uint64_t request_id, uint64_t raw_id, uint64_t timestamp, bool staggered); struct SyncData { uint64_t timestamp; uint64_t frame_id_offset = 0; + bool staggered = false; }; inline static std::map camera_sync_data; inline static bool first_frame_synced = false; diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index 5f8de868991..3abe4db6548 100644 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -105,17 +105,23 @@ def test_frame_skips(self, logs): assert set(np.diff(logs[c]['frameId'])) == {1, }, f"{c} has frame skips" def test_frame_sync(self, logs): + SYNCED_CAMS = ('roadCameraState', 'wideRoadCameraState') n = range(len(logs['roadCameraState']['t'][:-10])) frame_ids = {i: [logs[cam]['frameId'][i] for cam in CAMERAS] for i in n} assert all(len(set(v)) == 1 for v in frame_ids.values()), "frame IDs not aligned" - frame_times = {i: [logs[cam]['timestampSof'][i] for cam in CAMERAS] for i in n} - diffs = {i: (max(ts) - min(ts))/1e6 for i, ts in frame_times.items()} - + # road and wide cameras should be synced within 1.1ms + synced_times = {i: [logs[cam]['timestampSof'][i] for cam in SYNCED_CAMS] for i in n} + diffs = {i: (max(ts) - min(ts))/1e6 for i, ts in synced_times.items()} laggy_frames = {k: v for k, v in diffs.items() if v > 1.1} assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" + # driver camera should be staggered ~25ms from road camera + for i in n: + offset_ms = abs(logs['driverCameraState']['timestampSof'][i] - logs['roadCameraState']['timestampSof'][i]) / 1e6 + assert 20 < offset_ms < 30, f"driver camera stagger out of range at frame {i}: {offset_ms:.1f}ms (expected ~25ms)" + def test_sanity_checks(self, logs): self._sanity_checks(logs) From bd5fbbabda6c4328e2efe144be10f8184807b03b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 9 Mar 2026 22:25:49 -0700 Subject: [PATCH 524/685] setup: simplify cache branch (#37630) * this wasn't atomic! * start mici * always require internet to download installer * this made it never use cached fetch! * this skipped installer when it wrote it raced trying to run * entirely remove * clean up mici * fix tici setup * inline * works --- system/ui/mici_setup.py | 36 +++++------------------------------- system/ui/tici_setup.py | 34 +++------------------------------- 2 files changed, 8 insertions(+), 62 deletions(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 4e340335b98..616991b5604 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -7,7 +7,6 @@ import urllib.request import urllib.error from urllib.parse import urlparse -import shutil from collections.abc import Callable import pyray as rl @@ -36,20 +35,9 @@ OPENPILOT_URL = "https://openpilot.comma.ai" USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" -CONTINUE_PATH = "/data/continue.sh" -TMP_CONTINUE_PATH = "/data/continue.sh.new" -INSTALL_PATH = "/data/openpilot" -VALID_CACHE_PATH = "/data/.openpilot_cache" -INSTALLER_SOURCE_PATH = "/usr/comma/installer" INSTALLER_DESTINATION_PATH = "/tmp/installer" INSTALLER_URL_PATH = "/tmp/installer_url" -CONTINUE = """#!/usr/bin/env bash - -cd /data/openpilot -exec ./launch_openpilot.sh -""" - class NetworkConnectivityMonitor: def __init__(self, should_check: Callable[[], bool] | None = None): @@ -499,7 +487,7 @@ def getting_started_button_callback(): self._network_setup_page = NetworkSetupPage(self._network_monitor, self._network_setup_continue_callback, self._pop_to_software_selection) - self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page)) + self._software_selection_page = SoftwareSelectionPage(self._push_network_setup, lambda: gui_app.push_widget(self._custom_software_warning_page)) self._download_failed_page = FailedPage(self._pop_to_software_selection, icon="icons_mici/setup/red_warning.png") @@ -528,21 +516,6 @@ def _pop_to_software_selection(self): # reset sliders after dismiss completes gui_app.pop_widgets_to(self._software_selection_page, self._software_selection_page.reset) - def _use_openpilot(self): - if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): - os.remove(VALID_CACHE_PATH) - with open(TMP_CONTINUE_PATH, "w") as f: - f.write(CONTINUE) - run_cmd(["chmod", "+x", TMP_CONTINUE_PATH]) - shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH) - shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) - - # give time for installer UI to take over - time.sleep(0.1) - gui_app.request_close() - else: - self._push_network_setup() - def _push_network_setup(self, custom_software: bool = False): # to fire the correct continue callback later self._network_setup_page.set_custom_software(custom_software) @@ -612,14 +585,15 @@ def _download_thread(self): self._download_failed_reason = "No custom software found at this URL: " + self.download_url.replace("https://", "", 1) return + # NOTE: currently unused, for future logging + with open(INSTALLER_URL_PATH, "w") as f: + f.write(self.download_url) + # AGNOS might try to execute the installer before this process exits. # Therefore, important to close the fd before renaming the installer. os.close(fd) os.rename(tmpfile, INSTALLER_DESTINATION_PATH) - with open(INSTALLER_URL_PATH, "w") as f: - f.write(self.download_url) - # give time for installer UI to take over time.sleep(0.1) gui_app.request_close() diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py index 8098e9ea273..f98ab5ffaa1 100755 --- a/system/ui/tici_setup.py +++ b/system/ui/tici_setup.py @@ -7,12 +7,10 @@ import urllib.error from urllib.parse import urlparse from enum import IntEnum -import shutil import pyray as rl from cereal import log -from openpilot.common.utils import run_cmd from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE @@ -35,20 +33,9 @@ OPENPILOT_URL = "https://openpilot.comma.ai" USER_AGENT = f"AGNOSSetup-{HARDWARE.get_os_version()}" -CONTINUE_PATH = "/data/continue.sh" -TMP_CONTINUE_PATH = "/data/continue.sh.new" -INSTALL_PATH = "/data/openpilot" -VALID_CACHE_PATH = "/data/.openpilot_cache" -INSTALLER_SOURCE_PATH = "/usr/comma/installer" INSTALLER_DESTINATION_PATH = "/tmp/installer" INSTALLER_URL_PATH = "/tmp/installer_url" -CONTINUE = """#!/usr/bin/env bash - -cd /data/openpilot -exec ./launch_openpilot.sh -""" - class SetupState(IntEnum): LOW_VOLTAGE = 0 @@ -176,7 +163,9 @@ def _software_selection_back_button_callback(self): def _software_selection_continue_button_callback(self): if self._software_selection_openpilot_button.selected: - self.use_openpilot() + self.state = SetupState.NETWORK_SETUP + self.stop_network_check_thread.clear() + self.start_network_check() else: self.state = SetupState.CUSTOM_SOFTWARE_WARNING @@ -342,23 +331,6 @@ def handle_keyboard_result(result): self.keyboard.set_callback(handle_keyboard_result) gui_app.push_widget(self.keyboard) - def use_openpilot(self): - if os.path.isdir(INSTALL_PATH) and os.path.isfile(VALID_CACHE_PATH): - os.remove(VALID_CACHE_PATH) - with open(TMP_CONTINUE_PATH, "w") as f: - f.write(CONTINUE) - run_cmd(["chmod", "+x", TMP_CONTINUE_PATH]) - shutil.move(TMP_CONTINUE_PATH, CONTINUE_PATH) - shutil.copyfile(INSTALLER_SOURCE_PATH, INSTALLER_DESTINATION_PATH) - - # give time for installer UI to take over - time.sleep(0.1) - gui_app.request_close() - else: - self.state = SetupState.NETWORK_SETUP - self.stop_network_check_thread.clear() - self.start_network_check() - def download(self, url: str): # autocomplete incomplete URLs if re.match("^([^/.]+)/([^/]+)$", url): From 4acf0438c8a15488cf86416e2ec602dd9ba126e7 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Mar 2026 03:17:18 -0700 Subject: [PATCH 525/685] AGNOS 17.1 (#37631) * agnos 17.1 * bump version --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 20 ++++++------- system/hardware/tici/all-partitions.json | 36 ++++++++++++------------ 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index 097f5fbe948..efdb4bf0e71 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="17" + export AGNOS_VERSION="17.1" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index ec5f43fa7b9..58162fc3113 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -56,28 +56,28 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0.img.xz", - "hash": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", - "hash_raw": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "url": "https://commadist.azureedge.net/agnosupdate/boot-d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51.img.xz", + "hash": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", + "hash_raw": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "a64fc06e2508dbb2af4fa20808c10009bb5a31517ff39b0fb1dad882c9bec808" + "ondevice_hash": "2454108de1161289bc4a75449ad6421f1772b13b3e5cba68a84fca7530557699" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img.xz", - "hash": "ef7ad7d290d74285e9e73f30442d2adc8ff3a616e1d7ddcceee6ba5420aa2fb7", - "hash_raw": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", + "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img.xz", + "hash": "5d49176870f05328c9b4a934863644c2a9b59c993df97a9134ff63f7a4e1d81d", + "hash_raw": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "bfe5187bb754fd0df5069d0b2b12f79f5938588f97bb87063532da5d6e7a1cfb", + "ondevice_hash": "ce18addaec7dd87a511fb7cb068500a1de5720fd76fceddf2944084b7d6fdf15", "alt": { - "hash": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img", + "hash": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", + "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 8b4c0d43125..468142be601 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -339,51 +339,51 @@ }, { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0.img.xz", - "hash": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", - "hash_raw": "1bce912831b41cc65d651acbc07ca68a1d528298c22ebae7c060db5f524f4ee0", + "url": "https://commadist.azureedge.net/agnosupdate/boot-d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51.img.xz", + "hash": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", + "hash_raw": "d726315cf98a43e1090e5b49297404cf3d084cfbd42ad8bb7d8afb68136b9f51", "size": 17500160, "sparse": false, "full_check": true, "has_ab": true, - "ondevice_hash": "a64fc06e2508dbb2af4fa20808c10009bb5a31517ff39b0fb1dad882c9bec808" + "ondevice_hash": "2454108de1161289bc4a75449ad6421f1772b13b3e5cba68a84fca7530557699" }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img.xz", - "hash": "ef7ad7d290d74285e9e73f30442d2adc8ff3a616e1d7ddcceee6ba5420aa2fb7", - "hash_raw": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", + "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img.xz", + "hash": "5d49176870f05328c9b4a934863644c2a9b59c993df97a9134ff63f7a4e1d81d", + "hash_raw": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "bfe5187bb754fd0df5069d0b2b12f79f5938588f97bb87063532da5d6e7a1cfb", + "ondevice_hash": "ce18addaec7dd87a511fb7cb068500a1de5720fd76fceddf2944084b7d6fdf15", "alt": { - "hash": "1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6", - "url": "https://commadist.azureedge.net/agnosupdate/system-1a64e41bf8d9f63c1a4d5e3e0a5a4c6c60c7dfb9d1e677c03adc2c668bc3fcb6.img", + "hash": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", + "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-3711c021ee7a6512c93452857893325a6ed845e4cfad52398b531eaede22f913.img.xz", - "hash": "67e0066cc5d2b7173f6280a7a8836d6681e2cd55b0f4a79eafac5b9fdd4fd7c8", - "hash_raw": "3711c021ee7a6512c93452857893325a6ed845e4cfad52398b531eaede22f913", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-24f985765b91593e468fa76333a994420a9f460b9a66368a8dbdcac6ee04d8f5.img.xz", + "hash": "f24ee158408d4b41a95c79361f442b5a3304c46e0872e387afddbdbb56d934ac", + "hash_raw": "24f985765b91593e468fa76333a994420a9f460b9a66368a8dbdcac6ee04d8f5", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "31fe9e6e1b4ae1fe267868daff1b56660c9b4345e6a6272fce161b30ba70ed4c" + "ondevice_hash": "9793fa1de3a22cedace28a43f001fbdc4fb73c81c949dc4ec4f5e60ce9eef0b6" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-8fcd15c164625c81199d7d3cca11009c862efa3ae19112f57fb10b4202474363.img.xz", - "hash": "ccb3bde6f31b340816d8499a5a205ba0e9984c189451807501a76f05820c3f4c", - "hash_raw": "8fcd15c164625c81199d7d3cca11009c862efa3ae19112f57fb10b4202474363", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-01336e05d3f85a398f2255ecbd4ebbc14b7f688a33295390891ad90234db8f0b.img.xz", + "hash": "ec13a458352c8d3a974a97acc1aa5d7f1118ab59989d375e00d81c7091b75e8e", + "hash_raw": "01336e05d3f85a398f2255ecbd4ebbc14b7f688a33295390891ad90234db8f0b", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "4167c69304e8038050793dbde70ff230614146836d9d31e5eb83aa03b6ed787f" + "ondevice_hash": "963c071664375b531bb867d83ffc9e2de7883408ca1481330a1ee4db1a964e68" } ] \ No newline at end of file From ba19527181a0ce3f8b98ea78398c06c9ca384469 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Mar 2026 10:19:20 -0700 Subject: [PATCH 526/685] 0.11.1: a nice DM focused release --- RELEASES.md | 5 +++++ common/version.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index bf4e0c75aac..a3a95ae6a86 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,3 +1,8 @@ +Version 0.11.1 (2026-04-08) +======================== +* New driver monitoring model +* Improved image processing pipeline for driver camera + Version 0.11.0 (2026-03-17) ======================== * New driving model #36798 diff --git a/common/version.h b/common/version.h index 78264e074cd..41440556c54 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.11.0" +#define COMMA_VERSION "0.11.1" From ac3dcbe62fc7b60d7285bc056b0210a7aaf7cf9a Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Tue, 10 Mar 2026 10:55:17 -0700 Subject: [PATCH 527/685] Revert "op switch: sync submodules" (#37632) Revert "op switch: sync submodules (#37618)" This reverts commit 1dbae159a8aff24d7325c91bb875ed90472c4313. --- tools/op.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/op.sh b/tools/op.sh index 29f0b630202..7c20403a27f 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -405,7 +405,6 @@ function op_switch() { git submodule deinit --all --force git reset --hard "${REMOTE}/${BRANCH}" git clean -df - git submodule sync --recursive git submodule update --init --recursive git submodule foreach git reset --hard git submodule foreach git clean -df From 9164148d487d7004142b28900e1cd496ce6a8d1d Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 10 Mar 2026 10:58:21 -0700 Subject: [PATCH 528/685] feat: uv manages python (#37535) --- SConstruct | 3 +-- pyproject.toml | 4 +++- tools/op.sh | 27 +-------------------------- uv.lock | 7 ------- 4 files changed, 5 insertions(+), 36 deletions(-) diff --git a/SConstruct b/SConstruct index 3f974f09c16..c6c86325602 100644 --- a/SConstruct +++ b/SConstruct @@ -41,7 +41,6 @@ assert arch in [ pkg_names = ['bzip2', 'capnproto', 'eigen', 'ffmpeg', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd'] pkgs = [importlib.import_module(name) for name in pkg_names] -py_include = importlib.import_module('python3_dev').INCLUDE_DIR env = Environment( ENV={ @@ -163,7 +162,7 @@ if os.environ.get('SCONS_PROGRESS'): # ********** Cython build environment ********** envCython = env.Clone() -envCython["CPPPATH"] += [py_include, np.get_include()] +envCython["CPPPATH"] += [sysconfig.get_paths()['include'], np.get_include()] envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"] envCython["CCFLAGS"].remove("-Werror") diff --git a/pyproject.toml b/pyproject.toml index 5b77eb76d8f..408513600ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,7 +32,6 @@ dependencies = [ "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", "libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv", - "python3-dev @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=python3-dev", "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", @@ -248,3 +247,6 @@ unsupported-operator = "ignore" # Ignore not-subscriptable - false positives from dynamic types not-subscriptable = "ignore" # not-iterable errors are now fixed + +[tool.uv] +python-preference = "only-managed" diff --git a/tools/op.sh b/tools/op.sh index 7c20403a27f..f21a285d175 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -167,29 +167,6 @@ function op_check_os() { fi } -function op_check_python() { - echo "Checking for compatible python version..." - REQUIRED_PYTHON_VERSION=$(grep "requires-python" $OPENPILOT_ROOT/pyproject.toml) - INSTALLED_PYTHON_VERSION=$(python3 --version 2> /dev/null || true) - - if [[ -z $INSTALLED_PYTHON_VERSION ]]; then - echo -e " ↳ [${RED}✗${NC}] python3 not found on your system. You need python version satisfying $(echo $REQUIRED_PYTHON_VERSION | cut -d '=' -f2-) to continue!" - loge "ERROR_PYTHON_NOT_FOUND" - return 1 - else - LB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($4, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') - UB=$(echo $REQUIRED_PYTHON_VERSION | tr -d '",' | awk '{ split($6, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') - VERSION=$(echo $INSTALLED_PYTHON_VERSION | awk '{ split($2, v, "."); printf "%d%02d%02d", v[1], v[2], v[3] }') - if [[ $VERSION -ge LB && $VERSION -lt UB ]]; then - echo -e " ↳ [${GREEN}✔${NC}] $INSTALLED_PYTHON_VERSION detected." - else - echo -e " ↳ [${RED}✗${NC}] You need a python version satisfying $(echo $REQUIRED_PYTHON_VERSION | cut -d '=' -f2-) to continue!" - loge "ERROR_PYTHON_VERSION" "$INSTALLED_PYTHON_VERSION" - return 1 - fi - fi -} - function op_check_venv() { echo "Checking for venv..." if [[ -f $OPENPILOT_ROOT/.venv/bin/activate ]]; then @@ -214,8 +191,6 @@ function op_before_cmd() { op_activate_venv - result="${result}\n$(( op_check_python ) 2>&1)" || (echo -e "$result" && return 1) - if [[ -z $VERBOSE ]]; then echo -e "${BOLD}Checking system →${NC} [${GREEN}✔${NC}]" else @@ -436,7 +411,7 @@ function op_default() { echo "" echo -e "${BOLD}${UNDERLINE}Commands [System]:${NC}" echo -e " ${BOLD}auth${NC} Authenticate yourself for API use" - echo -e " ${BOLD}check${NC} Check the development environment (git, os, python) to start using openpilot" + echo -e " ${BOLD}check${NC} Check the development environment (git, os) to start using openpilot" echo -e " ${BOLD}esim${NC} Manage eSIM profiles on your comma device" echo -e " ${BOLD}venv${NC} Activate the python virtual environment" echo -e " ${BOLD}setup${NC} Install openpilot dependencies" diff --git a/uv.lock b/uv.lock index 743521fb2f1..578b19f6462 100644 --- a/uv.lock +++ b/uv.lock @@ -806,7 +806,6 @@ dependencies = [ { name = "pycryptodome" }, { name = "pyjwt" }, { name = "pyserial" }, - { name = "python3-dev" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib" }, @@ -897,7 +896,6 @@ requires-dist = [ { name = "pytest-mock", marker = "extra == 'testing'" }, { name = "pytest-subtests", marker = "extra == 'testing'" }, { name = "pytest-xdist", marker = "extra == 'testing'", git = "https://github.com/sshane/pytest-xdist?rev=2b4372bd62699fb412c4fe2f95bf9f01bd2018da" }, - { name = "python3-dev", git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases" }, { name = "pyzmq" }, { name = "qrcode" }, { name = "raylib", specifier = ">5.5.0.3" }, @@ -1286,11 +1284,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] -[[package]] -name = "python3-dev" -version = "3.12.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=python3-dev&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } - [[package]] name = "pyyaml" version = "6.0.3" From bf4bf0e5b7faa98212f62786bdd13863feeb85a4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Mar 2026 11:44:25 -0700 Subject: [PATCH 529/685] qcomgpsd, timed: reject invalid GPS timestamps (#37633) --- common/time_helpers.py | 3 ++- system/qcomgpsd/qcomgpsd.py | 3 +++ system/timed.py | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/common/time_helpers.py b/common/time_helpers.py index 8564e270c2b..c709182d455 100644 --- a/common/time_helpers.py +++ b/common/time_helpers.py @@ -2,6 +2,7 @@ from pathlib import Path MIN_DATE = datetime.datetime(year=2025, month=2, day=21) +MAX_DATE = datetime.datetime(year=2035, month=1, day=1) def min_date(): # on systemd systems, the default time is the systemd build time @@ -12,4 +13,4 @@ def min_date(): return MIN_DATE def system_time_valid(): - return datetime.datetime.now() > min_date() + return min_date() < datetime.datetime.now() < MAX_DATE diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 82b9ea927fd..47d64ff4e9f 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -357,6 +357,9 @@ def cleanup(sig, frame): report = unpack_position(log_payload) if report["u_PosSource"] != 2: continue + # uint16_t max is an invalid sentinel value from the modem + if report['w_GpsWeekNumber'] >= 0xFFFF: + continue vNED = [report["q_FltVelEnuMps[1]"], report["q_FltVelEnuMps[0]"], -report["q_FltVelEnuMps[2]"]] vNEDsigma = [report["q_FltVelSigmaMps[1]"], report["q_FltVelSigmaMps[0]"], -report["q_FltVelSigmaMps[2]"]] diff --git a/system/timed.py b/system/timed.py index b7131b04c07..c74ba51da59 100755 --- a/system/timed.py +++ b/system/timed.py @@ -5,7 +5,7 @@ from typing import NoReturn import cereal.messaging as messaging -from openpilot.common.time_helpers import min_date, system_time_valid +from openpilot.common.time_helpers import min_date, MAX_DATE, system_time_valid from openpilot.common.swaglog import cloudlog from openpilot.common.params import Params from openpilot.common.gps import get_gps_location_service @@ -52,7 +52,7 @@ def main() -> NoReturn: continue if not gps.hasFix: continue - if gps_time < min_date(): + if gps_time < min_date() or gps_time > MAX_DATE: continue set_time(gps_time) From dd89bc30fa131d7a859f5538f10c99509228dbbf Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 10 Mar 2026 15:08:56 -0700 Subject: [PATCH 530/685] set preference for python 3.12.13 (#37637) --- .python-version | 1 + 1 file changed, 1 insertion(+) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 00000000000..28d9a01b1fa --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12.13 From 592731678813565fd4cd4f25fad8a9d45f844abd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Mar 2026 15:57:26 -0700 Subject: [PATCH 531/685] ci: revert first-interaction to v1 (#37639) * ci: revert first-interaction to v1 * ci: retrigger PR review on synchronize --- .github/workflows/auto_pr_review.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 63cb062ebe3..6052ec07120 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -36,12 +36,11 @@ jobs: # Welcome comment - name: "First timers PR" - uses: actions/first-interaction@v3 + uses: actions/first-interaction@v1 if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' with: - repo_token: ${{ secrets.GITHUB_TOKEN }} - issue_message: "" - pr_message: | + repo-token: ${{ secrets.GITHUB_TOKEN }} + pr-message: | Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following: * Convert your PR to a draft unless it's ready to review From 40b61a82122d5ec41cecdbe5cfaff8415c5726c2 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:01:31 -0500 Subject: [PATCH 532/685] clip: load metadata params within OpenpilotPrefix (#37634) fix: move metadata loading inside OpenpilotPrefix context --- tools/clip/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index a5587f239b2..2deb95dede2 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -302,11 +302,11 @@ def clip(route: Route, output: str, start: int, end: int, headless: bool = True, logger.error("No messages to render") sys.exit(1) - metadata = load_route_metadata(route) if show_metadata else None if headless: rl.set_config_flags(rl.ConfigFlags.FLAG_WINDOW_HIDDEN) with OpenpilotPrefix(shared_download_cache=True): + metadata = load_route_metadata(route) if show_metadata else None camera_paths = route.qcamera_paths() if use_qcam else route.camera_paths() frame_queue = FrameQueue(camera_paths, start, end, fps=FRAMERATE, use_qcam=use_qcam) From b750229e7087c896a0cbd1af3b263156cfcf96f4 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:02:02 -0500 Subject: [PATCH 533/685] fix(sim): remove alpha channel for improved performance (#37602) fix: update RGB image processing in CopyRamRGBCamera --- tools/sim/bridge/metadrive/metadrive_common.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tools/sim/bridge/metadrive/metadrive_common.py b/tools/sim/bridge/metadrive/metadrive_common.py index 079d933d1a8..0106579b20c 100644 --- a/tools/sim/bridge/metadrive/metadrive_common.py +++ b/tools/sim/bridge/metadrive/metadrive_common.py @@ -13,11 +13,9 @@ def __init__(self, *args, **kwargs): def get_rgb_array_cpu(self): origin_img = self.cpu_texture - img = np.frombuffer(origin_img.getRamImageAs("RGBA").getData(), dtype=np.uint8) - img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), 4)) - img = img[:, :, :3] - # img = np.swapaxes(img, 1, 0) - img = img[::-1] # Flip on vertical axis + img = np.frombuffer(origin_img.getRamImageAs("RGB").getData(), dtype=np.uint8) + img = img.reshape((origin_img.getYSize(), origin_img.getXSize(), 3)) + img = img[::-1] # Flip on vertical axis return img From f85b3473a22ba406d25d762e0afc9d8f6515bff7 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:02:55 -0500 Subject: [PATCH 534/685] ui replay: Improve big (tizi) replay coverage (#37468) * fix pairing qr code * test pair device * merge and pick from explore-more * key * fast click * again * add branch selection test * click uninstall * test prime states * view regulatory * test expand calibration desc * override interactive timeout * reorder * remove todo * update * clarify * test reset calibration * update * add calibration params test * comments * reorganize * clarify * add click through training guide --- selfdrive/ui/tests/diff/replay.py | 14 ++- selfdrive/ui/tests/diff/replay_script.py | 148 ++++++++++++++++++----- 2 files changed, 133 insertions(+), 29 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 9860969efbb..49e17a6c2a9 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -7,9 +7,12 @@ from typing import Literal from collections.abc import Callable from cereal.messaging import PubMaster +from openpilot.common.api import Api +from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.prefix import OpenpilotPrefix from openpilot.selfdrive.ui.tests.diff.diff import DIFF_OUT_DIR +from openpilot.system.updated.updated import parse_release_notes from openpilot.system.version import terms_version, training_version LayoutVariant = Literal["mici", "tizi"] @@ -25,13 +28,16 @@ def setup_state(): params.put("DongleId", "test123456789") # Combined description for layouts that still use it (BIG home, settings/software) params.put("UpdaterCurrentDescription", "0.10.1 / test-branch / abc1234 / Nov 30") - + params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) # Params for mici home params.put("Version", "0.10.1") params.put("GitBranch", "test-branch") params.put("GitCommit", "abc12340ff9131237ba23a1d0fbd8edf9c80e87") params.put("GitCommitDate", "'1732924800 2024-11-30 00:00:00 +0000'") + # Patch Api.get_token to return a static token so the pairing QR code is deterministic across runs + Api.get_token = lambda self, payload_extra=None, expiry_hours=0: "test_token" + def run_replay(variant: LayoutVariant) -> None: if HEADLESS: @@ -41,7 +47,7 @@ def run_replay(variant: LayoutVariant) -> None: setup_state() os.makedirs(DIFF_OUT_DIR, exist_ok=True) - from openpilot.selfdrive.ui.ui_state import ui_state # Import within OpenpilotPrefix context so param values are setup correctly + from openpilot.selfdrive.ui.ui_state import ui_state, device # Import within OpenpilotPrefix context so param values are setup correctly from openpilot.system.ui.lib.application import gui_app # Import here for accurate coverage from openpilot.selfdrive.ui.tests.diff.replay_script import build_script @@ -54,6 +60,10 @@ def run_replay(variant: LayoutVariant) -> None: from openpilot.selfdrive.ui.layouts.main import MainLayout main_layout = MainLayout() + # Disable interactive timeout — replay clicks use left_down=False so they never reset the timer, + # and after 30s of real wall-clock time the settings panel would close automatically. + device.set_override_interactive_timeout(99999) + pm = PubMaster(["deviceState", "pandaStates", "driverStateV2", "selfdriveState"]) script = build_script(pm, main_layout, variant) script_index = 0 diff --git a/selfdrive/ui/tests/diff/replay_script.py b/selfdrive/ui/tests/diff/replay_script.py index 9f2104ec497..c43442a33d3 100644 --- a/selfdrive/ui/tests/diff/replay_script.py +++ b/selfdrive/ui/tests/diff/replay_script.py @@ -3,15 +3,20 @@ from collections.abc import Callable from dataclasses import dataclass +import math + from cereal import car, log, messaging from cereal.messaging import PubMaster from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert +from openpilot.selfdrive.ui.lib.prime_state import PrimeType from openpilot.selfdrive.ui.tests.diff.replay import FPS, LayoutVariant from openpilot.system.updated.updated import parse_release_notes -WAIT = int(FPS * 0.5) # Default frames to wait after events +# Default frames to wait after events +WAIT = FPS // 2 +FAST_CLICK = FPS // 6 AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -79,26 +84,48 @@ def click(self, x: int, y: int, wait_after: int = WAIT, wait_between: int = 2) - # --- Setup functions --- -def put_update_params(params: Params | None = None) -> None: - if params is None: - params = Params() - params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) - params.put("UpdaterTargetBranch", BRANCH_NAME) + +def set_prime_state(prime_type: PrimeType) -> None: + from openpilot.selfdrive.ui.ui_state import ui_state + ui_state.prime_state.set_type(prime_type) def setup_offroad_alerts() -> None: - put_update_params(Params()) set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C') set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal') set_offroad_alert("Offroad_IsTakingSnapshot", True) -def setup_update_available() -> None: +def setup_update_available(available: bool = True) -> None: params = Params() - params.put_bool("UpdateAvailable", True) - params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01") - put_update_params(params) + params.put_bool("UpdateAvailable", available) + params.put("UpdaterAvailableBranches", ",".join(["test-branch", "test-branch-2", BRANCH_NAME])) + if available: + params.put("UpdaterNewDescription", f"0.10.2 / {BRANCH_NAME} / 0a1b2c3 / Jan 01") + params.put("UpdaterNewReleaseNotes", parse_release_notes(BASEDIR)) + params.put("UpdaterTargetBranch", BRANCH_NAME) + else: + params.remove("UpdaterNewDescription") + params.remove("UpdaterNewReleaseNotes") + params.remove("UpdaterTargetBranch") + + +def setup_calibration_params() -> None: + params = Params() + # live calibration + calib = messaging.new_message('liveCalibration') + calib.liveCalibration.calStatus = log.LiveCalibrationData.Status.calibrated + calib.liveCalibration.rpyCalib = [0.0, math.radians(2.5), math.radians(-1.2)] + params.put("CalibrationParams", calib.to_bytes()) + # live delay + delay = messaging.new_message('liveDelay') + delay.liveDelay.calPerc = 75 + params.put("LiveDelay", delay.to_bytes()) + # live torque parameters + torque = messaging.new_message('liveTorqueParameters') + torque.liveTorqueParameters.useParams = True + torque.liveTorqueParameters.calPerc = 60 + params.put("LiveTorqueParameters", torque.to_bytes()) def setup_developer_params() -> None: @@ -132,7 +159,6 @@ def _send() -> None: def make_alert_setup(pm: PubMaster, size, text1, text2, status) -> Callable: def _send() -> None: - send_onroad(pm) alert = messaging.new_message('selfdriveState') ss = alert.selfdriveState ss.alertSize = size @@ -171,34 +197,104 @@ def setup(): return setup + def add_prime_state_setup(prime_type: PrimeType) -> None: + script.set_send(lambda: set_prime_state(prime_type)) + + def do_onboarding() -> None: + """Click through the training guide and close.""" + from openpilot.selfdrive.ui.layouts.onboarding import STEP_RECTS + step = 0 + for step_rect in STEP_RECTS: + if step < len(STEP_RECTS) - 1: + script.click(int(step_rect.x), int(step_rect.y), wait_after=FAST_CLICK) + else: + script.click(950, 900) # On the last step, click Finish instead of restart + step += 1 + + def type_keyboard() -> None: + """Types 8 characters using the big keyboard to test different layouts and interactions.""" + KEY = (150, 430) # e.g. 'Q' key + SHIFT = (150, 750) # also symbols key in number mode + NUMBERS = (150, 950) + SPACE = (1060, 950) + BACKSPACE = (2000, 780) + for key in [ + SHIFT, KEY, KEY, SHIFT, SHIFT, KEY, KEY, # test casing (upper, lower, caps lock) + SPACE, SPACE, BACKSPACE, BACKSPACE, # test multiple space and backspace + NUMBERS, KEY, KEY, SHIFT, KEY, KEY # test numbers and symbols + ]: + script.click(*key, wait_after=FAST_CLICK) + # TODO: Better way of organizing the events # === Homescreen === script.set_send(make_network_state_setup(pm, log.DeviceState.NetworkType.wifi)) - - # === Offroad Alerts (auto-transitions via HomeLayout refresh) === - script.setup(make_home_refresh_setup(setup_offroad_alerts)) + # Go through different prime state layouts + add_prime_state_setup(PrimeType.LITE) + add_prime_state_setup(PrimeType.NONE) + add_prime_state_setup(PrimeType.UNPAIRED) # === Update Available (auto-transitions via HomeLayout refresh) === script.setup(make_home_refresh_setup(setup_update_available)) - # === Settings - Device (click sidebar settings button) === + # === Offroad Alerts (auto-transitions via HomeLayout refresh, overrides update) === + script.setup(make_home_refresh_setup(setup_offroad_alerts)) + script.click(620, 950) # close alerts + + # === Settings (click sidebar settings button) === script.click(150, 90) - script.click(1985, 790) # reset calibration confirmation - script.click(650, 750) # cancel + + # === Settings - Device === + # pair device + script.click(2000, 450) # pair device + script.click(110, 110) # close pairing dialog + add_prime_state_setup(PrimeType.NONE) # changed from unpaired to hide pair device button + # calibration + script.setup(setup_calibration_params, wait_after=0) + script.click(1000, 620) # expand calibration description + script.click(2000, 620) # reset calibration confirmation + script.click(1500, 750) # confirm reset + script.click(1000, 620) # collapse calibration description + # training guide + script.click(2000, 800) # open training guide + do_onboarding() + # regulatory info + script.click(2000, 970) # regulatory button + script.click(2000, 970) # OK # === Settings - Network === script.click(278, 450) + # TODO: mock networks script.click(1880, 100) # advanced network settings - script.click(630, 80) # back + + # Keyboard (tethering password) + script.click(2000, 420, wait_after=FAST_CLICK) # open tether password keyboard + script.click(2000, 950, wait_after=FAST_CLICK) # click confirm (disabled, should not close) + script.click(2000, 115) # cancel (close without typing) + script.click(2000, 420, wait_after=FAST_CLICK) # open keyboard again + type_keyboard() # test various keyboard layouts and interactions + script.click(2050, 250, wait_after=FAST_CLICK) # toggle show/hide password + script.click(2000, 950) # confirm (close keyboard) + + script.click(630, 80) # back from advanced network # === Settings - Toggles === script.click(278, 600) - script.click(1200, 280) # experimental mode description + script.click(1200, 280) # expand experimental mode description # === Settings - Software === - script.setup(put_update_params, wait_after=0) - script.click(278, 720) + script.setup(lambda: setup_update_available(False), wait_after=0) # start with no update available + script.click(278, 720) # software + for _ in range(2): + script.click(720, 120) # toggle current release notes + script.setup(setup_update_available) # set update available + for _ in range(2): + script.click(720, 450) # toggle new release notes + script.click(2000, 630) # open select branch dialog + script.click(1000, 300) # select 1st option + script.click(1600, 900) # confirm selection + script.click(2000, 800) # uninstall + script.click(650, 750) # cancel uninstall # === Settings - Firehose === script.click(278, 845) @@ -206,13 +302,11 @@ def setup(): # === Settings - Developer (set CarParamsPersistent first) === script.setup(setup_developer_params, wait_after=0) script.click(278, 950) + script.click(1930, 470) # SSH keys (keyboard) + script.click(1930, 115) # click cancel on keyboard script.click(2000, 960) # toggle alpha long script.click(1500, 875) # confirm - # === Keyboard modal (SSH keys button in developer panel) === - script.click(1930, 470) # click SSH keys - script.click(1930, 115) # click cancel on keyboard - # === Close settings === script.click(250, 160) From d55ccba5fe7174c52ff3cbaa4b7c067f445da54e Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:42:38 -0500 Subject: [PATCH 535/685] clip: only fast rendering when headless (#37635) only set offscreen when headless --- tools/clip/run.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tools/clip/run.py b/tools/clip/run.py index 2deb95dede2..ed2a5075b4b 100755 --- a/tools/clip/run.py +++ b/tools/clip/run.py @@ -63,8 +63,10 @@ def parse_args(): return args -def setup_env(output_path: str, big: bool = False, speed: int = 1, target_mb: float = 0, duration: int = 0): - os.environ.update({"RECORD": "1", "OFFSCREEN": "1", "RECORD_OUTPUT": str(Path(output_path).with_suffix(".mp4"))}) +def setup_env(output_path: str, big: bool = False, speed: int = 1, target_mb: float = 0, duration: int = 0, headless: bool = True): + os.environ.update({"RECORD": "1", "RECORD_OUTPUT": str(Path(output_path).with_suffix(".mp4"))}) + if headless: + os.environ["OFFSCREEN"] = "1" if speed > 1: os.environ["RECORD_SPEED"] = str(speed) if target_mb > 0 and duration > 0: @@ -349,8 +351,9 @@ def main(): logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s\t%(message)s") args = parse_args() - setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start) - clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, not args.windowed, + headless = not args.windowed + setup_env(args.output, big=args.big, speed=args.speed, target_mb=args.file_size, duration=args.end - args.start, headless=headless) + clip(Route(args.route, data_dir=args.data_dir), args.output, args.start, args.end, headless, args.big, args.title, not args.no_metadata, not args.no_time_overlay, args.qcam) From 0ce679f687985bec6d10c57d8baa65ce9e8f6ee4 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:42:56 -0500 Subject: [PATCH 536/685] ui replay: Add progress bar (#37471) * add replay progress bar * simplify * use frames instead * update * disable in CI * +1 --- selfdrive/ui/tests/diff/replay.py | 57 ++++++++++++++++--------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay.py b/selfdrive/ui/tests/diff/replay.py index 49e17a6c2a9..b38026048dd 100755 --- a/selfdrive/ui/tests/diff/replay.py +++ b/selfdrive/ui/tests/diff/replay.py @@ -4,6 +4,7 @@ import coverage import pyray as rl +from tqdm import tqdm from typing import Literal from collections.abc import Callable from cereal.messaging import PubMaster @@ -75,33 +76,35 @@ def run_replay(variant: LayoutVariant) -> None: rl.get_time = lambda: frame / FPS # Main loop to replay events and render frames - for _ in gui_app.render(): - # Handle all events for the current frame - while script_index < len(script) and script[script_index][0] == frame: - _, event = script[script_index] - # Call setup function, if any - if event.setup: - event.setup() - # Send mouse events to the application - if event.mouse_events: - with gui_app._mouse._lock: - gui_app._mouse._events.extend(event.mouse_events) - # Update persistent send function - if event.send_fn is not None: - send_fn = event.send_fn - # Move to next script event - script_index += 1 - - # Keep sending cereal messages for persistent states (onroad, alerts) - if send_fn: - send_fn() - - ui_state.update() - - frame += 1 - - if script_index >= len(script): - break + with tqdm(total=script[-1][0] + 1, desc="Replaying", unit="frame", disable=bool(os.getenv("CI"))) as pbar: + for _ in gui_app.render(): + # Handle all events for the current frame + while script_index < len(script) and script[script_index][0] == frame: + _, event = script[script_index] + # Call setup function, if any + if event.setup: + event.setup() + # Send mouse events to the application + if event.mouse_events: + with gui_app._mouse._lock: + gui_app._mouse._events.extend(event.mouse_events) + # Update persistent send function + if event.send_fn is not None: + send_fn = event.send_fn + # Move to next script event + script_index += 1 + + # Keep sending cereal messages for persistent states (onroad, alerts) + if send_fn: + send_fn() + + ui_state.update() + + frame += 1 + pbar.update(1) + + if script_index >= len(script): + break gui_app.close() From d3bcc80d284fd5c72e40ed157758b0625010e550 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 10 Mar 2026 17:01:23 -0700 Subject: [PATCH 537/685] jenkins: push mici and tizi builds together --- Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 5c785f1d982..39175d89e37 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -167,7 +167,7 @@ node { env.GIT_COMMIT = checkout(scm).GIT_COMMIT def excludeBranches = ['__nightly', 'devel', 'devel-staging', 'release3', 'release3-staging', - 'release-tici', 'release-tizi', 'release-tizi-staging', 'testing-closet*', 'hotfix-*'] + 'release-tici', 'release-tizi', 'release-tizi-staging', 'release-mici-staging', 'testing-closet*', 'hotfix-*'] def excludeRegex = excludeBranches.join('|').replaceAll('\\*', '.*') if (env.BRANCH_NAME != 'master' && !env.BRANCH_NAME.contains('__jenkins_loop_')) { @@ -179,7 +179,7 @@ node { try { if (env.BRANCH_NAME == 'devel-staging') { deviceStage("build release-tizi-staging", "tizi-needs-can", [], [ - step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh"), + step("build release-tizi-staging", "RELEASE_BRANCH=release-tizi-staging $SOURCE_DIR/release/build_release.sh && git push -f origin release-tizi-staging:release-mici-staging"), ]) } From 3584523a93991f23f10e3795a2c25dcd814552ee Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 10 Mar 2026 20:27:40 -0700 Subject: [PATCH 538/685] fix process replay race on push (#37643) --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8add04c4654..f4be0ad5a62 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -164,7 +164,7 @@ jobs: echo "${{ github.sha }}" > ref_commit git add . git commit -m "process-replay refs for ${{ github.repository }}@${{ github.sha }}" || echo "No changes to commit" - git push origin process-replay + git push origin process-replay --force - name: Run regen if: false timeout-minutes: 4 From bea040095c75a1292541beff1e8e58df936d3996 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Mar 2026 20:44:56 -0700 Subject: [PATCH 539/685] Make sliders children --- selfdrive/ui/mici/widgets/dialog.py | 4 ++-- system/ui/mici_setup.py | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 94091b92692..1f67b987f1d 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -72,9 +72,9 @@ def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable[[], self._slider: BigSlider | RedBigSlider if red: - self._slider = RedBigSlider(title, icon, confirm_callback=self._on_confirm) + self._slider = self._child(RedBigSlider(title, icon, confirm_callback=self._on_confirm)) else: - self._slider = BigSlider(title, icon, confirm_callback=self._on_confirm) + self._slider = self._child(BigSlider(title, icon, confirm_callback=self._on_confirm)) self._slider.set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + NavWidget def _on_confirm(self): diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 616991b5604..f2e18cde71c 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -130,9 +130,9 @@ def __init__(self, use_openpilot_callback: Callable, use_custom_software_callback: Callable): super().__init__() - self._openpilot_slider = LargerSlider("slide to install\nopenpilot", use_openpilot_callback) + self._openpilot_slider = self._child(LargerSlider("slide to install\nopenpilot", use_openpilot_callback)) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False) + self._custom_software_slider = self._child(LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False)) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): @@ -478,7 +478,6 @@ def __init__(self): self._network_monitor.start() def getting_started_button_callback(): - self._software_selection_page.reset() gui_app.push_widget(self._software_selection_page) self._start_page = StartPage() From 50f0cf25a61c4b3db8ece03b808def4ae359e0a0 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Mar 2026 20:53:17 -0700 Subject: [PATCH 540/685] ui: slider shimmer sans shader (#37640) * actually epic * use child * inside label * revert other stuff * no reset_shimmer: bool * try 2 char * not worth dynamic chunking * bring back * rm * no emoji support on shimmer --- system/ui/widgets/label.py | 70 +++++++++++++++++++++++++++++++++++-- system/ui/widgets/slider.py | 31 +++++++++------- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/system/ui/widgets/label.py b/system/ui/widgets/label.py index 0b2444ec65d..7fe25ab51d1 100644 --- a/system/ui/widgets/label.py +++ b/system/ui/widgets/label.py @@ -1,3 +1,4 @@ +import math from enum import IntEnum from collections.abc import Callable from itertools import zip_longest @@ -241,6 +242,13 @@ class UnifiedLabel(Widget): - Proper multiline vertical alignment - Height calculation for layout purposes """ + # Shimmer constants + SHIMMER_BAND_WIDTH = 0.3 # shimmer width as fraction of text width + SHIMMER_BLUR_RADIUS = 0.12 # gaussian blur as fraction of text width + SHIMMER_CYCLE_PERIOD = 2.5 # seconds per full shimmer cycle + SHIMMER_SWEEP_FRACTION = 0.9 # fraction of cycle spent sweeping (rest is pause) + SHIMMER_LOW_OPACITY = 0.65 # text opacity at rest, shimmer brings to 1.0 + def __init__(self, text: str | Callable[[], str], font_size: int = DEFAULT_TEXT_SIZE, @@ -254,7 +262,8 @@ def __init__(self, wrap_text: bool = True, scroll: bool = False, line_height: float = 1.0, - letter_spacing: float = 0.0): + letter_spacing: float = 0.0, + shimmer: bool = False): super().__init__() self._text = text self._font_size = font_size @@ -272,6 +281,10 @@ def __init__(self, self._letter_spacing = letter_spacing # 0.1 = 10% self._spacing_pixels = font_size * letter_spacing + # Shimmer state + self._shimmer = shimmer + self._shimmer_start_time = 0.0 + # Scroll state self._scroll = scroll self._needs_scroll = False @@ -365,6 +378,15 @@ def reset_scroll(self): self._scroll_pause_t = None self._scroll_state = ScrollState.STARTING + def show_event(self): + super().show_event() + if self._shimmer: + self.reset_shimmer() + + def reset_shimmer(self, offset: float = 0.0): + """Reset shimmer animation timing.""" + self._shimmer_start_time = rl.get_time() + offset + def set_max_width(self, max_width: int | None): """Set the maximum width constraint for wrapping/eliding.""" if self._max_width != max_width: @@ -618,6 +640,24 @@ def _render(self, _): rl.end_scissor_mode() + def _shimmer_alpha(self, char_x: float, shimmer_left: float, shimmer_width: float) -> float: + """Compute shimmer opacity multiplier for a character at the given x position.""" + sigma = shimmer_width * self.SHIMMER_BLUR_RADIUS + if sigma <= 0: + return self.SHIMMER_LOW_OPACITY + + elapsed = rl.get_time() - self._shimmer_start_time + t_raw = (elapsed % self.SHIMMER_CYCLE_PERIOD) / self.SHIMMER_CYCLE_PERIOD + t_clamped = max(0.0, min(t_raw / self.SHIMMER_SWEEP_FRACTION, 1.0)) + t = t_clamped * t_clamped * (3.0 - 2.0 * t_clamped) # smoothstep + + margin = shimmer_width * self.SHIMMER_BAND_WIDTH + center = shimmer_left + shimmer_width + margin - t * (shimmer_width + 2.0 * margin) + + d = char_x - center + shimmer = math.exp(-0.5 * d * d / (sigma * sigma)) + return self.SHIMMER_LOW_OPACITY + (1.0 - self.SHIMMER_LOW_OPACITY) * shimmer + def _render_line(self, line, size, emojis, current_y, x_offset=0.0): # Calculate horizontal position if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_LEFT: @@ -630,7 +670,13 @@ def _render_line(self, line, size, emojis, current_y, x_offset=0.0): line_x = self._rect.x + self._text_padding line_x += self._scroll_offset + x_offset - # Render line with emojis + if self._shimmer: + self._render_line_shimmer(line, line_x, current_y) + else: + # Render line with emojis + self._render_line_normal(line, emojis, line_x, current_y) + + def _render_line_normal(self, line, emojis, line_x, current_y): line_pos = rl.Vector2(line_x, current_y) prev_index = 0 @@ -654,3 +700,23 @@ def _render_line(self, line, size, emojis, current_y, x_offset=0.0): text_after = line[prev_index:] if text_after: rl.draw_text_ex(self._font, text_after, line_pos, self._font_size, self._spacing_pixels, self._text_color) + + def _render_line_shimmer(self, line, line_x, current_y): + # Shimmer range based on widest line so sweep is even across all lines + max_width = self.text_width + if self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_RIGHT: + shimmer_left = self._rect.x + self._rect.width - self._text_padding - max_width + elif self._alignment == rl.GuiTextAlignment.TEXT_ALIGN_CENTER: + shimmer_left = self._rect.x + (self._rect.width - max_width) / 2 + else: + shimmer_left = self._rect.x + self._text_padding + + base_a = self._text_color.a / 255.0 + cursor_x = line_x + for ch in line: + char_width = measure_text_cached(self._font, ch, self._font_size, self._spacing_pixels).x + char_center_x = cursor_x + char_width / 2.0 + alpha = int(255 * self._shimmer_alpha(char_center_x, shimmer_left, max_width) * base_a) + color = rl.Color(self._text_color.r, self._text_color.g, self._text_color.b, alpha) + rl.draw_text_ex(self._font, ch, rl.Vector2(cursor_x, current_y), self._font_size, 0, color) + cursor_x += char_width + self._spacing_pixels diff --git a/system/ui/widgets/slider.py b/system/ui/widgets/slider.py index 7a10548acc7..bf965954f2e 100644 --- a/system/ui/widgets/slider.py +++ b/system/ui/widgets/slider.py @@ -19,9 +19,10 @@ class SliderBase(Widget, abc.ABC): _circle_bg_pressed_txt: rl.Texture _circle_arrow_txt: rl.Texture - def __init__(self, title: str, confirm_callback: Callable | None = None): + def __init__(self, title: str, confirm_callback: Callable | None = None, shimmer_offset: float = 0.0): super().__init__() self._confirm_callback = confirm_callback + self._shimmer_offset = shimmer_offset self._load_assets() @@ -39,9 +40,9 @@ def __init__(self, title: str, confirm_callback: Callable | None = None): self._is_dragging_circle = False - self._label = UnifiedLabel(title, font_size=36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, - alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9) + self._label = self._child(UnifiedLabel(title, font_size=36, font_weight=FontWeight.SEMI_BOLD, text_color=rl.WHITE, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, line_height=0.9, shimmer=True)) @abc.abstractmethod def _load_assets(self): @@ -51,12 +52,17 @@ def _load_assets(self): def confirmed(self) -> bool: return self._confirmed_time > 0.0 + def show_event(self): + super().show_event() + self.reset() + def reset(self): # reset all slider state self._is_dragging_circle = False self._circle_press_time = None self._confirmed_time = 0.0 self._confirm_callback_called = False + self._label.reset_shimmer(self._shimmer_offset) def set_opacity(self, opacity: float, smooth: bool = False): if smooth: @@ -123,8 +129,6 @@ def _update_state(self): self._scroll_x_circle_filter.x = self._scroll_x_circle def _render(self, _): - # TODO: iOS text shimmering animation - white = rl.Color(255, 255, 255, int(255 * self._opacity_filter.x)) bg_txt_x = self._rect.x + (self._rect.width - self._bg_txt.width) / 2 @@ -134,8 +138,9 @@ def _render(self, _): btn_x = bg_txt_x + self._bg_txt.width - self._circle_bg_txt.width + self._scroll_x_circle_filter.x btn_y = self._rect.y + (self._rect.height - self._circle_bg_txt.height) / 2 - if not self.confirmed: - self._label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.65 * (1.0 - self.slider_percentage) * self._opacity_filter.x))) + label_alpha = int(255 * (1.0 - self.slider_percentage) * self._opacity_filter.x) + if label_alpha > 0: + self._label.set_text_color(rl.Color(255, 255, 255, label_alpha)) label_rect = rl.Rectangle( self._rect.x + 20, self._rect.y, @@ -158,9 +163,9 @@ def _render(self, _): class LargerSlider(SliderBase): - def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True): + def __init__(self, title: str, confirm_callback: Callable | None = None, green: bool = True, shimmer_offset: float = 0.0): self._green = green - super().__init__(title, confirm_callback=confirm_callback) + super().__init__(title, confirm_callback=confirm_callback, shimmer_offset=shimmer_offset) def _load_assets(self): self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 115)) @@ -176,9 +181,9 @@ class BigSlider(SliderBase): def __init__(self, title: str, icon: rl.Texture, confirm_callback: Callable | None = None): self._icon = icon super().__init__(title, confirm_callback=confirm_callback) - self._label = UnifiedLabel(title, font_size=48, font_weight=FontWeight.DISPLAY, text_color=rl.Color(255, 255, 255, int(255 * 0.65)), - alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE, - line_height=0.875) + self._label.set_font_size(48) + self._label.set_font_weight(FontWeight.DISPLAY) + self._label.set_line_height(0.875) def _load_assets(self): self.set_rect(rl.Rectangle(0, 0, 520 + self.HORIZONTAL_PADDING * 2, 180)) From 18da21e65be6a1ea5619fd7c3a035a2cb268790e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 10 Mar 2026 23:26:39 -0700 Subject: [PATCH 541/685] Add shimmer offset for custom software --- system/ui/mici_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index f2e18cde71c..62d9dcd8274 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -132,7 +132,7 @@ def __init__(self, use_openpilot_callback: Callable, self._openpilot_slider = self._child(LargerSlider("slide to install\nopenpilot", use_openpilot_callback)) self._openpilot_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) - self._custom_software_slider = self._child(LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False)) + self._custom_software_slider = self._child(LargerSlider("slide to install\ncustom software", use_custom_software_callback, green=False, shimmer_offset=0.4)) self._custom_software_slider.set_enabled(lambda: self.enabled and not self.is_dismissing) def show_event(self): From 3469d9aadbc16eb854bad3d851bd580d2b8f132e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 00:05:04 -0700 Subject: [PATCH 542/685] AGNOS 17.2 (#37644) * 17.2 * 17.2 * new updater * shimmer offset --- launch_env.sh | 2 +- system/hardware/tici/agnos.json | 12 +++++----- system/hardware/tici/all-partitions.json | 28 ++++++++++++------------ system/hardware/tici/updater_magic | 4 ++-- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/launch_env.sh b/launch_env.sh index efdb4bf0e71..e409a80dd40 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -16,7 +16,7 @@ export VECLIB_MAXIMUM_THREADS=1 export QCOM_PRIORITY=12 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="17.1" + export AGNOS_VERSION="17.2" fi export STAGING_ROOT="/data/safe_staging" diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 58162fc3113..295b0279d95 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -67,17 +67,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img.xz", - "hash": "5d49176870f05328c9b4a934863644c2a9b59c993df97a9134ff63f7a4e1d81d", - "hash_raw": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img.xz", + "hash": "5f319030ad05942267b77f1a4686c4ca24cc09b2c2a4688e57342ffc9720fd49", + "hash_raw": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "ce18addaec7dd87a511fb7cb068500a1de5720fd76fceddf2944084b7d6fdf15", + "ondevice_hash": "c12f1b7d790a418aea17424accf4cd59c575e5745cad82bdc9452f384483648c", "alt": { - "hash": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", - "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img", + "hash": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img", "size": 4718592000 } } diff --git a/system/hardware/tici/all-partitions.json b/system/hardware/tici/all-partitions.json index 468142be601..0801907a2d9 100644 --- a/system/hardware/tici/all-partitions.json +++ b/system/hardware/tici/all-partitions.json @@ -350,40 +350,40 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img.xz", - "hash": "5d49176870f05328c9b4a934863644c2a9b59c993df97a9134ff63f7a4e1d81d", - "hash_raw": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img.xz", + "hash": "5f319030ad05942267b77f1a4686c4ca24cc09b2c2a4688e57342ffc9720fd49", + "hash_raw": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", "size": 4718592000, "sparse": true, "full_check": false, "has_ab": true, - "ondevice_hash": "ce18addaec7dd87a511fb7cb068500a1de5720fd76fceddf2944084b7d6fdf15", + "ondevice_hash": "c12f1b7d790a418aea17424accf4cd59c575e5745cad82bdc9452f384483648c", "alt": { - "hash": "8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f", - "url": "https://commadist.azureedge.net/agnosupdate/system-8014b6aab8ea2d76f7ea90e76efb9f94504495d29a37d281f0df903b3bdb630f.img", + "hash": "dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5", + "url": "https://commadist.azureedge.net/agnosupdate/system-dcdea6bd675d0276a63c25151727829620794baf42ada2e5e19a3f77b3f583a5.img", "size": 4718592000 } }, { "name": "userdata_90", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-24f985765b91593e468fa76333a994420a9f460b9a66368a8dbdcac6ee04d8f5.img.xz", - "hash": "f24ee158408d4b41a95c79361f442b5a3304c46e0872e387afddbdbb56d934ac", - "hash_raw": "24f985765b91593e468fa76333a994420a9f460b9a66368a8dbdcac6ee04d8f5", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_90-a7b25ea29255f4fd3a2da99e037f40b4ca10bd4afd57dd96563353b8dfb0f634.img.xz", + "hash": "7ea9d7d4685ec36bbfdf06afe0b51650d567416c3092fef96bd97158ed322742", + "hash_raw": "a7b25ea29255f4fd3a2da99e037f40b4ca10bd4afd57dd96563353b8dfb0f634", "size": 96636764160, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "9793fa1de3a22cedace28a43f001fbdc4fb73c81c949dc4ec4f5e60ce9eef0b6" + "ondevice_hash": "79ed653c1679d84b13ee23083a511b0e668454e4af9b0db99a3279072ed041c1" }, { "name": "userdata_89", - "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-01336e05d3f85a398f2255ecbd4ebbc14b7f688a33295390891ad90234db8f0b.img.xz", - "hash": "ec13a458352c8d3a974a97acc1aa5d7f1118ab59989d375e00d81c7091b75e8e", - "hash_raw": "01336e05d3f85a398f2255ecbd4ebbc14b7f688a33295390891ad90234db8f0b", + "url": "https://commadist.azureedge.net/agnosupdate/userdata_89-8e428632c967aa609cac184bff938a90240e53ffd3b4fca40bc94c33c81202ba.img.xz", + "hash": "7104cdb0384e4ecb1ebfa6136a2330251bc8aa829b9ec48c4b740f656252d382", + "hash_raw": "8e428632c967aa609cac184bff938a90240e53ffd3b4fca40bc94c33c81202ba", "size": 95563022336, "sparse": true, "full_check": true, "has_ab": false, - "ondevice_hash": "963c071664375b531bb867d83ffc9e2de7883408ca1481330a1ee4db1a964e68" + "ondevice_hash": "fbede3b0831dbc4a4edd336e5f547f4978902b9421fb1484e86c416192c59165" } ] \ No newline at end of file diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index f0689d50724..44b82d0c547 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:238d750f12c8b6758fd47998891d0cfa4bc52d72662801553c7d03952f8166b2 -size 24744419 +oid sha256:3a94ab8395f20d20a9d5a2a2bacca0694f072df8421cf13adca6250d28065bdc +size 24709205 From 4e239dbc22a22e425cd02162ee33f4eceea44c4e Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 11 Mar 2026 11:35:32 -0700 Subject: [PATCH 543/685] bump opendbc: in-memory DBC generation, drop scons build (#37646) --- .github/workflows/repo-maintenance.yaml | 1 - SConstruct | 1 - opendbc_repo | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 2c5d049e4c3..1ac6efb4f0a 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -68,7 +68,6 @@ jobs: git add . - name: update car docs run: | - scons -j$(nproc) --minimal opendbc_repo python selfdrive/car/docs.py git add docs/CARS.md - name: Create Pull Request diff --git a/SConstruct b/SConstruct index c6c86325602..6ec98b429e4 100644 --- a/SConstruct +++ b/SConstruct @@ -195,7 +195,6 @@ Export('common') env_swaglog = env.Clone() env_swaglog['CXXFLAGS'].append('-DSWAGLOG="\\"common/swaglog.h\\""') SConscript(['msgq_repo/SConscript'], exports={'env': env_swaglog}) -SConscript(['opendbc_repo/SConscript'], exports={'env': env_swaglog}) SConscript(['cereal/SConscript']) diff --git a/opendbc_repo b/opendbc_repo index ffe10c0c809..ddeba888a3d 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit ffe10c0c809cc48726292c832b109a14fee603be +Subproject commit ddeba888a3d03b32269f0c780490ad8578917527 From 58d6211bc27cc88066ca65ede50812e621f8aae9 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 18:58:51 -0700 Subject: [PATCH 544/685] ui: no int textures (#37649) * no int textures * round qr code * unround firehose * ignore here --- pyproject.toml | 1 + selfdrive/ui/layouts/onboarding.py | 2 +- selfdrive/ui/layouts/sidebar.py | 8 ++--- selfdrive/ui/mici/layouts/home.py | 2 +- selfdrive/ui/mici/layouts/offroad_alerts.py | 4 +-- .../ui/mici/layouts/settings/firehose.py | 12 +++---- .../mici/layouts/settings/network/wifi_ui.py | 2 +- selfdrive/ui/mici/onroad/alert_renderer.py | 4 +-- .../ui/mici/onroad/augmented_road_view.py | 2 +- selfdrive/ui/mici/onroad/driver_state.py | 17 +++++----- selfdrive/ui/mici/onroad/hud_renderer.py | 2 +- selfdrive/ui/mici/widgets/dialog.py | 34 +++++++++---------- selfdrive/ui/mici/widgets/pairing_dialog.py | 2 +- selfdrive/ui/onroad/exp_button.py | 2 +- system/ui/tici_setup.py | 2 +- system/ui/widgets/button.py | 6 ++-- system/ui/widgets/layouts.py | 2 +- system/ui/widgets/list_view.py | 2 +- system/ui/widgets/mici_keyboard.py | 2 +- system/ui/widgets/nav_widget.py | 8 ++--- system/ui/widgets/scroller.py | 4 +-- tools/replay/ui.py | 8 ++--- 22 files changed, 64 insertions(+), 64 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 408513600ff..bf841613179 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -204,6 +204,7 @@ lint.flake8-implicit-str-concat.allow-multiline = false "pyray.is_mouse_button_pressed".msg = "This can miss events. Use Widget._handle_mouse_press" "pyray.is_mouse_button_released".msg = "This can miss events. Use Widget._handle_mouse_release" "pyray.draw_text".msg = "Use a function (such as rl.draw_font_ex) that takes font as an argument" +"pyray.draw_texture".msg = "Use rl.draw_texture_ex for float position support" [tool.ruff.format] quote-style = "preserve" diff --git a/selfdrive/ui/layouts/onboarding.py b/selfdrive/ui/layouts/onboarding.py index 25294511d4d..37205b0e26a 100644 --- a/selfdrive/ui/layouts/onboarding.py +++ b/selfdrive/ui/layouts/onboarding.py @@ -91,7 +91,7 @@ def _update_state(self): def _render(self, _): # Safeguard against fast tapping step = min(self._step, len(self._textures) - 1) - rl.draw_texture(self._textures[step], 0, 0, rl.WHITE) + rl.draw_texture_ex(self._textures[step], rl.Vector2(0, 0), 0.0, 1.0, rl.WHITE) # progress bar if 0 < step < len(STEP_RECTS) - 1: diff --git a/selfdrive/ui/layouts/sidebar.py b/selfdrive/ui/layouts/sidebar.py index 050cd795bff..a7f3a46279b 100644 --- a/selfdrive/ui/layouts/sidebar.py +++ b/selfdrive/ui/layouts/sidebar.py @@ -161,14 +161,14 @@ def _draw_buttons(self, rect: rl.Rectangle): # Settings button settings_down = mouse_down and rl.check_collision_point_rec(mouse_pos, SETTINGS_BTN) tint = Colors.BUTTON_PRESSED if settings_down else Colors.BUTTON_NORMAL - rl.draw_texture(self._settings_img, int(SETTINGS_BTN.x), int(SETTINGS_BTN.y), tint) + rl.draw_texture_ex(self._settings_img, rl.Vector2(SETTINGS_BTN.x, SETTINGS_BTN.y), 0.0, 1.0, tint) # Home/Flag button flag_pressed = mouse_down and rl.check_collision_point_rec(mouse_pos, HOME_BTN) button_img = self._flag_img if ui_state.started else self._home_img tint = Colors.BUTTON_PRESSED if (ui_state.started and flag_pressed) else Colors.BUTTON_NORMAL - rl.draw_texture(button_img, int(HOME_BTN.x), int(HOME_BTN.y), tint) + rl.draw_texture_ex(button_img, rl.Vector2(HOME_BTN.x, HOME_BTN.y), 0.0, 1.0, tint) # Microphone button if self._recording_audio: @@ -178,8 +178,8 @@ def _draw_buttons(self, rect: rl.Rectangle): bg_color = rl.Color(Colors.DANGER.r, Colors.DANGER.g, Colors.DANGER.b, int(255 * 0.65)) if mic_pressed else Colors.DANGER rl.draw_rectangle_rounded(self._mic_indicator_rect, 1, 10, bg_color) - rl.draw_texture(self._mic_img, int(self._mic_indicator_rect.x + (self._mic_indicator_rect.width - self._mic_img.width) / 2), - int(self._mic_indicator_rect.y + (self._mic_indicator_rect.height - self._mic_img.height) / 2), Colors.WHITE) + rl.draw_texture_ex(self._mic_img, rl.Vector2(self._mic_indicator_rect.x + (self._mic_indicator_rect.width - self._mic_img.width) / 2, + self._mic_indicator_rect.y + (self._mic_indicator_rect.height - self._mic_img.height) / 2), 0.0, 1.0, Colors.WHITE) def _draw_network_indicator(self, rect: rl.Rectangle): # Signal strength dots diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index da5b0ac5e05..77b665f5d8b 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -77,7 +77,7 @@ def _render(self, _): # Offset by difference in height between slashless and slash icons to make center align match draw_y -= (self._wifi_slash_txt.height - self._wifi_none_txt.height) / 2 - rl.draw_texture(draw_net_txt, int(draw_x), int(draw_y), rl.Color(255, 255, 255, int(255 * 0.9))) + rl.draw_texture_ex(draw_net_txt, rl.Vector2(draw_x, draw_y), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.9))) class MiciHomeLayout(Widget): diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index 5ccb815da65..0dae5d20759 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -144,7 +144,7 @@ def _render(self, _): bg_texture = self._bg_small_pressed if self.is_pressed else self._bg_small # Draw background - rl.draw_texture(bg_texture, int(self._rect.x), int(self._rect.y), rl.WHITE) + rl.draw_texture_ex(bg_texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, 1.0, rl.WHITE) # Calculate text area (left side, avoiding icon on right) title_width = self.ALERT_WIDTH - (self.ALERT_PADDING * 2) - self.ICON_SIZE - self.ICON_MARGIN @@ -183,7 +183,7 @@ def _render(self, _): icon_texture = self._icon_orange icon_x = self._rect.x + self.ALERT_WIDTH - self.ALERT_PADDING - self.ICON_SIZE icon_y = self._rect.y + self.ALERT_PADDING - rl.draw_texture(icon_texture, int(icon_x), int(icon_y), rl.WHITE) + rl.draw_texture_ex(icon_texture, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.WHITE) class MiciOffroadAlerts(Scroller): diff --git a/selfdrive/ui/mici/layouts/settings/firehose.py b/selfdrive/ui/mici/layouts/settings/firehose.py index e5b6301acfd..4c27a909f96 100644 --- a/selfdrive/ui/mici/layouts/settings/firehose.py +++ b/selfdrive/ui/mici/layouts/settings/firehose.py @@ -81,12 +81,12 @@ def _get_segment_count(self) -> int: def _render(self, rect: rl.Rectangle): # compute total content height for scrolling content_height = self._measure_content_height(rect) - scroll_offset = round(self._scroll_panel.update(rect, content_height)) + scroll_offset = self._scroll_panel.update(rect, content_height) # start drawing with offset - x = int(rect.x + 40) - y = int(rect.y + 40 + scroll_offset) - w = int(rect.width - 80) + x = rect.x + 40 + y = rect.y + 40 + scroll_offset + w = rect.width - 80 # Title title_text = tr(TITLE) @@ -100,7 +100,7 @@ def _render(self, rect: rl.Rectangle): y += 20 # Separator - rl.draw_rectangle(x, y, w, 2, self.GRAY) + rl.draw_rectangle_rec(rl.Rectangle(x, y, w, 2), self.GRAY) y += 20 # Status @@ -116,7 +116,7 @@ def _render(self, rect: rl.Rectangle): y += 20 # Separator - rl.draw_rectangle(x, y, w, 2, self.GRAY) + rl.draw_rectangle_rec(rl.Rectangle(x, y, w, 2), self.GRAY) y += 20 # Instructions intro diff --git a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py index 10d828fbd8a..006027e258d 100644 --- a/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py +++ b/selfdrive/ui/mici/layouts/settings/network/wifi_ui.py @@ -165,7 +165,7 @@ def _draw_content(self, btn_y: float): if self._is_connected and not self._network_forgetting: check_y = int(label_y - sub_label_height + (sub_label_height - self._check_txt.height) / 2) - rl.draw_texture(self._check_txt, int(sub_label_x), check_y, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))) + rl.draw_texture_ex(self._check_txt, rl.Vector2(sub_label_x, check_y), 0.0, 1.0, rl.Color(255, 255, 255, int(255 * 0.9 * 0.65))) sub_label_x += self._check_txt.width + 14 sub_label_rect = rl.Rectangle(sub_label_x, label_y - sub_label_height, sub_label_w, sub_label_height) diff --git a/selfdrive/ui/mici/onroad/alert_renderer.py b/selfdrive/ui/mici/onroad/alert_renderer.py index 1c1fd8404f7..7b006aaaeaa 100644 --- a/selfdrive/ui/mici/onroad/alert_renderer.py +++ b/selfdrive/ui/mici/onroad/alert_renderer.py @@ -258,8 +258,8 @@ def _draw_icons(self, alert_layout: AlertLayout) -> None: else: icon_alpha = int(min(self._turn_signal_alpha_filter.x, 255)) - rl.draw_texture(alert_layout.icon.texture, pos_x, int(self._rect.y + alert_layout.icon.margin_y), - rl.Color(255, 255, 255, int(icon_alpha * self._alpha_filter.x))) + rl.draw_texture_ex(alert_layout.icon.texture, rl.Vector2(pos_x, self._rect.y + alert_layout.icon.margin_y), 0.0, 1.0, + rl.Color(255, 255, 255, int(icon_alpha * self._alpha_filter.x))) def _draw_background(self, alert: Alert) -> None: # draw top gradient for alert text at top diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 2c083e84b98..09d5d57fbba 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -126,7 +126,7 @@ def _render(self, _): if self._offset_filter.x > 0: icon_x = self.rect.x + self.rect.width - round(self._offset_filter.x) icon_y = self.rect.y + (self.rect.height - self._icon.height) / 2 # Vertically centered - rl.draw_texture(self._icon, int(icon_x), int(icon_y), rl.WHITE) + rl.draw_texture_ex(self._icon, rl.Vector2(icon_x, icon_y), 0.0, 1.0, rl.WHITE) class AugmentedRoadView(CameraView): diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index 356d7ac832c..92ff07c1e9b 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -61,7 +61,7 @@ def load_icons(self): self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", cone_and_person_size, cone_and_person_size) center_size = round(36 / self.BASE_SIZE * self._rect.width) self._dm_center = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_center.png", center_size, center_size) - self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", self._rect.width, self._rect.height) + self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", int(self._rect.width), int(self._rect.height)) def set_should_draw(self, should_draw: bool): self._should_draw = should_draw @@ -88,15 +88,14 @@ def _render(self, _): if DEBUG: rl.draw_rectangle_lines_ex(self._rect, 1, rl.RED) - rl.draw_texture(self._dm_background, - int(self._rect.x), - int(self._rect.y), - rl.Color(255, 255, 255, int(255 * self._fade_filter.x))) + rl.draw_texture_ex(self._dm_background, + rl.Vector2(self._rect.x, self._rect.y), 0.0, 1.0, + rl.Color(255, 255, 255, int(255 * self._fade_filter.x))) - rl.draw_texture(self._dm_person, - int(self._rect.x + (self._rect.width - self._dm_person.width) / 2), - int(self._rect.y + (self._rect.height - self._dm_person.height) / 2), - rl.Color(255, 255, 255, int(255 * 0.9 * self._fade_filter.x))) + rl.draw_texture_ex(self._dm_person, + rl.Vector2(self._rect.x + (self._rect.width - self._dm_person.width) / 2, + self._rect.y + (self._rect.height - self._dm_person.height) / 2), 0.0, 1.0, + rl.Color(255, 255, 255, int(255 * 0.9 * self._fade_filter.x))) if self.effective_active: source_rect = rl.Rectangle(0, 0, self._dm_cone.width, self._dm_cone.height) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 21e30e74263..35d04fe702f 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -219,7 +219,7 @@ def _draw_steering_wheel(self, rect: rl.Rectangle) -> None: EXCLAMATION_POINT_SPACING = 10 exclamation_pos_x = pos_x - self._txt_exclamation_point.width / 2 + wheel_txt.width / 2 + EXCLAMATION_POINT_SPACING exclamation_pos_y = pos_y - self._txt_exclamation_point.height / 2 - rl.draw_texture(self._txt_exclamation_point, int(exclamation_pos_x), int(exclamation_pos_y), rl.WHITE) + rl.draw_texture_ex(self._txt_exclamation_point, rl.Vector2(exclamation_pos_x, exclamation_pos_y), 0.0, 1.0, rl.WHITE) def _draw_set_speed(self, rect: rl.Rectangle) -> None: """Draw the MAX speed indicator box.""" diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 1f67b987f1d..396ea35cc8d 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -44,20 +44,20 @@ def _render(self, _): title_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.BOLD), self._title, 50, int(max_width))) title_size = measure_text_cached(gui_app.font(FontWeight.BOLD), title_wrapped, 50) text_x_offset = 0 - title_rect = rl.Rectangle(int(self._rect.x + text_x_offset + PADDING), - int(self._rect.y + PADDING), - int(max_width), - int(title_size.y)) + title_rect = rl.Rectangle(self._rect.x + text_x_offset + PADDING, + self._rect.y + PADDING, + max_width, + title_size.y) gui_label(title_rect, title_wrapped, 50, font_weight=FontWeight.BOLD, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) # draw description desc_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.MEDIUM), self._description, 30, int(max_width))) desc_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), desc_wrapped, 30) - desc_rect = rl.Rectangle(int(self._rect.x + text_x_offset + PADDING), - int(self._rect.y + self._rect.height / 3), - int(max_width), - int(desc_size.y)) + desc_rect = rl.Rectangle(self._rect.x + text_x_offset + PADDING, + self._rect.y + self._rect.height / 3, + max_width, + desc_size.y) # TODO: text align doesn't seem to work properly with newlines gui_label(desc_rect, desc_wrapped, 30, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) @@ -156,9 +156,9 @@ def _render(self, _): bg_block_margin = 5 text_x = PADDING / 2 + self._enter_img.width + PADDING - text_field_rect = rl.Rectangle(text_x, int(self._rect.y + PADDING) - bg_block_margin, - int(self._rect.width - text_x * 2), - int(text_size.y)) + text_field_rect = rl.Rectangle(text_x, self._rect.y + PADDING - bg_block_margin, + self._rect.width - text_x * 2, + text_size.y) # draw text input # push text left with a gradient on left side if too long @@ -179,8 +179,8 @@ def _render(self, _): # draw gradient on left side to indicate more text if text_size.x > text_field_rect.width: - rl.draw_rectangle_gradient_h(int(text_field_rect.x), int(text_field_rect.y), 80, int(text_field_rect.height), - rl.BLACK, rl.BLANK) + rl.draw_rectangle_gradient_ex(rl.Rectangle(text_field_rect.x, text_field_rect.y, 80, text_field_rect.height), + rl.BLACK, rl.BLANK, rl.BLANK, rl.BLACK) # draw cursor blink_alpha = (math.sin(rl.get_time() * 6) + 1) / 2 @@ -188,14 +188,14 @@ def _render(self, _): cursor_x = min(text_x + text_size.x + 3, text_field_rect.x + text_field_rect.width) else: cursor_x = text_field_rect.x - 6 - rl.draw_rectangle_rounded(rl.Rectangle(int(cursor_x), int(text_field_rect.y), 4, int(text_size.y)), + rl.draw_rectangle_rounded(rl.Rectangle(cursor_x, text_field_rect.y, 4, text_size.y), 1, 4, rl.Color(255, 255, 255, int(255 * blink_alpha))) # draw backspace icon with nice fade self._backspace_img_alpha.update(255 * bool(text)) if self._backspace_img_alpha.x > 1: color = rl.Color(255, 255, 255, int(self._backspace_img_alpha.x)) - rl.draw_texture(self._backspace_img, int(self._rect.width - self._backspace_img.width - 27), int(self._rect.y + 14), color) + rl.draw_texture_ex(self._backspace_img, rl.Vector2(self._rect.width - self._backspace_img.width - 27, self._rect.y + 14), 0.0, 1.0, color) if not text and self._hint_label.text and not candidate_char: # draw description if no text entered yet and not drawing candidate char @@ -213,9 +213,9 @@ def _render(self, _): # draw enter button self._enter_img_alpha.update(255 if len(text) >= self._minimum_length else 0) color = rl.Color(255, 255, 255, int(self._enter_img_alpha.x)) - rl.draw_texture(self._enter_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) + rl.draw_texture_ex(self._enter_img, rl.Vector2(self._rect.x + PADDING / 2, self._rect.y), 0.0, 1.0, color) color = rl.Color(255, 255, 255, 255 - int(self._enter_img_alpha.x)) - rl.draw_texture(self._enter_disabled_img, int(self._rect.x + PADDING / 2), int(self._rect.y), color) + rl.draw_texture_ex(self._enter_disabled_img, rl.Vector2(self._rect.x + PADDING / 2, self._rect.y), 0.0, 1.0, color) # keyboard goes over everything self._keyboard.render(self._rect) diff --git a/selfdrive/ui/mici/widgets/pairing_dialog.py b/selfdrive/ui/mici/widgets/pairing_dialog.py index 8421b516d24..a18b26ec024 100644 --- a/selfdrive/ui/mici/widgets/pairing_dialog.py +++ b/selfdrive/ui/mici/widgets/pairing_dialog.py @@ -92,7 +92,7 @@ def _render_qr_code(self) -> None: return scale = self._rect.height / self._qr_texture.height - pos = rl.Vector2(self._rect.x + 8, self._rect.y) + pos = rl.Vector2(round(self._rect.x + 8), round(self._rect.y)) rl.draw_texture_ex(self._qr_texture, pos, 0.0, scale, rl.WHITE) def __del__(self): diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py index e5d81714130..9a92ebc3c35 100644 --- a/selfdrive/ui/onroad/exp_button.py +++ b/selfdrive/ui/onroad/exp_button.py @@ -50,7 +50,7 @@ def _render(self, rect: rl.Rectangle) -> None: texture = self._txt_exp if self._held_or_actual_mode() else self._txt_wheel rl.draw_circle(center_x, center_y, self._rect.width / 2, self._black_bg) - rl.draw_texture(texture, center_x - texture.width // 2, center_y - texture.height // 2, self._white_color) + rl.draw_texture_ex(texture, rl.Vector2(center_x - texture.width / 2, center_y - texture.height / 2), 0.0, 1.0, self._white_color) def _held_or_actual_mode(self): now = time.monotonic() diff --git a/system/ui/tici_setup.py b/system/ui/tici_setup.py index f98ab5ffaa1..9eefb6af537 100755 --- a/system/ui/tici_setup.py +++ b/system/ui/tici_setup.py @@ -183,7 +183,7 @@ def _network_setup_continue_button_callback(self): self.state = SetupState.CUSTOM_SOFTWARE def render_low_voltage(self, rect: rl.Rectangle): - rl.draw_texture(self.warning, int(rect.x + 150), int(rect.y + 110), rl.WHITE) + rl.draw_texture_ex(self.warning, rl.Vector2(rect.x + 150, rect.y + 110), 0.0, 1.0, rl.WHITE) self._low_voltage_title_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 100, rect.width - 500 - 150, TITLE_FONT_SIZE * FONT_SCALE)) self._low_voltage_body_label.render(rl.Rectangle(rect.x + 150, rect.y + 110 + 150 + 150, rect.width - 500, BODY_FONT_SIZE * FONT_SCALE * 3)) diff --git a/system/ui/widgets/button.py b/system/ui/widgets/button.py index 60f9e60735b..36ef3bedab6 100644 --- a/system/ui/widgets/button.py +++ b/system/ui/widgets/button.py @@ -191,7 +191,7 @@ def _render(self, rect: rl.Rectangle): color = rl.Color(255, 255, 255, int(255 * 0.9 * 0.35 * self._opacity_filter.x)) draw_x = rect.x + (rect.width - self._texture.width) / 2 draw_y = rect.y + (rect.height - self._texture.height) / 2 - rl.draw_texture(self._texture, int(draw_x), int(draw_y), color) + rl.draw_texture_ex(self._texture, rl.Vector2(draw_x, draw_y), 0.0, 1.0, color) class SmallCircleIconButton(Widget): @@ -219,7 +219,7 @@ def _render(self, _): bg_txt = self._icon_bg_pressed_txt if self.is_pressed else self._icon_bg_txt icon_white = white - rl.draw_texture(bg_txt, int(self.rect.x), int(self.rect.y), white) + rl.draw_texture_ex(bg_txt, rl.Vector2(self.rect.x, self.rect.y), 0.0, 1.0, white) icon_x = self.rect.x + (self.rect.width - self._icon_txt.width) / 2 icon_y = self.rect.y + (self.rect.height - self._icon_txt.height) / 2 - rl.draw_texture(self._icon_txt, int(icon_x), int(icon_y), icon_white) + rl.draw_texture_ex(self._icon_txt, rl.Vector2(icon_x, icon_y), 0.0, 1.0, icon_white) diff --git a/system/ui/widgets/layouts.py b/system/ui/widgets/layouts.py index 6fd3bffd8c9..6bbc49e9275 100644 --- a/system/ui/widgets/layouts.py +++ b/system/ui/widgets/layouts.py @@ -54,6 +54,6 @@ def _render(self, _): y = self._rect.y + (self._rect.height - widget.rect.height) / 2 # Update widget position and render - widget.set_position(round(x), round(y)) + widget.set_position(x, y) widget.set_parent_rect(self._rect) widget.render() diff --git a/system/ui/widgets/list_view.py b/system/ui/widgets/list_view.py index 1cf530a66aa..82613c37c8e 100644 --- a/system/ui/widgets/list_view.py +++ b/system/ui/widgets/list_view.py @@ -355,7 +355,7 @@ def _render(self, _): if self.title: # Draw icon if present if self.icon: - rl.draw_texture(self._icon_texture, int(content_x), int(self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.height) // 2), rl.WHITE) + rl.draw_texture_ex(self._icon_texture, rl.Vector2(content_x, self._rect.y + (ITEM_BASE_HEIGHT - self._icon_texture.height) / 2), 0.0, 1.0, rl.WHITE) text_x += ICON_SIZE + ITEM_PADDING # Draw main text diff --git a/system/ui/widgets/mici_keyboard.py b/system/ui/widgets/mici_keyboard.py index 74f1f563463..75a3c29e6b3 100644 --- a/system/ui/widgets/mici_keyboard.py +++ b/system/ui/widgets/mici_keyboard.py @@ -95,7 +95,7 @@ def set_font_size(self, size: float): self._size_filter.update(size) def _get_font_size(self) -> int: - return int(round(self._size_filter.x)) + return round(self._size_filter.x) class SmallKey(Key): diff --git a/system/ui/widgets/nav_widget.py b/system/ui/widgets/nav_widget.py index 6f2bbd025b8..11770bbe5de 100644 --- a/system/ui/widgets/nav_widget.py +++ b/system/ui/widgets/nav_widget.py @@ -148,7 +148,7 @@ def _update_state(self): if self._playing_dismiss_animation: new_y = self._rect.height + DISMISS_PUSH_OFFSET - new_y = round(self._y_pos_filter.update(new_y)) + new_y = self._y_pos_filter.update(new_y) if abs(new_y) < 1 and abs(self._y_pos_filter.velocity.x) < 0.5: new_y = self._y_pos_filter.x = 0.0 self._y_pos_filter.velocity.x = 0.0 @@ -176,10 +176,10 @@ def _update_state(self): def _layout(self): # Dim whatever is behind this widget, fading with position (runs after _update_state so position is correct) overlay_alpha = int(200 * max(0.0, min(1.0, 1.0 - self._rect.y / self._rect.height))) if self._rect.height > 0 else 0 - rl.draw_rectangle(0, 0, int(self._rect.width), int(self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) + rl.draw_rectangle_rec(rl.Rectangle(0, 0, self._rect.width, self._rect.height), rl.Color(0, 0, 0, overlay_alpha)) bounce_height = 20 - rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.width), int(self._rect.height + bounce_height), rl.BLACK) + rl.draw_rectangle_rec(rl.Rectangle(self._rect.x, self._rect.y, self._rect.width, self._rect.height + bounce_height), rl.BLACK) def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: ret = super().render(rect) @@ -196,7 +196,7 @@ def render(self, rect: rl.Rectangle | None = None) -> bool | int | None: else: self._nav_bar_y_filter.update(NAV_BAR_MARGIN) - self._nav_bar.set_position(bar_x, round(self._nav_bar_y_filter.x)) + self._nav_bar.set_position(bar_x, self._nav_bar_y_filter.x) self._nav_bar.render() return ret diff --git a/system/ui/widgets/scroller.py b/system/ui/widgets/scroller.py index 65f23739c1f..a3a0d2b38f4 100644 --- a/system/ui/widgets/scroller.py +++ b/system/ui/widgets/scroller.py @@ -190,7 +190,7 @@ def _get_scroll(self, visible_items: list[Widget], content_size: float) -> float self.scroll_panel.set_enabled(scroll_enabled and self.enabled and not self._scrolling_to[1]) self.scroll_panel.update(self._rect, content_size) if not self._snap_items: - return round(self.scroll_panel.get_offset()) + return self.scroll_panel.get_offset() # Snap closest item to center center_pos = self._rect.x + self._rect.width / 2 if self._horizontal else self._rect.y + self._rect.height / 2 @@ -341,7 +341,7 @@ def _layout(self): x, y = self._do_move_animation(item, x, y) # Update item state - item.set_position(round(x), round(y)) # round to prevent jumping when settling + item.set_position(x, y) item.set_parent_rect(self._rect) def _render_item(self, item: Widget): diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 8707f2be99c..7fe2f405a16 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -218,7 +218,7 @@ def ui_thread(addr): # Update camera texture from numpy array img_rgba = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA) rl.update_texture(camera_texture, rl.ffi.cast("void *", img_rgba.ctypes.data)) - rl.draw_texture(camera_texture, 0, 0, rl.WHITE) + rl.draw_texture(camera_texture, 0, 0, rl.WHITE) # noqa: TID251 # display alerts rl.draw_text_ex(font, sm['selfdriveState'].alertText1, rl.Vector2(180, 150), 30, 0, rl.RED) @@ -227,15 +227,15 @@ def ui_thread(addr): # draw plots (texture is reused internally) plot_texture = draw_plots(plot_arr) if hor_mode: - rl.draw_texture(plot_texture, 640 + 384, 0, rl.WHITE) + rl.draw_texture(plot_texture, 640 + 384, 0, rl.WHITE) # noqa: TID251 else: - rl.draw_texture(plot_texture, 0, 600, rl.WHITE) + rl.draw_texture(plot_texture, 0, 600, rl.WHITE) # noqa: TID251 # Convert lid_overlay to RGBA and update top_down texture # lid_overlay is (384, 960), need to transpose to (960, 384) for row-major RGBA buffer lid_rgba = palette[lid_overlay.T] rl.update_texture(top_down_texture, rl.ffi.cast("void *", np.ascontiguousarray(lid_rgba).ctypes.data)) - rl.draw_texture(top_down_texture, 640, 0, rl.WHITE) + rl.draw_texture(top_down_texture, 640, 0, rl.WHITE) # noqa: TID251 SPACING = 25 lines = [ From 7dfb7967b63c378211cb45013ed7bbae856bb5f8 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 19:51:34 -0700 Subject: [PATCH 545/685] ui: proper mici scaling (#37652) * scale * remove low res image finder * check self scale * simplify --- system/ui/lib/application.py | 40 +++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index ddcde014968..07a0169ccbb 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -1,5 +1,6 @@ import atexit import cffi +import math import os import queue import time @@ -278,7 +279,7 @@ def _close(sig, frame): if self._scale != 1.0: rl.set_mouse_scale(1 / self._scale, 1 / self._scale) if needs_render_texture: - self._render_texture = rl.load_render_texture(self._width, self._height) + self._render_texture = rl.load_render_texture(self._scaled_width, self._scaled_height) rl.set_texture_filter(self._render_texture.texture, rl.TextureFilter.TEXTURE_FILTER_BILINEAR) if RECORD: @@ -289,7 +290,7 @@ def _close(sig, frame): '-nostats', # Suppress encoding progress '-f', 'rawvideo', # Input format '-pix_fmt', 'rgba', # Input pixel format - '-s', f'{self._width}x{self._height}', # Input resolution + '-s', f'{self._scaled_width}x{self._scaled_height}', # Input resolution '-r', str(fps), # Input frame rate '-i', 'pipe:0', # Input from stdin '-vf', 'vflip,format=yuv420p', # Flip vertically and convert to yuv420p @@ -319,6 +320,7 @@ def _close(sig, frame): self._set_styles() self._load_fonts() self._patch_text_functions() + self._patch_scissor_mode() if BURN_IN_MODE and self._burn_in_shader is None: self._burn_in_shader = rl.load_shader_from_memory(BURN_IN_VERTEX_SHADER, BURN_IN_FRAGMENT_SHADER) @@ -454,6 +456,12 @@ def texture(self, asset_path: str, width: int | None = None, height: int | None with as_file(ASSETS_DIR.joinpath(asset_path)) as fspath: image_obj = self._load_image_from_path(fspath.as_posix(), width, height, alpha_premultiply, keep_aspect_ratio, flip_x) texture_obj = self._load_texture_from_image(image_obj) + + # Set logical size so widget layout math stays at 1x coordinates + if self._scale != 1.0 and width is not None and height is not None: + texture_obj.width = width + texture_obj.height = height + self._textures[cache_key] = texture_obj return texture_obj @@ -465,6 +473,11 @@ def _load_image_from_path(self, image_path: str, width: int | None = None, heigh if alpha_premultiply: rl.image_alpha_premultiply(image) + # Scale up load size for sharper rendering, capped at source resolution + if self._scale != 1.0 and width is not None and height is not None: + width = min(int(width * self._scale), image.width) + height = min(int(height * self._scale), image.height) + if width is not None and height is not None: same_dimensions = image.width == width and image.height == height @@ -588,6 +601,10 @@ def render(self): rl.begin_drawing() rl.clear_background(rl.BLACK) + if self._scale != 1.0: + rl.rl_push_matrix() + rl.rl_scalef(self._scale, self._scale, 1.0) + # Allow a Widget to still run a function regardless of the stack depth for tick in self._nav_stack_ticks: tick() @@ -598,11 +615,14 @@ def render(self): yield True + if self._scale != 1.0: + rl.rl_pop_matrix() + if self._render_texture: rl.end_texture_mode() rl.begin_drawing() rl.clear_background(rl.BLACK) - src_rect = rl.Rectangle(0, 0, float(self._width), -float(self._height)) + src_rect = rl.Rectangle(0, 0, float(self._scaled_width), -float(self._scaled_height)) dst_rect = rl.Rectangle(0, 0, float(self._scaled_width), float(self._scaled_height)) texture = self._render_texture.texture if texture: @@ -679,6 +699,20 @@ def _draw_text_ex_scaled(font, text, position, font_size, spacing, tint): rl.draw_text_ex = _draw_text_ex_scaled + def _patch_scissor_mode(self): + if self._scale == 1.0: + return + + if not hasattr(rl, "_orig_begin_scissor_mode"): + rl._orig_begin_scissor_mode = rl.begin_scissor_mode + + def _begin_scissor_mode_scaled(x, y, width, height): + return rl._orig_begin_scissor_mode( + int(x * self._scale), int(y * self._scale), + int(math.ceil(width * self._scale)), int(math.ceil(height * self._scale))) + + rl.begin_scissor_mode = _begin_scissor_mode_scaled + def _set_log_callback(self): ffi_libc = cffi.FFI() ffi_libc.cdef(""" From c631a22eb6f01adcae677503835b9f45dfb36c0a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 23:19:02 -0700 Subject: [PATCH 546/685] ui: fix 1px flash at bottom of DM camera during onboarding swipe (#37653) Co-authored-by: Claude Opus 4.6 --- selfdrive/ui/mici/layouts/onboarding.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 781f60ee216..35db4412d52 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -151,8 +151,10 @@ def _update_state(self): def _render(self, _): self._dialog.render(self._rect) - rl.draw_rectangle_gradient_v(int(self._rect.x), int(self._rect.y + self._rect.height - 80), - int(self._rect.width), 80, rl.BLANK, rl.BLACK) + gradient_y = int(self._rect.y + self._rect.height - 80) + gradient_h = int(self._rect.y) + int(self._rect.height) - gradient_y + rl.draw_rectangle_gradient_v(int(self._rect.x), gradient_y, + int(self._rect.width), gradient_h, rl.BLANK, rl.BLACK) # draw white ring around dm icon to indicate progress ring_thickness = 8 From 6e7587a75ce6d0c38d2e281e643e001c5cd41454 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 23:35:56 -0700 Subject: [PATCH 547/685] modeld: quiet do_chunk output during scons build (#37654) * modeld: quiet do_chunk output during scons build SCons default-prints Python function actions with all their args. The do_chunk function has 1259 tinygrad source files as deps, causing a wall of text during builds. Wrap in SAction with a short strfunction. Co-Authored-By: Claude Opus 4.6 * split compile and chunk into separate Commands cleaner fix: do_chunk only depends on the pkl, not tinygrad files Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- selfdrive/modeld/SConscript | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 95ac06bb1af..3f38324a6e9 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -45,13 +45,17 @@ def tg_compile(flags, model_name): pkl = fn + "_tinygrad.pkl" onnx_path = fn + ".onnx" chunk_targets = get_chunk_paths(pkl, estimate_pickle_max_size(os.path.getsize(onnx_path))) + compile_node = lenv.Command( + pkl, + [onnx_path] + tinygrad_files + [chunker_file], + f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', + ) def do_chunk(target, source, env): chunk_file(pkl, chunk_targets) return lenv.Command( chunk_targets, - [onnx_path] + tinygrad_files + [chunker_file], - [f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', - do_chunk] + compile_node, + do_chunk, ) # Compile small models From 9bcd965f0ba44ccc7fd67d26b0df72203d3e0ec4 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 11 Mar 2026 23:38:51 -0700 Subject: [PATCH 548/685] ui: don't load unused light font --- system/ui/lib/application.py | 1 - 1 file changed, 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 07a0169ccbb..4fbfbc0de2f 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -95,7 +95,6 @@ class FontWeight(StrEnum): - LIGHT = "Inter-Light.fnt" NORMAL = "Inter-Regular.fnt" if BIG_UI else "Inter-Medium.fnt" MEDIUM = "Inter-Medium.fnt" BOLD = "Inter-Bold.fnt" From d8ae8c201a635195b9113ddd8263309e068c5474 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Mar 2026 00:15:14 -0700 Subject: [PATCH 549/685] onboarding: block back (#37655) no back from onboarding --- selfdrive/ui/mici/layouts/onboarding.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 35db4412d52..6f35370dc5a 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -258,9 +258,11 @@ def __init__(self, continue_callback: Callable[[], None]): class TrainingGuide(NavWidget): - def __init__(self, completed_callback: Callable[[], None]): + def __init__(self, completed_callback: Callable[[], None], block_back: bool = False): super().__init__() + self._block_back = block_back + self._steps = [ TrainingGuideAttentionNotice(continue_callback=lambda: gui_app.push_widget(self._steps[1])), TrainingGuidePreDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[2])), @@ -271,6 +273,14 @@ def __init__(self, completed_callback: Callable[[], None]): self._child(self._steps[0]) self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack + def _back_enabled(self) -> bool: + return not self._block_back + + def show_event(self): + super().show_event() + if self._block_back: + self._nav_bar._alpha = 0.0 + def _render(self, _): self._steps[0].render(self._rect) @@ -314,7 +324,8 @@ class TermsPage(Scroller): def __init__(self, on_accept, on_decline): super().__init__() - self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept) + self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept, + exit_on_confirm=False) self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, red=True, exit_on_confirm=False) @@ -349,7 +360,7 @@ def __init__(self, completed_callback: Callable[[], None]): # Windows self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_uninstall) self._terms.set_enabled(lambda: self.enabled) # for nav stack - self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) + self._training_guide = TrainingGuide(completed_callback=self._on_completed_training, block_back=True) self._training_guide.set_enabled(lambda: self.enabled) # for nav stack def _on_uninstall(self): From 2b0aab3a38f71a32023ec50ebf02e8fbbc4bd85b Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Mar 2026 01:47:20 -0700 Subject: [PATCH 550/685] ui: round QR code draw position in onboarding (#37656) Co-authored-by: Claude Opus 4.6 --- selfdrive/ui/mici/layouts/onboarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index 6f35370dc5a..e19ac9dcf19 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -313,7 +313,7 @@ def _generate_qr(self, url: str): def _render(self, _): if self._qr_texture: scale = self._size / self._qr_texture.height - rl.draw_texture_ex(self._qr_texture, rl.Vector2(self._rect.x, self._rect.y), 0.0, scale, rl.WHITE) + rl.draw_texture_ex(self._qr_texture, rl.Vector2(round(self._rect.x), round(self._rect.y)), 0.0, scale, rl.WHITE) def __del__(self): if self._qr_texture and self._qr_texture.id != 0: From bbed1a2551d625dfc1eef66a4293b31f5d37c473 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Mar 2026 02:55:56 -0700 Subject: [PATCH 551/685] scroll: use iOS-style weighted velocity averaging for fling (#37659) * scroll: use iOS-style weighted velocity averaging for fling Weight older velocity samples more heavily on finger release to produce more consistent fling velocities. The last touch samples before lift are noisy (finger decelerating, rotating, jittering), so we trust the earlier steadier samples more: 60% oldest, 35% middle, 5% newest. Reverse-engineered from iOS UIScrollView by the Flutter team. Co-Authored-By: Claude Opus 4.6 * Update system/ui/lib/application.py * Apply suggestions from code review --------- Co-authored-by: Claude Opus 4.6 --- system/ui/lib/application.py | 4 ++++ system/ui/lib/scroll_panel2.py | 25 ++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 4fbfbc0de2f..5d40abdc143 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -169,6 +169,10 @@ def _run_thread(self): self._rk.keep_time() def _handle_mouse_event(self): + # TODO: read touch events from evdev directly to get real kernel timestamps. + # Polling at 140Hz with time.monotonic() causes timing jitter that makes scroll + # velocity oscillate (alternating high/low). Real timestamps would also let us + # detect swipe-stop-lift via event gaps instead of the fragile decel heuristic. for slot in range(MAX_TOUCH_SLOTS): mouse_pos = rl.get_touch_position(slot) x = mouse_pos.x / self._scale if self._scale != 1.0 else mouse_pos.x diff --git a/system/ui/lib/scroll_panel2.py b/system/ui/lib/scroll_panel2.py index e2a548ba261..18fd8a9a671 100644 --- a/system/ui/lib/scroll_panel2.py +++ b/system/ui/lib/scroll_panel2.py @@ -20,6 +20,21 @@ DEBUG = os.getenv("DEBUG_SCROLL", "0") == "1" +# Weights older (steadier) velocity samples more heavily on release. +# Finger-lift samples are noisy; trusting earlier samples gives consistent fling velocity. +# Reverse-engineered from iOS UIScrollView (tuned at 120Hz touch) by Flutter team: +# https://github.com/flutter/flutter/pull/60501 +# 3 samples ≈ 25ms at 120Hz (iOS) / ~21ms at 140Hz (comma). Scale if touch rate changes. +def weighted_velocity(buffer: deque) -> float: + if len(buffer) >= 3: + return buffer[-3] * 0.6 + buffer[-2] * 0.35 + buffer[-1] * 0.05 + elif len(buffer) == 2: + return buffer[-2] * 0.7 + buffer[-1] * 0.3 + elif len(buffer) == 1: + return buffer[-1] + return 0.0 + + # from https://ariya.io/2011/10/flick-list-with-its-momentum-scrolling-and-deceleration class ScrollState(Enum): STEADY = 0 @@ -151,7 +166,13 @@ def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, bou # Touch rejection: when releasing finger after swiping and stopping, panel # reports a few erroneous touch events with high velocity, try to ignore. - # If velocity decelerates very quickly, assume user doesn't intend to auto scroll + # If velocity decelerates very quickly, assume user doesn't intend to auto scroll. + # Catches two cases: 1) swipe, stop finger, then lift (stale high velocity in buffer) + # 2) dirty finger lift where finger rotates/slides producing spurious velocity spike. + # TODO: this heuristic false-positives on fast swipes because 140Hz touch polling + # jitter causes velocity to oscillate (not real deceleration). Better approaches: + # - Use evdev kernel timestamps to eliminate velocity oscillation at the source + # - Replace with a time-since-last-event check (40ms timeout) for swipe-stop-lift high_decel = False if len(self._velocity_buffer) > 2: # We limit max to first half since final few velocities can surpass first few @@ -166,6 +187,8 @@ def _handle_mouse_event(self, mouse_event: MouseEvent, bounds: rl.Rectangle, bou print('deceleration too high, going to STEADY') high_decel = True + self._velocity = weighted_velocity(self._velocity_buffer) + # If final velocity is below some threshold, switch to steady state too low_speed = abs(self._velocity) <= MIN_VELOCITY_FOR_CLICKING * 1.5 # plus some margin From d0375942b8cc49722a3ad846e9f3d1a6822cb90e Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 12 Mar 2026 20:03:22 -0700 Subject: [PATCH 552/685] Revert "onboarding: block back" (#37663) Revert "onboarding: block back (#37655)" This reverts commit d8ae8c201a635195b9113ddd8263309e068c5474. --- selfdrive/ui/mici/layouts/onboarding.py | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index e19ac9dcf19..b918bf6ef60 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -258,11 +258,9 @@ def __init__(self, continue_callback: Callable[[], None]): class TrainingGuide(NavWidget): - def __init__(self, completed_callback: Callable[[], None], block_back: bool = False): + def __init__(self, completed_callback: Callable[[], None]): super().__init__() - self._block_back = block_back - self._steps = [ TrainingGuideAttentionNotice(continue_callback=lambda: gui_app.push_widget(self._steps[1])), TrainingGuidePreDMTutorial(continue_callback=lambda: gui_app.push_widget(self._steps[2])), @@ -273,14 +271,6 @@ def __init__(self, completed_callback: Callable[[], None], block_back: bool = Fa self._child(self._steps[0]) self._steps[0].set_enabled(lambda: self.enabled and not self.is_dismissing) # for nav stack - def _back_enabled(self) -> bool: - return not self._block_back - - def show_event(self): - super().show_event() - if self._block_back: - self._nav_bar._alpha = 0.0 - def _render(self, _): self._steps[0].render(self._rect) @@ -324,8 +314,7 @@ class TermsPage(Scroller): def __init__(self, on_accept, on_decline): super().__init__() - self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept, - exit_on_confirm=False) + self._accept_button = BigConfirmationCircleButton("accept\nterms", gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), on_accept) self._decline_button = BigConfirmationCircleButton("decline &\nuninstall", gui_app.texture("icons_mici/setup/cancel.png", 64, 64), on_decline, red=True, exit_on_confirm=False) @@ -360,7 +349,7 @@ def __init__(self, completed_callback: Callable[[], None]): # Windows self._terms = TermsPage(on_accept=self._on_terms_accepted, on_decline=self._on_uninstall) self._terms.set_enabled(lambda: self.enabled) # for nav stack - self._training_guide = TrainingGuide(completed_callback=self._on_completed_training, block_back=True) + self._training_guide = TrainingGuide(completed_callback=self._on_completed_training) self._training_guide.set_enabled(lambda: self.enabled) # for nav stack def _on_uninstall(self): From 5908b7cda00a0d79453cf876b5dc867397a23b85 Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Thu, 12 Mar 2026 22:09:10 -0500 Subject: [PATCH 553/685] ui replay: add mici UI exploration (#37641) * replay: add dragging gesture support * update dragging to support distance and duration; update mici script to go through settings * refactor * fix and add network * add more * interact device * fix * match statements * more * improve * simplify script * add keyboard test * format * simplify * improve * comment * improve * clarify * clean * simplify * simplify * move * improve * more delay * simplify keyboard test * simplify * comment * add onroad alert tests to mici * scroll less * test offroad alerts * remove space * scroll faster * more toggle tests * back to home * test settings onroad * fix pairing qr code * add replay progress bar * add replay progress bar * simplify * correct comment * remove _ * we don't need this * change click * add return types * fast typing * use frames instead * use frames instead * update * disable in CI * +1 * fix script * refactor how mici replay script cases are built * refactor * refactor: rename helper function for exploring settings in build_mici_script * remove onroad settings check * refactor * simplify * refactor: use explore_setting in more places to reduce duplication * add type * refactor: simplify explore_cases function by removing swipe_wait parameter * add case to open wifi selection * refactor: enhance run_actions to support after_each callback for interaction tests; rename explore_cases to scroll_through_cases * add review training guide * update comment * comments * comment * fix swipe back --- selfdrive/ui/tests/diff/replay_script.py | 226 ++++++++++++++++++++--- 1 file changed, 203 insertions(+), 23 deletions(-) diff --git a/selfdrive/ui/tests/diff/replay_script.py b/selfdrive/ui/tests/diff/replay_script.py index c43442a33d3..c53d2f116bf 100644 --- a/selfdrive/ui/tests/diff/replay_script.py +++ b/selfdrive/ui/tests/diff/replay_script.py @@ -15,9 +15,16 @@ from openpilot.system.updated.updated import parse_release_notes # Default frames to wait after events -WAIT = FPS // 2 +WAIT_LONG = FPS +WAIT_SHORT = FPS // 2 FAST_CLICK = FPS // 6 +# Direction vectors for drag gestures +DIR_LEFT = (-1, 0) +DIR_RIGHT = (1, 0) +DIR_UP = (0, -1) +DIR_DOWN = (0, 1) + AlertSize = log.SelfdriveState.AlertSize AlertStatus = log.SelfdriveState.AlertStatus @@ -61,26 +68,47 @@ def wait(self, frames: int) -> None: """Add a delay for the given number of frames followed by an empty event.""" self.add(ScriptEvent(), before=frames) - def setup(self, fn: Callable, wait_after: int = WAIT) -> None: + def setup(self, fn: Callable, wait_after: int = WAIT_SHORT) -> None: """Add a setup function to be called immediately followed by a delay of the given number of frames.""" self.add(ScriptEvent(setup=fn), after=wait_after) - def set_send(self, fn: Callable, wait_after: int = WAIT) -> None: + def set_send(self, fn: Callable, wait_after: int = WAIT_SHORT) -> None: """Set a new persistent send function to be called every frame.""" self.add(ScriptEvent(send_fn=fn), after=wait_after) - # TODO: Also add more complex gestures, like swipe or drag - def click(self, x: int, y: int, wait_after: int = WAIT, wait_between: int = 2) -> None: + def click(self, x: int, y: int, wait_after: int = WAIT_SHORT, wait_between: int = 2) -> None: """Add a click event to the script for the given position and specify frames to wait between mouse events or after the click.""" # NOTE: By default we wait a couple frames between mouse events so pressed states will be rendered from openpilot.system.ui.lib.application import MouseEvent, MousePos - # TODO: Add support for long press (left_down=True) mouse_down = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=True, left_released=False, left_down=False, t=self.get_frame_time()) self.add(ScriptEvent(mouse_events=[mouse_down]), after=wait_between) mouse_up = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=True, left_down=False, t=self.get_frame_time()) self.add(ScriptEvent(mouse_events=[mouse_up]), after=wait_after) + def drag(self, start_x: int, start_y: int, direction: tuple[int, int], distance: int, duration_frames: int, wait_after: int = WAIT_LONG) -> None: + """Add a drag gesture to the script from start position in the specified direction by the given distance over the given number of frames.""" + from openpilot.system.ui.lib.application import MouseEvent, MousePos + + # Calculate delta and end position based on direction and distance + delta_x, delta_y = direction[0] * distance, direction[1] * distance + end_x, end_y = start_x + delta_x, start_y + delta_y + + # Mouse down at start + mouse_down = MouseEvent(pos=MousePos(start_x, start_y), slot=0, left_pressed=True, left_released=False, left_down=True, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_down]), after=1) + + # Interpolate positions over duration_frames + for i in range(1, duration_frames): + t = i / duration_frames + x, y = int(start_x + delta_x * t), int(start_y + delta_y * t) + mouse_move = MouseEvent(pos=MousePos(x, y), slot=0, left_pressed=False, left_released=False, left_down=True, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_move]), after=1) + + # Mouse up at end + mouse_up = MouseEvent(pos=MousePos(end_x, end_y), slot=0, left_pressed=False, left_released=True, left_down=False, t=self.get_frame_time()) + self.add(ScriptEvent(mouse_events=[mouse_up]), after=wait_after) + # --- Setup functions --- @@ -169,18 +197,181 @@ def _send() -> None: return _send +def test_onroad_alerts(script: Script, pm: PubMaster) -> None: + """Go through various alert types and sizes and add them to the script to test alert rendering. + Each alert is sent as a separate event with a delay in between.""" + # Small alert (normal) + script.set_send(make_alert_setup(pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)) + # Medium alert (userPrompt) + script.set_send(make_alert_setup(pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)) + # Full alert (critical) + script.set_send(make_alert_setup(pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)) + # Full alert multiline + script.set_send(make_alert_setup(pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)) + # Full alert long text + script.set_send(make_alert_setup(pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)) + + # --- Script builders --- def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None: """Build the replay script for the mici layout.""" from openpilot.system.ui.lib.application import gui_app - center = (gui_app.width // 2, gui_app.height // 2) + width, height = gui_app.width, gui_app.height + center = (width // 2, height // 2) + right = (width * 4 // 5, height // 2) + left = (width // 5, height // 2) + top = (width // 2, height // 10) + bottom = (width // 2, height * 9 // 10) + + DURATION = 5 + SWIPE_WAIT = FPS * 3 // 4 + + def click(times: int = 1, wait_after: int = WAIT_SHORT) -> None: + """Click at the center of the screen the given number of times with optional delay after.""" + for _ in range(times): + script.click(*center, wait_after=wait_after) + + def press(x: int, y: int, duration_frames: int = DURATION, wait_after: int = WAIT_SHORT) -> None: + """Perform a drag with no movement to simulate a left_down mouse event at the given position for the specified duration and delay after.""" + script.drag(x, y, (0, 0), 0, duration_frames, wait_after=wait_after) + + def swipe_left(distance: int = right[0] - left[0], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from right edge to left (scroll right / slide confirmation).""" + script.drag(*right, DIR_LEFT, distance, duration_frames, wait_after) + + def swipe_right(distance: int = right[0] - left[0], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from left edge to right (scroll left).""" + script.drag(*left, DIR_RIGHT, distance, duration_frames, wait_after) + + def swipe_down(distance: int = bottom[1] - top[1], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from top edge to bottom (scroll up / go back).""" + script.drag(*top, DIR_DOWN, distance, duration_frames, wait_after) + + def swipe_up(distance: int = bottom[1] - top[1], duration_frames: int = DURATION, wait_after: int = SWIPE_WAIT) -> None: + """Drag from bottom edge to top (scroll down).""" + script.drag(*bottom, DIR_UP, distance, duration_frames, wait_after) + + ActionFn = Callable[[], None] | None + Cases = list[ActionFn] + + def run_actions(*actions: ActionFn, after_each: ActionFn = None) -> None: + """Helper function to run a sequence of actions in order for interaction tests, calling after_each callback after each action if provided.""" + for action in actions: + if action is not None: + action() + if after_each is not None: + after_each() + + def explore_setting(*actions: ActionFn) -> None: + """Helper function to open a settings item, run the given actions, and go back.""" + run_actions(click, *actions, swipe_down) # open, interact, go back + + def scroll_through_cases(cases: Cases) -> None: + """Helper function to explore a panel by calling the interaction callbacks for each item/page before swiping to the next one.""" + run_actions(*cases, after_each=lambda: swipe_left(210, 10)) # swipe to roughly the center of the next toggle after each case + + def interact_keyboard() -> None: + """Interact with the keyboard in various ways to test different actions and states. + Assumes it's a password keyboard with 8 characters required. Closes by pressing confirm at the end.""" + KEY = (250, 160) # key in the middle of the keyboard ('G') + SHIFT = (50, 210) + NUMBERS = (480, 210) + SPACE = (500, 160) + BACKSPACE = (490, 30) + CONFIRM = (50, 30) + # Begin interactions + press(*CONFIRM, wait_after=FAST_CLICK) # confirm while disabled should do nothing + swipe_left(duration_frames=FPS // 2) # swipe to type + swipe_up(duration_frames=FPS // 2) # swipe out of keyboard (nothing typed) + # press various keys to test different states: + for key in [ + SHIFT, KEY, KEY, SHIFT, SHIFT, KEY, KEY, # test casing (upper, lower, caps lock) + SPACE, SPACE, BACKSPACE, BACKSPACE, # test multiple space and backspace + NUMBERS, KEY, center, SHIFT, KEY # test numbers and symbols + ]: + press(*key, wait_after=FAST_CLICK) + # press confirm to close + script.wait(WAIT_SHORT) # wait for confirm to enable + press(*CONFIRM) + + toggle_cases: Cases = [ + lambda: click(times=3, wait_after=FAST_CLICK), # first toggle is personality, which has 3 states + None, None, None, None, None, None, # skip other toggles to save time + lambda: click(times=2, wait_after=FAST_CLICK), # test final toggle (enable openpilot) + ] + + network_cases: Cases = [ + explore_setting, # select wifi (just open and close) + None, None, + lambda: run_actions(click, interact_keyboard), # tether password keyboard + ] + + device_cases: Cases = [ + None, + click, # update + explore_setting, # pairing (just open and close) + lambda: explore_setting( + # training guide + lambda: swipe_left(width * 2), click, # first page, click next + lambda: swipe_left(width * 2), swipe_down # second page, go back (TODO: make driver cam preview work) + ), + None, # TODO: preview driver camera; enabling this causes MultiplePublishersError later in onroad alert tests + lambda: explore_setting(swipe_left), # terms & conditions (swipe to view QR code) + lambda: explore_setting(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # regulatory info + lambda: run_actions(click, lambda: swipe_left(width)), # reset calibration confirm (goes back automatically) + lambda: explore_setting(lambda: swipe_left(width)), # uninstall + lambda: run_actions( + lambda: explore_setting(lambda: swipe_left(width)), # reboot + lambda: script.click(430, 120), lambda: swipe_left(width), swipe_down, # shutdown + ), + ] + + developer_cases: Cases = [ + lambda: click(times=2, wait_after=FAST_CLICK), # toggle ssh mode + explore_setting, # SSH keys keyboard (just open and close) + None, # joystick mode + lambda: click(wait_after=FAST_CLICK), # longitudinal maneuver mode (disabled; should do nothing) + lambda: click(times=2, wait_after=FAST_CLICK), # toggle UI debug mode + ] + + settings_cases: Cases = [ + lambda: scroll_through_cases(toggle_cases), + lambda: scroll_through_cases(network_cases), + lambda: scroll_through_cases(device_cases), + lambda: script.wait(WAIT_SHORT), # pairing + lambda: run_actions(lambda: swipe_up(height * 3), lambda: swipe_down(height * 3)), # firehose (scroll down and back up) + lambda: scroll_through_cases(developer_cases), + ] + + # === Homescreen === # + script.wait(WAIT_SHORT) + swipe_left(width, wait_after=WAIT_SHORT) # onroad screen + swipe_right(width, wait_after=WAIT_SHORT) # back to home + + # === Offroad Alerts === + def setup_offroad_alerts_and_refresh() -> None: + """Setup function to trigger offroad alerts and force a refresh on the alerts layout.""" + setup_offroad_alerts() + main_layout._alerts_layout.refresh() + + swipe_right(width, wait_after=WAIT_SHORT) # open alerts + script.setup(setup_offroad_alerts_and_refresh) # show alerts + swipe_up(height) # scroll alerts + swipe_left(width, wait_after=WAIT_SHORT) # close alerts + + # === Settings === # + click() # open settings + scroll_through_cases([lambda case=case: explore_setting(case) for case in settings_cases]) # explore settings + swipe_down() # back to home + + # === Onroad === + script.set_send(lambda: send_onroad(pm)) + swipe_left(width, wait_after=WAIT_SHORT) # onroad screen + test_onroad_alerts(script, pm) + swipe_right(width) # back to home - # TODO: Explore more - script.wait(FPS) - script.click(*center, FPS) # Open settings - script.click(*center, FPS) # Open toggles script.end() @@ -313,18 +504,7 @@ def type_keyboard() -> None: # === Onroad === script.set_send(lambda: send_onroad(pm)) script.click(1000, 500) # click onroad to toggle sidebar - - # === Onroad alerts === - # Small alert (normal) - script.set_send(make_alert_setup(pm, AlertSize.small, "Small Alert", "This is a small alert", AlertStatus.normal)) - # Medium alert (userPrompt) - script.set_send(make_alert_setup(pm, AlertSize.mid, "Medium Alert", "This is a medium alert", AlertStatus.userPrompt)) - # Full alert (critical) - script.set_send(make_alert_setup(pm, AlertSize.full, "DISENGAGE IMMEDIATELY", "Driver Distracted", AlertStatus.critical)) - # Full alert multiline - script.set_send(make_alert_setup(pm, AlertSize.full, "Reverse\nGear", "", AlertStatus.normal)) - # Full alert long text - script.set_send(make_alert_setup(pm, AlertSize.full, "TAKE CONTROL IMMEDIATELY", "Calibration Invalid: Remount Device & Recalibrate", AlertStatus.userPrompt)) + test_onroad_alerts(script, pm) # End script.end() From 2cc70ef2e4c3d0f7109342e119c67dafcb6f329c Mon Sep 17 00:00:00 2001 From: David <49467229+TheSecurityDev@users.noreply.github.com> Date: Fri, 13 Mar 2026 18:34:22 -0500 Subject: [PATCH 554/685] record: smaller clip sizes by adjusting preset (#37666) use veryfast instead of ultrafast --- system/ui/lib/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 5d40abdc143..1d518309d3e 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -299,7 +299,7 @@ def _close(sig, frame): '-vf', 'vflip,format=yuv420p', # Flip vertically and convert to yuv420p '-r', str(output_fps), # Output frame rate (for speed multiplier) '-c:v', 'libx264', - '-preset', 'ultrafast', + '-preset', 'veryfast', '-crf', str(RECORD_QUALITY) ] if RECORD_BITRATE: From 06630e8a398d9491d173fb4e70a19a58f41c8a2a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 13 Mar 2026 19:20:02 -0700 Subject: [PATCH 555/685] setup: remove brew (#37669) --- tools/setup_dependencies.sh | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 0b785bf4a2e..8132cd16dc1 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -113,24 +113,12 @@ function install_python_deps() { source .venv/bin/activate } -function install_macos_deps() { - if ! command -v brew > /dev/null 2>&1; then - echo "homebrew not found, skipping macOS system dependency install" - return 0 - fi - - if ! command -v cmake > /dev/null 2>&1; then - brew install cmake - fi -} - # --- Main --- if [[ "$OSTYPE" == "linux-gnu"* ]]; then install_ubuntu_deps echo "[ ] installed system dependencies t=$SECONDS" elif [[ "$OSTYPE" == "darwin"* ]]; then - install_macos_deps if [[ $SHELL == "/bin/zsh" ]]; then RC_FILE="$HOME/.zshrc" elif [[ $SHELL == "/bin/bash" ]]; then From ee9da82aab4bfa59af65a5ce00604b4b2073786b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 13 Mar 2026 19:20:33 -0700 Subject: [PATCH 556/685] cleanup build paths (#37667) * cleanup build paths * not used * lil more * rm those too * rm * lil more --- SConstruct | 28 ++++++------------- common/SConscript | 2 +- selfdrive/SConscript | 6 ---- selfdrive/ui/SConscript | 62 ++++++++++++++++++++--------------------- tools/cabana/SConscript | 4 +-- 5 files changed, 41 insertions(+), 61 deletions(-) delete mode 100644 selfdrive/SConscript diff --git a/SConstruct b/SConstruct index 6ec98b429e4..0a4a3701a64 100644 --- a/SConstruct +++ b/SConstruct @@ -15,9 +15,6 @@ Decider('MD5-timestamp') SetOption('num_jobs', max(1, int(os.cpu_count()/2))) -AddOption('--asan', action='store_true', help='turn on ASAN') -AddOption('--ubsan', action='store_true', help='turn on UBSan') -AddOption('--mutation', action='store_true', help='generate mutation-ready code') AddOption('--ccflags', action='store', type='string', default='', help='pass arbitrary flags over the command line') AddOption('--verbose', action='store_true', default=False, help='show full build commands') AddOption('--minimal', @@ -99,8 +96,6 @@ if arch == "larch64": env["CC"] = "clang" env["CXX"] = "clang++" env.Append(LIBPATH=[ - "/usr/local/lib", - "/system/vendor/lib64", "/usr/lib/aarch64-linux-gnu", ]) arch_flags = ["-D__TICI__", "-mcpu=cortex-a57"] @@ -112,19 +107,6 @@ elif arch == "Darwin": ]) env.Append(CCFLAGS=["-DGL_SILENCE_DEPRECATION"]) env.Append(CXXFLAGS=["-DGL_SILENCE_DEPRECATION"]) -else: - env.Append(LIBPATH=[ - "/usr/lib", - "/usr/local/lib", - ]) - -# Sanitizers and extra CCFLAGS from CLI -if GetOption('asan'): - env.Append(CCFLAGS=["-fsanitize=address", "-fno-omit-frame-pointer"]) - env.Append(LINKFLAGS=["-fsanitize=address"]) -elif GetOption('ubsan'): - env.Append(CCFLAGS=["-fsanitize=undefined"]) - env.Append(LINKFLAGS=["-fsanitize=undefined"]) _extra_cc = shlex.split(GetOption('ccflags') or '') if _extra_cc: @@ -220,7 +202,15 @@ if arch == "larch64": # Build openpilot SConscript(['third_party/SConscript']) -SConscript(['selfdrive/SConscript']) +# Build selfdrive +SConscript([ + 'selfdrive/pandad/SConscript', + 'selfdrive/controls/lib/lateral_mpc_lib/SConscript', + 'selfdrive/controls/lib/longitudinal_mpc_lib/SConscript', + 'selfdrive/locationd/SConscript', + 'selfdrive/modeld/SConscript', + 'selfdrive/ui/SConscript', +]) if Dir('#tools/cabana/').exists() and arch != "larch64": SConscript(['tools/cabana/SConscript']) diff --git a/common/SConscript b/common/SConscript index 15a0e5eff15..c9bd1c72d13 100644 --- a/common/SConscript +++ b/common/SConscript @@ -1,4 +1,4 @@ -Import('env', 'envCython', 'arch') +Import('env', 'envCython') common_libs = [ 'params.cc', diff --git a/selfdrive/SConscript b/selfdrive/SConscript deleted file mode 100644 index 55f347c44eb..00000000000 --- a/selfdrive/SConscript +++ /dev/null @@ -1,6 +0,0 @@ -SConscript(['pandad/SConscript']) -SConscript(['controls/lib/lateral_mpc_lib/SConscript']) -SConscript(['controls/lib/longitudinal_mpc_lib/SConscript']) -SConscript(['locationd/SConscript']) -SConscript(['modeld/SConscript']) -SConscript(['ui/SConscript']) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 4d7448c62f8..1a662e6b245 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,4 +1,3 @@ -import re from pathlib import Path Import('env', 'arch', 'common') @@ -19,39 +18,38 @@ env.Command( if GetOption('extras') and arch == "larch64": # build installers - if arch != "Darwin": - raylib_env = env.Clone() - raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] - raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') + raylib_env = env.Clone() + raylib_env['LIBPATH'] += [f'#third_party/raylib/{arch}/'] + raylib_env['LINKFLAGS'].append('-Wl,-strip-debug') - raylib_libs = common + ["raylib"] - if arch == "larch64": - raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] - else: - raylib_libs += ["GL"] + raylib_libs = common + ["raylib"] + if arch == "larch64": + raylib_libs += ["GLESv2", "EGL", "gbm", "drm"] + else: + raylib_libs += ["GL"] - release = "release3" - installers = [ - ("openpilot", release), - ("openpilot_test", f"{release}-staging"), - ("openpilot_nightly", "nightly"), - ("openpilot_internal", "nightly-dev"), - ] + release = "release3" + installers = [ + ("openpilot", release), + ("openpilot_test", f"{release}-staging"), + ("openpilot_nightly", "nightly"), + ("openpilot_internal", "nightly-dev"), + ] - cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", + cont = raylib_env.Command("installer/continue_openpilot.o", "installer/continue_openpilot.sh", + "ld -r -b binary -o $TARGET $SOURCE") + inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", + "ld -r -b binary -o $TARGET $SOURCE") + inter_bold = raylib_env.Command("installer/inter_bold.o", "../assets/fonts/Inter-Bold.ttf", "ld -r -b binary -o $TARGET $SOURCE") - inter = raylib_env.Command("installer/inter_ttf.o", "installer/inter-ascii.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - inter_bold = raylib_env.Command("installer/inter_bold.o", "../assets/fonts/Inter-Bold.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - inter_light = raylib_env.Command("installer/inter_light.o", "../assets/fonts/Inter-Light.ttf", - "ld -r -b binary -o $TARGET $SOURCE") - for name, branch in installers: - d = {'BRANCH': f"'\"{branch}\"'"} - if "internal" in name: - d['INTERNAL'] = "1" + inter_light = raylib_env.Command("installer/inter_light.o", "../assets/fonts/Inter-Light.ttf", + "ld -r -b binary -o $TARGET $SOURCE") + for name, branch in installers: + d = {'BRANCH': f"'\"{branch}\"'"} + if "internal" in name: + d['INTERNAL'] = "1" - obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) - f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter, inter_bold, inter_light], LIBS=raylib_libs) - # keep installers small - assert f[0].get_size() < 2500*1e3, f[0].get_size() + obj = raylib_env.Object(f"installer/installers/installer_{name}.o", ["installer/installer.cc"], CPPDEFINES=d) + f = raylib_env.Program(f"installer/installers/installer_{name}", [obj, cont, inter, inter_bold, inter_light], LIBS=raylib_libs) + # keep installers small + assert f[0].get_size() < 2500*1e3, f[0].get_size() diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 1f7ba3eaef7..ad77231ea66 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -71,14 +71,12 @@ if arch == "Darwin": else: base_libs.append('Qt5Charts') -qt_libs = base_libs - cabana_env = qt_env.Clone() if arch == "Darwin": cabana_env['CPPPATH'] += [f"{brew_prefix}/include"] cabana_env['LIBPATH'] += [f"{brew_prefix}/lib"] -cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + qt_libs +cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + base_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] From 9d19cca006648da50f8e86bb92f10c107a1bf63d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 13 Mar 2026 20:12:13 -0700 Subject: [PATCH 557/685] scons: whitelist non-vendored includes and libraries (#37670) --- SConstruct | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/SConstruct b/SConstruct index 0a4a3701a64..be77027ad98 100644 --- a/SConstruct +++ b/SConstruct @@ -8,6 +8,7 @@ import importlib import numpy as np import SCons.Errors +from SCons.Defaults import _stripixes SCons.Warnings.warningAsException(True) @@ -39,6 +40,44 @@ assert arch in [ pkg_names = ['bzip2', 'capnproto', 'eigen', 'ffmpeg', 'libjpeg', 'libyuv', 'ncurses', 'zeromq', 'zstd'] pkgs = [importlib.import_module(name) for name in pkg_names] + +# ***** enforce a whitelist of system libraries ***** +# this prevents silently relying on a 3rd party package, +# e.g. apt-installed libusb. all libraries should either +# be distributed with all Linux distros and macOS, or +# vendored in commaai/dependencies. +allowed_system_libs = { + "EGL", "GLESv2", "GL", "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", + "crypto", "dl", "drm", "gbm", "m", "pthread", "ssl", "usb-1.0", +} + +def _resolve_lib(env, name): + for d in env.Flatten(env.get('LIBPATH', [])): + p = Dir(str(d)).abspath + for ext in ('.a', '.so', '.dylib'): + f = File(os.path.join(p, f'lib{name}{ext}')) + if f.exists() or f.has_builder(): + return f + if name in allowed_system_libs: + return name + raise SCons.Errors.UserError(f"Unexpected non-vendored library '{name}'") + +def _libflags(target, source, env, for_signature): + libs = [] + lp = env.subst('$LIBLITERALPREFIX') + for lib in env.Flatten(env.get('LIBS', [])): + if isinstance(lib, str): + if os.sep in lib or lib.startswith('#'): + libs.append(File(lib)) + elif lib.startswith('-') or (lp and lib.startswith(lp)): + libs.append(lib) + else: + libs.append(_resolve_lib(env, lib)) + else: + libs.append(lib) + return _stripixes(env['LIBLINKPREFIX'], libs, env['LIBLINKSUFFIX'], + env['LIBPREFIXES'], env['LIBSUFFIXES'], env, env['LIBLITERALPREFIX']) + env = Environment( ENV={ "PATH": os.environ['PATH'], @@ -90,6 +129,7 @@ env = Environment( tools=["default", "cython", "compilation_db", "rednose_filter"], toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"], ) +env['_LIBFLAGS'] = _libflags # Arch-specific flags and paths if arch == "larch64": From 24121f8abff77ec86d3cdbc79f5de4445d94716c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Mar 2026 20:16:34 -0700 Subject: [PATCH 558/685] ui: asynchronous ssh key fetcher (#37668) * async * clean on failure * fix * meh job * one less * no clear * disable * no clue * better * always passed --- .../ui/mici/layouts/settings/developer.py | 29 +++--- selfdrive/ui/widgets/ssh_key.py | 90 ++++++++++++------- 2 files changed, 77 insertions(+), 42 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index eccaa3ec093..b471c50f99b 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -5,25 +5,30 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyAction +from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyFetcher class DeveloperLayoutMici(NavScroller): def __init__(self): super().__init__() + self._ssh_fetcher = SshKeyFetcher(ui_state.params) def github_username_callback(username: str): if username: - ssh_keys = SshKeyAction() - ssh_keys._fetch_ssh_key(username) - if not ssh_keys._error_message: - self._ssh_keys_btn.set_value(username) - else: - dlg = BigDialog("", ssh_keys._error_message) - gui_app.push_widget(dlg) + self._ssh_keys_btn.set_value("Loading...") + self._ssh_keys_btn.set_enabled(False) + + def on_response(error): + self._ssh_keys_btn.set_enabled(True) + if error is None: + self._ssh_keys_btn.set_value(username) + else: + self._ssh_keys_btn.set_value("Not set") + gui_app.push_widget(BigDialog("", error)) + + self._ssh_fetcher.fetch(username, on_response) else: - ui_state.params.remove("GithubUsername") - ui_state.params.remove("GithubSshKeys") + self._ssh_fetcher.clear() self._ssh_keys_btn.set_value("Not set") def ssh_keys_callback(): @@ -99,6 +104,10 @@ def ssh_keys_callback(): ui_state.add_offroad_transition_callback(self._update_toggles) + def _update_state(self): + super()._update_state() + self._ssh_fetcher.update() + def show_event(self): super().show_event() self._update_toggles() diff --git a/selfdrive/ui/widgets/ssh_key.py b/selfdrive/ui/widgets/ssh_key.py index b31a9eb3bd1..b3ff5c1711b 100644 --- a/selfdrive/ui/widgets/ssh_key.py +++ b/selfdrive/ui/widgets/ssh_key.py @@ -1,7 +1,6 @@ import pyray as rl import requests import threading -import copy from collections.abc import Callable from enum import Enum @@ -25,6 +24,51 @@ VALUE_FONT_SIZE = 48 +class SshKeyFetcher: + HTTP_TIMEOUT = 15 # seconds + + def __init__(self, params: Params): + self._params = params + self._on_response: Callable[[str | None], None] | None = None + self._done: bool = False + self._error: str | None = None + + def fetch(self, username: str, on_response: Callable[[str | None], None]): + self._error = None + self._on_response = on_response + threading.Thread(target=self._fetch_thread, args=(username,), daemon=True).start() + + def update(self): + if not self._done: + return + self._done = False + if self._error is not None: + self.clear() + if self._on_response: + self._on_response(self._error) + + def clear(self): + self._params.remove("GithubUsername") + self._params.remove("GithubSshKeys") + + def _fetch_thread(self, username: str): + try: + response = requests.get(f"https://github.com/{username}.keys", timeout=self.HTTP_TIMEOUT) + response.raise_for_status() + keys = response.text.strip() + if not keys: + raise requests.exceptions.HTTPError("No SSH keys found") + + self._params.put("GithubUsername", username) + self._params.put("GithubSshKeys", keys) + except requests.exceptions.Timeout: + self._error = tr("Request timed out") + except Exception: + self._error = tr("No SSH keys found for user '{}'").format(username) + finally: + self._done = True + + class SshKeyActionState(Enum): LOADING = tr_noop("LOADING") ADD = tr_noop("ADD") @@ -32,7 +76,6 @@ class SshKeyActionState(Enum): class SshKeyAction(ItemAction): - HTTP_TIMEOUT = 15 # seconds MAX_WIDTH = 500 def __init__(self): @@ -40,7 +83,7 @@ def __init__(self): self._keyboard = Keyboard(min_text_size=1) self._params = Params() - self._error_message: str = "" + self._fetcher = SshKeyFetcher(self._params) self._text_font = gui_app.font(FontWeight.NORMAL) self._button = Button("", click_callback=self._handle_button_click, button_style=ButtonStyle.LIST_ACTION, border_radius=BUTTON_BORDER_RADIUS, font_size=BUTTON_FONT_SIZE) @@ -55,14 +98,11 @@ def _refresh_state(self): self._username = self._params.get("GithubUsername") self._state = SshKeyActionState.REMOVE if self._params.get("GithubSshKeys") else SshKeyActionState.ADD - def _render(self, rect: rl.Rectangle) -> bool: - # Show error dialog if there's an error - if self._error_message: - message = copy.copy(self._error_message) - gui_app.push_widget(alert_dialog(message)) - self._username = "" - self._error_message = "" + def _update_state(self): + super()._update_state() + self._fetcher.update() + def _render(self, rect: rl.Rectangle) -> bool: # Draw username if exists if self._username: text_size = measure_text_cached(self._text_font, self._username, VALUE_FONT_SIZE) @@ -90,8 +130,7 @@ def _handle_button_click(self): self._keyboard.set_callback(self._on_username_submit) gui_app.push_widget(self._keyboard) elif self._state == SshKeyActionState.REMOVE: - self._params.remove("GithubUsername") - self._params.remove("GithubSshKeys") + self._fetcher.clear() self._refresh_state() def _on_username_submit(self, result: DialogResult): @@ -103,29 +142,16 @@ def _on_username_submit(self, result: DialogResult): return self._state = SshKeyActionState.LOADING - threading.Thread(target=lambda: self._fetch_ssh_key(username), daemon=True).start() + self._fetcher.fetch(username, self._on_fetch_response) - def _fetch_ssh_key(self, username: str): - try: - url = f"https://github.com/{username}.keys" - response = requests.get(url, timeout=self.HTTP_TIMEOUT) - response.raise_for_status() - keys = response.text.strip() - if not keys: - raise requests.exceptions.HTTPError(tr("No SSH keys found")) - - # Success - save keys - self._params.put("GithubUsername", username) - self._params.put("GithubSshKeys", keys) + def _on_fetch_response(self, error: str | None): + if error is None: self._state = SshKeyActionState.REMOVE - self._username = username - - except requests.exceptions.Timeout: - self._error_message = tr("Request timed out") - self._state = SshKeyActionState.ADD - except Exception: - self._error_message = tr("No SSH keys found for user '{}'").format(username) + self._username = self._params.get("GithubUsername") + else: self._state = SshKeyActionState.ADD + self._username = "" + gui_app.push_widget(alert_dialog(error)) def ssh_key_item(title: str | Callable[[], str], description: str | Callable[[], str]) -> ListItem: From 380d91c8f71a2d6cdc61384892be9d7fa3f1505f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 13 Mar 2026 20:26:32 -0700 Subject: [PATCH 559/685] don't need to whitelist on larch64 --- SConstruct | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index be77027ad98..e8edfe1feee 100644 --- a/SConstruct +++ b/SConstruct @@ -129,7 +129,8 @@ env = Environment( tools=["default", "cython", "compilation_db", "rednose_filter"], toolpath=["#site_scons/site_tools", "#rednose_repo/site_scons/site_tools"], ) -env['_LIBFLAGS'] = _libflags +if arch != "larch64": + env['_LIBFLAGS'] = _libflags # Arch-specific flags and paths if arch == "larch64": From 46bbe6890a8888e3e7db95b931650f024a64c0fb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 13 Mar 2026 21:56:07 -0700 Subject: [PATCH 560/685] mici ui: consistent dialogs (#37671) * new dialog * clean up * got wish * use in mici reset * punctuation * clean up --- .../ui/mici/layouts/settings/developer.py | 2 +- selfdrive/ui/mici/layouts/settings/device.py | 8 ++-- selfdrive/ui/mici/widgets/button.py | 37 +++++++++++++++ selfdrive/ui/mici/widgets/dialog.py | 46 ++++--------------- system/ui/mici_reset.py | 20 +++----- system/ui/mici_setup.py | 39 +--------------- 6 files changed, 59 insertions(+), 93 deletions(-) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index b471c50f99b..386b4689280 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -35,7 +35,7 @@ def ssh_keys_callback(): github_username = ui_state.params.get("GithubUsername") or "" dlg = BigInputDialog("enter GitHub username...", github_username, minimum_length=0, confirm_callback=github_username_callback) if not system_time_valid(): - dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "") + dlg = BigDialog("", "Please connect to Wi-Fi to fetch your key.") gui_app.push_widget(dlg) return gui_app.push_widget(dlg) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 909e30ac719..3c165b5bb35 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -74,7 +74,7 @@ def confirm_callback(): gui_app.push_widget(BigConfirmationDialog(f"slide to\n{action_text.lower()}", icon, confirm_callback, exit_on_confirm=exit_on_confirm, red=red)) else: - gui_app.push_widget(BigDialog(f"Disengage to {action_text}", "")) + gui_app.push_widget(BigDialog("", f"Disengage to {action_text}")) class EngagedConfirmationCircleButton(BigCircleButton): @@ -156,9 +156,9 @@ def _handle_mouse_release(self, mouse_pos: MousePos): return dlg: BigDialog | PairingDialog if not system_time_valid(): - dlg = BigDialog(tr("Please connect to Wi-Fi to complete initial pairing"), "") + dlg = BigDialog("", tr("Please connect to Wi-Fi to complete initial pairing.")) elif UNREGISTERED_DONGLE_ID == (ui_state.params.get("DongleId") or UNREGISTERED_DONGLE_ID): - dlg = BigDialog(tr("Device must be registered with the comma.ai backend to pair"), "") + dlg = BigDialog("", tr("Device must be registered with the comma.ai backend to pair.")) else: dlg = PairingDialog() gui_app.push_widget(dlg) @@ -188,7 +188,7 @@ def _handle_mouse_release(self, mouse_pos: MousePos): super()._handle_mouse_release(mouse_pos) if not system_time_valid(): - dlg = BigDialog(tr("Please connect to Wi-Fi to update"), "") + dlg = BigDialog("", tr("Please connect to Wi-Fi to update.")) gui_app.push_widget(dlg) return diff --git a/selfdrive/ui/mici/widgets/button.py b/selfdrive/ui/mici/widgets/button.py index 9724a18192f..058c351fb63 100644 --- a/selfdrive/ui/mici/widgets/button.py +++ b/selfdrive/ui/mici/widgets/button.py @@ -335,6 +335,43 @@ def _draw_content(self, btn_y: float): y += 35 +class GreyBigButton(BigButton): + """Users should manage newlines with this class themselves""" + + LABEL_HORIZONTAL_PADDING = 30 + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.set_touch_valid_callback(lambda: False) + + self._rect.width = 476 + + self._label.set_font_size(36) + self._label.set_font_weight(FontWeight.BOLD) + self._label.set_line_height(1.0) + + self._sub_label.set_font_size(36) + self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9))) + self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR) + self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else + rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + self._sub_label.set_line_height(0.95) + + @property + def LABEL_VERTICAL_PADDING(self): + return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18 + + def _width_hint(self) -> int: + return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2) + + def _get_label_font_size(self): + return 36 + + def _render(self, _): + rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15))) + self._draw_content(self._rect.y) + + class BigMultiParamToggle(BigMultiToggle): def __init__(self, text: str, param: str, options: list[str], toggle_callback: Callable | None = None, select_callback: Callable | None = None): diff --git a/selfdrive/ui/mici/widgets/dialog.py b/selfdrive/ui/mici/widgets/dialog.py index 396ea35cc8d..ed1466449b2 100644 --- a/selfdrive/ui/mici/widgets/dialog.py +++ b/selfdrive/ui/mici/widgets/dialog.py @@ -4,14 +4,13 @@ from typing import Union from collections.abc import Callable from openpilot.system.ui.widgets.nav_widget import NavWidget -from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label +from openpilot.system.ui.widgets.label import UnifiedLabel from openpilot.system.ui.widgets.mici_keyboard import MiciKeyboard from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.wrap_text import wrap_text from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.system.ui.widgets.slider import RedBigSlider, BigSlider from openpilot.common.filter_simple import FirstOrderFilter -from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton, GreyBigButton DEBUG = False @@ -25,42 +24,17 @@ def __init__(self): class BigDialog(BigDialogBase): - def __init__(self, - title: str, - description: str): + def __init__(self, title: str, description: str, icon: Union[rl.Texture, None] = None): super().__init__() - self._title = title - self._description = description + self._card = GreyBigButton(title, description, icon) def _render(self, _): - super()._render(_) - - # draw title - # TODO: we desperately need layouts - # TODO: coming up with these numbers manually is a pain and not scalable - # TODO: no clue what any of these numbers mean. VBox and HBox would remove all of this shite - max_width = self._rect.width - PADDING * 2 - - title_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.BOLD), self._title, 50, int(max_width))) - title_size = measure_text_cached(gui_app.font(FontWeight.BOLD), title_wrapped, 50) - text_x_offset = 0 - title_rect = rl.Rectangle(self._rect.x + text_x_offset + PADDING, - self._rect.y + PADDING, - max_width, - title_size.y) - gui_label(title_rect, title_wrapped, 50, font_weight=FontWeight.BOLD, - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) - - # draw description - desc_wrapped = '\n'.join(wrap_text(gui_app.font(FontWeight.MEDIUM), self._description, 30, int(max_width))) - desc_size = measure_text_cached(gui_app.font(FontWeight.MEDIUM), desc_wrapped, 30) - desc_rect = rl.Rectangle(self._rect.x + text_x_offset + PADDING, - self._rect.y + self._rect.height / 3, - max_width, - desc_size.y) - # TODO: text align doesn't seem to work properly with newlines - gui_label(desc_rect, desc_wrapped, 30, font_weight=FontWeight.MEDIUM, - alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER) + self._card.render(rl.Rectangle( + self._rect.x + self._rect.width / 2 - self._card.rect.width / 2, + self._rect.y + self._rect.height / 2 - self._card.rect.height / 2, + self._card.rect.width, + self._card.rect.height, + )) class BigConfirmationDialog(BigDialogBase): diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index af9d866d544..9cc6e7f3f8e 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -10,9 +10,8 @@ from openpilot.system.hardware import HARDWARE, PC from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets.scroller import Scroller -from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage -from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" TIMEOUT = 3*60 @@ -36,16 +35,14 @@ def _back_enabled(self) -> bool: return False -class ResettingPage(NavWidget): +class ResettingPage(BigDialog): DOT_STEP = 0.6 def __init__(self): - super().__init__() + super().__init__("resetting device", "this may take up to\na minute...", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) self._show_time = 0.0 - self._resetting_card = GreyBigButton("resetting device", "this may take up to\na minute...", - gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) - def show_event(self): super().show_event() self._nav_bar._alpha = 0.0 # not dismissable @@ -57,13 +54,8 @@ def _back_enabled(self) -> bool: def _render(self, _): t = (rl.get_time() - self._show_time) % (self.DOT_STEP * 2) dots = "." * min(int(t / (self.DOT_STEP / 4)), 3) - self._resetting_card.set_value(f"this may take up to\na minute{dots}") - self._resetting_card.render(rl.Rectangle( - self._rect.x + self._rect.width / 2 - self._resetting_card.rect.width / 2, - self._rect.y + self._rect.height / 2 - self._resetting_card.rect.height / 2, - self._resetting_card.rect.width, - self._resetting_card.rect.height, - )) + self._card.set_value(f"this may take up to\na minute{dots}") + super()._render(_) class Reset(Scroller): diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 62d9dcd8274..d55fc5e1eb3 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -28,7 +28,7 @@ from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationCircleButton -from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, GreyBigButton NetworkType = log.DeviceState.NetworkType @@ -254,43 +254,6 @@ def set_reason(self, reason: str): self._reason_card.set_visible(False) -class GreyBigButton(BigButton): - """Users should manage newlines with this class themselves""" - - LABEL_HORIZONTAL_PADDING = 30 - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.set_touch_valid_callback(lambda: False) - - self._rect.width = 476 - - self._label.set_font_size(36) - self._label.set_font_weight(FontWeight.BOLD) - self._label.set_line_height(1.0) - - self._sub_label.set_font_size(36) - self._sub_label.set_text_color(rl.Color(255, 255, 255, int(255 * 0.9))) - self._sub_label.set_font_weight(FontWeight.DISPLAY_REGULAR) - self._sub_label.set_alignment_vertical(rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE if not self._label.text else - rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) - self._sub_label.set_line_height(0.95) - - @property - def LABEL_VERTICAL_PADDING(self): - return BigButton.LABEL_VERTICAL_PADDING if self._label.text else 18 - - def _width_hint(self) -> int: - return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2) - - def _get_label_font_size(self): - return 36 - - def _render(self, _): - rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15))) - self._draw_content(self._rect.y) - - class BigPillButton(BigButton): def __init__(self, *args, green: bool = False, disabled_background: bool = False, **kwargs): self._green = green From f4657aa2d5442b382a9db9c646a9f076b078281d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sat, 14 Mar 2026 13:42:57 -0700 Subject: [PATCH 561/685] Sconstruct: use name (#37675) --- SConstruct | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SConstruct b/SConstruct index e8edfe1feee..e649a6be168 100644 --- a/SConstruct +++ b/SConstruct @@ -57,7 +57,7 @@ def _resolve_lib(env, name): for ext in ('.a', '.so', '.dylib'): f = File(os.path.join(p, f'lib{name}{ext}')) if f.exists() or f.has_builder(): - return f + return name if name in allowed_system_libs: return name raise SCons.Errors.UserError(f"Unexpected non-vendored library '{name}'") From cc4f786846d0b43ecbb2cac26539dfaaa473831c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Mar 2026 15:01:45 -0700 Subject: [PATCH 562/685] deps: switch vendored packages to per-package release branches (#37678) --- pyproject.toml | 22 +++++++++++----------- uv.lock | 44 ++++++++++++++++++++++---------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index bf841613179..b60346b0313 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,17 +26,17 @@ dependencies = [ "numpy >=2.0", # vendored native dependencies - "bzip2 @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=bzip2", - "capnproto @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=capnproto", - "eigen @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=eigen", - "ffmpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ffmpeg", - "libjpeg @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libjpeg", - "libyuv @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=libyuv", - "zstd @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zstd", - "ncurses @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=ncurses", - "zeromq @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=zeromq", - "git-lfs @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=git-lfs", - "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@releases#subdirectory=gcc-arm-none-eabi", + "bzip2 @ git+https://github.com/commaai/dependencies.git@release-bzip2#subdirectory=bzip2", + "capnproto @ git+https://github.com/commaai/dependencies.git@release-capnproto#subdirectory=capnproto", + "eigen @ git+https://github.com/commaai/dependencies.git@release-eigen#subdirectory=eigen", + "ffmpeg @ git+https://github.com/commaai/dependencies.git@release-ffmpeg#subdirectory=ffmpeg", + "libjpeg @ git+https://github.com/commaai/dependencies.git@release-libjpeg#subdirectory=libjpeg", + "libyuv @ git+https://github.com/commaai/dependencies.git@release-libyuv#subdirectory=libyuv", + "zstd @ git+https://github.com/commaai/dependencies.git@release-zstd#subdirectory=zstd", + "ncurses @ git+https://github.com/commaai/dependencies.git@release-ncurses#subdirectory=ncurses", + "zeromq @ git+https://github.com/commaai/dependencies.git@release-zeromq#subdirectory=zeromq", + "git-lfs @ git+https://github.com/commaai/dependencies.git@release-git-lfs#subdirectory=git-lfs", + "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@release-gcc-arm-none-eabi#subdirectory=gcc-arm-none-eabi", # body / webrtcd "av", diff --git a/uv.lock b/uv.lock index 578b19f6462..ce613e08bad 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#4808f76c45cb797e697ab21e5e37d68a0ab3b2d4" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#f735fd22c66029b92019019d0596da6a4445b931" } [[package]] name = "casadi" @@ -371,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#fc9915c3a81d6488eafcdbbdc428f15d8123e540" } [[package]] name = "execnet" @@ -385,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#8d693da088e5905d4479550e07484961765df45b" } [[package]] name = "fonttools" @@ -432,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#e101138b29023effc932df7a58fb76a26c4e443a" } [[package]] name = "ghp-import" @@ -449,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#5407b1d37b7a8a9ae3747cc20cb6e7a7b01f5059" } [[package]] name = "google-crc32c" @@ -567,7 +567,7 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#83fa530843e5109c51aef14327b6fde5dcb4507b" } [[package]] name = "libusb1" @@ -583,7 +583,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#600cdd08cb77cbcc001daeb031abcb5c6008c7c2" } [[package]] name = "markdown" @@ -735,7 +735,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#0503ac0d54799b58c84f900dba75abcad17e780f" } [[package]] name = "numpy" @@ -857,30 +857,30 @@ requires-dist = [ { name = "aiohttp" }, { name = "aiortc" }, { name = "av" }, - { name = "bzip2", git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=releases" }, - { name = "capnproto", git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=releases" }, + { name = "bzip2", git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2" }, + { name = "capnproto", git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto" }, { name = "casadi", specifier = ">=3.6.6" }, { name = "cffi" }, { name = "codespell", marker = "extra == 'testing'" }, { name = "coverage", marker = "extra == 'testing'" }, { name = "crcmod-plus" }, { name = "cython" }, - { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=releases" }, - { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=releases" }, - { name = "gcc-arm-none-eabi", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=releases" }, - { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=releases" }, + { name = "eigen", git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen" }, + { name = "ffmpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg" }, + { name = "gcc-arm-none-eabi", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi" }, + { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, { name = "inputs" }, { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, - { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=releases" }, + { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg" }, { name = "libusb1" }, - { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=releases" }, + { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv" }, { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, { name = "mkdocs", marker = "extra == 'docs'" }, - { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=releases" }, + { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses" }, { name = "numpy", specifier = ">=2.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, { name = "pillow" }, @@ -912,9 +912,9 @@ requires-dist = [ { name = "ty", marker = "extra == 'testing'" }, { name = "websocket-client" }, { name = "xattr" }, - { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases" }, + { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq" }, { name = "zstandard" }, - { name = "zstd", git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases" }, + { name = "zstd", git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd" }, ] provides-extras = ["docs", "testing", "dev", "tools"] @@ -1652,7 +1652,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#768bd6d6d67acc7b4e919993967187532af0d410" } [[package]] name = "zstandard" @@ -1682,4 +1682,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=releases#9777ee38aa5ca9439843125392af38ed1262e500" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#4d4dd0b74dfc52bdeec36706fd1a3a27754679ec" } From 5e7f5dd840995fb806433e2eacefce74fb3d92a1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Mar 2026 16:43:19 -0700 Subject: [PATCH 563/685] replay/cabana: remove unused openssl dependency (#37680) --- SConstruct | 2 +- tools/cabana/SConscript | 2 +- tools/replay/SConscript | 2 +- tools/replay/util.cc | 10 ---------- tools/replay/util.h | 1 - 5 files changed, 3 insertions(+), 14 deletions(-) diff --git a/SConstruct b/SConstruct index e649a6be168..9d3dc8526e7 100644 --- a/SConstruct +++ b/SConstruct @@ -48,7 +48,7 @@ pkgs = [importlib.import_module(name) for name in pkg_names] # vendored in commaai/dependencies. allowed_system_libs = { "EGL", "GLESv2", "GL", "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", - "crypto", "dl", "drm", "gbm", "m", "pthread", "ssl", "usb-1.0", + "dl", "drm", "gbm", "m", "pthread", "usb-1.0", } def _resolve_lib(env, name): diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index ad77231ea66..d604fe67ef1 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -64,7 +64,7 @@ qt_env['LIBPATH'] += ['#selfdrive/ui', ] qt_env['LIBS'] = qt_libs base_frameworks = qt_env['FRAMEWORKS'] -base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] + qt_env["LIBS"] +base_libs = [common, messaging, cereal, visionipc, 'm', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": base_frameworks.append('QtCharts') diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 3efa970b376..757f3fec4e6 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -4,7 +4,7 @@ replay_env = env.Clone() replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] base_frameworks = [] -base_libs = [common, messaging, cereal, visionipc, 'm', 'ssl', 'crypto', 'pthread'] +base_libs = [common, messaging, cereal, visionipc, 'm', 'pthread'] replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", "route.cc", "util.cc", "seg_mgr.cc", "timeline.cc", "py_downloader.cc"] diff --git a/tools/replay/util.cc b/tools/replay/util.cc index 7294de82820..7b308b5c3d4 100644 --- a/tools/replay/util.cc +++ b/tools/replay/util.cc @@ -1,7 +1,6 @@ #include "tools/replay/util.h" #include -#include #include #include @@ -162,15 +161,6 @@ void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_reques } } -std::string sha256(const std::string &str) { - unsigned char hash[SHA256_DIGEST_LENGTH]; - SHA256_CTX sha256; - SHA256_Init(&sha256); - SHA256_Update(&sha256, str.c_str(), str.size()); - SHA256_Final(hash, &sha256); - return util::hexdump(hash, SHA256_DIGEST_LENGTH); -} - std::vector split(std::string_view source, char delimiter) { std::vector fields; size_t last = 0; diff --git a/tools/replay/util.h b/tools/replay/util.h index ee92190337d..a2d0f6203a8 100644 --- a/tools/replay/util.h +++ b/tools/replay/util.h @@ -46,7 +46,6 @@ class MonotonicBuffer { static constexpr float growth_factor = 1.5; }; -std::string sha256(const std::string &str); void precise_nano_sleep(int64_t nanoseconds, std::atomic &interrupt_requested); std::string decompressBZ2(const std::string &in, std::atomic *abort = nullptr); std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); From a68ea44af3418cf80d6b8d34bd1f7299af05c19a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 14 Mar 2026 16:47:17 -0700 Subject: [PATCH 564/685] cabana: use vendored libusb from commaai/dependencies (#37681) --- SConstruct | 2 +- pyproject.toml | 1 + tools/cabana/SConscript | 7 ++++--- uv.lock | 7 +++++++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/SConstruct b/SConstruct index 9d3dc8526e7..feaadd5a41c 100644 --- a/SConstruct +++ b/SConstruct @@ -48,7 +48,7 @@ pkgs = [importlib.import_module(name) for name in pkg_names] # vendored in commaai/dependencies. allowed_system_libs = { "EGL", "GLESv2", "GL", "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", - "dl", "drm", "gbm", "m", "pthread", "usb-1.0", + "dl", "drm", "gbm", "m", "pthread", } def _resolve_lib(env, name): diff --git a/pyproject.toml b/pyproject.toml index b60346b0313..a754f580bdd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ dependencies = [ "zstd @ git+https://github.com/commaai/dependencies.git@release-zstd#subdirectory=zstd", "ncurses @ git+https://github.com/commaai/dependencies.git@release-ncurses#subdirectory=ncurses", "zeromq @ git+https://github.com/commaai/dependencies.git@release-zeromq#subdirectory=zeromq", + "libusb @ git+https://github.com/commaai/dependencies.git@release-libusb#subdirectory=libusb", "git-lfs @ git+https://github.com/commaai/dependencies.git@release-git-lfs#subdirectory=git-lfs", "gcc-arm-none-eabi @ git+https://github.com/commaai/dependencies.git@release-gcc-arm-none-eabi#subdirectory=gcc-arm-none-eabi", diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index d604fe67ef1..dbe4dbc6593 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -2,6 +2,8 @@ import subprocess import os import shutil +import libusb + Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') # Detect Qt - skip build if not available @@ -72,9 +74,8 @@ else: base_libs.append('Qt5Charts') cabana_env = qt_env.Clone() -if arch == "Darwin": - cabana_env['CPPPATH'] += [f"{brew_prefix}/include"] - cabana_env['LIBPATH'] += [f"{brew_prefix}/lib"] +cabana_env['CPPPATH'] += [libusb.INCLUDE_DIR] +cabana_env['LIBPATH'] += [libusb.LIB_DIR] cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + base_libs opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) diff --git a/uv.lock b/uv.lock index ce613e08bad..8e597db6ef1 100644 --- a/uv.lock +++ b/uv.lock @@ -569,6 +569,11 @@ name = "libjpeg" version = "3.1.0" source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#83fa530843e5109c51aef14327b6fde5dcb4507b" } +[[package]] +name = "libusb" +version = "1.0.29" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#5f188080524c3c6d098ab6cb8d206ceef0394e8e" } + [[package]] name = "libusb1" version = "3.3.1" @@ -796,6 +801,7 @@ dependencies = [ { name = "jeepney" }, { name = "json-rpc" }, { name = "libjpeg" }, + { name = "libusb" }, { name = "libusb1" }, { name = "libyuv" }, { name = "ncurses" }, @@ -875,6 +881,7 @@ requires-dist = [ { name = "jinja2", marker = "extra == 'docs'" }, { name = "json-rpc" }, { name = "libjpeg", git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg" }, + { name = "libusb", git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb" }, { name = "libusb1" }, { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv" }, { name = "matplotlib", marker = "extra == 'dev'" }, From 1c1437579638939f2741dbd89ba84240c1c6f051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Thu, 19 Mar 2026 19:35:10 -0700 Subject: [PATCH 565/685] locationd: cam odo delay compensation (#37543) * Delay compensation for camera odomtry * Frame skip definition * CAM_ODO_POSE_DELAY const * Remove import * Use timestampEof * CAM_ODO_STD_MULT * locationd processing_time=0.01 * Update angular velocity Q * Try 075 * Acc obs std 0.75 * Adjust Cam odo std mults * More tweaking * Smoothing in lld tests * Comment * Remove import * Revert gyro bias P update * Tweak to 0.75 --- selfdrive/locationd/locationd.py | 9 +++++++-- selfdrive/locationd/models/pose_kf.py | 4 ++-- selfdrive/locationd/test/test_locationd_scenarios.py | 12 +++++++++--- selfdrive/test/process_replay/process_replay.py | 1 + 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/selfdrive/locationd/locationd.py b/selfdrive/locationd/locationd.py index f6a0935ed97..a34a584ff37 100755 --- a/selfdrive/locationd/locationd.py +++ b/selfdrive/locationd/locationd.py @@ -28,6 +28,9 @@ INPUT_INVALID_RECOVERY = 10.0 # ~10 secs to resume after exceeding allowed bad inputs by one POSENET_STD_INITIAL_VALUE = 10.0 POSENET_STD_HIST_HALF = 20 +CAM_ODO_POSE_DELAY = 0.1 # dependent on the vision model context frames and temporal frequency (current model is 5 fps with 2 context frames) +CAM_ODO_ROT_STD_MULT = 10 +CAM_ODO_TRANS_STD_MULT = 4 def calculate_invalid_input_decay(invalid_limit, recovery_time, frequency): @@ -155,6 +158,8 @@ def handle_log(self, t: float, which: str, msg: capnp._DynamicStructReader) -> H self.device_from_calib = rot_from_euler(calib) elif which == "cameraOdometry": + # camera odometry is delayed depending on the model context frames and temporal frequency + t = msg.timestampEof * 1e-9 - CAM_ODO_POSE_DELAY if not self._validate_timestamp(t): return HandleLogResult.TIMING_INVALID @@ -177,8 +182,8 @@ def handle_log(self, t: float, which: str, msg: capnp._DynamicStructReader) -> H self.posenet_stds[-1] = trans_calib_std[0] # Multiply by N to avoid to high certainty in kalman filter because of temporally correlated noise - rot_calib_std *= 10 - trans_calib_std *= 2 + rot_calib_std *= CAM_ODO_ROT_STD_MULT + trans_calib_std *= CAM_ODO_TRANS_STD_MULT rot_device_std = rotate_std(self.device_from_calib, rot_calib_std) trans_device_std = rotate_std(self.device_from_calib, trans_calib_std) diff --git a/selfdrive/locationd/models/pose_kf.py b/selfdrive/locationd/models/pose_kf.py index 020e51ad6e5..a8ff80c7130 100755 --- a/selfdrive/locationd/models/pose_kf.py +++ b/selfdrive/locationd/models/pose_kf.py @@ -47,13 +47,13 @@ class PoseKalman(KalmanFilter): # process noise Q = np.diag([0.001**2, 0.001**2, 0.001**2, 0.01**2, 0.01**2, 0.01**2, - 0.1**2, 0.1**2, 0.1**2, + 0.085**2, 0.085**2, 0.085**2, (0.005 / 100)**2, (0.005 / 100)**2, (0.005 / 100)**2, 3**2, 3**2, 3**2, 0.005**2, 0.005**2, 0.005**2]) obs_noise = {ObservationKind.PHONE_GYRO: np.diag([0.025**2, 0.025**2, 0.025**2]), - ObservationKind.PHONE_ACCEL: np.diag([.5**2, .5**2, .5**2]), + ObservationKind.PHONE_ACCEL: np.diag([0.75**2, 0.75**2, 0.75**2]), ObservationKind.CAMERA_ODO_TRANSLATION: np.diag([0.5**2, 0.5**2, 0.5**2]), ObservationKind.CAMERA_ODO_ROTATION: np.diag([0.05**2, 0.05**2, 0.05**2])} diff --git a/selfdrive/locationd/test/test_locationd_scenarios.py b/selfdrive/locationd/test/test_locationd_scenarios.py index 0ea7ac183f2..69f2ca2821b 100644 --- a/selfdrive/locationd/test/test_locationd_scenarios.py +++ b/selfdrive/locationd/test/test_locationd_scenarios.py @@ -3,6 +3,7 @@ from enum import Enum from openpilot.tools.lib.logreader import LogReader +from openpilot.selfdrive.locationd.lagd import masked_symmetric_moving_average from openpilot.selfdrive.test.process_replay.migration import migrate_all from openpilot.selfdrive.test.process_replay.process_replay import replay_process_with_name @@ -15,6 +16,7 @@ 'inputs_flag': ['inputsOK'], 'sensors_flag': ['sensorsOK'], } +SMOOTH_FIELDS = ['yaw_rate', 'roll'] JUNK_IDX = 100 CONSISTENT_SPIKES_COUNT = 10 @@ -32,6 +34,8 @@ class Scenario(Enum): def get_select_fields_data(logs): + def sig_smooth(signal): + return masked_symmetric_moving_average(signal, np.ones_like(signal), 5, 1.0) def get_nested_keys(msg, keys): val = None for key in keys: @@ -44,6 +48,8 @@ def get_nested_keys(msg, keys): data[key].append(get_nested_keys(msg, fields)) for key in data: data[key] = np.array(data[key][JUNK_IDX:], dtype=float) + if key in SMOOTH_FIELDS: + data[key] = sig_smooth(data[key]) return data @@ -110,7 +116,7 @@ def test_base(self): """ orig_data, replayed_data = run_scenarios(Scenario.BASE, self.logs) assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.35)) - assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.55)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.35)) def test_gyro_off(self): """ @@ -135,7 +141,7 @@ def test_gyro_spike(self): """ orig_data, replayed_data = run_scenarios(Scenario.GYRO_SPIKE_MIDWAY, self.logs) assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.35)) - assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.55)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.35)) assert np.all(replayed_data['inputs_flag'] == orig_data['inputs_flag']) assert np.all(replayed_data['sensors_flag'] == orig_data['sensors_flag']) @@ -169,7 +175,7 @@ def test_accel_spike(self): """ orig_data, replayed_data = run_scenarios(Scenario.ACCEL_SPIKE_MIDWAY, self.logs) assert np.allclose(orig_data['yaw_rate'], replayed_data['yaw_rate'], atol=np.radians(0.35)) - assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.55)) + assert np.allclose(orig_data['roll'], replayed_data['roll'], atol=np.radians(0.35)) def test_single_timing_spike(self): """ diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index d168a7e8002..5e9b2e742c6 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -509,6 +509,7 @@ def selfdrived_config_callback(params, cfg, lr): ignore=["logMonoTime"], should_recv_callback=MessageBasedRcvCallback("cameraOdometry"), tolerance=NUMPY_TOLERANCE, + processing_time=0.01, ), ProcessConfig( proc_name="paramsd", From f95959afdb98138ead2bb5e640b3526a5c286942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Thu, 19 Mar 2026 19:44:14 -0700 Subject: [PATCH 566/685] Bump rednose (#37698) --- rednose_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rednose_repo b/rednose_repo index 6ccb8d05565..7ffefa3d881 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit 6ccb8d055652cd9769b5e418edf116272fde4e09 +Subproject commit 7ffefa3d8811a842f8ec97d311103ce3a45dfae0 From 78b15773c95d368c75c4cfa48c2fe4d51b16fe70 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Mar 2026 14:07:33 -0700 Subject: [PATCH 567/685] pj: update stale layouts --- .../layouts/controls_mismatch_debug.xml | 3 +-- tools/plotjuggler/layouts/gps_vs_llk.xml | 9 +++---- .../plotjuggler/layouts/system_lag_debug.xml | 3 +-- tools/plotjuggler/layouts/tuning.xml | 27 ++++++++++++------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/tools/plotjuggler/layouts/controls_mismatch_debug.xml b/tools/plotjuggler/layouts/controls_mismatch_debug.xml index 646e12a281a..cf337aa7df7 100644 --- a/tools/plotjuggler/layouts/controls_mismatch_debug.xml +++ b/tools/plotjuggler/layouts/controls_mismatch_debug.xml @@ -16,7 +16,7 @@ - + @@ -58,4 +58,3 @@ - diff --git a/tools/plotjuggler/layouts/gps_vs_llk.xml b/tools/plotjuggler/layouts/gps_vs_llk.xml index 69b8f20058b..2051c2bef2d 100644 --- a/tools/plotjuggler/layouts/gps_vs_llk.xml +++ b/tools/plotjuggler/layouts/gps_vs_llk.xml @@ -24,8 +24,8 @@ - - + + @@ -72,12 +72,11 @@ return distance /gpsLocationExternal/latitude /gpsLocationExternal/longitude - /liveLocationKalman/positionGeodetic/value/0 - /liveLocationKalman/positionGeodetic/value/1 + /liveLocationKalmanDEPRECATED/positionGeodetic/value/0 + /liveLocationKalmanDEPRECATED/positionGeodetic/value/1 - diff --git a/tools/plotjuggler/layouts/system_lag_debug.xml b/tools/plotjuggler/layouts/system_lag_debug.xml index a90bba0e279..88511ffe09b 100644 --- a/tools/plotjuggler/layouts/system_lag_debug.xml +++ b/tools/plotjuggler/layouts/system_lag_debug.xml @@ -45,7 +45,7 @@ - + @@ -64,4 +64,3 @@ - diff --git a/tools/plotjuggler/layouts/tuning.xml b/tools/plotjuggler/layouts/tuning.xml index 503e726caf4..699f6ff683e 100644 --- a/tools/plotjuggler/layouts/tuning.xml +++ b/tools/plotjuggler/layouts/tuning.xml @@ -24,14 +24,14 @@ - + - + @@ -39,7 +39,7 @@ - + @@ -71,7 +71,7 @@ - + @@ -126,7 +126,7 @@ - + @@ -161,11 +161,11 @@ if (time > last_bad_time + engage_delay) then else return 0 end - /liveLocationKalman/angularVelocityCalibrated/value/2 + /carControl/angularVelocity/2 /carState/steeringPressed /carControl/enabled - /liveLocationKalman/velocityCalibrated/value/0 + /carState/vEgo @@ -206,7 +206,7 @@ if (time > last_bad_time + engage_delay) then else return 0 end - /lateralPlan/curvatures/0 + /modelV2/action/desiredCurvature /carState/steeringPressed /carControl/enabled @@ -284,8 +284,17 @@ end /carControl/enabled + + + return (math.abs(value - v1) > 0.001 or math.abs(v2 - v3) > 0.05) and 1 or 0 + /carControl/actuators/torque + + /carOutput/actuatorsOutput/torque + /carControl/actuators/steeringAngleDeg + /carOutput/actuatorsOutput/steeringAngleDeg + + - From d0382e2d48999176bf0d7b802a719aaa77eb3875 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 20 Mar 2026 15:02:47 -0700 Subject: [PATCH 568/685] just remove this, actions is so broken --- .github/workflows/auto_pr_review.yaml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 6052ec07120..99c3a258c64 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -33,20 +33,3 @@ jobs: change-to: ${{ github.base_ref }} already-exists-action: close_this already-exists-comment: "Your PR should be made against the `master` branch" - - # Welcome comment - - name: "First timers PR" - uses: actions/first-interaction@v1 - if: github.event.pull_request.head.repo.full_name != 'commaai/openpilot' - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - pr-message: | - - Thanks for contributing to openpilot! In order for us to review your PR as quickly as possible, check the following: - * Convert your PR to a draft unless it's ready to review - * Read the [contributing docs](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md) - * Before marking as "ready for review", ensure: - * the goal is clearly stated in the description - * all the tests are passing - * the change is [something we merge](https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md#what-gets-merged) - * include a route or your device' dongle ID if relevant From e53cc41b470624bc92089eb09fa15ea29a80756d Mon Sep 17 00:00:00 2001 From: Thomas Burgess Date: Fri, 20 Mar 2026 17:23:02 -0500 Subject: [PATCH 569/685] docs: rename comma 3X references to comma four (#37701) * docs: rename comma 3X references to comma four * docs: update comma four links and labels --- README.md | 8 ++++---- docs/CONTRIBUTING.md | 2 +- docs/concepts/glossary.md | 2 +- docs/getting-started/what-is-openpilot.md | 2 +- docs/how-to/connect-to-comma.md | 12 ++++++------ docs/how-to/replay-a-drive.md | 2 +- docs/how-to/turn-the-speed-blue.md | 2 +- 7 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index a77a80935d5..a1f494c33cf 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ · Community · - Try it on a comma 3X + Try it on a comma four Quick start: `bash <(curl -fsSL openpilot.comma.ai)` @@ -42,10 +42,10 @@ Using openpilot in a car ------ To use openpilot in a car, you need four things: -1. **Supported Device:** a comma 3X, available at [comma.ai/shop](https://comma.ai/shop/comma-3x). -2. **Software:** The setup procedure for the comma 3X allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. +1. **Supported Device:** a comma four, available at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four). +2. **Software:** The setup procedure for the comma four allows users to enter a URL for custom software. Use the URL `openpilot.comma.ai` to install the release version. 3. **Supported Car:** Ensure that you have one of [the 275+ supported cars](docs/CARS.md). -4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma 3X to your car. +4. **Car Harness:** You will also need a [car harness](https://comma.ai/shop/car-harness) to connect your comma four to your car. We have detailed instructions for [how to install the harness and device in a car](https://comma.ai/setup). Note that it's possible to run openpilot on [other hardware](https://blog.comma.ai/self-driving-car-for-free/), although it's not plug-and-play. diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 62468c7448f..3d39420c01b 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -39,7 +39,7 @@ All of these are examples of good PRs: ### First contribution [Projects / openpilot bounties](https://github.com/orgs/commaai/projects/26/views/1?pane=info) is the best place to get started and goes in-depth on what's expected when working on a bounty. -There are a lot of bounties that don't require a comma 3X or a car. +There are a lot of bounties that don't require a comma four or a car. ## Pull Requests diff --git a/docs/concepts/glossary.md b/docs/concepts/glossary.md index a09b0f07853..3bfe71bcb4a 100644 --- a/docs/concepts/glossary.md +++ b/docs/concepts/glossary.md @@ -6,4 +6,4 @@ * **segment**: routes are split into one minute chunks called segments. * **comma connect**: the web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai). * **panda**: this is the secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda). -* **comma 3X**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop](https://comma.ai/shop). +* **comma four**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four). diff --git a/docs/getting-started/what-is-openpilot.md b/docs/getting-started/what-is-openpilot.md index b3c56c8410d..6fab2b979b1 100644 --- a/docs/getting-started/what-is-openpilot.md +++ b/docs/getting-started/what-is-openpilot.md @@ -5,7 +5,7 @@ ## How do I use it? -openpilot is designed to be used on the comma 3X. +openpilot is designed to be used on the comma four. ## How does it work? diff --git a/docs/how-to/connect-to-comma.md b/docs/how-to/connect-to-comma.md index 5f02e115994..58d4f91bb2b 100644 --- a/docs/how-to/connect-to-comma.md +++ b/docs/how-to/connect-to-comma.md @@ -1,15 +1,15 @@ -# connect to a comma 3X +# connect to a comma four -A comma 3X is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). +A comma four is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). ## Serial Console -On both the comma three and 3X, the serial console is accessible from the main OBD-C port. -Connect the comma 3X to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power. +On both the comma three and comma four, the serial console is accessible from the main OBD-C port. +Connect the comma four to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power. On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect. -On the comma 3X, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. +On the comma four, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. * Username: `comma` * Password: `comma` @@ -45,7 +45,7 @@ In order to use ADB on your device, you'll need to perform the following steps u * Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555` > [!NOTE] -> The default port for ADB is 5555 on the comma 3X. +> The default port for ADB is 5555 on the comma four. For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb). diff --git a/docs/how-to/replay-a-drive.md b/docs/how-to/replay-a-drive.md index b0db36a46f0..a11b29dcc4e 100644 --- a/docs/how-to/replay-a-drive.md +++ b/docs/how-to/replay-a-drive.md @@ -8,7 +8,7 @@ Replaying is a critical tool for openpilot development and debugging. Just run `tools/replay/replay --demo`. ## Replaying CAN data -*Hardware required: jungle and comma 3X* +*Hardware required: jungle and comma four* 1. Connect your PC to a jungle. 2. diff --git a/docs/how-to/turn-the-speed-blue.md b/docs/how-to/turn-the-speed-blue.md index 644c35e0abe..bc1d6340129 100644 --- a/docs/how-to/turn-the-speed-blue.md +++ b/docs/how-to/turn-the-speed-blue.md @@ -3,7 +3,7 @@ In 30 minutes, we'll get an openpilot development environment set up on your computer and make some changes to openpilot's UI. -And if you have a comma 3X, we'll deploy the change to your device for testing. +And if you have a comma four, we'll deploy the change to your device for testing. ## 1. Set up your development environment From d5e75dd0afafac5e449f647e92320646831ff35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 20 Mar 2026 15:29:58 -0700 Subject: [PATCH 570/685] locationd: publish filter time (#37697) * Include filter time in the message * Move the line up * Use nantonum --- cereal/log.capnp | 2 ++ selfdrive/locationd/locationd.py | 1 + selfdrive/test/process_replay/migration.py | 1 + 3 files changed, 4 insertions(+) diff --git a/cereal/log.capnp b/cereal/log.capnp index d1f85d325ca..c26c1f9d3a2 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -1426,6 +1426,8 @@ struct LivePose { posenetOK @5 :Bool = false; sensorsOK @6 :Bool = false; + timestamp @8 :UInt64; + debugFilterState @7 :FilterState; struct XYZMeasurement { diff --git a/selfdrive/locationd/locationd.py b/selfdrive/locationd/locationd.py index a34a584ff37..57aecb22e73 100755 --- a/selfdrive/locationd/locationd.py +++ b/selfdrive/locationd/locationd.py @@ -239,6 +239,7 @@ def get_msg(self, sensors_valid: bool, inputs_valid: bool, filter_valid: bool): livePose.inputsOK = inputs_valid livePose.posenetOK = not std_spike or self.car_speed <= 5.0 livePose.sensorsOK = sensors_valid + livePose.timestamp = int(np.nan_to_num(self.kf.t) * 1e9) return msg diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 232722d1b1e..2a2633c34bb 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -175,6 +175,7 @@ def migrate_liveLocationKalman(msgs): m = messaging.new_message('livePose') m.valid = msg.valid m.logMonoTime = msg.logMonoTime + m.livePose.timestamp = msg.logMonoTime for field in ["orientationNED", "velocityDevice", "accelerationDevice", "angularVelocityDevice"]: lp_field, llk_field = getattr(m.livePose, field), getattr(msg.liveLocationKalmanDEPRECATED, field) lp_field.x, lp_field.y, lp_field.z = llk_field.value or nans From 240e0036d257cef2bb9d7291ee64b5f076555db1 Mon Sep 17 00:00:00 2001 From: royjr Date: Fri, 20 Mar 2026 18:52:01 -0400 Subject: [PATCH 571/685] macOS: fix build (#37686) * Update SConscript * do we need this? * fix that --------- Co-authored-by: Adeeb Shihadeh --- tools/cabana/SConscript | 2 +- tools/cabana/cabana | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index dbe4dbc6593..7c5581f5269 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -69,7 +69,7 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": - base_frameworks.append('QtCharts') + base_frameworks += ['QtCharts', 'CoreFoundation', 'IOKit', 'Security'] else: base_libs.append('Qt5Charts') diff --git a/tools/cabana/cabana b/tools/cabana/cabana index 00709734a54..128e49400e8 100755 --- a/tools/cabana/cabana +++ b/tools/cabana/cabana @@ -33,6 +33,6 @@ fi # Build _cabana cd "$ROOT" -scons -j"$(nproc)" tools/cabana/_cabana +scons -j4 tools/cabana/_cabana exec "$DIR/_cabana" "$@" From 08d8bb9975a3b42fdd2d2fecb81e2a015c98bcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 20 Mar 2026 18:19:47 -0700 Subject: [PATCH 572/685] livePose timestamp migration (#37705) * Add livePose migraiton * Fix --- selfdrive/test/process_replay/migration.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 2a2633c34bb..14b38e0481a 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -39,6 +39,7 @@ def migrate_all(lr: LogIterable, manager_states: bool = False, panda_states: boo migrate_controlsState, migrate_carState, migrate_liveLocationKalman, + migrate_livePose, migrate_liveTracks, migrate_driverAssistance, migrate_drivingModelData, @@ -187,6 +188,21 @@ def migrate_liveLocationKalman(msgs): return ops, [], [] +@migration(inputs=["livePose"]) +def migrate_livePose(msgs): + ops = [] + needs_migration = all(msg.livePose.timestamp == 0 for _, msg in msgs if msg.which() == 'livePose') + if not needs_migration: + return [], [], [] + + for index, msg in msgs: + if msg.which() == "livePose": + new_msg = msg.as_builder() + new_msg.livePose.timestamp = msg.logMonoTime + ops.append((index, new_msg.as_reader())) + return ops, [], [] + + @migration(inputs=["controlsState"], product="selfdriveState") def migrate_controlsState(msgs): add_ops = [] From 7fae59167e6c156fb4a378501b1e0a20cf1e8b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Fri, 20 Mar 2026 19:10:59 -0700 Subject: [PATCH 573/685] paramsd/torqued: use the correct livePose timestamp (#37704) * Use the correct filter time in torqued/paramsd * Fix * Check if lp valid * Update tests fake data with new required fields --- selfdrive/controls/tests/test_torqued_lat_accel_offset.py | 6 ++++-- selfdrive/locationd/paramsd.py | 1 + selfdrive/locationd/torqued.py | 4 +++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/selfdrive/controls/tests/test_torqued_lat_accel_offset.py b/selfdrive/controls/tests/test_torqued_lat_accel_offset.py index 84389856b64..2f95d7c14f7 100644 --- a/selfdrive/controls/tests/test_torqued_lat_accel_offset.py +++ b/selfdrive/controls/tests/test_torqued_lat_accel_offset.py @@ -50,8 +50,10 @@ def simulate_straight_road_msgs(est): lat_accels = TORQUE_TUNE.latAccelFactor * steer_torques for t, steer_torque, lat_accel in zip(ts, steer_torques, lat_accels, strict=True): carOutput.actuatorsOutput.torque = float(-steer_torque) - livePose.orientationNED.x = float(np.deg2rad(ROLL_BIAS_DEG)) - livePose.angularVelocityDevice.z = float(lat_accel / V_EGO) + livePose.orientationNED = {'x': float(np.deg2rad(ROLL_BIAS_DEG)), 'valid': True} + livePose.angularVelocityDevice = {'z': float(lat_accel / V_EGO), 'valid': True} + livePose.inputsOK, livePose.sensorsOK, livePose.posenetOK = True, True, True + livePose.timestamp = int(t * 1e9) for which, msg in (('carControl', carControl), ('carOutput', carOutput), ('carState', carState), ('livePose', livePose)): est.handle_log(t, which, msg) diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index fd03d3d0936..0489ae41744 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -65,6 +65,7 @@ def reset(self, t: float | None): def handle_log(self, t: float, which: str, msg: capnp._DynamicStructReader): if which == 'livePose': + t = msg.timestamp * 1e-9 device_pose = Pose.from_live_pose(msg) calibrated_pose = self.calibrator.build_calibrated_pose(device_pose) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index f4dc5a14716..9a2b6c17b16 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -180,7 +180,9 @@ def handle_log(self, t, which, msg): self.lag = msg.lateralDelay # calculate lateral accel from past steering torque elif which == "livePose": - if len(self.raw_points['steer_torque']) == self.hist_len: + is_valid = msg.angularVelocityDevice.valid and msg.orientationNED.valid and msg.inputsOK and msg.sensorsOK and msg.posenetOK + if len(self.raw_points['steer_torque']) == self.hist_len and is_valid: + t = msg.timestamp * 1e-9 device_pose = Pose.from_live_pose(msg) calibrated_pose = self.calibrator.build_calibrated_pose(device_pose) angular_velocity_calibrated = calibrated_pose.angular_velocity From af09b7a45b9aafd111cb89192f7432dc6b96b013 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 21 Mar 2026 09:47:15 -0700 Subject: [PATCH 574/685] add imgui package (#37711) --- pyproject.toml | 1 + uv.lock | 321 +++++++++++++++++++++++++------------------------ 2 files changed, 168 insertions(+), 154 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index a754f580bdd..a1123234001 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,6 +107,7 @@ dev = [ ] tools = [ + "imgui @ git+https://github.com/commaai/dependencies.git@release-imgui#subdirectory=imgui", "metadrive-simulator @ git+https://github.com/commaai/metadrive.git@minimal ; (platform_machine != 'aarch64')", ] diff --git a/uv.lock b/uv.lock index 8e597db6ef1..2e7a0f0ac97 100644 --- a/uv.lock +++ b/uv.lock @@ -91,11 +91,11 @@ wheels = [ [[package]] name = "attrs" -version = "25.4.0" +version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, ] [[package]] @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#4808f76c45cb797e697ab21e5e37d68a0ab3b2d4" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#347ac440220660253b244526e6e0ed96bea63595" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#f735fd22c66029b92019019d0596da6a4445b931" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#8883c2a61e7056212e9b541a954cc96e5dfd4fdf" } [[package]] name = "casadi" @@ -174,27 +174,27 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, - { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, - { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, - { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, - { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, - { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, - { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, - { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, - { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, - { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, - { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, - { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, - { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, - { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, - { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +version = "3.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, ] [[package]] @@ -211,11 +211,11 @@ wheels = [ [[package]] name = "codespell" -version = "2.4.1" +version = "2.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/15/e0/709453393c0ea77d007d907dd436b3ee262e28b30995ea1aa36c6ffbccaf/codespell-2.4.1.tar.gz", hash = "sha256:299fcdcb09d23e81e35a671bbe746d5ad7e8385972e65dbb833a2eaac33c01e5", size = 344740, upload-time = "2025-01-28T18:52:39.411Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2d/9d/1d0903dff693160f893ca6abcabad545088e7a2ee0a6deae7c24e958be69/codespell-2.4.2.tar.gz", hash = "sha256:3c33be9ae34543807f088aeb4832dfad8cb2dae38da61cac0a7045dd376cfdf3", size = 352058, upload-time = "2026-03-05T18:10:42.936Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/01/b394922252051e97aab231d416c86da3d8a6d781eeadcdca1082867de64e/codespell-2.4.1-py3-none-any.whl", hash = "sha256:3dadafa67df7e4a3dbf51e0d7315061b80d265f9552ebd699b3dd6834b47e425", size = 344501, upload-time = "2025-01-28T18:52:37.057Z" }, + { url = "https://files.pythonhosted.org/packages/42/a1/52fa05533e95fe45bcc09bcf8a503874b1c08f221a4e35608017e0938f55/codespell-2.4.2-py3-none-any.whl", hash = "sha256:97e0c1060cf46bd1d5db89a936c98db8c2b804e1fdd4b5c645e82a1ec6b1f886", size = 353715, upload-time = "2026-03-05T18:10:41.398Z" }, ] [[package]] @@ -251,26 +251,26 @@ wheels = [ [[package]] name = "coverage" -version = "7.13.4" +version = "7.13.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, - { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, - { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, - { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, - { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, - { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, - { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, - { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, - { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, - { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, - { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, - { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, - { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, ] [[package]] @@ -371,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#fc9915c3a81d6488eafcdbbdc428f15d8123e540" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#42ca1061f8bfff9b0d8836781e4222bd6dd28a31" } [[package]] name = "execnet" @@ -385,23 +385,23 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#8d693da088e5905d4479550e07484961765df45b" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#f33e25f6fd36f3c581add7bb3740f659aab002c1" } [[package]] name = "fonttools" -version = "4.61.1" +version = "4.62.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/ca/cf17b88a8df95691275a3d77dc0a5ad9907f328ae53acbe6795da1b2f5ed/fonttools-4.61.1.tar.gz", hash = "sha256:6675329885c44657f826ef01d9e4fb33b9158e9d93c537d84ad8399539bc6f69", size = 3565756, upload-time = "2025-12-12T17:31:24.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/16/7decaa24a1bd3a70c607b2e29f0adc6159f36a7e40eaba59846414765fd4/fonttools-4.61.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f3cb4a569029b9f291f88aafc927dd53683757e640081ca8c412781ea144565e", size = 2851593, upload-time = "2025-12-12T17:30:04.225Z" }, - { url = "https://files.pythonhosted.org/packages/94/98/3c4cb97c64713a8cf499b3245c3bf9a2b8fd16a3e375feff2aed78f96259/fonttools-4.61.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:41a7170d042e8c0024703ed13b71893519a1a6d6e18e933e3ec7507a2c26a4b2", size = 2400231, upload-time = "2025-12-12T17:30:06.47Z" }, - { url = "https://files.pythonhosted.org/packages/b7/37/82dbef0f6342eb01f54bca073ac1498433d6ce71e50c3c3282b655733b31/fonttools-4.61.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10d88e55330e092940584774ee5e8a6971b01fc2f4d3466a1d6c158230880796", size = 4954103, upload-time = "2025-12-12T17:30:08.432Z" }, - { url = "https://files.pythonhosted.org/packages/6c/44/f3aeac0fa98e7ad527f479e161aca6c3a1e47bb6996b053d45226fe37bf2/fonttools-4.61.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:15acc09befd16a0fb8a8f62bc147e1a82817542d72184acca9ce6e0aeda9fa6d", size = 5004295, upload-time = "2025-12-12T17:30:10.56Z" }, - { url = "https://files.pythonhosted.org/packages/14/e8/7424ced75473983b964d09f6747fa09f054a6d656f60e9ac9324cf40c743/fonttools-4.61.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e6bcdf33aec38d16508ce61fd81838f24c83c90a1d1b8c68982857038673d6b8", size = 4944109, upload-time = "2025-12-12T17:30:12.874Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8b/6391b257fa3d0b553d73e778f953a2f0154292a7a7a085e2374b111e5410/fonttools-4.61.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5fade934607a523614726119164ff621e8c30e8fa1ffffbbd358662056ba69f0", size = 5093598, upload-time = "2025-12-12T17:30:15.79Z" }, - { url = "https://files.pythonhosted.org/packages/d9/71/fd2ea96cdc512d92da5678a1c98c267ddd4d8c5130b76d0f7a80f9a9fde8/fonttools-4.61.1-cp312-cp312-win32.whl", hash = "sha256:75da8f28eff26defba42c52986de97b22106cb8f26515b7c22443ebc9c2d3261", size = 2269060, upload-time = "2025-12-12T17:30:18.058Z" }, - { url = "https://files.pythonhosted.org/packages/80/3b/a3e81b71aed5a688e89dfe0e2694b26b78c7d7f39a5ffd8a7d75f54a12a8/fonttools-4.61.1-cp312-cp312-win_amd64.whl", hash = "sha256:497c31ce314219888c0e2fce5ad9178ca83fe5230b01a5006726cdf3ac9f24d9", size = 2319078, upload-time = "2025-12-12T17:30:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/c7/4e/ce75a57ff3aebf6fc1f4e9d508b8e5810618a33d900ad6c19eb30b290b97/fonttools-4.61.1-py3-none-any.whl", hash = "sha256:17d2bf5d541add43822bcf0c43d7d847b160c9bb01d15d5007d84e2217aaa371", size = 1148996, upload-time = "2025-12-12T17:31:21.03Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, ] [[package]] @@ -432,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#e101138b29023effc932df7a58fb76a26c4e443a" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#770e647fd6e1db963c8d8168a223bebb8696c8b1" } [[package]] name = "ghp-import" @@ -449,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#5407b1d37b7a8a9ae3747cc20cb6e7a7b01f5059" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#75aa1619f3a1d283979aa404fec7b0c777c237bf" } [[package]] name = "google-crc32c" @@ -495,6 +495,11 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/9c/1f/19ebc343cc71a7ffa78f17018535adc5cbdd87afb31d7c34874680148b32/ifaddr-0.2.0-py3-none-any.whl", hash = "sha256:085e0305cfe6f16ab12d72e2024030f5d52674afad6911bb1eee207177b8a748", size = 12314, upload-time = "2022-06-15T21:40:25.756Z" }, ] +[[package]] +name = "imgui" +version = "1.92.7" +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui#013161b390b962778878a1d42cbe7cc738e5537f" } + [[package]] name = "iniconfig" version = "2.3.0" @@ -545,29 +550,35 @@ wheels = [ [[package]] name = "kiwisolver" -version = "1.4.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, - { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, - { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, - { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, - { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, - { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, ] [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#83fa530843e5109c51aef14327b6fde5dcb4507b" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#8ca1f9de9dc7a31e65454f7f987bbf8aa673e908" } [[package]] name = "libusb" @@ -588,7 +599,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#600cdd08cb77cbcc001daeb031abcb5c6008c7c2" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#4d09f32d9de02c74adc3c0cbbd0e59ae2193217a" } [[package]] name = "markdown" @@ -689,16 +700,16 @@ wheels = [ [[package]] name = "mkdocs-get-deps" -version = "0.2.0" +version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mergedeep" }, { name = "platformdirs" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/f5/ed29cd50067784976f25ed0ed6fcd3c2ce9eb90650aa3b2796ddf7b6870b/mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c", size = 10239, upload-time = "2023-11-20T17:51:09.981Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/d4/029f984e8d3f3b6b726bd33cafc473b75e9e44c0f7e80a5b29abc466bdea/mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134", size = 9521, upload-time = "2023-11-20T17:51:08.587Z" }, + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, ] [[package]] @@ -740,25 +751,25 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#0503ac0d54799b58c84f900dba75abcad17e780f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#a1f3891afb2143ae34d80b6ecd502bda30fb1e28" } [[package]] name = "numpy" -version = "2.4.2" +version = "2.4.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, - { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, - { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, - { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, - { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, - { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, - { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, - { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, + { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, + { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, + { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, + { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, + { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, + { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, + { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, + { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, + { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, ] [[package]] @@ -855,6 +866,7 @@ testing = [ { name = "ty" }, ] tools = [ + { name = "imgui" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64'" }, ] @@ -876,6 +888,7 @@ requires-dist = [ { name = "gcc-arm-none-eabi", git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi" }, { name = "git-lfs", git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs" }, { name = "hypothesis", marker = "extra == 'testing'", specifier = "==6.47.*" }, + { name = "imgui", marker = "extra == 'tools'", git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui" }, { name = "inputs" }, { name = "jeepney" }, { name = "jinja2", marker = "extra == 'docs'" }, @@ -1003,11 +1016,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.2" +version = "4.9.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, ] [[package]] @@ -1144,11 +1157,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.11.0" +version = "2.12.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, ] [[package]] @@ -1175,15 +1188,15 @@ wheels = [ [[package]] name = "pyopenssl" -version = "25.3.0" +version = "26.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/be/97b83a464498a79103036bc74d1038df4a7ef0e402cfaf4d5e113fb14759/pyopenssl-25.3.0.tar.gz", hash = "sha256:c981cb0a3fd84e8602d7afc209522773b94c1c2446a3c710a75b06fe1beae329", size = 184073, upload-time = "2025-09-17T00:32:21.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8e/11/a62e1d33b373da2b2c2cd9eb508147871c80f12b1cacde3c5d314922afdd/pyopenssl-26.0.0.tar.gz", hash = "sha256:f293934e52936f2e3413b89c6ce36df66a0b34ae1ea3a053b8c5020ff2f513fc", size = 185534, upload-time = "2026-03-15T14:28:26.353Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/81/ef2b1dfd1862567d573a4fdbc9f969067621764fbb74338496840a1d2977/pyopenssl-25.3.0-py3-none-any.whl", hash = "sha256:1fda6fc034d5e3d179d39e59c1895c9faeaf40a79de5fc4cbbfbe0d36f4a77b6", size = 57268, upload-time = "2025-09-17T00:32:19.474Z" }, + { url = "https://files.pythonhosted.org/packages/fb/7d/d4f7d908fa8415571771b30669251d57c3cf313b36a856e6d7548ae01619/pyopenssl-26.0.0-py3-none-any.whl", hash = "sha256:df94d28498848b98cc1c0ffb8ef1e71e40210d3b0a8064c9d29571ed2904bf81", size = 57969, upload-time = "2026-03-15T14:28:24.864Z" }, ] [[package]] @@ -1398,27 +1411,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/da/31/d6e536cdebb6568ae75a7f00e4b4819ae0ad2640c3604c305a0428680b0c/ruff-0.15.4.tar.gz", hash = "sha256:3412195319e42d634470cc97aa9803d07e9d5c9223b99bcb1518f0c725f26ae1", size = 4569550, upload-time = "2026-02-26T20:04:14.959Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/82/c11a03cfec3a4d26a0ea1e571f0f44be5993b923f905eeddfc397c13d360/ruff-0.15.4-py3-none-linux_armv6l.whl", hash = "sha256:a1810931c41606c686bae8b5b9a8072adac2f611bb433c0ba476acba17a332e0", size = 10453333, upload-time = "2026-02-26T20:04:20.093Z" }, - { url = "https://files.pythonhosted.org/packages/ce/5d/6a1f271f6e31dffb31855996493641edc3eef8077b883eaf007a2f1c2976/ruff-0.15.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5a1632c66672b8b4d3e1d1782859e98d6e0b4e70829530666644286600a33992", size = 10853356, upload-time = "2026-02-26T20:04:05.808Z" }, - { url = "https://files.pythonhosted.org/packages/b1/d8/0fab9f8842b83b1a9c2bf81b85063f65e93fb512e60effa95b0be49bfc54/ruff-0.15.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:a4386ba2cd6c0f4ff75252845906acc7c7c8e1ac567b7bc3d373686ac8c222ba", size = 10187434, upload-time = "2026-02-26T20:03:54.656Z" }, - { url = "https://files.pythonhosted.org/packages/85/cc/cc220fd9394eff5db8d94dec199eec56dd6c9f3651d8869d024867a91030/ruff-0.15.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2496488bdfd3732747558b6f95ae427ff066d1fcd054daf75f5a50674411e75", size = 10535456, upload-time = "2026-02-26T20:03:52.738Z" }, - { url = "https://files.pythonhosted.org/packages/fa/0f/bced38fa5cf24373ec767713c8e4cadc90247f3863605fb030e597878661/ruff-0.15.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3f1c4893841ff2d54cbda1b2860fa3260173df5ddd7b95d370186f8a5e66a4ac", size = 10287772, upload-time = "2026-02-26T20:04:08.138Z" }, - { url = "https://files.pythonhosted.org/packages/2b/90/58a1802d84fed15f8f281925b21ab3cecd813bde52a8ca033a4de8ab0e7a/ruff-0.15.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:820b8766bd65503b6c30aaa6331e8ef3a6e564f7999c844e9a547c40179e440a", size = 11049051, upload-time = "2026-02-26T20:04:03.53Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ac/b7ad36703c35f3866584564dc15f12f91cb1a26a897dc2fd13d7cb3ae1af/ruff-0.15.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9fb74bab47139c1751f900f857fa503987253c3ef89129b24ed375e72873e85", size = 11890494, upload-time = "2026-02-26T20:04:10.497Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/3eb2f47a39a8b0da99faf9c54d3eb24720add1e886a5309d4d1be73a6380/ruff-0.15.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f80c98765949c518142b3a50a5db89343aa90f2c2bf7799de9986498ae6176db", size = 11326221, upload-time = "2026-02-26T20:04:12.84Z" }, - { url = "https://files.pythonhosted.org/packages/ff/90/bf134f4c1e5243e62690e09d63c55df948a74084c8ac3e48a88468314da6/ruff-0.15.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:451a2e224151729b3b6c9ffb36aed9091b2996fe4bdbd11f47e27d8f2e8888ec", size = 11168459, upload-time = "2026-02-26T20:04:00.969Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e5/a64d27688789b06b5d55162aafc32059bb8c989c61a5139a36e1368285eb/ruff-0.15.4-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:a8f157f2e583c513c4f5f896163a93198297371f34c04220daf40d133fdd4f7f", size = 11104366, upload-time = "2026-02-26T20:03:48.099Z" }, - { url = "https://files.pythonhosted.org/packages/f1/f6/32d1dcb66a2559763fc3027bdd65836cad9eb09d90f2ed6a63d8e9252b02/ruff-0.15.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:917cc68503357021f541e69b35361c99387cdbbf99bd0ea4aa6f28ca99ff5338", size = 10510887, upload-time = "2026-02-26T20:03:45.771Z" }, - { url = "https://files.pythonhosted.org/packages/ff/92/22d1ced50971c5b6433aed166fcef8c9343f567a94cf2b9d9089f6aa80fe/ruff-0.15.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e9737c8161da79fd7cfec19f1e35620375bd8b2a50c3e77fa3d2c16f574105cc", size = 10285939, upload-time = "2026-02-26T20:04:22.42Z" }, - { url = "https://files.pythonhosted.org/packages/e6/f4/7c20aec3143837641a02509a4668fb146a642fd1211846634edc17eb5563/ruff-0.15.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:291258c917539e18f6ba40482fe31d6f5ac023994ee11d7bdafd716f2aab8a68", size = 10765471, upload-time = "2026-02-26T20:03:58.924Z" }, - { url = "https://files.pythonhosted.org/packages/d0/09/6d2f7586f09a16120aebdff8f64d962d7c4348313c77ebb29c566cefc357/ruff-0.15.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3f83c45911da6f2cd5936c436cf86b9f09f09165f033a99dcf7477e34041cbc3", size = 11263382, upload-time = "2026-02-26T20:04:24.424Z" }, - { url = "https://files.pythonhosted.org/packages/1b/fa/2ef715a1cd329ef47c1a050e10dee91a9054b7ce2fcfdd6a06d139afb7ec/ruff-0.15.4-py3-none-win32.whl", hash = "sha256:65594a2d557d4ee9f02834fcdf0a28daa8b3b9f6cb2cb93846025a36db47ef22", size = 10506664, upload-time = "2026-02-26T20:03:50.56Z" }, - { url = "https://files.pythonhosted.org/packages/d0/a8/c688ef7e29983976820d18710f955751d9f4d4eb69df658af3d006e2ba3e/ruff-0.15.4-py3-none-win_amd64.whl", hash = "sha256:04196ad44f0df220c2ece5b0e959c2f37c777375ec744397d21d15b50a75264f", size = 11651048, upload-time = "2026-02-26T20:04:17.191Z" }, - { url = "https://files.pythonhosted.org/packages/3e/0a/9e1be9035b37448ce2e68c978f0591da94389ade5a5abafa4cf99985d1b2/ruff-0.15.4-py3-none-win_arm64.whl", hash = "sha256:60d5177e8cfc70e51b9c5fad936c634872a74209f934c1e79107d11787ad5453", size = 10966776, upload-time = "2026-02-26T20:03:56.908Z" }, +version = "0.15.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, + { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, + { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, + { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, + { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, + { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, + { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, + { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, + { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, + { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, + { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, + { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, + { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, ] [[package]] @@ -1432,15 +1445,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.54.0" +version = "2.55.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c8/e9/2e3a46c304e7fa21eaa70612f60354e32699c7102eb961f67448e222ad7c/sentry_sdk-2.54.0.tar.gz", hash = "sha256:2620c2575128d009b11b20f7feb81e4e4e8ae08ec1d36cbc845705060b45cc1b", size = 413813, upload-time = "2026-03-02T15:12:41.355Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e9/b8/285293dc60fc198fffc3fcdbc7c6d4e646e0f74e61461c355d40faa64ceb/sentry_sdk-2.55.0.tar.gz", hash = "sha256:3774c4d8820720ca4101548131b9c162f4c9426eb7f4d24aca453012a7470f69", size = 424505, upload-time = "2026-03-17T14:15:51.707Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/39/be412cc86bc6247b8f69e9383d7950711bd86f8d0a4a4b0fe8fad685bc21/sentry_sdk-2.54.0-py2.py3-none-any.whl", hash = "sha256:fd74e0e281dcda63afff095d23ebcd6e97006102cdc8e78a29f19ecdf796a0de", size = 439198, upload-time = "2026-03-02T15:12:39.546Z" }, + { url = "https://files.pythonhosted.org/packages/9a/66/20465097782d7e1e742d846407ea7262d338c6e876ddddad38ca8907b38f/sentry_sdk-2.55.0-py2.py3-none-any.whl", hash = "sha256:97026981cb15699394474a196b88503a393cbc58d182ece0d3abe12b9bd978d4", size = 449284, upload-time = "2026-03-17T14:15:49.604Z" }, ] [[package]] @@ -1463,11 +1476,11 @@ wheels = [ [[package]] name = "setuptools" -version = "82.0.0" +version = "82.0.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/82/f3/748f4d6f65d1756b9ae577f329c951cda23fb900e4de9f70900ced962085/setuptools-82.0.0.tar.gz", hash = "sha256:22e0a2d69474c6ae4feb01951cb69d515ed23728cf96d05513d36e42b62b37cb", size = 1144893, upload-time = "2026-02-08T15:08:40.206Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/c6/76dc613121b793286a3f91621d7b75a2b493e0390ddca50f11993eadf192/setuptools-82.0.0-py3-none-any.whl", hash = "sha256:70b18734b607bd1da571d097d236cfcfacaf01de45717d59e6e04b96877532e0", size = 1003468, upload-time = "2026-02-08T15:08:38.723Z" }, + { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, ] [[package]] @@ -1536,26 +1549,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.20" +version = "0.0.24" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/95/8de69bb98417227b01f1b1d743c819d6456c9fd140255b6124b05b17dfd6/ty-0.0.20.tar.gz", hash = "sha256:ebba6be7974c14efbb2a9adda6ac59848f880d7259f089dfa72a093039f1dcc6", size = 5262529, upload-time = "2026-03-02T15:51:36.587Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/96/652a425030f95dc2c9548d9019e52502e17079e1daeefbc4036f1c0905b4/ty-0.0.24.tar.gz", hash = "sha256:9fe42f6b98207bdaef51f71487d6d087f2cb02555ee3939884d779b2b3cc8bfc", size = 5354286, upload-time = "2026-03-19T16:55:57.035Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/2c/718abe48393e521bf852cd6b0f984766869b09c258d6e38a118768a91731/ty-0.0.20-py3-none-linux_armv6l.whl", hash = "sha256:7cc12769c169c9709a829c2248ee2826b7aae82e92caeac813d856f07c021eae", size = 10333656, upload-time = "2026-03-02T15:51:56.461Z" }, - { url = "https://files.pythonhosted.org/packages/41/0e/eb1c4cc4a12862e2327b72657bcebb10b7d9f17046f1bdcd6457a0211615/ty-0.0.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:3b777c1bf13bc0a95985ebb8a324b8668a4a9b2e514dde5ccf09e4d55d2ff232", size = 10168505, upload-time = "2026-03-02T15:51:51.895Z" }, - { url = "https://files.pythonhosted.org/packages/89/7f/10230798e673f0dd3094dfd16e43bfd90e9494e7af6e8e7db516fb431ddf/ty-0.0.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b2a4a7db48bf8cba30365001bc2cad7fd13c1a5aacdd704cc4b7925de8ca5eb3", size = 9678510, upload-time = "2026-03-02T15:51:48.451Z" }, - { url = "https://files.pythonhosted.org/packages/7a/3d/59d9159577494edd1728f7db77b51bb07884bd21384f517963114e3ab5f6/ty-0.0.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6846427b8b353a43483e9c19936dc6a25612573b44c8f7d983dfa317e7f00d4c", size = 10162926, upload-time = "2026-03-02T15:51:40.558Z" }, - { url = "https://files.pythonhosted.org/packages/9c/a8/b7273eec3e802f78eb913fbe0ce0c16ef263723173e06a5776a8359b2c66/ty-0.0.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:245ceef5bd88df366869385cf96411cb14696334f8daa75597cf7e41c3012eb8", size = 10171702, upload-time = "2026-03-02T15:51:44.069Z" }, - { url = "https://files.pythonhosted.org/packages/9f/32/5f1144f2f04a275109db06e3498450c4721554215b80ae73652ef412eeab/ty-0.0.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c4d21d1cdf67a444d3c37583c17291ddba9382a9871021f3f5d5735e09e85efe", size = 10682552, upload-time = "2026-03-02T15:51:33.102Z" }, - { url = "https://files.pythonhosted.org/packages/6a/db/9f1f637310792f12bd6ed37d5fc8ab39ba1a9b0c6c55a33865e9f1cad840/ty-0.0.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bd4ffd907d1bd70e46af9e9a2f88622f215e1bf44658ea43b32c2c0b357299e4", size = 11242605, upload-time = "2026-03-02T15:51:34.895Z" }, - { url = "https://files.pythonhosted.org/packages/1a/68/cc9cae2e732fcfd20ccdffc508407905a023fc8493b8771c392d915528dc/ty-0.0.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6594b58d8b0e9d16a22b3045fc1305db4b132c8d70c17784ab8c7a7cc986807", size = 10974655, upload-time = "2026-03-02T15:51:46.011Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c1/b9e3e3f28fe63486331e653f6aeb4184af8b1fe80542fcf74d2dda40a93d/ty-0.0.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3662f890518ce6cf4d7568f57d03906912d2afbf948a01089a28e325b1ef198c", size = 10761325, upload-time = "2026-03-02T15:51:26.818Z" }, - { url = "https://files.pythonhosted.org/packages/39/9e/67db935bdedf219a00fb69ec5437ba24dab66e0f2e706dd54a4eca234b84/ty-0.0.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e3ffbae58f9f0d17cdc4ac6d175ceae560b7ed7d54f9ddfb1c9f31054bcdc2c", size = 10145793, upload-time = "2026-03-02T15:51:38.562Z" }, - { url = "https://files.pythonhosted.org/packages/c7/de/b0eb815d4dc5a819c7e4faddc2a79058611169f7eef07ccc006531ce228c/ty-0.0.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:176e52bc8bb00b0e84efd34583962878a447a3a0e34ecc45fd7097a37554261b", size = 10189640, upload-time = "2026-03-02T15:51:50.202Z" }, - { url = "https://files.pythonhosted.org/packages/b8/71/63734923965cbb70df1da3e93e4b8875434e326b89e9f850611122f279bf/ty-0.0.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2bc73025418e976ca4143dde71fb9025a90754a08ac03e6aa9b80d4bed1294b", size = 10370568, upload-time = "2026-03-02T15:51:42.295Z" }, - { url = "https://files.pythonhosted.org/packages/32/a0/a532c2048533347dff48e9ca98bd86d2c224356e101688a8edaf8d6973fb/ty-0.0.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d52f7c9ec6e363e094b3c389c344d5a140401f14a77f0625e3f28c21918552f5", size = 10853999, upload-time = "2026-03-02T15:51:58.963Z" }, - { url = "https://files.pythonhosted.org/packages/48/88/36c652c658fe96658043e4abc8ea97801de6fb6e63ab50aaa82807bff1d8/ty-0.0.20-py3-none-win32.whl", hash = "sha256:c7d32bfe93f8fcaa52b6eef3f1b930fd7da410c2c94e96f7412c30cfbabf1d17", size = 9744206, upload-time = "2026-03-02T15:51:54.183Z" }, - { url = "https://files.pythonhosted.org/packages/ff/a7/a4a13bed1d7fd9d97aaa3c5bb5e6d3e9a689e6984806cbca2ab4c9233cac/ty-0.0.20-py3-none-win_amd64.whl", hash = "sha256:a5e10f40fc4a0a1cbcb740a4aad5c7ce35d79f030836ea3183b7a28f43170248", size = 10711999, upload-time = "2026-03-02T15:51:29.212Z" }, - { url = "https://files.pythonhosted.org/packages/8d/7e/6bfd748a9f4ff9267ed3329b86a0f02cdf6ab49f87bc36c8a164852f99fc/ty-0.0.20-py3-none-win_arm64.whl", hash = "sha256:53f7a5c12c960e71f160b734f328eff9a35d578af4b67a36b0bb5990ac5cdc27", size = 10150143, upload-time = "2026-03-02T15:51:31.283Z" }, + { url = "https://files.pythonhosted.org/packages/da/e5/34457ee11708e734ba81ad65723af83030e484f961e281d57d1eecf08951/ty-0.0.24-py3-none-linux_armv6l.whl", hash = "sha256:1ab4f1f61334d533a3fdf5d9772b51b1300ac5da4f3cdb0be9657a3ccb2ce3e7", size = 10394877, upload-time = "2026-03-19T16:55:54.246Z" }, + { url = "https://files.pythonhosted.org/packages/44/81/bc9a1b1a87f43db15ab64ad781a4f999734ec3b470ad042624fa875b20e6/ty-0.0.24-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:facbf2c4aaa6985229e08f8f9bf152215eb078212f22b5c2411f35386688ab42", size = 10211109, upload-time = "2026-03-19T16:55:28.554Z" }, + { url = "https://files.pythonhosted.org/packages/e4/63/cfc805adeaa61d63ba3ea71127efa7d97c40ba36d97ee7bd957341d05107/ty-0.0.24-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b6d2a3b6d4470c483552a31e9b368c86f154dcc964bccb5406159dc9cd362246", size = 9694769, upload-time = "2026-03-19T16:55:34.309Z" }, + { url = "https://files.pythonhosted.org/packages/33/09/edc220726b6ec44a58900401f6b27140997ef15026b791e26b69a6e69eb5/ty-0.0.24-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c94c25d0500939fd5f8f16ce41cbed5b20528702c1d649bf80300253813f0a2", size = 10176287, upload-time = "2026-03-19T16:55:37.17Z" }, + { url = "https://files.pythonhosted.org/packages/f8/bf/cbe2227be711e65017655d8ee4d050f4c92b113fb4dc4c3bd6a19d3a86d8/ty-0.0.24-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:89cbe7bc7df0fab02dbd8cda79b737df83f1ef7fb573b08c0ee043dc68cffb08", size = 10214832, upload-time = "2026-03-19T16:56:08.518Z" }, + { url = "https://files.pythonhosted.org/packages/af/1d/d15803ee47e9143d10e10bd81ccc14761d08758082bda402950685f0ddfe/ty-0.0.24-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2c5d269bcc9b764850c99f457b5018a79b3ef40ecfbc03344e65effd6cf743", size = 10709892, upload-time = "2026-03-19T16:56:05.727Z" }, + { url = "https://files.pythonhosted.org/packages/36/12/6db0d86c477147f67b9052de209421d76c3e855197b000c25fcbbe86b3a2/ty-0.0.24-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba44512db5b97c3bbd59d93e11296e8548d0c9a3bdd1280de36d7ff22d351896", size = 11280872, upload-time = "2026-03-19T16:56:02.899Z" }, + { url = "https://files.pythonhosted.org/packages/1b/fc/155fe83a97c06d33ccc9e0f428258b32df2e08a428300c715d34757f0111/ty-0.0.24-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a52b7f589c3205512a9c50ba5b2b1e8c0698b72e51b8b9285c90420c06f1cae8", size = 11060520, upload-time = "2026-03-19T16:55:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/ac/f1/32c05a1c4c3c2a95c5b7361dee03a9bf1231d4ad096b161c838b45bce5a0/ty-0.0.24-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7981df5c709c054da4ac5d7c93f8feb8f45e69e829e4461df4d5f0988fe67d04", size = 10791455, upload-time = "2026-03-19T16:55:25.728Z" }, + { url = "https://files.pythonhosted.org/packages/17/2c/53c1ea6bedfa4d4ab64d4de262d8f5e405ecbffefd364459c628c0310d33/ty-0.0.24-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2860151ad95a00d0f0280b8fef79900d08dcd63276b57e6e5774f2c055979c5", size = 10156708, upload-time = "2026-03-19T16:55:45.563Z" }, + { url = "https://files.pythonhosted.org/packages/45/39/7d2919cf194707169474d80720a5f3d793e983416f25e7ffcf80504c9df2/ty-0.0.24-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5674a1146d927ab77ff198a88e0c4505134ced342a0e7d1beb4a076a728b7496", size = 10236263, upload-time = "2026-03-19T16:55:31.474Z" }, + { url = "https://files.pythonhosted.org/packages/cf/7f/48eac722f2fd12a5b7aae0effdcb75c46053f94b783d989e3ef0d7380082/ty-0.0.24-py3-none-musllinux_1_2_i686.whl", hash = "sha256:438ecbf1608a9b16dd84502f3f1b23ef2ef32bbd0ab3e0ca5a82f0e0d1cd41ea", size = 10402559, upload-time = "2026-03-19T16:55:39.602Z" }, + { url = "https://files.pythonhosted.org/packages/75/e0/8cf868b9749ce1e5166462759545964e95b02353243594062b927d8bff2a/ty-0.0.24-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ddeed3098dd92a83964e7aa7b41e509ba3530eb539fc4cd8322ff64a09daf1f5", size = 10893684, upload-time = "2026-03-19T16:55:51.439Z" }, + { url = "https://files.pythonhosted.org/packages/17/9f/f54bf3be01d2c2ed731d10a5afa3324dc66f987a6ae0a4a6cbfa2323d080/ty-0.0.24-py3-none-win32.whl", hash = "sha256:83013fb3a4764a8f8bcc6ca11ff8bdfd8c5f719fc249241cb2b8916e80778eb1", size = 9781542, upload-time = "2026-03-19T16:56:11.588Z" }, + { url = "https://files.pythonhosted.org/packages/fb/49/c004c5cc258b10b3a145666e9a9c28ae7678bc958c8926e8078d5d769081/ty-0.0.24-py3-none-win_amd64.whl", hash = "sha256:748a60eb6912d1cf27aaab105ffadb6f4d2e458a3fcadfbd3cf26db0d8062eeb", size = 10764801, upload-time = "2026-03-19T16:55:42.752Z" }, + { url = "https://files.pythonhosted.org/packages/e2/59/006a074e185bfccf5e4c026015245ab4fcd2362b13a8d24cf37a277909a9/ty-0.0.24-py3-none-win_arm64.whl", hash = "sha256:280a3d31e86d0721947238f17030c33f0911cae851d108ea9f4e3ab12a5ed01f", size = 10194093, upload-time = "2026-03-19T16:55:48.303Z" }, ] [[package]] @@ -1659,7 +1672,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#768bd6d6d67acc7b4e919993967187532af0d410" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#1bfcfd916e94f0b8ef94c9e6e783f765b07850bf" } [[package]] name = "zstandard" @@ -1689,4 +1702,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#4d4dd0b74dfc52bdeec36706fd1a3a27754679ec" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#3c54d941828579c8fc5956eb71b69df85d3b7f07" } From 470c3f4a92c99333ec293995dafd677f3c44833c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 21 Mar 2026 12:08:10 -0700 Subject: [PATCH 575/685] pandad: remove best case startup time test case --- selfdrive/pandad/tests/test_pandad.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/selfdrive/pandad/tests/test_pandad.py b/selfdrive/pandad/tests/test_pandad.py index 88d3939a6ad..6a5840d4875 100644 --- a/selfdrive/pandad/tests/test_pandad.py +++ b/selfdrive/pandad/tests/test_pandad.py @@ -78,22 +78,6 @@ def test_internal_panda_reset(self): assert any(Panda(s).is_internal() for s in Panda.list()) - def test_best_case_startup_time(self): - # run once so we're up to date - self._run_test(60) - - ts = [] - for _ in range(10): - # should be nearly instant this time - dt = self._run_test(5) - ts.append(dt) - - # 5s for USB (due to enumeration) - # - 0.2s pandad -> pandad - # - plus some buffer - print("startup times", ts, sum(ts) / len(ts)) - assert 0.1 < (sum(ts)/len(ts)) < 0.7 - def test_old_spi_protocol(self): # flash firmware with old SPI protocol self._flash_bootstub(os.path.join(HERE, "bootstub.panda_h7_spiv0.bin")) From a8b5c7450789f685774791e6fd6bb85bb006ba5f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sat, 21 Mar 2026 16:49:57 -0700 Subject: [PATCH 576/685] prep for imgui tools (#37712) * prep for imgui tools * build cleanups --- SConstruct | 13 ++++++++---- third_party/bootstrap/bootstrap-icons.ttf | 3 +++ third_party/bootstrap/pull.sh | 10 +++++++++ tools/cabana/SConscript | 5 +---- uv.lock | 26 +++++++++++------------ 5 files changed, 36 insertions(+), 21 deletions(-) create mode 100644 third_party/bootstrap/bootstrap-icons.ttf diff --git a/SConstruct b/SConstruct index feaadd5a41c..792a48eb7d3 100644 --- a/SConstruct +++ b/SConstruct @@ -47,8 +47,9 @@ pkgs = [importlib.import_module(name) for name in pkg_names] # be distributed with all Linux distros and macOS, or # vendored in commaai/dependencies. allowed_system_libs = { - "EGL", "GLESv2", "GL", "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", - "dl", "drm", "gbm", "m", "pthread", + "EGL", "GLESv2", "GL", + "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", + "dl", "drm", "gbm", "m", "pthread", } def _resolve_lib(env, name): @@ -253,8 +254,12 @@ SConscript([ 'selfdrive/ui/SConscript', ]) -if Dir('#tools/cabana/').exists() and arch != "larch64": - SConscript(['tools/cabana/SConscript']) +# Build tools +if arch != "larch64": + SConscript([ + 'tools/replay/SConscript', + 'tools/cabana/SConscript', + ]) env.CompilationDatabase('compile_commands.json') diff --git a/third_party/bootstrap/bootstrap-icons.ttf b/third_party/bootstrap/bootstrap-icons.ttf new file mode 100644 index 00000000000..49c8ea699a0 --- /dev/null +++ b/third_party/bootstrap/bootstrap-icons.ttf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:57e798d421bb56bb058ed9b0c83dd97fe1e411cde3a2bd6eb4a8705234f69027 +size 453096 diff --git a/third_party/bootstrap/pull.sh b/third_party/bootstrap/pull.sh index 0b03b4db9ef..5c4c955c043 100755 --- a/third_party/bootstrap/pull.sh +++ b/third_party/bootstrap/pull.sh @@ -12,3 +12,13 @@ cd icons git fetch --all git checkout d5aa187483a1b0b186f87adcfa8576350d970d98 cp bootstrap-icons.svg ../ + +# Convert WOFF → TTF for imgui (imgui only reads TTF/OTF) +python3 -c " +from fontTools.ttLib import TTFont +import io +f = TTFont('font/fonts/bootstrap-icons.woff') +f.flavor = None +f.save('../bootstrap-icons.ttf') +print('bootstrap-icons.ttf written') +" diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index 7c5581f5269..cc688ba679b 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -4,7 +4,7 @@ import shutil import libusb -Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') +Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'replay_lib') # Detect Qt - skip build if not available if arch == "Darwin": @@ -18,9 +18,6 @@ else: if not has_qt: Return() -SConscript(['#tools/replay/SConscript']) -Import('replay_lib') - qt_env = env.Clone() qt_modules = ["Widgets", "Gui", "Core"] diff --git a/uv.lock b/uv.lock index 2e7a0f0ac97..795c85bd527 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#347ac440220660253b244526e6e0ed96bea63595" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#fa14088a2deba2f4d511d4008e3a867dd8227867" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#8883c2a61e7056212e9b541a954cc96e5dfd4fdf" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#022412ed0d8f0ca3d566d6b9442a6867756194b9" } [[package]] name = "casadi" @@ -371,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#42ca1061f8bfff9b0d8836781e4222bd6dd28a31" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#79b2584590b24652c930dac2669fb8cd44624743" } [[package]] name = "execnet" @@ -385,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#f33e25f6fd36f3c581add7bb3740f659aab002c1" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#da4537d3bf8132e4a715ebe8f10efa956123c394" } [[package]] name = "fonttools" @@ -432,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#770e647fd6e1db963c8d8168a223bebb8696c8b1" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#11b0bc7decdb804608c7192e69edac3d3f08bedf" } [[package]] name = "ghp-import" @@ -449,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#75aa1619f3a1d283979aa404fec7b0c777c237bf" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#8afe53e9efe0ab4fd6b32ef2fa1408eb872806bc" } [[package]] name = "google-crc32c" @@ -498,7 +498,7 @@ wheels = [ [[package]] name = "imgui" version = "1.92.7" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui#013161b390b962778878a1d42cbe7cc738e5537f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui#23fe87ea4bfee37224134bfc8c8c26a832ea054e" } [[package]] name = "iniconfig" @@ -578,12 +578,12 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#8ca1f9de9dc7a31e65454f7f987bbf8aa673e908" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#24abc422ee0fd9d5485873ca673658bde791b061" } [[package]] name = "libusb" version = "1.0.29" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#5f188080524c3c6d098ab6cb8d206ceef0394e8e" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#9443bd2571f772a8538b8cf69e293519822ad354" } [[package]] name = "libusb1" @@ -599,7 +599,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#4d09f32d9de02c74adc3c0cbbd0e59ae2193217a" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#01e4405ee8c1ed8a73024b6cee27294c306a288f" } [[package]] name = "markdown" @@ -751,7 +751,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#a1f3891afb2143ae34d80b6ecd502bda30fb1e28" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#c4d49b4c8a9c804f708236f95933b857e98d50b5" } [[package]] name = "numpy" @@ -1672,7 +1672,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#1bfcfd916e94f0b8ef94c9e6e783f765b07850bf" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#8852c13c0ba3788704c0f0eb0d1b5586e7846510" } [[package]] name = "zstandard" @@ -1702,4 +1702,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#3c54d941828579c8fc5956eb71b69df85d3b7f07" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#70ffdccbe7bcf49f2d186c54a255eadf946b32f3" } From 31e4fe55ac666da16be0ade30d1f950731d309e5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Sun, 22 Mar 2026 17:36:35 -0700 Subject: [PATCH 577/685] tools: setup ffmpeg hwaccel (#37718) --- system/loggerd/SConscript | 11 ++++++++--- tools/cabana/SConscript | 4 +++- tools/replay/SConscript | 4 +++- uv.lock | 26 +++++++++++++------------- 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/system/loggerd/SConscript b/system/loggerd/SConscript index b02c4092408..45e8b25d208 100644 --- a/system/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -3,11 +3,16 @@ Import('env', 'arch', 'messaging', 'common', 'visionipc') libs = [common, messaging, visionipc, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'pthread', 'z', 'm', 'zstd'] +frameworks = [] src = ['logger.cc', 'zstd_writer.cc', 'video_writer.cc', 'encoder/encoder.cc', 'encoder/v4l_encoder.cc', 'encoder/jpeg_encoder.cc'] if arch != "larch64": src += ['encoder/ffmpeg_encoder.cc'] libs += ['yuv'] + if arch == "Darwin": + frameworks += ['VideoToolbox', 'CoreMedia', 'CoreFoundation', 'CoreVideo'] + else: + libs += ['va', 'va-drm', 'drm'] if arch == "Darwin": # exclude v4l @@ -16,9 +21,9 @@ if arch == "Darwin": logger_lib = env.Library('logger', src) libs.insert(0, logger_lib) -env.Program('loggerd', ['loggerd.cc'], LIBS=libs) -env.Program('encoderd', ['encoderd.cc'], LIBS=libs + ["jpeg"]) -env.Program('bootlog.cc', LIBS=libs) +env.Program('loggerd', ['loggerd.cc'], LIBS=libs, FRAMEWORKS=frameworks) +env.Program('encoderd', ['encoderd.cc'], LIBS=libs + ["jpeg"], FRAMEWORKS=frameworks) +env.Program('bootlog.cc', LIBS=libs, FRAMEWORKS=frameworks) if GetOption('extras'): env.Program('tests/test_logger', ['tests/test_runner.cc', 'tests/test_logger.cc', 'tests/test_zstd_writer.cc'], LIBS=libs) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index cc688ba679b..f5ef0f43939 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -66,7 +66,7 @@ base_frameworks = qt_env['FRAMEWORKS'] base_libs = [common, messaging, cereal, visionipc, 'm', 'pthread'] + qt_env["LIBS"] if arch == "Darwin": - base_frameworks += ['QtCharts', 'CoreFoundation', 'IOKit', 'Security'] + base_frameworks += ['QtCharts', 'CoreFoundation', 'CoreVideo', 'CoreMedia', 'IOKit', 'Security', 'VideoToolbox'] else: base_libs.append('Qt5Charts') @@ -75,6 +75,8 @@ cabana_env['CPPPATH'] += [libusb.INCLUDE_DIR] cabana_env['LIBPATH'] += [libusb.LIB_DIR] cabana_libs = [cereal, messaging, visionipc, replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'usb-1.0'] + base_libs +if arch != "Darwin": + cabana_libs += ['va', 'va-drm', 'drm'] opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc/dbc").abspath) cabana_env['CXXFLAGS'] += [opendbc_path] diff --git a/tools/replay/SConscript b/tools/replay/SConscript index 757f3fec4e6..d047415f58d 100644 --- a/tools/replay/SConscript +++ b/tools/replay/SConscript @@ -3,7 +3,7 @@ Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal') replay_env = env.Clone() replay_env['CCFLAGS'] += ['-Wno-deprecated-declarations'] -base_frameworks = [] +base_frameworks = ['VideoToolbox', 'CoreMedia', 'CoreFoundation', 'CoreVideo'] if arch == "Darwin" else [] base_libs = [common, messaging, cereal, visionipc, 'm', 'pthread'] replay_lib_src = ["replay.cc", "consoleui.cc", "camera.cc", "filereader.cc", "logreader.cc", "framereader.cc", @@ -13,6 +13,8 @@ if arch != "Darwin": replay_lib = replay_env.Library("replay", replay_lib_src, LIBS=base_libs, FRAMEWORKS=base_frameworks) Export('replay_lib') replay_libs = [replay_lib, 'avformat', 'avcodec', 'swresample', 'avutil', 'x264', 'z', 'bz2', 'zstd', 'yuv', 'ncurses'] + base_libs +if arch != "Darwin": + replay_libs += ['va', 'va-drm', 'drm'] replay_env.Program("replay", ["main.cc"], LIBS=replay_libs, FRAMEWORKS=base_frameworks) if GetOption('extras'): diff --git a/uv.lock b/uv.lock index 795c85bd527..272421934ce 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#fa14088a2deba2f4d511d4008e3a867dd8227867" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#90b7fefbe37fc2ca26597e6e9e0035dd386effa1" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#022412ed0d8f0ca3d566d6b9442a6867756194b9" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#05582563f2fdf6638a550fef61b129a2fb288d05" } [[package]] name = "casadi" @@ -371,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#79b2584590b24652c930dac2669fb8cd44624743" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#40e5d76de1b33a86c5181b63db6782d8f06da1da" } [[package]] name = "execnet" @@ -385,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#da4537d3bf8132e4a715ebe8f10efa956123c394" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#b9732165bcf5a3fab83b05994187802a0d115b6e" } [[package]] name = "fonttools" @@ -432,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#11b0bc7decdb804608c7192e69edac3d3f08bedf" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#15a616d4f08f6b8ecaa9b2390c75d2fe0c0fffb8" } [[package]] name = "ghp-import" @@ -449,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#8afe53e9efe0ab4fd6b32ef2fa1408eb872806bc" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#f77417aad13a05b03bb2696a0b5a124f339d117b" } [[package]] name = "google-crc32c" @@ -498,7 +498,7 @@ wheels = [ [[package]] name = "imgui" version = "1.92.7" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui#23fe87ea4bfee37224134bfc8c8c26a832ea054e" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui#c5c108b23a2e0346480d7f4c4981bf6ec7ba9054" } [[package]] name = "iniconfig" @@ -578,12 +578,12 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#24abc422ee0fd9d5485873ca673658bde791b061" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#2d69723fe445dadc68ceb9072510a505111b64a7" } [[package]] name = "libusb" version = "1.0.29" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#9443bd2571f772a8538b8cf69e293519822ad354" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#8daf8079f98809ef4674177bca915a0a81eac52f" } [[package]] name = "libusb1" @@ -599,7 +599,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#01e4405ee8c1ed8a73024b6cee27294c306a288f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#28c3c2a2444232aeeaf989c33fd333ce74e6fc90" } [[package]] name = "markdown" @@ -751,7 +751,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#c4d49b4c8a9c804f708236f95933b857e98d50b5" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#e33e7f648009ad97638b1a0a373a06a05526c040" } [[package]] name = "numpy" @@ -1672,7 +1672,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#8852c13c0ba3788704c0f0eb0d1b5586e7846510" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#0f7d2b9121cc30c0e377717fc1db52205a8e4c80" } [[package]] name = "zstandard" @@ -1702,4 +1702,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#70ffdccbe7bcf49f2d186c54a255eadf946b32f3" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#b2b10636beba0384eada30979651b4ca7cf919ff" } From 54db569c2cd30deb75f1fbfac5bc80afbced247c Mon Sep 17 00:00:00 2001 From: Ethan Reish Date: Sun, 22 Mar 2026 21:03:40 -0500 Subject: [PATCH 578/685] Do not map tici to tizi release (#37719) * Do not map tici to tizi release * tici --------- Co-authored-by: Adeeb Shihadeh --- selfdrive/ui/installer/installer.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 75994541946..fb661b966da 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -48,7 +48,7 @@ Font font_display; const bool tici_device = Hardware::get_device_type() == cereal::InitData::DeviceType::TICI || Hardware::get_device_type() == cereal::InitData::DeviceType::TIZI; -std::vector tici_prebuilt_branches = {"release3", "release-tizi", "release3-staging", "nightly", "nightly-dev"}; +std::vector tici_prebuilt_branches = {"release3", "release-tici", "release3-staging", "nightly", "nightly-dev"}; std::string migrated_branch; void branchMigration() { From 1d48cbdffa6392aaf20f4e5c8883fd6644317591 Mon Sep 17 00:00:00 2001 From: royjr Date: Mon, 23 Mar 2026 04:00:28 -0400 Subject: [PATCH 579/685] ui: fix BIG ui with scale (#37690) * Update application.py * Apply suggestions from code review --------- Co-authored-by: Shane Smiskol --- system/ui/lib/application.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/system/ui/lib/application.py b/system/ui/lib/application.py index 1d518309d3e..980410b022a 100644 --- a/system/ui/lib/application.py +++ b/system/ui/lib/application.py @@ -452,6 +452,11 @@ def set_should_render(self, should_render: bool): def texture(self, asset_path: str, width: int | None = None, height: int | None = None, alpha_premultiply=False, keep_aspect_ratio=True, flip_x: bool = False) -> rl.Texture: + if width is not None: + width = round(width) + if height is not None: + height = round(height) + cache_key = f"{asset_path}_{width}_{height}_{alpha_premultiply}_{keep_aspect_ratio}_{flip_x}" if cache_key in self._textures: return self._textures[cache_key] From 6871203c450ed9264d4b2aae1253ee3105e40ae6 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Mon, 23 Mar 2026 08:36:14 -0700 Subject: [PATCH 580/685] [bot] Update Python packages (#37529) * Update Python packages * revert tg --------- Co-authored-by: Vehicle Researcher Co-authored-by: Adeeb Shihadeh --- docs/CARS.md | 4 ++-- msgq_repo | 2 +- opendbc_repo | 2 +- panda | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 5487ef1ee1f..097f45e34f9 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -171,7 +171,7 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Niro EV (with HDA II) 2025|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV (with HDA II) 2024-25|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro EV (without HDA II) 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro Hybrid 2018|Smart Cruise Control (SCC)|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -339,7 +339,7 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| ### Footnotes -1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `nightly-dev`.
+1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `nightly-dev`.
2Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in North and South America/Southeast Asia.
3See more setup details for GM.
42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
diff --git a/msgq_repo b/msgq_repo index ed2777747d6..b7688b9bd73 160000 --- a/msgq_repo +++ b/msgq_repo @@ -1 +1 @@ -Subproject commit ed2777747d60de5a399b74ef1d4be4c1fb406ae1 +Subproject commit b7688b9bd731dea4520adf248bf1eb49b6dde776 diff --git a/opendbc_repo b/opendbc_repo index ddeba888a3d..e27af8c188c 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit ddeba888a3d03b32269f0c780490ad8578917527 +Subproject commit e27af8c188cec1ad7ef6c39ad57f6338f8b02281 diff --git a/panda b/panda index c10b82f8ff0..d079b0958b5 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit c10b82f8ff03c7d677a9420c21c0c50126a5071e +Subproject commit d079b0958b51ce33fc313def95317ef52b54b2ec From 576620276350291c19c768711d7fe3de5af6d24d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 23 Mar 2026 08:59:37 -0700 Subject: [PATCH 581/685] translations: auto-generate with codex (#37462) --- .github/workflows/repo-maintenance.yaml | 22 - selfdrive/ui/tests/test_translations.py | 196 ++-- selfdrive/ui/translations/README.md | 3 - selfdrive/ui/translations/app.pot | 922 ++++++--------- selfdrive/ui/translations/app_de.po | 671 ++++------- selfdrive/ui/translations/app_en.po | 643 ++++------ selfdrive/ui/translations/app_es.po | 721 ++++-------- selfdrive/ui/translations/app_fr.po | 644 ++++------ selfdrive/ui/translations/app_ja.po | 639 ++++------ selfdrive/ui/translations/app_ko.po | 637 ++++------ selfdrive/ui/translations/app_pt-BR.po | 667 ++++------- selfdrive/ui/translations/app_th.po | 1034 +++++++---------- selfdrive/ui/translations/app_tr.po | 717 ++++-------- selfdrive/ui/translations/app_uk.po | 638 ++++------ selfdrive/ui/translations/app_zh-CHS.po | 643 ++++------ selfdrive/ui/translations/app_zh-CHT.po | 643 ++++------ selfdrive/ui/translations/auto_translate.py | 138 --- selfdrive/ui/translations/auto_translate.sh | 26 + selfdrive/ui/translations/create_badges.py | 108 -- selfdrive/ui/translations/potools.py | 79 +- .../{ => translations}/update_translations.py | 0 21 files changed, 3385 insertions(+), 6406 deletions(-) delete mode 100644 selfdrive/ui/translations/README.md delete mode 100755 selfdrive/ui/translations/auto_translate.py create mode 100755 selfdrive/ui/translations/auto_translate.sh delete mode 100755 selfdrive/ui/translations/create_badges.py rename selfdrive/ui/{ => translations}/update_translations.py (100%) diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index 1ac6efb4f0a..f829415f4ee 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -9,28 +9,6 @@ env: PYTHONPATH: ${{ github.workspace }} jobs: - update_translations: - runs-on: ubuntu-latest - if: github.repository == 'commaai/openpilot' - steps: - - uses: actions/checkout@v6 - with: - submodules: true - - run: ./tools/op.sh setup - - name: Update translations - run: python3 selfdrive/ui/update_translations.py --vanish - - name: Create Pull Request - uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 - with: - author: Vehicle Researcher - commit-message: "Update translations" - title: "[bot] Update translations" - body: "Automatic PR from repo-maintenance -> update_translations" - branch: "update-translations" - base: "master" - delete-branch: true - labels: bot - package_updates: name: package_updates runs-on: ubuntu-latest diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 599c99013c6..fba595acadb 100644 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -1,124 +1,106 @@ -import pytest import json -import os import re -import xml.etree.ElementTree as ET import string -import requests -from openpilot.common.parameterized import parameterized_class -from openpilot.system.ui.lib.multilang import TRANSLATIONS_DIR, LANGUAGES_FILE - -with open(str(LANGUAGES_FILE)) as f: - translation_files = json.load(f) - -UNFINISHED_TRANSLATION_TAG = "" not in cur_translations, \ - f"{self.file} ({self.name}) translation file has obsolete translations. Run selfdrive/ui/update_translations.py --vanish to remove them" - - def test_finished_translations(self): - """ - Tests ran on each translation marked "finished" - Plural: - - that any numerus (plural) translations have all plural forms non-empty - - that the correct format specifier is used (%n) - Non-plural: - - that translation is not empty - - that translation format arguments are consistent - """ - tr_xml = ET.parse(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")) - - for context in tr_xml.getroot(): - for message in context.iterfind("message"): - translation = message.find("translation") - source_text = message.find("source").text - - # Do not test unfinished translations - if translation.get("type") == "unfinished": - continue +from pathlib import Path + +import pytest + +from openpilot.selfdrive.ui.translations.potools import parse_po +from openpilot.system.ui.lib.multilang import LANGUAGES_FILE, TRANSLATIONS_DIR + +PERCENT_PLACEHOLDER_RE = re.compile(r"%(?:n|\d+)") +BAD_ENTITY_RE = re.compile(r'@(\w+);') +LINE_NUMBER_REF_RE = re.compile(r'^#:\s+.+:\d+(?:\s|$)') +FORMATTER = string.Formatter() +PO_DIR = Path(str(TRANSLATIONS_DIR)) + +with LANGUAGES_FILE.open(encoding='utf-8') as f: + TRANSLATION_LANGUAGES = json.load(f) + + +def extract_placeholders(text: str) -> list[str]: + placeholders = PERCENT_PLACEHOLDER_RE.findall(text) - if message.get("numerus") == "yes": - numerusform = [t.text for t in translation.findall("numerusform")] + try: + parsed = list(FORMATTER.parse(text)) + except ValueError as e: + raise AssertionError(f"invalid brace formatting in {text!r}: {e}") from e - for nf in numerusform: - assert nf is not None, f"Ensure all plural translation forms are completed: {source_text}" - assert "%n" in nf, "Ensure numerus argument (%n) exists in translation." - assert FORMAT_ARG.search(nf) is None, f"Plural translations must use %n, not %1, %2, etc.: {numerusform}" + for _, field_name, format_spec, conversion in parsed: + if field_name is None: + continue - else: - assert translation.text is not None, f"Ensure translation is completed: {source_text}" + token = "{" + token += field_name + if conversion: + token += f"!{conversion}" + if format_spec: + token += f":{format_spec}" + token += "}" + placeholders.append(token) - source_args = FORMAT_ARG.findall(source_text) - translation_args = FORMAT_ARG.findall(translation.text) - assert sorted(source_args) == sorted(translation_args), \ - f"Ensure format arguments are consistent: `{source_text}` vs. `{translation.text}`" + return sorted(placeholders) - def test_no_locations(self): - for line in self._read_translation_file(TRANSLATIONS_DIR, self.file).splitlines(): - assert not line.strip().startswith(LOCATION_TAG), \ - f"Line contains location tag: {line.strip()}, remove all line numbers." - def test_entities_error(self): - cur_translations = self._read_translation_file(TRANSLATIONS_DIR, self.file) - matches = re.findall(r'@(\w+);', cur_translations) - assert len(matches) == 0, f"The string(s) {matches} were found with '@' instead of '&'" +def load_po_text(po_path: Path) -> str: + return po_path.read_text(encoding='utf-8') - def test_bad_language(self): - IGNORED_WORDS = {'pédale'} - match = re.search(r'([a-zA-Z]{2,3})', self.file) - assert match, f"{self.name} - could not parse language" +@pytest.mark.parametrize("language_code", sorted(TRANSLATION_LANGUAGES.values())) +def test_translation_file_exists(language_code: str): + po_path = PO_DIR / f"app_{language_code}.po" + assert po_path.exists(), f"missing translation file: {po_path}" - try: - response = requests.get( - f"https://raw.githubusercontent.com/LDNOOBW/List-of-Dirty-Naughty-Obscene-and-Otherwise-Bad-Words/master/{match.group(1)}" + +@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) +def test_translation_placeholders_are_preserved(po_path: Path): + _, entries = parse_po(po_path) + language = po_path.stem.removeprefix("app_") + + for entry in entries: + source_placeholders = extract_placeholders(entry.msgid) + + if entry.is_plural: + plural_placeholders = extract_placeholders(entry.msgid_plural) + message = ( + f"{language}: source plural placeholders do not match singular for " + + f"{entry.msgid!r}: {source_placeholders} vs {plural_placeholders}" ) - response.raise_for_status() - except requests.exceptions.HTTPError as e: - if e.response is not None and e.response.status_code == 429: - pytest.skip("word list rate limited") - raise - - banned_words = {line.strip() for line in response.text.splitlines()} - - for context in ET.parse(os.path.join(TRANSLATIONS_DIR, f"{self.file}.ts")).getroot(): - for message in context.iterfind("message"): - translation = message.find("translation") - if translation.get("type") == "unfinished": + assert plural_placeholders == source_placeholders, message + + for idx, msgstr in sorted(entry.msgstr_plural.items()): + if not msgstr: continue - translation_text = " ".join([t.text for t in translation.findall("numerusform")]) if message.get("numerus") == "yes" else translation.text + translated_placeholders = extract_placeholders(msgstr) + message = ( + f"{language}: plural form {idx} changes placeholders for {entry.msgid!r}: " + + f"expected {source_placeholders}, got {translated_placeholders}" + ) + assert translated_placeholders == source_placeholders, message + else: + if not entry.msgstr: + continue + + translated_placeholders = extract_placeholders(entry.msgstr) + message = ( + f"{language}: translation changes placeholders for {entry.msgid!r}: " + + f"expected {source_placeholders}, got {translated_placeholders}" + ) + assert translated_placeholders == source_placeholders, message + + +@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) +def test_translation_refs_do_not_include_line_numbers(po_path: Path): + for line in load_po_text(po_path).splitlines(): + assert not LINE_NUMBER_REF_RE.match(line), ( + f"{po_path.name}: line-number source reference found: {line}" + ) - if not translation_text: - continue - words = set(translation_text.translate(str.maketrans('', '', string.punctuation + '%n')).lower().split()) - bad_words_found = words & (banned_words - IGNORED_WORDS) - assert not bad_words_found, f"Bad language found in {self.name}: '{translation_text}'. Bad word(s): {', '.join(bad_words_found)}" +@pytest.mark.parametrize("po_path", sorted(PO_DIR.glob("app_*.po")), ids=lambda p: p.name) +def test_translation_entities_are_valid(po_path: Path): + matches = BAD_ENTITY_RE.findall(load_po_text(po_path)) + assert not matches, ( + f"{po_path.name}: found '@...;' entity typo(s): {', '.join(sorted(set(matches)))}" + ) diff --git a/selfdrive/ui/translations/README.md b/selfdrive/ui/translations/README.md deleted file mode 100644 index 433eb7d64a0..00000000000 --- a/selfdrive/ui/translations/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Multilanguage - -[![languages](https://raw.githubusercontent.com/commaai/openpilot/badges/translation_badge.svg)](#) diff --git a/selfdrive/ui/translations/app.pot b/selfdrive/ui/translations/app.pot index 468ff35fd9d..0872ed538e3 100644 --- a/selfdrive/ui/translations/app.pot +++ b/selfdrive/ui/translations/app.pot @@ -1,1035 +1,823 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2026-03-09 14:21+0000\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py +msgid "OK" +msgstr "" + +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py +msgid "Cancel" +msgstr "" + +#: system/ui/widgets/network.py msgid "Advanced" msgstr "" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" msgstr "" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" msgstr "" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" msgstr "" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" msgstr "" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" msgstr "" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" msgstr "" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "" -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format -msgid "Cancel" -msgstr "" - -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format -msgid "OK" +#: system/ui/widgets/list_view.py +msgid "Error" msgstr "" -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format -msgid "Error" -msgstr "" - -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format -msgid "Pair your device to your comma account" +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py +msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format -msgid "Go to https://connect.comma.ai on your phone" +#: openpilot/selfdrive/ui/widgets/setup.py +msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format -msgid "Click \"add new device\" and scan the QR code on the right" +#: openpilot/selfdrive/ui/widgets/setup.py +msgid "Finish Setup" msgstr "" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format -msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +#: openpilot/selfdrive/ui/widgets/setup.py +msgid "Pair device" msgstr "" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format -msgid "QR Code Error" +#: openpilot/selfdrive/ui/widgets/setup.py +msgid "Open" msgstr "" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format -msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." +#: openpilot/selfdrive/ui/widgets/setup.py +msgid "🔥 Firehose Mode 🔥" msgstr "" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format -msgid "Maximize your training data uploads to improve openpilot's driving models." +#: openpilot/selfdrive/ui/widgets/setup.py +msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "" -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format -msgid "Finish Setup" +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py +msgid "Close" msgstr "" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format -msgid "Pair device" +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py +msgid "Snooze Update" msgstr "" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format -msgid "Open" +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py +msgid "Acknowledge Excessive Actuation" msgstr "" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format -msgid "🔥 Firehose Mode 🔥" +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py +msgid "Reboot and Update" msgstr "" -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format -msgid "Please connect to Wi-Fi to complete initial pairing" +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py +msgid "No release notes available." msgstr "" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py +msgid "Request timed out" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format -msgid "Request timed out" +#: openpilot/selfdrive/ui/widgets/ssh_key.py +msgid "No SSH keys found for user '{}'" msgstr "" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py +msgid "Pair your device to your comma account" msgstr "" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format -msgid "No SSH keys found for user '{}'" +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py +msgid "Go to https://connect.comma.ai on your phone" msgstr "" -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py +msgid "Click \"add new device\" and scan the QR code on the right" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py +msgid "Bookmark connect.comma.ai to your home screen to use it like an app" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py +msgid "QR Code Error" +msgstr "" + +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "" -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "" -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format -msgid "Close" +#: openpilot/selfdrive/ui/layouts/home.py +msgid "UPDATE" msgstr "" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format -msgid "Snooze Update" +#: openpilot/selfdrive/ui/layouts/home.py +msgid "{} ALERT" +msgid_plural "{} ALERTS" +msgstr[0] "" +msgstr[1] "" + +#: openpilot/selfdrive/ui/layouts/onboarding.py +msgid "Welcome to openpilot" msgstr "" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format -msgid "Acknowledge Excessive Actuation" +#: openpilot/selfdrive/ui/layouts/onboarding.py +msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format -msgid "Reboot and Update" +#: openpilot/selfdrive/ui/layouts/onboarding.py +msgid "Decline" msgstr "" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format -msgid "No release notes available." +#: openpilot/selfdrive/ui/layouts/onboarding.py +msgid "Agree" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/onboarding.py +msgid "You must accept the Terms and Conditions in order to use openpilot." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/onboarding.py +msgid "Decline, uninstall openpilot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format -msgid "UPDATE" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format -msgid "{} ALERT" -msgid_plural "{} ALERTS" -msgstr[0] "" -msgstr[1] "" - -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format -msgid "Welcome to openpilot" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format -msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format -msgid "Decline" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format -msgid "Agree" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format -msgid "You must accept the Terms and Conditions in order to use openpilot." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format -msgid "Decline, uninstall openpilot" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 -msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 -msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 -msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 -msgid "Display speed in km/h instead of mph." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 -msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format -msgid "Driving Personality" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format -msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format -msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format -msgid "Enable" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format -msgid "Enable openpilot" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format -msgid "Experimental Mode" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format -msgid "Disengage on Accelerator Pedal" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format -msgid "Enable Lane Departure Warnings" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format -msgid "Always-On Driver Monitoring" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format -msgid "Record and Upload Driver Camera" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format -msgid "Record and Upload Microphone Audio" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format -msgid "Use Metric System" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format -msgid "openpilot longitudinal control may come in a future update." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Aggressive" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Standard" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format -msgid "Relaxed" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format -msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 -msgid "Device" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 -msgid "Network" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 -msgid "Toggles" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 -msgid "Software" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 -msgid "Firehose" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 -msgid "Developer" -msgstr "" - -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "" msgstr[1] "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Enable ADB" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Enable SSH" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "SSH Keys" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Joystick Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "Longitudinal Maneuver Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "openpilot Longitudinal Control (Alpha)" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/developer.py +msgid "UI Debug Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Enable" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "" msgstr[1] "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "" msgstr[1] "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "" msgstr[1] "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format -msgid "Enable ADB" +#: openpilot/selfdrive/ui/layouts/settings/settings.py +msgid "Device" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format -msgid "Enable SSH" +#: openpilot/selfdrive/ui/layouts/settings/settings.py +msgid "Network" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format -msgid "SSH Keys" +#: openpilot/selfdrive/ui/layouts/settings/settings.py +msgid "Toggles" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format -msgid "Joystick Debug Mode" +#: openpilot/selfdrive/ui/layouts/settings/settings.py +msgid "Software" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format -msgid "Longitudinal Maneuver Mode" +#: openpilot/selfdrive/ui/layouts/settings/settings.py +msgid "Firehose" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format -msgid "openpilot Longitudinal Control (Alpha)" +#: openpilot/selfdrive/ui/layouts/settings/settings.py +msgid "Developer" msgstr "" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format -msgid "UI Debug Mode" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format -msgid "openpilot Unavailable" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format -msgid "Waiting to start" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format -msgid "TAKE CONTROL IMMEDIATELY" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Display speed in km/h instead of mph." msgstr "" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format -msgid "System Unresponsive" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format -msgid "Reboot Device" +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Driving Personality" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Changing this setting will restart openpilot if the car is powered on." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Enable openpilot" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Experimental Mode" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Disengage on Accelerator Pedal" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Enable Lane Departure Warnings" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Always-On Driver Monitoring" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Record and Upload Driver Camera" msgstr "" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Record and Upload Microphone Audio" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Use Metric System" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "openpilot longitudinal control may come in a future update." +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Aggressive" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Standard" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Relaxed" +msgstr "" + +#: openpilot/selfdrive/ui/layouts/settings/toggles.py +msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." +msgstr "" + +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "" +#: openpilot/selfdrive/ui/onroad/alert_renderer.py +msgid "openpilot Unavailable" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/alert_renderer.py +msgid "Waiting to start" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/alert_renderer.py +msgid "TAKE CONTROL IMMEDIATELY" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/alert_renderer.py +msgid "System Unresponsive" +msgstr "" + +#: openpilot/selfdrive/ui/onroad/alert_renderer.py +msgid "Reboot Device" +msgstr "" + diff --git a/selfdrive/ui/translations/app_de.po b/selfdrive/ui/translations/app_de.po index 9888bb718e7..287ecde1a01 100644 --- a/selfdrive/ui/translations/app_de.po +++ b/selfdrive/ui/translations/app_de.po @@ -1,1034 +1,825 @@ -# German translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-20 16:35-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: de\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: de\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist abgeschlossen." -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " Die Lenkmoment-Reaktionskalibrierung ist zu {}% abgeschlossen." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ihr Gerät ist um {:.1f}° {} und {:.1f}° {} ausgerichtet." -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "1 Jahr Fahrtdatenspeicherung" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "24/7 LTE‑Verbindung" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "

Kalibrierung der Lenkverzögerung abgeschlossen." -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "

Kalibrierung der Lenkverzögerung zu {}% abgeschlossen." -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "HINZUFÜGEN" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "APN‑Einstellung" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "Übermäßige Betätigung bestätigen" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" msgstr "Erweitert" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "Aggressiv" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "Zustimmen" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "Immer aktive Fahrerüberwachung" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "Sind Sie sicher, dass Sie ausschalten möchten?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "Sind Sie sicher, dass Sie neu starten möchten?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "Sind Sie sicher, dass Sie die Kalibrierung zurücksetzen möchten?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "Sind Sie sicher, dass Sie deinstallieren möchten?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "Zurück" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "Werden Sie comma prime Mitglied auf connect.comma.ai" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Fügen Sie connect.comma.ai Ihrem Startbildschirm hinzu, um es wie eine App zu verwenden" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "ÄNDERN" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "PRÜFEN" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "CHILL‑MODUS AKTIV" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" msgstr "VERBINDUNG" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "VERBINDUNG" -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Abbrechen" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Getaktete Mobilfunkverbindung" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "Sprache ändern" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr " Durch Ändern dieser Einstellung wird openpilot neu gestartet, wenn das Auto eingeschaltet ist." -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Klicken Sie auf \"add new device\" und scannen Sie den QR‑Code rechts" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "Schließen" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "Aktuelle Version" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "HERUNTERLADEN" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "Ablehnen" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "Ablehnen, openpilot deinstallieren" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "Entwickler" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "Gerät" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "Beim Gaspedal deaktivieren" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "Zum Ausschalten deaktivieren" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "Zum Neustart deaktivieren" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "Zum Zurücksetzen der Kalibrierung deaktivieren" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "Geschwindigkeit in km/h statt mph anzeigen." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" msgstr "Dongle-ID" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "Herunterladen" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "Fahrerkamera" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Fahrstil" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "BEARBEITEN" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "FEHLER" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTALMODUS AKTIV" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "Aktivieren" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "ADB aktivieren" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "Spurverlassenswarnungen aktivieren" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" -msgstr "openpilot aktivieren" +msgstr "Roaming aktivieren" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "SSH aktivieren" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" -msgstr "Spurverlassenswarnungen aktivieren" +msgstr "Tethering aktivieren" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Fahrerüberwachung auch aktivieren, wenn openpilot nicht aktiv ist." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "openpilot aktivieren" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Den Schalter für die openpilot-Längsregelung (Alpha) aktivieren, um den Experimentalmodus zu erlauben." -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "APN eingeben" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "SSID eingeben" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Neues Tethering‑Passwort eingeben" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "Passwort eingeben" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "Geben Sie Ihren GitHub‑Benutzernamen ein" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" msgstr "Fehler" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "Experimentalmodus" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Der Experimentalmodus ist derzeit auf diesem Fahrzeug nicht verfügbar, da der serienmäßige ACC für die Längsregelung verwendet wird." -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "WIRD VERGESSEN..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "Einrichtung abschließen" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" -msgstr "Firehose" +msgstr "Datenstrom" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Firehose‑Modus" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" msgstr "Vergessen" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "WLAN‑Netz „{}“ vergessen?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "GUT" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "Gehen Sie auf Ihrem Telefon zu https://connect.comma.ai" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "HOCH" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" -msgstr "Netzwerk" +msgstr "Verstecktes Netzwerk" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "INSTALLIEREN" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "IP‑Adresse" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "Update installieren" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "Joystick‑Debugmodus" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "LADEN" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Längsmanövermodus" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" -msgstr "MAX" +msgstr "MAX." -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "Maximieren Sie Ihre Trainingsdaten‑Uploads, um die Fahrmodelle von openpilot zu verbessern." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" msgstr "k. A." -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "KEIN" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "Netzwerk" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "Keine SSH‑Schlüssel gefunden" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" -msgstr "Keine SSH‑Schlüssel für Benutzer '{username}' gefunden" +msgstr "Keine SSH‑Schlüssel für Benutzer '{}' gefunden" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "Keine Versionshinweise verfügbar." -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" -msgstr "OFFLINE" +msgstr "GETRENNT" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "OK" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" -msgstr "ONLINE" +msgstr "VERBUNDEN" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "Öffnen" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "KOPPELN" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "VORSCHAU" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "PRIME‑FUNKTIONEN:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "Gerät koppeln" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "Gerät koppeln" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "Koppeln Sie Ihr Gerät mit Ihrem comma‑Konto" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "Koppeln Sie Ihr Gerät mit comma connect (connect.comma.ai) und lösen Sie Ihr comma‑prime‑Angebot ein." -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Bitte mit WLAN verbinden, um das erste Koppeln abzuschließen" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "Ausschalten" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" +msgstr "Verhindern Sie das Hochladen großer Datenmengen bei einer getakteten WLAN-Verbindung" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" +msgstr "Verhindern Sie das Hochladen großer Datenmengen bei einer getakteten Mobilfunkverbindung" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "Vorschau der Fahrer‑Kamera, um sicherzustellen, dass die Fahrerüberwachung gute Sicht hat. (Fahrzeug muss ausgeschaltet sein)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "QR‑Code‑Fehler" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "ENTFERNEN" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "ZURÜCKSETZEN" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "ANSEHEN" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "Neustart" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "Gerät neu starten" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "Neustarten und aktualisieren" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "Fahrerkamera aufzeichnen und hochladen" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "Mikrofonton aufzeichnen und hochladen" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "Mikrofonton während der Fahrt aufzeichnen und speichern. Die Audiospur wird im Dashcam‑Video in comma connect enthalten sein." -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "Vorschriften" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "Entspannt" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "Fernzugriff" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "Remote‑Schnappschüsse" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "Zeitüberschreitung bei der Anfrage" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "Zurücksetzen" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "Kalibrierung zurücksetzen" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "Trainingsanleitung ansehen" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "Überprüfen Sie die Regeln, Funktionen und Einschränkungen von openpilot" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" -msgstr "" +msgstr "WÄHLEN" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" msgstr "SSH‑Schlüssel" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "WLAN‑Netzwerke werden gesucht..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "Auswählen" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" -msgstr "" +msgstr "Zweig auswählen" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "Sprache auswählen" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "Seriennummer" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "Update verschieben" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" -msgstr "Software" +msgstr "Softwarebereich" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" -msgstr "Standard" +msgstr "Standardmodus" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "System reagiert nicht" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "SOFORT DIE KONTROLLE ÜBERNEHMEN" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" -msgstr "TEMP" +msgstr "TEMP." -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" -msgstr "" +msgstr "Zielzweig" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "Tethering‑Passwort" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "Schalter" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "UI-Debug-Modus" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "DEINSTALLIEREN" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" -msgstr "UPDATE" +msgstr "AKTUALISIEREN" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "Deinstallieren" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "Unbekannt" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "Updates werden nur heruntergeladen, wenn das Auto aus ist." -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "Jetzt abonnieren" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "Daten von der Fahrer‑Kamera hochladen und den Fahrerüberwachungs‑Algorithmus verbessern." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "Metersystem verwenden" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "FAHRZEUG" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "ANSEHEN" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "Warten auf Start" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "Willkommen bei openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Wenn aktiviert, deaktiviert das Drücken des Gaspedals openpilot." -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "WLAN" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Getaktetes WLAN‑Netzwerk" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "Falsches Passwort" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden." -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Sie müssen die Nutzungsbedingungen akzeptieren, um openpilot zu verwenden. Lesen Sie die aktuellen Bedingungen unter https://comma.ai/terms, bevor Sie fortfahren." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "Kamera startet" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "Überprüfung..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" msgstr "Standard" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "unten" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "Herunterladen..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "Überprüfung auf Updates fehlgeschlagen" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "Update wird finalisiert..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "für „{}“" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "für automatische Konfiguration leer lassen" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "links" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "getaktet" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "nie" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "jetzt" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Längsregelung (Alpha)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot nicht verfügbar" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "Die openpilot‑Längsregelung könnte in einem zukünftigen Update kommen." -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot erfordert, dass das Gerät innerhalb von 4° nach links oder rechts und innerhalb von 5° nach oben oder 9° nach unten montiert ist." -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "rechts" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "unbegrenzt" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "oben" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "Aktuell, zuletzt geprüft: nie" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "Aktuell, zuletzt geprüft: {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "Update verfügbar" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} WARNUNG" msgstr[1] "{} WARNUNGEN" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "vor {} Tag" msgstr[1] "vor {} Tagen" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "vor {} Stunde" msgstr[1] "vor {} Stunden" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "vor {} Minute" msgstr[1] "vor {} Minuten" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} Segment Ihrer Fahrten ist bisher im Trainingsdatensatz." msgstr[1] "{} Segmente Ihrer Fahrten sind bisher im Trainingsdatensatz." -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ABONNIERT" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose‑Modus 🔥" diff --git a/selfdrive/ui/translations/app_en.po b/selfdrive/ui/translations/app_en.po index 37440962266..9f99c42b111 100644 --- a/selfdrive/ui/translations/app_en.po +++ b/selfdrive/ui/translations/app_en.po @@ -1,1034 +1,825 @@ -# English translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-21 18:18-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: en\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: en\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " Steering torque response calibration is complete." -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " Steering torque response calibration is {}% complete." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Your device is pointed {:.1f}° {} and {:.1f}° {}." -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "1 year of drive storage" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "24/7 LTE connectivity" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "

Steering lag calibration is complete." -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "

Steering lag calibration is {}% complete." -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "ADD" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "APN Setting" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "Acknowledge Excessive Actuation" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" msgstr "Advanced" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "Aggressive" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "Agree" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "Always-On Driver Monitoring" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "Are you sure you want to power off?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "Are you sure you want to reboot?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "Are you sure you want to reset calibration?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "Are you sure you want to uninstall?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "Back" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "Become a comma prime member at connect.comma.ai" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Bookmark connect.comma.ai to your home screen to use it like an app" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "CHANGE" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "CHECK" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "CHILL MODE ON" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" msgstr "CONNECT" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "CONNECTING..." -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Cancel" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Cellular Metered" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "Change Language" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "Changing this setting will restart openpilot if the car is powered on." -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Click \"add new device\" and scan the QR code on the right" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "Close" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "Current Version" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "DOWNLOAD" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "Decline" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "Decline, uninstall openpilot" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "Developer" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "Device" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "Disengage on Accelerator Pedal" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "Disengage to Power Off" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "Disengage to Reboot" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "Disengage to Reset Calibration" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "Display speed in km/h instead of mph." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" msgstr "Dongle ID" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "Download" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "Driver Camera" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Driving Personality" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "EDIT" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "ERROR" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "EXPERIMENTAL MODE ON" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "Enable" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "Enable ADB" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "Enable Lane Departure Warnings" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Enable Roaming" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "Enable SSH" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" msgstr "Enable Tethering" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Enable driver monitoring even when openpilot is not engaged." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "Enable openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "Enter APN" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "Enter SSID" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Enter new tethering password" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "Enter password" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "Enter your GitHub username" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" msgstr "Error" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "Experimental Mode" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "FORGETTING..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "Finish Setup" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" msgstr "Firehose" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Firehose Mode" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" msgstr "Forget" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Forget Wi-Fi Network \"{}\"?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "GOOD" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "Go to https://connect.comma.ai on your phone" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "HIGH" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" msgstr "Hidden Network" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "INSTALL" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "IP Address" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "Install Update" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "Joystick Debug Mode" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "LOADING" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Longitudinal Maneuver Mode" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "MAX" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "Maximize your training data uploads to improve openpilot's driving models." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" msgstr "N/A" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "NO" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "Network" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "No SSH keys found" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" msgstr "No SSH keys found for user '{}'" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "No release notes available." -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "OFFLINE" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "OK" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "ONLINE" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "Open" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "PAIR" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "PREVIEW" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "PRIME FEATURES:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "Pair Device" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "Pair device" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "Pair your device to your comma account" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Please connect to Wi-Fi to complete initial pairing" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "Power Off" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Prevent large data uploads when on a metered Wi-Fi connection" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Prevent large data uploads when on a metered cellular connection" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "QR Code Error" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "REMOVE" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "RESET" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "REVIEW" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "Reboot" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "Reboot Device" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "Reboot and Update" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "Record and Upload Driver Camera" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "Record and Upload Microphone Audio" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "Regulatory" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "Relaxed" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "Remote access" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "Remote snapshots" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "Request timed out" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "Reset" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "Reset Calibration" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "Review Training Guide" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "Review the rules, features, and limitations of openpilot" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" -msgstr "" +msgstr "SELECT" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" msgstr "SSH Keys" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Scanning Wi-Fi networks..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "Select" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" -msgstr "" +msgstr "Select a branch" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "Select a language" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "Serial" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "Snooze Update" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" msgstr "Software" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" msgstr "Standard" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "System Unresponsive" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "TAKE CONTROL IMMEDIATELY" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" msgstr "TEMP" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" -msgstr "" +msgstr "Target Branch" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "Tethering Password" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "Toggles" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "UI Debug Mode" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "UNINSTALL" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "UPDATE" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "Uninstall" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "Unknown" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "Updates are only downloaded while the car is off." -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "Upgrade Now" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "Use Metric System" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "VEHICLE" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "VIEW" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "Waiting to start" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "Welcome to openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "When enabled, pressing the accelerator pedal will disengage openpilot." -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi-Fi Network Metered" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "Wrong password" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "You must accept the Terms and Conditions in order to use openpilot." -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "camera starting" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "checking..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" msgstr "default" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "down" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "downloading..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "failed to check for update" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "finalizing update..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "for \"{}\"" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "leave blank for automatic configuration" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "left" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "metered" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "never" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "now" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Longitudinal Control (Alpha)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot Unavailable" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot longitudinal control may come in a future update." -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "right" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "unmetered" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "up" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "up to date, last checked never" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "up to date, last checked {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "update available" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERT" msgstr[1] "{} ALERTS" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} day ago" msgstr[1] "{} days ago" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hour ago" msgstr[1] "{} hours ago" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minute ago" msgstr[1] "{} minutes ago" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment of your driving is in the training dataset so far." msgstr[1] "{} segments of your driving is in the training dataset so far." -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ SUBSCRIBED" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Mode 🔥" diff --git a/selfdrive/ui/translations/app_es.po b/selfdrive/ui/translations/app_es.po index 35188fe2fc9..707816bc009 100644 --- a/selfdrive/ui/translations/app_es.po +++ b/selfdrive/ui/translations/app_es.po @@ -1,1034 +1,825 @@ -# Spanish translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-20 16:35-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: es\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: es\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " La calibración de respuesta de par de dirección está completa." -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " La calibración de respuesta de par de dirección está {}% completa." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Tu dispositivo está orientado {:.1f}° {} y {:.1f}° {}." -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "1 año de almacenamiento de conducción" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "Conectividad LTE 24/7" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." -msgstr "" +msgstr "

La calibración del retraso de dirección está completa." -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." -msgstr "" +msgstr "

La calibración del retraso de dirección está completa en un {}%." -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "AÑADIR" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" -msgstr "" +msgstr "Configuración de APN" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "Reconocer actuación excesiva" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" -msgstr "" +msgstr "Avanzado" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "Agresivo" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "Aceptar" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "Supervisión del conductor siempre activa" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "¿Seguro que quieres apagar?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "¿Seguro que quieres reiniciar?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "¿Seguro que quieres restablecer la calibración?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "¿Seguro que quieres desinstalar?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "Atrás" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "Hazte miembro de comma prime en connect.comma.ai" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Añade connect.comma.ai a tu pantalla de inicio para usarlo como una app" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "CAMBIAR" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "COMPROBAR" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "MODO CHILL ACTIVADO" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "CONECTAR" -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" -msgstr "" +msgstr "Cancelar" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" -msgstr "" +msgstr "Medición celular" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "Cambiar idioma" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr " Cambiar esta configuración reiniciará openpilot si el coche está encendido." -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Haz clic en \"añadir nuevo dispositivo\" y escanea el código QR de la derecha" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "Cerrar" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "Versión actual" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "DESCARGAR" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "Rechazar" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "Rechazar, desinstalar openpilot" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "Desarrollador" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "Dispositivo" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "Desactivar con el pedal del acelerador" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "Desactivar para apagar" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "Desactivar para reiniciar" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "Desactivar para restablecer la calibración" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "Mostrar la velocidad en km/h en lugar de mph." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" msgstr "ID del dongle" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "Descargar" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "Cámara del conductor" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Estilo de conducción" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" -msgstr "" +msgstr "EDITAR" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" -msgstr "ERROR" +msgstr "FALLO" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ACTIVADO" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "Activar" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "Activar ADB" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "Activar advertencias de salida de carril" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" -msgstr "Activar openpilot" +msgstr "Activar roaming" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "Activar SSH" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" -msgstr "Activar advertencias de salida de carril" +msgstr "Activar anclaje" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Activar la supervisión del conductor incluso cuando openpilot no esté activado." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "Activar openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Activa el interruptor de control longitudinal de openpilot (alpha) para permitir el modo Experimental." -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" -msgstr "" +msgstr "Introduce APN" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" -msgstr "" +msgstr "Introduzca SSID" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" -msgstr "" +msgstr "Ingrese una nueva contraseña de anclaje a red" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" -msgstr "" +msgstr "Introduce la contraseña" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "Introduce tu nombre de usuario de GitHub" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" -msgstr "" +msgstr "Fallo" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "Modo experimental" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "El modo experimental no está disponible actualmente en este coche, ya que se usa el ACC de fábrica para el control longitudinal." -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." -msgstr "" +msgstr "OLVIDAR..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "Finalizar configuración" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" -msgstr "Firehose" +msgstr "Flujo masivo" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Modo Firehose" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" -msgstr "" +msgstr "Olvidar" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "" +msgstr "¿Olvidaste la red Wi-Fi \"{}\"?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "BUENO" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "Ve a https://connect.comma.ai en tu teléfono" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" -msgstr "Red" +msgstr "Red oculta" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" -msgstr "" +msgstr "Dirección IP" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "Instalar actualización" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "Modo de depuración de joystick" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "CARGANDO" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Modo de maniobra longitudinal" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "MÁX" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "Maximiza tus cargas de datos de entrenamiento para mejorar los modelos de conducción de openpilot." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" -msgstr "" +msgstr "N / A" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" -msgstr "NO" +msgstr "SIN" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "Red" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "No se encontraron claves SSH" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" -msgstr "No se encontraron claves SSH para el usuario '{username}'" +msgstr "No se encontraron claves SSH para el usuario '{}'" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "No hay notas de versión disponibles." -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "SIN CONEXIÓN" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "OK" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "EN LÍNEA" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "Abrir" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "EMPAREJAR" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "VISTA PREVIA" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "FUNCIONES PRIME:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "Emparejar dispositivo" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "Emparejar dispositivo" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "Empareja tu dispositivo con tu cuenta de comma" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "Empareja tu dispositivo con comma connect (connect.comma.ai) y reclama tu oferta de comma prime." -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conéctate a Wi‑Fi para completar el emparejamiento inicial" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "Apagar" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" +msgstr "Evite grandes cargas de datos cuando esté en una conexión Wi-Fi medida" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" +msgstr "Evite grandes cargas de datos cuando esté en una conexión celular medida" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "Previsualiza la cámara hacia el conductor para asegurarte de que la supervisión del conductor tenga buena visibilidad. (el vehículo debe estar apagado)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "Error de código QR" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "ELIMINAR" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "RESTABLECER" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "REVISAR" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "Reiniciar" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "Reiniciar dispositivo" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "Reiniciar y actualizar" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "Grabar y subir cámara del conductor" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "Grabar y subir audio del micrófono" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "Grabar y almacenar audio del micrófono mientras conduces. El audio se incluirá en el video de la dashcam en comma connect." -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "Reglamentario" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "Relajado" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "Acceso remoto" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "Capturas remotas" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "Se agotó el tiempo de espera de la solicitud" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "Restablecer" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "Restablecer calibración" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "Revisar guía de entrenamiento" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "Revisa las reglas, funciones y limitaciones de openpilot" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" -msgstr "" +msgstr "SELECCIONAR" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" -msgstr "" +msgstr "Claves SSH" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." -msgstr "" +msgstr "Escaneando redes Wi-Fi..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" -msgstr "" +msgstr "Seleccionar" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" -msgstr "" +msgstr "Selecciona una rama" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "Selecciona un idioma" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "Número de serie" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "Posponer actualización" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" -msgstr "Software" +msgstr "Sistema" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" msgstr "Estándar" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "Sistema sin respuesta" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "TOME EL CONTROL INMEDIATAMENTE" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" -msgstr "TEMP" +msgstr "TEMP." -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" -msgstr "" +msgstr "Rama objetivo" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" -msgstr "" +msgstr "Contraseña de anclaje" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "Interruptores" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "Modo de depuración de la interfaz de usuario" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "DESINSTALAR" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "ACTUALIZAR" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "Desinstalar" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "Desconocido" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "Las actualizaciones solo se descargan cuando el coche está apagado." -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "Mejorar ahora" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "Sube datos de la cámara orientada al conductor y ayuda a mejorar el algoritmo de supervisión del conductor." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "Usar sistema métrico" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "VEHÍCULO" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "VER" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "Esperando para iniciar" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "Bienvenido a openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Cuando está activado, al presionar el pedal del acelerador se desactivará openpilot." -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" -msgstr "" +msgstr "Red Wi-Fi medida" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" -msgstr "" +msgstr "Contraseña incorrecta" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Debes aceptar los Términos y Condiciones para poder usar openpilot." -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Debes aceptar los Términos y Condiciones para usar openpilot. Lee los términos más recientes en https://comma.ai/terms antes de continuar." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "iniciando cámara" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "de cheques..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" -msgstr "" +msgstr "por defecto" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "abajo" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "descargando..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "Error al buscar actualizaciones" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "finalizando actualización..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" -msgstr "" +msgstr "para \"{}\"" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" -msgstr "" +msgstr "dejar en blanco para configuración automática" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "izquierda" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" -msgstr "" +msgstr "medido" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "nunca" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "ahora" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "Control longitudinal de openpilot (Alpha)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot no disponible" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "El control longitudinal de openpilot podría llegar en una actualización futura." -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot requiere que el dispositivo esté montado dentro de 4° a izquierda o derecha y dentro de 5° hacia arriba o 9° hacia abajo." -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "derecha" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" -msgstr "" +msgstr "sin medir" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "arriba" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "actualizado, última comprobación: nunca" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "actualizado, última comprobación: {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "actualización disponible" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "hace {} día" msgstr[1] "hace {} días" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "hace {} hora" msgstr[1] "hace {} horas" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "hace {} minuto" msgstr[1] "hace {} minutos" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segmento de tu conducción está en el conjunto de entrenamiento hasta ahora." msgstr[1] "{} segmentos de tu conducción están en el conjunto de entrenamiento hasta ahora." -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ SUSCRITO" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" diff --git a/selfdrive/ui/translations/app_fr.po b/selfdrive/ui/translations/app_fr.po index d3ce386bdcb..7c0aecc9ec3 100644 --- a/selfdrive/ui/translations/app_fr.po +++ b/selfdrive/ui/translations/app_fr.po @@ -1,1035 +1,825 @@ -# French translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2026-01-24 12:37+0100\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: fr\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: fr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Generator: Poedit 3.8\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " L'étalonnage de la réponse du couple de direction est terminé." -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " L'étalonnage de la réponse du couple de direction est terminé à {}%." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Votre appareil est orienté {:.1f}° {} et {:.1f}° {}." -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "1 an de stockage de trajets" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "Connexion LTE 24/7" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "

L'étalonnage du délai de réponse de la direction est terminé." -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "

L'étalonnage du délai de réponse de la direction est terminé à {}%." -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "AJOUTER" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "Paramètres APN" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "Accuser réception d'actionnement excessif" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" msgstr "Avancé" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "Agressif" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "Accepter" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "Surveillance continue du conducteur" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "Êtes-vous sûr de vouloir éteindre ?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "Êtes-vous sûr de vouloir redémarrer ?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "Êtes-vous sûr de vouloir réinitialiser la calibration ?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "Êtes-vous sûr de vouloir désinstaller ?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "Retour" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "Devenez membre comma prime sur connect.comma.ai" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Ajoutez connect.comma.ai à votre écran d'accueil pour l'utiliser comme une application" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "CHANGER" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "VÉRIFIER" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "MODE CHILL ACTIVÉ" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" msgstr "CONNECTER" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "CONNECTER..." -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Annuler" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Données cellulaire limitées" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "Changer la langue" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "La modification de ce réglage redémarrera openpilot si la voiture est sous tension." -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Cliquez sur \"add new device\" et scannez le code QR à droite" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "Fermer" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "Version actuelle" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "TÉLÉCHARGER" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "Refuser" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "Refuser, désinstaller openpilot" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "Développeur" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "Appareil" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "Désengager à l'appui sur l'accélérateur" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "Désengager pour éteindre" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "Désengager pour redémarrer" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "Désengager pour réinitialiser la calibration" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "Afficher la vitesse en km/h au lieu de mph." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" msgstr "ID du dongle" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "Télécharger" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "Caméra conducteur" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Personnalité de conduite" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "EDITER" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "ERREUR" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "MODE EXPÉRIMENTAL ACTIVÉ" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "Activer" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "Activer ADB" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "Activer les alertes de sortie de voie" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Activer openpilot" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "Activer SSH" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" msgstr "Activer les alertes de sortie de voie" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Activer la surveillance du conducteur même lorsque openpilot n'est pas engagé." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "Activer openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Activez l'option de contrôle longitudinal openpilot (alpha) pour autoriser le mode expérimental." -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "Saisir l'APN" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "Entrer le SSID" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Saisir le mot de passe du partage de connexion" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "Saisir le mot de passe" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "Entrez votre nom d'utilisateur GitHub" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" msgstr "Erreur" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "Mode expérimental" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Le mode expérimental est actuellement indisponible sur cette voiture car l'ACC d'origine est utilisé pour le contrôle longitudinal." -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "OUBLIER..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "Terminer la configuration" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" -msgstr "Firehose" +msgstr "Flux continu" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Mode Firehose" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" msgstr "Oublier" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Oublier le réseau Wi-Fi \"{}\" ?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "BON" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "Allez sur https://connect.comma.ai sur votre téléphone" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "ÉLEVÉ" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" msgstr "Réseau" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "INSTALLER" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "Adresse IP" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "Installer la mise à jour" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "Mode débogage joystick" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "CHARGEMENT" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Mode de manœuvre longitudinale" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" -msgstr "MAX" +msgstr "MAX." -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "Maximisez vos envois de données d'entraînement pour améliorer les modèles de conduite d'openpilot." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" msgstr "NC" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "NON" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "Réseau" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "Aucune clé SSH trouvée" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" msgstr "Aucune clé SSH trouvée pour l'utilisateur '{}'" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "Aucune note de version disponible." -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "HORS LIGNE" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "OK" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "EN LIGNE" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "Ouvrir" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "ASSOCIER" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "APERÇU" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "FONCTIONNALITÉS PRIME :" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "Associer l'appareil" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "Associer l'appareil" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "Associez votre appareil à votre compte comma" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "Associez votre appareil à comma connect (connect.comma.ai) et réclamez votre offre comma prime." -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Veuillez vous connecter au Wi‑Fi pour terminer l'association initiale" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "Éteindre" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Eviter les transferts de données volumineux lorsque vous êtes connecté à un réseau Wi-Fi limité" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Eviter les transferts de données volumineux lors d'une connexion à un réseau cellulaire limité" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "Prévisualisez la caméra orientée conducteur pour vous assurer que la surveillance du conducteur a une bonne visibilité. (le véhicule doit être éteint)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "Erreur de code QR" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "SUPPRIMER" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "RÉINITIALISER" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "CONSULTER" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "Redémarrer" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "Redémarrer l'appareil" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "Redémarrer et mettre à jour" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "Enregistrer et téléverser la caméra conducteur" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "Enregistrer et téléverser l'audio du microphone" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "Enregistrer et stocker l'audio du microphone pendant la conduite. L'audio sera inclus dans la vidéo dashcam dans comma connect." -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "Réglementaire" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "Détendu" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "Accès à distance" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "Captures à distance" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "Délai de la requête dépassé" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "Réinitialiser" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "Réinitialiser la calibration" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "Consulter le guide d'entraînement" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "Consultez les règles, fonctionnalités et limitations d'openpilot" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" msgstr "SELECTIONNER" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" msgstr "Clefs SSH" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Analyse des réseaux Wi-Fi..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "Sélectionner" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" msgstr "Sélectionner une branche" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "Sélectionner un langage" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "Numéro de série" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "Reporter la mise à jour" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" msgstr "Logiciel" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" -msgstr "Standard" +msgstr "Normal" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "Système non réactif" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "REPRENEZ IMMÉDIATEMENT LE CONTRÔLE" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" msgstr "TEMPÉRATURE" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" msgstr "Branche cible" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "Mot de passe du partage de connexion" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "Options" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "Mode de débogage de l'interface utilisateur" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "DÉSINSTALLER" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "METTRE À JOUR" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "Désinstaller" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "Inconnu" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "Les mises à jour ne sont téléchargées que lorsque la voiture est éteinte." -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "Mettre à niveau maintenant" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "Téléverser les données de la caméra orientée conducteur et aider à améliorer l'algorithme de surveillance du conducteur." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "Utiliser le système métrique" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "VÉHICULE" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "VOIR" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "En attente de démarrage" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "Bienvenue sur openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Lorsque activé, appuyer sur la pédale d'accélérateur désengagera openpilot." -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Réseau Wi-Fi limité" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "Mauvais mot de passe" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Vous devez accepter les conditions générales pour utiliser openpilot." -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Vous devez accepter les conditions générales pour utiliser openpilot. Lisez les dernières conditions sur https://comma.ai/terms avant de continuer." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "démarrage de la caméra" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "vérification..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" msgstr "défaut" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "bas" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "téléchargement..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "échec de la vérification de mise à jour" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "finalisation de la mise à jour..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "pour \"{}\"" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "ne pas remplir pour une configuration automatique" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "gauche" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "limité" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "jamais" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "maintenant" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "Contrôle longitudinal openpilot (Alpha)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot indisponible" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "Le contrôle longitudinal openpilot pourra arriver dans une future mise à jour." -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot exige que l'appareil soit monté à moins de 4° à gauche ou à droite et à moins de 5° vers le haut ou 9° vers le bas." -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "droite" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "non limité" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "haut" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "à jour, dernière vérification jamais" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "à jour, dernière vérification {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "mise à jour disponible" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTE" msgstr[1] "{} ALERTES" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "il y a {} jour" msgstr[1] "il y a {} jours" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "il y a {} heure" msgstr[1] "il y a {} heures" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "il y a {} minute" msgstr[1] "il y a {} minutes" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment de votre conduite est dans l'ensemble d'entraînement jusqu'à présent." msgstr[1] "{} segments de votre conduite sont dans l'ensemble d'entraînement jusqu'à présent." -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ABONNÉ" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Mode Firehose 🔥" diff --git a/selfdrive/ui/translations/app_ja.po b/selfdrive/ui/translations/app_ja.po index 41eb91dd58b..78d3cf17c67 100644 --- a/selfdrive/ui/translations/app_ja.po +++ b/selfdrive/ui/translations/app_ja.po @@ -1,1029 +1,820 @@ -# Japanese translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-22 16:32-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: ja\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: ja\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " ステアリングトルク応答のキャリブレーションが完了しました。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " ステアリングトルク応答のキャリブレーションは{}%完了しました。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " デバイスは{:.1f}°{}、{:.1f}°{}の向きです。" -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "走行データを1年間保存" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "24時間365日のLTE接続" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "

ステアリング遅延のキャリブレーションが完了しました。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "

ステアリング遅延のキャリブレーションは{}%完了しました。" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "追加" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "APN設定" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "過度な作動を承認" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" msgstr "詳細設定" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "アグレッシブ" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "同意する" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "常時ドライバーモニタリング" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "本当に電源をオフにしますか?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "本当に再起動しますか?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "本当にキャリブレーションをリセットしますか?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "本当にアンインストールしますか?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "戻る" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.aiで comma prime に加入" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.aiをホーム画面に追加してアプリのように使いましょう" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "変更" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "確認" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "チルモードON" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" msgstr "接続" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "接続中..." -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "キャンセル" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "従量課金の携帯回線" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "言語を変更" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "車が起動中の場合、この設定を変更するとopenpilotが再起動します。" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"を押して右側のQRコードをスキャン" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "閉じる" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "現在のバージョン" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "ダウンロード" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "拒否する" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "拒否してopenpilotをアンインストール" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "開発者" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "デバイス" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "アクセルで解除" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "解除して電源オフ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "解除して再起動" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "解除してキャリブレーションをリセット" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "速度をmphではなくkm/hで表示します。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" msgstr "ドングルID" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "ダウンロード" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "ドライバーカメラ" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "走行性格" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "編集" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "エラー" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "実験モードON" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "有効化" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "ADBを有効化" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "車線逸脱警報を有効化" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" msgstr "ローミングを有効化" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "SSHを有効化" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" msgstr "テザリングを有効化" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilotが未作動でもドライバーモニタリングを有効にします。" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "openpilotを有効化" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "openpilot縦制御(アルファ)のトグルを有効にすると実験モードが使用できます。" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "APNを入力" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "SSIDを入力" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "新しいテザリングのパスワードを入力" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "パスワードを入力" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "GitHubユーザー名を入力" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" msgstr "エラー" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "実験モード" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "この車では縦制御に純正ACCを使用するため、現在実験モードは利用できません。" -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "削除中..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "セットアップを完了" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" -msgstr "Firehose" +msgstr "大量配信" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Firehoseモード" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" msgstr "削除" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fiネットワーク「{}」を削除しますか?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "良好" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "スマートフォンで https://connect.comma.ai にアクセス" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "高温" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" msgstr "非公開ネットワーク" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "インストール" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "IPアドレス" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "アップデートをインストール" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "ジョイスティックデバッグモード" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "読み込み中" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "縦制御マヌーバーモード" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "最大" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "学習データのアップロードを最大化してopenpilotの運転モデルを改善しましょう。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" msgstr "該当なし" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "いいえ" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "ネットワーク" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "SSH鍵が見つかりません" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" msgstr "ユーザー'{}'のSSH鍵が見つかりません" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "リリースノートはありません。" -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "オフライン" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "OK" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "オンライン" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "開く" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "ペアリング" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "プレビュー" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "prime の特典:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "デバイスをペアリング" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "デバイスをペアリング" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "デバイスをあなたの comma アカウントにペアリング" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "デバイスを comma connect(connect.comma.ai)とペアリングして、comma prime 特典を受け取りましょう。" -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "初回ペアリングを完了するにはWi‑Fiに接続してください" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "電源オフ" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "従量課金のWi‑Fi接続時は大きなデータのアップロードを抑制" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "従量課金の携帯回線接続時は大きなデータのアップロードを抑制" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "ドライバー向きカメラのプレビューでモニタリングの視界を確認します。(車両は停止状態である必要があります)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "QRコードエラー" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "削除" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "リセット" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "確認" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "再起動" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "デバイスを再起動" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "再起動して更新" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "ドライバーカメラを記録してアップロード" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "マイク音声を記録してアップロード" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "走行中にマイク音声を記録・保存します。音声は comma connect のドライブレコーダー動画に含まれます。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "規制情報" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "リラックス" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "リモートアクセス" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "リモートスナップショット" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "リクエストがタイムアウトしました" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "リセット" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "キャリブレーションをリセット" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "トレーニングガイドを確認" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilotのルール、機能、制限を確認" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" msgstr "選択" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" msgstr "SSH鍵" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fiネットワークを検索中..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "選択" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" msgstr "ブランチを選択" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "言語を選択" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "シリアル" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "更新を後で通知" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" msgstr "ソフトウェア" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" msgstr "スタンダード" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "システムが応答しません" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "すぐに手動介入してください" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" msgstr "温度" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" msgstr "対象ブランチ" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "テザリングのパスワード" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "トグル" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "UIデバッグモード" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "アンインストール" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "更新" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "アンインストール" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "不明" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "アップデートは車両の電源が切れている間のみダウンロードされます。" -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "今すぐアップグレード" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "ドライバー向きカメラのデータをアップロードしてモニタリングアルゴリズムの改善に協力してください。" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "メートル法を使用" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "車両" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "表示" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "開始待機中" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "openpilotへようこそ" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "有効にすると、アクセルを踏むとopenpilotが解除されます。" -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi‑Fiネットワーク(従量課金)" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "パスワードが違います" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilotを使用するには、利用規約に同意する必要があります。" -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "openpilotを使用するには利用規約に同意する必要があります。続行する前に https://comma.ai/terms の最新の規約をお読みください。" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "カメラを起動中" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "チェック中..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" msgstr "既定" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "下" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "ダウンロード中..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "アップデートの確認に失敗しました" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "アップデートを終了しています..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "「{}」向け" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "自動設定の場合は空欄のままにしてください" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "左" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "従量" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "なし" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "今" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縦制御(アルファ)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilotは利用できません" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "openpilotの縦制御は将来のアップデートで提供される可能性があります。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilotでは、デバイスの取り付け角度が左右±4°、上方向5°以内、下方向9°以内である必要があります。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "右" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "非従量" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "上" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "最新です。最終確認: なし" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "最新です。最終確認: {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "更新があります" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}件のアラート" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}日前" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}時間前" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}分前" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "これまでにあなたの走行の{}セグメントが学習データセットに含まれています。" -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ 登録済み" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehoseモード 🔥" diff --git a/selfdrive/ui/translations/app_ko.po b/selfdrive/ui/translations/app_ko.po index 9b73a22387a..24306ae02a7 100644 --- a/selfdrive/ui/translations/app_ko.po +++ b/selfdrive/ui/translations/app_ko.po @@ -1,1029 +1,820 @@ -# Korean translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-22 16:32-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: ko\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: ko\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " 스티어링 토크 응답 보정이 완료되었습니다." -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " 스티어링 토크 응답 보정이 {}% 완료되었습니다." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 장치는 {:.1f}° {} 및 {:.1f}° {} 방향을 가리키고 있습니다." -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "주행 데이터 1년 보관" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "연중무휴 LTE 연결" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "

스티어링 지연 보정이 완료되었습니다." -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "

스티어링 지연 보정이 {}% 완료되었습니다." -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "추가" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "APN 설정" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "과도한 작동을 확인" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" msgstr "고급" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "공격적" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "동의" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "운전자 모니터링 항상 켜짐" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "정말 전원을 끄시겠습니까?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "정말 재시작하시겠습니까?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "정말 보정을 재설정하시겠습니까?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "정말 제거하시겠습니까?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "뒤로" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai에서 comma prime 회원이 되세요" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.ai를 홈 화면에 추가하여 앱처럼 사용하세요" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "변경" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "확인" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "안정적 모드 켜짐" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" msgstr "연결" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "연결 중..." -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "취소" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "종량제 셀룰러" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "언어 변경" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "차량 전원이 켜져 있으면 이 설정을 변경할 때 openpilot이 재시작됩니다." -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"add new device\"를 눌러 오른쪽의 QR 코드를 스캔하세요" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "닫기" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "현재 버전" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "다운로드" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "거부" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "거부하고 openpilot 제거" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "개발자" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "장치" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "가속 페달로 해제" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "해제 후 전원 끄기" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "해제 후 재시작" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "해제 후 캘리브레이션 재설정" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "속도를 mph 대신 km/h로 표시합니다." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" msgstr "동글 ID" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "다운로드" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "운전자 카메라" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "주행 성향" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "편집" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "오류" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "실험 모드 켜짐" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "사용" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "ADB 사용" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "차선 이탈 경고 사용" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" msgstr "로밍 사용" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "SSH 사용" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" msgstr "테더링 사용" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot이 작동 중이 아닐 때도 운전자 모니터링을 사용합니다." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "openpilot 사용" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "실험 모드를 사용하려면 openpilot 롱컨 제어(알파) 토글을 켜세요." -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "APN 입력" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "SSID 입력" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "새 테더링 비밀번호 입력" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "비밀번호 입력" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "GitHub 사용자 이름 입력" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" msgstr "오류" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "실험 모드" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "이 차량은 롱컨 제어에 순정 ACC를 사용하므로 현재 실험 모드를 사용할 수 없습니다." -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "삭제 중..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "설정 완료" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" msgstr "파이어호스" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "파이어호스 모드" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" msgstr "삭제" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Wi‑Fi 네트워크 \"{}\"를 삭제하시겠습니까?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "양호" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "휴대폰에서 https://connect.comma.ai 에 접속하세요" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "높음" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" msgstr "숨겨진 네트워크" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "설치" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "IP 주소" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "업데이트 설치" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "조이스틱 디버그 모드" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "로딩 중" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "롱컨 기동 모드" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "최대" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "학습 데이터 업로드를 최대화하여 openpilot의 주행 모델을 개선하세요." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" msgstr "해당 없음" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "아니오" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "네트워크" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "SSH 키를 찾을 수 없습니다" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" msgstr "사용자 '{}'의 SSH 키를 찾을 수 없습니다" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "릴리스 노트가 없습니다." -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "오프라인" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "확인" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "온라인" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "열기" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "페어링" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "미리보기" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "프라임 기능:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "장치 페어링" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "장치 페어링" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "장치를 귀하의 comma 계정에 페어링하세요" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "장치를 comma connect(connect.comma.ai)와 페어링하고 comma 프라임 혜택을 받으세요." -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "초기 페어링을 완료하려면 Wi‑Fi에 연결하세요" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "전원 끄기" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "종량제 Wi‑Fi 연결 시 대용량 업로드 방지" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "종량제 셀룰러 연결 시 대용량 업로드 방지" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "운전자 모니터링의 가시성을 확인하기 위해 운전자 카메라를 미리 봅니다. (차량은 꺼져 있어야 합니다)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "QR 코드 오류" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "제거" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "재설정" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "검토" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "재시작" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "장치 재시작" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "재시작 및 업데이트" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "운전자 카메라 기록 및 업로드" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "마이크 오디오 기록 및 업로드" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "주행 중 마이크 오디오를 기록하고 저장합니다. 오디오는 comma connect의 대시캠 영상에 포함됩니다." -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "규제 정보" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "편안한" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "원격 액세스" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "원격 스냅샷" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "요청 시간이 초과되었습니다" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "재설정" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "캘리브레이션 재설정" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "학습 가이드 검토" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilot의 규칙, 기능 및 제한을 검토" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" msgstr "선택" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" msgstr "SSH 키" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Wi‑Fi 네트워크 검색 중..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "선택" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" msgstr "브랜치 선택" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "언어 선택" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "시리얼" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "업데이트 나중에 알림" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" msgstr "소프트웨어" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" msgstr "표준" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "시스템 응답 없음" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "즉시 수동 조작하세요" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" msgstr "온도" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" msgstr "대상 브랜치" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "테더링 비밀번호" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "토글" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "UI 디버그 모드" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "제거" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "업데이트" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "제거" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "알수없음" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "업데이트는 차량 전원이 꺼져 있을 때만 다운로드됩니다." -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "지금 업그레이드" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "운전자 방향 카메라 데이터를 업로드하여 운전자 모니터링 알고리즘 개선에 도움을 주세요." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "미터법 사용" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "차량" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "보기" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "시작 대기 중" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "openpilot에 오신 것을 환영합니다" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "이 옵션을 켜면 가속 페달을 밟을 때 openpilot이 해제됩니다." -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 네트워크 종량제" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "비밀번호가 올바르지 않습니다" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot을 사용하려면 약관에 동의해야 합니다." -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "openpilot을 사용하려면 약관에 동의해야 합니다. 계속하기 전에 https://comma.ai/terms 에서 최신 약관을 읽어주세요." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "카메라 시작 중" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "확인 중..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma 프라임" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" msgstr "기본값" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "아래" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "다운로드 중..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "업데이트 확인 실패" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "업데이트 마무리 중..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "\"{}\"용" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "자동 구성을 사용하려면 비워 두세요" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "왼쪽" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "종량제" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "없음" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "지금" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 롱컨 제어(알파)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot 사용 불가" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 롱컨 제어는 향후 업데이트에서 제공될 수 있습니다." -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot은 장치를 좌우 4°, 위쪽 5°, 아래쪽 9° 이내로 장착해야 합니다." -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "오른쪽" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "비종량제" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "위" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "최신입니다. 마지막 확인: 없음" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "최신입니다. 마지막 확인: {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "업데이트 가능" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{}건의 알림" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{}일 전" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{}시간 전" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{}분 전" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "현재까지 귀하의 주행 {}구간이 학습 데이터셋에 포함되었습니다." -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ 구독됨" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 파이어호스 모드 🔥" diff --git a/selfdrive/ui/translations/app_pt-BR.po b/selfdrive/ui/translations/app_pt-BR.po index 1adb797c887..58f20944794 100644 --- a/selfdrive/ui/translations/app_pt-BR.po +++ b/selfdrive/ui/translations/app_pt-BR.po @@ -1,1036 +1,825 @@ -# Language pt-BR translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-21 00:00-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: pt-BR\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: pt-BR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -"X-Language: pt_BR\n" -"X-Source-Language: C\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " A calibração da resposta de torque da direção foi concluída." -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " A calibração da resposta de torque da direção está {}% concluída." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Seu dispositivo está apontado {:.1f}° {} e {:.1f}° {}." -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "1 ano de armazenamento de condução" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "Conectividade LTE 24/7" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "

A calibração da latência da direção está concluída." -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "

A calibração da latência da direção está {}% concluída." -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "ADICIONAR" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "Configuração de APN" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "Reconhecer Atuação Excessiva" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" msgstr "Avançado" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "Agressivo" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "Concordo" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "Monitoramento de Motorista Sempre Ativo" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "Tem certeza de que deseja desligar?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "Tem certeza de que deseja reiniciar?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "Tem certeza de que deseja redefinir a calibração?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "Tem certeza de que deseja desinstalar?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "Voltar" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "Torne-se membro comma prime em connect.comma.ai" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Adicione connect.comma.ai à tela inicial para usá-lo como um app" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "ALTERAR" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "VERIFICAR" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "MODO CHILL ATIVO" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" msgstr "CONECTAR" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "CONECTANDO..." -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Cancelar" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Dados móveis limitados" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "Alterar Idioma" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "Alterar esta configuração reiniciará o openpilot se o carro estiver ligado." -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Toque em \"adicionar novo dispositivo\" e escaneie o QR code à direita" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "Fechar" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "Versão Atual" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "BAIXAR" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "Recusar" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "Recusar, desinstalar o openpilot" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "Desenvolv" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "Dispositivo" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "Desativar ao pressionar o acelerador" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "Desativar para Desligar" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "Desativar para Reiniciar" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "Desativar para Redefinir Calibração" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "Exibir velocidade em km/h em vez de mph." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" msgstr "ID do Dongle" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "Baixar" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "Câmera do Motorista" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Personalidade" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "EDITAR" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "ERRO" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "MODO EXPERIMENTAL ATIVO" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "Ativar" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "Ativar ADB" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "Ativar alertas de saída de faixa" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" -msgstr "Ativar openpilot" +msgstr "Ativar roaming" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "Ativar SSH" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" -msgstr "Ativar alertas de saída de faixa" +msgstr "Ativar compartilhamento" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Ativar monitoramento do motorista mesmo quando o openpilot não está engajado." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "Ativar openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Ative a opção de controle longitudinal do openpilot (alpha) para permitir o Modo Experimental." -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "Digite APN" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "Digite SSID" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Digite nova senha tethering" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "Digite a senha" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "Digite seu nome de usuário do GitHub" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" msgstr "Erro" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "Modo Experimental" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "O Modo Experimental está indisponível neste carro pois o ACC original do carro é usado para controle longitudinal." -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "ESQUECENDO..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "Configure" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" -msgstr "Firehose" +msgstr "Fluxo contínuo" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Modo Firehose" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" msgstr "Esquecer" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Esquecer rede Wi-Fi \"{}\"?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "BOM" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "Acesse https://connect.comma.ai no seu telefone" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "ALTO" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" -msgstr "Rede" +msgstr "Rede oculta" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "INSTALAR" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "Endereço IP" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "Instalar Atualização" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "Modo de Depuração do Joystick" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "CARREGANDO" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Modo de Manobra Longitudinal" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "MÁX" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "Maximize seus envios de dados de treinamento para melhorar os modelos de condução do openpilot." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" -msgstr "N/A" +msgstr "Indisp." -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "NÃO" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "Rede" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "Nenhuma chave SSH encontrada" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" -msgstr "Nenhuma chave SSH encontrada para o usuário '{username}'" +msgstr "Nenhuma chave SSH encontrada para o usuário '{}'" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "Sem notas de versão disponíveis." -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" -msgstr "OFFLINE" +msgstr "OFF-LINE" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "OK" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" -msgstr "ONLINE" +msgstr "ON-LINE" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "Abrir" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "EMPARELHAR" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "PRÉVIA" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "RECURSOS PRIME:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "Emparelhar Dispositivo" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "Emparelhar dispositivo" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "Emparelhe seu dispositivo à sua conta comma" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "Emparelhe seu dispositivo com o comma connect (connect.comma.ai) e resgate sua oferta comma prime." -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Conecte-se ao Wi‑Fi para concluir o emparelhamento inicial" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "Desligar" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Evitar uploads grandes de dados em conexões Wi-Fi limitadas" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Evitar uploads grandes de dados em conexões móveis limitadas" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "Pré-visualize a câmera voltada para o motorista para garantir que o monitoramento do motorista tenha boa visibilidade. (veículo deve estar desligado)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "Erro no QR Code" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "REMOVER" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "REDEFINIR" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "REVISAR" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "Reiniciar" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "Reiniciar Dispositivo" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "Reiniciar e Atualizar" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "Gravar e Enviar Câmera do Motorista" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "Gravar e Enviar Áudio do Microfone" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "Grave e armazene o áudio do microfone enquanto dirige. O áudio será incluído no vídeo da dashcam no comma connect." -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "Regulatório" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "Relaxado" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "Acesso remoto" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "Capturas remotas" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "Tempo da solicitação esgotado" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "Redefinir" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "Redefinir Calibração" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "Revisar Guia de Treinamento" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "Revise as regras, recursos e limitações do openpilot" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" msgstr "SELECIONAR" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" msgstr "Chaves SSH" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Procurando redes Wi-Fi..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "Selecione" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" -msgstr "Selecione uma branch" +msgstr "Selecione uma ramificação" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "Selecione um idioma" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" -msgstr "Serial" +msgstr "Número de série" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "Adiar Atualização" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" -msgstr "Software" +msgstr "Sistema" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" msgstr "Padrão" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "Sistema sem resposta" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "ASSUMA O CONTROLE IMEDIATAMENTE" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" -msgstr "TEMP" +msgstr "TEMP." -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" msgstr "Branch Alvo" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "Senha Tethering" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" -msgstr "Toggles" +msgstr "Alternativas" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "Modo de depuração da interface do usuário" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "DESINSTALAR" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "ATUALIZAR" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "Desinstalar" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "Desconhecido" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "Atualizações são baixadas apenas com o carro desligado." -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "Atualizar Agora" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "Envie dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramento do motorista." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "Usar Sistema Métrico" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "VEÍCULO" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "VER" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "Aguardando para iniciar" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "Bem-vindo ao openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Quando ativado, pressionar o pedal do acelerador desengajará o openpilot." -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Rede Wi-Fi limitada" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "Senha errada" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Você deve aceitar os Termos e Condições para usar o openpilot." -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Você deve aceitar os Termos e Condições para usar o openpilot. Leia os termos mais recentes em https://comma.ai/terms antes de continuar." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "câmera iniciando" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "verificando..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" -msgstr "default" +msgstr "padrão" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "para baixo" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "baixando..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "falha ao verificar atualização" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "finalizando atualização..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "para \"{}\"" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "deixe em branco para configuração automática" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "à esquerda" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "limitados" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "nunca" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "agora" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "Controle Longitudinal do openpilot (Alpha)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot Indisponível" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "o controle longitudinal do openpilot pode vir em uma atualização futura." -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "o openpilot requer que o dispositivo seja montado dentro de 4° para a esquerda ou direita e dentro de 5° para cima ou 9° para baixo." -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "à direita" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "ilimitados" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "para cima" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "atualizado, última verificação: nunca" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "atualizado, última verificação: {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "atualização disponível" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} ALERTA" msgstr[1] "{} ALERTAS" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} dia atrás" msgstr[1] "{} dias atrás" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} hora atrás" msgstr[1] "{} horas atrás" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} minuto atrás" msgstr[1] "{} minutos atrás" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segmento da sua condução está no conjunto de treinamento até agora." msgstr[1] "{} segmentos da sua condução estão no conjunto de treinamento até agora." -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ASSINADO" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Modo Firehose 🔥" diff --git a/selfdrive/ui/translations/app_th.po b/selfdrive/ui/translations/app_th.po index facf52d9228..4e45cae14b3 100644 --- a/selfdrive/ui/translations/app_th.po +++ b/selfdrive/ui/translations/app_th.po @@ -1,1034 +1,820 @@ -# Thai translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-22 16:32-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: th\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: th\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." -msgstr "" +msgstr "การสอบเทียบการตอบสนองแรงบิดของพวงมาลัยเสร็จสมบูรณ์" -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." -msgstr "" +msgstr "การสอบเทียบการตอบสนองแรงบิดของพวงมาลัยเสร็จสมบูรณ์ {}%" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." -msgstr "" +msgstr "อุปกรณ์ของคุณชี้ไปที่ {:.1f}° {} และ {:.1f}° {}" -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" -msgstr "" +msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" -msgstr "" +msgstr "พื้นที่เก็บข้อมูลไดรฟ์ 1 ปี" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" -msgstr "" +msgstr "การเชื่อมต่อ LTE ตลอด 24 ชั่วโมงทุกวัน" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" -msgstr "" +msgstr "2จี" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" -msgstr "" +msgstr "3จี" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" -msgstr "" +msgstr "5จี" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." -msgstr "" +msgstr "

การปรับเทียบความล่าช้าของพวงมาลัยเสร็จสมบูรณ์" -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." -msgstr "" +msgstr "

การปรับเทียบความล่าช้าของพวงมาลัย {}% เสร็จสมบูรณ์" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" -msgstr "" +msgstr "เพิ่ม" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" -msgstr "" +msgstr "การตั้งค่า APN" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" -msgstr "" +msgstr "รับทราบการดำเนินการที่มากเกินไป" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" -msgstr "" +msgstr "ขั้นสูง" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" -msgstr "" +msgstr "ก้าวร้าว" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" -msgstr "" +msgstr "เห็นด้วย" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" -msgstr "" +msgstr "การตรวจสอบไดรเวอร์ตลอดเวลา" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" -msgstr "" +msgstr "คุณแน่ใจหรือไม่ว่าต้องการปิดเครื่อง?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" -msgstr "" +msgstr "คุณแน่ใจหรือไม่ว่าต้องการรีบูต?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" -msgstr "" +msgstr "คุณแน่ใจหรือไม่ว่าต้องการรีเซ็ตการปรับเทียบ" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" -msgstr "" +msgstr "คุณแน่ใจหรือไม่ว่าต้องการถอนการติดตั้ง?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" -msgstr "" +msgstr "กลับ" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" -msgstr "" +msgstr "เป็นสมาชิกจุลภาคไพรม์ที่ Connect.comma.ai" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" -msgstr "" +msgstr "คั่นหน้า Connect.comma.ai ไปที่หน้าจอหลักของคุณเพื่อใช้เหมือนแอป" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" -msgstr "" +msgstr "เปลี่ยน" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" -msgstr "" +msgstr "ตรวจสอบ" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" -msgstr "" +msgstr "เปิดโหมด Chill" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" -msgstr "" +msgstr "เชื่อมต่อ" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." -msgstr "" +msgstr "กำลังเชื่อมต่อ..." -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" -msgstr "" +msgstr "ยกเลิก" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" -msgstr "" +msgstr "เซลล์วัดแสง" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" -msgstr "" +msgstr "เปลี่ยนภาษา" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." -msgstr "" +msgstr "การเปลี่ยนการตั้งค่านี้จะรีสตาร์ท Openpilot หากรถเปิดอยู่" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" -msgstr "" +msgstr "คลิก \"เพิ่มอุปกรณ์ใหม่\" และสแกนโค้ด QR ทางด้านขวา" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" -msgstr "" +msgstr "ปิด" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" -msgstr "" +msgstr "เวอร์ชันปัจจุบัน" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" -msgstr "" +msgstr "ดาวน์โหลด" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" -msgstr "" +msgstr "ปฏิเสธ" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" -msgstr "" +msgstr "ปฏิเสธ ถอนการติดตั้ง openpilot" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" -msgstr "" +msgstr "นักพัฒนา" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" -msgstr "" +msgstr "อุปกรณ์" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" -msgstr "" +msgstr "ปลดเมื่อเหยียบคันเร่ง" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" -msgstr "" +msgstr "ปลดเพื่อปิดเครื่อง" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" -msgstr "" +msgstr "ยกเลิกการเชื่อมต่อเพื่อรีบูต" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" -msgstr "" +msgstr "ปลดเพื่อรีเซ็ตการปรับเทียบ" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." -msgstr "" +msgstr "แสดงความเร็วเป็น km/h แทน mph" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" -msgstr "" +msgstr "รหัสดองเกิล" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" -msgstr "" +msgstr "ดาวน์โหลด" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" -msgstr "" +msgstr "กล้องไดร์เวอร์" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" -msgstr "" +msgstr "บุคลิกภาพในการขับขี่" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" -msgstr "" +msgstr "แก้ไข" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" -msgstr "" +msgstr "ข้อผิดพลาด" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" -msgstr "" +msgstr "ผลประโยชน์ทับซ้อน" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" -msgstr "" +msgstr "โหมดทดลองเปิดอยู่" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" -msgstr "" +msgstr "เปิดใช้งาน" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" -msgstr "" +msgstr "เปิดใช้งาน ADB" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" -msgstr "" +msgstr "เปิดใช้งานคำเตือนการออกนอกเลน" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" -msgstr "" +msgstr "เปิดใช้งานโรมมิ่ง" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" -msgstr "" +msgstr "เปิดใช้งาน SSH" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" -msgstr "" +msgstr "เปิดใช้งานการปล่อยสัญญาณ" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." -msgstr "" +msgstr "เปิดใช้งานการตรวจสอบไดรเวอร์แม้ว่าจะไม่ได้ใช้งาน openpilot ก็ตาม" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" -msgstr "" +msgstr "เปิดใช้งานโอเพ่นไพลอต" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." -msgstr "" +msgstr "เปิดใช้งานการสลับการควบคุมตามยาวของ openpilot (อัลฟา) เพื่ออนุญาตโหมดการทดลอง" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" -msgstr "" +msgstr "ป้อน APN" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" -msgstr "" +msgstr "ป้อน SSID" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" -msgstr "" +msgstr "ป้อนรหัสผ่านการปล่อยสัญญาณใหม่" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" -msgstr "" +msgstr "ใส่รหัสผ่าน" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" -msgstr "" +msgstr "ป้อนชื่อผู้ใช้ GitHub ของคุณ" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" -msgstr "" +msgstr "ข้อผิดพลาด" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" -msgstr "" +msgstr "โหมดทดลอง" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." -msgstr "" +msgstr "ขณะนี้โหมดทดลองไม่สามารถใช้งานได้บนรถคันนี้ เนื่องจาก ACC ในสต็อกของรถใช้สำหรับการควบคุมตามยาว" -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." -msgstr "" +msgstr "กำลังลืม..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" -msgstr "" +msgstr "เสร็จสิ้นการตั้งค่า" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" -msgstr "" +msgstr "สายดับเพลิง" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" -msgstr "" +msgstr "โหมดสายดับเพลิง" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" -msgstr "" +msgstr "ลืม" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "" +msgstr "ลืมเครือข่าย Wi-Fi \"{}\" หรือไม่" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" -msgstr "" +msgstr "ดี" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" -msgstr "" +msgstr "ไปที่ https://connect.comma.ai บนโทรศัพท์ของคุณ" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" -msgstr "" +msgstr "สูง" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" -msgstr "" +msgstr "เครือข่ายที่ซ่อนอยู่" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" -msgstr "" +msgstr "ติดตั้ง" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" -msgstr "" +msgstr "ที่อยู่ IP" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" -msgstr "" +msgstr "ติดตั้งอัปเดต" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" -msgstr "" +msgstr "โหมดดีบักจอยสติ๊ก" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" -msgstr "" +msgstr "กำลังโหลด" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" -msgstr "" +msgstr "แอลทีที" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" -msgstr "" +msgstr "โหมดการซ้อมรบตามยาว" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" -msgstr "" +msgstr "สูงสุด" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." -msgstr "" +msgstr "เพิ่มการอัปโหลดข้อมูลการฝึกของคุณให้สูงสุดเพื่อปรับปรุงโมเดลการขับขี่ของ Openpilot" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" -msgstr "" +msgstr "ไม่มี" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" -msgstr "" +msgstr "เลขที่" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" -msgstr "" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "" +msgstr "เครือข่าย" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" -msgstr "" +msgstr "ไม่พบคีย์ SSH สำหรับผู้ใช้ '{}'" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." -msgstr "" +msgstr "ไม่มีบันทึกประจำรุ่น" -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" -msgstr "" +msgstr "ออฟไลน์" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" -msgstr "" +msgstr "ตกลง" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" -msgstr "" +msgstr "ออนไลน์" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" -msgstr "" +msgstr "เปิด" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" -msgstr "" +msgstr "คู่" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" -msgstr "" +msgstr "แพนด้า" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" -msgstr "" +msgstr "ดูตัวอย่าง" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" -msgstr "" +msgstr "คุณสมบัติเด่น:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" -msgstr "" +msgstr "จับคู่อุปกรณ์" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" -msgstr "" +msgstr "จับคู่อุปกรณ์" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" -msgstr "" +msgstr "จับคู่อุปกรณ์ของคุณกับบัญชีลูกน้ำของคุณ" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." -msgstr "" +msgstr "จับคู่อุปกรณ์ของคุณกับการเชื่อมต่อด้วยเครื่องหมายจุลภาค (connect.comma.ai) และรับข้อเสนอจุลภาคเฉพาะของคุณ" -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" -msgstr "" +msgstr "โปรดเชื่อมต่อ Wi-Fi เพื่อทำการจับคู่ครั้งแรกให้เสร็จสิ้น" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" -msgstr "" +msgstr "ปิดเครื่อง" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" +msgstr "ป้องกันการอัปโหลดข้อมูลขนาดใหญ่เมื่อใช้การเชื่อมต่อ Wi-Fi แบบมิเตอร์" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" +msgstr "ป้องกันการอัพโหลดข้อมูลขนาดใหญ่เมื่อใช้การเชื่อมต่อมือถือแบบคิดค่าบริการตามปริมาณข้อมูล" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" -msgstr "" +msgstr "ดูตัวอย่างกล้องที่หันหน้าไปทางคนขับเพื่อให้แน่ใจว่าการตรวจสอบผู้ขับขี่มีทัศนวิสัยที่ดี (รถจะต้องถูกปิด)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" -msgstr "" +msgstr "ข้อผิดพลาดรหัส QR" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" -msgstr "" +msgstr "ลบ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" -msgstr "" +msgstr "รีเซ็ต" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" -msgstr "" +msgstr "ทบทวน" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" -msgstr "" +msgstr "รีบูต" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" -msgstr "" +msgstr "รีบูตอุปกรณ์" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" -msgstr "" +msgstr "รีบูตและอัปเดต" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" -msgstr "" +msgstr "บันทึกและอัพโหลดกล้องไดร์เวอร์" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" -msgstr "" +msgstr "บันทึกและอัปโหลดเสียงไมโครโฟน" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." -msgstr "" +msgstr "บันทึกและจัดเก็บเสียงไมโครโฟนขณะขับรถ เสียงจะรวมอยู่ในวิดีโอ dashcam ด้วยการเชื่อมต่อด้วยเครื่องหมายจุลภาค" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" -msgstr "" +msgstr "กฎระเบียบ" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" -msgstr "" +msgstr "ผ่อนคลาย" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" -msgstr "" +msgstr "การเข้าถึงระยะไกล" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" -msgstr "" +msgstr "สแนปชอตระยะไกล" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" -msgstr "" +msgstr "คำขอหมดเวลา" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" -msgstr "" +msgstr "รีเซ็ต" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" -msgstr "" +msgstr "รีเซ็ตการปรับเทียบ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" -msgstr "" +msgstr "ทบทวนคู่มือการฝึกอบรม" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" -msgstr "" +msgstr "ตรวจสอบกฎ คุณสมบัติ และข้อจำกัดของ openpilot" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" -msgstr "" +msgstr "เลือก" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" -msgstr "" +msgstr "คีย์ SSH" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." -msgstr "" +msgstr "กำลังสแกนเครือข่าย Wi-Fi..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" -msgstr "" +msgstr "เลือก" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" -msgstr "" +msgstr "เลือกสาขา" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" -msgstr "" +msgstr "เลือกภาษา" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" -msgstr "" +msgstr "อนุกรม" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" -msgstr "" +msgstr "เลื่อนการอัปเดต" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" -msgstr "" +msgstr "ซอฟต์แวร์" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" -msgstr "" +msgstr "มาตรฐาน" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" -msgstr "" +msgstr "ระบบไม่ตอบสนอง" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" -msgstr "" +msgstr "เข้าควบคุมทันที" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" -msgstr "" +msgstr "อุณหภูมิ" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" -msgstr "" +msgstr "สาขาเป้าหมาย" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" -msgstr "" +msgstr "รหัสผ่านการแชร์อินเทอร์เน็ต" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" -msgstr "" +msgstr "สลับ" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "โหมดดีบัก UI" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" -msgstr "" +msgstr "ถอนการติดตั้ง" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" -msgstr "" +msgstr "อัปเดต" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" -msgstr "" +msgstr "ถอนการติดตั้ง" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" -msgstr "" +msgstr "ไม่ทราบ" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." -msgstr "" +msgstr "การอัพเดตจะถูกดาวน์โหลดในขณะที่รถดับอยู่เท่านั้น" -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" -msgstr "" +msgstr "อัพเกรดทันที" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." -msgstr "" +msgstr "อัปโหลดข้อมูลจากกล้องที่หันเข้าหาคนขับและช่วยปรับปรุงอัลกอริธึมการตรวจสอบผู้ขับขี่" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" -msgstr "" +msgstr "ใช้ระบบเมตริก" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" -msgstr "" +msgstr "ยานพาหนะ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" -msgstr "" +msgstr "ดู" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" -msgstr "" +msgstr "กำลังรอที่จะเริ่ม" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" -msgstr "" +msgstr "ยินดีต้อนรับสู่ openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." -msgstr "" +msgstr "เมื่อเปิดใช้งาน การกดแป้นคันเร่งจะเป็นการปลดโอเพ่นไพลอต" -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" -msgstr "" +msgstr "อินเตอร์เน็ตไร้สาย" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" -msgstr "" +msgstr "เครือข่าย Wi-Fi มีการตรวจวัด" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" -msgstr "" +msgstr "รหัสผ่านผิด" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." -msgstr "" +msgstr "คุณต้องยอมรับข้อกำหนดและเงื่อนไขเพื่อใช้งาน openpilot" -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." -msgstr "" +msgstr "คุณต้องยอมรับข้อกำหนดและเงื่อนไขเพื่อใช้ openpilot อ่านข้อกำหนดล่าสุดได้ที่ https://comma.ai/terms ก่อนดำเนินการต่อ" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" -msgstr "" +msgstr "กำลังเริ่มกล้อง" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "กำลังตรวจสอบ..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" -msgstr "" +msgstr "เครื่องหมายลูกน้ำเฉพาะ" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" -msgstr "" +msgstr "ค่าเริ่มต้น" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" -msgstr "" +msgstr "ลง" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "กำลังดาวน์โหลด..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" -msgstr "" +msgstr "ไม่สามารถตรวจสอบการอัปเดตได้" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "กำลังสรุปการอัปเดต..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" -msgstr "" +msgstr "สำหรับ \"{}\"" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" -msgstr "" +msgstr "กม./ชม" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" -msgstr "" +msgstr "เว้นว่างไว้เพื่อกำหนดค่าอัตโนมัติ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" -msgstr "" +msgstr "ซ้าย" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" -msgstr "" +msgstr "คิดค่าบริการตามปริมาณ" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" -msgstr "" +msgstr "ไมล์/ชม." -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" -msgstr "" +msgstr "ไม่เคย" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" -msgstr "" +msgstr "ตอนนี้" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" -msgstr "" +msgstr "การควบคุมตามยาวของ openpilot (อัลฟา)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" -msgstr "" +msgstr "openpilot ไม่พร้อมใช้งาน" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." -msgstr "" +msgstr "ระบบควบคุมตามยาวของ openpilot อาจมาในการอัปเดตครั้งถัดไป" -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." -msgstr "" +msgstr "openpilot ต้องติดตั้งอุปกรณ์ให้อยู่ในช่วงเอียงซ้ายหรือขวาไม่เกิน 4° และเอียงขึ้นไม่เกิน 5° หรือเอียงลงไม่เกิน 9°" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" -msgstr "" +msgstr "ขวา" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" -msgstr "" +msgstr "ไม่จำกัดปริมาณ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" -msgstr "" +msgstr "ขึ้น" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" -msgstr "" +msgstr "เป็นเวอร์ชันล่าสุด ตรวจสอบครั้งล่าสุด: ไม่เคย" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" -msgstr "" +msgstr "เป็นเวอร์ชันล่าสุด ตรวจสอบครั้งล่าสุดเมื่อ {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" -msgstr "" +msgstr "มีอัปเดตใหม่" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "การแจ้งเตือน {} รายการ" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "{} วันที่แล้ว" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "{} ชั่วโมงที่แล้ว" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" -msgstr[0] "" -msgstr[1] "" +msgstr[0] "{} นาทีที่แล้ว" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." -msgstr[0] "" -msgstr[1] "" +msgstr[0] "ขณะนี้มีช่วงการขับขี่ของคุณ {} ช่วงอยู่ในชุดข้อมูลฝึกสอนแล้ว" -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" -msgstr "" +msgstr "✓ สมัครแล้ว" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" -msgstr "" +msgstr "🔥 โหมด Firehose 🔥" diff --git a/selfdrive/ui/translations/app_tr.po b/selfdrive/ui/translations/app_tr.po index 137d350c2a0..dbb5b325a61 100644 --- a/selfdrive/ui/translations/app_tr.po +++ b/selfdrive/ui/translations/app_tr.po @@ -1,1034 +1,825 @@ -# Turkish translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-20 18:19-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: tr\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: tr\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " Direksiyon tork tepkisi kalibrasyonu tamamlandı." -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " Direksiyon tork tepkisi kalibrasyonu {}% tamamlandı." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Cihazınız {:.1f}° {} ve {:.1f}° {} yönünde konumlandırılmış." -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "1 yıl sürüş depolaması" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "7/24 LTE bağlantısı" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." -msgstr "" +msgstr "

Direksiyon gecikmesi kalibrasyonu tamamlandı." -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." -msgstr "" +msgstr "

Direksiyon gecikmesi kalibrasyonu %{} tamamlandı." -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "EKLE" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" -msgstr "" +msgstr "APN Ayarı" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "Aşırı Müdahaleyi Onayla" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" -msgstr "" +msgstr "Gelişmiş" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "Agresif" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "Kabul et" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "Sürekli Sürücü İzleme" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "Kapatmak istediğinizden emin misiniz?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "Yeniden başlatmak istediğinizden emin misiniz?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "Kalibrasyonu sıfırlamak istediğinizden emin misiniz?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "Kaldırmak istediğinizden emin misiniz?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "Geri" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "connect.comma.ai adresinde comma prime üyesi olun" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "connect.comma.ai'yi ana ekranınıza ekleyerek bir uygulama gibi kullanın" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "DEĞİŞTİR" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "KONTROL ET" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "CHILL MODU AÇIK" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" msgstr "BAĞLAN" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "BAĞLAN" -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" -msgstr "" +msgstr "İptal" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" -msgstr "" +msgstr "Ölçülü Hücresel" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "Dili Değiştir" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr " Bu ayarı değiştirmek, araç çalışıyorsa openpilot'u yeniden başlatacaktır." -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "\"yeni cihaz ekle\"ye tıklayın ve sağdaki QR kodunu tarayın" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "Kapat" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "Geçerli Sürüm" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "İNDİR" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "Reddet" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "Reddet, openpilot'u kaldır" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "Geliştirici" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "Cihaz" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "Gaz Pedalında Devreden Çık" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "Kapatmak için Devreden Çıkın" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "Yeniden Başlatmak için Devreden Çıkın" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "Kalibrasyonu Sıfırlamak için Devreden Çıkın" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "Hızı mph yerine km/h olarak göster." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" -msgstr "Dongle ID" +msgstr "Dongle kimliği" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "İndir" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "Sürücü Kamerası" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Sürüş Kişiliği" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" -msgstr "" +msgstr "DÜZENLE" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "HATA" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "DENEYSEL MOD AÇIK" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "Etkinleştir" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "ADB'yi Etkinleştir" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "Şerit Terk Uyarılarını Etkinleştir" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" -msgstr "openpilot'u etkinleştir" +msgstr "Dolaşımı Etkinleştir" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "SSH'yi Etkinleştir" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" -msgstr "Şerit Terk Uyarılarını Etkinleştir" +msgstr "İnternet Paylaşımını Etkinleştir" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "openpilot devrede değilken bile sürücü izlemesini etkinleştir." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "openpilot'u etkinleştir" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Deneysel modu etkinleştirmek için openpilot boylamsal kontrolünü (alfa) açın." -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" -msgstr "" +msgstr "APN girin" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" -msgstr "" +msgstr "SSID girin" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" -msgstr "" +msgstr "Yeni internet paylaşımı şifresini girin" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" -msgstr "" +msgstr "Şifre girin" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "GitHub kullanıcı adınızı girin" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" -msgstr "" +msgstr "Hata" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "Deneysel Mod" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Bu araçta boylamsal kontrol için stok ACC kullanıldığından şu anda Deneysel mod kullanılamıyor." -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." -msgstr "" +msgstr "UNUTULUYOR..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "Kurulumu Bitir" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" -msgstr "Firehose" +msgstr "Yoğun veri akışı" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Firehose Modu" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" -msgstr "" +msgstr "Unut" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" -msgstr "" +msgstr "\"{}\" Wi‑Fi ağı unutulsun mu?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "İYİ" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "Telefonunuzda https://connect.comma.ai adresine gidin" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "YÜKSEK" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" -msgstr "Ağ" +msgstr "Gizli Ağ" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "YÜKLE" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" -msgstr "" +msgstr "IP Adresi" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "Güncellemeyi Yükle" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "Joystick Hata Ayıklama Modu" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "YÜKLENİYOR" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Boylamsal Manevra Modu" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "MAKS" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "openpilot'un sürüş modellerini iyileştirmek için eğitim veri yüklemelerinizi en üst düzeye çıkarın." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" -msgstr "" +msgstr "Yok" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "HAYIR" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "Ağ" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "SSH anahtarı bulunamadı" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" -msgstr "'{username}' için SSH anahtarı bulunamadı" +msgstr "'{}' kullanıcısı için SSH anahtarı bulunamadı" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "Sürüm notu mevcut değil." -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "ÇEVRİMDIŞI" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "OK" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "ÇEVRİMİÇİ" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "Aç" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "EŞLE" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "ÖNİZLEME" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "PRIME ÖZELLİKLERİ:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "Cihazı Eşle" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "Cihazı eşle" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "Cihazınızı comma hesabınızla eşleştirin" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "Cihazınızı comma connect (connect.comma.ai) ile eşleştirin ve comma prime teklifinizi alın." -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "İlk eşleştirmeyi tamamlamak için lütfen Wi‑Fi'a bağlanın" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "Kapat" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" -msgstr "" +msgstr "Ölçülü bir Wi‑Fi bağlantısındayken büyük veri yüklemelerini engelle" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" -msgstr "" +msgstr "Ölçülü bir hücresel bağlantıdayken büyük veri yüklemelerini engelle" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "Sürücü izleme görünürlüğünün iyi olduğundan emin olmak için sürücüye bakan kamerayı önizleyin. (araç kapalı olmalıdır)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "QR Kod Hatası" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "KALDIR" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "SIFIRLA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "GÖZDEN GEÇİR" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "Yeniden Başlat" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "Cihazı Yeniden Başlat" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "Yeniden Başlat ve Güncelle" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "Sürücü Kamerasını Kaydet ve Yükle" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "Mikrofon Sesini Kaydet ve Yükle" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "Sürüş sırasında mikrofon sesini kaydedip saklayın. Ses, comma connect'teki ön kamera videosuna dahil edilecektir." -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "Mevzuat" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "Rahat" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "Uzaktan erişim" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "Uzaktan anlık görüntüler" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "İstek zaman aşımına uğradı" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "Sıfırla" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "Kalibrasyonu Sıfırla" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "Eğitim Kılavuzunu İncele" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "openpilot'un kurallarını, özelliklerini ve sınırlamalarını gözden geçirin" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" -msgstr "" +msgstr "SEÇ" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" -msgstr "" +msgstr "SSH Anahtarları" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." -msgstr "" +msgstr "Wi‑Fi ağları taranıyor..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" -msgstr "" +msgstr "Seç" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" -msgstr "" +msgstr "Bir dal seçin" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "Bir dil seçin" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "Seri" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "Güncellemeyi Ertele" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" msgstr "Yazılım" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" msgstr "Standart" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "Sistem Yanıt Vermiyor" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "HEMEN KONTROLÜ DEVRALIN" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" -msgstr "TEMP" +msgstr "SIC." -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" -msgstr "" +msgstr "Hedef Dal" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" -msgstr "" +msgstr "İnternet Paylaşımı Şifresi" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "Seçenekler" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "Arayüz Hata Ayıklama Modu" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "KALDIR" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "GÜNCELLE" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "Kaldır" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "Bilinmiyor" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "Güncellemeler yalnızca araç kapalıyken indirilir." -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "Şimdi Yükselt" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "Sürücüye bakan kameradan veri yükleyin ve sürücü izleme algoritmasını geliştirmeye yardımcı olun." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "Metrik Sistemi Kullan" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "ARAÇ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "GÖRÜNTÜLE" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "Başlatma bekleniyor" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "openpilot'a hoş geldiniz" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Etkinleştirildiğinde, gaz pedalına basmak openpilot'u devreden çıkarır." -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" -msgstr "" +msgstr "Ölçülü Wi‑Fi Ağı" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" -msgstr "" +msgstr "Yanlış şifre" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz." -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "openpilot'u kullanmak için Şartlar ve Koşulları kabul etmelisiniz. Devam etmeden önce en güncel şartları https://comma.ai/terms adresinde okuyun." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "kamera başlatılıyor" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "kontrol ediliyor..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" -msgstr "" +msgstr "varsayılan" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "aşağı" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "indiriliyor..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "güncelleme kontrolü başarısız" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "güncelleme tamamlanıyor..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" -msgstr "" +msgstr "\"{}\" için" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" -msgstr "" +msgstr "otomatik yapılandırma için boş bırakın" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "sol" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" -msgstr "" +msgstr "ölçülü" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "asla" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "şimdi" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot Boylamsal Kontrol (Alfa)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot Kullanılamıyor" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot boylamsal kontrolü gelecekteki bir güncellemede gelebilir." -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot, cihazın sağa/sola 4° ve yukarı 5° veya aşağı 9° içinde monte edilmesini gerektirir." -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "sağ" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" -msgstr "" +msgstr "ölçüsüz" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "yukarı" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "güncel, son kontrol asla" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "güncel, son kontrol {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "güncelleme mevcut" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} UYARI" msgstr[1] "{} UYARILAR" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} gün önce" msgstr[1] "{} gün önce" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} saat önce" msgstr[1] "{} saat önce" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} dakika önce" msgstr[1] "{} dakika önce" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} segment sürüşünüz eğitim veri setinde." msgstr[1] "{} segment sürüşünüz eğitim veri setinde." -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ABONE" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose Modu 🔥" diff --git a/selfdrive/ui/translations/app_uk.po b/selfdrive/ui/translations/app_uk.po index e36ceac2ceb..3f3d1866579 100644 --- a/selfdrive/ui/translations/app_uk.po +++ b/selfdrive/ui/translations/app_uk.po @@ -1,1040 +1,830 @@ -# Ukrainian translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: \n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-11-19 12:21+0200\n" -"PO-Revision-Date: 2025-11-19 13:27+0200\n" -"Last-Translator: KeeFeeRe \n" -"Language-Team: none\n" -"Language: uk\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" -"X-Generator: Poedit 3.8\n" +"Language: uk\n" +"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " Калібрування реакції крутного моменту керма завершено." -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr "Калібрування реакції крутного моменту керма завершено на {}%." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " Ваш пристрій нахилено на {:.1f}° {} та {:.1f}° {}." -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "1 рік зберігання поїздок" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "Підключення LTE 24/7" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "

Калібрування затримки кермування завершено." -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "

Калібрування затримки кермування завершено на {}%." -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "ДОДАТИ" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "Налаштування APN" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "Визнайте надмірне спрацьовування" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" msgstr "Розширені" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "Агресивн." -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "Погодитися" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "Постійний моніторинг водія" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "Ви впевнені, що хочете вимкнути?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "Ви впевнені, що хочете перезавантажити?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "Ви впевнені, що хочете скинути калібрування?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "Ви впевнені, що хочете видалити?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "Назад" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "Станьте членом comma prime на connect.comma.ai" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "Додайте connect.comma.ai до головного екрану, щоб використовувати його як додаток." -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "ЗМІНИТИ" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "ПЕРЕВІРИТИ" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "СПОКІЙНИЙ РЕЖИМ" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" -msgstr "CONNECT" +msgstr "ПІДКЛЮЧИТИ" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "ПІДКЛЮЧА..." -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "Скасувати" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "Лімітне стільникове з'єднання" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "Змінити мову" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "Зміна цього параметра призведе до перезапуску openpilot, якщо автомобіль увімкнено." -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "Натисніть «додати новий пристрій» і відскануйте QR-код праворуч." -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "Закрити" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "Поточна версія" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "ВАНТАЖ" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "Відхилити" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "Відхилити, видалити openpilot" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "Розробник" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "Пристрій" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "Вимкнення при натисканні на педаль газу" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "Вимкніть openpilot, щоб вимкнути пристрій" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "Вимкніть openpilot, щоб перезавантажити" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "Деактивуйте для скидання калібрування" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "Відображати швидкість у км/год замість миль/год." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" msgstr "ID ключа" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "Завантажити" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "Камера водія" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "Стиль водіння" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "РЕДАГ." -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "ПОМИЛКА" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "ЕКСПЕРИМЕНТ. РЕЖИМ" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "Увімкнути" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "Увімкнути ADB" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "Увімкнути попередження про виїзд зі смуги" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" msgstr "Увімкнути роумінг" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "Увімкнути SSH" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" msgstr "Увімкнути точку доступу" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "Увімкнути моніторинг водія, навіть коли openpilot не ввімкнено." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "Увімкнути openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "Увімкніть перемикач поздовжнього керування openpilot (альфа), щоб увімкнути експериментальний режим." -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "Введіть APN" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "Введіть SSID" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "Введіть новий пароль для модему" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "Введіть пароль" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "Введіть ваш логін GitHub" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" msgstr "Помилка" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "Експериментальний режим" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "Експериментальний режим наразі недоступний для цього автомобіля, оскільки для поздовжнього керування використовується штатний адаптивний круїз-контроль (ACC)." -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "ЗАБУВАЮ..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "Завершити налаштування" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" msgstr "Злива" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Режим зливи" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" -msgstr "Заб-и" +msgstr "Забути" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "Забути мережу Wi-Fi \"{}\"?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "ДОБРА" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "Перейдіть на сайт https://connect.comma.ai на своєму телефоні." -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "ВИСОКА" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" msgstr "Прихована мережа" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "ВСТАНОВ." -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "IP-адреса" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "Встановити оновлення" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "Режим зневадження джойстика" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "ЗАВАНТАЖЕННЯ" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "Режим поздовжнього маневрування" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "МАКС" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "Максимізуйте завантаження навчальних даних, щоб поліпшити моделі openpilot." -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" msgstr "Н/Д" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "НЕМАЄ" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "Мережа" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "Не знайдено ключів SSH" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" msgstr "Користувач '{}' не має ключів на GitHub" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "Інформація про випуск відсутня." -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "ОФЛАЙН" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "OK" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "ОНЛАЙН" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "ВІДКРИТИ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "ПІДКЛЮЧИТИ" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "ПОКАЖИ" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "XАРАКТЕРИСТИКИ PRIME:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "Підключити пристрій" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "Підключити пристрій" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "Підключіть свій пристрій до обліковки comma connect" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "Підключіть свій пристрій до comma connect (connect.comma.ai) і отримайте свою пропозицію comma prime." -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "Будь ласка, підключіться до Wi-Fi, щоб завершити початкове сполучення." -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "Вимкнути" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "Запобігайте завантаженню великих обсягів даних під час використання Wi-Fi-з'єднання з обмеженим трафіком" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "Запобігати великим завантаженням даних під час лімітного стільникового з'єднання" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "Попередньо перегляньте камеру, спрямовану на водія, щоб переконатися, що система моніторингу водія має добру видимість. (автомобіль повинен бути вимкнений)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "Помилка QR-коду" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "ВИДАЛИТИ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "Скинути" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "ДИВИТИСЬ" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "Перезавантажити" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "Перезавантажте пристрій" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "Перезавантажити та оновити" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "Писати та вантажити відео з камери водія" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "Запис та завантаження аудіо з мікрофона" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "Записуйте та зберігайте аудіо з мікрофона під час руху. Аудіо буде включено до відео з відеореєстратора в comma connect." -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "Нормативні документи" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "Спокійний" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "Віддалений доступ" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "Віддалені знімки" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "Час запиту вичерпано" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "Скинути" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "Скинути калібрування" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "Переглянути посібник з навчання" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "Перегляньте правила, функції та обмеження openpilot" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" msgstr "ВИБРАТИ" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" msgstr "SSH ключі" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "Пошук мереж..." -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "Вибрати" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" msgstr "Виберіть гілку" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "Виберіть мову" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "Серійний номер" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "Відкласти оновлення" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" msgstr "Програма" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" msgstr "Стандарт" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "Система не реагує" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "КЕРМУЙТЕ НЕГАЙНО" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" msgstr "ТЕМП" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" msgstr "Цільова гілка" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "Пароль для точки доступу" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "Перемикачі" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "Режим налагодження інтерфейсу" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "ВИДАЛИТИ" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "ОНОВИТИ" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "Видалити" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "Невідомо" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "Оновлення завантажуються лише тоді, коли автомобіль вимкнено." -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "Оновити зараз" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "Завантажуйте дані з камери, спрямованої на водія, та допоможіть покращити алгоритм моніторингу водія." -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "Використовувати метричну систему" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "АВТО" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "ДИВИСЬ" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "Очікування початку" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "Ласкаво просимо до openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "Якщо увімкнено, натискання на педаль акселератора вимкне openpilot." -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi-Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Трафік Wi-Fi" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "Невірний пароль" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "Ви повинні прийняти Умови та положення, щоб користуватися openpilot." -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "Ви повинні прийняти Умови використання, щоб користуватися openpilot. Перед тим, як продовжити, ознайомтеся з останніми умовами на сайті https://comma.ai/terms." -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "запуск камери" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." msgstr "перевіряю..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" msgstr "замовч." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "вниз" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." msgstr "завантажую..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "не вдалося перевірити оновлення" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." msgstr "завершую..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "для \"{}\"" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "км/год" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "залиште порожнім для автоматичного налаштування" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "вліво" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "обмеж." -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "миль/год" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "ніколи" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "зараз" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "Поздовжнє керування openpilot (Альфа)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot Недоступний" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "Поздовжнє керування openpilot може з'явитися в майбутньому оновленні." -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "Для роботи openpilot потрібно, щоб пристрій був встановлений з нахилом не більше 4° вліво або вправо та не більше 5° вгору або 9° вниз. openpilot постійно калібрується, тому скидання калібрування потрібне рідко." -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "вправо" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "необмеж." -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "вгору" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "оновлено, ніколи не перевірялось" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "оновлено, перевірив {}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "доступне оновлення" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} СПОВІЩЕННЯ" msgstr[1] "{} СПОВІЩЕННЯ" msgstr[2] "{} СПОВІЩЕНЬ" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} день тому" msgstr[1] "{} дні тому" msgstr[2] "{} днів тому" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} година тому" msgstr[1] "{} години тому" msgstr[2] "{} годин тому" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} хвилина тому" msgstr[1] "{} хвилини тому" msgstr[2] "{} хвилин тому" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "{} сегмент вашого водіння на даний момент містяться в тренувальному наборі даних." msgstr[1] "{} сегменти вашого водіння на даний момент містяться в тренувальному наборі даних." msgstr[2] "{} сегментів вашого водіння на даний момент містяться в тренувальному наборі даних." -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ ПІДПИСАНО" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🌧️ Режим зливи 🌧️" diff --git a/selfdrive/ui/translations/app_zh-CHS.po b/selfdrive/ui/translations/app_zh-CHS.po index 4d9a5b78e27..55a7c329f63 100644 --- a/selfdrive/ui/translations/app_zh-CHS.po +++ b/selfdrive/ui/translations/app_zh-CHS.po @@ -1,1034 +1,825 @@ -# Language zh-CHS translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-22 16:32-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: zh-CHS\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: zh-CHS\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " 转向扭矩响应校准完成。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " 转向扭矩响应校准已完成 {}%。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的设备朝向 {:.1f}° {} 与 {:.1f}° {}。" -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "1 年行驶数据存储" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "全天候 LTE 连接" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "

转向延迟校准完成。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "

转向延迟校准已完成 {}%。" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "添加" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "APN 设置" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "确认过度作动" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" msgstr "高级" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "激进" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "同意" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "始终启用驾驶员监控" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "确定要关机吗?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "确定要重启吗?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "确定要重置校准吗?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "确定要卸载吗?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "返回" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成为 comma prime 会员" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "将 connect.comma.ai 添加到主屏幕,像应用一样使用" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "更改" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "检查" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "安稳模式已开启" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" -msgstr "CONNECT" +msgstr "连接" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "连接中..." -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "蜂窝计量" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "更改语言" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若车辆通电,更改此设置将重启 openpilot。" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "点击“添加新设备”,扫描右侧二维码" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "关闭" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "当前版本" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "下载" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "拒绝" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "拒绝并卸载 openpilot" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "开发者" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "设备" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板时脱离" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "脱离以关机" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "脱离以重启" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "脱离以重置校准" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "以 km/h 显示速度(非 mph)。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" -msgstr "Dongle ID" +msgstr "设备 ID" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "下载" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "车内摄像头" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "驾驶风格" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "编辑" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "错误" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "实验模式已开启" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "启用" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "启用 ADB" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "启用车道偏离警示" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" msgstr "启用漫游" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "启用 SSH" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" msgstr "启用网络共享" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未启用 openpilot 也启用驾驶员监控。" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "启用 openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "启用 openpilot 纵向控制(alpha)开关,以使用实验模式。" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "输入 APN" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "输入 SSID" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "输入新的网络共享密码" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "输入密码" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "输入您的 GitHub 用户名" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" msgstr "错误" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "实验模式" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此车型当前无法使用实验模式,因为纵向控制使用的是原厂 ACC。" -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "正在遗忘..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "完成设置" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" -msgstr "Firehose" +msgstr "数据洪流" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Firehose 模式" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" msgstr "忘记" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘记 Wi‑Fi 网络“{}”吗?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "良好" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "在手机上前往 https://connect.comma.ai" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" msgstr "隐藏网络" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "安装" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "IP 地址" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "安装更新" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "摇杆调试模式" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "加载中" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "纵向操作模式" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "最大" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上传训练数据,以改进 openpilot 的驾驶模型。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" msgstr "无" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "否" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "网络" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "未找到 SSH 密钥" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" msgstr "未找到用户“{}”的 SSH 密钥" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "暂无发行说明。" -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "离线" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "确定" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "在线" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "打开" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "配对" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "预览" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "配对设备" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "配对设备" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "将设备配对到您的 comma 账号" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "将设备与 comma connect(connect.comma.ai)配对,领取您的 comma prime 优惠。" -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "请连接 Wi‑Fi 以完成初始配对" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "关机" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在计量制 Wi‑Fi 连接时避免大量上传" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在计量制蜂窝网络时避免大量上传" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "预览车内摄像头以确保驾驶员监控视野良好。(车辆必须熄火)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "二维码错误" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "移除" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "重置" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "查看" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "重启" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "重启设备" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "重启并更新" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "录制并上传车内摄像头" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "录制并上传麦克风音频" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "行驶时录制并保存麦克风音频。音频将包含在 comma connect 的行车记录视频中。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "法规" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "从容" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "远程访问" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "远程快照" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "请求超时" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "重置" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "重置校准" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "查看训练指南" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "查看 openpilot 的规则、功能与限制" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" msgstr "选择" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" msgstr "SSH 密钥" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "正在扫描 Wi‑Fi 网络…" -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "选择" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" msgstr "选择分支" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "选择语言" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "序列号" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "延后更新" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" msgstr "软件" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" msgstr "标准" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "系统无响应" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "请立即接管控制" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" msgstr "温度" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" msgstr "目标分支" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "网络共享密码" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "切换" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "界面调试模式" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "卸载" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "更新" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "卸载" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "未知" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "仅在车辆熄火时下载更新。" -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "立即升级" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "上传车内摄像头数据,帮助改进驾驶员监控算法。" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "使用公制" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "车辆" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "查看" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "等待开始" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "欢迎使用 openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "启用后,踩下加速踏板将会脱离 openpilot。" -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 计量网络" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "密码错误" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必须接受条款与条件才能使用 openpilot。" -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "您必须接受条款与条件才能使用 openpilot。继续前请阅读 https://comma.ai/terms 上的最新条款。" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "相机启动中" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "检查中..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" msgstr "默认" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "下" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "下载中..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "检查更新失败" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "正在完成更新..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "用于“{}”" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "留空以自动配置" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "左" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "计量" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "从不" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "现在" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 纵向控制(Alpha)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot 无法使用" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 纵向控制可能会在未来更新中提供。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot 要求设备安装在左右 4°、上 5° 或下 9° 以内。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "右" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "不限流量" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "上" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "已是最新,最后检查:从未" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "已是最新,最后检查:{}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "有可用更新" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 条警报" msgstr[1] "{} 条警报" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小时前" msgstr[1] "{} 小时前" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分钟前" msgstr[1] "{} 分钟前" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" msgstr[1] "目前已有 {} 个您的驾驶片段被纳入训练数据集。" -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ 已订阅" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" diff --git a/selfdrive/ui/translations/app_zh-CHT.po b/selfdrive/ui/translations/app_zh-CHT.po index 1c2fd065633..93f9b9ed8e1 100644 --- a/selfdrive/ui/translations/app_zh-CHT.po +++ b/selfdrive/ui/translations/app_zh-CHT.po @@ -1,1034 +1,825 @@ -# Language zh-CHT translations for PACKAGE package. -# Copyright (C) 2025 THE PACKAGE'S COPYRIGHT HOLDER -# This file is distributed under the same license as the PACKAGE package. -# Automatically generated, 2025. -# msgid "" msgstr "" -"Project-Id-Version: PACKAGE VERSION\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-10-23 00:50-0700\n" -"PO-Revision-Date: 2025-10-22 16:32-0700\n" -"Last-Translator: Automatically generated\n" -"Language-Team: none\n" -"Language: zh-CHT\n" -"MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" +"Language: zh-CHT\n" "Plural-Forms: nplurals=1; plural=0;\n" -#: openpilot/selfdrive/ui/layouts/settings/device.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is complete." msgstr " 轉向扭矩回應校正完成。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:150 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Steering torque response calibration is {}% complete." msgstr " 轉向扭矩回應校正已完成 {}%。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid " Your device is pointed {:.1f}° {} and {:.1f}° {}." msgstr " 您的裝置朝向 {:.1f}° {} 與 {:.1f}° {}。" -#: openpilot/selfdrive/ui/layouts/sidebar.py:43 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "--" msgstr "--" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "1 year of drive storage" msgstr "1 年行駛資料儲存" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "24/7 LTE connectivity" msgstr "全年無休 LTE 連線" -#: openpilot/selfdrive/ui/layouts/sidebar.py:46 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "2G" msgstr "2G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:47 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "3G" msgstr "3G" -#: openpilot/selfdrive/ui/layouts/sidebar.py:49 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "5G" msgstr "5G" -#: openpilot/selfdrive/ui/layouts/settings/device.py:140 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is complete." msgstr "

轉向延遲校正完成。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "

Steering lag calibration is {}% complete." msgstr "

轉向延遲校正已完成 {}%。" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:30 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "ADD" msgstr "新增" -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "APN Setting" msgstr "APN 設定" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:109 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Acknowledge Excessive Actuation" msgstr "確認過度作動" -#: system/ui/widgets/network.py:92 -#: system/ui/widgets/network.py:74 -#, python-format +#: system/ui/widgets/network.py msgid "Advanced" msgstr "進階" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Aggressive" msgstr "積極" -#: openpilot/selfdrive/ui/layouts/onboarding.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Agree" msgstr "同意" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Always-On Driver Monitoring" msgstr "持續啟用駕駛監控" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to power off?" msgstr "確定要關機嗎?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reboot?" msgstr "確定要重新啟動嗎?" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Are you sure you want to reset calibration?" msgstr "確定要重設校正嗎?" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Are you sure you want to uninstall?" msgstr "確定要解除安裝嗎?" -#: system/ui/widgets/network.py:96 -#: openpilot/selfdrive/ui/layouts/onboarding.py:151 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py +#: system/ui/widgets/network.py msgid "Back" msgstr "返回" -#: openpilot/selfdrive/ui/widgets/prime.py:38 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Become a comma prime member at connect.comma.ai" msgstr "前往 connect.comma.ai 成為 comma prime 會員" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:119 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Bookmark connect.comma.ai to your home screen to use it like an app" msgstr "將 connect.comma.ai 加到主畫面,像 App 一樣使用" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "CHANGE" msgstr "變更" -#: openpilot/selfdrive/ui/layouts/settings/software.py:157 -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#: openpilot/selfdrive/ui/layouts/settings/software.py:117 -#: openpilot/selfdrive/ui/layouts/settings/software.py:128 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "CHECK" msgstr "檢查" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "CHILL MODE ON" msgstr "安穩模式已開啟" -#: system/ui/widgets/network.py:152 -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/network.py msgid "CONNECT" -msgstr "CONNECT" +msgstr "連線" -#: system/ui/widgets/network.py:376 -#, python-format +#: system/ui/widgets/network.py msgid "CONNECTING..." msgstr "連線中..." -#: system/ui/widgets/network.py:326 -#: system/ui/widgets/confirm_dialog.py:24 -#: system/ui/widgets/option_dialog.py:36 -#: system/ui/widgets/keyboard.py:83 -#, python-format +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/keyboard.py +#: system/ui/widgets/network.py +#: system/ui/widgets/option_dialog.py msgid "Cancel" msgstr "取消" -#: system/ui/widgets/network.py:131 -#, python-format +#: system/ui/widgets/network.py msgid "Cellular Metered" msgstr "行動網路計量" -#: openpilot/selfdrive/ui/layouts/settings/device.py:66 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Change Language" msgstr "變更語言" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Changing this setting will restart openpilot if the car is powered on." msgstr "若車輛通電,變更此設定將重新啟動 openpilot。" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:118 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Click \"add new device\" and scan the QR code on the right" msgstr "點選「新增裝置」,掃描右側 QR 碼" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:104 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Close" msgstr "關閉" -#: openpilot/selfdrive/ui/layouts/settings/software.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Current Version" msgstr "目前版本" -#: openpilot/selfdrive/ui/layouts/settings/software.py:120 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "DOWNLOAD" msgstr "下載" -#: openpilot/selfdrive/ui/layouts/onboarding.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline" msgstr "拒絕" -#: openpilot/selfdrive/ui/layouts/onboarding.py:152 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Decline, uninstall openpilot" msgstr "拒絕並解除安裝 openpilot" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:64 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Developer" msgstr "開發人員" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:59 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Device" msgstr "裝置" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Disengage on Accelerator Pedal" msgstr "踩下加速踏板時脫離" -#: openpilot/selfdrive/ui/layouts/settings/device.py:176 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Power Off" msgstr "脫離以關機" -#: openpilot/selfdrive/ui/layouts/settings/device.py:164 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reboot" msgstr "脫離以重新啟動" -#: openpilot/selfdrive/ui/layouts/settings/device.py:95 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Disengage to Reset Calibration" msgstr "脫離以重設校正" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:32 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Display speed in km/h instead of mph." msgstr "以 km/h 顯示速度(非 mph)。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Dongle ID" -msgstr "Dongle ID" +msgstr "裝置 ID" -#: openpilot/selfdrive/ui/layouts/settings/software.py:57 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Download" msgstr "下載" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Driver Camera" msgstr "車內鏡頭" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:96 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Driving Personality" msgstr "駕駛風格" -#: system/ui/widgets/network.py:120 -#: system/ui/widgets/network.py:136 -#, python-format +#: system/ui/widgets/network.py msgid "EDIT" msgstr "編輯" -#: openpilot/selfdrive/ui/layouts/sidebar.py:138 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ERROR" msgstr "錯誤" -#: openpilot/selfdrive/ui/layouts/sidebar.py:45 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ETH" msgstr "ETH" -#: openpilot/selfdrive/ui/widgets/exp_mode_button.py:51 -#, python-format +#: openpilot/selfdrive/ui/widgets/exp_mode_button.py msgid "EXPERIMENTAL MODE ON" msgstr "實驗模式已開啟" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:229 -#: openpilot/selfdrive/ui/layouts/settings/developer.py:180 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable" msgstr "啟用" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:39 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable ADB" msgstr "啟用 ADB" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable Lane Departure Warnings" msgstr "啟用偏離車道警示" -#: system/ui/widgets/network.py:126 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Roaming" msgstr "啟用漫遊" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:48 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Enable SSH" msgstr "啟用 SSH" -#: system/ui/widgets/network.py:117 -#, python-format +#: system/ui/widgets/network.py msgid "Enable Tethering" msgstr "啟用網路共享" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:30 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable driver monitoring even when openpilot is not engaged." msgstr "即使未啟動 openpilot 亦啟用駕駛監控。" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:46 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable openpilot" msgstr "啟用 openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:190 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Enable the openpilot longitudinal control (alpha) toggle to allow Experimental mode." msgstr "啟用 openpilot 縱向控制(alpha)切換,以使用實驗模式。" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "Enter APN" msgstr "輸入 APN" -#: system/ui/widgets/network.py:243 -#, python-format +#: system/ui/widgets/network.py msgid "Enter SSID" msgstr "輸入 SSID" -#: system/ui/widgets/network.py:257 -#, python-format +#: system/ui/widgets/network.py msgid "Enter new tethering password" msgstr "輸入新的網路共享密碼" -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Enter password" msgstr "輸入密碼" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:89 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Enter your GitHub username" msgstr "輸入您的 GitHub 使用者名稱" -#: system/ui/widgets/list_view.py:123 -#: system/ui/widgets/list_view.py:160 -#, python-format +#: system/ui/widgets/list_view.py msgid "Error" msgstr "錯誤" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:52 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental Mode" msgstr "實驗模式" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:182 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control." msgstr "此車款目前無法使用實驗模式,因為縱向控制使用的是原廠 ACC。" -#: system/ui/widgets/network.py:380 -#, python-format +#: system/ui/widgets/network.py msgid "FORGETTING..." msgstr "正在遺忘..." -#: openpilot/selfdrive/ui/widgets/setup.py:43 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Finish Setup" msgstr "完成設定" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:63 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Firehose" -msgstr "Firehose" +msgstr "資料洪流" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:10 +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "Firehose Mode" msgstr "Firehose 模式" -#: system/ui/widgets/network.py:458 -#: system/ui/widgets/network.py:326 -#, python-format +#: system/ui/widgets/network.py msgid "Forget" msgstr "忘記" -#: system/ui/widgets/network.py:327 -#, python-format +#: system/ui/widgets/network.py msgid "Forget Wi-Fi Network \"{}\"?" msgstr "要忘記 Wi‑Fi 網路「{}」嗎?" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "GOOD" msgstr "良好" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:117 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Go to https://connect.comma.ai on your phone" msgstr "在手機上前往 https://connect.comma.ai" -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "HIGH" msgstr "高" -#: system/ui/widgets/network.py:152 -#, python-format +#: system/ui/widgets/network.py msgid "Hidden Network" msgstr "隱藏網路" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#: openpilot/selfdrive/ui/layouts/settings/software.py:146 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "INSTALL" msgstr "安裝" -#: system/ui/widgets/network.py:147 -#, python-format +#: system/ui/widgets/network.py msgid "IP Address" msgstr "IP 位址" -#: openpilot/selfdrive/ui/layouts/settings/software.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Install Update" msgstr "安裝更新" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:56 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Joystick Debug Mode" msgstr "搖桿除錯模式" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:29 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "LOADING" msgstr "載入中" -#: openpilot/selfdrive/ui/layouts/sidebar.py:48 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "LTE" msgstr "LTE" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:64 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "Longitudinal Maneuver Mode" msgstr "縱向操作模式" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:148 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "MAX" msgstr "最大" -#: openpilot/selfdrive/ui/widgets/setup.py:74 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Maximize your training data uploads to improve openpilot's driving models." msgstr "最大化上傳訓練資料,以改進 openpilot 的駕駛模型。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:57 -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "N/A" msgstr "無" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "NO" msgstr "否" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:60 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Network" msgstr "網路" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:115 -#, python-format -msgid "No SSH keys found" -msgstr "找不到 SSH 金鑰" - -#: openpilot/selfdrive/ui/widgets/ssh_key.py:127 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "No SSH keys found for user '{}'" msgstr "找不到使用者 '{}' 的 SSH 金鑰" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:321 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "No release notes available." msgstr "無可用發行說明。" -#: openpilot/selfdrive/ui/layouts/sidebar.py:73 -#: openpilot/selfdrive/ui/layouts/sidebar.py:134 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "OFFLINE" msgstr "離線" -#: system/ui/widgets/confirm_dialog.py:93 -#: system/ui/widgets/html_render.py:263 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/sidebar.py +#: system/ui/widgets/confirm_dialog.py +#: system/ui/widgets/html_render.py msgid "OK" msgstr "確定" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 -#: openpilot/selfdrive/ui/layouts/sidebar.py:136 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "ONLINE" msgstr "線上" -#: openpilot/selfdrive/ui/widgets/setup.py:19 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Open" msgstr "開啟" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PAIR" msgstr "配對" -#: openpilot/selfdrive/ui/layouts/sidebar.py:142 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "PANDA" msgstr "PANDA" -#: openpilot/selfdrive/ui/layouts/settings/device.py:60 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "PREVIEW" msgstr "預覽" -#: openpilot/selfdrive/ui/widgets/prime.py:44 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "PRIME FEATURES:" msgstr "PRIME 功能:" -#: openpilot/selfdrive/ui/layouts/settings/device.py:45 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Pair Device" msgstr "配對裝置" -#: openpilot/selfdrive/ui/widgets/setup.py:18 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair device" msgstr "配對裝置" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:92 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "Pair your device to your comma account" msgstr "將裝置配對至您的 comma 帳號" -#: openpilot/selfdrive/ui/widgets/setup.py:47 -#: openpilot/selfdrive/ui/layouts/settings/device.py:23 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer." msgstr "將裝置與 comma connect(connect.comma.ai)配對,領取您的 comma prime 優惠。" -#: openpilot/selfdrive/ui/widgets/setup.py:91 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "Please connect to Wi-Fi to complete initial pairing" msgstr "請連線至 Wi‑Fi 以完成初始化配對" -#: openpilot/selfdrive/ui/layouts/settings/device.py:183 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Power Off" msgstr "關機" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered Wi-Fi connection" msgstr "在計量制 Wi‑Fi 連線時避免大量上傳" -#: system/ui/widgets/network.py:132 -#, python-format +#: system/ui/widgets/network.py msgid "Prevent large data uploads when on a metered cellular connection" msgstr "在計量制行動網路時避免大量上傳" -#: openpilot/selfdrive/ui/layouts/settings/device.py:24 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)" msgstr "預覽車內鏡頭以確保駕駛監控視野良好。(車輛須熄火)" -#: openpilot/selfdrive/ui/widgets/pairing_dialog.py:150 -#, python-format +#: openpilot/selfdrive/ui/widgets/pairing_dialog.py msgid "QR Code Error" msgstr "QR 碼錯誤" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:31 +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "REMOVE" msgstr "移除" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "RESET" msgstr "重設" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "REVIEW" msgstr "檢視" -#: openpilot/selfdrive/ui/layouts/settings/device.py:171 -#: openpilot/selfdrive/ui/layouts/settings/device.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reboot" msgstr "重新啟動" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:66 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Reboot Device" msgstr "重新啟動裝置" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:112 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Reboot and Update" msgstr "重新啟動並更新" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:76 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Driver Camera" msgstr "錄製並上傳車內鏡頭" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:82 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and Upload Microphone Audio" msgstr "錄製並上傳麥克風音訊" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:33 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect." msgstr "行車時錄製並儲存麥克風音訊。音訊將包含在 comma connect 的行車紀錄影片中。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Regulatory" msgstr "法規" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Relaxed" msgstr "從容" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote access" msgstr "遠端存取" -#: openpilot/selfdrive/ui/widgets/prime.py:47 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Remote snapshots" msgstr "遠端擷圖" -#: openpilot/selfdrive/ui/widgets/ssh_key.py:124 -#, python-format +#: openpilot/selfdrive/ui/widgets/ssh_key.py msgid "Request timed out" msgstr "要求逾時" -#: openpilot/selfdrive/ui/layouts/settings/device.py:111 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset" msgstr "重設" -#: openpilot/selfdrive/ui/layouts/settings/device.py:49 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Reset Calibration" msgstr "重設校正" -#: openpilot/selfdrive/ui/layouts/settings/device.py:63 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review Training Guide" msgstr "檢視訓練指南" -#: openpilot/selfdrive/ui/layouts/settings/device.py:26 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Review the rules, features, and limitations of openpilot" msgstr "檢視 openpilot 的規則、功能與限制" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "SELECT" msgstr "選取" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:53 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "SSH Keys" msgstr "SSH 金鑰" -#: system/ui/widgets/network.py:316 -#, python-format +#: system/ui/widgets/network.py msgid "Scanning Wi-Fi networks..." msgstr "正在掃描 Wi‑Fi 網路…" -#: system/ui/widgets/option_dialog.py:37 -#, python-format +#: system/ui/widgets/option_dialog.py msgid "Select" msgstr "選取" -#: openpilot/selfdrive/ui/layouts/settings/software.py:203 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Select a branch" msgstr "選取分支" -#: openpilot/selfdrive/ui/layouts/settings/device.py:89 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Select a language" msgstr "選取語言" -#: openpilot/selfdrive/ui/layouts/settings/device.py:58 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "Serial" msgstr "序號" -#: openpilot/selfdrive/ui/widgets/offroad_alerts.py:106 -#, python-format +#: openpilot/selfdrive/ui/widgets/offroad_alerts.py msgid "Snooze Update" msgstr "延後更新" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:62 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Software" msgstr "軟體" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:98 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Standard" msgstr "標準" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:59 -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:65 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "System Unresponsive" msgstr "系統無回應" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:58 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "TAKE CONTROL IMMEDIATELY" msgstr "請立刻接手控制" -#: openpilot/selfdrive/ui/layouts/sidebar.py:71 -#: openpilot/selfdrive/ui/layouts/sidebar.py:125 -#: openpilot/selfdrive/ui/layouts/sidebar.py:127 -#: openpilot/selfdrive/ui/layouts/sidebar.py:129 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "TEMP" msgstr "溫度" -#: openpilot/selfdrive/ui/layouts/settings/software.py:68 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Target Branch" msgstr "目標分支" -#: system/ui/widgets/network.py:121 -#, python-format +#: system/ui/widgets/network.py msgid "Tethering Password" msgstr "網路共享密碼" -#: openpilot/selfdrive/ui/layouts/settings/settings.py:61 +#: openpilot/selfdrive/ui/layouts/settings/settings.py msgid "Toggles" msgstr "切換" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "UI Debug Mode" -msgstr "" +msgstr "介面除錯模式" -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "UNINSTALL" msgstr "解除安裝" -#: openpilot/selfdrive/ui/layouts/home.py:155 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "UPDATE" msgstr "更新" -#: openpilot/selfdrive/ui/layouts/settings/software.py:173 -#: openpilot/selfdrive/ui/layouts/settings/software.py:79 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Uninstall" msgstr "解除安裝" -#: openpilot/selfdrive/ui/layouts/sidebar.py:117 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Unknown" msgstr "未知" -#: openpilot/selfdrive/ui/layouts/settings/software.py:55 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "Updates are only downloaded while the car is off." msgstr "僅在車輛熄火時下載更新。" -#: openpilot/selfdrive/ui/widgets/prime.py:33 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "Upgrade Now" msgstr "立即升級" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:31 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Upload data from the driver facing camera and help improve the driver monitoring algorithm." msgstr "上傳車內鏡頭資料,協助改善駕駛監控演算法。" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:88 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "Use Metric System" msgstr "使用公制" -#: openpilot/selfdrive/ui/layouts/sidebar.py:72 -#: openpilot/selfdrive/ui/layouts/sidebar.py:144 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "VEHICLE" msgstr "車輛" -#: openpilot/selfdrive/ui/layouts/settings/device.py:65 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "VIEW" msgstr "檢視" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:52 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "Waiting to start" msgstr "等待開始" -#: openpilot/selfdrive/ui/layouts/onboarding.py:115 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "Welcome to openpilot" msgstr "歡迎使用 openpilot" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:20 +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "When enabled, pressing the accelerator pedal will disengage openpilot." msgstr "啟用後,踩下加速踏板將會脫離 openpilot。" -#: openpilot/selfdrive/ui/layouts/sidebar.py:44 +#: openpilot/selfdrive/ui/layouts/sidebar.py msgid "Wi-Fi" msgstr "Wi‑Fi" -#: system/ui/widgets/network.py:141 -#, python-format +#: system/ui/widgets/network.py msgid "Wi-Fi Network Metered" msgstr "Wi‑Fi 計量網路" -#: system/ui/widgets/network.py:320 -#, python-format +#: system/ui/widgets/network.py msgid "Wrong password" msgstr "密碼錯誤" -#: openpilot/selfdrive/ui/layouts/onboarding.py:149 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions in order to use openpilot." msgstr "您必須接受條款與細則才能使用 openpilot。" -#: openpilot/selfdrive/ui/layouts/onboarding.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/onboarding.py msgid "You must accept the Terms and Conditions to use openpilot. Read the latest terms at https://comma.ai/terms before continuing." msgstr "您必須接受條款與細則才能使用 openpilot。繼續前請閱讀 https://comma.ai/terms 上的最新條款。" -#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py:38 -#, python-format +#: openpilot/selfdrive/ui/onroad/driver_camera_dialog.py msgid "camera starting" msgstr "相機啟動中" -#: openpilot/selfdrive/ui/layouts/settings/software.py:19 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "checking..." -msgstr "" +msgstr "檢查中..." -#: openpilot/selfdrive/ui/widgets/prime.py:63 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "comma prime" msgstr "comma prime" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "default" msgstr "預設" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "down" msgstr "下" -#: openpilot/selfdrive/ui/layouts/settings/software.py:20 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "downloading..." -msgstr "" +msgstr "下載中..." -#: openpilot/selfdrive/ui/layouts/settings/software.py:116 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "failed to check for update" msgstr "檢查更新失敗" -#: openpilot/selfdrive/ui/layouts/settings/software.py:21 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "finalizing update..." -msgstr "" +msgstr "正在完成更新..." -#: system/ui/widgets/network.py:238 -#: system/ui/widgets/network.py:321 -#, python-format +#: system/ui/widgets/network.py msgid "for \"{}\"" msgstr "適用於「{}」" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "km/h" msgstr "km/h" -#: system/ui/widgets/network.py:201 -#, python-format +#: system/ui/widgets/network.py msgid "leave blank for automatic configuration" msgstr "留空以自動設定" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "left" msgstr "左" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "metered" msgstr "計量" -#: openpilot/selfdrive/ui/onroad/hud_renderer.py:177 -#, python-format +#: openpilot/selfdrive/ui/onroad/hud_renderer.py msgid "mph" msgstr "mph" -#: openpilot/selfdrive/ui/layouts/settings/software.py:27 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "never" msgstr "從不" -#: openpilot/selfdrive/ui/layouts/settings/software.py:38 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "now" msgstr "現在" -#: openpilot/selfdrive/ui/layouts/settings/developer.py:71 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/developer.py msgid "openpilot Longitudinal Control (Alpha)" msgstr "openpilot 縱向控制(Alpha)" -#: openpilot/selfdrive/ui/onroad/alert_renderer.py:51 -#, python-format +#: openpilot/selfdrive/ui/onroad/alert_renderer.py msgid "openpilot Unavailable" msgstr "openpilot 無法使用" -#: openpilot/selfdrive/ui/layouts/settings/toggles.py:184 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/toggles.py msgid "openpilot longitudinal control may come in a future update." msgstr "openpilot 縱向控制可能於未來更新提供。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:25 +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down." msgstr "openpilot 要求裝置安裝在左右 4°、上 5° 或下 9° 以內。" -#: openpilot/selfdrive/ui/layouts/settings/device.py:126 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "right" msgstr "右" -#: system/ui/widgets/network.py:139 -#, python-format +#: system/ui/widgets/network.py msgid "unmetered" msgstr "不限流量" -#: openpilot/selfdrive/ui/layouts/settings/device.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/device.py msgid "up" msgstr "上" -#: openpilot/selfdrive/ui/layouts/settings/software.py:127 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked never" msgstr "已為最新,最後檢查:從未" -#: openpilot/selfdrive/ui/layouts/settings/software.py:125 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "up to date, last checked {}" msgstr "已為最新,最後檢查:{}" -#: openpilot/selfdrive/ui/layouts/settings/software.py:119 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "update available" msgstr "有可用更新" -#: openpilot/selfdrive/ui/layouts/home.py:169 -#, python-format +#: openpilot/selfdrive/ui/layouts/home.py msgid "{} ALERT" msgid_plural "{} ALERTS" msgstr[0] "{} 則警示" msgstr[1] "{} 則警示" -#: openpilot/selfdrive/ui/layouts/settings/software.py:47 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} day ago" msgid_plural "{} days ago" msgstr[0] "{} 天前" msgstr[1] "{} 天前" -#: openpilot/selfdrive/ui/layouts/settings/software.py:44 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} hour ago" msgid_plural "{} hours ago" msgstr[0] "{} 小時前" msgstr[1] "{} 小時前" -#: openpilot/selfdrive/ui/layouts/settings/software.py:41 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/software.py msgid "{} minute ago" msgid_plural "{} minutes ago" msgstr[0] "{} 分鐘前" msgstr[1] "{} 分鐘前" -#: openpilot/selfdrive/ui/layouts/settings/firehose.py:70 -#, python-format +#: openpilot/selfdrive/ui/layouts/settings/firehose.py msgid "{} segment of your driving is in the training dataset so far." msgid_plural "{} segments of your driving is in the training dataset so far." msgstr[0] "目前已有 {} 個您的駕駛片段納入訓練資料集。" msgstr[1] "目前已有 {} 個您的駕駛片段納入訓練資料集。" -#: openpilot/selfdrive/ui/widgets/prime.py:62 -#, python-format +#: openpilot/selfdrive/ui/widgets/prime.py msgid "✓ SUBSCRIBED" msgstr "✓ 已訂閱" -#: openpilot/selfdrive/ui/widgets/setup.py:21 -#, python-format +#: openpilot/selfdrive/ui/widgets/setup.py msgid "🔥 Firehose Mode 🔥" msgstr "🔥 Firehose 模式 🔥" diff --git a/selfdrive/ui/translations/auto_translate.py b/selfdrive/ui/translations/auto_translate.py deleted file mode 100755 index 9354790f94f..00000000000 --- a/selfdrive/ui/translations/auto_translate.py +++ /dev/null @@ -1,138 +0,0 @@ -#!/usr/bin/env python3 - -import argparse -import json -import os -import pathlib -import xml.etree.ElementTree as ET -from typing import cast - -import requests - -TRANSLATIONS_DIR = pathlib.Path(__file__).resolve().parent -TRANSLATIONS_LANGUAGES = TRANSLATIONS_DIR / "languages.json" - -OPENAI_MODEL = "gpt-4" -OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY") -OPENAI_PROMPT = "You are a professional translator from English to {language} (ISO 639 language code). " + \ - "The following sentence or word is in the GUI of a software called openpilot, translate it accordingly." - - -def get_language_files(languages: list[str] | None = None) -> dict[str, pathlib.Path]: - files = {} - - with open(TRANSLATIONS_LANGUAGES) as fp: - language_dict = json.load(fp) - - for filename in language_dict.values(): - path = TRANSLATIONS_DIR / f"{filename}.ts" - language = path.stem - - if languages is None or language in languages: - files[language] = path - - return files - - -def translate_phrase(text: str, language: str) -> str: - response = requests.post( - "https://api.openai.com/v1/chat/completions", - json={ - "model": OPENAI_MODEL, - "messages": [ - { - "role": "system", - "content": OPENAI_PROMPT.format(language=language), - }, - { - "role": "user", - "content": text, - }, - ], - "temperature": 0.8, - "max_tokens": 1024, - "top_p": 1, - }, - headers={ - "Authorization": f"Bearer {OPENAI_API_KEY}", - "Content-Type": "application/json", - }, - ) - - if 400 <= response.status_code < 600: - raise requests.HTTPError(f'Error {response.status_code}: {response.json()}', response=response) - - data = response.json() - - return cast(str, data["choices"][0]["message"]["content"]) - - -def translate_file(path: pathlib.Path, language: str, all_: bool) -> None: - tree = ET.parse(path) - - root = tree.getroot() - - for context in root.findall("./context"): - name = context.find("name") - if name is None: - raise ValueError("name not found") - - print(f"Context: {name.text}") - - for message in context.findall("./message"): - source = message.find("source") - translation = message.find("translation") - - if source is None or translation is None: - raise ValueError("source or translation not found") - - if not all_ and translation.attrib.get("type") != "unfinished": - continue - - llm_translation = translate_phrase(cast(str, source.text), language) - - print(f"Source: {source.text}\n" + - f"Current translation: {translation.text}\n" + - f"LLM translation: {llm_translation}") - - translation.text = llm_translation - - with path.open("w", encoding="utf-8") as fp: - fp.write('\n' + - '\n' + - ET.tostring(root, encoding="utf-8").decode()) - - -def main(): - arg_parser = argparse.ArgumentParser("Auto translate") - - group = arg_parser.add_mutually_exclusive_group(required=True) - group.add_argument("-a", "--all-files", action="store_true", help="Translate all files") - group.add_argument("-f", "--file", nargs="+", help="Translate the selected files. (Example: -f fr de)") - - arg_parser.add_argument("-t", "--all-translations", action="store_true", default=False, help="Translate all sections. (Default: only unfinished)") - - args = arg_parser.parse_args() - - if OPENAI_API_KEY is None: - print("OpenAI API key is missing. (Hint: use `export OPENAI_API_KEY=YOUR-KEY` before you run the script).\n" + - "If you don't have one go to: https://beta.openai.com/account/api-keys.") - exit(1) - - files = get_language_files(None if args.all_files else args.file) - - if args.file: - missing_files = set(args.file) - set(files) - if len(missing_files): - print(f"No language files found: {missing_files}") - exit(1) - - print(f"Translation mode: {'all' if args.all_translations else 'only unfinished'}. Files: {list(files)}") - - for lang, path in files.items(): - print(f"Translate {lang} ({path})") - translate_file(path, lang, args.all_translations) - - -if __name__ == "__main__": - main() diff --git a/selfdrive/ui/translations/auto_translate.sh b/selfdrive/ui/translations/auto_translate.sh new file mode 100755 index 00000000000..03a207ca3c4 --- /dev/null +++ b/selfdrive/ui/translations/auto_translate.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +set -euo pipefail + +DIR="$(cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd)" +ROOT="$DIR/../../../" + +cd $DIR +./update_translations.py + +command -v codex >/dev/null || { + echo "Install codex CLI to continue:" + echo "-> https://developers.openai.com/codex/cli" + echo + exit 1 +} + +codex exec --cd "$ROOT" -c 'model_reasoning_effort="low"' --dangerously-bypass-approvals-and-sandbox "$(cat < 2: - has_content = True - break - # End of entry - if stripped.startswith(('msgid', '#')) or not stripped: - break - - if not has_content: - unfinished_translations += 1 - - return (total_translations, unfinished_translations) - -if __name__ == "__main__": - with open(LANGUAGES_FILE) as f: - translation_files = json.load(f) - - badge_svg = [] - max_badge_width = 0 # keep track of max width to set parent element - for idx, (name, file) in enumerate(translation_files.items()): - po_file_path = os.path.join(str(TRANSLATIONS_DIR), f"app_{file}.po") - - total_translations, unfinished_translations = parse_po_file(po_file_path) - - percent_finished = int(100 - (unfinished_translations / total_translations * 100.)) if total_translations > 0 else 0 - color = f"rgb{(94, 188, 0) if percent_finished == 100 else (248, 255, 50) if percent_finished > 90 else (204, 55, 27)}" - - # Download badge - badge_label = f"LANGUAGE {name}" - badge_message = f"{percent_finished}% complete" - if unfinished_translations != 0: - badge_message += f" ({unfinished_translations} unfinished)" - - r = requests.get(f"{SHIELDS_URL}/{badge_label}-{badge_message}-{color}", timeout=10) - assert r.status_code == 200, "Error downloading badge" - content_svg = r.content.decode("utf-8") - - xml = ET.fromstring(content_svg) - assert "width" in xml.attrib - max_badge_width = max(max_badge_width, int(xml.attrib["width"])) - - # Make tag ids in each badge unique to combine them into one svg - for tag in ("r", "s"): - content_svg = content_svg.replace(f'id="{tag}"', f'id="{tag}{idx}"') - content_svg = content_svg.replace(f'"url(#{tag})"', f'"url(#{tag}{idx})"') - - badge_svg.extend([f'', content_svg, ""]) - - badge_svg.insert(0, '') - badge_svg.append("") - - with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f: - badge_f.write("\n".join(badge_svg)) diff --git a/selfdrive/ui/translations/potools.py b/selfdrive/ui/translations/potools.py index 7571cccdd64..ac4dafb9888 100644 --- a/selfdrive/ui/translations/potools.py +++ b/selfdrive/ui/translations/potools.py @@ -8,7 +8,6 @@ import os import re from dataclasses import dataclass, field -from datetime import UTC, datetime from pathlib import Path @@ -165,18 +164,18 @@ def write_po(path: str | Path, header: POEntry | None, entries: list[POEntry]) - if header: for c in header.comments: f.write(c + '\n') - if header.flags: - f.write('#, ' + ', '.join(header.flags) + '\n') f.write(f'msgid {_quote("")}\n') f.write(f'msgstr {_quote(header.msgstr)}\n\n') for entry in entries: for c in entry.comments: f.write(c + '\n') - for ref in entry.source_refs: + # Keep file-level context for translators, but drop line numbers to + # avoid churning PO diffs on unrelated code edits. + source_files = sorted({ref.rsplit(':', 1)[0] for ref in entry.source_refs}) + for ref in source_files: f.write(f'#: {ref}\n') - if entry.flags: - f.write('#, ' + ', '.join(entry.flags) + '\n') + # Runtime loading ignores gettext flags; omit them to reduce noise. f.write(f'msgid {_quote(entry.msgid)}\n') if entry.is_plural: f.write(f'msgid_plural {_quote(entry.msgid_plural)}\n') @@ -256,31 +255,24 @@ def extract_strings(files: list[str], basedir: str) -> list[POEntry]: # ──── POT generation ──── +def _build_pot_header() -> POEntry: + return POEntry( + msgstr='Content-Type: text/plain; charset=UTF-8\n', + ) + + +def _build_po_header(language: str) -> POEntry: + plural_forms = PLURAL_FORMS.get(language, 'nplurals=2; plural=(n != 1);') + return POEntry( + msgstr='Content-Type: text/plain; charset=UTF-8\n' + + f'Language: {language}\n' + + f'Plural-Forms: {plural_forms}\n', + ) + + def generate_pot(entries: list[POEntry], pot_path: str | Path) -> None: """Generate a .pot template file from extracted entries.""" - now = datetime.now(UTC).strftime('%Y-%m-%d %H:%M%z') - header = POEntry( - comments=[ - '# SOME DESCRIPTIVE TITLE.', - "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER", - '# This file is distributed under the same license as the PACKAGE package.', - '# FIRST AUTHOR , YEAR.', - '#', - ], - flags=['fuzzy'], - msgstr='Project-Id-Version: PACKAGE VERSION\n' + - 'Report-Msgid-Bugs-To: \n' + - f'POT-Creation-Date: {now}\n' + - 'PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n' + - 'Last-Translator: FULL NAME \n' + - 'Language-Team: LANGUAGE \n' + - 'Language: \n' + - 'MIME-Version: 1.0\n' + - 'Content-Type: text/plain; charset=UTF-8\n' + - 'Content-Transfer-Encoding: 8bit\n' + - 'Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n', - ) - write_po(pot_path, header, entries) + write_po(pot_path, _build_pot_header(), entries) # ──── PO init (replaces msginit) ──── @@ -305,43 +297,22 @@ def init_po(pot_path: str | Path, po_path: str | Path, language: str) -> None: """Create a new .po file from a .pot template (replaces msginit).""" _, entries = parse_po(pot_path) plural_forms = PLURAL_FORMS.get(language, 'nplurals=2; plural=(n != 1);') - now = datetime.now(UTC).strftime('%Y-%m-%d %H:%M%z') - - header = POEntry( - comments=[ - f'# {language} translations for PACKAGE package.', - "# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER", - '# This file is distributed under the same license as the PACKAGE package.', - '# Automatically generated.', - '#', - ], - msgstr='Project-Id-Version: PACKAGE VERSION\n' + - 'Report-Msgid-Bugs-To: \n' + - f'POT-Creation-Date: {now}\n' + - f'PO-Revision-Date: {now}\n' + - 'Last-Translator: Automatically generated\n' + - 'Language-Team: none\n' + - f'Language: {language}\n' + - 'MIME-Version: 1.0\n' + - 'Content-Type: text/plain; charset=UTF-8\n' + - 'Content-Transfer-Encoding: 8bit\n' + - f'Plural-Forms: {plural_forms}\n', - ) nplurals = int(re.search(r'nplurals=(\d+)', plural_forms).group(1)) for e in entries: if e.is_plural: e.msgstr_plural = dict.fromkeys(range(nplurals), '') - write_po(po_path, header, entries) + write_po(po_path, _build_po_header(language), entries) # ──── PO merge (replaces msgmerge) ──── def merge_po(po_path: str | Path, pot_path: str | Path) -> None: """Update a .po file with entries from a .pot template (replaces msgmerge --update).""" - po_header, po_entries = parse_po(po_path) + _, po_entries = parse_po(po_path) _, pot_entries = parse_po(pot_path) + language = Path(po_path).stem.removeprefix("app_") existing = {e.msgid: e for e in po_entries} merged = [] @@ -359,4 +330,4 @@ def merge_po(po_path: str | Path, pot_path: str | Path) -> None: merged.append(pot_e) merged.sort(key=lambda e: e.msgid) - write_po(po_path, po_header, merged) + write_po(po_path, _build_po_header(language), merged) diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/translations/update_translations.py similarity index 100% rename from selfdrive/ui/update_translations.py rename to selfdrive/ui/translations/update_translations.py From f4b8384332bd98f4bc8a445017ab938db83d4d04 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Mon, 23 Mar 2026 09:41:52 -0700 Subject: [PATCH 582/685] Process replay: add diff report (#37048) * rm upload * use ci-artifacts * sanitize * rm ref_commit * add ci * handle exept * bootstrap * always * fix * replay * keep ref_commit fork compatibility * remove upload-only * apply comments * safe diffs in master * Revert "safe diffs in master" This reverts commit 369fccac786a67799193e9152488813c6df20414. * continue on master diff * imports * copy formatting from car_diff * main * setup refs and cur * copy diff * copy formatting * comment * rm token * rm hash * continue on master diff * use ci-artifacts refs * add run card diff * checkout * shebang * card_diff.yml * rm ci-artifacts * apply ci-artifacts * call differ * rename * uv lock * tests * readme * checkout * add all configs * import base_url * rename yaml * integrate in test_processes * fix diff report * var names * extract to module * print report * add msg count to diff * traceback * diff format * typing * name step * allow NaN * replace join --- .github/workflows/diff_report.yaml | 45 +++++++++ .github/workflows/tests.yaml | 10 ++ selfdrive/test/process_replay/diff_report.py | 92 +++++++++++++++++++ .../test/process_replay/test_processes.py | 24 +++-- 4 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/diff_report.yaml create mode 100644 selfdrive/test/process_replay/diff_report.py diff --git a/.github/workflows/diff_report.yaml b/.github/workflows/diff_report.yaml new file mode 100644 index 00000000000..2ddb8509449 --- /dev/null +++ b/.github/workflows/diff_report.yaml @@ -0,0 +1,45 @@ +name: diff report + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + comment: + name: comment + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + pull-requests: write + actions: read + steps: + - name: Wait for process replay + id: wait + continue-on-error: true + uses: lewagon/wait-on-check-action@v1.3.4 + with: + ref: ${{ github.event.pull_request.head.sha }} + check-name: process replay + repo-token: ${{ secrets.GITHUB_TOKEN }} + allowed-conclusions: success,failure + wait-interval: 20 + - name: Download diff + if: steps.wait.outcome == 'success' + uses: dawidd6/action-download-artifact@v6 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + workflow: tests.yaml + workflow_conclusion: '' + pr: ${{ github.event.number }} + name: diff_report_${{ github.event.number }} + path: . + allow_forks: true + - name: Comment on PR + if: steps.wait.outcome == 'success' + uses: thollander/actions-comment-pull-request@v2 + with: + filePath: diff_report.txt + comment_tag: diff_report + pr_number: ${{ github.event.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index f4be0ad5a62..91a8e5c3244 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -139,12 +139,22 @@ jobs: id: print-diff if: always() run: cat selfdrive/test/process_replay/diff.txt + - name: Print diff report + if: always() + run: cat selfdrive/test/process_replay/diff_report.txt - uses: actions/upload-artifact@v6 if: always() continue-on-error: true with: name: process_replay_diff.txt path: selfdrive/test/process_replay/diff.txt + - name: Upload diff report + uses: actions/upload-artifact@v6 + if: always() && github.event_name == 'pull_request' + continue-on-error: true + with: + name: diff_report_${{ github.event.number }} + path: selfdrive/test/process_replay/diff_report.txt - name: Checkout ci-artifacts if: github.repository == 'commaai/openpilot' && github.ref == 'refs/heads/master' uses: actions/checkout@v4 diff --git a/selfdrive/test/process_replay/diff_report.py b/selfdrive/test/process_replay/diff_report.py new file mode 100644 index 00000000000..32f058f8ee9 --- /dev/null +++ b/selfdrive/test/process_replay/diff_report.py @@ -0,0 +1,92 @@ +import os +from collections import defaultdict + +from opendbc.car.tests.car_diff import format_diff, format_numeric_diffs +from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs +from openpilot.selfdrive.test.process_replay.process_replay import PROC_REPLAY_DIR + + +class MsgWrap: + """Adapter so to_dict() includes defaults""" + def __init__(self, msg): + self._msg = msg + def to_dict(self) -> dict: + return self._msg.to_dict(verbose=True) + + +def diff_process(cfg, ref_msgs, new_msgs) -> tuple | None: + ref = defaultdict(list) + new = defaultdict(list) + for m in ref_msgs: + if m.which() in cfg.subs: + ref[m.which()].append(m) + for m in new_msgs: + if m.which() in cfg.subs: + new[m.which()].append(m) + + diffs = [] + for sub in cfg.subs: + if len(ref[sub]) != len(new[sub]): + diffs.append((f"{sub} (message count)", 0, (len(ref[sub]), len(new[sub])), 0)) + for i, (r, n) in enumerate(zip(ref[sub], new[sub], strict=False)): + for d in compare_logs([r], [n], cfg.ignore, tolerance=cfg.tolerance): + if d[0] == "change": + a, b = d[2] + if a != a and b != b: + continue + diffs.append((d[1], i, d[2], r.logMonoTime)) + elif d[0] in ("add", "remove"): + for item in d[2]: + if item[1] != item[1]: + continue + diffs.append((f"{d[1]}.{item[0]}", i, (d[0], item[1]), r.logMonoTime)) + return (diffs, ref, new) if diffs else None + + +def diff_format(diffs, ref, new, field) -> list[str]: + if any(part.isdigit() for part in field.split(".")): + return format_numeric_diffs(diffs) + msg_type = field.split(".")[0] + ref_ts = [(m.logMonoTime, MsgWrap(m)) for m in ref.get(msg_type, [])] + new_wrapped = [MsgWrap(m) for m in new.get(msg_type, [])] + return format_diff(diffs, ref_ts, new_wrapped, field) + + +def diff_report(replay_diffs, segments) -> None: + seg_to_plat = {seg: plat for plat, seg in segments} + + with_diffs, errors, n_passed = [], [], 0 + for seg, proc, data in replay_diffs: + plat = seg_to_plat.get(seg, "UNKNOWN") + if data is None: + n_passed += 1 + elif isinstance(data, str): + errors.append((plat, seg, proc, data)) + else: + with_diffs.append((plat, seg, proc, data)) + + icon = "⚠️" if with_diffs else "✅" + lines = [ + "## Process replay diff report", + "Replays driving segments through this PR and compares the behavior to master.", + "Please review any changes carefully to ensure they are expected.\n", + f"{icon} {len(with_diffs)} changed, {n_passed} passed, {len(errors)} errors", + ] + + for plat, seg, proc, err in errors: + lines.append(f"\nERROR {plat} - {seg} [{proc}]: {err}") + + if with_diffs: + lines.append("
Show changes\n\n```") + for plat, seg, proc, (diffs, ref, new) in with_diffs: + lines.append(f"\n{plat} - {seg} [{proc}]") + by_field = defaultdict(list) + for d in diffs: + by_field[d[0]].append(d) + for field, fd in sorted(by_field.items()): + lines.append(f"\n {field} ({len(fd)} diffs)") + lines.extend(diff_format(fd, ref, new, field)) + lines.append("```\n
") + + with open(os.path.join(PROC_REPLAY_DIR, "diff_report.txt"), "w") as f: + f.write("\n".join(lines)) diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index fbe300a7c9a..bc0085534c1 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -3,6 +3,7 @@ import concurrent.futures import os import sys +import traceback from collections import defaultdict from tqdm import tqdm from typing import Any @@ -11,6 +12,7 @@ from openpilot.common.git import get_commit from openpilot.tools.lib.openpilotci import get_url from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_diff +from openpilot.selfdrive.test.process_replay.diff_report import diff_process, diff_report from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process, \ check_most_messages_valid from openpilot.tools.lib.filereader import FileReader @@ -72,11 +74,16 @@ def run_test_process(data): segment, cfg, args, cur_log_fn, ref_log_path, lr_dat = data + ref_log_msgs = list(LogReader(ref_log_path)) lr = LogReader.from_bytes(lr_dat) - res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) + res, log_msgs = test_process(cfg, lr, segment, ref_log_msgs, cur_log_fn, args.ignore_fields, args.ignore_msgs) # save logs so we can update refs save_log(cur_log_fn, log_msgs) - return (segment, cfg.proc_name, res) + try: + diff_data = diff_process(cfg, ref_log_msgs, log_msgs) + except Exception: + diff_data = traceback.format_exc() + return (segment, cfg.proc_name, res, diff_data) def get_log_data(segment): @@ -85,14 +92,12 @@ def get_log_data(segment): return (segment, f.read()) -def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None): +def test_process(cfg, lr, segment, ref_log_msgs, new_log_path, ignore_fields=None, ignore_msgs=None): if ignore_fields is None: ignore_fields = [] if ignore_msgs is None: ignore_msgs = [] - ref_log_msgs = list(LogReader(ref_log_path)) - try: log_msgs = replay_process(cfg, lr, disable_progress=True) except Exception as e: @@ -201,9 +206,11 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non log_paths[segment][cfg.proc_name]['new'] = cur_log_fn results: Any = defaultdict(dict) + diffs: list = [] p2 = pool.map(run_test_process, pool_args) - for (segment, proc, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)): + for (segment, proc, result, diff_data) in tqdm(p2, desc="Running Tests", total=len(pool_args)): results[segment][proc] = result + diffs.append((segment, proc, diff_data)) diff_short, diff_long, failed = format_diff(results, log_paths, ref_commit) if not args.update_refs: @@ -211,6 +218,11 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non f.write(diff_long) print(diff_short) + try: + diff_report(diffs, segments) + except Exception: + print(f"failed to generate diff report:\n{traceback.format_exc()}") + if failed: print("TEST FAILED") else: From d75b8f45406fb6d962a66493c20d6d1051aae885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Mon, 23 Mar 2026 13:25:31 -0700 Subject: [PATCH 583/685] process_replay: fix logMonoTime simulation (#37708) * Fix logMonoTime * Fix last drain * Remove import * Bring it back --- selfdrive/test/process_replay/process_replay.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 5e9b2e742c6..a74dfcbb436 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -145,6 +145,7 @@ def __init__(self, cfg: ProcessConfig): self.cfg = copy.deepcopy(cfg) self.process = copy.deepcopy(managed_processes[cfg.proc_name]) self.msg_queue: list[capnp._DynamicStructReader] = [] + self.last_input_log_mono_time: int = -1 self.cnt = 0 self.pm: messaging.PubMaster | None = None self.sockets: list[messaging.SubSocket] | None = None @@ -267,6 +268,7 @@ def get_output_msgs(self, start_time: int): ms = messaging.drain_sock(socket) for m in ms: m = m.as_builder() + assert start_time > 0, "start_time must be positive" m.logMonoTime = start_time + int(self.cfg.processing_time * 1e9) output_msgs.append(m.as_reader()) return output_msgs @@ -293,10 +295,11 @@ def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, FrameReader] trigger_empty_recv = any(m.which() == self.cfg.main_pub for m in self.msg_queue) # get output msgs from previous inputs - output_msgs = self.get_output_msgs(msg.logMonoTime) + output_msgs = self.get_output_msgs(self.last_input_log_mono_time) for m in self.msg_queue: self.pm.send(m.which(), m.as_builder()) + self.last_input_log_mono_time = max(self.last_input_log_mono_time, m.logMonoTime) # send frames if needed if self.vipc_server is not None and m.which() in self.cfg.vision_pubs: camera_state = getattr(m, m.which()) @@ -713,7 +716,7 @@ def _replay_multi_process( # flush last set of messages from each process for container in containers: - last_time = log_msgs[-1].logMonoTime if len(log_msgs) > 0 else int(time.monotonic() * 1e9) + last_time = container.last_input_log_mono_time if container.last_input_log_mono_time > 0 else int(time.monotonic() * 1e9) log_msgs.extend(container.get_output_msgs(last_time)) finally: for container in containers: From 0870e26fb6aef77e9fc19a6634e6cf4bef673feb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Mar 2026 19:57:35 -0700 Subject: [PATCH 584/685] fix debug fw query script --- selfdrive/debug/debug_fw_fingerprinting_offline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/debug/debug_fw_fingerprinting_offline.py b/selfdrive/debug/debug_fw_fingerprinting_offline.py index d841e91053d..d36b350bbcd 100755 --- a/selfdrive/debug/debug_fw_fingerprinting_offline.py +++ b/selfdrive/debug/debug_fw_fingerprinting_offline.py @@ -44,7 +44,7 @@ def main(route: str | None, addrs: list[int], rxoffset: int | None): parser = argparse.ArgumentParser(description='View back and forth ISO-TP communication between various ECUs given an address') parser.add_argument('route', nargs='?', help='Route name, live if not specified') parser.add_argument('--addrs', nargs='*', default=[], help='List of tx address to view (0x7e0 for engine)') - parser.add_argument('--rxoffset', default='') + parser.add_argument('--rxoffset', default='0x8') args = parser.parse_args() addrs = [int(addr, base=16) if addr.startswith('0x') else int(addr) for addr in args.addrs] From e5ebd455761d9b40243e4f466b6cff46d69d52d5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Mon, 23 Mar 2026 22:04:11 -0700 Subject: [PATCH 585/685] fw query: remove aux panda support (#37725) * rm num_pandas * bump to master --- opendbc_repo | 2 +- selfdrive/car/card.py | 3 +-- selfdrive/debug/car/fw_versions.py | 4 +--- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index e27af8c188c..e72e18e113b 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit e27af8c188cec1ad7ef6c39ad57f6338f8b02281 +Subproject commit e72e18e113b8649fdda3e78b85110586751f1a81 diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 12b83134710..b64210514a6 100755 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -90,7 +90,6 @@ def __init__(self, CI=None, RI=None) -> None: break alpha_long_allowed = self.params.get_bool("AlphaLongitudinalEnabled") - num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates) cached_params = None cached_params_raw = self.params.get("CarParamsCache") @@ -98,7 +97,7 @@ def __init__(self, CI=None, RI=None) -> None: with car.CarParams.from_bytes(cached_params_raw) as _cached_params: cached_params = _cached_params - self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, num_pandas, cached_params) + self.CI = get_car(*self.can_callbacks, obd_callback(self.params), alpha_long_allowed, is_release, cached_params) self.RI = interfaces[self.CI.CP.carFingerprint].RadarInterface(self.CI.CP) self.CP = self.CI.CP diff --git a/selfdrive/debug/car/fw_versions.py b/selfdrive/debug/car/fw_versions.py index 6ae10d2fb23..5fb65e6972d 100755 --- a/selfdrive/debug/car/fw_versions.py +++ b/selfdrive/debug/car/fw_versions.py @@ -45,8 +45,6 @@ extra[(Ecu.unknown, 0x750, i)] = [] extra = {"any": {"debug": extra}} - num_pandas = len(messaging.recv_one_retry(pandaStates_sock).pandaStates) - t = time.monotonic() print("Getting vin...") set_obd_multiplexing(True) @@ -56,7 +54,7 @@ print() t = time.monotonic() - fw_vers = get_fw_versions(*can_callbacks, set_obd_multiplexing, query_brand=args.brand, extra=extra, num_pandas=num_pandas, progress=True) + fw_vers = get_fw_versions(*can_callbacks, set_obd_multiplexing, query_brand=args.brand, extra=extra, progress=True) _, candidates = match_fw_to_car(fw_vers, vin) print() From 12f1be19ccff662ebaba6b0337e9cf43d34d54db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 24 Mar 2026 14:40:45 -0700 Subject: [PATCH 586/685] POP model (#37727) * f9f6da19-c248-460f-8e16-d47e9824bfb7/100 * 05a58a51-e0e3-4e9b-8e27-e644685f2c50/100 --- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 611ae9fe85f..7c71bc94714 100644 --- a/selfdrive/modeld/models/driving_policy.onnx +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:78477124cbf3ffe30fa951ebada8410b43c4242c6054584d656f1d329b067e15 -size 14060847 +oid sha256:853c6634746ff439a848349d00e4d5581cd941f13f7c1862c31b72a31cc24858 +size 14061595 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 6c9fc4c84d3..afd617667c6 100644 --- a/selfdrive/modeld/models/driving_vision.onnx +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ee29ee5bce84d1ce23e9ff381280de9b4e4d96d2934cd751740354884e112c66 -size 46877473 +oid sha256:940e9006a25f27f0b6e85da798e6a8fd1f6dd492dd7d0b9ff1a9436460f46129 +size 46887794 From e4813645fa7e36221640eb21dcd50128b5744fb3 Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Wed, 25 Mar 2026 20:02:51 -0500 Subject: [PATCH 587/685] remove any stale scons lock on device startup (#37734) remove any stale scons lock at device startup --- launch_chffrplus.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index d4689aae53a..5e7b4fa0db8 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -7,6 +7,7 @@ source "$DIR/launch_env.sh" function agnos_init { # TODO: move this to agnos sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta + rm -f /data/scons_cache/config.lock # set success flag for current boot slot sudo abctl --set_success From b706673e1cf3ed61a928e6eb68bf8d663904a5de Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 25 Mar 2026 19:49:38 -0700 Subject: [PATCH 588/685] jotpluggler: part one (#37730) --- SConstruct | 3 +- tools/jotpluggler/.gitignore | 9 + tools/jotpluggler/SConscript | 92 + tools/jotpluggler/app.cc | 1914 ++++++++++++++ tools/jotpluggler/app.h | 884 +++++++ tools/jotpluggler/browser.cc | 465 ++++ tools/jotpluggler/camera.cc | 54 + tools/jotpluggler/camera.h | 5 + tools/jotpluggler/common.cc | 179 ++ tools/jotpluggler/common.h | 63 + tools/jotpluggler/custom_series.cc | 750 ++++++ tools/jotpluggler/dbc.h | 400 +++ tools/jotpluggler/generated_dbcs/.gitignore | 2 + tools/jotpluggler/icons.cc | 24 + tools/jotpluggler/internal.h | 166 ++ tools/jotpluggler/layout.cc | 704 ++++++ tools/jotpluggler/layout_io.cc | 128 + tools/jotpluggler/layouts/.gitignore | 1 + tools/jotpluggler/layouts/CAN-bus-debug.json | 1 + tools/jotpluggler/layouts/camera-timings.json | 1 + .../jotpluggler/layouts/cameras-and-map.json | 1 + tools/jotpluggler/layouts/can-states.json | 1 + .../layouts/controls_mismatch_debug.json | 1 + tools/jotpluggler/layouts/gps.json | 1 + tools/jotpluggler/layouts/gps_vs_llk.json | 1 + .../jotpluggler/layouts/locationd_debug.json | 1 + tools/jotpluggler/layouts/longitudinal.json | 1 + .../jotpluggler/layouts/max-torque-debug.json | 1 + tools/jotpluggler/layouts/new-layout.json | 1 + .../jotpluggler/layouts/system_lag_debug.json | 1 + tools/jotpluggler/layouts/thermal_debug.json | 1 + .../layouts/torque-controller.json | 1 + tools/jotpluggler/layouts/tuning.json | 1 + tools/jotpluggler/layouts/ublox-debug.json | 1 + tools/jotpluggler/logs.cc | 419 ++++ tools/jotpluggler/main.cc | 126 + tools/jotpluggler/map.cc | 1328 ++++++++++ tools/jotpluggler/map.h | 61 + tools/jotpluggler/math_eval.py | 145 ++ tools/jotpluggler/plot.cc | 1027 ++++++++ tools/jotpluggler/render.cc | 173 ++ tools/jotpluggler/runtime.cc | 1280 ++++++++++ tools/jotpluggler/session.cc | 773 ++++++ tools/jotpluggler/sidebar.cc | 215 ++ tools/jotpluggler/sketch_layout.cc | 2202 +++++++++++++++++ tools/jotpluggler/stream.cc | 207 ++ tools/jotpluggler/util.cc | 59 + tools/jotpluggler/util.h | 103 + tools/replay/logreader.cc | 58 +- tools/replay/logreader.h | 25 +- tools/replay/py_downloader.cc | 9 +- 51 files changed, 14061 insertions(+), 8 deletions(-) create mode 100644 tools/jotpluggler/.gitignore create mode 100644 tools/jotpluggler/SConscript create mode 100644 tools/jotpluggler/app.cc create mode 100644 tools/jotpluggler/app.h create mode 100644 tools/jotpluggler/browser.cc create mode 100644 tools/jotpluggler/camera.cc create mode 100644 tools/jotpluggler/camera.h create mode 100644 tools/jotpluggler/common.cc create mode 100644 tools/jotpluggler/common.h create mode 100644 tools/jotpluggler/custom_series.cc create mode 100644 tools/jotpluggler/dbc.h create mode 100644 tools/jotpluggler/generated_dbcs/.gitignore create mode 100644 tools/jotpluggler/icons.cc create mode 100644 tools/jotpluggler/internal.h create mode 100644 tools/jotpluggler/layout.cc create mode 100644 tools/jotpluggler/layout_io.cc create mode 100644 tools/jotpluggler/layouts/.gitignore create mode 100644 tools/jotpluggler/layouts/CAN-bus-debug.json create mode 100644 tools/jotpluggler/layouts/camera-timings.json create mode 100644 tools/jotpluggler/layouts/cameras-and-map.json create mode 100644 tools/jotpluggler/layouts/can-states.json create mode 100644 tools/jotpluggler/layouts/controls_mismatch_debug.json create mode 100644 tools/jotpluggler/layouts/gps.json create mode 100644 tools/jotpluggler/layouts/gps_vs_llk.json create mode 100644 tools/jotpluggler/layouts/locationd_debug.json create mode 100644 tools/jotpluggler/layouts/longitudinal.json create mode 100644 tools/jotpluggler/layouts/max-torque-debug.json create mode 100644 tools/jotpluggler/layouts/new-layout.json create mode 100644 tools/jotpluggler/layouts/system_lag_debug.json create mode 100644 tools/jotpluggler/layouts/thermal_debug.json create mode 100644 tools/jotpluggler/layouts/torque-controller.json create mode 100644 tools/jotpluggler/layouts/tuning.json create mode 100644 tools/jotpluggler/layouts/ublox-debug.json create mode 100644 tools/jotpluggler/logs.cc create mode 100644 tools/jotpluggler/main.cc create mode 100644 tools/jotpluggler/map.cc create mode 100644 tools/jotpluggler/map.h create mode 100755 tools/jotpluggler/math_eval.py create mode 100644 tools/jotpluggler/plot.cc create mode 100644 tools/jotpluggler/render.cc create mode 100644 tools/jotpluggler/runtime.cc create mode 100644 tools/jotpluggler/session.cc create mode 100644 tools/jotpluggler/sidebar.cc create mode 100644 tools/jotpluggler/sketch_layout.cc create mode 100644 tools/jotpluggler/stream.cc create mode 100644 tools/jotpluggler/util.cc create mode 100644 tools/jotpluggler/util.h diff --git a/SConstruct b/SConstruct index 792a48eb7d3..119209dcdbb 100644 --- a/SConstruct +++ b/SConstruct @@ -49,7 +49,7 @@ pkgs = [importlib.import_module(name) for name in pkg_names] allowed_system_libs = { "EGL", "GLESv2", "GL", "Qt5Charts", "Qt5Core", "Qt5Gui", "Qt5Widgets", - "dl", "drm", "gbm", "m", "pthread", + "dl", "drm", "gbm", "m", "pthread", } def _resolve_lib(env, name): @@ -259,6 +259,7 @@ if arch != "larch64": SConscript([ 'tools/replay/SConscript', 'tools/cabana/SConscript', + 'tools/jotpluggler/SConscript', ]) diff --git a/tools/jotpluggler/.gitignore b/tools/jotpluggler/.gitignore new file mode 100644 index 00000000000..7cb98300fef --- /dev/null +++ b/tools/jotpluggler/.gitignore @@ -0,0 +1,9 @@ +__pycache__/ +jot_*.o +*.o +jotpluggler +car_fingerprint_to_dbc.h +generated_dbcs/.stamp +generated_dbcs/*.dbc +layouts/.jotpluggler_autosave/ +reports/ diff --git a/tools/jotpluggler/SConscript b/tools/jotpluggler/SConscript new file mode 100644 index 00000000000..122d502341c --- /dev/null +++ b/tools/jotpluggler/SConscript @@ -0,0 +1,92 @@ +import os +import imgui +import libusb +from opendbc import get_generated_dbcs +from opendbc.car import Bus +from opendbc.car.fingerprints import MIGRATION +from opendbc.car.values import PLATFORMS +from openpilot.common.basedir import BASEDIR + +Import('env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'replay_lib') + +jot_env = env.Clone() +jot_env["LIBPATH"] += [imgui.MESA_DIR, libusb.LIB_DIR] +jot_env["CPPPATH"] += [imgui.INCLUDE_DIR, libusb.INCLUDE_DIR] +jot_env["CXXFLAGS"] += [ + "-DGLFW_INCLUDE_NONE", + '-DJOTP_REPO_ROOT=\'"%s"\'' % os.path.realpath(BASEDIR), +] + +def materialize_generated_dbcs(target, source, env): + out_dir = os.path.dirname(str(target[0])) + os.makedirs(out_dir, exist_ok=True) + + for name in os.listdir(out_dir): + if name.endswith('.dbc'): + os.unlink(os.path.join(out_dir, name)) + + for name, content in sorted(get_generated_dbcs().items()): + with open(os.path.join(out_dir, f"{name}.dbc"), "w") as f: + f.write(content) + + with open(str(target[0]), "w") as f: + f.write("ok\n") + + return None + +def write_car_fingerprint_to_dbc_header(target, source, env): + pairs = {} + + for name, platform in sorted(PLATFORMS.items()): + dbc = platform.config.dbc_dict.get(Bus.pt, "") + if not dbc and name.startswith("TESLA_"): + dbc = platform.config.dbc_dict.get(Bus.party, "") + if not dbc and name == "COMMA_BODY": + dbc = "comma_body" + if dbc and name != "MOCK": + pairs[name] = dbc + + for fingerprint, car in sorted(MIGRATION.items()): + dbc = pairs.get(str(car), "") + if dbc: + pairs[fingerprint] = dbc + + lines = [ + "#pragma once", + "", + "#include ", + "#include ", + "", + "inline constexpr std::pair kCarFingerprintToDbc[] = {", + ] + lines.extend(f' {{"{fingerprint}", "{dbc}"}},' for fingerprint, dbc in sorted(pairs.items())) + lines.extend([ + "};", + "", + "inline std::string_view dbc_for_car_fingerprint(std::string_view fingerprint) {", + " for (const auto &[car_fingerprint, dbc] : kCarFingerprintToDbc) {", + " if (car_fingerprint == fingerprint) return dbc;", + " }", + " return {};", + "}", + "", + ]) + + with open(str(target[0]), "w") as f: + f.write("\n".join(lines)) + + return None + +generated_dbc_stamp = jot_env.Command(f"generated_dbcs/.stamp", [], materialize_generated_dbcs) +car_fingerprint_to_dbc = jot_env.Command("car_fingerprint_to_dbc.h", [], write_car_fingerprint_to_dbc_header) + +libs = [replay_lib, common, messaging, visionipc, cereal, File(f"{imgui.LIB_DIR}/libimgui.a"), File(f"{imgui.LIB_DIR}/libglfw3.a"), + "avformat", "avcodec", "avutil", "x264", "yuv", "z", "bz2", "zstd", "m", "pthread", "usb-1.0"] +if arch == "Darwin": + jot_env["FRAMEWORKS"] = ["OpenGL", "Cocoa", "IOKit", "CoreFoundation", "CoreVideo", "CoreMedia", "VideoToolbox"] +else: + libs += ["GL", "dl", "va", "va-drm", "drm"] + +program = jot_env.Program("jotpluggler", jot_env.Glob("*.cc"), LIBS=libs) +jot_env.Depends(program, generated_dbc_stamp) +jot_env.Depends(program, car_fingerprint_to_dbc) diff --git a/tools/jotpluggler/app.cc b/tools/jotpluggler/app.cc new file mode 100644 index 00000000000..4b56299ead3 --- /dev/null +++ b/tools/jotpluggler/app.cc @@ -0,0 +1,1914 @@ +#include "tools/jotpluggler/app.h" +#include "tools/jotpluggler/camera.h" +#include "tools/jotpluggler/common.h" +#include "tools/jotpluggler/internal.h" +#include "tools/jotpluggler/map.h" +#include "system/hardware/hw.h" +#include "imgui_impl_glfw.h" + +#include "imgui_internal.h" +#include "imgui_impl_opengl3.h" +#include "imgui_impl_opengl3_loader.h" +#include "implot.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "third_party/json11/json11.hpp" + +namespace fs = std::filesystem; + +constexpr const char *UNTITLED_PANE_TITLE = "..."; +ImFont *g_ui_font = nullptr; +ImFont *g_ui_bold_font = nullptr; +ImFont *g_mono_font = nullptr; + +std::string layout_name_from_arg(const std::string &layout_arg) { + const fs::path raw(layout_arg); + if (raw.extension() == ".xml" || raw.extension() == ".json") { + return raw.stem().string(); + } + if (raw.filename() != raw) { + return raw.filename().replace_extension("").string(); + } + fs::path stem_path = raw; + return stem_path.replace_extension("").string(); +} + +fs::path layouts_dir() { + return repo_root() / "tools" / "jotpluggler" / "layouts"; +} + +std::string sanitize_layout_stem(std::string_view name) { + std::string out; + out.reserve(name.size()); + bool last_was_dash = false; + for (const char raw : name) { + const unsigned char c = static_cast(raw); + if (std::isalnum(c) != 0) { + out.push_back(static_cast(std::tolower(c))); + last_was_dash = false; + } else if (raw == '-' || raw == '_') { + out.push_back(raw); + last_was_dash = false; + } else if (!last_was_dash && !out.empty()) { + out.push_back('-'); + last_was_dash = true; + } + } + while (!out.empty() && out.back() == '-') { + out.pop_back(); + } + return out.empty() ? "untitled" : out; +} + +fs::path autosave_dir() { + return layouts_dir() / ".jotpluggler_autosave"; +} + +fs::path resolve_layout_path(const std::string &layout_arg) { + const fs::path direct(layout_arg); + if (fs::exists(direct)) { + if (direct.extension() == ".json") return fs::absolute(direct); + const fs::path sibling_json = direct.parent_path() / (direct.stem().string() + ".json"); + if (direct.extension() == ".xml" && fs::exists(sibling_json)) { + return fs::absolute(sibling_json); + } + } + const fs::path candidate = layouts_dir() / (layout_name_from_arg(layout_arg) + ".json"); + if (!fs::exists(candidate)) throw std::runtime_error("Unknown layout: " + layout_arg); + return candidate; +} + +fs::path autosave_path_for_layout(const fs::path &layout_path) { + const std::string stem = layout_path.empty() ? "untitled" : layout_path.stem().string(); + return autosave_dir() / (sanitize_layout_stem(stem) + ".json"); +} + +std::vector available_layout_names() { + std::vector names; + const fs::path root = layouts_dir(); + if (!fs::exists(root) || !fs::is_directory(root)) { + return names; + } + for (const auto &entry : fs::directory_iterator(root)) { + if (!entry.is_regular_file() || entry.path().extension() != ".json") { + continue; + } + names.push_back(entry.path().stem().string()); + } + std::sort(names.begin(), names.end()); + return names; +} + +void refresh_replaced_layout_ui(AppSession *session, UiState *state, bool mark_docks) { + state->tabs.clear(); + cancel_rename_tab(state); + sync_ui_state(state, session->layout); + sync_layout_buffers(state, *session); + if (mark_docks) { + mark_all_docks_dirty(state); + } +} + +void start_new_layout(AppSession *session, UiState *state, const std::string &status_text) { + session->layout = make_empty_layout(); + session->layout_path.clear(); + session->autosave_path.clear(); + state->undo.reset(session->layout); + state->layout_dirty = false; + state->status_text = status_text; + refresh_replaced_layout_ui(session, state, true); + reset_shared_range(state, *session); +} + +bool is_decoded_can_series_path(std::string_view path) { + const std::string value(path); + return util::starts_with(value, "/can/") || util::starts_with(value, "/sendcan/"); +} + +bool apply_route_can_decode_update(AppSession *session, UiState *state); + +void rebuild_series_lookup_preserving_formats(AppSession *session, + std::string_view updated_prefix, + bool refresh_updated_formats_only) { + const std::string prefix(updated_prefix); + if (!updated_prefix.empty()) { + for (auto it = session->route_data.series_formats.begin(); it != session->route_data.series_formats.end();) { + if (util::starts_with(it->first, prefix)) { + it = session->route_data.series_formats.erase(it); + } else { + ++it; + } + } + } + session->series_by_path.clear(); + session->series_by_path.reserve(session->route_data.series.size()); + for (RouteSeries &series : session->route_data.series) { + session->series_by_path.emplace(series.path, &series); + if (refresh_updated_formats_only) { + if (!updated_prefix.empty() && util::starts_with(series.path, prefix)) { + const bool enum_like = session->route_data.enum_info.find(series.path) != session->route_data.enum_info.end(); + session->route_data.series_formats[series.path] = compute_series_format(series.values, enum_like); + } + } else { + const bool enum_like = session->route_data.enum_info.find(series.path) != session->route_data.enum_info.end(); + session->route_data.series_formats[series.path] = compute_series_format(series.values, enum_like); + } + } +} + +bool apply_route_can_decode_update(AppSession *session, UiState *state) { + const std::string active_dbc_name = !session->dbc_override.empty() ? session->dbc_override : session->route_data.dbc_name; + if (!active_dbc_name.empty() && !load_dbc_by_name(active_dbc_name).has_value()) { + state->error_text = "DBC not found: " + active_dbc_name; + state->open_error_popup = true; + return false; + } + std::unordered_map can_enum_info; + std::vector can_series = decode_can_messages(session->route_data.can_messages, active_dbc_name, &can_enum_info); + + std::vector updated_series; + updated_series.reserve(session->route_data.series.size() + can_series.size()); + for (RouteSeries &series : session->route_data.series) { + if (!is_decoded_can_series_path(series.path)) { + updated_series.push_back(std::move(series)); + } + } + for (RouteSeries &series : can_series) { + updated_series.push_back(std::move(series)); + } + std::sort(updated_series.begin(), updated_series.end(), [](const RouteSeries &a, const RouteSeries &b) { + return a.path < b.path; + }); + + std::unordered_map updated_enum_info; + updated_enum_info.reserve(session->route_data.enum_info.size() + can_enum_info.size()); + for (auto &[path, info] : session->route_data.enum_info) { + if (!is_decoded_can_series_path(path)) { + updated_enum_info.emplace(path, std::move(info)); + } + } + for (auto &[path, info] : can_enum_info) { + updated_enum_info[path] = std::move(info); + } + + session->route_data.series = std::move(updated_series); + session->route_data.enum_info = std::move(updated_enum_info); + session->route_data.paths.clear(); + session->route_data.paths.reserve(session->route_data.series.size()); + for (const RouteSeries &series : session->route_data.series) { + session->route_data.paths.push_back(series.path); + } + std::sort(session->route_data.paths.begin(), session->route_data.paths.end()); + session->route_data.roots = collect_route_roots_for_paths(session->route_data.paths); + + rebuild_route_index(session); + rebuild_browser_nodes(session, state); + refresh_all_custom_curves(session, state); + sync_camera_feeds(session); + return true; +} + +void apply_dbc_override_change(AppSession *session, UiState *state, const std::string &dbc_override) { + session->dbc_override = dbc_override; + if (session->data_mode == SessionDataMode::Stream) { + start_stream_session(session, state, session->stream_source, session->stream_buffer_seconds, false); + } else if (!session->route_name.empty()) { + const bool ok = apply_route_can_decode_update(session, state); + if (ok) { + state->status_text = dbc_override.empty() ? "DBC auto-detect enabled" : "DBC set to " + dbc_override; + } else { + state->status_text = "Failed to apply DBC"; + } + } else if (dbc_override.empty()) { + state->status_text = "DBC auto-detect enabled"; + } else { + state->status_text = "DBC set to " + dbc_override; + } +} + +void configure_style() { + ImGui::StyleColorsLight(); + ImPlot::StyleColorsLight(); + + ImGuiIO &io = ImGui::GetIO(); + g_ui_font = nullptr; + g_ui_bold_font = nullptr; + g_mono_font = nullptr; + const fs::path fonts_dir = repo_root() / "selfdrive" / "assets" / "fonts"; + ImFontConfig font_cfg; + font_cfg.OversampleH = 2; + font_cfg.OversampleV = 2; + font_cfg.RasterizerDensity = 1.0f; + icon_add_font(16.0f); + const auto add_font_with_icons = [&](const fs::path &path, float size) -> ImFont * { + ImFont *font = io.Fonts->AddFontFromFileTTF(path.c_str(), size, &font_cfg); + if (font != nullptr) { + icon_add_font(size, true, font); + } + return font; + }; + if (ImFont *font = add_font_with_icons(fonts_dir / "Inter-Regular.ttf", 16.0f); font != nullptr) { + g_ui_font = font; + io.FontDefault = font; + } + g_ui_bold_font = add_font_with_icons(fonts_dir / "Inter-SemiBold.ttf", 16.75f); + if (g_ui_font == nullptr) { + if (ImFont *font = add_font_with_icons(fonts_dir / "JetBrainsMono-Medium.ttf", 15.75f); font != nullptr) { + g_mono_font = font; + io.FontDefault = font; + } + } + if (g_mono_font == nullptr) { + g_mono_font = add_font_with_icons(fonts_dir / "JetBrainsMono-Medium.ttf", 15.75f); + } + if (g_ui_bold_font == nullptr) { + g_ui_bold_font = g_ui_font; + } + + ImGuiStyle &style = ImGui::GetStyle(); + style.WindowRounding = 0.0f; + style.ChildRounding = 0.0f; + style.PopupRounding = 0.0f; + style.FrameRounding = 2.0f; + style.ScrollbarRounding = 2.0f; + style.GrabRounding = 2.0f; + style.TabRounding = 0.0f; + style.WindowBorderSize = 1.0f; + style.ChildBorderSize = 1.0f; + style.FrameBorderSize = 1.0f; + style.WindowPadding = ImVec2(8.0f, 7.0f); + style.FramePadding = ImVec2(6.0f, 3.0f); + style.ItemSpacing = ImVec2(8.0f, 5.0f); + style.ItemInnerSpacing = ImVec2(6.0f, 3.0f); + struct ColorDef { ImGuiCol idx; int r, g, b; }; + constexpr ColorDef COLORS[] = { + {ImGuiCol_WindowBg, 250, 250, 251}, {ImGuiCol_ChildBg, 255, 255, 255}, + {ImGuiCol_Border, 194, 198, 204}, {ImGuiCol_TitleBg, 252, 252, 253}, + {ImGuiCol_TitleBgActive, 252, 252, 253}, {ImGuiCol_TitleBgCollapsed, 252, 252, 253}, + {ImGuiCol_Text, 74, 80, 88}, {ImGuiCol_TextDisabled, 108, 118, 128}, + {ImGuiCol_Button, 255, 255, 255}, {ImGuiCol_ButtonHovered, 246, 248, 250}, + {ImGuiCol_ButtonActive, 238, 240, 244}, {ImGuiCol_FrameBg, 255, 255, 255}, + {ImGuiCol_FrameBgHovered, 248, 249, 251}, {ImGuiCol_FrameBgActive, 241, 244, 248}, + {ImGuiCol_Header, 243, 245, 248}, {ImGuiCol_HeaderHovered, 237, 240, 244}, + {ImGuiCol_HeaderActive, 232, 236, 240}, {ImGuiCol_PopupBg, 248, 249, 251}, + {ImGuiCol_MenuBarBg, 232, 236, 241}, {ImGuiCol_Separator, 194, 198, 204}, + {ImGuiCol_ScrollbarBg, 240, 242, 245}, {ImGuiCol_ScrollbarGrab, 202, 207, 214}, + {ImGuiCol_ScrollbarGrabHovered, 180, 186, 194}, {ImGuiCol_ScrollbarGrabActive, 164, 171, 180}, + {ImGuiCol_Tab, 219, 224, 230}, {ImGuiCol_TabHovered, 232, 236, 241}, + {ImGuiCol_TabSelected, 250, 251, 253}, {ImGuiCol_TabSelectedOverline, 92, 109, 136}, + {ImGuiCol_TabDimmed, 213, 219, 226}, {ImGuiCol_TabDimmedSelected, 244, 247, 249}, + {ImGuiCol_TabDimmedSelectedOverline, 92, 109, 136}, {ImGuiCol_DockingEmptyBg, 244, 246, 248}, + }; + for (const auto &c : COLORS) { style.Colors[c.idx] = color_rgb(c.r, c.g, c.b); } + style.Colors[ImGuiCol_DockingPreview] = color_rgb(69, 115, 184, 0.22f); + + ImPlotStyle &plot_style = ImPlot::GetStyle(); + plot_style.PlotBorderSize = 1.0f; + plot_style.MinorAlpha = 0.65f; + plot_style.LegendPadding = ImVec2(6.0f, 5.0f); + plot_style.LegendInnerPadding = ImVec2(6.0f, 3.0f); + plot_style.LegendSpacing = ImVec2(7.0f, 2.0f); + plot_style.PlotPadding = ImVec2(4.0f, 8.0f); + plot_style.FitPadding = ImVec2(0.02f, 0.4f); + + ImPlot::MapInputDefault(); + ImPlotInputMap &input_map = ImPlot::GetInputMap(); + input_map.Pan = ImGuiMouseButton_Right; + input_map.PanMod = ImGuiMod_None; + input_map.Select = ImGuiMouseButton_Left; + input_map.SelectCancel = ImGuiMouseButton_Right; + input_map.SelectMod = ImGuiMod_None; +} + +void app_push_mono_font() { + if (g_mono_font != nullptr) { + ImGui::PushFont(g_mono_font); + } +} + +void app_pop_mono_font() { + if (g_mono_font != nullptr) { + ImGui::PopFont(); + } +} + +void app_push_bold_font() { + if (g_ui_bold_font != nullptr) { + ImGui::PushFont(g_ui_bold_font); + } +} + +void app_pop_bold_font() { + if (g_ui_bold_font != nullptr) { + ImGui::PopFont(); + } +} + +UiMetrics compute_ui_metrics(const ImVec2 &size, float top_offset, float sidebar_width) { + UiMetrics ui; + ui.width = size.x; + ui.height = size.y; + ui.top_offset = top_offset; + ui.sidebar_width = sidebar_width <= 0.0f + ? 0.0f + : std::clamp(sidebar_width, SIDEBAR_MIN_WIDTH, std::min(SIDEBAR_MAX_WIDTH, size.x * 0.6f)); + ui.content_x = ui.sidebar_width; + ui.content_y = top_offset; + ui.content_w = std::max(1.0f, size.x - ui.content_x); + ui.content_h = std::max(1.0f, size.y - ui.content_y - STATUS_BAR_HEIGHT); + ui.status_bar_y = std::max(0.0f, size.y - STATUS_BAR_HEIGHT); + return ui; +} + +void sync_ui_state(UiState *state, const SketchLayout &layout) { + const bool initializing = state->tabs.empty(); + state->tabs.resize(layout.tabs.size()); + if (layout.tabs.empty()) { + state->active_tab_index = 0; + state->requested_tab_index = -1; + return; + } + if (initializing) { + state->active_tab_index = std::clamp(layout.current_tab_index, 0, static_cast(layout.tabs.size()) - 1); + state->requested_tab_index = state->active_tab_index; + } + state->active_tab_index = std::clamp(state->active_tab_index, 0, static_cast(layout.tabs.size()) - 1); + for (size_t i = 0; i < layout.tabs.size(); ++i) { + if (state->tabs[i].runtime_id == 0) { + state->tabs[i].runtime_id = state->next_tab_runtime_id++; + } + const int pane_count = static_cast(layout.tabs[i].panes.size()); + state->tabs[i].map_panes.resize(static_cast(std::max(0, pane_count))); + state->tabs[i].camera_panes.resize(static_cast(std::max(0, pane_count))); + state->tabs[i].active_pane_index = pane_count <= 0 + ? 0 + : std::clamp(state->tabs[i].active_pane_index, 0, pane_count - 1); + } +} + +void resize_tab_pane_state(TabUiState *tab_state, size_t pane_count) { + if (tab_state == nullptr) return; + tab_state->map_panes.resize(pane_count); + tab_state->camera_panes.resize(pane_count); +} + +void sync_route_buffers(UiState *state, const AppSession &session) { + state->route_buffer = session.route_name; + state->data_dir_buffer = session.data_dir; +} + +void sync_stream_buffers(UiState *state, const AppSession &session) { + state->stream_address_buffer = session.stream_source.address; + state->stream_source_kind = session.stream_source.kind; + state->stream_buffer_seconds = session.stream_buffer_seconds; +} + +fs::path default_layout_save_path(const AppSession &session) { + return session.layout_path.empty() ? layouts_dir() / "new-layout.json" : session.layout_path; +} + +void sync_layout_buffers(UiState *state, const AppSession &session) { + state->load_layout_buffer = session.layout_path.empty() ? std::string() : session.layout_path.string(); + state->save_layout_buffer = default_layout_save_path(session).string(); +} + +const WorkspaceTab *app_active_tab(const SketchLayout &layout, const UiState &state) { + if (layout.tabs.empty()) return nullptr; + const int index = std::clamp(state.active_tab_index, 0, static_cast(layout.tabs.size()) - 1); + return &layout.tabs[static_cast(index)]; +} + +WorkspaceTab *app_active_tab(SketchLayout *layout, const UiState &state) { + if (layout->tabs.empty()) return nullptr; + const int index = std::clamp(state.active_tab_index, 0, static_cast(layout->tabs.size()) - 1); + return &layout->tabs[static_cast(index)]; +} + +TabUiState *app_active_tab_state(UiState *state) { + if (state->tabs.empty()) return nullptr; + const int index = std::clamp(state->active_tab_index, 0, static_cast(state->tabs.size()) - 1); + return &state->tabs[static_cast(index)]; +} + +std::string pane_window_name(int tab_runtime_id, int pane_index, const Pane &pane) { + const char *title = pane.title.empty() ? UNTITLED_PANE_TITLE : pane.title.c_str(); + return util::string_format("%s###tab%d_pane%d", title, tab_runtime_id, pane_index); +} + +std::string tab_item_label(const WorkspaceTab &tab, int tab_runtime_id) { + return util::string_format("%s##workspace_tab_%d", tab.tab_name.c_str(), tab_runtime_id); +} + +void request_tab_selection(UiState *state, int tab_index) { + state->active_tab_index = tab_index; + state->requested_tab_index = tab_index; +} + +void begin_rename_tab(const SketchLayout &layout, UiState *state, int tab_index) { + if (tab_index < 0 || tab_index >= static_cast(layout.tabs.size())) { + return; + } + state->rename_tab_buffer = layout.tabs[static_cast(tab_index)].tab_name; + state->rename_tab_index = tab_index; + state->focus_rename_tab_input = true; + request_tab_selection(state, tab_index); +} + +void cancel_rename_tab(UiState *state) { + state->rename_tab_index = -1; + state->focus_rename_tab_input = false; +} + +ImGuiID dockspace_id_for_tab(int tab_runtime_id) { + return ImHashStr(util::string_format("jotpluggler_dockspace_%d", tab_runtime_id).c_str()); +} + +bool curve_has_local_samples(const Curve &curve) { + return curve.xs.size() > 1 && curve.xs.size() == curve.ys.size(); +} + +void mark_all_docks_dirty(UiState *state) { + for (TabUiState &tab_state : state->tabs) { + tab_state.dock_needs_build = true; + } +} + +void mark_tab_dock_dirty(UiState *state, int tab_index) { + if (tab_index >= 0 && tab_index < static_cast(state->tabs.size())) { + state->tabs[static_cast(tab_index)].dock_needs_build = true; + } +} + +void normalize_split_node(WorkspaceNode *node) { + if (node->is_pane) { + return; + } + for (WorkspaceNode &child : node->children) { + normalize_split_node(&child); + } + if (node->children.empty()) { + return; + } + if (node->children.size() == 1) { + *node = node->children.front(); + return; + } + if (node->sizes.size() != node->children.size()) { + node->sizes.assign(node->children.size(), 1.0f / static_cast(node->children.size())); + return; + } + float total = 0.0f; + for (float &size : node->sizes) { + size = std::max(size, 0.0f); + total += size; + } + if (total <= 0.0f) { + node->sizes.assign(node->children.size(), 1.0f / static_cast(node->children.size())); + return; + } + for (float &size : node->sizes) { + size /= total; + } +} + +void decrement_pane_indices(WorkspaceNode *node, int removed_index) { + if (node->is_pane) { + if (node->pane_index > removed_index) { + node->pane_index -= 1; + } + return; + } + for (WorkspaceNode &child : node->children) { + decrement_pane_indices(&child, removed_index); + } +} + +bool remove_pane_node(WorkspaceNode *node, int pane_index) { + if (node->is_pane) return node->pane_index == pane_index; + + for (size_t i = 0; i < node->children.size();) { + if (remove_pane_node(&node->children[i], pane_index)) { + node->children.erase(node->children.begin() + static_cast(i)); + if (i < node->sizes.size()) { + node->sizes.erase(node->sizes.begin() + static_cast(i)); + } + } else { + ++i; + } + } + + normalize_split_node(node); + return !node->is_pane && node->children.empty(); +} + +bool split_pane_node(WorkspaceNode *node, int target_pane_index, SplitOrientation orientation, + bool new_before, int new_pane_index) { + if (node->is_pane) { + if (node->pane_index != target_pane_index) return false; + WorkspaceNode existing_pane; + existing_pane.is_pane = true; + existing_pane.pane_index = target_pane_index; + + WorkspaceNode new_pane; + new_pane.is_pane = true; + new_pane.pane_index = new_pane_index; + + node->is_pane = false; + node->pane_index = -1; + node->orientation = orientation; + node->sizes = {0.5f, 0.5f}; + node->children.clear(); + if (new_before) { + node->children.push_back(std::move(new_pane)); + node->children.push_back(std::move(existing_pane)); + } else { + node->children.push_back(std::move(existing_pane)); + node->children.push_back(std::move(new_pane)); + } + return true; + } + + if (node->orientation == orientation) { + for (size_t i = 0; i < node->children.size(); ++i) { + WorkspaceNode &child = node->children[i]; + if (!child.is_pane || child.pane_index != target_pane_index) { + continue; + } + + WorkspaceNode new_pane; + new_pane.is_pane = true; + new_pane.pane_index = new_pane_index; + + const auto insert_it = node->children.begin() + static_cast(new_before ? i : i + 1); + node->children.insert(insert_it, std::move(new_pane)); + node->sizes.assign(node->children.size(), 1.0f / static_cast(node->children.size())); + return true; + } + } + + for (WorkspaceNode &child : node->children) { + if (split_pane_node(&child, target_pane_index, orientation, new_before, new_pane_index)) return true; + } + return false; +} + +Pane make_empty_pane(const std::string &title = UNTITLED_PANE_TITLE) { + Pane pane; + pane.title = title; + return pane; +} + +WorkspaceTab make_empty_tab(const std::string &tab_name) { + WorkspaceTab tab; + tab.tab_name = tab_name; + tab.panes.push_back(make_empty_pane()); + tab.root.is_pane = true; + tab.root.pane_index = 0; + return tab; +} + +SketchLayout make_empty_layout() { + SketchLayout layout; + layout.tabs.push_back(make_empty_tab("tab1")); + layout.current_tab_index = 0; + layout.roots.push_back("layout"); + return layout; +} + +bool tab_name_exists(const SketchLayout &layout, const std::string &name) { + return std::any_of(layout.tabs.begin(), layout.tabs.end(), [&](const WorkspaceTab &tab) { + return tab.tab_name == name; + }); +} + +std::string next_tab_name(const SketchLayout &layout, const std::string &base_name) { + if (base_name == "tab" || base_name == "tab1") { + int max_suffix = 0; + for (const WorkspaceTab &tab : layout.tabs) { + if (tab.tab_name.size() > 3 && util::starts_with(tab.tab_name, "tab")) { + const std::string suffix = tab.tab_name.substr(3); + if (!suffix.empty() && std::all_of(suffix.begin(), suffix.end(), ::isdigit)) { + max_suffix = std::max(max_suffix, std::stoi(suffix)); + } + } + } + return "tab" + std::to_string(std::max(1, max_suffix + 1)); + } + std::string base = base_name.empty() ? "tab" : base_name; + if (!tab_name_exists(layout, base)) return base; + for (int i = 2; i < 1000; ++i) { + const std::string candidate = base + " " + std::to_string(i); + if (!tab_name_exists(layout, candidate)) return candidate; + } + return base + " copy"; +} + +void clear_layout_autosave(const AppSession &session) { + if (!session.autosave_path.empty() && fs::exists(session.autosave_path)) { + fs::remove(session.autosave_path); + } +} + +bool autosave_layout(AppSession *session, UiState *state) { + try { + if (session->autosave_path.empty()) { + session->autosave_path = autosave_path_for_layout(session->layout_path); + } + session->layout.current_tab_index = state->active_tab_index; + save_layout_json(session->layout, session->autosave_path); + state->layout_dirty = true; + return true; + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + state->status_text = "Failed to save layout draft"; + return false; + } +} + +bool mark_layout_dirty(AppSession *session, UiState *state) { + return autosave_layout(session, state); +} + +bool active_tab_has_map_pane(const SketchLayout &layout) { + if (layout.tabs.empty()) { + return false; + } + const int tab_index = std::clamp(layout.current_tab_index, 0, static_cast(layout.tabs.size()) - 1); + const WorkspaceTab &tab = layout.tabs[static_cast(tab_index)]; + return std::any_of(tab.panes.begin(), tab.panes.end(), [](const Pane &pane) { + return pane_kind_is_special(pane.kind); + }); +} + +void draw_browser_special_item(const char *item_id, const char *label) { + const ImGuiStyle &style = ImGui::GetStyle(); + const ImVec2 row_size(std::max(1.0f, ImGui::GetContentRegionAvail().x), ImGui::GetFrameHeight()); + ImGui::PushID(item_id); + ImGui::InvisibleButton("##special_data_row", row_size); + const bool hovered = ImGui::IsItemHovered(); + const bool held = ImGui::IsItemActive(); + const ImRect rect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + if (hovered) { + const ImU32 bg = ImGui::GetColorU32(held ? ImGuiCol_HeaderActive : ImGuiCol_HeaderHovered); + draw_list->AddRectFilled(rect.Min, rect.Max, bg, 0.0f); + } + ImGui::RenderTextEllipsis(draw_list, + ImVec2(rect.Min.x + style.FramePadding.x, rect.Min.y + style.FramePadding.y), + ImVec2(rect.Max.x - style.FramePadding.x, rect.Max.y), + rect.Max.x - style.FramePadding.x, + label, + nullptr, + nullptr); + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { + ImGui::SetDragDropPayload("JOTP_SPECIAL_ITEM", item_id, std::strlen(item_id) + 1); + ImGui::TextUnformatted(label); + ImGui::EndDragDropSource(); + } + ImGui::PopID(); +} + +std::array app_next_curve_color(const Pane &pane) { + static constexpr std::array, 10> PALETTE = {{ + {35, 107, 180}, + {220, 82, 52}, + {67, 160, 71}, + {243, 156, 18}, + {123, 97, 255}, + {0, 150, 136}, + {214, 48, 49}, + {52, 73, 94}, + {197, 90, 17}, + {96, 125, 139}, + }}; + return PALETTE[pane.curves.size() % PALETTE.size()]; +} + +void draw_sidebar(AppSession *session, const UiMetrics &ui, UiState *state, bool show_camera_feed) { + ImGui::SetNextWindowPos(ImVec2(0.0f, ui.top_offset)); + ImGui::SetNextWindowSize(ImVec2(ui.sidebar_width, std::max(1.0f, ui.height - ui.top_offset))); + ImGui::PushStyleColor(ImGuiCol_WindowBg, color_rgb(238, 240, 244)); + ImGui::PushStyleColor(ImGuiCol_Border, color_rgb(190, 197, 205)); + const ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings; + if (ImGui::Begin("##sidebar", nullptr, flags)) { + const RouteLoadSnapshot load = session->route_loader ? session->route_loader->snapshot() : RouteLoadSnapshot{}; + const bool show_load_progress = session->route_loader && (load.active || load.total_segments > 0); + const bool streaming = session->data_mode == SessionDataMode::Stream; + CameraFeedView *sidebar_camera = session->pane_camera_feeds[static_cast(sidebar_preview_camera_view(*session))].get(); + if (show_camera_feed && sidebar_camera != nullptr) { + sidebar_camera->draw(ImGui::GetContentRegionAvail().x, load.active); + } else if (streaming) { + ImGui::SeparatorText("Camera"); + ImGui::TextDisabled("Camera not available during live stream."); + ImGui::Spacing(); + } + + ImGui::SeparatorText(streaming ? "Stream" : "Route"); + if (streaming) { + const StreamPollSnapshot stream = session->stream_poller ? session->stream_poller->snapshot() : StreamPollSnapshot{}; + const bool paused = stream.paused || session->stream_paused; + const bool live = stream.connected && !paused; + const ImVec4 status_color = live ? color_rgb(38, 135, 67) : (paused ? color_rgb(168, 119, 34) : color_rgb(155, 63, 63)); + ImGui::TextColored(status_color, "%s %s", live ? "●" : "○", stream.source_label.c_str()); + ImGui::TextDisabled("%s%s", stream_source_kind_label(stream.source_kind), paused ? " paused" : ""); + const double span = session->route_data.has_time_range ? (session->route_data.x_max - session->route_data.x_min) : 0.0; + const float fill = stream.buffer_seconds <= 0.0 + ? 0.0f + : std::clamp(static_cast(span / stream.buffer_seconds), 0.0f, 1.0f); + ImGui::ProgressBar(fill, ImVec2(-FLT_MIN, 0.0f), nullptr); + ImGui::TextDisabled("%.0fs buffer | %zu series", session->stream_buffer_seconds, session->route_data.series.size()); + const char *button_label = paused ? "Resume" : "Pause"; + if (ImGui::Button(button_label, ImVec2(std::max(1.0f, ImGui::GetContentRegionAvail().x), 0.0f))) { + if (paused) { + start_stream_session(session, state, session->stream_source, session->stream_buffer_seconds, true); + } else { + stop_stream_session(session, state); + state->status_text = "Paused stream " + stream_source_target_label(session->stream_source); + } + } + } else if (session->route_name.empty()) { + ImGui::TextDisabled("No route loaded"); + } + if (!session->route_data.car_fingerprint.empty()) { + ImGui::TextWrapped("Car: %s", session->route_data.car_fingerprint.c_str()); + } + const std::vector dbc_names = available_dbc_names(); + ImGui::SetNextItemWidth(-FLT_MIN); + if (ImGui::BeginCombo("##dbc_combo", dbc_combo_label(*session).c_str())) { + const bool auto_selected = session->dbc_override.empty(); + if (ImGui::Selectable("Auto", auto_selected)) { + apply_dbc_override_change(session, state, {}); + } + if (auto_selected) { + ImGui::SetItemDefaultFocus(); + } + ImGui::Separator(); + for (const std::string &dbc_name : dbc_names) { + const bool selected = session->dbc_override == dbc_name; + if (ImGui::Selectable(dbc_name.c_str(), selected) && !selected) { + apply_dbc_override_change(session, state, dbc_name); + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + ImGui::SeparatorText("Layout"); + ImGui::SetNextItemWidth(-FLT_MIN); + const std::string layout_combo_label = [&] { + const std::string base = session->layout_path.empty() ? std::string("untitled") : session->layout_path.stem().string(); + return state->layout_dirty ? base + " *" : base; + }(); + if (ImGui::BeginCombo("##layout_combo", layout_combo_label.c_str())) { + if (ImGui::Selectable("New Layout")) { + start_new_layout(session, state); + } + ImGui::Separator(); + const std::vector layouts = available_layout_names(); + const std::string current_layout = session->layout_path.empty() ? std::string("untitled") : session->layout_path.stem().string(); + for (const std::string &layout_name : layouts) { + const bool selected = layout_name == current_layout; + if (ImGui::Selectable(layout_name.c_str(), selected) && !selected) { + reload_layout(session, state, layout_name); + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndCombo(); + } + const float layout_button_gap = ImGui::GetStyle().ItemSpacing.x; + const float layout_row_width = std::max(1.0f, ImGui::GetContentRegionAvail().x); + const float layout_button_width = std::max(1.0f, (layout_row_width - 2.0f * layout_button_gap) / 3.0f); + if (ImGui::Button("New", ImVec2(layout_button_width, 0.0f))) { + start_new_layout(session, state); + } + ImGui::SameLine(0.0f, layout_button_gap); + if (ImGui::Button("Save", ImVec2(layout_button_width, 0.0f))) { + state->request_save_layout = true; + } + ImGui::SameLine(0.0f, layout_button_gap); + ImGui::BeginDisabled(!state->layout_dirty); + if (ImGui::Button("Reset", ImVec2(layout_button_width, 0.0f))) { + state->request_reset_layout = true; + } + ImGui::EndDisabled(); + ImGui::Spacing(); + + ImGui::SeparatorText("Data Sources"); + ImGui::SetNextItemWidth(-FLT_MIN); + input_text_with_hint_string("##browser_filter", "Search...", &state->browser_filter); + const float footer_height = ImGui::GetFrameHeightWithSpacing() + + ImGui::GetTextLineHeightWithSpacing() + + 16.0f + + (show_load_progress ? (ImGui::GetFrameHeightWithSpacing() + 12.0f) : 0.0f); + const float browser_height = std::max(1.0f, ImGui::GetContentRegionAvail().y - footer_height); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 2.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8.0f, 3.0f)); + if (ImGui::BeginChild("##timeseries_browser", ImVec2(0.0f, browser_height), true)) { + const std::string filter = lowercase_copy(state->browser_filter); + std::vector visible_paths; + for (const BrowserNode &node : session->browser_nodes) { + collect_visible_leaf_paths(node, filter, &visible_paths); + } + for (const SpecialItemSpec &spec : kSpecialItemSpecs) { + draw_browser_special_item(spec.id, spec.label); + } + ImGui::Dummy(ImVec2(0.0f, 2.0f)); + ImGui::Separator(); + ImGui::Dummy(ImVec2(0.0f, 2.0f)); + for (const BrowserNode &node : session->browser_nodes) { + draw_browser_node(session, node, state, filter, visible_paths); + } + } + ImGui::EndChild(); + ImGui::PopStyleVar(2); + + ImGui::SeparatorText("Custom Series"); + if (ImGui::Button("Create...", ImVec2(std::max(1.0f, ImGui::GetContentRegionAvail().x), 0.0f))) { + open_custom_series_editor(state, state->selected_browser_path); + } + if (show_load_progress) { + const float total = static_cast(std::max(1, load.total_segments)); + const bool finalizing = load.active + && load.total_segments > 0 + && load.segments_downloaded >= load.total_segments + && load.segments_parsed >= load.total_segments; + const float progress = load.total_segments == 0 + ? 0.0f + : (finalizing + ? 0.99f + : std::clamp(static_cast(load.segments_downloaded + load.segments_parsed) / (2.0f * total), 0.0f, 0.99f)); + ImGui::Dummy(ImVec2(0.0f, 8.0f)); + ImGui::ProgressBar(progress, ImVec2(-FLT_MIN, 0.0f), finalizing ? "Finalizing..." : nullptr); + } + } + ImGui::End(); + ImGui::PopStyleColor(2); +} + +std::string app_curve_display_name(const Curve &curve) { + if (!curve.label.empty()) return curve.label; + if (!curve.name.empty()) return curve.name; + return "curve"; +} + +Curve make_curve_for_path(const Pane &pane, const std::string &path) { + Curve curve; + curve.name = path; + curve.label = path; + curve.color = app_next_curve_color(pane); + return curve; +} + +bool add_curve_to_pane(WorkspaceTab *tab, int pane_index, Curve curve) { + if (pane_index < 0 || pane_index >= static_cast(tab->panes.size())) { + return false; + } + Pane &pane = tab->panes[static_cast(pane_index)]; + if (pane.kind != PaneKind::Plot) { + pane.kind = PaneKind::Plot; + if (is_default_special_title(pane.title)) { + pane.title = UNTITLED_PANE_TITLE; + } + } + for (Curve &existing : pane.curves) { + const bool same_named_curve = !curve.name.empty() && existing.name == curve.name; + const bool same_unnamed_curve = curve.name.empty() && existing.name.empty() && existing.label == curve.label; + if (same_named_curve || same_unnamed_curve) { + existing.visible = true; + return false; + } + } + pane.curves.push_back(std::move(curve)); + return true; +} + +bool add_path_curve_to_pane(AppSession *session, UiState *state, int pane_index, const std::string &path) { + if (app_find_route_series(*session, path) == nullptr) { + state->status_text = "Path not found in route"; + return false; + } + WorkspaceTab *tab = app_active_tab(&session->layout, *state); + if (tab == nullptr || pane_index < 0 || pane_index >= static_cast(tab->panes.size())) { + state->status_text = "No active pane"; + return false; + } + const SketchLayout before_layout = session->layout; + const bool inserted = add_curve_to_pane(tab, pane_index, make_curve_for_path(tab->panes[static_cast(pane_index)], path)); + bool autosave_ok = true; + if (inserted) { + state->undo.push(before_layout); + autosave_ok = mark_layout_dirty(session, state); + } + if (autosave_ok) { + state->status_text = inserted ? "Added " + path : "Curve already present"; + } + return true; +} + +int add_path_curves_to_pane(AppSession *session, UiState *state, int pane_index, const std::vector &paths) { + WorkspaceTab *tab = app_active_tab(&session->layout, *state); + if (tab == nullptr || pane_index < 0 || pane_index >= static_cast(tab->panes.size())) { + state->status_text = "No active pane"; + return 0; + } + + int inserted_count = 0; + int duplicate_count = 0; + const SketchLayout before_layout = session->layout; + for (const std::string &path : paths) { + if (app_find_route_series(*session, path) == nullptr) continue; + if (add_curve_to_pane(tab, pane_index, make_curve_for_path(tab->panes[static_cast(pane_index)], path))) { + ++inserted_count; + } else { + ++duplicate_count; + } + } + + if (inserted_count > 0) { + state->undo.push(before_layout); + if (mark_layout_dirty(session, state)) { + state->status_text = inserted_count == 1 + ? "Added " + paths.front() + : "Added " + std::to_string(inserted_count) + " curves"; + } + return inserted_count; + } + + if (duplicate_count > 0) { + state->status_text = duplicate_count == 1 ? "Curve already present" : "Curves already present"; + } else { + state->status_text = "No matching series found"; + } + return 0; +} + +bool app_add_curve_to_active_pane(AppSession *session, UiState *state, const std::string &path) { + const TabUiState *tab_state = app_active_tab_state(state); + if (tab_state == nullptr) { + state->status_text = "No active pane"; + return false; + } + return add_path_curve_to_pane(session, state, tab_state->active_pane_index, path); +} + +bool apply_special_item_to_pane(WorkspaceTab *tab, TabUiState *tab_state, int pane_index, std::string_view item_id) { + if (tab == nullptr || tab_state == nullptr) return false; + if (pane_index < 0 || pane_index >= static_cast(tab->panes.size())) return false; + const SpecialItemSpec *spec = special_item_spec(item_id); + if (spec == nullptr) return false; + Pane &pane = tab->panes[static_cast(pane_index)]; + if (!((pane.kind == PaneKind::Plot && pane.curves.empty()) || pane_kind_is_special(pane.kind))) { + return false; + } + if (pane.kind == spec->kind && (spec->kind != PaneKind::Camera || pane.camera_view == spec->camera_view)) { + tab_state->active_pane_index = pane_index; + return false; + } + const PaneKind previous_kind = pane.kind; + pane.kind = spec->kind; + pane.camera_view = spec->camera_view; + if (spec->kind == PaneKind::Map) { + if (pane.title == UNTITLED_PANE_TITLE || previous_kind != PaneKind::Plot) { + pane.title = spec->label; + } + } else { + pane.title = spec->label; + resize_tab_pane_state(tab_state, tab->panes.size()); + tab_state->camera_panes[static_cast(pane_index)].fit_to_pane = true; + } + tab_state->active_pane_index = pane_index; + return true; +} + +bool split_pane(WorkspaceTab *tab, int pane_index, PaneDropZone zone, std::optional curve = std::nullopt) { + if (pane_index < 0 || pane_index >= static_cast(tab->panes.size())) { + return false; + } + if (zone == PaneDropZone::Center) return false; + + const int new_pane_index = static_cast(tab->panes.size()); + Pane new_pane = make_empty_pane(); + if (curve.has_value()) { + new_pane.curves.push_back(*curve); + } + tab->panes.push_back(std::move(new_pane)); + + const bool vertical = zone == PaneDropZone::Top || zone == PaneDropZone::Bottom; + const bool new_before = zone == PaneDropZone::Left || zone == PaneDropZone::Top; + return split_pane_node(&tab->root, pane_index, + vertical ? SplitOrientation::Vertical : SplitOrientation::Horizontal, + new_before, new_pane_index); +} + +bool close_pane(WorkspaceTab *tab, int pane_index) { + if (pane_index < 0 || pane_index >= static_cast(tab->panes.size())) { + return false; + } + if (tab->panes.size() <= 1) { + tab->panes[static_cast(pane_index)] = make_empty_pane(); + return true; + } + if (remove_pane_node(&tab->root, pane_index)) return false; + tab->panes.erase(tab->panes.begin() + static_cast(pane_index)); + decrement_pane_indices(&tab->root, pane_index); + normalize_split_node(&tab->root); + return true; +} + +void clear_pane(WorkspaceTab *tab, int pane_index) { + if (pane_index < 0 || pane_index >= static_cast(tab->panes.size())) { + return; + } + Pane &pane = tab->panes[static_cast(pane_index)]; + pane.curves.clear(); + pane.title = UNTITLED_PANE_TITLE; +} + +void create_runtime_tab(SketchLayout *layout, UiState *state) { + const std::string tab_name = next_tab_name(*layout, "tab1"); + layout->tabs.push_back(make_empty_tab(tab_name)); + state->tabs.push_back(TabUiState{.dock_needs_build = true, .active_pane_index = 0, .runtime_id = state->next_tab_runtime_id++}); + request_tab_selection(state, static_cast(layout->tabs.size()) - 1); + state->status_text = "Created " + tab_name; +} + +void duplicate_runtime_tab(SketchLayout *layout, UiState *state) { + if (layout->tabs.empty()) { + return; + } + const int source_index = std::clamp(state->active_tab_index, 0, static_cast(layout->tabs.size()) - 1); + WorkspaceTab copy = layout->tabs[static_cast(source_index)]; + copy.tab_name = next_tab_name(*layout, copy.tab_name + " copy"); + layout->tabs.push_back(std::move(copy)); + const int active_pane_index = source_index < static_cast(state->tabs.size()) ? state->tabs[static_cast(source_index)].active_pane_index : 0; + state->tabs.push_back(TabUiState{.dock_needs_build = true, .active_pane_index = active_pane_index, .runtime_id = state->next_tab_runtime_id++}); + request_tab_selection(state, static_cast(layout->tabs.size()) - 1); + state->status_text = "Duplicated tab"; +} + +void close_runtime_tab(SketchLayout *layout, UiState *state) { + if (layout->tabs.empty()) { + return; + } + const int tab_index = std::clamp(state->active_tab_index, 0, static_cast(layout->tabs.size()) - 1); + if (layout->tabs.size() == 1) { + layout->tabs[0] = make_empty_tab(layout->tabs[0].tab_name.empty() ? "tab1" : layout->tabs[0].tab_name); + if (state->tabs.empty()) { + state->tabs.push_back(TabUiState{.dock_needs_build = true, .active_pane_index = 0}); + } else { + state->tabs.resize(1); + state->tabs[0] = TabUiState{ + .dock_needs_build = true, + .active_pane_index = 0, + .runtime_id = state->tabs[0].runtime_id == 0 ? state->next_tab_runtime_id++ : state->tabs[0].runtime_id, + }; + } + state->active_tab_index = 0; + state->requested_tab_index = 0; + layout->current_tab_index = 0; + cancel_rename_tab(state); + state->status_text = "Closed tab"; + return; + } + layout->tabs.erase(layout->tabs.begin() + static_cast(tab_index)); + if (tab_index < static_cast(state->tabs.size())) { + state->tabs.erase(state->tabs.begin() + static_cast(tab_index)); + } + if (state->active_tab_index >= static_cast(layout->tabs.size())) { + state->active_tab_index = static_cast(layout->tabs.size()) - 1; + } + sync_ui_state(state, *layout); + state->requested_tab_index = state->active_tab_index; + state->status_text = "Closed tab"; +} + +void rename_runtime_tab(SketchLayout *layout, UiState *state) { + if (state->rename_tab_index < 0 || state->rename_tab_index >= static_cast(layout->tabs.size())) { + return; + } + layout->tabs[static_cast(state->rename_tab_index)].tab_name = state->rename_tab_buffer; + state->status_text = "Renamed tab"; + layout->current_tab_index = state->rename_tab_index; + cancel_rename_tab(state); +} + +void draw_inline_tab_editor(AppSession *session, UiState *state, const ImRect &tab_rect) { + const int rename_tab_index = state->rename_tab_index; + if (rename_tab_index < 0 || rename_tab_index >= static_cast(session->layout.tabs.size())) { + return; + } + + const float width = std::max(48.0f, tab_rect.Max.x - tab_rect.Min.x - 10.0f); + const ImVec2 pos = ImVec2(tab_rect.Min.x + 5.0f, tab_rect.Min.y + 2.0f); + ImGui::SetCursorScreenPos(pos); + ImGui::PushItemWidth(width); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 2.0f)); + if (state->focus_rename_tab_input) { + ImGui::SetKeyboardFocusHere(); + state->focus_rename_tab_input = false; + } + const bool submitted = input_text_string("##rename_tab_inline", + &state->rename_tab_buffer, + ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue); + const bool active = ImGui::IsItemActive(); + const bool escape = active && ImGui::IsKeyPressed(ImGuiKey_Escape); + const bool deactivated = ImGui::IsItemDeactivated(); + ImGui::PopStyleVar(); + ImGui::PopItemWidth(); + + if (escape) { + cancel_rename_tab(state); + } else if (submitted || deactivated) { + const SketchLayout before_layout = session->layout; + rename_runtime_tab(&session->layout, state); + state->undo.push(before_layout); + mark_layout_dirty(session, state); + } +} + + +std::optional draw_pane_drop_target(int tab_index, int pane_index, const Pane &target_pane) { + if (ImGui::GetDragDropPayload() == nullptr) return std::nullopt; + + const ImVec2 window_pos = ImGui::GetWindowPos(); + const ImVec2 content_min = ImGui::GetWindowContentRegionMin(); + const ImVec2 content_max = ImGui::GetWindowContentRegionMax(); + ImRect content_rect(ImVec2(window_pos.x + content_min.x, window_pos.y + content_min.y), + ImVec2(window_pos.x + content_max.x, window_pos.y + content_max.y)); + content_rect.Expand(ImVec2(-6.0f, -6.0f)); + if (content_rect.GetWidth() < 60.0f || content_rect.GetHeight() < 60.0f) { + return std::nullopt; + } + + const float edge_w = std::min(90.0f, content_rect.GetWidth() * 0.24f); + const float edge_h = std::min(72.0f, content_rect.GetHeight() * 0.24f); + struct ZoneRect { + PaneDropZone zone; + ImRect rect; + }; + const std::array zones = {{ + {PaneDropZone::Left, ImRect(content_rect.Min, ImVec2(content_rect.Min.x + edge_w, content_rect.Max.y))}, + {PaneDropZone::Right, ImRect(ImVec2(content_rect.Max.x - edge_w, content_rect.Min.y), content_rect.Max)}, + {PaneDropZone::Top, ImRect(content_rect.Min, ImVec2(content_rect.Max.x, content_rect.Min.y + edge_h))}, + {PaneDropZone::Bottom, ImRect(ImVec2(content_rect.Min.x, content_rect.Max.y - edge_h), content_rect.Max)}, + {PaneDropZone::Center, ImRect(ImVec2(content_rect.Min.x + edge_w, content_rect.Min.y + edge_h), + ImVec2(content_rect.Max.x - edge_w, content_rect.Max.y - edge_h))}, + }}; + + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + for (const ZoneRect &zone : zones) { + if (zone.rect.GetWidth() <= 0.0f || zone.rect.GetHeight() <= 0.0f) { + continue; + } + + ImGui::PushID(static_cast(zone.zone) * 1000 + pane_index + tab_index * 100); + ImGui::SetCursorScreenPos(zone.rect.Min); + ImGui::InvisibleButton("##drop_zone", zone.rect.GetSize()); + if (ImGui::BeginDragDropTarget()) { + auto try_accept = [&](const char *type) -> const ImGuiPayload * { + const ImGuiPayload *p = ImGui::AcceptDragDropPayload(type, ImGuiDragDropFlags_AcceptBeforeDelivery); + if (p && p->Preview) { + draw_list->AddRectFilled(zone.rect.Min, zone.rect.Max, IM_COL32(70, 130, 220, 55)); + draw_list->AddRect(zone.rect.Min, zone.rect.Max, IM_COL32(45, 95, 175, 220), 0.0f, 0, 2.0f); + } + return p; + }; + auto deliver = [&](PaneDropAction action) -> std::optional { + action.zone = zone.zone; + action.target_pane_index = pane_index; + ImGui::EndDragDropTarget(); + ImGui::PopID(); + return action; + }; + if (const ImGuiPayload *p = try_accept("JOTP_BROWSER_PATHS"); p && p->Delivery) { + if (zone.zone != PaneDropZone::Center || target_pane.kind == PaneKind::Plot) { + PaneDropAction action; + action.from_browser = true; + action.browser_paths = decode_browser_drag_payload(static_cast(p->Data)); + return deliver(std::move(action)); + } + } + if (zone.zone != PaneDropZone::Center || (target_pane.kind == PaneKind::Plot && target_pane.curves.empty()) || pane_kind_is_special(target_pane.kind)) { + if (const ImGuiPayload *p = try_accept("JOTP_SPECIAL_ITEM"); p && p->Delivery) { + PaneDropAction action; + action.special_item_id = static_cast(p->Data); + return deliver(std::move(action)); + } + } + if (const ImGuiPayload *p = try_accept("JOTP_PANE_CURVE"); p && p->Delivery) { + if (zone.zone != PaneDropZone::Center || target_pane.kind == PaneKind::Plot) { + PaneDropAction action; + action.curve_ref = *static_cast(p->Data); + return deliver(std::move(action)); + } + } + ImGui::EndDragDropTarget(); + } + ImGui::PopID(); + } + return std::nullopt; +} + +bool commit_tab_layout_change(AppSession *session, + UiState *state, + WorkspaceTab *tab, + TabUiState *tab_state, + const SketchLayout &before_layout, + std::string_view status_text, + bool dock_changed) { + if (dock_changed) { + mark_tab_dock_dirty(state, state->active_tab_index); + } + resize_tab_pane_state(tab_state, tab->panes.size()); + state->undo.push(before_layout); + if (mark_layout_dirty(session, state)) { + state->status_text = std::string(status_text); + } + return true; +} + +bool apply_pane_menu_action(AppSession *session, UiState *state, int pane_index, + const PaneMenuAction &action) { + WorkspaceTab *tab = app_active_tab(&session->layout, *state); + TabUiState *tab_state = app_active_tab_state(state); + if (tab == nullptr || tab_state == nullptr) return false; + + const int original_pane_count = static_cast(tab->panes.size()); + const SketchLayout before_layout = session->layout; + bool dock_changed = false; + bool layout_changed = false; + std::string_view success_status = "Workspace updated"; + switch (action.kind) { + case PaneMenuActionKind::OpenAxisLimits: + tab_state->active_pane_index = pane_index; + open_axis_limits_editor(*session, state, pane_index); + state->status_text = "Axis limits editor opened"; + return true; + case PaneMenuActionKind::OpenCustomSeries: + tab_state->active_pane_index = pane_index; + open_custom_series_editor(state, preferred_custom_series_source(tab->panes[static_cast(pane_index)])); + state->status_text = "Custom series editor opened"; + return true; + case PaneMenuActionKind::SplitLeft: + case PaneMenuActionKind::SplitRight: + case PaneMenuActionKind::SplitTop: + case PaneMenuActionKind::SplitBottom: { + constexpr PaneDropZone kZones[] = {PaneDropZone::Left, PaneDropZone::Right, PaneDropZone::Top, PaneDropZone::Bottom}; + const auto zone = kZones[static_cast(action.kind) - static_cast(PaneMenuActionKind::SplitLeft)]; + if (split_pane(tab, pane_index, zone)) { + tab_state->active_pane_index = static_cast(tab->panes.size()) - 1; + dock_changed = true; + layout_changed = true; + } + break; + } + case PaneMenuActionKind::ResetView: + reset_shared_range(state, *session); + state->follow_latest = session->data_mode == SessionDataMode::Stream; + state->suppress_range_side_effects = true; + clamp_shared_range(state, *session); + persist_shared_range_to_tab(tab, *state); + clear_pane_vertical_limits(&tab->panes[static_cast(pane_index)]); + layout_changed = true; + success_status = "Plot view reset"; + break; + case PaneMenuActionKind::ResetHorizontal: + reset_shared_range(state, *session); + state->follow_latest = session->data_mode == SessionDataMode::Stream; + state->suppress_range_side_effects = true; + clamp_shared_range(state, *session); + persist_shared_range_to_tab(tab, *state); + layout_changed = true; + success_status = "Horizontal zoom reset"; + break; + case PaneMenuActionKind::ResetVertical: + clear_pane_vertical_limits(&tab->panes[static_cast(pane_index)]); + layout_changed = true; + success_status = "Vertical zoom reset"; + break; + case PaneMenuActionKind::Clear: + clear_pane(tab, pane_index); + tab_state->active_pane_index = pane_index; + layout_changed = true; + break; + case PaneMenuActionKind::Close: + if (close_pane(tab, pane_index)) { + tab_state->active_pane_index = std::clamp(pane_index, 0, static_cast(tab->panes.size()) - 1); + layout_changed = true; + dock_changed = static_cast(tab->panes.size()) != original_pane_count; + } + break; + case PaneMenuActionKind::None: + return false; + } + + if (!layout_changed) { + return false; + } + return commit_tab_layout_change(session, state, tab, tab_state, before_layout, success_status, dock_changed); +} + +bool apply_pane_drop_action(AppSession *session, UiState *state, const PaneDropAction &action) { + WorkspaceTab *tab = app_active_tab(&session->layout, *state); + TabUiState *tab_state = app_active_tab_state(state); + if (tab == nullptr || tab_state == nullptr) return false; + + if (!action.special_item_id.empty()) { + const SpecialItemSpec *spec = special_item_spec(action.special_item_id); + if (spec == nullptr) { + return false; + } + if (action.zone == PaneDropZone::Center) { + if (action.target_pane_index < 0 || action.target_pane_index >= static_cast(tab->panes.size())) { + return false; + } + if (!((tab->panes[static_cast(action.target_pane_index)].kind == PaneKind::Plot + && tab->panes[static_cast(action.target_pane_index)].curves.empty()) + || pane_kind_is_special(tab->panes[static_cast(action.target_pane_index)].kind))) { + state->status_text = std::string(special_item_label(action.special_item_id)) + " can only replace another special pane or use an empty pane"; + return false; + } + const SketchLayout before_layout = session->layout; + const bool changed = apply_special_item_to_pane(tab, tab_state, action.target_pane_index, spec->id); + if (!changed) { + state->status_text = std::string(special_item_label(action.special_item_id)) + " already shown in pane"; + return false; + } + return commit_tab_layout_change(session, state, tab, tab_state, before_layout, + std::string(special_item_label(action.special_item_id)) + " added to pane", + false); + } + const SketchLayout before_layout = session->layout; + if (split_pane(tab, action.target_pane_index, action.zone)) { + tab_state->active_pane_index = static_cast(tab->panes.size()) - 1; + const bool changed = apply_special_item_to_pane(tab, tab_state, tab_state->active_pane_index, spec->id); + if (!changed) { + return false; + } + return commit_tab_layout_change(session, state, tab, tab_state, before_layout, + "Split pane and added " + std::string(special_item_label(action.special_item_id)), + true); + } + return false; + } + + if (action.from_browser) { + if (action.browser_paths.empty()) return false; + if (action.zone == PaneDropZone::Center) { + const int inserted_count = add_path_curves_to_pane(session, state, action.target_pane_index, action.browser_paths); + if (inserted_count > 0) { + tab_state->active_pane_index = action.target_pane_index; + } + return inserted_count > 0; + } + const SketchLayout before_layout = session->layout; + if (split_pane(tab, action.target_pane_index, action.zone)) { + tab_state->active_pane_index = static_cast(tab->panes.size()) - 1; + int inserted_count = 0; + for (const std::string &path : action.browser_paths) { + if (app_find_route_series(*session, path) == nullptr) continue; + if (add_curve_to_pane(tab, tab_state->active_pane_index, + make_curve_for_path(tab->panes[static_cast(tab_state->active_pane_index)], path))) { + ++inserted_count; + } + } + if (inserted_count > 0) { + return commit_tab_layout_change(session, state, tab, tab_state, before_layout, + inserted_count == 1 + ? "Split pane and added " + action.browser_paths.front() + : "Split pane and added " + std::to_string(inserted_count) + " curves", + true); + } + return false; + } + return false; + } + + if (action.curve_ref.tab_index < 0 + || action.curve_ref.tab_index >= static_cast(session->layout.tabs.size())) { + return false; + } + WorkspaceTab &source_tab = session->layout.tabs[static_cast(action.curve_ref.tab_index)]; + if (action.curve_ref.pane_index < 0 + || action.curve_ref.pane_index >= static_cast(source_tab.panes.size())) { + return false; + } + const Pane &source_pane = source_tab.panes[static_cast(action.curve_ref.pane_index)]; + if (action.curve_ref.curve_index < 0 + || action.curve_ref.curve_index >= static_cast(source_pane.curves.size())) { + return false; + } + const Curve curve = source_pane.curves[static_cast(action.curve_ref.curve_index)]; + + if (action.zone == PaneDropZone::Center) { + const SketchLayout before_layout = session->layout; + const bool inserted = add_curve_to_pane(tab, action.target_pane_index, curve); + tab_state->active_pane_index = action.target_pane_index; + if (inserted) { + state->undo.push(before_layout); + if (mark_layout_dirty(session, state)) { + state->status_text = "Added " + app_curve_display_name(curve); + } + } else { + state->status_text = "Curve already present"; + } + return true; + } + const SketchLayout before_layout = session->layout; + if (split_pane(tab, action.target_pane_index, action.zone, curve)) { + tab_state->active_pane_index = static_cast(tab->panes.size()) - 1; + return commit_tab_layout_change(session, state, tab, tab_state, before_layout, + "Split pane and added " + app_curve_display_name(curve), + true); + } + return false; +} + +ImGuiDir dock_direction(SplitOrientation orientation) { + return orientation == SplitOrientation::Horizontal ? ImGuiDir_Left : ImGuiDir_Up; +} + +void build_dock_tree(const WorkspaceNode &node, const WorkspaceTab &tab, int tab_runtime_id, ImGuiID dock_id) { + if (node.is_pane) { + if (node.pane_index >= 0 && node.pane_index < static_cast(tab.panes.size())) { + ImGui::DockBuilderDockWindow( + pane_window_name(tab_runtime_id, node.pane_index, tab.panes[static_cast(node.pane_index)]).c_str(), + dock_id); + if (ImGuiDockNode *dock_node = ImGui::DockBuilderGetNode(dock_id); dock_node != nullptr) { + dock_node->LocalFlags |= ImGuiDockNodeFlags_AutoHideTabBar | + ImGuiDockNodeFlags_NoWindowMenuButton | + ImGuiDockNodeFlags_NoCloseButton; + } + } + return; + } + if (node.children.empty()) { + return; + } + if (node.children.size() == 1) { + build_dock_tree(node.children.front(), tab, tab_runtime_id, dock_id); + return; + } + + float remaining = 1.0f; + ImGuiID current = dock_id; + for (size_t i = 0; i + 1 < node.children.size(); ++i) { + const float child_size = i < node.sizes.size() ? node.sizes[i] : 0.0f; + const float ratio = remaining <= 0.0f ? 0.5f : std::clamp(child_size / remaining, 0.05f, 0.95f); + ImGuiID child_id = 0; + ImGuiID remainder_id = 0; + ImGui::DockBuilderSplitNode(current, dock_direction(node.orientation), ratio, &child_id, &remainder_id); + build_dock_tree(node.children[i], tab, tab_runtime_id, child_id); + current = remainder_id; + remaining = std::max(0.0f, remaining - child_size); + } + build_dock_tree(node.children.back(), tab, tab_runtime_id, current); +} + +void ensure_dockspace(const WorkspaceTab &tab, TabUiState *tab_state, ImVec2 dockspace_size) { + if (dockspace_size.x <= 0.0f || dockspace_size.y <= 0.0f || tab_state == nullptr) { + return; + } + const bool size_changed = std::abs(tab_state->last_dockspace_size.x - dockspace_size.x) > 1.0f + || std::abs(tab_state->last_dockspace_size.y - dockspace_size.y) > 1.0f; + if (!tab_state->dock_needs_build && !size_changed) { + return; + } + + const ImGuiID dockspace_id = dockspace_id_for_tab(tab_state->runtime_id); + ImGui::DockBuilderRemoveNode(dockspace_id); + ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace | ImGuiDockNodeFlags_AutoHideTabBar); + ImGui::DockBuilderSetNodeSize(dockspace_id, dockspace_size); + build_dock_tree(tab.root, tab, tab_state->runtime_id, dockspace_id); + ImGui::DockBuilderFinish(dockspace_id); + tab_state->dock_needs_build = false; + tab_state->last_dockspace_size = dockspace_size; +} + +void draw_pane_windows(AppSession *session, UiState *state) { + WorkspaceTab *tab = app_active_tab(&session->layout, *state); + TabUiState *tab_state = app_active_tab_state(state); + if (tab == nullptr || tab_state == nullptr) { + return; + } + + std::optional> pending_menu_action; + std::optional pending_close_pane; + std::optional pending_drop_action; + + for (size_t i = 0; i < tab->panes.size(); ++i) { + Pane &pane = tab->panes[i]; + std::optional menu_action; + std::optional drop_action; + bool close_pane_requested = false; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(2.0f, 2.0f)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, color_rgb(250, 250, 251)); + ImGui::PushStyleColor(ImGuiCol_Border, color_rgb(194, 198, 204)); + ImGui::PushStyleColor(ImGuiCol_TitleBg, color_rgb(252, 252, 253)); + ImGui::PushStyleColor(ImGuiCol_TitleBgActive, color_rgb(252, 252, 253)); + ImGui::PushStyleColor(ImGuiCol_TitleBgCollapsed, color_rgb(252, 252, 253)); + const ImGuiWindowFlags flags = ImGuiWindowFlags_NoCollapse; + const std::string window_name = pane_window_name(tab_state->runtime_id, static_cast(i), pane); + const bool opened = ImGui::Begin(window_name.c_str(), nullptr, flags); + if (opened) { + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) + || (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseClicked(0))) { + tab_state->active_pane_index = static_cast(i); + } + if (pane.kind == PaneKind::Map) { + draw_map_pane(session, state, &pane, static_cast(i)); + } else if (pane.kind == PaneKind::Camera) { + draw_camera_pane(session, state, tab_state, static_cast(i), pane); + } else { + draw_plot(*session, &pane, state); + } + draw_pane_frame_overlay(); + close_pane_requested = draw_pane_close_button_overlay(); + menu_action = draw_pane_context_menu(*tab, static_cast(i)); + drop_action = draw_pane_drop_target(state->active_tab_index, static_cast(i), pane); + } + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(5); + if (!pending_menu_action.has_value() && menu_action.has_value()) { + pending_menu_action = std::make_pair(static_cast(i), *menu_action); + } + if (!pending_menu_action.has_value() && !pending_close_pane.has_value() && close_pane_requested) { + pending_close_pane = static_cast(i); + } + if (!pending_menu_action.has_value() && !pending_close_pane.has_value() + && !pending_drop_action.has_value() && drop_action.has_value()) { + pending_drop_action = *drop_action; + } + } + + if (pending_menu_action.has_value()) { + apply_pane_menu_action(session, state, pending_menu_action->first, pending_menu_action->second); + return; + } + if (pending_close_pane.has_value()) { + PaneMenuAction action; + action.kind = PaneMenuActionKind::Close; + action.pane_index = *pending_close_pane; + apply_pane_menu_action(session, state, *pending_close_pane, action); + return; + } + if (pending_drop_action.has_value()) { + apply_pane_drop_action(session, state, *pending_drop_action); + } +} + +void draw_workspace(AppSession *session, const UiMetrics &ui, UiState *state) { + state->custom_series.selected = false; + state->logs.selected = false; + ImGui::SetNextWindowPos(ImVec2(ui.content_x, ui.content_y)); + ImGui::SetNextWindowSize(ImVec2(ui.content_w, ui.content_h)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, color_rgb(244, 246, 248)); + ImGui::PushStyleColor(ImGuiCol_Border, color_rgb(186, 191, 198)); + const ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoScrollWithMouse; + if (ImGui::Begin("##workspace_host", nullptr, flags)) { + const int selection_request = state->requested_tab_index; + std::optional rename_tab_rect; + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10.0f, 6.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing, ImVec2(8.0f, 4.0f)); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.0f, 0.0f, 0.0f, 0.0f)); + if (ImGui::BeginTabBar("##layout_tabs", ImGuiTabBarFlags_FittingPolicyScroll)) { + enum class TabActionKind { + None, + New, + Rename, + Duplicate, + Close, + }; + TabActionKind pending_action = TabActionKind::None; + int pending_tab_index = -1; + bool custom_series_tab_open = state->custom_series.open; + bool suppress_aux_tabs_this_frame = state->request_close_tab && session->layout.tabs.size() == 1; + for (size_t i = 0; i < session->layout.tabs.size(); ++i) { + const WorkspaceTab &tab = session->layout.tabs[i]; + const TabUiState &tab_ui = state->tabs[i]; + ImGuiTabItemFlags tab_flags = ImGuiTabItemFlags_None; + if (static_cast(i) == selection_request) { + tab_flags |= ImGuiTabItemFlags_SetSelected; + } + bool tab_open = true; + const bool opened = ImGui::BeginTabItem(tab_item_label(tab, tab_ui.runtime_id).c_str(), &tab_open, tab_flags); + if (state->rename_tab_index == static_cast(i)) { + rename_tab_rect = ImRect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + } + if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + pending_action = TabActionKind::Rename; + pending_tab_index = static_cast(i); + } + if (!tab_open) { + pending_action = TabActionKind::Close; + pending_tab_index = static_cast(i); + if (session->layout.tabs.size() == 1) { + suppress_aux_tabs_this_frame = true; + } + } + if (ImGui::BeginPopupContextItem()) { + if (ImGui::MenuItem("New Tab")) { + pending_action = TabActionKind::New; + } + if (ImGui::MenuItem("Rename Tab...")) { + pending_action = TabActionKind::Rename; + pending_tab_index = static_cast(i); + } + if (ImGui::MenuItem("Duplicate Tab")) { + pending_action = TabActionKind::Duplicate; + pending_tab_index = static_cast(i); + } + if (ImGui::MenuItem("Close Tab")) { + pending_action = TabActionKind::Close; + pending_tab_index = static_cast(i); + } + ImGui::EndPopup(); + } + if (opened) { + state->active_tab_index = static_cast(i); + session->layout.current_tab_index = state->active_tab_index; + if (i < state->tabs.size()) { + ensure_dockspace(tab, &state->tabs[i], ImGui::GetContentRegionAvail()); + } + ImGui::DockSpace(dockspace_id_for_tab(tab_ui.runtime_id), + ImVec2(0.0f, 0.0f), + ImGuiDockNodeFlags_AutoHideTabBar | + ImGuiDockNodeFlags_NoWindowMenuButton | + ImGuiDockNodeFlags_NoCloseButton); + ImGui::EndTabItem(); + } + } + if (!suppress_aux_tabs_this_frame) { + ImGuiTabItemFlags logs_flags = ImGuiTabItemFlags_None; + if (state->logs.request_select) { + logs_flags |= ImGuiTabItemFlags_SetSelected; + } + if (ImGui::BeginTabItem("Logs##workspace_logs", nullptr, logs_flags)) { + state->logs.request_select = false; + state->logs.selected = true; + draw_logs_tab(session, state); + ImGui::EndTabItem(); + } + if (custom_series_tab_open) { + ImGuiTabItemFlags custom_flags = ImGuiTabItemFlags_None; + if (state->custom_series.request_select) { + custom_flags |= ImGuiTabItemFlags_SetSelected; + } + if (ImGui::BeginTabItem("Custom Series##workspace_custom_series", &custom_series_tab_open, custom_flags)) { + state->custom_series.request_select = false; + state->custom_series.selected = true; + draw_custom_series_editor(session, state); + ImGui::EndTabItem(); + } + } + } + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(12.0f, 5.0f)); + ImGui::PushStyleColor(ImGuiCol_Tab, color_rgb(210, 217, 225)); + ImGui::PushStyleColor(ImGuiCol_TabHovered, color_rgb(224, 230, 237)); + ImGui::PushStyleColor(ImGuiCol_TabSelected, color_rgb(242, 245, 248)); + if (ImGui::TabItemButton(" ##new_tab_button", ImGuiTabItemFlags_Trailing)) { + pending_action = TabActionKind::New; + } + { + const ImRect rect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + const ImU32 color = ImGui::GetColorU32(color_rgb(72, 79, 88)); + const ImVec2 center((rect.Min.x + rect.Max.x) * 0.5f, (rect.Min.y + rect.Max.y) * 0.5f); + constexpr float half_extent = 6.25f; + constexpr float thickness = 2.0f; + draw_list->AddLine(ImVec2(center.x - half_extent, center.y), + ImVec2(center.x + half_extent, center.y), + color, + thickness); + draw_list->AddLine(ImVec2(center.x, center.y - half_extent), + ImVec2(center.x, center.y + half_extent), + color, + thickness); + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(8.0f, 6.0f)); + ImGui::BeginTooltip(); + ImGui::TextUnformatted("New Tab"); + ImGui::EndTooltip(); + ImGui::PopStyleVar(); + } + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(); + ImGui::EndTabBar(); + + if (!custom_series_tab_open) { + state->custom_series.open = false; + state->custom_series.request_select = false; + } + + if (rename_tab_rect.has_value()) { + draw_inline_tab_editor(session, state, *rename_tab_rect); + } + + if (state->request_new_tab || pending_action == TabActionKind::New) { + const SketchLayout before_layout = session->layout; + create_runtime_tab(&session->layout, state); + state->undo.push(before_layout); + mark_layout_dirty(session, state); + state->request_new_tab = false; + } else if (pending_action == TabActionKind::Rename) { + begin_rename_tab(session->layout, state, pending_tab_index); + } else if (state->request_duplicate_tab || pending_action == TabActionKind::Duplicate) { + if (pending_tab_index >= 0) { + request_tab_selection(state, pending_tab_index); + } + const SketchLayout before_layout = session->layout; + duplicate_runtime_tab(&session->layout, state); + state->undo.push(before_layout); + mark_layout_dirty(session, state); + state->request_duplicate_tab = false; + } else if (state->request_close_tab || pending_action == TabActionKind::Close) { + if (pending_tab_index >= 0) { + request_tab_selection(state, pending_tab_index); + } + const SketchLayout before_layout = session->layout; + close_runtime_tab(&session->layout, state); + state->undo.push(before_layout); + mark_layout_dirty(session, state); + state->request_close_tab = false; + } + if (state->requested_tab_index == selection_request) { + state->requested_tab_index = -1; + } + } + ImGui::PopStyleColor(3); + ImGui::PopStyleVar(2); + } + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::PopStyleColor(2); +} + +int run(const Options &options) { + try { + const fs::path layout_path = options.layout.empty() ? fs::path() : resolve_layout_path(options.layout); + AppSession session = { + .layout_path = layout_path, + .autosave_path = layout_path.empty() ? fs::path() : autosave_path_for_layout(layout_path), + .route_name = options.route_name, + .data_dir = options.data_dir, + .dbc_override = {}, + .stream_source = StreamSourceConfig{.kind = is_local_stream_address(options.stream_address) + ? StreamSourceKind::CerealLocal + : StreamSourceKind::CerealRemote, + .address = options.stream_address}, + .stream_buffer_seconds = options.stream_buffer_seconds, + .data_mode = options.stream ? SessionDataMode::Stream : SessionDataMode::Route, + .route_id = options.stream ? RouteIdentifier{} : parse_route_identifier(options.route_name), + .layout = options.layout.empty() ? make_empty_layout() : load_sketch_layout(layout_path), + }; + UiState ui_state; + if (!layout_path.empty() && !session.autosave_path.empty() && fs::exists(session.autosave_path)) { + session.layout = load_sketch_layout(session.autosave_path); + ui_state.layout_dirty = true; + } + ui_state.undo.reset(session.layout); + sync_ui_state(&ui_state, session.layout); + sync_route_buffers(&ui_state, session); + sync_stream_buffers(&ui_state, session); + sync_layout_buffers(&ui_state, session); + + session.async_route_loading = session.data_mode == SessionDataMode::Route + && options.show && options.output_path.empty() && !options.sync_load; + if (session.data_mode == SessionDataMode::Route && !session.async_route_loading) { + TerminalRouteProgress route_progress(::isatty(STDERR_FILENO) != 0); + rebuild_session_route_data(&session, &ui_state, [&](const RouteLoadProgress &update) { + route_progress.update(update); + }); + route_progress.finish(); + } + + GlfwRuntime glfw_runtime(options); + ImGuiRuntime imgui_runtime(glfw_runtime.window()); + configure_style(); + session.map_data = std::make_unique(); + for (std::unique_ptr &feed : session.pane_camera_feeds) { + feed = std::make_unique(); + } + sync_camera_feeds(&session); + + if (session.async_route_loading) { + session.route_loader = std::make_unique(::isatty(STDERR_FILENO) != 0); + start_async_route_load(&session, &ui_state); + } else if (session.data_mode == SessionDataMode::Stream) { + session.stream_poller = std::make_unique(); + start_stream_session(&session, &ui_state, session.stream_source, session.stream_buffer_seconds); + } + + const bool should_capture = !options.output_path.empty(); + const fs::path output_path = should_capture ? fs::path(options.output_path) : fs::path(); + const bool capture_has_map = should_capture && active_tab_has_map_pane(session.layout); + if (options.show) { + bool captured = false; + const auto capture_ready_at = std::chrono::steady_clock::now() + (capture_has_map ? std::chrono::milliseconds(1800) + : std::chrono::milliseconds(0)); + while (!glfwWindowShouldClose(glfw_runtime.window())) { + const bool capture_ready = std::chrono::steady_clock::now() >= capture_ready_at; + const fs::path *capture_path = (!captured && should_capture && capture_ready) ? &output_path : nullptr; + render_frame(glfw_runtime.window(), &session, &ui_state, capture_path); + captured = captured || capture_path != nullptr; + } + } else { + render_frame(glfw_runtime.window(), &session, &ui_state, nullptr); + if (should_capture) { + for (int i = 0; i < 3; ++i) { + render_frame(glfw_runtime.window(), &session, &ui_state, nullptr); + } + if (capture_has_map) { + for (int i = 0; i < 18; ++i) { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + render_frame(glfw_runtime.window(), &session, &ui_state, nullptr); + } + } + render_frame(glfw_runtime.window(), &session, &ui_state, &output_path); + } + } + if (session.stream_poller) { + session.stream_poller->stop(); + } + session.map_data.reset(); + for (std::unique_ptr &feed : session.pane_camera_feeds) { + feed.reset(); + } + return 0; + } catch (const std::exception &err) { + std::cerr << err.what() << "\n"; + return 1; + } +} diff --git a/tools/jotpluggler/app.h b/tools/jotpluggler/app.h new file mode 100644 index 00000000000..71f71f2d9f3 --- /dev/null +++ b/tools/jotpluggler/app.h @@ -0,0 +1,884 @@ +#pragma once + +#include "cereal/gen/cpp/log.capnp.h" +#include "imgui.h" +#include "tools/jotpluggler/dbc.h" +#include "tools/jotpluggler/util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ***** +// app options & entry point +// ***** + +struct Options { + std::string layout; + std::string route_name; + std::string data_dir; + std::string output_path; + std::string stream_address = "127.0.0.1"; + int width = 1600; + int height = 900; + bool show = false; + bool sync_load = false; + bool stream = false; + double stream_buffer_seconds = 30.0; +}; + +int run(const Options &options); + +// ***** +// sketch layout & route data +// ***** + +struct PlotRange { + bool valid = false; + double left = 0.0; + double right = 0.0; + double bottom = 0.0; + double top = 1.0; + bool has_y_limit_min = false; + bool has_y_limit_max = false; + double y_limit_min = 0.0; + double y_limit_max = 1.0; +}; + +struct CustomPythonSeries { + std::string linked_source; + std::vector additional_sources; + std::string globals_code; + std::string function_code; +}; + +struct Curve { + std::string name; + std::string label; + std::array color = {160, 170, 180}; + bool visible = true; + bool derivative = false; + double derivative_dt = 0.0; + double value_scale = 1.0; + double value_offset = 0.0; + bool runtime_only = false; + std::optional custom_python; + std::string runtime_error_message; + std::vector xs; + std::vector ys; +}; + +enum class PaneKind : uint8_t { + Plot, + Map, + Camera, +}; + +enum class CameraViewKind : uint8_t { + Road, + Driver, + WideRoad, + QRoad, +}; + +struct Pane { + PaneKind kind = PaneKind::Plot; + CameraViewKind camera_view = CameraViewKind::Road; + std::string title; + PlotRange range; + std::vector curves; +}; + +enum class SplitOrientation { + Horizontal, + Vertical, +}; + +struct WorkspaceNode { + bool is_pane = false; + int pane_index = -1; + SplitOrientation orientation = SplitOrientation::Horizontal; + std::vector sizes; + std::vector children; +}; + +struct WorkspaceTab { + std::string tab_name; + WorkspaceNode root; + std::vector panes; +}; + +struct RouteSeries { + std::string path; + std::vector times; + std::vector values; +}; + +struct CameraSegmentFile { + int segment = -1; + std::string path; +}; + +struct CameraFrameIndexEntry { + double timestamp = 0.0; + int segment = -1; + int decode_index = -1; + uint32_t frame_id = 0; +}; + +struct CameraFeedIndex { + std::vector segment_files; + std::vector entries; +}; + +enum class LogOrigin : uint8_t { + Log, + Android, + Alert, +}; + +struct LogEntry { + double mono_time = 0.0; + double boot_time = 0.0; + double wall_time = 0.0; + uint8_t level = 20; + std::string source; + std::string func; + std::string message; + std::string context; + LogOrigin origin = LogOrigin::Log; +}; + +struct EnumInfo { + std::vector names; +}; + +struct SeriesFormat { + int decimals = 3; + bool integer_like = false; + bool has_negative = false; + int digits_before = 1; + int total_width = 0; + char fmt[16] = "%7.3f"; +}; + +enum class CanServiceKind : uint8_t { + Can, + Sendcan, +}; + +struct CanMessageId { + CanServiceKind service = CanServiceKind::Can; + uint8_t bus = 0; + uint32_t address = 0; + + bool operator==(const CanMessageId &other) const { + return service == other.service && bus == other.bus && address == other.address; + } +}; + +struct CanMessageIdHash { + size_t operator()(const CanMessageId &id) const { + return (static_cast(id.service) << 40) + ^ (static_cast(id.bus) << 32) + ^ static_cast(id.address); + } +}; + +struct CanFrameSample { + double mono_time = 0.0; + uint16_t bus_time = 0; + std::string data; +}; + +struct LiveCanFrame { + double mono_time = 0.0; + uint8_t bus = 0; + uint32_t address = 0; + uint16_t bus_time = 0; + std::string data; +}; + +struct CanMessageData { + CanMessageId id; + std::vector samples; +}; + +struct TimelineEntry { + enum class Type : uint8_t { + None, + Engaged, + AlertInfo, + AlertWarning, + AlertCritical, + }; + + double start_time = 0.0; + double end_time = 0.0; + Type type = Type::None; +}; + +struct GpsPoint { + double time = 0.0; + double lat = 0.0; + double lon = 0.0; + float bearing = 0.0f; + TimelineEntry::Type type = TimelineEntry::Type::None; +}; + +struct GpsTrace { + std::vector points; + double min_lat = 0.0; + double max_lat = 0.0; + double min_lon = 0.0; + double max_lon = 0.0; +}; + +enum class LogSelector : uint8_t { + Auto, + RLog, + QLog, +}; + +struct RouteIdentifier { + std::string dongle_id; + std::string log_id; + int slice_begin = 0; + int slice_end = -1; + bool slice_explicit = false; + LogSelector selector = LogSelector::Auto; + bool selector_explicit = false; + int available_begin = 0; + int available_end = 0; + + bool empty() const { + return dongle_id.empty() || log_id.empty(); + } + + std::string canonical() const { + return empty() ? std::string() : dongle_id + "/" + log_id; + } + + std::string onebox() const { + return empty() ? std::string() : dongle_id + "|" + log_id; + } + + std::string display_slice() const { + const int begin = slice_explicit ? slice_begin : available_begin; + const int end = slice_explicit ? slice_end : available_end; + if (end < 0 || end == begin) { + return std::to_string(begin); + } + return std::to_string(begin) + ":" + std::to_string(end); + } + + char selector_char() const { + switch (selector) { + case LogSelector::RLog: return 'r'; + case LogSelector::QLog: return 'q'; + case LogSelector::Auto: + default: return 'a'; + } + } + + std::string full_spec() const { + if (empty()) return {}; + std::string spec = dongle_id + "/" + log_id; + if (slice_explicit) { + spec += "/"; + spec += display_slice(); + } + if (selector_explicit) { + spec += "/"; + spec.push_back(selector_char()); + } + return spec; + } +}; + +struct RouteData { + std::vector series; + std::vector paths; + std::vector roots; + std::vector can_messages; + CameraFeedIndex road_camera; + CameraFeedIndex driver_camera; + CameraFeedIndex wide_road_camera; + CameraFeedIndex qroad_camera; + GpsTrace gps_trace; + std::vector logs; + std::vector timeline; + std::unordered_map enum_info; + std::unordered_map series_formats; + std::string car_fingerprint; + std::string dbc_name; + RouteIdentifier route_id; + bool has_time_range = false; + double x_min = 0.0; + double x_max = 1.0; +}; + +struct StreamExtractBatch { + std::vector series; + std::vector can_messages; + std::vector logs; + std::vector timeline; + std::unordered_map enum_info; + std::string car_fingerprint; + std::string dbc_name; + bool has_time_offset = false; + double time_offset = 0.0; +}; + +struct SketchLayout { + std::vector tabs; + std::vector roots; + int current_tab_index = 0; +}; + +enum class RouteLoadStage { + Resolving, + DownloadingSegment, + ParsingSegment, + Finished, +}; + +struct RouteLoadProgress { + RouteLoadStage stage = RouteLoadStage::Resolving; + size_t segment_index = 0; + size_t segment_count = 0; + uint64_t current = 0; + uint64_t total = 0; + size_t segments_downloaded = 0; + size_t segments_parsed = 0; + size_t total_segments = 0; + uint64_t bytes_downloaded = 0; + int num_workers = 1; + std::string segment_name; +}; + +using RouteLoadProgressCallback = std::function; + +class StreamAccumulator { +public: + explicit StreamAccumulator(const std::string &dbc_name = {}, std::optional time_offset = std::nullopt); + ~StreamAccumulator(); + + StreamAccumulator(const StreamAccumulator &) = delete; + StreamAccumulator &operator=(const StreamAccumulator &) = delete; + + void setDbcName(const std::string &dbc_name); + void appendEvent(cereal::Event::Which which, kj::ArrayPtr data); + void appendCanFrames(CanServiceKind service, const std::vector &frames); + StreamExtractBatch takeBatch(); + const std::string &carFingerprint() const; + const std::string &dbc_name() const; + std::optional timeOffset() const; + +private: + struct Impl; + std::unique_ptr impl_; +}; + +SketchLayout load_sketch_layout(const std::filesystem::path &layout_path); +std::vector available_dbc_names(); +std::vector collect_route_roots_for_paths(const std::vector &paths); +std::optional load_dbc_by_name(const std::string &dbc_name); +std::vector decode_can_messages(const std::vector &can_messages, + const std::string &dbc_name, + std::unordered_map *enum_info = nullptr); +RouteData load_route_data(const std::string &route_name, + const std::string &data_dir = {}, + const std::string &dbc_name = {}, + const RouteLoadProgressCallback &progress = {}); +RouteIdentifier parse_route_identifier(std::string_view route_name); +void rebuild_gps_trace(RouteData *route_data); + +// ***** +// icons +// ***** + +namespace icon { +constexpr const char ARROW_DOWN_UP[] = "\xef\x84\xa7"; +constexpr const char ARROW_LEFT_RIGHT[] = "\xef\x84\xab"; +constexpr const char BAR_CHART[] = "\xef\x85\xbe"; +constexpr const char BOX_ARROW_UP_RIGHT[] = "\xef\x87\x85"; +constexpr const char CLIPBOARD[] = "\xef\x8a\x90"; +constexpr const char CLIPBOARD2[] = "\xef\x9c\xb3"; +constexpr const char DISTRIBUTE_HORIZONTAL[] = "\xef\x8c\x83"; +constexpr const char DISTRIBUTE_VERTICAL[] = "\xef\x8c\x84"; +constexpr const char FILE_EARMARK_IMAGE[] = "\xef\x8d\xad"; +constexpr const char FILES[] = "\xef\x8f\x82"; +constexpr const char INFO_CIRCLE[] = "\xef\x90\xb1"; +constexpr const char PALETTE[] = "\xef\x92\xb1"; +constexpr const char PLUS_SLASH_MINUS[] = "\xef\x9a\xaa"; +constexpr const char SAVE[] = "\xef\x94\xa5"; +constexpr const char SLIDERS[] = "\xef\x95\xab"; +constexpr const char TRASH[] = "\xef\x97\x9e"; +constexpr const char X_SQUARE[] = "\xef\x98\xa9"; +constexpr const char ZOOM_OUT[] = "\xef\x98\xad"; +} // namespace icon + +void icon_add_font(float size, bool merge = false, const ImFont *base_font = nullptr); +bool icon_menu_item(const char *glyph, + const char *label, + const char *shortcut = nullptr, + bool selected = false, + bool enabled = true); + +// ***** +// app session, UI state, & internal API +// ***** + +class AsyncRouteLoader; +class CameraFeedView; +class StreamPoller; +class MapDataManager; + +enum class SessionDataMode : uint8_t { + Route, + Stream, +}; + +enum class StreamSourceKind : uint8_t { + CerealLocal, + CerealRemote, +}; + +struct StreamSourceConfig { + StreamSourceKind kind = StreamSourceKind::CerealLocal; + std::string address = "127.0.0.1"; +}; + +struct BrowserNode { + std::string label; + std::string full_path; + std::vector children; +}; + +struct AppSession { + std::filesystem::path layout_path; + std::filesystem::path autosave_path; + std::string route_name; + std::string data_dir; + std::string dbc_override; + StreamSourceConfig stream_source; + double stream_buffer_seconds = 30.0; + SessionDataMode data_mode = SessionDataMode::Route; + RouteIdentifier route_id; + SketchLayout layout; + RouteData route_data; + std::unordered_map series_by_path; + std::vector browser_nodes; + std::unique_ptr route_loader; + std::unique_ptr stream_poller; + std::array, 4> pane_camera_feeds; + std::unique_ptr map_data; + bool async_route_loading = false; + double next_stream_custom_refresh_time = 0.0; + bool stream_paused = false; + std::optional stream_time_offset; +}; + +struct TabUiState { + struct MapPaneState { + bool initialized = false; + bool follow = false; + float zoom = 1.0f; + double center_lat = 0.0; + double center_lon = 0.0; + }; + + struct CameraPaneState { + bool fit_to_pane = true; + }; + + bool dock_needs_build = true; + int active_pane_index = 0; + int runtime_id = 0; + ImVec2 last_dockspace_size = ImVec2(0.0f, 0.0f); + std::vector map_panes; + std::vector camera_panes; +}; + +struct CustomSeriesEditorState { + bool open = false; + bool open_help = false; + bool request_select = false; + bool selected = false; + bool focus_name = false; + int selected_template = 0; + int selected_additional_source = -1; + std::string name; + std::string linked_source; + std::vector additional_sources; + std::string globals_code; + std::string function_code = "return value"; + std::string preview_label; + std::vector preview_xs; + std::vector preview_ys; + bool preview_is_result = false; +}; + +enum class LogTimeMode : uint8_t { + Route, + Boot, + WallClock, +}; + +struct LogsUiState { + bool selected = false; + bool request_select = false; + bool all_sources = true; + uint32_t enabled_levels_mask = 0b11110; + int expanded_index = -1; + std::string search; + std::vector selected_sources; + double last_auto_scroll_time = -1.0; + LogTimeMode time_mode = LogTimeMode::Route; +}; + +struct AxisLimitsEditorState { + bool open = false; + int pane_index = -1; + double x_min = 0.0; + double x_max = 1.0; + bool y_min_enabled = false; + bool y_max_enabled = false; + double y_min = 0.0; + double y_max = 1.0; +}; + +struct DbcEditorState { + bool open = false; + bool loaded = false; + std::string source_name; + std::filesystem::path source_path; + enum class SourceKind : uint8_t { + None, + Generated, + Opendbc, + }; + SourceKind source_kind = SourceKind::None; + std::string save_name; + std::string text; +}; + +enum class TimelineDragMode : uint8_t { + None, + ScrubCursor, + PanViewport, + ResizeLeft, + ResizeRight, +}; + +struct UndoStack { + static constexpr size_t kMaxHistory = 50; + + std::vector history; + int position = -1; + + void reset(const SketchLayout &layout) { + history.clear(); + history.push_back(layout); + position = 0; + } + + void push(const SketchLayout &layout) { + if (position < 0) { + reset(layout); + return; + } + if (position + 1 < static_cast(history.size())) { + history.resize(static_cast(position + 1)); + } + history.push_back(layout); + if (history.size() > kMaxHistory) { + history.erase(history.begin()); + } + position = static_cast(history.size()) - 1; + } + + bool can_undo() const { + return position > 0; + } + + bool can_redo() const { + return position >= 0 && position + 1 < static_cast(history.size()); + } + + const SketchLayout &undo() { + return history[static_cast(--position)]; + } + + const SketchLayout &redo() { + return history[static_cast(++position)]; + } +}; + +struct UiState { + bool open_open_route = false; + bool open_stream = false; + bool open_load_layout = false; + bool open_save_layout = false; + bool open_preferences = false; + bool open_find_signal = false; + bool request_close = false; + bool request_reset_layout = false; + bool request_save_layout = false; + bool request_new_tab = false; + bool request_duplicate_tab = false; + bool request_close_tab = false; + bool follow_latest = false; + bool has_shared_range = false; + bool has_tracker_time = false; + bool layout_dirty = false; + bool playback_loop = false; + bool playback_playing = false; + bool show_deprecated_fields = false; + bool show_fps_overlay = false; + bool fps_overlay_initialized = false; + bool suppress_range_side_effects = false; + bool browser_nodes_dirty = false; + int active_tab_index = 0; + int next_tab_runtime_id = 1; + int requested_tab_index = -1; + int rename_tab_index = -1; + bool focus_rename_tab_input = false; + std::vector tabs; + std::string route_buffer; + std::string stream_address_buffer; + std::string rename_tab_buffer; + std::string browser_filter; + std::string data_dir_buffer; + std::string load_layout_buffer; + std::string save_layout_buffer; + std::string find_signal_buffer; + std::string selected_browser_path; + std::vector selected_browser_paths; + std::string browser_selection_anchor; + std::string route_slice_buffer; + std::string error_text; + bool open_error_popup = false; + std::string status_text = "Ready"; + std::string route_copy_feedback_text; + double route_copy_feedback_until = 0.0; + bool editing_route_slice = false; + bool focus_route_slice_input = false; + StreamSourceKind stream_source_kind = StreamSourceKind::CerealLocal; + float sidebar_width = 320.0f; + double route_x_min = 0.0; + double route_x_max = 1.0; + double x_view_min = 0.0; + double x_view_max = 1.0; + double tracker_time = 0.0; + double playback_rate = 1.0; + double playback_step = 0.1; + double stream_buffer_seconds = 30.0; + TimelineDragMode timeline_drag_mode = TimelineDragMode::None; + double timeline_drag_anchor_time = 0.0; + double timeline_drag_anchor_x_min = 0.0; + double timeline_drag_anchor_x_max = 0.0; + AxisLimitsEditorState axis_limits; + DbcEditorState dbc_editor; + CustomSeriesEditorState custom_series; + LogsUiState logs; + UndoStack undo; +}; + +// app.cc public API + +const WorkspaceTab *app_active_tab(const SketchLayout &layout, const UiState &state); +WorkspaceTab *app_active_tab(SketchLayout *layout, const UiState &state); +TabUiState *app_active_tab_state(UiState *state); + +void app_push_mono_font(); +void app_pop_mono_font(); +bool app_add_curve_to_active_pane(AppSession *session, UiState *state, const std::string &path); + +std::string app_curve_display_name(const Curve &curve); +std::array app_next_curve_color(const Pane &pane); +const RouteSeries *app_find_route_series(const AppSession &session, const std::string &path); +void app_decimate_samples(const std::vector &xs_in, + const std::vector &ys_in, + int max_points, + std::vector *xs_out, + std::vector *ys_out); +std::optional app_sample_xy_value_at_time(const std::vector &xs, + const std::vector &ys, + bool stairs, + double tm); +void save_layout_json(const SketchLayout &layout, const std::filesystem::path &path); + +// ***** +// browser +// ***** + +void rebuild_route_index(AppSession *session); +void rebuild_browser_nodes(AppSession *session, UiState *state); +SeriesFormat compute_series_format(const std::vector &values, bool enum_like = false); +std::string format_display_value(double display_value, + const SeriesFormat &format, + const EnumInfo *enum_info); +std::vector decode_browser_drag_payload(std::string_view payload); +void collect_visible_leaf_paths(const BrowserNode &node, + const std::string &filter, + std::vector *out); +void draw_browser_node(AppSession *session, + const BrowserNode &node, + UiState *state, + const std::string &filter, + const std::vector &visible_paths); + +// ***** +// custom series +// ***** + +void open_custom_series_editor(UiState *state, const std::string &preferred_source = {}); +std::string preferred_custom_series_source(const Pane &pane); +void refresh_all_custom_curves(AppSession *session, UiState *state); +void draw_custom_series_editor(AppSession *session, UiState *state); + +// ***** +// logs +// ***** + +void draw_logs_tab(AppSession *session, UiState *state); + +// ***** +// map +// ***** + +void draw_map_pane(AppSession *session, UiState *state, Pane *pane, int pane_index); + +// ***** +// runtime (GLFW, async loaders, streaming, camera) +// ***** + +struct GLFWwindow; + +struct RouteLoadSnapshot { + bool active = false; + size_t total_segments = 0; + size_t segments_downloaded = 0; + size_t segments_parsed = 0; +}; + +struct StreamPollSnapshot { + bool active = false; + bool connected = false; + bool paused = false; + StreamSourceKind source_kind = StreamSourceKind::CerealLocal; + std::string source_label; + std::string dbc_name; + std::string car_fingerprint; + double buffer_seconds = 30.0; + uint64_t received_messages = 0; +}; + +class GlfwRuntime { +public: + explicit GlfwRuntime(const Options &options); + ~GlfwRuntime(); + + GlfwRuntime(const GlfwRuntime &) = delete; + GlfwRuntime &operator=(const GlfwRuntime &) = delete; + + GLFWwindow *window() const; + +private: + GLFWwindow *window_ = nullptr; +}; + +class ImGuiRuntime { +public: + explicit ImGuiRuntime(GLFWwindow *window); + ~ImGuiRuntime(); + + ImGuiRuntime(const ImGuiRuntime &) = delete; + ImGuiRuntime &operator=(const ImGuiRuntime &) = delete; +}; + +class TerminalRouteProgress { +public: + explicit TerminalRouteProgress(bool enabled); + ~TerminalRouteProgress(); + + TerminalRouteProgress(const TerminalRouteProgress &) = delete; + TerminalRouteProgress &operator=(const TerminalRouteProgress &) = delete; + + void update(const RouteLoadProgress &progress); + void finish(); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +class AsyncRouteLoader { +public: + explicit AsyncRouteLoader(bool enable_terminal_progress); + ~AsyncRouteLoader(); + + AsyncRouteLoader(const AsyncRouteLoader &) = delete; + AsyncRouteLoader &operator=(const AsyncRouteLoader &) = delete; + + void start(const std::string &route_name, const std::string &data_dir, const std::string &dbc_name); + RouteLoadSnapshot snapshot() const; + bool consume(RouteData *route_data, std::string *error_text); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +class StreamPoller { +public: + StreamPoller(); + ~StreamPoller(); + + StreamPoller(const StreamPoller &) = delete; + StreamPoller &operator=(const StreamPoller &) = delete; + + void start(const StreamSourceConfig &source, + double buffer_seconds, + const std::string &dbc_name, + std::optional time_offset = std::nullopt); + void setPaused(bool paused); + void stop(); + StreamPollSnapshot snapshot() const; + bool consume(StreamExtractBatch *batch, std::string *error_text); + +private: + struct Impl; + std::unique_ptr impl_; +}; + +class CameraFeedView { +public: + CameraFeedView(); + ~CameraFeedView(); + + CameraFeedView(const CameraFeedView &) = delete; + CameraFeedView &operator=(const CameraFeedView &) = delete; + + void setRouteData(const RouteData &route_data); + void setCameraIndex(const CameraFeedIndex &camera_index, CameraViewKind view); + void update(double tracker_time); + void draw(float width, bool loading); + void drawSized(ImVec2 size, bool loading, bool fit_to_pane = false); + +private: + struct Impl; + std::unique_ptr impl_; +}; diff --git a/tools/jotpluggler/browser.cc b/tools/jotpluggler/browser.cc new file mode 100644 index 00000000000..0d1b5a2c1ba --- /dev/null +++ b/tools/jotpluggler/browser.cc @@ -0,0 +1,465 @@ +#include "tools/jotpluggler/app.h" + +#include "imgui_internal.h" + +#include +#include +#include + +namespace { + +constexpr float BROWSER_VALUE_WIDTH = 88.0f; + +bool path_matches_filter(const std::string &path, const std::string &lower_filter) { + if (lower_filter.empty()) return true; + return lowercase_copy(path).find(lower_filter) != std::string::npos; +} + +void insert_browser_path(std::vector *nodes, const std::string &path) { + size_t start = 0; + while (start < path.size() && path[start] == '/') { + ++start; + } + std::vector parts; + while (start < path.size()) { + const size_t end = path.find('/', start); + parts.push_back(path.substr(start, end == std::string::npos ? std::string::npos : end - start)); + if (end == std::string::npos) break; + start = end + 1; + } + if (parts.empty()) { + return; + } + + std::vector *current_nodes = nodes; + std::string current_path; + for (size_t i = 0; i < parts.size(); ++i) { + if (!current_path.empty()) { + current_path += "/"; + } + current_path += parts[i]; + auto it = std::find_if(current_nodes->begin(), current_nodes->end(), + [&](const BrowserNode &node) { return node.label == parts[i]; }); + if (it == current_nodes->end()) { + current_nodes->push_back(BrowserNode{.label = parts[i]}); + it = std::prev(current_nodes->end()); + } + if (i + 1 == parts.size()) { + it->full_path = "/" + current_path; + } + current_nodes = &it->children; + } +} + +void sort_browser_nodes(std::vector *nodes) { + std::sort(nodes->begin(), nodes->end(), [](const BrowserNode &a, const BrowserNode &b) { + if (a.children.empty() != b.children.empty()) { + return !a.children.empty(); + } + return a.label < b.label; + }); + for (BrowserNode &node : *nodes) { + sort_browser_nodes(&node.children); + } +} + +std::vector build_browser_tree(const std::vector &paths) { + std::vector nodes; + for (const std::string &path : paths) { + insert_browser_path(&nodes, path); + } + sort_browser_nodes(&nodes); + return nodes; +} + +bool is_deprecated_browser_path(const std::string &path) { + return path.find("DEPRECATED") != std::string::npos; +} + +std::vector visible_browser_paths(const RouteData &route_data, bool show_deprecated_fields) { + if (show_deprecated_fields) return route_data.paths; + std::vector filtered; + filtered.reserve(route_data.paths.size()); + for (const std::string &path : route_data.paths) { + if (!is_deprecated_browser_path(path)) { + filtered.push_back(path); + } + } + return filtered; +} + +bool browser_selection_contains(const UiState &state, std::string_view path) { + return std::find(state.selected_browser_paths.begin(), state.selected_browser_paths.end(), path) + != state.selected_browser_paths.end(); +} + +std::vector browser_drag_paths(const UiState &state, const std::string &dragged_path) { + if (browser_selection_contains(state, dragged_path) && !state.selected_browser_paths.empty()) { + return state.selected_browser_paths; + } + return {dragged_path}; +} + +std::string encode_browser_drag_payload(const std::vector &paths) { + std::string payload; + for (size_t i = 0; i < paths.size(); ++i) { + if (i != 0) { + payload.push_back('\n'); + } + payload += paths[i]; + } + return payload; +} + +void set_browser_selection_single(UiState *state, const std::string &path) { + state->selected_browser_paths = {path}; + state->selected_browser_path = path; + state->browser_selection_anchor = path; +} + +void toggle_browser_selection(UiState *state, const std::string &path) { + auto it = std::find(state->selected_browser_paths.begin(), state->selected_browser_paths.end(), path); + if (it == state->selected_browser_paths.end()) { + state->selected_browser_paths.push_back(path); + } else { + state->selected_browser_paths.erase(it); + } + state->selected_browser_path = path; + state->browser_selection_anchor = path; + if (state->selected_browser_paths.empty()) { + state->selected_browser_path.clear(); + } +} + +void select_browser_range(UiState *state, const std::vector &visible_paths, const std::string &clicked_path) { + if (visible_paths.empty()) { + set_browser_selection_single(state, clicked_path); + return; + } + + const std::string anchor = state->browser_selection_anchor.empty() ? clicked_path : state->browser_selection_anchor; + const auto anchor_it = std::find(visible_paths.begin(), visible_paths.end(), anchor); + const auto clicked_it = std::find(visible_paths.begin(), visible_paths.end(), clicked_path); + if (clicked_it == visible_paths.end()) { + return; + } + if (anchor_it == visible_paths.end()) { + set_browser_selection_single(state, clicked_path); + return; + } + + const auto [begin_it, end_it] = std::minmax(anchor_it, clicked_it); + std::vector selected; + selected.reserve(static_cast(std::distance(begin_it, end_it)) + 1); + for (auto it = begin_it; it != end_it + 1; ++it) { + selected.push_back(*it); + } + state->selected_browser_paths = std::move(selected); + state->selected_browser_path = clicked_path; +} + +void prune_browser_selection(UiState *state, const std::vector &visible_paths) { + const std::unordered_set visible_set(visible_paths.begin(), visible_paths.end()); + auto is_visible = [&](const std::string &path) { + return visible_set.count(path) > 0; + }; + + state->selected_browser_paths.erase( + std::remove_if(state->selected_browser_paths.begin(), state->selected_browser_paths.end(), + [&](const std::string &path) { return !is_visible(path); }), + state->selected_browser_paths.end()); + + if (!state->selected_browser_path.empty() && !is_visible(state->selected_browser_path)) { + state->selected_browser_path.clear(); + } + if (!state->browser_selection_anchor.empty() && !is_visible(state->browser_selection_anchor)) { + state->browser_selection_anchor.clear(); + } + if (state->selected_browser_paths.empty()) { + state->selected_browser_path.clear(); + } else if (state->selected_browser_path.empty()) { + state->selected_browser_path = state->selected_browser_paths.back(); + } +} + +std::optional sample_route_series_value(const RouteSeries &series, double tm, bool stairs) { + return app_sample_xy_value_at_time(series.times, series.values, stairs, tm); +} + +std::string browser_series_value_text(const AppSession &session, const UiState &state, std::string_view path) { + auto it = session.series_by_path.find(std::string(path)); + if (it == session.series_by_path.end() || it->second == nullptr) return {}; + + const RouteSeries &series = *it->second; + if (series.values.empty()) return {}; + + const auto enum_it = session.route_data.enum_info.find(series.path); + const EnumInfo *enum_info = enum_it == session.route_data.enum_info.end() ? nullptr : &enum_it->second; + const bool stairs = enum_info != nullptr; + + std::optional value; + if (state.has_tracker_time) { + value = sample_route_series_value(series, state.tracker_time, stairs); + } else { + value = series.values.back(); + } + if (!value.has_value()) return {}; + + const auto display_it = session.route_data.series_formats.find(series.path); + const SeriesFormat display_info = display_it == session.route_data.series_formats.end() + ? compute_series_format(series.values, enum_info != nullptr) + : display_it->second; + + return format_display_value(*value, display_info, enum_info); +} + +bool browser_node_matches(const BrowserNode &node, const std::string &filter) { + if (filter.empty()) return true; + if (!node.full_path.empty() && path_matches_filter(node.full_path, filter)) { + return true; + } + for (const BrowserNode &child : node.children) { + if (browser_node_matches(child, filter)) return true; + } + return false; +} + +} // namespace + +namespace { + +int decimals_needed(double value) { + const double abs_value = std::abs(value); + if (abs_value < 1.0e-12) return 0; + for (int decimals = 0; decimals <= 6; ++decimals) { + const double scale = std::pow(10.0, decimals); + if (std::abs(abs_value * scale - std::round(abs_value * scale)) < 1.0e-6) { + return decimals; + } + } + return 6; +} + +void finalize_series_format(SeriesFormat *format) { + format->digits_before = std::max(format->digits_before, 1); + format->decimals = std::clamp(format->decimals, 0, 6); + format->integer_like = format->decimals == 0; + const int sign_width = format->has_negative ? 1 : 0; + const int dot_width = format->decimals > 0 ? 1 : 0; + format->total_width = sign_width + format->digits_before + dot_width + format->decimals; + std::snprintf(format->fmt, sizeof(format->fmt), "%%%d.%df", format->total_width, format->decimals); +} + +} // namespace + +SeriesFormat compute_series_format(const std::vector &values, bool enum_like) { + SeriesFormat format; + if (values.empty()) return format; + + const size_t step = std::max(1, values.size() / 256); + bool saw_finite = false; + bool all_integer = enum_like; + double min_value = 0.0; + double max_value = 0.0; + int max_needed_decimals = 0; + + for (size_t i = 0; i < values.size(); i += step) { + const double value = values[i]; + if (!std::isfinite(value)) continue; + if (!saw_finite) { + min_value = value; + max_value = value; + saw_finite = true; + } else { + min_value = std::min(min_value, value); + max_value = std::max(max_value, value); + } + if (std::abs(value - std::round(value)) > 1.0e-9) { + all_integer = false; + } + if (!all_integer) { + max_needed_decimals = std::max(max_needed_decimals, decimals_needed(value)); + } + } + + if (!saw_finite) return format; + + format.has_negative = min_value < 0.0; + const double peak = std::max(std::abs(min_value), std::abs(max_value)); + format.digits_before = peak < 1.0 ? 1 : static_cast(std::floor(std::log10(peak))) + 1; + + if (enum_like || all_integer) { + format.decimals = 0; + } else if (peak >= 1000.0) { + format.decimals = std::min(max_needed_decimals, 1); + } else if (peak >= 100.0) { + format.decimals = std::min(max_needed_decimals, 2); + } else { + format.decimals = std::min(max_needed_decimals, 4); + } + + finalize_series_format(&format); + return format; +} + +std::string format_display_value(double display_value, + const SeriesFormat &display_info, + const EnumInfo *enum_info) { + if (!std::isfinite(display_value)) return "---"; + if (enum_info != nullptr) { + const int idx = static_cast(std::llround(display_value)); + if (idx >= 0 && std::abs(display_value - static_cast(idx)) < 0.01 + && static_cast(idx) < enum_info->names.size() + && !enum_info->names[static_cast(idx)].empty()) { + return enum_info->names[static_cast(idx)]; + } + } + char buf[64] = {}; + std::snprintf(buf, sizeof(buf), display_info.fmt, display_value); + return buf; +} + +std::vector decode_browser_drag_payload(std::string_view payload) { + std::vector out; + size_t begin = 0; + while (begin <= payload.size()) { + const size_t end = payload.find('\n', begin); + const size_t length = (end == std::string_view::npos ? payload.size() : end) - begin; + if (length > 0) { + out.emplace_back(payload.substr(begin, length)); + } + if (end == std::string_view::npos) break; + begin = end + 1; + } + return out; +} + +void collect_visible_leaf_paths(const BrowserNode &node, + const std::string &filter, + std::vector *out) { + if (!browser_node_matches(node, filter)) { + return; + } + if (node.children.empty()) { + if (!node.full_path.empty()) { + out->push_back(node.full_path); + } + return; + } + for (const BrowserNode &child : node.children) { + collect_visible_leaf_paths(child, filter, out); + } +} + +void rebuild_browser_nodes(AppSession *session, UiState *state) { + const std::vector paths = visible_browser_paths(session->route_data, state->show_deprecated_fields); + session->browser_nodes = build_browser_tree(paths); + prune_browser_selection(state, paths); +} + +void rebuild_route_index(AppSession *session) { + session->series_by_path.clear(); + session->route_data.series_formats.clear(); + for (RouteSeries &series : session->route_data.series) { + session->series_by_path.emplace(series.path, &series); + const bool enum_like = session->route_data.enum_info.find(series.path) != session->route_data.enum_info.end(); + session->route_data.series_formats.emplace(series.path, compute_series_format(series.values, enum_like)); + } +} + +void draw_browser_node(AppSession *session, + const BrowserNode &node, + UiState *state, + const std::string &filter, + const std::vector &visible_paths) { + if (!browser_node_matches(node, filter)) { + return; + } + + if (node.children.empty()) { + const bool selected = browser_selection_contains(*state, node.full_path); + const std::string value_text = browser_series_value_text(*session, *state, node.full_path); + const ImGuiStyle &style = ImGui::GetStyle(); + const ImVec2 row_size(std::max(1.0f, ImGui::GetContentRegionAvail().x), ImGui::GetFrameHeight()); + ImGui::PushID(node.full_path.c_str()); + const bool clicked = ImGui::InvisibleButton("##browser_leaf", row_size); + const bool hovered = ImGui::IsItemHovered(); + const bool held = ImGui::IsItemActive(); + const ImRect rect(ImGui::GetItemRectMin(), ImGui::GetItemRectMax()); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + if (selected || hovered) { + const ImU32 bg = ImGui::GetColorU32(selected + ? (held ? ImGuiCol_HeaderActive : ImGuiCol_Header) + : ImGuiCol_HeaderHovered); + draw_list->AddRectFilled(rect.Min, rect.Max, bg, 0.0f); + } + + const float value_right = rect.Max.x - style.FramePadding.x; + const float value_left = value_right - (value_text.empty() ? 0.0f : BROWSER_VALUE_WIDTH); + const float label_left = rect.Min.x + style.FramePadding.x; + const float label_right = value_text.empty() + ? rect.Max.x - style.FramePadding.x + : std::max(label_left + 40.0f, value_left - 10.0f); + ImGui::RenderTextEllipsis(draw_list, + ImVec2(label_left, rect.Min.y + style.FramePadding.y), + ImVec2(label_right, rect.Max.y), + label_right, + node.label.c_str(), + nullptr, + nullptr); + if (!value_text.empty()) { + app_push_mono_font(); + ImGui::PushStyleColor(ImGuiCol_Text, selected ? color_rgb(70, 77, 86) : color_rgb(116, 124, 133)); + ImGui::RenderTextClipped(ImVec2(value_left, rect.Min.y + style.FramePadding.y), + ImVec2(value_right, rect.Max.y), + value_text.c_str(), + nullptr, + nullptr, + ImVec2(1.0f, 0.0f)); + ImGui::PopStyleColor(); + app_pop_mono_font(); + } + + if (clicked) { + const bool shift_down = ImGui::GetIO().KeyShift; + const bool ctrl_down = ImGui::GetIO().KeyCtrl || ImGui::GetIO().KeySuper; + if (shift_down) { + select_browser_range(state, visible_paths, node.full_path); + } else if (ctrl_down) { + toggle_browser_selection(state, node.full_path); + } else { + set_browser_selection_single(state, node.full_path); + } + } + if (hovered && ImGui::IsMouseDoubleClicked(0)) { + set_browser_selection_single(state, node.full_path); + app_add_curve_to_active_pane(session, state, node.full_path); + } + if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID)) { + const std::vector drag_paths = browser_drag_paths(*state, node.full_path); + const std::string payload = encode_browser_drag_payload(drag_paths); + ImGui::SetDragDropPayload("JOTP_BROWSER_PATHS", payload.c_str(), payload.size() + 1); + if (drag_paths.size() == 1) { + ImGui::TextUnformatted(drag_paths.front().c_str()); + } else { + ImGui::Text("%zu timeseries", drag_paths.size()); + ImGui::TextUnformatted(drag_paths.front().c_str()); + } + ImGui::EndDragDropSource(); + } + ImGui::PopID(); + return; + } + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_SpanAvailWidth; + if (!filter.empty()) { + flags |= ImGuiTreeNodeFlags_DefaultOpen; + } + const bool open = ImGui::TreeNodeEx(node.label.c_str(), flags); + if (open) { + for (const BrowserNode &child : node.children) { + draw_browser_node(session, child, state, filter, visible_paths); + } + ImGui::TreePop(); + } +} diff --git a/tools/jotpluggler/camera.cc b/tools/jotpluggler/camera.cc new file mode 100644 index 00000000000..24a35d87943 --- /dev/null +++ b/tools/jotpluggler/camera.cc @@ -0,0 +1,54 @@ +#include "tools/jotpluggler/camera.h" + +#include "imgui.h" +#include "imgui_internal.h" + +namespace { + +bool draw_camera_fit_toggle_overlay(bool fit_to_pane) { + const ImVec2 window_pos = ImGui::GetWindowPos(); + const ImVec2 content_min = ImGui::GetWindowContentRegionMin(); + const ImRect rect(ImVec2(window_pos.x + content_min.x + 8.0f, window_pos.y + content_min.y + 8.0f), + ImVec2(window_pos.x + content_min.x + 58.0f, window_pos.y + content_min.y + 28.0f)); + const bool hovered = ImGui::IsMouseHoveringRect(rect.Min, rect.Max, false); + const bool held = hovered && ImGui::IsMouseDown(ImGuiMouseButton_Left); + if (hovered) ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(rect.Min, rect.Max, hovered ? IM_COL32(255, 255, 255, 234) : IM_COL32(255, 255, 255, 214), 4.0f); + draw_list->AddRect(rect.Min, rect.Max, IM_COL32(184, 189, 196, 255), 4.0f, 0, 1.0f); + const ImRect box(ImVec2(rect.Min.x + 6.0f, rect.Min.y + 4.0f), ImVec2(rect.Min.x + 18.0f, rect.Min.y + 16.0f)); + draw_list->AddRect(box.Min, box.Max, IM_COL32(112, 120, 129, 255), 2.0f, 0, 1.0f); + if (fit_to_pane) { + draw_list->AddLine(ImVec2(box.Min.x + 2.5f, box.Min.y + 6.5f), ImVec2(box.Min.x + 5.5f, box.Max.y - 2.5f), IM_COL32(60, 111, 202, 255), 1.8f); + draw_list->AddLine(ImVec2(box.Min.x + 5.5f, box.Max.y - 2.5f), ImVec2(box.Max.x - 2.5f, box.Min.y + 2.5f), IM_COL32(60, 111, 202, 255), 1.8f); + } + draw_list->AddText(ImVec2(box.Max.x + 6.0f, rect.Min.y + 3.0f), IM_COL32(72, 79, 88, 255), "Fit"); + return hovered && !held && ImGui::IsMouseReleased(ImGuiMouseButton_Left); +} + +} // namespace + +void draw_camera_pane(AppSession *session, UiState *state, TabUiState *tab_state, int pane_index, const Pane &pane) { + CameraFeedView *feed = session->pane_camera_feeds[static_cast(pane.camera_view)].get(); + if (feed == nullptr) { + ImGui::TextDisabled("Camera unavailable"); + return; + } + + const bool fit_to_pane = tab_state != nullptr + && pane_index >= 0 + && pane_index < static_cast(tab_state->camera_panes.size()) + ? tab_state->camera_panes[static_cast(pane_index)].fit_to_pane + : true; + if (state->has_tracker_time) { + feed->update(state->tracker_time); + } + feed->drawSized(ImGui::GetContentRegionAvail(), session->async_route_loading, fit_to_pane); + if (tab_state != nullptr + && pane_index >= 0 + && pane_index < static_cast(tab_state->camera_panes.size()) + && draw_camera_fit_toggle_overlay(fit_to_pane)) { + tab_state->camera_panes[static_cast(pane_index)].fit_to_pane = !fit_to_pane; + } +} diff --git a/tools/jotpluggler/camera.h b/tools/jotpluggler/camera.h new file mode 100644 index 00000000000..666e335af89 --- /dev/null +++ b/tools/jotpluggler/camera.h @@ -0,0 +1,5 @@ +#pragma once + +#include "tools/jotpluggler/app.h" + +void draw_camera_pane(AppSession *session, UiState *state, TabUiState *tab_state, int pane_index, const Pane &pane); diff --git a/tools/jotpluggler/common.cc b/tools/jotpluggler/common.cc new file mode 100644 index 00000000000..9bd6c18ceaa --- /dev/null +++ b/tools/jotpluggler/common.cc @@ -0,0 +1,179 @@ +#include "tools/jotpluggler/common.h" + +#include +#include +#include + +namespace { + +std::string format_coord(const GpsPoint &point) { + return util::string_format("%.5f,%.5f", point.lat, point.lon); +} + +} // namespace + +const CameraViewSpec &camera_view_spec(CameraViewKind view) { + auto it = std::find_if(kCameraViewSpecs.begin(), kCameraViewSpecs.end(), [&](const CameraViewSpec &spec) { + return spec.view == view; + }); + return it != kCameraViewSpecs.end() ? *it : kCameraViewSpecs.front(); +} + +const CameraViewSpec *camera_view_spec_from_special_item(std::string_view item_id) { + auto it = std::find_if(kCameraViewSpecs.begin(), kCameraViewSpecs.end(), [&](const CameraViewSpec &spec) { + return item_id == spec.special_item_id; + }); + return it != kCameraViewSpecs.end() ? &*it : nullptr; +} + +const CameraViewSpec *camera_view_spec_from_layout_name(std::string_view layout_name) { + auto it = std::find_if(kCameraViewSpecs.begin(), kCameraViewSpecs.end(), [&](const CameraViewSpec &spec) { + return layout_name == spec.layout_name; + }); + return it != kCameraViewSpecs.end() ? &*it : nullptr; +} + +const SpecialItemSpec *special_item_spec(std::string_view item_id) { + auto it = std::find_if(kSpecialItemSpecs.begin(), kSpecialItemSpecs.end(), [&](const SpecialItemSpec &spec) { + return item_id == spec.id; + }); + return it != kSpecialItemSpecs.end() ? &*it : nullptr; +} + +const char *special_item_label(std::string_view item_id) { + const SpecialItemSpec *spec = special_item_spec(item_id); + return spec != nullptr ? spec->label : "Item"; +} + +bool pane_kind_is_special(PaneKind kind) { + return kind == PaneKind::Map || kind == PaneKind::Camera; +} + +bool is_default_special_title(std::string_view title) { + if (title == "Map") return true; + return std::any_of(kCameraViewSpecs.begin(), kCameraViewSpecs.end(), [&](const CameraViewSpec &spec) { + return title == spec.label; + }); +} + +CameraViewKind sidebar_preview_camera_view(const AppSession &session) { + return session.route_data.road_camera.entries.empty() && !session.route_data.qroad_camera.entries.empty() + ? CameraViewKind::QRoad + : CameraViewKind::Road; +} + +const std::filesystem::path &repo_root() { + static const std::filesystem::path root(JOTP_REPO_ROOT); + return root; +} + +ImU32 timeline_entry_color(TimelineEntry::Type type, float alpha) { + return timeline_entry_color(type, alpha, {111, 143, 175}); +} + +ImU32 timeline_entry_color(TimelineEntry::Type type, float alpha, std::array none_color) { + switch (type) { + case TimelineEntry::Type::Engaged: + return ImGui::GetColorU32(color_rgb(0, 163, 108, alpha)); + case TimelineEntry::Type::AlertInfo: + return ImGui::GetColorU32(color_rgb(255, 195, 0, alpha)); + case TimelineEntry::Type::AlertWarning: + case TimelineEntry::Type::AlertCritical: + return ImGui::GetColorU32(color_rgb(199, 0, 57, alpha)); + case TimelineEntry::Type::None: + default: + return ImGui::GetColorU32(color_rgb(none_color, alpha)); + } +} + +const char *timeline_entry_label(TimelineEntry::Type type) { + static constexpr const char *kLabels[] = { + "disengaged", + "engaged", + "alert info", + "alert warning", + "alert critical", + }; + const size_t index = static_cast(type); + return index < std::size(kLabels) ? kLabels[index] : kLabels[0]; +} + +TimelineEntry::Type timeline_type_at_time(const std::vector &timeline, double time_value) { + for (const TimelineEntry &entry : timeline) { + if (time_value >= entry.start_time && time_value <= entry.end_time) { + return entry.type; + } + } + return TimelineEntry::Type::None; +} + +std::string normalize_stream_address(std::string address) { + return is_local_stream_address(address) ? "127.0.0.1" : address; +} + +const char *stream_source_kind_label(StreamSourceKind kind) { + static constexpr const char *kLabels[] = { + "Local (MSGQ)", + "Remote (ZMQ)", + }; + const size_t index = static_cast(kind); + return index < std::size(kLabels) ? kLabels[index] : kLabels[0]; +} + +std::string stream_source_target_label(const StreamSourceConfig &source) { + switch (source.kind) { + case StreamSourceKind::CerealRemote: + return normalize_stream_address(source.address); + case StreamSourceKind::CerealLocal: + default: + return "127.0.0.1"; + } +} + +bool env_flag_enabled(const char *name, bool default_value) { + const char *raw = std::getenv(name); + if (raw == nullptr || raw[0] == '\0') { + return default_value; + } + const std::string value = lowercase_copy(util::strip(raw)); + return !(value == "0" || value == "false" || value == "no" || value == "off"); +} + +void open_external_url(std::string_view url) { +#ifdef __APPLE__ + const std::string command = "open " + shell_quote(url) + " &"; +#else + const std::string command = "xdg-open " + shell_quote(url) + " >/dev/null 2>&1 &"; +#endif + util::check_system(command); +} + +std::string route_useradmin_url(const RouteIdentifier &route_id) { + return route_id.empty() ? std::string() + : "https://useradmin.comma.ai/?onebox=" + route_id.dongle_id + "%7C" + route_id.log_id; +} + +std::string route_connect_url(const RouteIdentifier &route_id) { + return route_id.empty() ? std::string() + : "https://connect.comma.ai/" + route_id.canonical(); +} + +std::string route_google_maps_url(const GpsTrace &trace) { + if (trace.points.size() < 2) { + return {}; + } + + const std::string prefix = "https://www.google.com/maps/dir/?api=1&travelmode=driving&origin=" + + format_coord(trace.points.front()) + "&destination=" + format_coord(trace.points.back()); + for (size_t n = std::min(9, trace.points.size() > 2 ? trace.points.size() - 2 : 0); ; --n) { + std::string url = prefix; + if (n > 0) { + url += "&waypoints="; + for (size_t i = 0; i < n; ++i) { + if (i) url += "%7C"; + url += format_coord(trace.points[1 + ((trace.points.size() - 2) * (i + 1)) / (n + 1)]); + } + } + if (url.size() <= 1900 || n == 0) return url; + } +} diff --git a/tools/jotpluggler/common.h b/tools/jotpluggler/common.h new file mode 100644 index 00000000000..25b1f91e895 --- /dev/null +++ b/tools/jotpluggler/common.h @@ -0,0 +1,63 @@ +#pragma once + +#include "tools/jotpluggler/app.h" + +#include +#include + +struct CameraViewSpec { + CameraViewKind view = CameraViewKind::Road; + const char *label = ""; + const char *runtime_name = ""; + const char *layout_name = ""; + const char *special_item_id = ""; + CameraFeedIndex RouteData::*route_member = nullptr; +}; + +struct SpecialItemSpec { + const char *id = ""; + const char *label = ""; + PaneKind kind = PaneKind::Plot; + CameraViewKind camera_view = CameraViewKind::Road; +}; + +inline constexpr std::array kCameraViewSpecs = {{ + {CameraViewKind::Road, "Road Camera", "road", "road", "camera_road", &RouteData::road_camera}, + {CameraViewKind::Driver, "Driver Camera", "driver", "driver", "camera_driver", &RouteData::driver_camera}, + {CameraViewKind::WideRoad, "Wide Road Camera", "wide", "wide_road", "camera_wide_road", &RouteData::wide_road_camera}, + {CameraViewKind::QRoad, "qRoad Camera", "qroad", "qroad", "camera_qroad", &RouteData::qroad_camera}, +}}; + +inline constexpr std::array kSpecialItemSpecs = {{ + {"map", "Map", PaneKind::Map, CameraViewKind::Road}, + {kCameraViewSpecs[0].special_item_id, kCameraViewSpecs[0].label, PaneKind::Camera, kCameraViewSpecs[0].view}, + {kCameraViewSpecs[1].special_item_id, kCameraViewSpecs[1].label, PaneKind::Camera, kCameraViewSpecs[1].view}, + {kCameraViewSpecs[2].special_item_id, kCameraViewSpecs[2].label, PaneKind::Camera, kCameraViewSpecs[2].view}, + {kCameraViewSpecs[3].special_item_id, kCameraViewSpecs[3].label, PaneKind::Camera, kCameraViewSpecs[3].view}, +}}; + +const CameraViewSpec &camera_view_spec(CameraViewKind view); +const CameraViewSpec *camera_view_spec_from_special_item(std::string_view item_id); +const CameraViewSpec *camera_view_spec_from_layout_name(std::string_view layout_name); + +const SpecialItemSpec *special_item_spec(std::string_view item_id); +const char *special_item_label(std::string_view item_id); + +bool pane_kind_is_special(PaneKind kind); +bool is_default_special_title(std::string_view title); +CameraViewKind sidebar_preview_camera_view(const AppSession &session); +const std::filesystem::path &repo_root(); + +ImU32 timeline_entry_color(TimelineEntry::Type type, float alpha = 1.0f); +ImU32 timeline_entry_color(TimelineEntry::Type type, float alpha, std::array none_color); +const char *timeline_entry_label(TimelineEntry::Type type); +TimelineEntry::Type timeline_type_at_time(const std::vector &timeline, double time_value); +std::string normalize_stream_address(std::string address); +const char *stream_source_kind_label(StreamSourceKind kind); +std::string stream_source_target_label(const StreamSourceConfig &source); + +bool env_flag_enabled(const char *name, bool default_value = false); +void open_external_url(std::string_view url); +std::string route_useradmin_url(const RouteIdentifier &route_id); +std::string route_connect_url(const RouteIdentifier &route_id); +std::string route_google_maps_url(const GpsTrace &trace); diff --git a/tools/jotpluggler/custom_series.cc b/tools/jotpluggler/custom_series.cc new file mode 100644 index 00000000000..bd2a3f36d19 --- /dev/null +++ b/tools/jotpluggler/custom_series.cc @@ -0,0 +1,750 @@ +#include "tools/jotpluggler/app.h" +#include "tools/jotpluggler/common.h" + +#include "implot.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "third_party/json11/json11.hpp" + +namespace fs = std::filesystem; + +namespace { + +struct PythonEvalResult { + std::vector xs; + std::vector ys; +}; + +struct CustomSeriesTemplate { + const char *name; + const char *globals_code; + const char *function_code; + const char *preview_text; + int required_additional_sources; + const char *requirement_text; +}; + +void write_binary_vector(const fs::path &path, const std::vector &values) { + write_file_or_throw(path, values.data(), values.size() * sizeof(double)); +} + +std::vector read_binary_vector(const fs::path &path) { + const std::string raw = read_file_or_throw(path); + if (raw.size() % sizeof(double) != 0) { + throw std::runtime_error("Invalid binary series file: " + path.string()); + } + std::vector values(raw.size() / sizeof(double)); + if (!values.empty()) { + std::memcpy(values.data(), raw.data(), raw.size()); + } + return values; +} + +void write_text_file(const fs::path &path, std::string_view text) { + write_file_or_throw(path, text); +} + +fs::path create_custom_series_temp_dir() { + const auto stamp = std::chrono::steady_clock::now().time_since_epoch().count(); + const fs::path dir = fs::temp_directory_path() / ("jotpluggler_math_" + std::to_string(::getpid()) + "_" + std::to_string(stamp)); + fs::create_directories(dir); + return dir; +} + +void reset_custom_series_editor(CustomSeriesEditorState *editor) { + *editor = CustomSeriesEditorState{}; +} + +bool add_additional_source(CustomSeriesEditorState *editor, const std::string &path) { + if (path.empty() || path == editor->linked_source) return false; + if (std::find(editor->additional_sources.begin(), editor->additional_sources.end(), path) != editor->additional_sources.end()) { + return false; + } + editor->additional_sources.push_back(path); + return true; +} + +std::string next_custom_curve_name(const Pane &pane) { + std::set used; + for (const Curve &curve : pane.curves) { + if (!curve.label.empty()) { + used.insert(curve.label); + } + if (!curve.name.empty()) { + used.insert(curve.name); + } + } + for (int i = 1; i < 1000; ++i) { + const std::string candidate = "series" + std::to_string(i); + if (used.find(candidate) == used.end()) { + return candidate; + } + } + return "series"; +} + +Curve make_custom_curve(const Pane &pane, + const std::string &name, + const CustomPythonSeries &spec, + PythonEvalResult result) { + Curve curve; + curve.name = name; + curve.label = name; + curve.color = app_next_curve_color(pane); + curve.runtime_only = true; + curve.custom_python = spec; + curve.xs = std::move(result.xs); + curve.ys = std::move(result.ys); + return curve; +} + +bool upsert_custom_curve_in_pane(WorkspaceTab *tab, int pane_index, Curve curve) { + if (pane_index < 0 || pane_index >= static_cast(tab->panes.size())) { + return false; + } + Pane &pane = tab->panes[static_cast(pane_index)]; + for (Curve &existing : pane.curves) { + if (existing.runtime_only && existing.name == curve.name) { + existing.visible = true; + existing.label = curve.label; + existing.custom_python = curve.custom_python; + existing.xs = std::move(curve.xs); + existing.ys = std::move(curve.ys); + return false; + } + } + pane.curves.push_back(std::move(curve)); + return true; +} + +std::set collect_custom_series_paths(const CustomPythonSeries &spec, + std::string_view globals_code, + std::string_view function_code) { + std::set paths; + if (!spec.linked_source.empty()) { + paths.insert(spec.linked_source); + } + paths.insert(spec.additional_sources.begin(), spec.additional_sources.end()); + + static const std::regex kPathRegex(R"([tv]\(\s*["']([^"']+)["']\s*\))"); + const auto collect_from = [&](std::string_view code) { + std::string owned(code); + for (std::sregex_iterator it(owned.begin(), owned.end(), kPathRegex), end; it != end; ++it) { + paths.insert((*it)[1].str()); + } + }; + collect_from(globals_code); + collect_from(function_code); + return paths; +} + +PythonEvalResult evaluate_custom_python_series(const AppSession &session, + const CustomPythonSeries &spec) { + const std::set referenced_paths = + collect_custom_series_paths(spec, spec.globals_code, spec.function_code); + if (referenced_paths.empty()) throw std::runtime_error("No input series referenced. Set an input timeseries or reference route paths in code."); + + const fs::path temp_dir = create_custom_series_temp_dir(); + try { + const fs::path globals_path = temp_dir / "globals.py"; + const fs::path code_path = temp_dir / "code.py"; + const fs::path manifest_path = temp_dir / "manifest.json"; + const fs::path out_t_path = temp_dir / "result.t.bin"; + const fs::path out_v_path = temp_dir / "result.v.bin"; + + write_text_file(globals_path, spec.globals_code); + write_text_file(code_path, spec.function_code); + + json11::Json::array paths_json(session.route_data.paths.begin(), session.route_data.paths.end()); + json11::Json::array additional_json(spec.additional_sources.begin(), spec.additional_sources.end()); + json11::Json::array series_json; + size_t series_index = 0; + for (const std::string &path : referenced_paths) { + const RouteSeries *series = app_find_route_series(session, path); + if (series == nullptr || series->times.size() < 2 || series->times.size() != series->values.size()) { + throw std::runtime_error("Missing route series " + path); + } + const std::string prefix = "series_" + std::to_string(series_index++); + const fs::path time_path = temp_dir / (prefix + ".t.bin"); + const fs::path value_path = temp_dir / (prefix + ".v.bin"); + write_binary_vector(time_path, series->times); + write_binary_vector(value_path, series->values); + series_json.push_back(json11::Json::object{ + {"path", path}, {"t", time_path.string()}, {"v", value_path.string()}}); + } + const json11::Json manifest_json = json11::Json::object{ + {"paths", std::move(paths_json)}, + {"linked_source", spec.linked_source}, + {"additional_sources", std::move(additional_json)}, + {"series", std::move(series_json)}, + }; + write_text_file(manifest_path, manifest_json.dump()); + + const CommandResult process = run_process_capture_output({ + "python3", + (repo_root() / "tools" / "jotpluggler" / "math_eval.py").string(), + manifest_path.string(), + globals_path.string(), + code_path.string(), + out_t_path.string(), + out_v_path.string(), + }); + if (process.exit_code != 0) { + const std::string error_text = util::strip(process.output); + throw std::runtime_error(error_text.empty() ? "Python evaluation failed" : error_text); + } + + PythonEvalResult result; + result.xs = read_binary_vector(out_t_path); + result.ys = read_binary_vector(out_v_path); + if (result.xs.size() < 2 || result.xs.size() != result.ys.size()) { + throw std::runtime_error("Custom series returned invalid output"); + } + fs::remove_all(temp_dir); + return result; + } catch (...) { + std::error_code ignore_error; + fs::remove_all(temp_dir, ignore_error); + throw; + } +} + +void refresh_custom_curve_samples(AppSession *session, UiState *state, Curve *curve) { + if (!curve->custom_python.has_value()) { + return; + } + if (!session->route_data.has_time_range || session->route_data.series.empty()) { + curve->runtime_error_message.clear(); + curve->xs.clear(); + curve->ys.clear(); + return; + } + try { + PythonEvalResult result = evaluate_custom_python_series(*session, *curve->custom_python); + curve->runtime_error_message.clear(); + curve->xs = std::move(result.xs); + curve->ys = std::move(result.ys); + } catch (const std::exception &err) { + curve->xs.clear(); + curve->ys.clear(); + const std::string err_text = err.what(); + if (session->data_mode == SessionDataMode::Stream && util::starts_with(err_text, "Missing route series ")) { + curve->runtime_error_message = err_text; + return; + } + const std::string error_message = std::string("Failed to evaluate custom series \"") + + app_curve_display_name(*curve) + "\":\n\n" + err_text; + if (curve->runtime_error_message != error_message) { + curve->runtime_error_message = error_message; + state->error_text = error_message; + state->open_error_popup = true; + } + } +} + +const std::array &custom_series_templates() { + static constexpr std::array kTemplates = {{ + { + .name = "Derivative", + .globals_code = "", + .function_code = "return np.gradient(value, time)", + .preview_text = "return np.gradient(value, time)", + .required_additional_sources = 0, + .requirement_text = "", + }, + { + .name = "Difference", + .globals_code = "", + .function_code = "return value - v1", + .preview_text = "Requires one additional source timeseries.\n\nreturn value - v1", + .required_additional_sources = 1, + .requirement_text = "Difference requires one additional source timeseries for v1.", + }, + { + .name = "Smoothing", + .globals_code = "window = 20\nweights = np.ones(window) / window", + .function_code = "return np.convolve(value, weights, mode='same')", + .preview_text = "window = 20\nweights = np.ones(window) / window\n\nreturn np.convolve(value, weights, mode='same')", + .required_additional_sources = 0, + .requirement_text = "", + }, + { + .name = "Integral", + .globals_code = "", + .function_code = "dt = np.mean(np.diff(time))\nreturn np.cumsum(value) * dt", + .preview_text = "dt = np.mean(np.diff(time))\nreturn np.cumsum(value) * dt", + .required_additional_sources = 0, + .requirement_text = "", + }, + }}; + return kTemplates; +} + +void draw_custom_series_help_popup(CustomSeriesEditorState *editor) { + if (editor->open_help) { + ImGui::OpenPopup("Custom Series Help"); + editor->open_help = false; + } + if (!ImGui::BeginPopupModal("Custom Series Help", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + ImGui::TextUnformatted("Available variables"); + ImGui::Separator(); + ImGui::BulletText("np: numpy"); + ImGui::BulletText("t(path), v(path): timestamps and values for a route series"); + ImGui::BulletText("paths: all available route series paths"); + ImGui::BulletText("time, value: linked input timeseries"); + ImGui::BulletText("t1, v1, t2, v2, ...: additional source timeseries"); + ImGui::Spacing(); + ImGui::TextWrapped("Write either a single expression like \"return np.gradient(value, time)\" " + "or a multi-line Python body that returns an array or a (times, values) tuple."); + ImGui::Spacing(); + if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); +} + +void draw_custom_series_preview(const AppSession &session, CustomSeriesEditorState *editor) { + std::vector preview_xs; + std::vector preview_ys; + std::string preview_label = editor->preview_label; + if (editor->preview_is_result && editor->preview_xs.size() > 1 && editor->preview_xs.size() == editor->preview_ys.size()) { + preview_xs = editor->preview_xs; + preview_ys = editor->preview_ys; + if (preview_label.empty()) { + preview_label = "Result preview"; + } + } else if (!editor->linked_source.empty()) { + if (const RouteSeries *series = app_find_route_series(session, editor->linked_source); series != nullptr + && series->times.size() > 1 && series->times.size() == series->values.size()) { + preview_xs = series->times; + preview_ys = series->values; + preview_label = "Input preview (not result)"; + } + } + + if (!preview_xs.empty() && preview_xs.size() == preview_ys.size()) { + std::vector plot_xs; + std::vector plot_ys; + app_decimate_samples(preview_xs, preview_ys, 1200, &plot_xs, &plot_ys); + const double preview_x_min = preview_xs.front(); + const double preview_x_max = preview_xs.back() > preview_xs.front() + ? preview_xs.back() + : preview_xs.front() + 1e-6; + std::string plot_id = "##custom_series_preview"; + if (editor->preview_is_result) { + plot_id += "_result_"; + plot_id += editor->name.empty() ? preview_label : editor->name; + } else if (!editor->linked_source.empty()) { + plot_id += "_input_"; + plot_id += editor->linked_source; + } + ImGui::TextUnformatted(preview_label.c_str()); + if (!editor->linked_source.empty() && !editor->preview_is_result) { + ImGui::SameLine(); + ImGui::TextDisabled("%s", editor->linked_source.c_str()); + } + if (ImPlot::BeginPlot(plot_id.c_str(), + ImVec2(-1.0f, std::max(180.0f, ImGui::GetContentRegionAvail().y - 6.0f)), + ImPlotFlags_NoTitle | ImPlotFlags_NoMenus | ImPlotFlags_NoLegend)) { + ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_NoHighlight, + ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_NoHighlight | ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_RangeFit); + ImPlot::SetupAxisLimitsConstraints(ImAxis_X1, preview_x_min, preview_x_max); + ImPlot::SetupAxisLimits(ImAxis_X1, preview_x_min, preview_x_max, ImPlotCond_Once); + ImPlot::SetupAxisFormat(ImAxis_X1, "%.1f"); + ImPlot::SetupAxisFormat(ImAxis_Y1, "%.6g"); + ImPlotSpec spec; + spec.LineColor = color_rgb(35, 107, 180); + spec.LineWeight = 2.0f; + ImPlot::PlotLine("##custom_preview_line", plot_xs.data(), plot_ys.data(), static_cast(plot_xs.size()), spec); + ImPlot::EndPlot(); + } + } else { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 72.0f); + ImGui::PushStyleColor(ImGuiCol_Text, color_rgb(116, 124, 133)); + ImGui::TextWrapped("Choose an input timeseries or click Preview to evaluate the custom result."); + ImGui::PopStyleColor(); + } +} + +std::string custom_series_name_status(const Pane &pane, std::string_view name) { + const std::string trimmed = util::strip(std::string(name)); + if (trimmed.empty()) return "name required"; + if (!trimmed.empty() && trimmed.front() == '/') { + return "cannot start with /"; + } + for (const Curve &curve : pane.curves) { + if (curve.runtime_only && curve.name == trimmed) return "updates existing curve"; + } + return "new curve"; +} + +const CustomSeriesTemplate &selected_custom_series_template(const CustomSeriesEditorState &editor) { + const auto &templates = custom_series_templates(); + return templates[static_cast(std::clamp(editor.selected_template, 0, static_cast(templates.size()) - 1))]; +} + +bool custom_series_template_ready(const CustomSeriesEditorState &editor) { + const CustomSeriesTemplate &templ = selected_custom_series_template(editor); + return !editor.linked_source.empty() + && static_cast(editor.additional_sources.size()) >= templ.required_additional_sources; +} + +bool prepare_custom_series_spec(CustomSeriesEditorState *editor, + UiState *state, + bool require_name, + CustomPythonSeries *out_spec) { + editor->name = util::strip(editor->name); + editor->linked_source = util::strip(editor->linked_source); + for (std::string &path : editor->additional_sources) { + path = util::strip(path); + } + editor->additional_sources.erase( + std::remove_if(editor->additional_sources.begin(), editor->additional_sources.end(), + [&](const std::string &path) { return path.empty() || path == editor->linked_source; }), + editor->additional_sources.end()); + + if (require_name && editor->name.empty()) { + state->error_text = "Custom series name is required."; + state->open_error_popup = true; + return false; + } + if (require_name && !editor->name.empty() && editor->name.front() == '/') { + state->error_text = "Custom series names may not start with '/'."; + state->open_error_popup = true; + return false; + } + + *out_spec = CustomPythonSeries{ + .linked_source = editor->linked_source, + .additional_sources = editor->additional_sources, + .globals_code = editor->globals_code, + .function_code = editor->function_code, + }; + return true; +} + +bool preview_custom_series_editor(AppSession *session, UiState *state) { + CustomSeriesEditorState &editor = state->custom_series; + const CustomSeriesTemplate &templ = selected_custom_series_template(editor); + if (editor.linked_source.empty()) { + state->error_text = "Choose an input timeseries before previewing."; + state->open_error_popup = true; + state->status_text = "Custom series preview failed"; + return false; + } + if (static_cast(editor.additional_sources.size()) < templ.required_additional_sources) { + state->error_text = templ.requirement_text; + state->open_error_popup = true; + state->status_text = "Custom series preview failed"; + return false; + } + CustomPythonSeries spec; + if (!prepare_custom_series_spec(&editor, state, false, &spec)) return false; + + try { + PythonEvalResult result = evaluate_custom_python_series(*session, spec); + editor.preview_label = editor.name.empty() ? "Result preview" : editor.name; + editor.preview_xs = std::move(result.xs); + editor.preview_ys = std::move(result.ys); + editor.preview_is_result = true; + state->status_text = "Previewed custom series"; + return true; + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + state->status_text = "Custom series preview failed"; + return false; + } +} + +bool apply_custom_series_editor(AppSession *session, UiState *state) { + WorkspaceTab *tab = app_active_tab(&session->layout, *state); + TabUiState *tab_state = app_active_tab_state(state); + if (tab == nullptr || tab_state == nullptr) { + state->status_text = "No active pane"; + return false; + } + if (tab_state->active_pane_index < 0 || tab_state->active_pane_index >= static_cast(tab->panes.size())) { + state->status_text = "No active pane"; + return false; + } + + CustomSeriesEditorState &editor = state->custom_series; + CustomPythonSeries spec; + if (!prepare_custom_series_spec(&editor, state, true, &spec)) return false; + + try { + PythonEvalResult result = evaluate_custom_python_series(*session, spec); + const SketchLayout before_layout = session->layout; + Pane &pane = tab->panes[static_cast(tab_state->active_pane_index)]; + editor.preview_label = editor.name; + editor.preview_xs = result.xs; + editor.preview_ys = result.ys; + editor.preview_is_result = true; + const bool inserted = upsert_custom_curve_in_pane(tab, + tab_state->active_pane_index, + make_custom_curve(pane, editor.name, spec, std::move(result))); + state->undo.push(before_layout); + state->status_text = inserted ? "Created custom series " + editor.name + : "Updated custom series " + editor.name; + return true; + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + state->status_text = "Custom series failed"; + return false; + } +} + +} // namespace + +void open_custom_series_editor(UiState *state, const std::string &preferred_source) { + CustomSeriesEditorState &editor = state->custom_series; + if (!editor.open && editor.name.empty() && editor.linked_source.empty() && editor.function_code == "return value") { + editor.focus_name = true; + } + if (editor.linked_source.empty() && !preferred_source.empty()) { + editor.linked_source = preferred_source; + } + editor.open = true; + editor.request_select = true; +} + +std::string preferred_custom_series_source(const Pane &pane) { + for (const Curve &curve : pane.curves) { + if (!curve.name.empty() && curve.name.front() == '/') { + return curve.name; + } + if (curve.custom_python.has_value() && !curve.custom_python->linked_source.empty()) { + return curve.custom_python->linked_source; + } + } + return {}; +} + +void refresh_all_custom_curves(AppSession *session, UiState *state) { + for (WorkspaceTab &tab : session->layout.tabs) { + for (Pane &pane : tab.panes) { + for (Curve &curve : pane.curves) { + refresh_custom_curve_samples(session, state, &curve); + } + } + } +} + +void draw_editor_source_panel(UiState *state, CustomSeriesEditorState &editor) { + ImGui::TextWrapped("Input timeseries. Provides arguments time and value:"); + ImGui::SetNextItemWidth(-FLT_MIN); + input_text_string("##custom_linked_source", &editor.linked_source, ImGuiInputTextFlags_ReadOnly); + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("JOTP_BROWSER_PATH")) { + editor.linked_source = static_cast(payload->Data); + editor.additional_sources.erase( + std::remove(editor.additional_sources.begin(), editor.additional_sources.end(), editor.linked_source), + editor.additional_sources.end()); + editor.preview_is_result = false; + } + ImGui::EndDragDropTarget(); + } + if (ImGui::Button("Use Selected", ImVec2(120.0f, 0.0f)) && !state->selected_browser_path.empty()) { + editor.linked_source = state->selected_browser_path; + editor.additional_sources.erase( + std::remove(editor.additional_sources.begin(), editor.additional_sources.end(), editor.linked_source), + editor.additional_sources.end()); + editor.preview_is_result = false; + } + ImGui::SameLine(); + if (ImGui::Button("Clear", ImVec2(120.0f, 0.0f))) { + editor.linked_source.clear(); + editor.preview_is_result = false; + } + + ImGui::Spacing(); + ImGui::TextUnformatted("Additional source timeseries:"); + ImGui::SameLine(); + const CustomSeriesTemplate &tmpl = selected_custom_series_template(editor); + if (tmpl.required_additional_sources > 0) { + const bool ready = static_cast(editor.additional_sources.size()) >= tmpl.required_additional_sources; + ImGui::TextColored(ready ? color_rgb(58, 126, 73) : color_rgb(180, 122, 44), "%s", tmpl.requirement_text); + } + ImGui::SameLine(); + ImGui::BeginDisabled(editor.selected_additional_source < 0 + || editor.selected_additional_source >= static_cast(editor.additional_sources.size())); + if (ImGui::Button("Remove Selected", ImVec2(140.0f, 0.0f)) + && editor.selected_additional_source >= 0 + && editor.selected_additional_source < static_cast(editor.additional_sources.size())) { + editor.additional_sources.erase(editor.additional_sources.begin() + + static_cast(editor.selected_additional_source)); + editor.selected_additional_source = editor.additional_sources.empty() + ? -1 : std::clamp(editor.selected_additional_source, 0, static_cast(editor.additional_sources.size()) - 1); + editor.preview_is_result = false; + } + ImGui::EndDisabled(); + + if (ImGui::BeginChild("##custom_additional_sources", ImVec2(0.0f, 156.0f), true)) { + if (ImGui::BeginTable("##custom_additional_table", 2, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("id", ImGuiTableColumnFlags_WidthFixed, 42.0f); + ImGui::TableSetupColumn("path", ImGuiTableColumnFlags_WidthStretch); + for (size_t i = 0; i < editor.additional_sources.size(); ++i) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("v%zu", i + 1); + ImGui::TableNextColumn(); + if (ImGui::Selectable(editor.additional_sources[i].c_str(), + editor.selected_additional_source == static_cast(i), + ImGuiSelectableFlags_SpanAllColumns)) { + editor.selected_additional_source = static_cast(i); + } + } + ImGui::EndTable(); + } + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("JOTP_BROWSER_PATH")) { + if (add_additional_source(&editor, static_cast(payload->Data))) + editor.preview_is_result = false; + } + ImGui::EndDragDropTarget(); + } + } + ImGui::EndChild(); + if (ImGui::Button("Add Selected", ImVec2(120.0f, 0.0f))) { + for (const std::string &path : state->selected_browser_paths) { + if (add_additional_source(&editor, path)) editor.preview_is_result = false; + } + } + + ImGui::Spacing(); + ImGui::SeparatorText("Function library"); + const auto &templates = custom_series_templates(); + if (ImGui::BeginChild("##custom_series_template_list", ImVec2(0.0f, 132.0f), true)) { + for (size_t i = 0; i < templates.size(); ++i) { + if (ImGui::Selectable(templates[i].name, editor.selected_template == static_cast(i), + ImGuiSelectableFlags_AllowDoubleClick)) { + editor.selected_template = static_cast(i); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + editor.globals_code = templates[i].globals_code; + editor.function_code = templates[i].function_code; + editor.preview_is_result = false; + } + } + } + } + ImGui::EndChild(); + if (ImGui::Button("Use Selected Example")) { + const auto &sel = selected_custom_series_template(editor); + editor.globals_code = sel.globals_code; + editor.function_code = sel.function_code; + editor.preview_is_result = false; + } + ImGui::Spacing(); + ImGui::TextDisabled("Preview"); + ImGui::BeginChild("##custom_series_template_preview", ImVec2(0.0f, 0.0f), true); + ImGui::TextUnformatted(selected_custom_series_template(editor).preview_text); + ImGui::EndChild(); +} + +void draw_editor_code_panel(CustomSeriesEditorState &editor, const Pane *active_pane) { + const std::string name_status = active_pane != nullptr ? custom_series_name_status(*active_pane, editor.name) : "no active pane"; + ImGui::TextUnformatted("New name:"); + ImGui::SameLine(); + const bool name_error = name_status == "name required" || name_status == "cannot start with /"; + ImGui::TextColored(name_error ? color_rgb(200, 72, 64) : color_rgb(58, 126, 73), "%s", name_status.c_str()); + if (editor.focus_name) { ImGui::SetKeyboardFocusHere(); editor.focus_name = false; } + ImGui::SetNextItemWidth(-FLT_MIN); + input_text_string("##custom_series_name", &editor.name, ImGuiInputTextFlags_AutoSelectAll); + + ImGui::Spacing(); + ImGui::SeparatorText("Global variables"); + ImGui::SameLine(); + if (ImGui::SmallButton("Help")) editor.open_help = true; + const float globals_h = std::max(96.0f, ImGui::GetContentRegionAvail().y * 0.28f); + if (input_text_multiline_string("##custom_series_globals", &editor.globals_code, + ImVec2(-FLT_MIN, globals_h), ImGuiInputTextFlags_AllowTabInput)) + editor.preview_is_result = false; + + ImGui::Spacing(); + ImGui::TextUnformatted("def calc(time, value):"); + const float func_h = std::max(180.0f, ImGui::GetContentRegionAvail().y - 16.0f); + if (input_text_multiline_string("##custom_series_function", &editor.function_code, + ImVec2(-FLT_MIN, func_h), ImGuiInputTextFlags_AllowTabInput)) + editor.preview_is_result = false; +} + +void draw_custom_series_editor(AppSession *session, UiState *state) { + CustomSeriesEditorState &editor = state->custom_series; + if (!editor.open) return; + + WorkspaceTab *tab = app_active_tab(&session->layout, *state); + TabUiState *tab_state = app_active_tab_state(state); + Pane *active_pane = (tab && tab_state && tab_state->active_pane_index >= 0 + && tab_state->active_pane_index < static_cast(tab->panes.size())) + ? &tab->panes[static_cast(tab_state->active_pane_index)] : nullptr; + if (editor.focus_name && active_pane && editor.name.empty()) + editor.name = next_custom_curve_name(*active_pane); + + draw_custom_series_help_popup(&editor); + + if (ImGui::BeginTabBar("##custom_series_tabs")) { + if (ImGui::BeginTabItem("Single Function")) { + const float footer_height = ImGui::GetFrameHeightWithSpacing() * 2.0f + 10.0f; + if (ImGui::BeginChild("##custom_series_body", + ImVec2(0.0f, std::max(1.0f, ImGui::GetContentRegionAvail().y - footer_height)), false)) { + if (ImGui::BeginChild("##custom_series_preview_child", + ImVec2(0.0f, std::max(200.0f, ImGui::GetContentRegionAvail().y * 0.28f)), true)) + draw_custom_series_preview(*session, &editor); + ImGui::EndChild(); + ImGui::Spacing(); + + if (ImGui::BeginTable("##custom_series_editor_table", 2, + ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp, + ImVec2(0.0f, std::max(1.0f, ImGui::GetContentRegionAvail().y)))) { + ImGui::TableSetupColumn("left", ImGuiTableColumnFlags_WidthFixed, 320.0f); + ImGui::TableSetupColumn("right", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextColumn(); + if (ImGui::BeginChild("##custom_series_left", ImVec2(0.0f, 0.0f), false)) + draw_editor_source_panel(state, editor); + ImGui::EndChild(); + ImGui::TableNextColumn(); + if (ImGui::BeginChild("##custom_series_right", ImVec2(0.0f, 0.0f), false)) + draw_editor_code_panel(editor, active_pane); + ImGui::EndChild(); + ImGui::EndTable(); + } + } + ImGui::EndChild(); + + ImGui::Spacing(); + if (ImGui::Button("New", ImVec2(120.0f, 0.0f))) { + reset_custom_series_editor(&editor); + if (!state->selected_browser_path.empty()) editor.linked_source = state->selected_browser_path; + editor.open = true; + editor.focus_name = true; + } + ImGui::SameLine(); + ImGui::BeginDisabled(!custom_series_template_ready(editor)); + if (ImGui::Button("Preview Result", ImVec2(120.0f, 0.0f))) + preview_custom_series_editor(session, state); + ImGui::EndDisabled(); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled) && !custom_series_template_ready(editor)) { + if (editor.linked_source.empty()) ImGui::SetTooltip("Choose an input timeseries first."); + else ImGui::SetTooltip("%s", selected_custom_series_template(editor).requirement_text); + } + ImGui::SameLine(); + if (ImGui::Button("Apply", ImVec2(120.0f, 0.0f))) apply_custom_series_editor(session, state); + ImGui::SameLine(); + if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) { editor.open = false; editor.request_select = false; } + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } +} diff --git a/tools/jotpluggler/dbc.h b/tools/jotpluggler/dbc.h new file mode 100644 index 00000000000..d7c5461502b --- /dev/null +++ b/tools/jotpluggler/dbc.h @@ -0,0 +1,400 @@ +#pragma once + +#include "common/util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dbc { + + +struct ValueDescriptionEntry { + double value = 0.0; + std::string text; +}; + +struct Signal { + enum class Type { + Normal = 0, + Multiplexed, + Multiplexor, + }; + + Type type = Type::Normal; + std::string name; + int start_bit = 0; + int msb = 0; + int lsb = 0; + int size = 0; + double factor = 1.0; + double offset = 0.0; + double min = 0.0; + double max = 0.0; + bool is_signed = false; + bool is_little_endian = false; + std::string unit; + std::string comment; + std::string receiver_name; + int multiplex_value = 0; + int multiplexor_index = -1; + std::vector value_descriptions; +}; + +struct Message { + uint32_t address = 0; + std::string name; + uint32_t size = 0; + std::string comment; + std::string transmitter; + std::vector signals; + int multiplexor_index = -1; + + const std::vector &getSignals() const { return signals; } +}; + +class Database { +public: + Database() = default; + explicit Database(const std::filesystem::path &path); + static Database fromContent(const std::string &content, const std::string &filename = ""); + + const Message *message(uint32_t address) const; + const std::unordered_map &messages() const { return messages_; } + std::vector enumNames(const Signal &signal) const; + +private: + void parse(const std::string &content, const std::string &filename); + void parseBo(const std::string &line, int line_number, Message **current_message); + void parseSg(const std::string &line, int line_number, Message *current_message); + void parseVal(const std::string &line, int line_number); + void parseCmBo(const std::string &line, int line_number); + void parseCmSg(const std::string &line, int line_number); + void finalize(); + + std::string filename_; + std::unordered_map messages_; +}; + +void updateMsbLsb(Signal *signal); +double rawSignalValue(const Signal &signal, const uint8_t *data, size_t data_size); +std::optional signalValue(const Signal &signal, const Message &message, const uint8_t *data, size_t data_size); + +namespace { + +std::string unescape_dbc_string(std::string text) { + size_t pos = 0; + while ((pos = text.find("\\\"", pos)) != std::string::npos) { + text.replace(pos, 2, "\""); + ++pos; + } + return text; +} + +int flip_bit_pos(int start_bit) { + return 8 * (start_bit / 8) + 7 - start_bit % 8; +} + +std::string read_multiline_statement(std::istream &stream, std::string statement, int *line_number) { + static const std::regex statement_end(R"(\"\s*;\s*$)"); + while (true) { + const std::string trimmed = util::strip(statement); + if (std::regex_search(trimmed, statement_end)) { + return trimmed; + } + + std::string next_line; + if (!std::getline(stream, next_line)) { + return trimmed; + } + statement += "\n"; + statement += next_line; + ++(*line_number); + } +} + +} // namespace + +inline void updateMsbLsb(Signal *signal) { + if (signal->is_little_endian) { + signal->lsb = signal->start_bit; + signal->msb = signal->start_bit + signal->size - 1; + } else { + signal->lsb = flip_bit_pos(flip_bit_pos(signal->start_bit) + signal->size - 1); + signal->msb = signal->start_bit; + } +} + +inline double rawSignalValue(const Signal &signal, const uint8_t *data, size_t data_size) { + const int msb_byte = signal.msb / 8; + if (msb_byte >= static_cast(data_size)) return 0.0; + + const int lsb_byte = signal.lsb / 8; + uint64_t val = 0; + if (msb_byte == lsb_byte) { + val = (data[msb_byte] >> (signal.lsb & 7)) & ((1ULL << signal.size) - 1); + } else { + int bits = signal.size; + int i = msb_byte; + const int step = signal.is_little_endian ? -1 : 1; + while (i >= 0 && i < static_cast(data_size) && bits > 0) { + const int msb = (i == msb_byte) ? signal.msb & 7 : 7; + const int lsb = (i == lsb_byte) ? signal.lsb & 7 : 0; + const int nbits = msb - lsb + 1; + val = (val << nbits) | ((data[i] >> lsb) & ((1ULL << nbits) - 1)); + bits -= nbits; + i += step; + } + } + + if (signal.is_signed && (val & (1ULL << (signal.size - 1)))) { + val |= ~((1ULL << signal.size) - 1); + } + + return static_cast(val) * signal.factor + signal.offset; +} + +[[noreturn]] inline void parse_error(const std::string &filename, int line_number, const std::string &message, const std::string &line) { + std::ostringstream out; + out << "[" << filename << ":" << line_number << "] " << message << ": " << line; + throw std::runtime_error(out.str()); +} + +inline Database::Database(const std::filesystem::path &path) { + const std::string content = util::read_file(path.string()); + if (content.empty() && !std::filesystem::exists(path)) { + throw std::runtime_error("Failed to open DBC " + path.string()); + } + parse(content, path.filename().string()); +} + +inline Database Database::fromContent(const std::string &content, const std::string &filename) { + Database db; + db.parse(content, filename); + return db; +} + +inline const Message *Database::message(uint32_t address) const { + auto it = messages_.find(address); + return it == messages_.end() ? nullptr : &it->second; +} + +inline std::vector Database::enumNames(const Signal &signal) const { + if (signal.value_descriptions.empty()) return {}; + int max_index = -1; + for (const auto &entry : signal.value_descriptions) { + const double rounded = std::round(entry.value); + if (std::abs(entry.value - rounded) > 1e-6 || rounded < 0.0 || rounded > 512.0) return {}; + max_index = std::max(max_index, static_cast(rounded)); + } + if (max_index < 0) return {}; + std::vector names(static_cast(max_index + 1)); + for (const auto &entry : signal.value_descriptions) { + names[static_cast(std::llround(entry.value))] = entry.text; + } + return names; +} + +inline void Database::parse(const std::string &content, const std::string &filename) { + filename_ = filename; + messages_.clear(); + std::istringstream stream(content); + std::string raw_line; + Message *current_message = nullptr; + int line_number = 0; + while (std::getline(stream, raw_line)) { + ++line_number; + std::string line = util::strip(raw_line); + if (line.empty()) continue; + if (util::starts_with(line, "BO_ ")) { + parseBo(line, line_number, ¤t_message); + } else if (util::starts_with(line, "SG_ ")) { + if (current_message == nullptr) { + parse_error(filename, line_number, "Signal without current message", line); + } + parseSg(line, line_number, current_message); + } else if (util::starts_with(line, "VAL_ ")) { + parseVal(line, line_number); + } else if (util::starts_with(line, "CM_ BO_")) { + parseCmBo(read_multiline_statement(stream, raw_line, &line_number), line_number); + } else if (util::starts_with(line, "CM_ SG_")) { + parseCmSg(read_multiline_statement(stream, raw_line, &line_number), line_number); + } + } + finalize(); +} + +inline void Database::parseBo(const std::string &line, int line_number, Message **current_message) { + static const std::regex pattern(R"(^BO_\s+(\w+)\s+(\w+)\s*:\s*(\w+)\s+(\w+)\s*$)"); + std::smatch match; + if (!std::regex_match(line, match, pattern)) { + parse_error("", line_number, "Invalid BO_ line format", line); + } + uint32_t address = static_cast(std::stoul(match[1].str(), nullptr, 0)); + if (messages_.find(address) != messages_.end()) { + parse_error(filename_, line_number, "Duplicate message address", line); + } + Message &message = messages_[address]; + message.address = address; + message.name = match[2].str(); + message.size = static_cast(std::stoul(match[3].str(), nullptr, 0)); + message.transmitter = match[4].str(); + message.signals.clear(); + message.multiplexor_index = -1; + *current_message = &message; +} + +inline void Database::parseSg(const std::string &line, int line_number, Message *current_message) { + static const std::regex multiplex_pattern(R"(^SG_\s+(\w+)\s+(\w+)\s*:\s*(\d+)\|(\d+)@(\d)([+-])\s+\(([0-9.+\-eE]+),([0-9.+\-eE]+)\)\s+\[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\]\s+\"(.*)\"\s+(.*)$)"); + static const std::regex normal_pattern(R"(^SG_\s+(\w+)\s*:\s*(\d+)\|(\d+)@(\d)([+-])\s+\(([0-9.+\-eE]+),([0-9.+\-eE]+)\)\s+\[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\]\s+\"(.*)\"\s+(.*)$)"); + + std::smatch match; + Signal signal; + int offset = 0; + if (std::regex_match(line, match, normal_pattern)) { + offset = 0; + } else if (std::regex_match(line, match, multiplex_pattern)) { + offset = 1; + const std::string indicator = match[2].str(); + if (indicator == "M") { + if (std::any_of(current_message->signals.begin(), current_message->signals.end(), [](const Signal &existing) { + return existing.type == Signal::Type::Multiplexor; + })) { + parse_error(filename_, line_number, "Multiple multiplexor", line); + } + signal.type = Signal::Type::Multiplexor; + } else if (!indicator.empty() && indicator.front() == 'm') { + signal.type = Signal::Type::Multiplexed; + signal.multiplex_value = std::stoi(indicator.substr(1)); + } else { + parse_error("", line_number, "Invalid multiplex indicator", line); + } + } else { + parse_error("", line_number, "Invalid SG_ line format", line); + } + + signal.name = match[1].str(); + if (std::any_of(current_message->signals.begin(), current_message->signals.end(), [&](const Signal &existing) { + return existing.name == signal.name; + })) { + parse_error(filename_, line_number, "Duplicate signal name", line); + } + signal.start_bit = std::stoi(match[2 + offset].str()); + signal.size = std::stoi(match[3 + offset].str()); + signal.is_little_endian = match[4 + offset].str() == "1"; + signal.is_signed = match[5 + offset].str() == "-"; + signal.factor = std::stod(match[6 + offset].str()); + signal.offset = std::stod(match[7 + offset].str()); + signal.min = std::stod(match[8 + offset].str()); + signal.max = std::stod(match[9 + offset].str()); + signal.unit = match[10 + offset].str(); + signal.receiver_name = util::strip(match[11 + offset].str()); + updateMsbLsb(&signal); + current_message->signals.push_back(std::move(signal)); +} + +inline void Database::parseVal(const std::string &line, int line_number) { + static const std::regex prefix(R"(^VAL_\s+(\w+)\s+(\w+)\s+(.*);$)"); + std::smatch match; + if (!std::regex_match(line, match, prefix)) { + parse_error("", line_number, "Invalid VAL_ line format", line); + } + + const uint32_t address = static_cast(std::stoul(match[1].str(), nullptr, 0)); + auto msg_it = messages_.find(address); + if (msg_it == messages_.end()) { + return; + } + auto sig_it = std::find_if(msg_it->second.signals.begin(), msg_it->second.signals.end(), [&](const Signal &signal) { + return signal.name == match[2].str(); + }); + if (sig_it == msg_it->second.signals.end()) { + return; + } + + static const std::regex entry_pattern(R"(([+-]?\d+(?:\.\d+)?)\s+\"((?:[^\"\\]|\\.)*)\")"); + const std::string defs = match[3].str(); + for (std::sregex_iterator it(defs.begin(), defs.end(), entry_pattern), end; it != end; ++it) { + sig_it->value_descriptions.push_back(ValueDescriptionEntry{ + .value = std::stod((*it)[1].str()), + .text = (*it)[2].str(), + }); + } +} + +inline void Database::parseCmBo(const std::string &line, int line_number) { + static const std::regex pattern(R"(^CM_\s+BO_\s*(\w+)\s*\"((?:[^\"\\]|\\.|[\r\n])*)\"\s*;\s*$)"); + std::smatch match; + if (!std::regex_match(line, match, pattern)) { + parse_error(filename_, line_number, "Invalid message comment format", line); + } + const uint32_t address = static_cast(std::stoul(match[1].str(), nullptr, 0)); + auto it = messages_.find(address); + if (it != messages_.end()) { + it->second.comment = unescape_dbc_string(match[2].str()); + } +} + +inline void Database::parseCmSg(const std::string &line, int line_number) { + static const std::regex pattern(R"(^CM_\s+SG_\s*(\w+)\s*(\w+)\s*\"((?:[^\"\\]|\\.|[\r\n])*)\"\s*;\s*$)"); + std::smatch match; + if (!std::regex_match(line, match, pattern)) { + parse_error(filename_, line_number, "Invalid signal comment format", line); + } + + const uint32_t address = static_cast(std::stoul(match[1].str(), nullptr, 0)); + auto msg_it = messages_.find(address); + if (msg_it == messages_.end()) return; + + auto sig_it = std::find_if(msg_it->second.signals.begin(), msg_it->second.signals.end(), [&](const Signal &signal) { + return signal.name == match[2].str(); + }); + if (sig_it != msg_it->second.signals.end()) { + sig_it->comment = unescape_dbc_string(match[3].str()); + } +} + +inline void Database::finalize() { + for (auto &[_, message] : messages_) { + std::sort(message.signals.begin(), message.signals.end(), [](const Signal &left, const Signal &right) { + return std::tie(right.type, left.multiplex_value, left.start_bit, left.name) + < std::tie(left.type, right.multiplex_value, right.start_bit, right.name); + }); + message.multiplexor_index = -1; + for (size_t i = 0; i < message.signals.size(); ++i) { + if (message.signals[i].type == Signal::Type::Multiplexor) { + message.multiplexor_index = static_cast(i); + break; + } + } + for (Signal &signal : message.signals) { + signal.multiplexor_index = signal.type == Signal::Type::Multiplexed ? message.multiplexor_index : -1; + if (signal.type == Signal::Type::Multiplexed && signal.multiplexor_index < 0) { + signal.type = Signal::Type::Normal; + signal.multiplex_value = 0; + } + } + } +} + +inline std::optional signalValue(const Signal &signal, const Message &message, const uint8_t *data, size_t data_size) { + if (signal.multiplexor_index >= 0) { + const Signal &multiplexor = message.signals[static_cast(signal.multiplexor_index)]; + const double mux_value = rawSignalValue(multiplexor, data, data_size); + if (std::llround(mux_value) != signal.multiplex_value) return std::nullopt; + } + return rawSignalValue(signal, data, data_size); +} + +} // namespace dbc diff --git a/tools/jotpluggler/generated_dbcs/.gitignore b/tools/jotpluggler/generated_dbcs/.gitignore new file mode 100644 index 00000000000..d6b7ef32c84 --- /dev/null +++ b/tools/jotpluggler/generated_dbcs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/tools/jotpluggler/icons.cc b/tools/jotpluggler/icons.cc new file mode 100644 index 00000000000..9507090e036 --- /dev/null +++ b/tools/jotpluggler/icons.cc @@ -0,0 +1,24 @@ +#include "tools/jotpluggler/app.h" +#include "tools/jotpluggler/common.h" + +#include + +void icon_add_font(float size, bool merge, const ImFont *base_font) { + const std::filesystem::path ttf = repo_root() / "third_party" / "bootstrap" / "bootstrap-icons.ttf"; + ImGuiIO &io = ImGui::GetIO(); + ImFontConfig config; + config.MergeMode = merge; + config.GlyphMinAdvanceX = size; + if (base_font != nullptr) { + ImFontBaked *baked = const_cast(base_font)->GetFontBaked(size); + const float base_center = baked != nullptr ? (baked->Ascent + baked->Descent) * 0.5f : size * 0.5f; + config.GlyphOffset.y = std::round(size * 0.5f - base_center); + } + static const ImWchar ranges[] = {0xF000, 0xF8FF, 0}; + io.Fonts->AddFontFromFileTTF(ttf.c_str(), size, &config, ranges); +} + +bool icon_menu_item(const char *glyph, const char *label, const char *shortcut, bool selected, bool enabled) { + assert(glyph != nullptr && glyph[0] != '\0'); + return ImGui::MenuItem(util::string_format("%s %s", glyph, label).c_str(), shortcut, selected, enabled); +} diff --git a/tools/jotpluggler/internal.h b/tools/jotpluggler/internal.h new file mode 100644 index 00000000000..22a5c1dd959 --- /dev/null +++ b/tools/jotpluggler/internal.h @@ -0,0 +1,166 @@ +#pragma once + +#include "tools/jotpluggler/common.h" +#include "tools/jotpluggler/map.h" + +#include +#include +#include + +struct GLFWwindow; + +enum class PaneDropZone { + Center, + Left, + Right, + Top, + Bottom, +}; + +enum class PaneMenuActionKind { + None, + OpenAxisLimits, + OpenCustomSeries, + SplitLeft, + SplitRight, + SplitTop, + SplitBottom, + ResetView, + ResetHorizontal, + ResetVertical, + Clear, + Close, +}; + +struct PaneMenuAction { + PaneMenuActionKind kind = PaneMenuActionKind::None; + int pane_index = -1; +}; + +struct PaneCurveDragPayload { + int tab_index = -1; + int pane_index = -1; + int curve_index = -1; +}; + +struct PaneDropAction { + PaneDropZone zone = PaneDropZone::Center; + int target_pane_index = -1; + bool from_browser = false; + std::vector browser_paths; + std::string special_item_id; + PaneCurveDragPayload curve_ref; +}; + +inline constexpr float SIDEBAR_WIDTH = 320.0f; +inline constexpr float SIDEBAR_MIN_WIDTH = 220.0f; +inline constexpr float SIDEBAR_MAX_WIDTH = 520.0f; +inline constexpr float TIMELINE_BAR_HEIGHT = 14.0f; +inline constexpr float STATUS_BAR_HEIGHT = 52.0f; +inline constexpr double MIN_HORIZONTAL_ZOOM_SECONDS = 2.0; + +struct UiMetrics { + float width = 0.0f; + float height = 0.0f; + float top_offset = 0.0f; + float sidebar_width = SIDEBAR_WIDTH; + float content_x = 0.0f; + float content_y = 0.0f; + float content_w = 0.0f; + float content_h = 0.0f; + float status_bar_y = 0.0f; +}; + +std::filesystem::path resolve_layout_path(const std::string &layout_arg); +std::filesystem::path autosave_path_for_layout(const std::filesystem::path &layout_path); +std::vector available_layout_names(); + +SketchLayout make_empty_layout(); +void cancel_rename_tab(UiState *state); +void sync_ui_state(UiState *state, const SketchLayout &layout); +void sync_route_buffers(UiState *state, const AppSession &session); +void sync_stream_buffers(UiState *state, const AppSession &session); +void sync_layout_buffers(UiState *state, const AppSession &session); +void mark_all_docks_dirty(UiState *state); +void clear_layout_autosave(const AppSession &session); +bool autosave_layout(AppSession *session, UiState *state); +bool apply_axis_limits_editor(AppSession *session, UiState *state); +void open_axis_limits_editor(const AppSession &session, UiState *state, int pane_index); +void persist_shared_range_to_tab(WorkspaceTab *tab, const UiState &state); +void clear_pane_vertical_limits(Pane *pane); + +void refresh_replaced_layout_ui(AppSession *session, UiState *state, bool mark_docks); +void start_new_layout(AppSession *session, UiState *state, const std::string &status_text = "New untitled layout"); +void apply_dbc_override_change(AppSession *session, UiState *state, const std::string &dbc_override); + +void app_push_bold_font(); +void app_pop_bold_font(); +void draw_vertical_splitter(const char *id, float height, float min_left, float max_left, float *left_width); +void draw_right_splitter(const char *id, float height, float min_right, float max_right, float *right_width); +bool draw_horizontal_splitter(const char *id, float width, float min_top, float max_top, float *top_height); +void draw_payload_bytes(std::string_view data, const std::string *prev_data = nullptr); +void draw_payload_preview_boxes(const char *id, std::string_view data, const std::string *prev_data, float max_width); +void draw_signal_sparkline(const AppSession &session, + const UiState &state, + std::string_view signal_path, + bool selected, + ImVec2 size = ImVec2(0.0f, 24.0f)); +ImU32 mix_color(ImU32 a, ImU32 b, float t); +void draw_empty_panel(const char *title, const char *message); + +UiMetrics compute_ui_metrics(const ImVec2 &size, float top_offset, float sidebar_width); +void draw_sidebar(AppSession *session, const UiMetrics &ui, UiState *state, bool show_camera_feed); +void draw_workspace(AppSession *session, const UiMetrics &ui, UiState *state); +void draw_pane_windows(AppSession *session, UiState *state); + +// plot.cc +void draw_plot(const AppSession &session, Pane *pane, UiState *state); +bool draw_pane_close_button_overlay(); +void draw_pane_frame_overlay(); +std::optional draw_pane_context_menu(const WorkspaceTab &tab, int pane_index); +bool curve_has_samples(const AppSession &session, const Curve &curve); +bool curve_has_local_samples(const Curve &curve); +std::string app_curve_display_name(const Curve &curve); +bool mark_layout_dirty(AppSession *session, UiState *state); + +const RouteSeries *app_find_route_series(const AppSession &session, const std::string &path); +void sync_camera_feeds(AppSession *session); +void apply_route_data(AppSession *session, UiState *state, RouteData route_data); +bool apply_undo(AppSession *session, UiState *state); +bool apply_redo(AppSession *session, UiState *state); +bool infer_stream_follow_state(const UiState &state, const AppSession &session); +void ensure_shared_range(UiState *state, const AppSession &session); +void clamp_shared_range(UiState *state, const AppSession &session); +void reset_shared_range(UiState *state, const AppSession &session); +void update_follow_range(UiState *state, const AppSession &session); +void advance_playback(UiState *state, const AppSession &session); +void step_tracker(UiState *state, double direction); +std::string dbc_combo_label(const AppSession &session); +const char *log_selector_name(LogSelector selector); +const char *log_selector_description(LogSelector selector); +std::string format_cache_bytes(uint64_t bytes); +MapCacheStats directory_cache_stats(const std::filesystem::path &root); +float draw_main_menu_bar(AppSession *session, UiState *state); + +bool reset_layout(AppSession *session, UiState *state); +bool reload_layout(AppSession *session, UiState *state, const std::string &layout_arg); +bool save_layout(AppSession *session, UiState *state, const std::string &layout_path); +void rebuild_session_route_data(AppSession *session, UiState *state, + const RouteLoadProgressCallback &progress = {}); +void stop_stream_session(AppSession *session, UiState *state, bool preserve_data = true); +bool start_stream_session(AppSession *session, + UiState *state, + const StreamSourceConfig &source, + double buffer_seconds, + bool preserve_existing_data = false); +void start_async_route_load(AppSession *session, UiState *state); +void poll_async_route_load(AppSession *session, UiState *state); +bool reload_session(AppSession *session, UiState *state, const std::string &route_name, const std::string &data_dir); +void draw_popups(AppSession *session, UiState *state); + +void draw_status_bar(const AppSession &session, const UiMetrics &ui, UiState *state); +void draw_sidebar_resizer(const UiMetrics &ui, UiState *state); + +void apply_stream_batch(AppSession *session, UiState *state, StreamExtractBatch batch); + +void render_frame(GLFWwindow *window, AppSession *session, UiState *state, const std::filesystem::path *capture_path); diff --git a/tools/jotpluggler/layout.cc b/tools/jotpluggler/layout.cc new file mode 100644 index 00000000000..8a58ef7cd6b --- /dev/null +++ b/tools/jotpluggler/layout.cc @@ -0,0 +1,704 @@ +#include "tools/jotpluggler/internal.h" +#include "system/hardware/hw.h" + +#include + +namespace fs = std::filesystem; + +namespace { + +enum class ModalAction { + None, + Primary, + Secondary, +}; + +struct FindSignalMatch { + const std::string *path = nullptr; + int score = 0; +}; + +struct DbcEditorSource { + fs::path path; + DbcEditorState::SourceKind kind = DbcEditorState::SourceKind::None; +}; + +StreamSourceConfig stream_source_config_from_ui(const UiState &state) { + StreamSourceConfig source; + source.kind = state.stream_source_kind; + source.address = util::strip(state.stream_address_buffer); + if (source.kind == StreamSourceKind::CerealLocal) { + source.address = "127.0.0.1"; + } else { + source.address = normalize_stream_address(std::move(source.address)); + } + return source; +} + +void open_queued_popup(bool &flag, const char *name) { + if (flag) { + ImGui::OpenPopup(name); + flag = false; + } +} + +ModalAction draw_modal_action_row(const char *primary_label, + const char *secondary_label = "Cancel", + float width = 120.0f) { + if (ImGui::Button(primary_label, ImVec2(width, 0.0f))) { + return ModalAction::Primary; + } + ImGui::SameLine(); + if (ImGui::Button(secondary_label, ImVec2(width, 0.0f))) { + return ModalAction::Secondary; + } + return ModalAction::None; +} + +std::vector find_signal_matches(const AppSession &session, std::string_view query) { + std::vector matches; + if (query.empty()) { + return matches; + } + const std::string needle = lowercase_copy(query); + for (const std::string &path : session.route_data.paths) { + const std::string hay = lowercase_copy(path); + const size_t pos = hay.find(needle); + if (pos == std::string::npos) { + continue; + } + const size_t slash = path.find_last_of('/'); + const std::string_view label = slash == std::string::npos ? std::string_view(path) : std::string_view(path).substr(slash + 1); + int score = static_cast(pos * 8 + path.size()); + if (lowercase_copy(label) == needle) score -= 60; + if (util::starts_with(hay, needle)) score -= 30; + matches.push_back({.path = &path, .score = score}); + } + std::sort(matches.begin(), matches.end(), [](const FindSignalMatch &a, const FindSignalMatch &b) { + return std::tie(a.score, *a.path) < std::tie(b.score, *b.path); + }); + if (matches.size() > 200) { + matches.resize(200); + } + return matches; +} + +bool open_find_signal_result(UiState *state, const std::string &path) { + state->selected_browser_paths = {path}; + state->selected_browser_path = path; + state->browser_selection_anchor = path; + state->status_text = "Selected signal " + path; + return true; +} + +void draw_open_route_popup(AppSession *session, UiState *state) { + if (!ImGui::BeginPopupModal("Open Route", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + ImGui::TextUnformatted("Load a route into the current layout."); + ImGui::Separator(); + input_text_string("Route", &state->route_buffer); + input_text_string("Data Dir", &state->data_dir_buffer); + ImGui::Spacing(); + switch (draw_modal_action_row("Load")) { + case ModalAction::Primary: + reload_session(session, state, state->route_buffer, state->data_dir_buffer); + ImGui::CloseCurrentPopup(); + break; + case ModalAction::Secondary: + sync_route_buffers(state, *session); + ImGui::CloseCurrentPopup(); + break; + case ModalAction::None: + break; + } + ImGui::EndPopup(); +} + +void draw_stream_popup(AppSession *session, UiState *state) { + if (!ImGui::BeginPopupModal("Live Stream", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + + ImGui::TextUnformatted("Connect to a live source."); + ImGui::Separator(); + if (ImGui::RadioButton("Local (MSGQ)", state->stream_source_kind == StreamSourceKind::CerealLocal)) { + state->stream_source_kind = StreamSourceKind::CerealLocal; + } + if (ImGui::RadioButton("Remote (ZMQ)", state->stream_source_kind == StreamSourceKind::CerealRemote)) { + state->stream_source_kind = StreamSourceKind::CerealRemote; + } + + if (state->stream_source_kind == StreamSourceKind::CerealRemote) { + input_text_string("Address", &state->stream_address_buffer); + } + ImGui::InputDouble("Buffer (seconds)", &state->stream_buffer_seconds, 0.0, 0.0, "%.0f"); + ImGui::Spacing(); + switch (draw_modal_action_row("Connect")) { + case ModalAction::Primary: { + const StreamSourceConfig source = stream_source_config_from_ui(*state); + if (start_stream_session(session, state, source, state->stream_buffer_seconds, false)) { + ImGui::CloseCurrentPopup(); + } + break; + } + case ModalAction::Secondary: + sync_stream_buffers(state, *session); + ImGui::CloseCurrentPopup(); + break; + case ModalAction::None: + break; + } + ImGui::EndPopup(); +} + +void draw_load_layout_popup(AppSession *session, UiState *state) { + if (!ImGui::BeginPopupModal("Load Layout", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + ImGui::TextUnformatted("Load a JotPlugger JSON layout."); + ImGui::Separator(); + input_text_string("Layout", &state->load_layout_buffer); + ImGui::Spacing(); + switch (draw_modal_action_row("Load")) { + case ModalAction::Primary: + if (reload_layout(session, state, state->load_layout_buffer)) { + ImGui::CloseCurrentPopup(); + } + break; + case ModalAction::Secondary: + sync_layout_buffers(state, *session); + ImGui::CloseCurrentPopup(); + break; + case ModalAction::None: + break; + } + ImGui::EndPopup(); +} + +void draw_save_layout_popup(AppSession *session, UiState *state) { + if (!ImGui::BeginPopupModal("Save Layout", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + ImGui::TextUnformatted("Save the current workspace as a JotPlugger JSON layout."); + ImGui::Separator(); + input_text_string("Layout", &state->save_layout_buffer); + ImGui::Spacing(); + switch (draw_modal_action_row("Save")) { + case ModalAction::Primary: + if (save_layout(session, state, state->save_layout_buffer)) { + ImGui::CloseCurrentPopup(); + } + break; + case ModalAction::Secondary: + sync_layout_buffers(state, *session); + ImGui::CloseCurrentPopup(); + break; + case ModalAction::None: + break; + } + ImGui::EndPopup(); +} + +void draw_preferences_popup(AppSession *session, UiState *state) { + if (!ImGui::BeginPopupModal("Preferences", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + if (session->map_data) { + const MapCacheStats map_cache = session->map_data->cacheStats(); + const MapCacheStats download_cache = directory_cache_stats(Path::download_cache_root()); + ImGui::TextUnformatted("Map"); + ImGui::Separator(); + ImGui::Text("Map cache: %s in %zu file%s", + format_cache_bytes(map_cache.bytes).c_str(), + map_cache.files, + map_cache.files == 1 ? "" : "s"); + if (ImGui::Button("Clear Map Cache", ImVec2(120.0f, 0.0f))) { + session->map_data->clearCache(); + state->status_text = "Cleared map cache"; + } + ImGui::Spacing(); + ImGui::TextUnformatted("comma Download Cache"); + ImGui::Separator(); + ImGui::Text("Download cache: %s in %zu file%s", + format_cache_bytes(download_cache.bytes).c_str(), + download_cache.files, + download_cache.files == 1 ? "" : "s"); + ImGui::TextDisabled("%s", Path::download_cache_root().c_str()); + ImGui::Spacing(); + } + if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); +} + +void draw_find_signal_popup(AppSession *session, UiState *state) { + if (!ImGui::BeginPopupModal("Find Signal", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + ImGui::TextUnformatted("Search decoded signals across the loaded route."); + ImGui::Separator(); + ImGui::SetNextItemWidth(560.0f); + input_text_with_hint_string("##find_signal_query", "Search signal path or name...", &state->find_signal_buffer); + if (ImGui::IsWindowAppearing()) { + ImGui::SetKeyboardFocusHere(-1); + } + const std::vector matches = find_signal_matches(*session, state->find_signal_buffer); + ImGui::Spacing(); + ImGui::TextDisabled("%zu match%s", matches.size(), matches.size() == 1 ? "" : "es"); + if (ImGui::BeginChild("##find_signal_results", ImVec2(760.0f, 360.0f), true)) { + for (const FindSignalMatch &match : matches) { + const std::string &path = *match.path; + const size_t slash = path.find_last_of('/'); + const std::string_view label = slash == std::string::npos ? std::string_view(path) : std::string_view(path).substr(slash + 1); + if (ImGui::Selectable((std::string(label) + "##" + path).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) { + if (open_find_signal_result(state, path)) { + ImGui::CloseCurrentPopup(); + } + } + ImGui::SameLine(280.0f); + ImGui::TextDisabled("%s", path.c_str()); + } + } + ImGui::EndChild(); + ImGui::Spacing(); + if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); +} + +std::string default_dbc_template() { + return "VERSION \"\"\n\nNS_ :\nBS_:\nBU_: XXX\n"; +} + +DbcEditorSource resolve_dbc_editor_source(const std::string &dbc_name) { + const fs::path generated_dbc_dir = repo_root() / "tools" / "jotpluggler" / "generated_dbcs"; + const std::array candidates = {{ + {.path = repo_root() / "opendbc" / "dbc" / (dbc_name + ".dbc"), .kind = DbcEditorState::SourceKind::Opendbc}, + {.path = generated_dbc_dir / (dbc_name + ".dbc"), .kind = DbcEditorState::SourceKind::Generated}, + }}; + for (const DbcEditorSource &candidate : candidates) { + if (fs::exists(candidate.path)) { + return candidate; + } + } + return {}; +} + +void load_dbc_editor_state(const AppSession &session, UiState *state) { + DbcEditorState &editor = state->dbc_editor; + const std::string dbc_name = !session.dbc_override.empty() ? session.dbc_override : session.route_data.dbc_name; + editor.source_name = dbc_name.empty() ? "untitled" : dbc_name; + editor.source_path.clear(); + editor.source_kind = DbcEditorState::SourceKind::None; + if (dbc_name.empty()) { + editor.save_name = "custom_can"; + editor.text = default_dbc_template(); + } else { + const DbcEditorSource source = resolve_dbc_editor_source(dbc_name); + editor.source_kind = source.kind; + editor.source_path = source.path; + editor.text = source.path.empty() ? default_dbc_template() : read_file_or_throw(source.path); + editor.save_name = source.kind == DbcEditorState::SourceKind::Generated ? dbc_name : dbc_name + "_edited"; + } + editor.loaded = true; +} + +bool ensure_dbc_editor_loaded(const AppSession &session, UiState *state) { + if (!state->dbc_editor.loaded) { + try { + load_dbc_editor_state(session, state); + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + return false; + } + } + return true; +} + +bool save_dbc_editor_contents(AppSession *session, UiState *state) { + DbcEditorState &editor = state->dbc_editor; + editor.save_name = util::strip(editor.save_name); + if (editor.save_name.empty()) { + state->error_text = "DBC name cannot be empty"; + state->open_error_popup = true; + return false; + } + if (editor.source_kind == DbcEditorState::SourceKind::Opendbc && editor.save_name == editor.source_name) { + state->error_text = "Save edited opendbc files under a new name"; + state->open_error_popup = true; + return false; + } + try { + dbc::Database::fromContent(editor.text, editor.save_name + ".dbc"); + const fs::path generated_dbc_dir = repo_root() / "tools" / "jotpluggler" / "generated_dbcs"; + fs::create_directories(generated_dbc_dir); + const fs::path output = generated_dbc_dir / (editor.save_name + ".dbc"); + write_file_or_throw(output, editor.text); + apply_dbc_override_change(session, state, editor.save_name); + editor.source_name = editor.save_name; + editor.source_path = output; + editor.source_kind = DbcEditorState::SourceKind::Generated; + editor.loaded = false; + state->status_text = "Saved DBC " + editor.save_name; + return true; + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + return false; + } +} + +void draw_dbc_editor_popup(AppSession *session, UiState *state) { + if (!ImGui::BeginPopupModal("DBC Editor", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + DbcEditorState &editor = state->dbc_editor; + if (!ensure_dbc_editor_loaded(*session, state)) { + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + return; + } + ImGui::TextUnformatted("Edit DBC text and save it into generated_dbcs."); + ImGui::Separator(); + ImGui::SetNextItemWidth(260.0f); + input_text_string("DBC Name", &editor.save_name, ImGuiInputTextFlags_AutoSelectAll); + if (!editor.source_path.empty()) { + ImGui::TextDisabled("%s", editor.source_path.string().c_str()); + } else { + ImGui::TextDisabled("New in-memory DBC"); + } + ImGui::Spacing(); + input_text_multiline_string("##dbc_editor_text", &editor.text, ImVec2(920.0f, 520.0f), ImGuiInputTextFlags_AllowTabInput); + ImGui::Spacing(); + if (ImGui::Button("Apply + Save", ImVec2(140.0f, 0.0f))) { + if (save_dbc_editor_contents(session, state)) { + ImGui::CloseCurrentPopup(); + } + } + ImGui::SameLine(); + if (ImGui::Button("Reload Source", ImVec2(140.0f, 0.0f))) { + editor.loaded = false; + } + ImGui::SameLine(); + if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) { + editor.loaded = false; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); +} + +void draw_axis_limits_popup(AppSession *session, UiState *state) { + if (!ImGui::BeginPopupModal("Edit Axis Limits", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + const WorkspaceTab *tab = app_active_tab(session->layout, *state); + const bool valid_pane = tab != nullptr + && state->axis_limits.pane_index >= 0 + && state->axis_limits.pane_index < static_cast(tab->panes.size()); + if (!valid_pane) { + ImGui::TextWrapped("The selected pane is no longer available."); + ImGui::Spacing(); + if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) { + state->axis_limits.pane_index = -1; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + return; + } + + ImGui::TextUnformatted("X range applies to the active tab. Y limits apply to the selected pane."); + ImGui::Separator(); + ImGui::TextUnformatted("Horizontal"); + ImGui::SetNextItemWidth(180.0f); + ImGui::InputDouble("X Min", &state->axis_limits.x_min, 0.0, 0.0, "%.3f"); + ImGui::SetNextItemWidth(180.0f); + ImGui::InputDouble("X Max", &state->axis_limits.x_max, 0.0, 0.0, "%.3f"); + ImGui::Spacing(); + ImGui::TextUnformatted("Vertical"); + ImGui::Checkbox("Use Y Min", &state->axis_limits.y_min_enabled); + ImGui::BeginDisabled(!state->axis_limits.y_min_enabled); + ImGui::SetNextItemWidth(180.0f); + ImGui::InputDouble("Y Min", &state->axis_limits.y_min, 0.0, 0.0, "%.6g"); + ImGui::EndDisabled(); + ImGui::Checkbox("Use Y Max", &state->axis_limits.y_max_enabled); + ImGui::BeginDisabled(!state->axis_limits.y_max_enabled); + ImGui::SetNextItemWidth(180.0f); + ImGui::InputDouble("Y Max", &state->axis_limits.y_max, 0.0, 0.0, "%.6g"); + ImGui::EndDisabled(); + ImGui::Spacing(); + switch (draw_modal_action_row("Apply")) { + case ModalAction::Primary: + if (apply_axis_limits_editor(session, state)) { + state->axis_limits.pane_index = -1; + ImGui::CloseCurrentPopup(); + } + break; + case ModalAction::Secondary: + state->axis_limits.pane_index = -1; + ImGui::CloseCurrentPopup(); + break; + case ModalAction::None: + break; + } + ImGui::EndPopup(); +} + +void draw_error_popup(UiState *state) { + if (state->open_error_popup) { + ImGui::OpenPopup("Error"); + state->open_error_popup = false; + } + if (!ImGui::BeginPopupModal("Error", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { + return; + } + ImGui::TextWrapped("%s", state->error_text.c_str()); + ImGui::Spacing(); + if (ImGui::Button("Close", ImVec2(120.0f, 0.0f))) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); +} + +} // namespace + +bool reset_layout(AppSession *session, UiState *state) { + try { + if (session->layout_path.empty()) { + start_new_layout(session, state, "Reset layout"); + return true; + } + clear_layout_autosave(*session); + session->layout = load_sketch_layout(session->layout_path); + state->layout_dirty = false; + session->autosave_path = autosave_path_for_layout(session->layout_path); + state->undo.reset(session->layout); + refresh_replaced_layout_ui(session, state, false); + reset_shared_range(state, *session); + state->status_text = "Reset layout"; + return true; + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + state->status_text = "Failed to reset layout"; + return false; + } +} + +bool reload_layout(AppSession *session, UiState *state, const std::string &layout_arg) { + try { + const bool preserve_shared_range = session->route_data.has_time_range && state->has_shared_range; + const double preserved_x_min = state->x_view_min; + const double preserved_x_max = state->x_view_max; + const fs::path layout_path = resolve_layout_path(layout_arg); + session->autosave_path = autosave_path_for_layout(layout_path); + const bool load_draft = fs::exists(session->autosave_path); + session->layout = load_sketch_layout(load_draft ? session->autosave_path : layout_path); + session->layout_path = layout_path; + state->layout_dirty = load_draft; + state->undo.reset(session->layout); + refresh_replaced_layout_ui(session, state, true); + if (preserve_shared_range) { + state->has_shared_range = true; + state->x_view_min = preserved_x_min; + state->x_view_max = preserved_x_max; + clamp_shared_range(state, *session); + } else { + reset_shared_range(state, *session); + } + state->status_text = std::string(load_draft ? "Loaded layout draft " : "Loaded layout ") + + layout_path.filename().string(); + return true; + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + state->status_text = "Failed to load layout"; + return false; + } +} + +bool save_layout(AppSession *session, UiState *state, const std::string &layout_path) { + try { + if (layout_path.empty()) throw std::runtime_error("Layout path is empty"); + session->layout.current_tab_index = state->active_tab_index; + const fs::path previous_autosave = session->autosave_path; + const fs::path output = fs::absolute(fs::path(layout_path)); + save_layout_json(session->layout, output); + session->layout_path = output; + session->autosave_path = autosave_path_for_layout(output); + if (!previous_autosave.empty() && previous_autosave != session->autosave_path && fs::exists(previous_autosave)) { + fs::remove(previous_autosave); + } + clear_layout_autosave(*session); + state->layout_dirty = false; + sync_layout_buffers(state, *session); + state->status_text = "Saved layout " + output.filename().string(); + return true; + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + state->status_text = "Failed to save layout"; + return false; + } +} + +void rebuild_session_route_data(AppSession *session, UiState *state, + const RouteLoadProgressCallback &progress) { + apply_route_data(session, state, load_route_data(session->route_name, session->data_dir, session->dbc_override, progress)); +} + +void stop_stream_session(AppSession *session, UiState *state, bool preserve_data) { + if (preserve_data && session->stream_poller && session->data_mode == SessionDataMode::Stream) { + session->stream_poller->setPaused(true); + } else if (session->stream_poller) { + session->stream_poller->stop(); + } + session->stream_paused = preserve_data && session->data_mode == SessionDataMode::Stream; + if (!preserve_data) { + session->stream_time_offset.reset(); + apply_route_data(session, state, RouteData{}); + } + sync_stream_buffers(state, *session); +} + +bool start_stream_session(AppSession *session, + UiState *state, + const StreamSourceConfig &source, + double buffer_seconds, + bool preserve_existing_data) { + try { + if (session->route_loader) { + session->route_loader.reset(); + } + session->data_mode = SessionDataMode::Stream; + session->route_id = {}; + session->route_name.clear(); + session->data_dir.clear(); + session->stream_source = source; + if (session->stream_source.kind == StreamSourceKind::CerealLocal) { + session->stream_source.address = "127.0.0.1"; + } + session->stream_buffer_seconds = std::max(1.0, buffer_seconds); + session->next_stream_custom_refresh_time = 0.0; + session->stream_paused = false; + if (preserve_existing_data && session->stream_poller) { + StreamPollSnapshot snapshot = session->stream_poller->snapshot(); + if (snapshot.active) { + session->stream_poller->setPaused(false); + sync_route_buffers(state, *session); + sync_stream_buffers(state, *session); + state->follow_latest = true; + state->playback_playing = false; + state->status_text = "Resumed stream " + stream_source_target_label(session->stream_source); + return true; + } + } + if (!preserve_existing_data) { + session->stream_time_offset.reset(); + apply_route_data(session, state, RouteData{}); + } + if (!session->stream_poller) { + session->stream_poller = std::make_unique(); + } + session->stream_poller->start(session->stream_source, + session->stream_buffer_seconds, + session->dbc_override, + session->stream_time_offset); + sync_route_buffers(state, *session); + sync_stream_buffers(state, *session); + state->follow_latest = true; + state->playback_playing = false; + state->status_text = preserve_existing_data ? "Resumed stream " + stream_source_target_label(session->stream_source) + : "Streaming from " + stream_source_target_label(session->stream_source); + return true; + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + state->status_text = "Failed to start stream"; + return false; + } +} + +void start_async_route_load(AppSession *session, UiState *state) { + if (!session->route_loader) { + return; + } + apply_route_data(session, state, RouteData{}); + session->route_loader->start(session->route_name, session->data_dir, session->dbc_override); + state->status_text = session->route_name.empty() ? "Ready" : "Loading route " + session->route_name; +} + +void poll_async_route_load(AppSession *session, UiState *state) { + if (!session->route_loader) { + return; + } + RouteData loaded_route; + std::string error_text; + if (!session->route_loader->consume(&loaded_route, &error_text)) { + return; + } + if (!error_text.empty()) { + state->error_text = error_text; + state->open_error_popup = true; + state->status_text = "Failed to load route"; + return; + } + apply_route_data(session, state, std::move(loaded_route)); + state->status_text = session->route_name.empty() ? "Ready" : "Loaded route " + session->route_name; +} + +bool reload_session(AppSession *session, UiState *state, const std::string &route_name, const std::string &data_dir) { + try { + stop_stream_session(session, state, false); + session->data_mode = SessionDataMode::Route; + session->route_name = route_name; + session->route_id = parse_route_identifier(route_name); + session->data_dir = data_dir; + if (session->async_route_loading) { + if (!session->route_loader) { + session->route_loader = std::make_unique(::isatty(STDERR_FILENO) != 0); + } + start_async_route_load(session, state); + } else { + rebuild_session_route_data(session, state); + state->status_text = "Loaded route " + route_name; + } + sync_route_buffers(state, *session); + return true; + } catch (const std::exception &err) { + state->error_text = err.what(); + state->open_error_popup = true; + state->status_text = "Failed to load route"; + return false; + } +} + +void draw_popups(AppSession *session, UiState *state) { + open_queued_popup(state->open_open_route, "Open Route"); + if (state->open_stream) { + sync_stream_buffers(state, *session); + } + open_queued_popup(state->open_stream, "Live Stream"); + if (state->open_load_layout || state->open_save_layout) { + sync_layout_buffers(state, *session); + } + open_queued_popup(state->open_load_layout, "Load Layout"); + open_queued_popup(state->open_save_layout, "Save Layout"); + open_queued_popup(state->open_preferences, "Preferences"); + open_queued_popup(state->dbc_editor.open, "DBC Editor"); + open_queued_popup(state->open_find_signal, "Find Signal"); + open_queued_popup(state->axis_limits.open, "Edit Axis Limits"); + + draw_open_route_popup(session, state); + draw_stream_popup(session, state); + draw_load_layout_popup(session, state); + draw_save_layout_popup(session, state); + draw_preferences_popup(session, state); + draw_dbc_editor_popup(session, state); + draw_find_signal_popup(session, state); + draw_axis_limits_popup(session, state); + draw_error_popup(state); +} diff --git a/tools/jotpluggler/layout_io.cc b/tools/jotpluggler/layout_io.cc new file mode 100644 index 00000000000..f984c0f0e8e --- /dev/null +++ b/tools/jotpluggler/layout_io.cc @@ -0,0 +1,128 @@ +#include "tools/jotpluggler/app.h" +#include "tools/jotpluggler/common.h" + +#include +#include +#include +#include + +#include "third_party/json11/json11.hpp" + +namespace fs = std::filesystem; + +namespace { + +std::string curve_color_hex(const std::array &color) { + std::ostringstream hex; + hex << "#" << std::hex << std::setfill('0') + << std::setw(2) << static_cast(color[0]) + << std::setw(2) << static_cast(color[1]) + << std::setw(2) << static_cast(color[2]); + return hex.str(); +} + +json11::Json curve_to_json(const Curve &curve) { + json11::Json::object obj = { + {"name", curve.name}, + {"color", curve_color_hex(curve.color)}, + }; + if (curve.derivative) { + obj["transform"] = "derivative"; + if (curve.derivative_dt > 0.0) { + obj["derivative_dt"] = curve.derivative_dt; + } + } else if (std::abs(curve.value_scale - 1.0) > 1.0e-9 || std::abs(curve.value_offset) > 1.0e-9) { + obj["transform"] = "scale"; + obj["scale"] = curve.value_scale; + obj["offset"] = curve.value_offset; + } + if (curve.custom_python.has_value()) { + json11::Json::array additional_sources; + for (const std::string &path : curve.custom_python->additional_sources) { + additional_sources.push_back(path); + } + obj["custom_python"] = json11::Json::object{ + {"linked_source", curve.custom_python->linked_source}, + {"additional_sources", additional_sources}, + {"globals_code", curve.custom_python->globals_code}, + {"function_code", curve.custom_python->function_code}, + }; + } + return obj; +} + +json11::Json workspace_node_to_json(const WorkspaceNode &node, const WorkspaceTab &tab) { + if (node.is_pane) { + if (node.pane_index < 0 || node.pane_index >= static_cast(tab.panes.size())) { + return nullptr; + } + const Pane &pane = tab.panes[static_cast(node.pane_index)]; + json11::Json::object obj = { + {"title", pane.title.empty() ? std::string("...") : pane.title}, + }; + if (pane.kind == PaneKind::Map) { + obj["kind"] = "map"; + } else if (pane.kind == PaneKind::Camera) { + obj["kind"] = "camera"; + obj["camera_view"] = camera_view_spec(pane.camera_view).layout_name; + } + if (pane.range.valid) { + obj["range"] = json11::Json::object{ + {"left", pane.range.left}, {"right", pane.range.right}, + {"top", pane.range.top}, {"bottom", pane.range.bottom}, + }; + } + if (pane.range.has_y_limit_min || pane.range.has_y_limit_max) { + json11::Json::object limits; + if (pane.range.has_y_limit_min) { + limits["min"] = pane.range.y_limit_min; + } + if (pane.range.has_y_limit_max) { + limits["max"] = pane.range.y_limit_max; + } + obj["y_limits"] = limits; + } + json11::Json::array curves; + for (const Curve &curve : pane.curves) { + if (!curve.runtime_only) { + curves.push_back(curve_to_json(curve)); + } + } + obj["curves"] = curves; + return obj; + } + + if (node.children.empty()) return nullptr; + json11::Json::array sizes; + for (size_t i = 0; i < node.children.size(); ++i) { + sizes.push_back(i < node.sizes.size() ? static_cast(node.sizes[i]) + : 1.0 / static_cast(node.children.size())); + } + json11::Json::array children; + for (const WorkspaceNode &child : node.children) { + children.push_back(workspace_node_to_json(child, tab)); + } + return json11::Json::object{ + {"split", node.orientation == SplitOrientation::Horizontal ? "horizontal" : "vertical"}, + {"sizes", sizes}, + {"children", children}, + }; +} + +} // namespace + +void save_layout_json(const SketchLayout &layout, const fs::path &path) { + ensure_parent_dir(path); + json11::Json::array tabs; + for (const WorkspaceTab &tab : layout.tabs) { + tabs.push_back(json11::Json::object{ + {"name", tab.tab_name}, + {"root", workspace_node_to_json(tab.root, tab)}, + }); + } + const json11::Json root = json11::Json::object{ + {"current_tab_index", std::clamp(layout.current_tab_index, 0, std::max(0, static_cast(layout.tabs.size()) - 1))}, + {"tabs", tabs}, + }; + write_file_or_throw(path, root.dump() + "\n"); +} diff --git a/tools/jotpluggler/layouts/.gitignore b/tools/jotpluggler/layouts/.gitignore new file mode 100644 index 00000000000..a965bb777d6 --- /dev/null +++ b/tools/jotpluggler/layouts/.gitignore @@ -0,0 +1 @@ +.jotpluggler_autosave/ diff --git a/tools/jotpluggler/layouts/CAN-bus-debug.json b/tools/jotpluggler/layouts/CAN-bus-debug.json new file mode 100644 index 00000000000..496993a1fd4 --- /dev/null +++ b/tools/jotpluggler/layouts/CAN-bus-debug.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.33362,0.33276,0.33362],"children":[{"title":"CAN RX","range":{"left":0.0,"right":60.526742,"top":1101.875,"bottom":-26.875},"curves":[{"name":"/pandaStates/0/canState0/totalRxCnt","color":"#f14cc1","transform":"derivative","derivative_dt":1.0},{"name":"/pandaStates/0/canState1/totalRxCnt","color":"#9467bd","transform":"derivative","derivative_dt":1.0},{"name":"/pandaStates/0/canState2/totalRxCnt","color":"#ff7f0e","transform":"derivative","derivative_dt":1.0}]},{"title":"CAN TX","range":{"left":0.0,"right":60.526742,"top":455.1,"bottom":-11.1},"curves":[{"name":"/pandaStates/0/canState0/totalTxCnt","color":"#17becf","transform":"derivative","derivative_dt":1.0},{"name":"/pandaStates/0/canState1/totalTxCnt","color":"#bcbd22","transform":"derivative","derivative_dt":1.0},{"name":"/pandaStates/0/canState2/totalTxCnt","color":"#1f77b4","transform":"derivative","derivative_dt":1.0}]},{"title":"CAN errors","range":{"left":0.0,"right":60.526742,"top":2515.35,"bottom":-61.35},"curves":[{"name":"/pandaStates/0/canState0/totalErrorCnt","color":"#1f77b4","transform":"derivative","derivative_dt":1.0},{"name":"/pandaStates/0/canState1/totalErrorCnt","color":"#d62728","transform":"derivative","derivative_dt":1.0},{"name":"/pandaStates/0/canState2/totalErrorCnt","color":"#1ac938","transform":"derivative","derivative_dt":1.0}]}]}}]} diff --git a/tools/jotpluggler/layouts/camera-timings.json b/tools/jotpluggler/layouts/camera-timings.json new file mode 100644 index 00000000000..64decf15d38 --- /dev/null +++ b/tools/jotpluggler/layouts/camera-timings.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"SOF / EOF (encodeIdx)","root":{"split":"vertical","sizes":[0.500885,0.499115],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/driverEncodeIdx/timestampSof","color":"#1f77b4","transform":"derivative","derivative_dt":1.0},{"name":"/roadEncodeIdx/timestampSof","color":"#d62728","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadEncodeIdx/timestampSof","color":"#1ac938","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}},{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/driverEncodeIdx/timestampEof","color":"#f14cc1","transform":"derivative","derivative_dt":1.0},{"name":"/roadEncodeIdx/timestampEof","color":"#9467bd","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadEncodeIdx/timestampEof","color":"#17becf","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}}]}},{"name":"model timings","root":{"split":"vertical","sizes":[0.5,0.5],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":0.016865,"bottom":0.015143},"curves":[{"name":"/modelV2/modelExecutionTime","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.0,"right":630.006367,"top":0.1,"bottom":-0.1},"curves":[{"name":"/modelV2/frameDropPerc","color":"#f14cc1"}]}]}},{"name":"sensor info","root":{"split":"vertical","sizes":[1.0],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":0.1,"bottom":-0.1},"curves":[{"name":"/driverCameraState/sensor","color":"#bcbd22"},{"name":"/roadCameraState/sensor","color":"#1f77b4"},{"name":"/wideRoadCameraState/sensor","color":"#d62728"}]}]}},{"name":"SOF / EOF (cameraState)","root":{"split":"vertical","sizes":[0.500885,0.499115],"children":[{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/driverCameraState/timestampSof","color":"#1f77b4","transform":"derivative","derivative_dt":1.0},{"name":"/roadCameraState/timestampSof","color":"#d62728","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadCameraState/timestampSof","color":"#1ac938","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}},{"title":"...","range":{"left":0.0,"right":630.006367,"top":65000000.0,"bottom":35000000.0},"curves":[{"name":"/driverCameraState/timestampEof","color":"#ff7f0e","transform":"derivative","derivative_dt":1.0},{"name":"/roadCameraState/timestampEof","color":"#f14cc1","transform":"derivative","derivative_dt":1.0},{"name":"/wideRoadCameraState/timestampEof","color":"#9467bd","transform":"derivative","derivative_dt":1.0}],"y_limits":{"min":35000000.0,"max":65000000.0}}]}}]} diff --git a/tools/jotpluggler/layouts/cameras-and-map.json b/tools/jotpluggler/layouts/cameras-and-map.json new file mode 100644 index 00000000000..68c590f7bc5 --- /dev/null +++ b/tools/jotpluggler/layouts/cameras-and-map.json @@ -0,0 +1 @@ +{"current_tab_index": 0, "tabs": [{"name": "tab1", "root": {"children": [{"children": [{"curves": [], "kind": "map", "title": "Map"}, {"camera_view": "road", "curves": [], "kind": "camera", "title": "Road Camera"}], "sizes": [0.5, 0.5], "split": "horizontal"}, {"children": [{"camera_view": "wide_road", "curves": [], "kind": "camera", "title": "Wide Road Camera"}, {"camera_view": "driver", "curves": [], "kind": "camera", "title": "Driver Camera"}], "sizes": [0.5, 0.5], "split": "horizontal"}], "sizes": [0.5, 0.5], "split": "vertical"}}]} diff --git a/tools/jotpluggler/layouts/can-states.json b/tools/jotpluggler/layouts/can-states.json new file mode 100644 index 00000000000..6f04940a335 --- /dev/null +++ b/tools/jotpluggler/layouts/can-states.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.500381,0.499619],"children":[{"split":"horizontal","sizes":[0.5,0.5],"children":[{"title":"...","range":{"left":0.0,"right":632.799721,"top":771630.925,"bottom":-17755.925},"curves":[{"name":"/pandaStates/0/canState0/totalRxCnt","color":"#1f77b4"},{"name":"/pandaStates/0/canState1/totalRxCnt","color":"#d62728"},{"name":"/pandaStates/0/canState2/totalRxCnt","color":"#1ac938"}]},{"title":"...","range":{"left":0.0,"right":632.799721,"top":760365.5,"bottom":-18545.5},"curves":[{"name":"/pandaStates/0/canState0/totalTxCnt","color":"#ff7f0e"},{"name":"/pandaStates/0/canState1/totalTxCnt","color":"#f14cc1"},{"name":"/pandaStates/0/canState2/totalTxCnt","color":"#9467bd"}]}]},{"split":"horizontal","sizes":[0.333333,0.333333,0.333333],"children":[{"title":"...","range":{"left":0.0,"right":632.799721,"top":55.35,"bottom":-1.35},"curves":[{"name":"/pandaStates/0/canState0/totalRxLostCnt","color":"#ff7f0e"},{"name":"/pandaStates/0/canState1/totalRxLostCnt","color":"#f14cc1"},{"name":"/pandaStates/0/canState2/totalRxLostCnt","color":"#9467bd"}]},{"title":"...","range":{"left":0.0,"right":632.799721,"top":2.05,"bottom":-0.05},"curves":[{"name":"/pandaStates/0/canState0/totalTxLostCnt","color":"#17becf"},{"name":"/pandaStates/0/canState1/totalTxLostCnt","color":"#bcbd22"},{"name":"/pandaStates/0/canState2/totalTxLostCnt","color":"#1f77b4"}]},{"title":"...","range":{"left":0.0,"right":632.799721,"top":0.1,"bottom":-0.1},"curves":[{"name":"/pandaStates/0/canState0/busOffCnt","color":"#17becf"},{"name":"/pandaStates/0/canState1/busOffCnt","color":"#1ac938"},{"name":"/pandaStates/0/canState2/busOffCnt","color":"#bcbd22"}]}]}]}}]} diff --git a/tools/jotpluggler/layouts/controls_mismatch_debug.json b/tools/jotpluggler/layouts/controls_mismatch_debug.json new file mode 100644 index 00000000000..16912cd6840 --- /dev/null +++ b/tools/jotpluggler/layouts/controls_mismatch_debug.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.2,0.2,0.2,0.2,0.2],"children":[{"title":"...","range":{"left":0.018309,"right":59.674401,"top":1.025,"bottom":-0.025},"curves":[{"name":"/carControl/enabled","color":"#1f77b4"},{"name":"/pandaStates/0/controlsAllowed","color":"#d62728"}]},{"title":"...","range":{"left":0.018309,"right":59.674401,"top":27.087398,"bottom":-0.905168},"curves":[{"name":"/carState/cumLagMs","color":"#9467bd"}]},{"title":"...","range":{"left":0.018309,"right":59.674401,"top":1.025,"bottom":-0.025},"curves":[{"name":"/pandaStates/0/safetyRxInvalid","color":"#1f77b4"},{"name":"/pandaStates/0/safetyRxChecksInvalid","color":"#e801ce"}]},{"title":"...","range":{"left":0.018309,"right":59.674401,"top":158.85,"bottom":-2.85},"curves":[{"name":"/pandaStates/0/safetyTxBlocked","color":"#d62728"}]},{"title":"...","range":{"left":0.018309,"right":59.674401,"top":1.025,"bottom":-0.025},"curves":[{"name":"/carState/gasPressed","color":"#1ac938"},{"name":"/carState/brakePressed","color":"#ff7f0e"}]}]}}]} diff --git a/tools/jotpluggler/layouts/gps.json b/tools/jotpluggler/layouts/gps.json new file mode 100644 index 00000000000..fdabbfd381c --- /dev/null +++ b/tools/jotpluggler/layouts/gps.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.24977,0.250689,0.24977,0.24977],"children":[{"title":"...","range":{"left":0.0,"right":1678.753571,"top":1.025,"bottom":-0.025},"curves":[{"name":"/gpsLocationExternal/hasFix","color":"#1f77b4"}]},{"title":"...","range":{"left":0.0,"right":1678.753571,"top":17.425,"bottom":-0.425},"curves":[{"name":"/gpsLocationExternal/satelliteCount","color":"#d62728"}]},{"title":"...","range":{"left":0.0,"right":1678.753571,"top":3.0,"bottom":0.0},"curves":[{"name":"/gpsLocationExternal/horizontalAccuracy","color":"#1ac938"}],"y_limits":{"min":0.0,"max":3.0}},{"title":"...","range":{"left":0.0,"right":1678.753571,"top":766.374004,"bottom":-17.262},"curves":[{"name":"/gpsLocationExternal/horizontalAccuracy","color":"#1ac938"}]}]}}]} diff --git a/tools/jotpluggler/layouts/gps_vs_llk.json b/tools/jotpluggler/layouts/gps_vs_llk.json new file mode 100644 index 00000000000..878e0a57a81 --- /dev/null +++ b/tools/jotpluggler/layouts/gps_vs_llk.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.333805,0.33239,0.333805],"children":[{"title":"...","range":{"left":76.646983,"right":196.811937,"top":32.070386,"bottom":0.368228},"curves":[{"name":"haversine distance [m]","color":"#1f77b4","custom_python":{"linked_source":"/gpsLocationExternal/latitude","additional_sources":["/gpsLocationExternal/longitude","/liveLocationKalmanDEPRECATED/positionGeodetic/value/0","/liveLocationKalmanDEPRECATED/positionGeodetic/value/1"],"globals_code":"R = 6378.137 # Radius of earth in KM","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3):\n global R\n # Compute the Haversine distance between\n # two points defined by latitude and longitude.\n # Return the distance in meters\n lat1, lon1 = value, v1\n lat2, lon2 = v2, v3\n dLat = (lat2 - lat1) * np.pi / 180\n dLon = (lon2 - lon1) * np.pi / 180\n a = np.sin(dLat/2) * np.sin(dLat/2) +\n np.cos(lat1 * np.pi / 180) * np.cos(lat2 * np.pi / 180) *\n np.sin(dLon/2) * np.sin(dLon/2)\n c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1-a))\n d = R * c\n distance = d * 1000 # meters\n return distance\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"...","range":{"left":76.646983,"right":196.811937,"top":12.637299,"bottom":-0.259115},"curves":[{"name":"/carState/vEgo","color":"#17becf"},{"name":"/gpsLocationExternal/speed","color":"#bcbd22"}]},{"split":"horizontal","sizes":[0.500516,0.499484],"children":[{"title":"...","range":{"left":76.646983,"right":196.811937,"top":0.1,"bottom":-0.1},"curves":[{"name":"/liveLocationKalmanDEPRECATED/positionGeodetic/std/0","color":"#d62728"},{"name":"/liveLocationKalmanDEPRECATED/positionGeodetic/std/1","color":"#1ac938"}]},{"title":"...","range":{"left":76.646983,"right":196.811937,"top":7.160833,"bottom":-0.449385},"curves":[{"name":"/gpsLocationExternal/horizontalAccuracy","color":"#ff7f0e"},{"name":"/gpsLocationExternal/verticalAccuracy","color":"#f14cc1"},{"name":"/gpsLocationExternal/speedAccuracy","color":"#9467bd"}]}]}]}}]} diff --git a/tools/jotpluggler/layouts/locationd_debug.json b/tools/jotpluggler/layouts/locationd_debug.json new file mode 100644 index 00000000000..0541427bc1e --- /dev/null +++ b/tools/jotpluggler/layouts/locationd_debug.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.166588,0.167062,0.166113,0.166588,0.167062,0.166588],"children":[{"title":"...","range":{"left":0.0,"right":2280.128382,"top":1.025,"bottom":-0.025},"curves":[{"name":"/livePose/inputsOK","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":14.542814,"bottom":-5.586039},"curves":[{"name":"/accelerometer/acceleration/v/0","color":"#f14cc1"},{"name":"/accelerometer/acceleration/v/1","color":"#9467bd"},{"name":"/accelerometer/acceleration/v/2","color":"#17becf"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":0.988911,"bottom":-0.745939},"curves":[{"name":"/gyroscope/gyroUncalibrated/v/0","color":"#d62728"},{"name":"/gyroscope/gyroUncalibrated/v/1","color":"#1ac938"},{"name":"/gyroscope/gyroUncalibrated/v/2","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":1.025,"bottom":-0.025},"curves":[{"name":"/accelerometer/__valid","color":"#17becf"},{"name":"/gyroscope/__valid","color":"#bcbd22"},{"name":"/carState/__valid","color":"#f14cc1"},{"name":"/liveCalibration/__valid","color":"#1ac938"},{"name":"/cameraOdometry/__valid","color":"#9467bd"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":1000000000.292252,"bottom":999999999.735447},"curves":[{"name":"/gyroscope/__logMonoTime","color":"#1f77b4","transform":"derivative"},{"name":"/accelerometer/__logMonoTime","color":"#d62728","transform":"derivative"}]},{"title":"...","range":{"left":0.0,"right":2280.128382,"top":20790107743.93223,"bottom":-529653831.495853},"curves":[{"name":"/accelerometer/timestamp","color":"#bcbd22","transform":"derivative"},{"name":"/gyroscope/timestamp","color":"#1f77b4","transform":"derivative"}]}]}}]} diff --git a/tools/jotpluggler/layouts/longitudinal.json b/tools/jotpluggler/layouts/longitudinal.json new file mode 100644 index 00000000000..27f43eb3577 --- /dev/null +++ b/tools/jotpluggler/layouts/longitudinal.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.250401,0.249599,0.250401,0.249599],"children":[{"title":"...","range":{"left":104.907277,"right":126.285782,"top":1.391623,"bottom":-2.563614},"curves":[{"name":"/carState/aEgo","color":"#f14cc1"},{"name":"/longitudinalPlan/accels/0","color":"#9467bd"},{"name":"/carControl/actuators/accel","color":"#17becf"},{"name":"/carOutput/actuatorsOutput/accel","color":"#d62728"}]},{"title":"...","range":{"left":104.907277,"right":126.285782,"top":1.18496,"bottom":-1.811222},"curves":[{"name":"/controlsState/upAccelCmd","color":"#1f77b4"},{"name":"/controlsState/uiAccelCmd","color":"#d62728"},{"name":"/controlsState/ufAccelCmd","color":"#1ac938"}]},{"title":"...","range":{"left":104.907277,"right":126.285782,"top":15.862889,"bottom":-0.568809},"curves":[{"name":"/carState/vEgo","color":"#1ac938"},{"name":"/longitudinalPlan/speeds/0","color":"#ff7f0e"}]},{"title":"...","range":{"left":104.907277,"right":126.285782,"top":1.025,"bottom":-0.025},"curves":[{"name":"/carControl/longActive","color":"#1f77b4"},{"name":"/carState/gasPressed","color":"#d62728"}]}]}}]} diff --git a/tools/jotpluggler/layouts/max-torque-debug.json b/tools/jotpluggler/layouts/max-torque-debug.json new file mode 100644 index 00000000000..3a87fb3217b --- /dev/null +++ b/tools/jotpluggler/layouts/max-torque-debug.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.249724,0.250829,0.249724,0.249724],"children":[{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":6.050533,"bottom":-7.599037},"curves":[{"name":"Actual lateral accel (roll compensated)","color":"#1ac938","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/vEgo","/liveParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"Desired lateral accel (roll compensated)","color":"#ff7f0e","custom_python":{"linked_source":"/controlsState/desiredCurvature","additional_sources":["/carState/vEgo","/liveParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":5.384416,"bottom":-7.503945},"curves":[{"name":"roll compensated lateral acceleration","color":"#1ac938","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/vEgo","/liveParameters/roll","/carState/steeringPressed","/carControl/latActive"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3, v4):\n if (v3 == 0 and v4 == 1):\n return (value * v1 ** 2) - (v2 * 9.81)\n return 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i], v4[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":1.05,"bottom":-1.05},"curves":[{"name":"/carState/steeringPressed","color":"#0097ff"},{"name":"/carOutput/actuatorsOutput/torque","color":"#d62728"}]},{"title":"...","range":{"left":0.00045,"right":2483.624998,"top":80.762969,"bottom":-2.181837},"curves":[{"name":"/carState/vEgo","color":"#f14cc1","transform":"scale","scale":2.23694,"offset":0.0}]}]}}]} diff --git a/tools/jotpluggler/layouts/new-layout.json b/tools/jotpluggler/layouts/new-layout.json new file mode 100644 index 00000000000..bffb62d7c76 --- /dev/null +++ b/tools/jotpluggler/layouts/new-layout.json @@ -0,0 +1 @@ +{"current_tab_index": 0, "tabs": [{"name": "tab1", "root": {"curves": [], "title": "..."}}]} diff --git a/tools/jotpluggler/layouts/system_lag_debug.json b/tools/jotpluggler/layouts/system_lag_debug.json new file mode 100644 index 00000000000..281de440faa --- /dev/null +++ b/tools/jotpluggler/layouts/system_lag_debug.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.249729,0.250814,0.249729,0.249729],"children":[{"title":"...","range":{"left":0.0,"right":59.992103,"top":102.5,"bottom":-2.5},"curves":[{"name":"/deviceState/cpuUsagePercent/0","color":"#1f77b4"},{"name":"/deviceState/cpuUsagePercent/1","color":"#d62728"},{"name":"/deviceState/cpuUsagePercent/2","color":"#1ac938"},{"name":"/deviceState/cpuUsagePercent/3","color":"#ff7f0e"},{"name":"/deviceState/cpuUsagePercent/4","color":"#f14cc1"},{"name":"/deviceState/cpuUsagePercent/5","color":"#9467bd"},{"name":"/deviceState/cpuUsagePercent/6","color":"#17becf"},{"name":"/deviceState/cpuUsagePercent/7","color":"#bcbd22"}]},{"title":"...","range":{"left":0.0,"right":59.992103,"top":64.005001,"bottom":51.195},"curves":[{"name":"/deviceState/cpuTempC/0","color":"#d62728"},{"name":"/deviceState/cpuTempC/1","color":"#1ac938"},{"name":"/deviceState/cpuTempC/2","color":"#ff7f0e"},{"name":"/deviceState/cpuTempC/3","color":"#f14cc1"},{"name":"/deviceState/cpuTempC/4","color":"#9467bd"},{"name":"/deviceState/cpuTempC/5","color":"#17becf"},{"name":"/deviceState/cpuTempC/6","color":"#bcbd22"},{"name":"/deviceState/cpuTempC/7","color":"#1f77b4"},{"name":"/deviceState/gpuTempC/0","color":"#d62728"},{"name":"/deviceState/gpuTempC/1","color":"#1ac938"}]},{"title":"...","range":{"left":0.0,"right":59.992103,"top":37.371108,"bottom":-0.91149},"curves":[{"name":"/modelV2/frameDropPerc","color":"#f14cc1"}]},{"title":"...","range":{"left":0.0,"right":59.992103,"top":-3.593455,"bottom":-12.190956},"curves":[{"name":"/carState/cumLagMs","color":"#9467bd"}]}]}}]} diff --git a/tools/jotpluggler/layouts/thermal_debug.json b/tools/jotpluggler/layouts/thermal_debug.json new file mode 100644 index 00000000000..3a7ce454cf6 --- /dev/null +++ b/tools/jotpluggler/layouts/thermal_debug.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.166785,0.166785,0.166075,0.166785,0.166785,0.166785],"children":[{"title":"...","range":{"left":0.006955,"right":301.842654,"top":87.987497,"bottom":75.912497},"curves":[{"name":"/deviceState/cpuTempC/0","color":"#1f77b4"},{"name":"/deviceState/cpuTempC/1","color":"#d62728"},{"name":"/deviceState/cpuTempC/2","color":"#1ac938"},{"name":"/deviceState/cpuTempC/3","color":"#ff7f0e"},{"name":"/deviceState/cpuTempC/4","color":"#f14cc1"},{"name":"/deviceState/cpuTempC/5","color":"#9467bd"},{"name":"/deviceState/cpuTempC/6","color":"#17becf"},{"name":"/deviceState/cpuTempC/7","color":"#bcbd22"}]},{"title":"...","range":{"left":0.006955,"right":301.842654,"top":85.861052,"bottom":66.49695},"curves":[{"name":"/deviceState/pmicTempC/0","color":"#1f77b4"},{"name":"/deviceState/gpuTempC/0","color":"#d62728"},{"name":"/deviceState/gpuTempC/1","color":"#1ac938"},{"name":"/deviceState/memoryTempC","color":"#f14cc1"}]},{"title":"...","range":{"left":0.006955,"right":301.842654,"top":86.207876,"bottom":70.665918},"curves":[{"name":"/deviceState/maxTempC","color":"#1f77b4"}]},{"title":"...","range":{"left":0.006955,"right":301.842654,"top":1.025,"bottom":-0.025},"curves":[{"name":"/deviceState/thermalStatus","color":"#1f77b4"}]},{"split":"horizontal","sizes":[0.333124,0.333752,0.333124],"children":[{"title":"...","range":{"left":0.006955,"right":301.842654,"top":12.057358,"bottom":4.843517},"curves":[{"name":"/deviceState/powerDrawW","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.006955,"right":301.842654,"top":100.0,"bottom":0.0},"curves":[{"name":"/deviceState/fanSpeedPercentDesired","color":"#9467bd"},{"name":"/pandaStates/0/fanPower","color":"#1f77b4"}],"y_limits":{"min":0.0,"max":100.0}},{"title":"...","range":{"left":0.006955,"right":301.842654,"top":5018.4,"bottom":255.6},"curves":[{"name":"/peripheralState/fanSpeedRpm","color":"#1f77b4"}]}]},{"split":"horizontal","sizes":[0.502513,0.497487],"children":[{"title":"...","range":{"left":0.006955,"right":301.842654,"top":100.025,"bottom":14.975},"curves":[{"name":"/deviceState/cpuUsagePercent/0","color":"#1f77b4"},{"name":"/deviceState/cpuUsagePercent/1","color":"#d62728"},{"name":"/deviceState/cpuUsagePercent/2","color":"#1ac938"},{"name":"/deviceState/cpuUsagePercent/3","color":"#ff7f0e"}]},{"title":"...","range":{"left":0.006955,"right":301.842654,"top":102.5,"bottom":-2.5},"curves":[{"name":"/deviceState/cpuUsagePercent/4","color":"#f14cc1"},{"name":"/deviceState/cpuUsagePercent/5","color":"#9467bd"},{"name":"/deviceState/cpuUsagePercent/6","color":"#17becf"},{"name":"/deviceState/cpuUsagePercent/7","color":"#bcbd22"}]}]}]}}]} diff --git a/tools/jotpluggler/layouts/torque-controller.json b/tools/jotpluggler/layouts/torque-controller.json new file mode 100644 index 00000000000..7e269e59e6c --- /dev/null +++ b/tools/jotpluggler/layouts/torque-controller.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"Lateral Plan Conformance","root":{"split":"vertical","sizes":[0.250949,0.249051,0.250949,0.249051],"children":[{"title":"desired vs actual lateral acceleration (closer means better conformance to plan)","range":{"left":0.000194,"right":1138.891674,"top":1.858161,"bottom":-1.823407},"curves":[{"name":"/controlsState/lateralControlState/torqueState/actualLateralAccel","color":"#1f77b4"},{"name":"/controlsState/lateralControlState/torqueState/desiredLateralAccel","color":"#d62728"}]},{"title":"desired vs actual lateral acceleration, road-roll factored out (closer means better conformance to plan)","range":{"left":0.000194,"right":1138.891674,"top":2.749816,"bottom":-3.723091},"curves":[{"name":"Actual lateral accel (roll compensated)","color":"#1ac938","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/vEgo","/liveParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"Desired lateral accel (roll compensated)","color":"#ff7f0e","custom_python":{"linked_source":"/controlsState/desiredCurvature","additional_sources":["/carState/vEgo","/liveParameters/roll"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n return (value * v1 ** 2) - (v2 * 9.81)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"controller feed-forward vs actuator output (closer means controller prediction is more accurate)","range":{"left":0.000194,"right":1138.891674,"top":1.978032,"bottom":-1.570956},"curves":[{"name":"/carOutput/actuatorsOutput/torque","color":"#9467bd","transform":"scale","scale":-1.0,"offset":0.0},{"name":"/controlsState/lateralControlState/torqueState/f","color":"#1f77b4"},{"name":"/carState/steeringPressed","color":"#ff000f"}]},{"title":"vehicle speed","range":{"left":0.000194,"right":1138.891674,"top":105.981304,"bottom":-2.709314},"curves":[{"name":"carState.vEgo mph","color":"#d62728","custom_python":{"linked_source":"/carState/vEgo","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return value * 2.23694\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"carState.vEgo kmh","color":"#1ac938","custom_python":{"linked_source":"/carState/vEgo","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return value * 3.6\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"/carState/vEgo","color":"#ff7f0e"}]}]}},{"name":"Vehicle Dynamics","root":{"split":"vertical","sizes":[0.334282,0.331437,0.334282],"children":[{"title":"configured-initial vs online-learned steerRatio, set configured value to match learned","range":{"left":0.0,"right":1138.816328,"top":19.665784,"bottom":19.359553},"curves":[{"name":"/carParams/steerRatio","color":"#1f77b4"},{"name":"/liveParameters/steerRatio","color":"#1ac938"}]},{"title":"configured-initial vs online-learned tireStiffnessRatio, set configured value to match learned","range":{"left":0.0,"right":1138.816328,"top":1.11221,"bottom":0.995631},"curves":[{"name":"/carParams/tireStiffnessFactor","color":"#d62728"},{"name":"/liveParameters/stiffnessFactor","color":"#ff7f0e"}]},{"title":"live steering angle offsets for straight-ahead driving, large values here may indicate alignment problems","range":{"left":0.0,"right":1138.816328,"top":-1.081041,"bottom":-4.494133},"curves":[{"name":"/liveParameters/angleOffsetAverageDeg","color":"#f14cc1"},{"name":"/liveParameters/angleOffsetDeg","color":"#9467bd"}]}]}},{"name":"Actuator Performance","root":{"split":"vertical","sizes":[0.333333,0.333333,0.333333],"children":[{"title":"offline-calculated vs online-learned lateral accel scaling factor, accel obtained from 100% actuator output","range":{"left":0.0,"right":1138.920072,"top":1.21611,"bottom":0.539474},"curves":[{"name":"/liveTorqueParameters/latAccelFactorFiltered","color":"#1f77b4"},{"name":"/liveTorqueParameters/latAccelFactorRaw","color":"#d62728"},{"name":"/carParams/lateralTuning/torque/latAccelFactor","color":"#1c9222"}]},{"title":"learned lateral accel offset, vehicle-specific compensation to obtain true zero lateral accel","range":{"left":0.0,"right":1138.920072,"top":-0.304367,"bottom":-0.418688},"curves":[{"name":"/liveTorqueParameters/latAccelOffsetFiltered","color":"#1ac938"},{"name":"/liveTorqueParameters/latAccelOffsetRaw","color":"#ff7f0e"}]},{"title":"offline-calculated vs online-learned EPS friction factor, necessary to start moving the steering wheel","range":{"left":0.0,"right":1138.920072,"top":0.226389,"bottom":0.15805},"curves":[{"name":"/liveTorqueParameters/frictionCoefficientFiltered","color":"#f14cc1"},{"name":"/liveTorqueParameters/frictionCoefficientRaw","color":"#9467bd"},{"name":"/carParams/lateralTuning/torque/friction","color":"#1c9222"}]}]}},{"name":"Actuator Delay","root":{"split":"vertical","sizes":[0.30441,0.358464,0.337127],"children":[{"title":"actuator lag learning state, 0 = learning, 1 = learned/applying, 2 = invalid","range":{"left":0.0,"right":1138.749979,"top":1.025,"bottom":-0.025},"curves":[{"name":"/liveDelay/status","color":"#ff7f0e"}]},{"title":"offline default vs online estimated steering actuator lag","range":{"left":0.0,"right":1138.749979,"top":0.419648,"bottom":0.318362},"curves":[{"name":"/liveDelay/lateralDelay","color":"#1f77b4"},{"name":"/liveDelay/lateralDelayEstimate","color":"#d62728"},{"name":"opendbc default steering lag","color":"#1ac938","custom_python":{"linked_source":"/carParams/steerActuatorDelay","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return value + 0.2\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"online estimated steering actuator lag, standard deviation","range":{"left":0.0,"right":1138.749979,"top":0.06732,"bottom":-0.001642},"curves":[{"name":"/liveDelay/lateralDelayEstimateStd","color":"#f14cc1"}]}]}},{"name":"Controls Performance","root":{"split":"vertical","sizes":[0.265655,0.251898,0.245731,0.236717],"children":[{"title":"rate-of-change limits on steering actuator (blue = original, green = rate-limited before CAN output)","range":{"left":0.000194,"right":1138.891921,"top":1.05,"bottom":-1.05},"curves":[{"name":"/carControl/actuators/torque","color":"#0c00f2"},{"name":"/carOutput/actuatorsOutput/torque","color":"#2cd63a"}]},{"title":"controller feed-forward vs actuator output (closer means controller prediction is more accurate)","range":{"left":0.000194,"right":1138.891921,"top":1.978032,"bottom":-1.570956},"curves":[{"name":"/carOutput/actuatorsOutput/torque","color":"#9467bd","transform":"scale","scale":-1.0,"offset":0.0},{"name":"/controlsState/lateralControlState/torqueState/f","color":"#1f77b4"},{"name":"/carState/steeringPressed","color":"#ff000f"}]},{"title":"proportional, integral, and feed-forward terms (actuator output = sum of PIF terms)","range":{"left":0.000194,"right":1138.891921,"top":2.099784,"bottom":-4.027542},"curves":[{"name":"/controlsState/lateralControlState/torqueState/f","color":"#0ab027"},{"name":"/controlsState/lateralControlState/torqueState/p","color":"#d62728"},{"name":"/controlsState/lateralControlState/torqueState/i","color":"#ffaf00"},{"name":"Zero","color":"#756a6a","custom_python":{"linked_source":"/carState/canValid","additional_sources":[],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value):\n return (0)\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"road roll angle, from openpilot localizer","range":{"left":0.000194,"right":1138.891921,"top":0.109446,"bottom":-0.045525},"curves":[{"name":"/liveParameters/roll","color":"#f14cc1"}]}]}}]} diff --git a/tools/jotpluggler/layouts/tuning.json b/tools/jotpluggler/layouts/tuning.json new file mode 100644 index 00000000000..0a8e81743eb --- /dev/null +++ b/tools/jotpluggler/layouts/tuning.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"Lateral","root":{"split":"vertical","sizes":[0.200458,0.199313,0.200458,0.199313,0.200458],"children":[{"title":"Velocity [m/s]","range":{"left":1.253354,"right":631.055584,"top":29.954036,"bottom":-0.841715},"curves":[{"name":"/carState/vEgo","color":"#0072b2"}]},{"title":"Curvature [1/m] True [blue] Vehicle Model [purple] Plan [green]","range":{"left":0.0,"right":631.055209,"top":0.006648,"bottom":-0.00315},"curves":[{"name":"engaged curvature plan","color":"#009e73","custom_python":{"linked_source":"/modelV2/action/desiredCurvature","additional_sources":["/carState/steeringPressed","/carControl/enabled"],"globals_code":"engage_delay = 5\nlast_bad_time = -engage_delay","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n global engage_delay, last_bad_time\n curvature = value\n pressed = v1\n enabled = v2\n if (pressed == 1 or enabled == 0):\n last_bad_time = time\n if (time > last_bad_time + engage_delay):\n return value\n else:\n return 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"engaged curvature vehicle model","color":"#785ef0","custom_python":{"linked_source":"/controlsState/curvature","additional_sources":["/carState/steeringPressed","/carControl/enabled"],"globals_code":"engage_delay = 5\nlast_bad_time = -engage_delay","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2):\n global engage_delay, last_bad_time\n curvature = value\n pressed = v1\n enabled = v2\n if (pressed == 1 or enabled == 0):\n last_bad_time = time\n if (time > last_bad_time + engage_delay):\n return value\n else:\n return 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"engaged curvature yaw","color":"#0072b2","custom_python":{"linked_source":"/carControl/angularVelocity/2","additional_sources":["/carState/steeringPressed","/carControl/enabled","/carState/vEgo"],"globals_code":"engage_delay = 5\nlast_bad_time = -engage_delay","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3):\n global engage_delay, last_bad_time\n curvature = value / v3\n pressed = v1\n enabled = v2\n if (pressed == 1 or enabled == 0):\n last_bad_time = time\n if (time > last_bad_time + engage_delay):\n return curvature\n else:\n return 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"Roll [rad]","range":{"left":0.0,"right":631.038276,"top":0.166067,"bottom":-1.598381},"curves":[{"name":"/carControl/orientationNED/0","color":"#ffb000"}]},{"title":"Engaged [green] Steering Pressed [blue]","range":{"left":1.252984,"right":631.055584,"top":1.025,"bottom":-0.025},"curves":[{"name":"/selfdriveState/enabled","color":"#009e73"},{"name":"/carState/steeringPressed","color":"#0072b2"}]},{"title":"Steering Limited: Rate [orange] Saturated [magenta]","range":{"left":1.253354,"right":631.055584,"top":1.025,"bottom":-0.025},"curves":[{"name":"steering rate limited","color":"#ffb000","custom_python":{"linked_source":"/carControl/actuators/torque","additional_sources":["/carOutput/actuatorsOutput/torque","/carControl/actuators/steeringAngleDeg","/carOutput/actuatorsOutput/steeringAngleDeg"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3):\n return (np.abs(value - v1) > 0.001 or np.abs(v2 - v3) > 0.05) and 1 or 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"/controlsState/lateralControlState/pidState/saturated","color":"#dc267f"}]}]}},{"name":"Longitudinal","root":{"split":"vertical","sizes":[0.1875,0.1875,0.1875,0.1875,0.25],"children":[{"title":"Velocity [m/s] True [blue] Plan [green] Cruise [magenta]","range":{"left":0.0,"right":631.055584,"top":42.713492,"bottom":-1.041792},"curves":[{"name":"/carState/cruiseState/speed","color":"#dc267f"},{"name":"/longitudinalPlan/speeds/0","color":"#009e73"},{"name":"/carState/vEgo","color":"#0072b2"}]},{"title":"Acceleration [m/s^2] True [blue] Actuator [purple] Plan [green]","range":{"left":1.253354,"right":631.055759,"top":0.808303,"bottom":-1.213305},"curves":[{"name":"engaged_accel_plan","color":"#009e73","custom_python":{"linked_source":"/longitudinalPlan/accels/0","additional_sources":["/carState/brakePressed","/carState/gasPressed","/carControl/enabled"],"globals_code":"engage_delay = 5\nlast_bad_time = -engage_delay","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3):\n global engage_delay, last_bad_time\n accel = value\n brake = v1\n gas = v2\n enabled = v3\n if (brake != 0 or gas != 0 or enabled == 0):\n last_bad_time = time\n if (time > last_bad_time + engage_delay):\n return value\n else:\n return 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"engaged_accel_actuator","color":"#785ef0","custom_python":{"linked_source":"/carControl/actuators/accel","additional_sources":["/carState/brakePressed","/carState/gasPressed","/carControl/enabled"],"globals_code":"engage_delay = 5\nlast_bad_time = -engage_delay","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3):\n global engage_delay, last_bad_time\n accel = value\n brake = v1\n gas = v2\n enabled = v3\n if (brake != 0 or gas != 0 or enabled == 0):\n last_bad_time = time\n if (time > last_bad_time + engage_delay):\n return value\n else:\n return 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"engaged_accel_actual","color":"#0072b2","custom_python":{"linked_source":"/carState/aEgo","additional_sources":["/carState/brakePressed","/carState/gasPressed","/carControl/enabled"],"globals_code":"engage_delay = 5\nlast_bad_time = -engage_delay","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3):\n global engage_delay, last_bad_time\n accel = value\n brake = v1\n gas = v2\n enabled = v3\n if (brake != 0 or gas != 0 or enabled == 0):\n last_bad_time = time\n if (time > last_bad_time + engage_delay):\n return value\n else:\n return 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i])\nreturn __jotpluggler_result"}}]},{"title":"Pitch [rad]","range":{"left":0.0,"right":631.038276,"top":0.158854,"bottom":-0.594843},"curves":[{"name":"/carControl/orientationNED/1","color":"#ffb000"}]},{"title":"Engaged [green] Gas [orange] Brake [magenta]","range":{"left":1.253354,"right":631.055759,"top":1.025,"bottom":-0.025},"curves":[{"name":"/carControl/enabled","color":"#009e73"},{"name":"/carState/gasPressed","color":"#ffb000"},{"name":"/carState/brakePressed","color":"#dc267f"}]},{"title":"State [blue: off,pid,stop,start] Source [green: cruise,lead0,lead1,lead2,e2e]","range":{"left":1.25362,"right":631.055759,"top":5.125,"bottom":-0.125},"curves":[{"name":"/carControl/actuators/longControlState","color":"#0072b2"},{"name":"/longitudinalPlan/longitudinalPlanSource","color":"#009e73"}]}]}},{"name":"Lateral Debug","root":{"split":"vertical","sizes":[0.25,0.25,0.25,0.25],"children":[{"title":"Controller F [magenta] P [purple] I [blue]","range":{"left":0.0,"right":1.0,"top":1.0,"bottom":0.0},"curves":[{"name":"/controlsState/lateralControlState/pidState/f","color":"#f14cc1"},{"name":"/controlsState/lateralControlState/pidState/p","color":"#9467bd"},{"name":"/controlsState/lateralControlState/pidState/i","color":"#17becf"}]},{"title":"Driver Torque [blue] EPS Torque [green]","range":{"left":1.253354,"right":631.055584,"top":2690.99903,"bottom":-3450.198981},"curves":[{"name":"/carState/steeringTorqueEps","color":"#009e73"},{"name":"/carState/steeringTorque","color":"#0072b2"}]},{"title":"Engaged [green] Steering Pressed [blue]","range":{"left":1.253354,"right":631.055759,"top":1.025,"bottom":-0.025},"curves":[{"name":"/carControl/enabled","color":"#009e73"},{"name":"/carState/steeringPressed","color":"#0072b2"}]},{"title":"Steering Limited: Rate [orange] Saturated [magenta]","range":{"left":1.253354,"right":631.055584,"top":1.025,"bottom":-0.025},"curves":[{"name":"steering rate limited","color":"#ffb000","custom_python":{"linked_source":"/carControl/actuators/torque","additional_sources":["/carOutput/actuatorsOutput/torque","/carControl/actuators/steeringAngleDeg","/carOutput/actuatorsOutput/steeringAngleDeg"],"globals_code":"","function_code":"def __jotpluggler_eval_sample(time, value, v1, v2, v3):\n return (np.abs(value - v1) > 0.001 or np.abs(v2 - v3) > 0.05) and 1 or 0\n\n__jotpluggler_result = np.empty_like(value, dtype=np.float64)\nfor __jotpluggler_i in range(len(value)):\n __jotpluggler_result[__jotpluggler_i] = __jotpluggler_eval_sample(time[__jotpluggler_i], value[__jotpluggler_i], v1[__jotpluggler_i], v2[__jotpluggler_i], v3[__jotpluggler_i])\nreturn __jotpluggler_result"}},{"name":"/controlsState/lateralControlState/pidState/saturated","color":"#dc267f"}]}]}}]} diff --git a/tools/jotpluggler/layouts/ublox-debug.json b/tools/jotpluggler/layouts/ublox-debug.json new file mode 100644 index 00000000000..4509a192df1 --- /dev/null +++ b/tools/jotpluggler/layouts/ublox-debug.json @@ -0,0 +1 @@ +{"current_tab_index":0,"tabs":[{"name":"tab1","root":{"split":"vertical","sizes":[0.333333,0.333333,0.333333],"children":[{"title":"...","range":{"left":0.0,"right":134.825489,"top":4402341.574525,"bottom":-107369.555525},"curves":[{"name":"/gpsLocationExternal/horizontalAccuracy","color":"#1f77b4"}]},{"title":"...","range":{"left":0.0,"right":134.825489,"top":1.025,"bottom":-0.025},"curves":[{"name":"/gpsLocationExternal/flags","color":"#d62728"}]},{"title":"...","range":{"left":0.0,"right":134.825489,"top":6.15,"bottom":-0.15},"curves":[{"name":"/ubloxGnss/measurementReport/numMeas","color":"#1ac938"}]}]}}]} diff --git a/tools/jotpluggler/logs.cc b/tools/jotpluggler/logs.cc new file mode 100644 index 00000000000..4da1cbf501a --- /dev/null +++ b/tools/jotpluggler/logs.cc @@ -0,0 +1,419 @@ +#include "tools/jotpluggler/app.h" + +#include +#include + +namespace { + +struct LevelOption { + const char *label; + int value; +}; + +constexpr std::array LEVEL_OPTIONS = {{ + {"DEBUG", 10}, + {"INFO", 20}, + {"WARNING", 30}, + {"ERROR", 40}, + {"CRITICAL", 50}, +}}; +constexpr uint32_t ALL_LEVEL_MASK = (1u << LEVEL_OPTIONS.size()) - 1u; + +bool log_matches_search(const LogEntry &entry, std::string_view query) { + if (query.empty()) return true; + const std::string needle = lowercase_copy(query); + const auto contains = [&](std::string_view haystack) { + return lowercase_copy(haystack).find(needle) != std::string::npos; + }; + return contains(entry.message) || contains(entry.source) || contains(entry.func); +} + +std::vector collect_log_sources(const std::vector &logs) { + std::vector sources; + for (const LogEntry &entry : logs) { + if (entry.source.empty()) continue; + if (std::find(sources.begin(), sources.end(), entry.source) == sources.end()) { + sources.push_back(entry.source); + } + } + std::sort(sources.begin(), sources.end()); + return sources; +} + +std::vector filter_log_indices(const RouteData &route_data, const LogsUiState &logs_state) { + std::vector indices; + indices.reserve(route_data.logs.size()); + for (size_t i = 0; i < route_data.logs.size(); ++i) { + const LogEntry &entry = route_data.logs[i]; + int level_index = 0; + if (entry.level >= 50) { + level_index = 4; + } else if (entry.level >= 40) { + level_index = 3; + } else if (entry.level >= 30) { + level_index = 2; + } else if (entry.level >= 20) { + level_index = 1; + } + if ((logs_state.enabled_levels_mask & (1u << level_index)) == 0) { + continue; + } + if (!logs_state.all_sources) { + const auto it = std::find(logs_state.selected_sources.begin(), + logs_state.selected_sources.end(), + entry.source); + if (it == logs_state.selected_sources.end()) continue; + } + if (!log_matches_search(entry, logs_state.search)) continue; + indices.push_back(static_cast(i)); + } + return indices; +} + +int find_active_log_position(const RouteData &route_data, + const std::vector &filtered_indices, + double tracker_time) { + if (filtered_indices.empty()) return -1; + auto it = std::lower_bound(filtered_indices.begin(), filtered_indices.end(), tracker_time, + [&](int log_index, double tm) { + return route_data.logs[static_cast(log_index)].mono_time < tm; + }); + if (it == filtered_indices.begin()) return static_cast(std::distance(filtered_indices.begin(), it)); + if (it == filtered_indices.end()) return static_cast(filtered_indices.size()) - 1; + if (route_data.logs[static_cast(*it)].mono_time > tracker_time) { + --it; + } + return static_cast(std::distance(filtered_indices.begin(), it)); +} + +std::string format_route_time(double seconds) { + if (seconds < 0.0) { + seconds = 0.0; + } + const int minutes = static_cast(seconds / 60.0); + const double remaining = seconds - static_cast(minutes) * 60.0; + return util::string_format("%d:%06.3f", minutes, remaining); +} + +std::string format_boot_time(double seconds) { + return util::string_format("%.3f", seconds); +} + +std::string format_wall_time(double seconds) { + if (seconds <= 0.0) return "--"; + const time_t wall_seconds = static_cast(seconds); + std::tm wall_tm = {}; + localtime_r(&wall_seconds, &wall_tm); + const int millis = static_cast(std::llround((seconds - std::floor(seconds)) * 1000.0)); + return util::string_format("%02d:%02d:%02d.%03d", + wall_tm.tm_hour, wall_tm.tm_min, wall_tm.tm_sec, millis); +} + +std::string format_log_time(const LogEntry &entry, LogTimeMode mode) { + switch (mode) { + case LogTimeMode::Route: + return format_route_time(entry.mono_time); + case LogTimeMode::Boot: + return format_boot_time(entry.boot_time); + case LogTimeMode::WallClock: + return format_wall_time(entry.wall_time); + } + return format_route_time(entry.mono_time); +} + +const char *time_mode_label(LogTimeMode mode) { + switch (mode) { + case LogTimeMode::Route: return "Route"; + case LogTimeMode::Boot: return "Boot"; + case LogTimeMode::WallClock: return "Wall clock"; + } + return "Route"; +} + +std::string level_filter_label(uint32_t mask) { + if (mask == ALL_LEVEL_MASK) return "All levels"; + if (mask == 0b11110) return "INFO+"; + if (mask == 0b11100) return "WARNING+"; + if (mask == 0b11000) return "ERROR+"; + if (mask == 0b10000) return "CRITICAL"; + + int enabled_count = 0; + const char *last_label = "None"; + for (size_t i = 0; i < LEVEL_OPTIONS.size(); ++i) { + if ((mask & (1u << i)) == 0) { + continue; + } + ++enabled_count; + last_label = LEVEL_OPTIONS[i].label; + } + if (enabled_count == 0) return "None"; + if (enabled_count == 1) return last_label; + return "Custom"; +} + +std::string source_filter_label(const LogsUiState &logs_state, const std::vector &sources) { + if (logs_state.all_sources || logs_state.selected_sources.size() == sources.size()) { + return "All sources"; + } + if (logs_state.selected_sources.empty()) return "No sources"; + if (logs_state.selected_sources.size() == 1) return logs_state.selected_sources.front(); + return std::to_string(logs_state.selected_sources.size()) + " sources"; +} + +const char *level_label(const LogEntry &entry) { + if (entry.origin == LogOrigin::Alert) return "ALRT"; + if (entry.level >= 50) return "CRIT"; + if (entry.level >= 40) return "ERR"; + if (entry.level >= 30) return "WARN"; + if (entry.level >= 20) return "INFO"; + return "DBG"; +} + +ImVec4 level_text_color(const LogEntry &entry, bool active) { + if (active) return color_rgb(46, 54, 63); + if (entry.origin == LogOrigin::Alert) return color_rgb(50, 100, 200); + if (entry.level >= 50) return color_rgb(176, 26, 18); + if (entry.level >= 40) return color_rgb(200, 50, 40); + if (entry.level >= 30) return color_rgb(200, 130, 0); + if (entry.level >= 20) return color_rgb(80, 86, 94); + return color_rgb(126, 133, 141); +} + +ImU32 row_bg_color(const LogEntry &entry, bool active) { + if (active) return IM_COL32(80, 140, 210, 38); + return 0; +} + +void set_tracker_to_log(UiState *state, const LogEntry &entry) { + state->tracker_time = entry.mono_time; + state->has_tracker_time = true; + state->logs.last_auto_scroll_time = entry.mono_time; +} + +void draw_log_expansion_row(const LogEntry &entry) { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::TextUnformatted(""); + ImGui::TableSetColumnIndex(1); + ImGui::TextUnformatted(""); + ImGui::TableSetColumnIndex(2); + ImGui::TextUnformatted(entry.func.empty() ? "" : entry.func.c_str()); + ImGui::TableSetColumnIndex(3); + ImGui::PushStyleColor(ImGuiCol_Text, color_rgb(96, 104, 113)); + ImGui::TextWrapped("%s", entry.message.c_str()); + if (!entry.func.empty()) { + ImGui::TextWrapped("func: %s", entry.func.c_str()); + } + if (!entry.context.empty()) { + ImGui::TextWrapped("ctx: %s", entry.context.c_str()); + } + ImGui::PopStyleColor(); +} + +void draw_log_row(const LogEntry &entry, + int log_index, + bool active, + UiState *state) { + ImGui::PushID(log_index); + const ImU32 bg = row_bg_color(entry, active); + ImGui::TableNextRow(); + if (bg != 0) { + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0, bg); + } + + const std::string time_text = std::string(active ? "\xE2\x96\xB6 " : " ") + format_log_time(entry, state->logs.time_mode); + const auto clickable_text = [&](const char *id, const std::string &text, ImVec4 color = color_rgb(74, 80, 88)) { + ImGui::PushID(id); + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0)); + ImGui::PushStyleColor(ImGuiCol_HeaderActive, ImVec4(0, 0, 0, 0)); + const bool clicked = ImGui::Selectable(text.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick); + ImGui::PopStyleColor(4); + ImGui::PopID(); + return clicked; + }; + + bool clicked = false; + ImGui::TableSetColumnIndex(0); + app_push_mono_font(); + clicked = clickable_text("time", time_text); + app_pop_mono_font(); + + ImGui::TableSetColumnIndex(1); + clicked = clickable_text("level", level_label(entry), level_text_color(entry, active)) || clicked; + + ImGui::TableSetColumnIndex(2); + clicked = clickable_text("source", entry.source) || clicked; + + ImGui::TableSetColumnIndex(3); + clicked = clickable_text("message", entry.message) || clicked; + + if (clicked) { + set_tracker_to_log(state, entry); + state->logs.expanded_index = state->logs.expanded_index == log_index ? -1 : log_index; + } + ImGui::PopID(); +} + +} // namespace + +void draw_logs_tab(AppSession *session, UiState *state) { + LogsUiState &logs_state = state->logs; + const RouteData &route_data = session->route_data; + const RouteLoadSnapshot load = session->route_loader ? session->route_loader->snapshot() : RouteLoadSnapshot{}; + const bool loading_logs = load.active && route_data.logs.empty(); + const std::vector sources = collect_log_sources(route_data.logs); + + if (!logs_state.all_sources) { + logs_state.selected_sources.erase( + std::remove_if(logs_state.selected_sources.begin(), + logs_state.selected_sources.end(), + [&](const std::string &source) { + return std::find(sources.begin(), sources.end(), source) == sources.end(); + }), + logs_state.selected_sources.end()); + } + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(6.0f, 3.0f)); + ImGui::SetNextItemWidth(110.0f); + const std::string levels_label = level_filter_label(logs_state.enabled_levels_mask); + if (ImGui::BeginCombo("##logs_level", levels_label.c_str())) { + bool all_levels = logs_state.enabled_levels_mask == ALL_LEVEL_MASK; + if (ImGui::Checkbox("All levels", &all_levels)) { + logs_state.enabled_levels_mask = all_levels ? ALL_LEVEL_MASK : 0u; + } + ImGui::Separator(); + for (size_t i = 0; i < LEVEL_OPTIONS.size(); ++i) { + bool enabled = (logs_state.enabled_levels_mask & (1u << i)) != 0; + if (ImGui::Checkbox(LEVEL_OPTIONS[i].label, &enabled)) { + if (enabled) { + logs_state.enabled_levels_mask |= (1u << i); + } else { + logs_state.enabled_levels_mask &= ~(1u << i); + } + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + + ImGui::SetNextItemWidth(150.0f); + input_text_with_hint_string("##logs_search", "Search...", &logs_state.search); + ImGui::SameLine(); + + const std::string sources_label = source_filter_label(logs_state, sources); + ImGui::SetNextItemWidth(180.0f); + if (ImGui::BeginCombo("##logs_source", sources_label.c_str())) { + bool all_sources = logs_state.all_sources; + if (ImGui::Checkbox("All sources", &all_sources)) { + logs_state.all_sources = all_sources; + if (logs_state.all_sources) { + logs_state.selected_sources.clear(); + } else { + logs_state.selected_sources = sources; + } + } + ImGui::Separator(); + for (const std::string &source : sources) { + bool enabled = logs_state.all_sources + || std::find(logs_state.selected_sources.begin(), logs_state.selected_sources.end(), source) != logs_state.selected_sources.end(); + if (ImGui::Checkbox(source.c_str(), &enabled)) { + if (logs_state.all_sources) { + logs_state.all_sources = false; + logs_state.selected_sources = sources; + } + auto it = std::find(logs_state.selected_sources.begin(), logs_state.selected_sources.end(), source); + if (enabled) { + if (it == logs_state.selected_sources.end()) { + logs_state.selected_sources.push_back(source); + } + } else if (it != logs_state.selected_sources.end()) { + logs_state.selected_sources.erase(it); + } + if (logs_state.selected_sources.size() == sources.size()) { + logs_state.all_sources = true; + logs_state.selected_sources.clear(); + } + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + + ImGui::SetNextItemWidth(110.0f); + if (ImGui::BeginCombo("##logs_time_mode", time_mode_label(logs_state.time_mode))) { + for (LogTimeMode mode : {LogTimeMode::Route, LogTimeMode::Boot, LogTimeMode::WallClock}) { + const bool selected = logs_state.time_mode == mode; + if (ImGui::Selectable(time_mode_label(mode), selected)) { + logs_state.time_mode = mode; + } + } + ImGui::EndCombo(); + } + + const std::vector filtered_indices = filter_log_indices(route_data, logs_state); + const bool have_tracker = state->has_tracker_time && !filtered_indices.empty(); + const int active_pos = have_tracker ? find_active_log_position(route_data, filtered_indices, state->tracker_time) : -1; + + ImGui::SameLine(); + ImGui::SetCursorPosX(std::max(ImGui::GetCursorPosX(), ImGui::GetWindowContentRegionMax().x - 110.0f)); + ImGui::Text("%zu / %zu", filtered_indices.size(), route_data.logs.size()); + ImGui::PopStyleVar(); + + if (route_data.logs.empty()) { + ImGui::Spacing(); + ImGui::PushStyleColor(ImGuiCol_Text, color_rgb(116, 124, 133)); + ImGui::TextWrapped("%s", loading_logs ? "Loading logs..." : "No text logs available for this route."); + ImGui::PopStyleColor(); + return; + } + + if (ImGui::BeginChild("##logs_table_child", ImVec2(0.0f, 0.0f), false)) { + if (have_tracker && std::abs(logs_state.last_auto_scroll_time - state->tracker_time) > 1.0e-6) { + const float row_height = ImGui::GetTextLineHeightWithSpacing() + 6.0f; + const float visible_h = std::max(1.0f, ImGui::GetWindowHeight()); + const float target = std::max(0.0f, static_cast(active_pos) * row_height - visible_h * 0.45f); + ImGui::SetScrollY(target); + logs_state.last_auto_scroll_time = state->tracker_time; + } + + if (ImGui::BeginTable("##logs_table", + 4, + ImGuiTableFlags_BordersInnerV | + ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable | + ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("Time", ImGuiTableColumnFlags_WidthFixed, 120.0f); + ImGui::TableSetupColumn("Level", ImGuiTableColumnFlags_WidthFixed, 72.0f); + ImGui::TableSetupColumn("Source", ImGuiTableColumnFlags_WidthFixed, 180.0f); + ImGui::TableSetupColumn("Message", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableHeadersRow(); + + const bool use_clipper = logs_state.expanded_index < 0; + if (use_clipper) { + ImGuiListClipper clipper; + clipper.Begin(static_cast(filtered_indices.size())); + while (clipper.Step()) { + for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; ++i) { + const int log_index = filtered_indices[static_cast(i)]; + const LogEntry &entry = route_data.logs[static_cast(log_index)]; + draw_log_row(entry, log_index, i == active_pos, state); + } + } + } else { + for (int i = 0; i < static_cast(filtered_indices.size()); ++i) { + const int log_index = filtered_indices[static_cast(i)]; + const LogEntry &entry = route_data.logs[static_cast(log_index)]; + draw_log_row(entry, log_index, i == active_pos, state); + if (logs_state.expanded_index == log_index) { + draw_log_expansion_row(entry); + } + } + } + + ImGui::EndTable(); + } + } + ImGui::EndChild(); +} diff --git a/tools/jotpluggler/main.cc b/tools/jotpluggler/main.cc new file mode 100644 index 00000000000..22bc29664c2 --- /dev/null +++ b/tools/jotpluggler/main.cc @@ -0,0 +1,126 @@ +#include +#include + +#include "tools/jotpluggler/app.h" + +namespace { + +constexpr const char *DEMO_ROUTE = "5beb9b58bd12b691/0000010a--a51155e496"; + +void print_usage(const char *argv0) { + std::cerr + << "Usage: " << argv0 << " [--layout ] [options] [route]\n" + << "\n" + << "Options:\n" + << " --demo\n" + << " --data-dir \n" + << " --stream\n" + << " --address \n" + << " --buffer-seconds \n" + << " --width \n" + << " --height \n" + << " --output \n" + << " --show\n" + << " --sync-load\n" + << "\n" + << "Examples:\n" + << " " << argv0 << "\n" + << " " << argv0 << " --demo\n" + << " " << argv0 << " --layout longitudinal --demo\n" + << " " << argv0 << " --layout longitudinal --demo --output /tmp/longitudinal.png\n" + << " " << argv0 << " --stream --show\n" + << " " << argv0 << " --stream --address 192.168.60.52 --buffer-seconds 45 --show\n"; +} + +bool parse_int(const char *value, int *out) { + char *end = nullptr; + const long parsed = std::strtol(value, &end, 10); + if (end == nullptr || *end != '\0') return false; + *out = static_cast(parsed); + return true; +} + +bool parse_double(const char *value, double *out) { + char *end = nullptr; + const double parsed = std::strtod(value, &end); + if (end == nullptr || *end != '\0') return false; + *out = parsed; + return true; +} + +} // namespace + +int main(int argc, char *argv[]) { + Options options; + for (int i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + const auto require_value = [&](const char *flag) -> const char * { + if (i + 1 >= argc) { + std::cerr << "Missing value for " << flag << "\n"; + print_usage(argv[0]); + std::exit(2); + } + return argv[++i]; + }; + + if (arg == "--layout") { + options.layout = require_value("--layout"); + } else if (arg == "--demo") { + options.route_name = DEMO_ROUTE; + } else if (arg == "--data-dir") { + options.data_dir = require_value("--data-dir"); + } else if (arg == "--stream") { + options.stream = true; + } else if (arg == "--address") { + options.stream_address = require_value("--address"); + } else if (arg == "--buffer-seconds") { + if (!parse_double(require_value("--buffer-seconds"), &options.stream_buffer_seconds)) { + std::cerr << "Invalid buffer seconds\n"; + return 2; + } + } else if (arg == "--output") { + options.output_path = require_value("--output"); + } else if (arg == "--width") { + if (!parse_int(require_value("--width"), &options.width)) { + std::cerr << "Invalid width\n"; + return 2; + } + } else if (arg == "--height") { + if (!parse_int(require_value("--height"), &options.height)) { + std::cerr << "Invalid height\n"; + return 2; + } + } else if (arg == "--show") { + options.show = true; + } else if (arg == "--sync-load") { + options.sync_load = true; + } else if (arg == "--help" || arg == "-h") { + print_usage(argv[0]); + return 0; + } else if (!arg.empty() && arg[0] != '-' && options.route_name.empty()) { + options.route_name = arg; + } else { + std::cerr << "Unknown argument: " << arg << "\n"; + print_usage(argv[0]); + return 2; + } + } + + if (options.output_path.empty() && !options.show) { + options.show = true; + } + if (options.width <= 0 || options.height <= 0) { + std::cerr << "Width and height must be positive\n"; + return 2; + } + if (options.stream && !options.route_name.empty()) { + std::cerr << "Route/file mode and --stream are mutually exclusive\n"; + return 2; + } + if (options.stream_buffer_seconds <= 0.0) { + std::cerr << "Buffer seconds must be positive\n"; + return 2; + } + + return run(options); +} diff --git a/tools/jotpluggler/map.cc b/tools/jotpluggler/map.cc new file mode 100644 index 00000000000..8725908ea02 --- /dev/null +++ b/tools/jotpluggler/map.cc @@ -0,0 +1,1328 @@ +#include "tools/jotpluggler/app.h" +#include "tools/jotpluggler/common.h" +#include "tools/jotpluggler/map.h" + +#include + +extern "C" { +#include +} + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/util.h" +#include "third_party/json11/json11.hpp" + +namespace fs = std::filesystem; + +namespace { + +constexpr int MAP_MIN_ZOOM = 1; +constexpr int MAP_MAX_ZOOM = 18; +constexpr int MAP_SINGLE_POINT_MIN_ZOOM = 14; +constexpr float MAP_WHEEL_ZOOM_STEP = 0.25f; +constexpr double MAP_TRACE_PAD_FRAC = 0.45; +constexpr double MAP_TRACE_MIN_LAT_PAD = 0.01; +constexpr double MAP_BOUNDS_GRID = 0.005; +constexpr double MAP_CORRIDOR_LAT_PAD = 0.010; +constexpr double MAP_CORRIDOR_MIN_STEP_S = 1.5; +constexpr size_t MAP_CORRIDOR_MAX_BOXES = 36; +constexpr float MAP_INITIAL_FIT_FILL = 0.88f; +constexpr float MAP_MIN_ZOOM_FILL = 0.98f; +constexpr float MAP_EDGE_FADE_FRAC = 0.28f; +constexpr const char *MAP_QUERY_ENDPOINTS[] = { + "https://overpass-api.de/api/interpreter", + "https://overpass.private.coffee/api/interpreter", +}; +struct GeoPoint { + double lat = 0.0; + double lon = 0.0; +}; + +struct ProjectedPoint { + float x = 0.0f; + float y = 0.0f; +}; + +struct ProjectedBounds { + float min_x = 0.0f; + float min_y = 0.0f; + float max_x = 0.0f; + float max_y = 0.0f; + + bool valid() const { + return max_x >= min_x && max_y >= min_y; + } +}; + +enum class RoadClass : uint8_t { + Motorway, + Primary, + Secondary, + Local, +}; + +struct RoadFeature { + RoadClass road_class = RoadClass::Local; + ProjectedBounds bounds; + std::vector points; +}; + +struct WaterLineFeature { + ProjectedBounds bounds; + std::vector points; +}; + +struct WaterPolygonFeature { + ProjectedBounds bounds; + std::vector ring; +}; + +} // namespace + +struct RouteBasemap { + std::string key; + GeoBounds bounds; + ProjectedBounds projected_bounds; + std::vector roads; + std::vector water_lines; + std::vector water_polygons; +}; + +struct MapRequestSpec { + std::string key; + GeoBounds bounds; + std::string query; +}; + +namespace { + +double lon_to_world_x(double lon, double zoom) { + return (lon + 180.0) / 360.0 * 256.0 * std::exp2(zoom); +} + +double lat_to_world_y(double lat, double zoom) { + const double lat_rad = lat * M_PI / 180.0; + return (1.0 - std::log(std::tan(lat_rad) + 1.0 / std::cos(lat_rad)) / M_PI) / 2.0 * 256.0 * std::exp2(zoom); +} + +double world_x_to_lon(double x, double zoom) { + return x / std::exp2(zoom) / 256.0 * 360.0 - 180.0; +} + +double world_y_to_lat(double y, double zoom) { + const double n = M_PI - (2.0 * M_PI * (y / std::exp2(zoom))) / 256.0; + return 180.0 / M_PI * std::atan(std::sinh(n)); +} + +double map_trace_center_lat(const GpsTrace &trace) { + return (trace.min_lat + trace.max_lat) * 0.5; +} + +double map_trace_center_lon(const GpsTrace &trace) { + return (trace.min_lon + trace.max_lon) * 0.5; +} + +double clamp_lat(double lat) { + return std::clamp(lat, -85.0, 85.0); +} + +double clamp_lon(double lon) { + return std::clamp(lon, -179.999, 179.999); +} + +float project_lon0(double lon) { + return static_cast((lon + 180.0) / 360.0 * 256.0); +} + +float project_lat0(double lat) { + const double lat_rad = lat * M_PI / 180.0; + return static_cast((1.0 - std::log(std::tan(lat_rad) + 1.0 / std::cos(lat_rad)) / M_PI) / 2.0 * 256.0); +} + +double cos_lat_scale(double lat) { + return std::max(0.2, std::cos(lat * M_PI / 180.0)); +} + +double quantize_down(double value, double step) { + return std::floor(value / step) * step; +} + +double quantize_up(double value, double step) { + return std::ceil(value / step) * step; +} + +ProjectedBounds compute_projected_bounds(const std::vector &points) { + ProjectedBounds bounds; + if (points.empty()) { + return bounds; + } + bounds.min_x = bounds.max_x = points.front().x; + bounds.min_y = bounds.max_y = points.front().y; + for (const ProjectedPoint &point : points) { + bounds.min_x = std::min(bounds.min_x, point.x); + bounds.max_x = std::max(bounds.max_x, point.x); + bounds.min_y = std::min(bounds.min_y, point.y); + bounds.max_y = std::max(bounds.max_y, point.y); + } + return bounds; +} + +ProjectedBounds project_bounds0(const GeoBounds &bounds) { + if (!bounds.valid()) { + return {}; + } + return ProjectedBounds{ + .min_x = project_lon0(bounds.west), + .min_y = project_lat0(bounds.north), + .max_x = project_lon0(bounds.east), + .max_y = project_lat0(bounds.south), + }; +} + +bool feature_intersects_view(const ProjectedBounds &feature, const ProjectedBounds &view, float zoom_scale) { + const float min_x = feature.min_x * zoom_scale; + const float max_x = feature.max_x * zoom_scale; + const float min_y = feature.min_y * zoom_scale; + const float max_y = feature.max_y * zoom_scale; + return !(max_x < view.min_x || min_x > view.max_x + || max_y < view.min_y || min_y > view.max_y); +} + +GeoBounds requested_bounds_for_trace(const GpsTrace &trace) { + if (trace.points.empty()) { + return {}; + } + const double center_lat = map_trace_center_lat(trace); + const double lat_span = std::max(trace.max_lat - trace.min_lat, 0.002); + const double lon_span = std::max(trace.max_lon - trace.min_lon, 0.002 / cos_lat_scale(center_lat)); + const double lat_pad = std::max(lat_span * MAP_TRACE_PAD_FRAC, MAP_TRACE_MIN_LAT_PAD); + const double lon_pad = std::max(lon_span * MAP_TRACE_PAD_FRAC, MAP_TRACE_MIN_LAT_PAD / cos_lat_scale(center_lat)); + + GeoBounds bounds; + bounds.south = clamp_lat(quantize_down(trace.min_lat - lat_pad, MAP_BOUNDS_GRID)); + bounds.north = clamp_lat(quantize_up(trace.max_lat + lat_pad, MAP_BOUNDS_GRID)); + bounds.west = clamp_lon(quantize_down(trace.min_lon - lon_pad, MAP_BOUNDS_GRID)); + bounds.east = clamp_lon(quantize_up(trace.max_lon + lon_pad, MAP_BOUNDS_GRID)); + return bounds; +} + +GeoBounds merge_bounds(const GeoBounds &a, const GeoBounds &b) { + if (!a.valid()) return b; + if (!b.valid()) return a; + return GeoBounds{ + .south = std::min(a.south, b.south), + .west = std::min(a.west, b.west), + .north = std::max(a.north, b.north), + .east = std::max(a.east, b.east), + }; +} + +bool bounds_overlap_or_touch(const GeoBounds &a, const GeoBounds &b) { + return !(a.east < b.west || b.east < a.west || a.north < b.south || b.north < a.south); +} + +std::vector corridor_boxes_for_trace(const GpsTrace &trace) { + std::vector boxes; + if (trace.points.empty()) { + return boxes; + } + + const double center_lat = map_trace_center_lat(trace); + const double lon_pad = MAP_CORRIDOR_LAT_PAD / cos_lat_scale(center_lat); + const double total_time = trace.points.back().time - trace.points.front().time; + const double target_boxes = std::min(MAP_CORRIDOR_MAX_BOXES, std::max(8.0, total_time / MAP_CORRIDOR_MIN_STEP_S)); + const size_t stride = std::max(1, static_cast(std::ceil(trace.points.size() / target_boxes))); + + auto add_box = [&](double lat, double lon) { + GeoBounds box{ + .south = clamp_lat(quantize_down(lat - MAP_CORRIDOR_LAT_PAD, MAP_BOUNDS_GRID)), + .west = clamp_lon(quantize_down(lon - lon_pad, MAP_BOUNDS_GRID)), + .north = clamp_lat(quantize_up(lat + MAP_CORRIDOR_LAT_PAD, MAP_BOUNDS_GRID)), + .east = clamp_lon(quantize_up(lon + lon_pad, MAP_BOUNDS_GRID)), + }; + if (!box.valid()) { + return; + } + for (GeoBounds &existing : boxes) { + if (bounds_overlap_or_touch(existing, box)) { + existing = merge_bounds(existing, box); + return; + } + } + boxes.push_back(box); + }; + + add_box(trace.points.front().lat, trace.points.front().lon); + for (size_t i = stride; i < trace.points.size(); i += stride) { + add_box(trace.points[i].lat, trace.points[i].lon); + } + add_box(trace.points.back().lat, trace.points.back().lon); + + bool merged = true; + while (merged) { + merged = false; + for (size_t i = 0; i < boxes.size() && !merged; ++i) { + for (size_t j = i + 1; j < boxes.size(); ++j) { + if (bounds_overlap_or_touch(boxes[i], boxes[j])) { + boxes[i] = merge_bounds(boxes[i], boxes[j]); + boxes.erase(boxes.begin() + static_cast(j)); + merged = true; + break; + } + } + } + } + return boxes; +} + +ProjectedBounds view_bounds(double top_left_x, double top_left_y, float width, float height) { + return ProjectedBounds{ + .min_x = static_cast(top_left_x), + .min_y = static_cast(top_left_y), + .max_x = static_cast(top_left_x + width), + .max_y = static_cast(top_left_y + height), + }; +} + +int fit_map_zoom_for_bounds(const GeoBounds &bounds, float width, float height, float fill_fraction) { + if (!bounds.valid()) { + return MAP_MIN_ZOOM; + } + const double max_width = std::max(1.0f, width * fill_fraction); + const double max_height = std::max(1.0f, height * fill_fraction); + for (int z = MAP_MAX_ZOOM; z >= MAP_MIN_ZOOM; --z) { + const double pixel_width = std::abs(lon_to_world_x(bounds.east, z) - lon_to_world_x(bounds.west, z)); + const double pixel_height = std::abs(lat_to_world_y(bounds.south, z) - lat_to_world_y(bounds.north, z)); + if (pixel_width <= max_width && pixel_height <= max_height) { + return z; + } + } + return MAP_MIN_ZOOM; +} + +int fit_map_zoom_for_trace(const GpsTrace &trace, float width, float height) { + return fit_map_zoom_for_bounds(requested_bounds_for_trace(trace), width, height, MAP_INITIAL_FIT_FILL); +} + +int minimum_allowed_map_zoom(const GeoBounds &bounds, const GpsTrace &trace, ImVec2 size) { + if (trace.points.size() <= 1) { + return MAP_SINGLE_POINT_MIN_ZOOM; + } + const int fit_zoom = fit_map_zoom_for_bounds(bounds.valid() ? bounds : requested_bounds_for_trace(trace), + size.x, size.y, MAP_MIN_ZOOM_FILL); + return std::clamp(fit_zoom, MAP_MIN_ZOOM, MAP_MAX_ZOOM); +} + +std::optional interpolate_gps(const GpsTrace &trace, double time_value) { + if (trace.points.empty()) { + return std::nullopt; + } + if (time_value <= trace.points.front().time) { + return trace.points.front(); + } + if (time_value >= trace.points.back().time) { + return trace.points.back(); + } + auto upper = std::lower_bound(trace.points.begin(), trace.points.end(), time_value, + [](const GpsPoint &point, double target) { + return point.time < target; + }); + if (upper == trace.points.begin()) { + return trace.points.front(); + } + const GpsPoint &p1 = *upper; + const GpsPoint &p0 = *(upper - 1); + const double dt = p1.time - p0.time; + if (dt <= 1.0e-9) { + return p0; + } + const double alpha = (time_value - p0.time) / dt; + GpsPoint out; + out.time = time_value; + out.lat = p0.lat + (p1.lat - p0.lat) * alpha; + out.lon = p0.lon + (p1.lon - p0.lon) * alpha; + out.bearing = static_cast(p0.bearing + (p1.bearing - p0.bearing) * alpha); + out.type = alpha < 0.5 ? p0.type : p1.type; + return out; +} + +ImU32 map_timeline_color(TimelineEntry::Type type, float alpha = 1.0f) { + return timeline_entry_color(type, alpha, {140, 150, 165}); +} + +ImVec2 gps_to_screen(double lat, double lon, double zoom, double top_left_x, double top_left_y, const ImVec2 &rect_min) { + return ImVec2(rect_min.x + static_cast(lon_to_world_x(lon, zoom) - top_left_x), + rect_min.y + static_cast(lat_to_world_y(lat, zoom) - top_left_y)); +} + +bool point_in_rect_with_margin(const ImVec2 &point, const ImVec2 &rect_min, const ImVec2 &rect_max, + float margin_fraction) { + const float width = rect_max.x - rect_min.x; + const float height = rect_max.y - rect_min.y; + const float margin_x = width * margin_fraction; + const float margin_y = height * margin_fraction; + return point.x >= rect_min.x + margin_x && point.x <= rect_max.x - margin_x + && point.y >= rect_min.y + margin_y && point.y <= rect_max.y - margin_y; +} + +void draw_car_marker(ImDrawList *draw_list, ImVec2 center, float bearing_deg, ImU32 color, float size) { + const float rad = bearing_deg * static_cast(M_PI / 180.0); + const ImVec2 forward(std::sin(rad), -std::cos(rad)); + const ImVec2 perp(-forward.y, forward.x); + const ImVec2 tip(center.x + forward.x * size, center.y + forward.y * size); + const ImVec2 base(center.x - forward.x * size * 0.45f, center.y - forward.y * size * 0.45f); + const ImVec2 left(base.x + perp.x * size * 0.6f, base.y + perp.y * size * 0.6f); + const ImVec2 right(base.x - perp.x * size * 0.6f, base.y - perp.y * size * 0.6f); + draw_list->AddTriangleFilled(tip, left, right, color); + draw_list->AddTriangle(tip, left, right, IM_COL32(255, 255, 255, 210), 2.0f); +} + +bool is_convex_ring(const std::vector &points) { + if (points.size() < 4) { + return false; + } + float sign = 0.0f; + const size_t n = points.size(); + for (size_t i = 0; i < n; ++i) { + const ImVec2 &a = points[i]; + const ImVec2 &b = points[(i + 1) % n]; + const ImVec2 &c = points[(i + 2) % n]; + const float cross = (b.x - a.x) * (c.y - b.y) - (b.y - a.y) * (c.x - b.x); + if (std::abs(cross) < 1.0e-3f) { + continue; + } + if (sign == 0.0f) { + sign = cross; + } else if ((cross > 0.0f) != (sign > 0.0f)) { + return false; + } + } + return sign != 0.0f; +} + +uint64_t fnv1a64(std::string_view text) { + uint64_t value = 1469598103934665603ULL; + for (unsigned char c : text) { + value ^= static_cast(c); + value *= 1099511628211ULL; + } + return value; +} + +fs::path basemap_cache_root() { + const char *home = std::getenv("HOME"); + fs::path root = home != nullptr ? fs::path(home) / ".comma" : fs::temp_directory_path(); + root /= "jotpluggler_vector_map"; + fs::create_directories(root); + return root; +} + +std::string bounds_key(const GeoBounds &bounds) { + return util::string_format("v2_%.5f_%.5f_%.5f_%.5f", + bounds.south, bounds.west, bounds.north, bounds.east); +} + +fs::path basemap_cache_path(const std::string &key) { + const uint64_t hash = fnv1a64(key); + return basemap_cache_root() / util::string_format("%016llx.bin.zst", static_cast(hash)); +} + +uint64_t cache_directory_size_bytes() { + uint64_t total = 0; + const fs::path root = basemap_cache_root(); + if (!fs::exists(root)) { + return 0; + } + for (const fs::directory_entry &entry : fs::directory_iterator(root)) { + if (entry.is_regular_file()) { + total += static_cast(entry.file_size()); + } + } + return total; +} + +size_t cache_directory_file_count() { + size_t count = 0; + const fs::path root = basemap_cache_root(); + if (!fs::exists(root)) { + return 0; + } + for (const fs::directory_entry &entry : fs::directory_iterator(root)) { + if (entry.is_regular_file()) { + ++count; + } + } + return count; +} + +void clear_cache_directory() { + const fs::path root = basemap_cache_root(); + if (!fs::exists(root)) { + return; + } + for (const fs::directory_entry &entry : fs::directory_iterator(root)) { + if (entry.is_regular_file()) { + std::error_code ec; + fs::remove(entry.path(), ec); + } + } +} + +std::string percent_encode(std::string_view text) { + std::string out; + out.reserve(text.size() * 3); + for (unsigned char c : text) { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') + || c == '-' || c == '_' || c == '.' || c == '~') { + out.push_back(static_cast(c)); + } else { + out += util::string_format("%%%02X", static_cast(c)); + } + } + return out; +} + +std::string bbox_string(const GeoBounds &bounds) { + return util::string_format("%.6f,%.6f,%.6f,%.6f", + bounds.south, bounds.west, bounds.north, bounds.east); +} + +MapRequestSpec build_request_for_trace(const GpsTrace &trace) { + const std::vector boxes = corridor_boxes_for_trace(trace); + GeoBounds union_bounds; + std::string query = "[out:json][timeout:25];("; + for (const GeoBounds &box : boxes) { + union_bounds = merge_bounds(union_bounds, box); + const std::string bbox = bbox_string(box); + query += "way[\"highway\"][\"area\"!=\"yes\"](" + bbox + ");"; + query += "way[\"natural\"=\"water\"](" + bbox + ");"; + query += "way[\"waterway\"=\"riverbank\"](" + bbox + ");"; + query += "way[\"waterway\"~\"river|stream|canal\"](" + bbox + ");"; + } + query += ");out tags geom;"; + + std::string key = bounds_key(union_bounds); + key += ":"; + key += std::to_string(boxes.size()); + for (const GeoBounds &box : boxes) { + key += ":"; + key += bbox_string(box); + } + return MapRequestSpec{ + .key = std::move(key), + .bounds = union_bounds, + .query = std::move(query), + }; +} + +bool fetch_overpass_json(std::string_view query, std::string *out) { + const std::string body = std::string("data=") + percent_encode(query); + for (const char *endpoint : MAP_QUERY_ENDPOINTS) { + const std::string command = "curl -fsSL --compressed --connect-timeout 8 --max-time 30 " + "-A 'jotpluggler-vector-map/1.0' " + "-H 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8' " + "--data-raw " + shell_quote(body) + " " + + shell_quote(endpoint); + const std::string response = util::check_output(command); + if (!response.empty() && response.front() == '{') { + *out = response; + return true; + } + } + return false; +} + +std::string load_overpass_json(std::string_view query) { + std::string response; + if (!fetch_overpass_json(query, &response)) { + return {}; + } + return response; +} + +template +void append_pod(std::string *out, const T &value) { + const size_t start = out->size(); + out->resize(start + sizeof(T)); + std::memcpy(out->data() + start, &value, sizeof(T)); +} + +template +bool read_pod(std::string_view data, size_t *offset, T *value) { + if (*offset + sizeof(T) > data.size()) { + return false; + } + std::memcpy(value, data.data() + *offset, sizeof(T)); + *offset += sizeof(T); + return true; +} + +void append_points(std::string *out, const std::vector &points) { + const uint32_t count = static_cast(points.size()); + append_pod(out, count); + for (const ProjectedPoint &point : points) { + append_pod(out, point.x); + append_pod(out, point.y); + } +} + +bool read_points(std::string_view data, size_t *offset, std::vector *points) { + uint32_t count = 0; + if (!read_pod(data, offset, &count)) { + return false; + } + points->clear(); + points->reserve(count); + for (uint32_t i = 0; i < count; ++i) { + ProjectedPoint point; + if (!read_pod(data, offset, &point.x) || !read_pod(data, offset, &point.y)) { + return false; + } + points->push_back(point); + } + return true; +} + +std::string serialize_basemap_payload(const RouteBasemap &basemap) { + std::string raw; + raw.reserve(1024 + basemap.roads.size() * 48); + raw.append("JBM2", 4); + append_pod(&raw, basemap.bounds.south); + append_pod(&raw, basemap.bounds.west); + append_pod(&raw, basemap.bounds.north); + append_pod(&raw, basemap.bounds.east); + + const uint32_t road_count = static_cast(basemap.roads.size()); + const uint32_t water_line_count = static_cast(basemap.water_lines.size()); + const uint32_t water_polygon_count = static_cast(basemap.water_polygons.size()); + append_pod(&raw, road_count); + append_pod(&raw, water_line_count); + append_pod(&raw, water_polygon_count); + + for (const RoadFeature &road : basemap.roads) { + const uint8_t kind = static_cast(road.road_class); + append_pod(&raw, kind); + append_points(&raw, road.points); + } + for (const WaterLineFeature &water : basemap.water_lines) { + append_points(&raw, water.points); + } + for (const WaterPolygonFeature &water : basemap.water_polygons) { + append_points(&raw, water.ring); + } + return raw; +} + +std::optional deserialize_basemap_payload(std::string_view raw, const std::string &key) { + if (!util::starts_with(std::string(raw), "JBM2")) { + return std::nullopt; + } + size_t offset = 4; + RouteBasemap basemap; + basemap.key = key; + if (!read_pod(raw, &offset, &basemap.bounds.south) + || !read_pod(raw, &offset, &basemap.bounds.west) + || !read_pod(raw, &offset, &basemap.bounds.north) + || !read_pod(raw, &offset, &basemap.bounds.east)) { + return std::nullopt; + } + basemap.projected_bounds = project_bounds0(basemap.bounds); + + uint32_t road_count = 0; + uint32_t water_line_count = 0; + uint32_t water_polygon_count = 0; + if (!read_pod(raw, &offset, &road_count) + || !read_pod(raw, &offset, &water_line_count) + || !read_pod(raw, &offset, &water_polygon_count)) { + return std::nullopt; + } + + basemap.roads.reserve(road_count); + for (uint32_t i = 0; i < road_count; ++i) { + uint8_t kind = 0; + std::vector points; + if (!read_pod(raw, &offset, &kind) || !read_points(raw, &offset, &points)) { + return std::nullopt; + } + basemap.roads.push_back(RoadFeature{ + .road_class = static_cast(kind), + .bounds = compute_projected_bounds(points), + .points = std::move(points), + }); + } + + basemap.water_lines.reserve(water_line_count); + for (uint32_t i = 0; i < water_line_count; ++i) { + std::vector points; + if (!read_points(raw, &offset, &points)) { + return std::nullopt; + } + basemap.water_lines.push_back(WaterLineFeature{ + .bounds = compute_projected_bounds(points), + .points = std::move(points), + }); + } + + basemap.water_polygons.reserve(water_polygon_count); + for (uint32_t i = 0; i < water_polygon_count; ++i) { + std::vector ring; + if (!read_points(raw, &offset, &ring)) { + return std::nullopt; + } + basemap.water_polygons.push_back(WaterPolygonFeature{ + .bounds = compute_projected_bounds(ring), + .ring = std::move(ring), + }); + } + return basemap; +} + +bool save_compressed_basemap(const fs::path &path, const RouteBasemap &basemap) { + const std::string raw = serialize_basemap_payload(basemap); + const size_t bound = ZSTD_compressBound(raw.size()); + std::string compressed(bound, '\0'); + const size_t size = ZSTD_compress(compressed.data(), compressed.size(), raw.data(), raw.size(), 5); + if (ZSTD_isError(size)) { + return false; + } + compressed.resize(size); + ensure_parent_dir(path); + const std::string path_string = path.string(); + return util::write_file(path_string.c_str(), compressed.data(), compressed.size(), O_WRONLY | O_CREAT | O_TRUNC) == 0; +} + +std::optional load_compressed_basemap(const fs::path &path, const std::string &key) { + const std::string compressed = util::read_file(path.string()); + if (compressed.empty()) { + return std::nullopt; + } + const unsigned long long raw_size = ZSTD_getFrameContentSize(compressed.data(), compressed.size()); + if (raw_size == ZSTD_CONTENTSIZE_ERROR || raw_size == ZSTD_CONTENTSIZE_UNKNOWN || raw_size > (1ULL << 31)) { + return std::nullopt; + } + std::string raw(static_cast(raw_size), '\0'); + const size_t actual = ZSTD_decompress(raw.data(), raw.size(), compressed.data(), compressed.size()); + if (ZSTD_isError(actual)) { + return std::nullopt; + } + raw.resize(actual); + return deserialize_basemap_payload(raw, key); +} + +std::vector geometry_points(const json11::Json &geometry_json) { + std::vector points; + const auto items = geometry_json.array_items(); + points.reserve(items.size()); + for (const json11::Json &point : items) { + if (!point["lat"].is_number() || !point["lon"].is_number()) { + continue; + } + points.push_back(ProjectedPoint{ + .x = project_lon0(point["lon"].number_value()), + .y = project_lat0(point["lat"].number_value()), + }); + } + return points; +} + +std::optional classify_road(std::string_view highway) { + if (highway == "motorway" || highway == "motorway_link" || highway == "trunk" || highway == "trunk_link") { + return RoadClass::Motorway; + } + if (highway == "primary" || highway == "primary_link") { + return RoadClass::Primary; + } + if (highway == "secondary" || highway == "secondary_link" || highway == "tertiary" || highway == "tertiary_link") { + return RoadClass::Secondary; + } + if (highway == "residential" || highway == "unclassified" || highway == "living_street" || highway == "road") { + return RoadClass::Local; + } + return std::nullopt; +} + +std::optional parse_basemap_json(const std::string &raw, const GeoBounds &bounds, const std::string &key) { + std::string parse_error; + const json11::Json root = json11::Json::parse(raw, parse_error); + if (!parse_error.empty() || !root.is_object()) { + return std::nullopt; + } + + RouteBasemap basemap; + basemap.key = key; + basemap.bounds = bounds; + basemap.projected_bounds = project_bounds0(bounds); + + for (const json11::Json &element : root["elements"].array_items()) { + if (element["type"].string_value() != "way") { + continue; + } + const json11::Json &tags = element["tags"]; + const std::vector points = geometry_points(element["geometry"]); + if (points.size() < 2) { + continue; + } + + const std::string highway = tags["highway"].string_value(); + if (!highway.empty()) { + const std::optional road_class = classify_road(highway); + if (!road_class.has_value()) { + continue; + } + basemap.roads.push_back(RoadFeature{ + .road_class = *road_class, + .bounds = compute_projected_bounds(points), + .points = points, + }); + continue; + } + + const std::string natural = tags["natural"].string_value(); + const std::string waterway = tags["waterway"].string_value(); + const bool closed = points.size() >= 4 + && std::abs(points.front().x - points.back().x) < 1.0e-6f + && std::abs(points.front().y - points.back().y) < 1.0e-6f; + if ((natural == "water" || waterway == "riverbank") && closed) { + basemap.water_polygons.push_back(WaterPolygonFeature{ + .bounds = compute_projected_bounds(points), + .ring = points, + }); + continue; + } + if (waterway == "river" || waterway == "stream" || waterway == "canal") { + basemap.water_lines.push_back(WaterLineFeature{ + .bounds = compute_projected_bounds(points), + .points = points, + }); + } + } + + return basemap; +} + +struct RoadPaint { + ImU32 casing = 0; + ImU32 fill = 0; + float casing_width = 1.0f; + float fill_width = 1.0f; +}; + +constexpr ImU32 MAP_BG_COLOR = IM_COL32(244, 243, 238, 255); +constexpr ImU32 MAP_WATER_FILL = IM_COL32(193, 216, 235, 185); +constexpr ImU32 MAP_WATER_OUTLINE = IM_COL32(143, 173, 201, 220); +constexpr ImU32 MAP_WATER_LINE = IM_COL32(156, 186, 214, 205); +constexpr ImU32 MAP_ROUTE_HALO = IM_COL32(31, 40, 50, 92); + +RoadPaint road_paint(RoadClass road_class, float zoom) { + const float scale = std::clamp(0.88f + 0.12f * (zoom - 12.0f), 0.76f, 1.95f); + switch (road_class) { + case RoadClass::Motorway: + return { + .casing = IM_COL32(163, 157, 149, 235), + .fill = IM_COL32(245, 235, 215, 255), + .casing_width = 5.6f * scale, + .fill_width = 3.7f * scale, + }; + case RoadClass::Primary: + return { + .casing = IM_COL32(171, 171, 168, 220), + .fill = IM_COL32(249, 246, 237, 248), + .casing_width = 4.6f * scale, + .fill_width = 2.95f * scale, + }; + case RoadClass::Secondary: + return { + .casing = IM_COL32(183, 186, 189, 210), + .fill = IM_COL32(252, 251, 247, 240), + .casing_width = 3.5f * scale, + .fill_width = 2.15f * scale, + }; + case RoadClass::Local: + default: + return { + .casing = IM_COL32(200, 202, 205, 195), + .fill = IM_COL32(255, 255, 254, 230), + .casing_width = 2.5f * scale, + .fill_width = 1.5f * scale, + }; + } +} + +void clamp_map_center(TabUiState::MapPaneState *map_state, const GeoBounds &bounds, const ImVec2 &size) { + if (!bounds.valid() || size.x <= 1.0f || size.y <= 1.0f) { + return; + } + const double zoom = map_state->zoom; + const double min_x = lon_to_world_x(bounds.west, zoom); + const double max_x = lon_to_world_x(bounds.east, zoom); + const double min_y = lat_to_world_y(bounds.north, zoom); + const double max_y = lat_to_world_y(bounds.south, zoom); + const double half_w = size.x * 0.5; + const double half_h = size.y * 0.5; + double center_x = lon_to_world_x(map_state->center_lon, zoom); + double center_y = lat_to_world_y(map_state->center_lat, zoom); + if (max_x - min_x <= size.x) { + center_x = (min_x + max_x) * 0.5; + } else { + center_x = std::clamp(center_x, min_x + half_w, max_x - half_w); + } + if (max_y - min_y <= size.y) { + center_y = (min_y + max_y) * 0.5; + } else { + center_y = std::clamp(center_y, min_y + half_h, max_y - half_h); + } + map_state->center_lon = world_x_to_lon(center_x, zoom); + map_state->center_lat = world_y_to_lat(center_y, zoom); +} + +void initialize_map_pane_state(TabUiState::MapPaneState *map_state, + const GpsTrace &trace, + const GeoBounds &bounds, + ImVec2 size, + SessionDataMode mode, + std::optional cursor_point) { + if (trace.points.empty()) { + return; + } + map_state->initialized = true; + map_state->follow = mode == SessionDataMode::Stream; + const int min_zoom = minimum_allowed_map_zoom(bounds, trace, size); + if (mode == SessionDataMode::Stream && cursor_point.has_value()) { + map_state->zoom = std::max(16.0f, static_cast(min_zoom)); + map_state->center_lat = cursor_point->lat; + map_state->center_lon = cursor_point->lon; + } else { + map_state->zoom = std::max(static_cast(fit_map_zoom_for_trace(trace, size.x, size.y)), + static_cast(min_zoom)); + map_state->center_lat = map_trace_center_lat(trace); + map_state->center_lon = map_trace_center_lon(trace); + } + clamp_map_center(map_state, bounds, size); +} + +void draw_feature_polyline(ImDrawList *draw_list, + const std::vector &points, + float zoom_scale, + double top_left_x, + double top_left_y, + const ImVec2 &rect_min, + ImU32 color, + float thickness, + bool closed = false) { + if (points.size() < 2) { + return; + } + std::vector screen; + screen.reserve(points.size()); + for (const ProjectedPoint &point : points) { + screen.push_back(ImVec2(rect_min.x + point.x * zoom_scale - static_cast(top_left_x), + rect_min.y + point.y * zoom_scale - static_cast(top_left_y))); + } + draw_list->AddPolyline(screen.data(), static_cast(screen.size()), color, + closed ? ImDrawFlags_Closed : ImDrawFlags_None, thickness); +} + +void draw_water_polygon(ImDrawList *draw_list, + const WaterPolygonFeature &feature, + float zoom_scale, + double top_left_x, + double top_left_y, + const ImVec2 &rect_min) { + if (feature.ring.size() < 3) { + return; + } + std::vector screen; + screen.reserve(feature.ring.size()); + for (const ProjectedPoint &point : feature.ring) { + screen.push_back(ImVec2(rect_min.x + point.x * zoom_scale - static_cast(top_left_x), + rect_min.y + point.y * zoom_scale - static_cast(top_left_y))); + } + if (screen.size() >= 3 && is_convex_ring(screen)) { + draw_list->AddConvexPolyFilled(screen.data(), static_cast(screen.size()), MAP_WATER_FILL); + } + draw_list->AddPolyline(screen.data(), static_cast(screen.size()), MAP_WATER_OUTLINE, + ImDrawFlags_Closed, 1.8f); +} + +void draw_edge_fade(ImDrawList *draw_list, + const GeoBounds &bounds, + double zoom, + double top_left_x, + double top_left_y, + const ImVec2 &rect_min, + const ImVec2 &rect_max) { + if (!bounds.valid()) { + return; + } + + const float west_x = rect_min.x + static_cast(lon_to_world_x(bounds.west, zoom) - top_left_x); + const float east_x = rect_min.x + static_cast(lon_to_world_x(bounds.east, zoom) - top_left_x); + const float north_y = rect_min.y + static_cast(lat_to_world_y(bounds.north, zoom) - top_left_y); + const float south_y = rect_min.y + static_cast(lat_to_world_y(bounds.south, zoom) - top_left_y); + + const float fade_x = std::max(28.0f, (rect_max.x - rect_min.x) * MAP_EDGE_FADE_FRAC); + const float fade_y = std::max(28.0f, (rect_max.y - rect_min.y) * MAP_EDGE_FADE_FRAC); + const ImU32 solid = MAP_BG_COLOR; + const ImU32 clear = IM_COL32(244, 243, 238, 6); + + if (west_x > rect_min.x) { + const float x0 = rect_min.x; + const float x1 = std::min(rect_max.x, west_x); + const float xfade = std::max(x0, x1 - fade_x); + draw_list->AddRectFilledMultiColor(ImVec2(x0, rect_min.y), ImVec2(xfade, rect_max.y), solid, solid, solid, solid); + draw_list->AddRectFilledMultiColor(ImVec2(xfade, rect_min.y), ImVec2(x1, rect_max.y), solid, clear, clear, solid); + } + if (east_x < rect_max.x) { + const float x0 = std::max(rect_min.x, east_x); + const float x1 = rect_max.x; + const float xfade = std::min(x1, x0 + fade_x); + draw_list->AddRectFilledMultiColor(ImVec2(x0, rect_min.y), ImVec2(xfade, rect_max.y), clear, solid, solid, clear); + draw_list->AddRectFilledMultiColor(ImVec2(xfade, rect_min.y), ImVec2(x1, rect_max.y), solid, solid, solid, solid); + } + if (north_y > rect_min.y) { + const float y0 = rect_min.y; + const float y1 = std::min(rect_max.y, north_y); + const float yfade = std::max(y0, y1 - fade_y); + draw_list->AddRectFilledMultiColor(ImVec2(rect_min.x, y0), ImVec2(rect_max.x, yfade), solid, solid, solid, solid); + draw_list->AddRectFilledMultiColor(ImVec2(rect_min.x, yfade), ImVec2(rect_max.x, y1), solid, solid, clear, clear); + } + if (south_y < rect_max.y) { + const float y0 = std::max(rect_min.y, south_y); + const float y1 = rect_max.y; + const float yfade = std::min(y1, y0 + fade_y); + draw_list->AddRectFilledMultiColor(ImVec2(rect_min.x, y0), ImVec2(rect_max.x, yfade), clear, clear, solid, solid); + draw_list->AddRectFilledMultiColor(ImVec2(rect_min.x, yfade), ImVec2(rect_max.x, y1), solid, solid, solid, solid); + } +} + +} // namespace + +MapDataManager::MapDataManager() : worker_([this]() { run(); }) {} + +MapDataManager::~MapDataManager() { + { + std::lock_guard lock(mutex_); + stopping_ = true; + } + cv_.notify_all(); + if (worker_.joinable()) { + worker_.join(); + } +} + +void MapDataManager::pump() { + std::unique_ptr ready; + { + std::lock_guard lock(mutex_); + ready = std::move(completed_); + } + if (ready) { + current_ = std::move(ready); + } +} + +void MapDataManager::ensureTrace(const GpsTrace &trace) { + if (trace.points.empty()) { + return; + } + const MapRequestSpec wanted = build_request_for_trace(trace); + if (!wanted.bounds.valid()) { + return; + } + + std::lock_guard lock(mutex_); + if ((current_ && current_->key == wanted.key) || (pending_ && pending_->key == wanted.key)) { + return; + } + + if (const auto cached = load_compressed_basemap(basemap_cache_path(wanted.key), wanted.key)) { + current_ = std::make_unique(std::move(*cached)); + completed_.reset(); + pending_.reset(); + active_.reset(); + return; + } + + pending_ = std::make_unique(Request{ + .key = wanted.key, + .bounds = wanted.bounds, + .query = wanted.query, + }); + cv_.notify_one(); +} + +bool MapDataManager::loading() const { + std::lock_guard lock(mutex_); + return active_ || pending_; +} + +const RouteBasemap *MapDataManager::current() const { + return current_.get(); +} + +void MapDataManager::clearCache() { + std::lock_guard lock(mutex_); + clear_cache_directory(); +} + +MapCacheStats MapDataManager::cacheStats() const { + return MapCacheStats{ + .bytes = cache_directory_size_bytes(), + .files = cache_directory_file_count(), + }; +} + +void MapDataManager::run() { + while (true) { + Request request; + { + std::unique_lock lock(mutex_); + cv_.wait(lock, [&]() { return stopping_ || pending_ != nullptr; }); + if (stopping_) { + return; + } + request = *pending_; + active_ = std::move(pending_); + } + + std::unique_ptr parsed; + const std::string raw = load_overpass_json(request.query); + if (!raw.empty()) { + if (auto basemap = parse_basemap_json(raw, request.bounds, request.key)) { + save_compressed_basemap(basemap_cache_path(request.key), *basemap); + parsed = std::make_unique(std::move(*basemap)); + } + } + + { + std::lock_guard lock(mutex_); + if (active_ && active_->key == request.key) { + completed_ = std::move(parsed); + active_.reset(); + } + } + } +} + +void draw_map_pane(AppSession *session, UiState *state, Pane *, int pane_index) { + TabUiState *tab_state = app_active_tab_state(state); + if (tab_state == nullptr || pane_index < 0 || pane_index >= static_cast(tab_state->map_panes.size())) { + ImGui::TextUnformatted("Map unavailable"); + return; + } + if (!session->map_data) { + ImGui::TextUnformatted("Map unavailable"); + return; + } + + session->map_data->ensureTrace(session->route_data.gps_trace); + session->map_data->pump(); + + TabUiState::MapPaneState &map_state = tab_state->map_panes[static_cast(pane_index)]; + const GpsTrace &trace = session->route_data.gps_trace; + const RouteBasemap *basemap = session->map_data->current(); + const GeoBounds map_bounds = basemap != nullptr ? basemap->bounds : requested_bounds_for_trace(trace); + + const ImVec2 rect_min = ImGui::GetCursorScreenPos(); + const ImVec2 size = ImGui::GetContentRegionAvail(); + const ImVec2 input_size(std::max(1.0f, size.x - 22.0f), std::max(1.0f, size.y)); + ImGui::SetNextItemAllowOverlap(); + ImGui::InvisibleButton("##map_canvas", input_size); + const ImVec2 rect_max(rect_min.x + size.x, rect_min.y + size.y); + const float rect_width = rect_max.x - rect_min.x; + const float rect_height = rect_max.y - rect_min.y; + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + + draw_list->PushClipRect(rect_min, rect_max, true); + draw_list->AddRectFilled(rect_min, rect_max, MAP_BG_COLOR); + + if (trace.points.empty()) { + const char *label = session->async_route_loading ? "Loading map..." : "No GPS trace"; + const ImVec2 text = ImGui::CalcTextSize(label); + draw_list->AddText(ImVec2(rect_min.x + (rect_width - text.x) * 0.5f, + rect_min.y + (rect_height - text.y) * 0.5f), + IM_COL32(110, 118, 128, 255), label); + draw_list->PopClipRect(); + return; + } + + const std::optional cursor_point = state->has_tracker_time + ? interpolate_gps(trace, state->tracker_time) + : std::optional{}; + if (!map_state.initialized) { + initialize_map_pane_state(&map_state, trace, map_bounds, size, session->data_mode, cursor_point); + } + + const int min_zoom = minimum_allowed_map_zoom(map_bounds, trace, size); + if (map_state.follow && cursor_point.has_value()) { + const float follow_zoom = std::clamp(map_state.zoom, static_cast(min_zoom), static_cast(MAP_MAX_ZOOM)); + const double center_x = lon_to_world_x(map_state.center_lon, follow_zoom); + const double center_y = lat_to_world_y(map_state.center_lat, follow_zoom); + const double top_left_x = center_x - rect_width * 0.5; + const double top_left_y = center_y - rect_height * 0.5; + const ImVec2 car_screen = gps_to_screen(cursor_point->lat, cursor_point->lon, follow_zoom, top_left_x, top_left_y, rect_min); + if (!point_in_rect_with_margin(car_screen, rect_min, rect_max, 0.22f)) { + map_state.center_lat = cursor_point->lat; + map_state.center_lon = cursor_point->lon; + } + } + + map_state.zoom = std::clamp(map_state.zoom, static_cast(min_zoom), static_cast(MAP_MAX_ZOOM)); + clamp_map_center(&map_state, map_bounds, size); + + const double zoom = map_state.zoom; + const float zoom_scale = static_cast(std::exp2(zoom)); + const double center_x = lon_to_world_x(map_state.center_lon, zoom); + const double center_y = lat_to_world_y(map_state.center_lat, zoom); + const double top_left_x = center_x - rect_width * 0.5; + const double top_left_y = center_y - rect_height * 0.5; + const ProjectedBounds current_view = view_bounds(top_left_x, top_left_y, rect_width, rect_height); + + if (basemap != nullptr) { + for (const WaterPolygonFeature &water : basemap->water_polygons) { + if (feature_intersects_view(water.bounds, current_view, zoom_scale)) { + draw_water_polygon(draw_list, water, zoom_scale, top_left_x, top_left_y, rect_min); + } + } + for (const WaterLineFeature &water : basemap->water_lines) { + if (feature_intersects_view(water.bounds, current_view, zoom_scale)) { + draw_feature_polyline(draw_list, water.points, zoom_scale, top_left_x, top_left_y, rect_min, + MAP_WATER_LINE, 2.4f); + } + } + + std::array order = { + RoadClass::Local, + RoadClass::Secondary, + RoadClass::Primary, + RoadClass::Motorway, + }; + for (RoadClass road_class : order) { + const RoadPaint paint = road_paint(road_class, static_cast(zoom)); + for (const RoadFeature &road : basemap->roads) { + if (road.road_class != road_class || !feature_intersects_view(road.bounds, current_view, zoom_scale)) { + continue; + } + draw_feature_polyline(draw_list, road.points, zoom_scale, top_left_x, top_left_y, rect_min, + paint.casing, paint.casing_width); + draw_feature_polyline(draw_list, road.points, zoom_scale, top_left_x, top_left_y, rect_min, + paint.fill, paint.fill_width); + } + } + } + + if (basemap != nullptr) { + draw_edge_fade(draw_list, basemap->bounds, zoom, top_left_x, top_left_y, rect_min, rect_max); + } + + for (size_t i = 1; i < trace.points.size(); ++i) { + const GpsPoint &p0 = trace.points[i - 1]; + const GpsPoint &p1 = trace.points[i]; + const ImVec2 s0 = gps_to_screen(p0.lat, p0.lon, zoom, top_left_x, top_left_y, rect_min); + const ImVec2 s1 = gps_to_screen(p1.lat, p1.lon, zoom, top_left_x, top_left_y, rect_min); + draw_list->AddLine(s0, s1, MAP_ROUTE_HALO, 5.8f); + draw_list->AddLine(s0, s1, map_timeline_color(p1.type, 1.0f), 3.25f); + } + + if (cursor_point.has_value()) { + const ImVec2 marker = gps_to_screen(cursor_point->lat, cursor_point->lon, zoom, top_left_x, top_left_y, rect_min); + const float marker_size = std::clamp(9.0f + 1.0f * static_cast(zoom - min_zoom), 9.0f, 20.0f); + draw_car_marker(draw_list, marker, cursor_point->bearing, map_timeline_color(cursor_point->type, 1.0f), marker_size); + } + + if (session->map_data->loading()) { + const char *label = basemap != nullptr ? "Refreshing roads..." : "Loading roads..."; + const ImVec2 text = ImGui::CalcTextSize(label); + const ImVec2 pos(rect_min.x + 12.0f, rect_max.y - text.y - 12.0f); + draw_list->AddRectFilled(ImVec2(pos.x - 6.0f, pos.y - 4.0f), + ImVec2(pos.x + text.x + 6.0f, pos.y + text.y + 4.0f), + IM_COL32(255, 255, 255, 180), 4.0f); + draw_list->AddText(pos, IM_COL32(84, 93, 105, 255), label); + } + draw_list->PopClipRect(); + + const bool canvas_hovered = ImGui::IsItemHovered(); + const bool double_clicked = canvas_hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left); + bool overlay_hovered = false; + if (const std::string google_maps_url = route_google_maps_url(trace); !google_maps_url.empty()) { + std::string label = std::string("Google Maps ") + icon::BOX_ARROW_UP_RIGHT; + const ImVec2 text_size = ImGui::CalcTextSize(label.c_str()); + const ImVec2 button_size(text_size.x + 20.0f, text_size.y + 10.0f); + const ImVec2 button_pos(rect_max.x - button_size.x - 28.0f, rect_min.y + 10.0f); + ImGui::SetCursorScreenPos(button_pos); + ImGui::SetNextItemAllowOverlap(); + if (ImGui::Button("##open_google_maps", button_size)) { + open_external_url(google_maps_url); + state->status_text = "Opened Google Maps"; + } + overlay_hovered = ImGui::IsItemHovered(); + draw_list->AddText(ImVec2(button_pos.x + 10.0f, button_pos.y + (button_size.y - text_size.y) * 0.5f), + ImGui::GetColorU32(ImGuiCol_Text), label.c_str()); + } + const bool hovered = canvas_hovered && !overlay_hovered; + if (hovered && ImGui::GetIO().MouseWheel != 0.0f) { + const float next_zoom = std::clamp(static_cast(zoom) + ImGui::GetIO().MouseWheel * MAP_WHEEL_ZOOM_STEP, + static_cast(min_zoom), static_cast(MAP_MAX_ZOOM)); + if (std::abs(next_zoom - zoom) > 1.0e-4f) { + const ImVec2 mouse = ImGui::GetIO().MousePos; + const double mouse_world_x = top_left_x + (mouse.x - rect_min.x); + const double mouse_world_y = top_left_y + (mouse.y - rect_min.y); + const double mouse_lon = world_x_to_lon(mouse_world_x, zoom); + const double mouse_lat = world_y_to_lat(mouse_world_y, zoom); + const double next_center_x = lon_to_world_x(mouse_lon, next_zoom) - (mouse.x - rect_min.x) + rect_width * 0.5; + const double next_center_y = lat_to_world_y(mouse_lat, next_zoom) - (mouse.y - rect_min.y) + rect_height * 0.5; + map_state.zoom = next_zoom; + map_state.center_lon = world_x_to_lon(next_center_x, next_zoom); + map_state.center_lat = world_y_to_lat(next_center_y, next_zoom); + map_state.follow = false; + clamp_map_center(&map_state, map_bounds, size); + } + } + if (hovered && ImGui::IsMouseDragging(ImGuiMouseButton_Left, 2.0f)) { + const ImVec2 delta = ImGui::GetIO().MouseDelta; + const double next_center_x = center_x - delta.x; + const double next_center_y = center_y - delta.y; + map_state.center_lon = world_x_to_lon(next_center_x, zoom); + map_state.center_lat = world_y_to_lat(next_center_y, zoom); + map_state.follow = false; + clamp_map_center(&map_state, map_bounds, size); + } else if (hovered && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { + const ImVec2 drag_delta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left); + if (drag_delta.x * drag_delta.x + drag_delta.y * drag_delta.y < 16.0f) { + const ImVec2 mouse = ImGui::GetIO().MousePos; + double best_dist = std::numeric_limits::max(); + double best_time = state->tracker_time; + for (const GpsPoint &point : trace.points) { + const ImVec2 screen = gps_to_screen(point.lat, point.lon, zoom, top_left_x, top_left_y, rect_min); + const double dx = static_cast(screen.x - mouse.x); + const double dy = static_cast(screen.y - mouse.y); + const double dist = dx * dx + dy * dy; + if (dist < best_dist) { + best_dist = dist; + best_time = point.time; + } + } + state->tracker_time = best_time; + state->has_tracker_time = true; + } + ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left); + } + if (double_clicked) { + map_state.initialized = false; + } +} diff --git a/tools/jotpluggler/map.h b/tools/jotpluggler/map.h new file mode 100644 index 00000000000..97473f1ba90 --- /dev/null +++ b/tools/jotpluggler/map.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +struct GpsTrace; +struct GeoBounds { + double south = 0.0; + double west = 0.0; + double north = 0.0; + double east = 0.0; + + bool valid() const { + return south < north && west < east; + } +}; + +struct RouteBasemap; +struct MapCacheStats { + uint64_t bytes = 0; + size_t files = 0; +}; + +class MapDataManager { +public: + MapDataManager(); + ~MapDataManager(); + + MapDataManager(const MapDataManager &) = delete; + MapDataManager &operator=(const MapDataManager &) = delete; + + void pump(); + void ensureTrace(const GpsTrace &trace); + void clearCache(); + bool loading() const; + const RouteBasemap *current() const; + MapCacheStats cacheStats() const; + +private: + struct Request { + std::string key; + GeoBounds bounds; + std::string query; + }; + + void run(); + + mutable std::mutex mutex_; + std::condition_variable cv_; + bool stopping_ = false; + std::unique_ptr pending_; + std::unique_ptr active_; + std::unique_ptr completed_; + std::unique_ptr current_; + std::thread worker_; +}; diff --git a/tools/jotpluggler/math_eval.py b/tools/jotpluggler/math_eval.py new file mode 100755 index 00000000000..a865c88a3a7 --- /dev/null +++ b/tools/jotpluggler/math_eval.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 + +import json +import sys +import textwrap +import traceback + +import numpy as np + + +def _load_manifest(path: str) -> dict: + with open(path, encoding="utf-8") as f: + return json.load(f) + + +def _load_vector(path: str) -> np.ndarray: + return np.fromfile(path, dtype=np.float64) + + +def _write_vector(path: str, values: np.ndarray) -> None: + np.asarray(values, dtype=np.float64).tofile(path) + + +def _resample_to_reference(ref_t: np.ndarray, src_t: np.ndarray, src_v: np.ndarray) -> np.ndarray: + ref_t = np.asarray(ref_t, dtype=np.float64).reshape(-1) + src_t = np.asarray(src_t, dtype=np.float64).reshape(-1) + src_v = np.asarray(src_v, dtype=np.float64).reshape(-1) + if ref_t.size == 0 or src_t.size == 0 or src_v.size == 0: + return np.empty_like(ref_t) + indices = np.searchsorted(src_t, ref_t, side="right") - 1 + indices = np.clip(indices, 0, src_v.size - 1) + return src_v[indices] + + +def _evaluate_user_code(code: str, env: dict): + stripped = code.strip() + if not stripped: + raise ValueError("Function body is empty") + + expr = stripped + if expr.startswith("return "): + expr = expr[7:].strip() + try: + return eval(expr, env, env) + except SyntaxError: + pass + + function_src = "def __jotpluggler_eval__():\n" + textwrap.indent(code, " ") + exec(function_src, env, env) + return env["__jotpluggler_eval__"]() + + +def main() -> int: + if len(sys.argv) != 6: + print("usage: math_eval.py ", file=sys.stderr) + return 2 + + manifest_path, globals_path, code_path, out_t_path, out_v_path = sys.argv[1:6] + manifest = _load_manifest(manifest_path) + + series_t = {} + series_v = {} + for entry in manifest.get("series", []): + path = entry["path"] + series_t[path] = _load_vector(entry["t"]) + series_v[path] = _load_vector(entry["v"]) + + first_path = manifest.get("linked_source") or None + + def remember(path: str) -> None: + nonlocal first_path + if first_path is None: + first_path = path + + def t(path: str) -> np.ndarray: + remember(path) + return series_t[path] + + def v(path: str) -> np.ndarray: + remember(path) + return series_v[path] + + additional_sources = list(manifest.get("additional_sources", [])) + linked_source = manifest.get("linked_source") or "" + paths = list(manifest.get("paths", [])) + + env = { + "__builtins__": __builtins__, + "np": np, + "t": t, + "v": v, + "paths": paths, + "linked_source": linked_source, + "additional_sources": additional_sources, + } + + reference_time = None + if linked_source: + reference_time = series_t[linked_source] + env["time"] = reference_time + env["value"] = series_v[linked_source] + + for i, path in enumerate(additional_sources, start=1): + if reference_time is None: + env[f"t{i}"] = series_t[path] + env[f"v{i}"] = series_v[path] + else: + env[f"t{i}"] = reference_time + env[f"v{i}"] = _resample_to_reference(reference_time, series_t[path], series_v[path]) + + with open(globals_path, encoding="utf-8") as f: + globals_code = f.read() + if globals_code.strip(): + exec(globals_code, env, env) + + with open(code_path, encoding="utf-8") as f: + user_code = f.read() + result = _evaluate_user_code(user_code, env) + + if isinstance(result, tuple) and len(result) == 2: + result_t, result_v = result + else: + if first_path is None: + raise ValueError("No reference series found. Set an input timeseries or return (times, values).") + result_t = series_t[first_path] + result_v = result + + result_t = np.asarray(result_t, dtype=np.float64).reshape(-1) + result_v = np.asarray(result_v, dtype=np.float64).reshape(-1) + if result_t.size == 0 or result_v.size == 0: + raise ValueError("Custom series returned an empty result") + if result_t.shape != result_v.shape: + raise ValueError(f"Time/value arrays must have the same shape, got {result_t.shape} and {result_v.shape}") + + _write_vector(out_t_path, result_t) + _write_vector(out_v_path, result_v) + return 0 + + +if __name__ == "__main__": + try: + raise SystemExit(main()) + except Exception as err: + traceback.print_exc() + raise SystemExit(1) from err diff --git a/tools/jotpluggler/plot.cc b/tools/jotpluggler/plot.cc new file mode 100644 index 00000000000..a3c68ddcefa --- /dev/null +++ b/tools/jotpluggler/plot.cc @@ -0,0 +1,1027 @@ +#include "tools/jotpluggler/internal.h" + +#include "implot.h" +#include "imgui_internal.h" + +#include +#include +#include +#include + +constexpr double PLOT_Y_PAD_FRACTION = 0.4; + +struct PlotBounds { + double x_min = 0.0; + double x_max = 1.0; + double y_min = 0.0; + double y_max = 1.0; +}; + +bool curve_has_samples(const AppSession &session, const Curve &curve) { + if (curve_has_local_samples(curve)) return true; + if (curve.name.empty() || curve.name.front() != '/') { + return false; + } + const RouteSeries *series = app_find_route_series(session, curve.name); + return series != nullptr && series->times.size() > 1 && series->times.size() == series->values.size(); +} + +void extend_range(const std::vector &values, bool *found, double *min_value, double *max_value) { + if (values.empty()) { + return; + } + const auto [min_it, max_it] = std::minmax_element(values.begin(), values.end()); + if (!*found) { + *min_value = *min_it; + *max_value = *max_it; + *found = true; + return; + } + *min_value = std::min(*min_value, *min_it); + *max_value = std::max(*max_value, *max_it); +} + +void ensure_non_degenerate_range(double *min_value, double *max_value, double pad_fraction, double fallback_pad) { + if (*max_value <= *min_value) { + const double pad = std::max(std::abs(*min_value) * 0.1, fallback_pad); + *min_value -= pad; + *max_value += pad; + return; + } + const double span = *max_value - *min_value; + const double pad = std::max(span * pad_fraction, fallback_pad); + *min_value -= pad; + *max_value += pad; +} + +struct PreparedCurve { + int pane_curve_index = -1; + std::string label; + std::array color = {160, 170, 180}; + float line_weight = 2.0f; + bool stairs = false; + const EnumInfo *enum_info = nullptr; + SeriesFormat display_info; + std::optional legend_value; + std::vector xs; + std::vector ys; +}; + +struct StateBlock { + double t0 = 0.0; + double t1 = 0.0; + int value = 0; + std::string label; +}; + +struct PaneEnumContext { + std::vector enums; +}; + +struct PaneValueFormatContext { + SeriesFormat format; + bool valid = false; +}; + +bool curves_are_bool_like(const std::vector &prepared_curves) { + if (prepared_curves.empty()) { + return false; + } + for (const PreparedCurve &curve : prepared_curves) { + if (!curve.display_info.integer_like || curve.ys.empty()) { + return false; + } + bool found_finite = false; + for (double value : curve.ys) { + if (!std::isfinite(value)) continue; + found_finite = true; + if (std::abs(value) > 0.01 && std::abs(value - 1.0) > 0.01) { + return false; + } + } + if (!found_finite) { + return false; + } + } + return true; +} + +bool curve_is_state_like(const PreparedCurve &curve) { + if (!curve.display_info.integer_like || curve.xs.size() < 2 || curve.xs.size() != curve.ys.size()) { + return false; + } + if (curve.enum_info != nullptr) { + return true; + } + std::unordered_set distinct_values; + for (double value : curve.ys) { + if (!std::isfinite(value)) { + continue; + } + distinct_values.insert(static_cast(std::llround(value))); + if (distinct_values.size() > 12) { + return false; + } + } + return !distinct_values.empty(); +} + +bool curves_use_state_blocks(const std::vector &prepared_curves) { + if (prepared_curves.empty()) { + return false; + } + for (const PreparedCurve &curve : prepared_curves) { + if (!curve_is_state_like(curve)) { + return false; + } + } + return true; +} + +ImU32 state_block_color(int value, float alpha = 1.0f) { + static constexpr std::array, 8> kPalette = {{ + {{111, 143, 175}}, + {{0, 163, 108}}, + {{255, 195, 0}}, + {{199, 0, 57}}, + {{123, 97, 255}}, + {{0, 150, 136}}, + {{214, 48, 49}}, + {{52, 73, 94}}, + }}; + const size_t index = static_cast(std::abs(value)) % kPalette.size(); + return ImGui::GetColorU32(color_rgb(kPalette[index], alpha)); +} + +std::string state_block_label(const PreparedCurve &curve, int value) { + if (curve.enum_info != nullptr && value >= 0 && static_cast(value) < curve.enum_info->names.size()) { + const std::string &name = curve.enum_info->names[static_cast(value)]; + if (!name.empty()) { + return name; + } + } + return std::to_string(value); +} + +std::vector build_state_blocks(const PreparedCurve &curve) { + std::vector blocks; + if (curve.xs.size() < 2 || curve.xs.size() != curve.ys.size()) { + return blocks; + } + + int current_value = static_cast(std::llround(curve.ys.front())); + double start_time = curve.xs.front(); + for (size_t i = 1; i < curve.xs.size(); ++i) { + const int value = static_cast(std::llround(curve.ys[i])); + if (value == current_value) { + continue; + } + const double end_time = curve.xs[i]; + if (end_time > start_time) { + blocks.push_back(StateBlock{ + .t0 = start_time, + .t1 = end_time, + .value = current_value, + .label = state_block_label(curve, current_value), + }); + } + current_value = value; + start_time = end_time; + } + + const double final_time = curve.xs.back(); + if (final_time >= start_time) { + blocks.push_back(StateBlock{ + .t0 = start_time, + .t1 = final_time, + .value = current_value, + .label = state_block_label(curve, current_value), + }); + } + return blocks; +} + +void app_decimate_samples_impl(const std::vector &xs_in, + const std::vector &ys_in, + int max_points, + std::vector *xs_out, + std::vector *ys_out) { + + const size_t bucket_count = std::max(1, static_cast(max_points / 4)); + const size_t bucket_size = std::max( + 1, + static_cast(std::ceil(static_cast(xs_in.size()) / static_cast(bucket_count)))); + xs_out->reserve(bucket_count * 4 + 2); + ys_out->reserve(bucket_count * 4 + 2); + + size_t last_index = std::numeric_limits::max(); + auto append_index = [&](size_t index) { + if (index >= xs_in.size() || index == last_index) { + return; + } + xs_out->push_back(xs_in[index]); + ys_out->push_back(ys_in[index]); + last_index = index; + }; + + for (size_t start = 0; start < xs_in.size(); start += bucket_size) { + const size_t end = std::min(xs_in.size(), start + bucket_size); + size_t min_index = start; + size_t max_index = start; + for (size_t index = start + 1; index < end; ++index) { + if (ys_in[index] < ys_in[min_index]) { + min_index = index; + } + if (ys_in[index] > ys_in[max_index]) { + max_index = index; + } + } + + std::array indices = {start, min_index, max_index, end - 1}; + std::sort(indices.begin(), indices.end()); + for (size_t index : indices) { + append_index(index); + } + } +} + +void app_decimate_samples(const std::vector &xs_in, + const std::vector &ys_in, + int max_points, + std::vector *xs_out, + std::vector *ys_out) { + xs_out->clear(); + ys_out->clear(); + if (xs_in.empty() || xs_in.size() != ys_in.size()) { + return; + } + if (max_points <= 0 || static_cast(xs_in.size()) <= max_points) { + *xs_out = xs_in; + *ys_out = ys_in; + return; + } + app_decimate_samples_impl(xs_in, ys_in, max_points, xs_out, ys_out); +} + +void app_decimate_samples(std::vector &&xs_in, + std::vector &&ys_in, + int max_points, + std::vector *xs_out, + std::vector *ys_out) { + xs_out->clear(); + ys_out->clear(); + if (xs_in.empty() || xs_in.size() != ys_in.size()) { + return; + } + if (max_points <= 0 || static_cast(xs_in.size()) <= max_points) { + *xs_out = std::move(xs_in); + *ys_out = std::move(ys_in); + return; + } + app_decimate_samples_impl(xs_in, ys_in, max_points, xs_out, ys_out); +} + +std::optional app_sample_xy_value_at_time(const std::vector &xs, + const std::vector &ys, + bool stairs, + double tm) { + if (xs.size() < 2 || xs.size() != ys.size()) { + return std::nullopt; + } + if (tm <= xs.front()) return ys.front(); + if (tm >= xs.back()) return ys.back(); + + const auto upper = std::lower_bound(xs.begin(), xs.end(), tm); + if (upper == xs.begin()) return ys.front(); + if (upper == xs.end()) return ys.back(); + + const size_t upper_index = static_cast(std::distance(xs.begin(), upper)); + const size_t lower_index = upper_index - 1; + const double x0 = xs[lower_index]; + const double x1 = xs[upper_index]; + const double y0 = ys[lower_index]; + const double y1 = ys[upper_index]; + if (std::abs(tm - x1) < 1.0e-9) return y1; + if (stairs || x1 <= x0) return y0; + const double alpha = (tm - x0) / (x1 - x0); + return y0 + (y1 - y0) * alpha; +} + +int format_enum_axis_tick(double value, char *buf, int size, void *user_data) { + const auto *ctx = static_cast(user_data); + const int idx = static_cast(std::llround(value)); + if (ctx != nullptr && idx >= 0 && std::abs(value - static_cast(idx)) < 0.01) { + std::vector names; + names.reserve(ctx->enums.size()); + for (const EnumInfo *info : ctx->enums) { + if (info == nullptr || static_cast(idx) >= info->names.size()) { + continue; + } + const std::string &name = info->names[static_cast(idx)]; + if (name.empty()) continue; + if (std::find(names.begin(), names.end(), std::string_view(name)) == names.end()) { + names.emplace_back(name); + } + } + if (!names.empty()) { + std::string joined; + for (size_t i = 0; i < names.size(); ++i) { + if (i != 0) { + joined += ", "; + } + joined += names[i]; + } + return std::snprintf(buf, size, "%d (%s)", idx, joined.c_str()); + } + } + return std::snprintf(buf, size, "%.6g", value); +} + +int format_numeric_axis_tick(double value, char *buf, int size, void *user_data) { + const auto *ctx = static_cast(user_data); + if (ctx == nullptr || !ctx->valid) { + return std::snprintf(buf, size, "%.6g", value); + } + if (ctx->format.integer_like) { + const double nearest_int = std::round(value); + if (std::abs(value - nearest_int) > 1.0e-6) { + int decimals = 1; + while (decimals < 4) { + const double scale = std::pow(10.0, decimals); + const double rounded = std::round(value * scale) / scale; + if (std::abs(value - rounded) <= 1.0e-6) { + break; + } + ++decimals; + } + return std::snprintf(buf, size, "%.*f", decimals, value); + } + } + return std::snprintf(buf, size, ctx->format.fmt, value); +} + +void merge_pane_value_format(PaneValueFormatContext *ctx, const SeriesFormat &format) { + if (!ctx->valid) { + ctx->format = format; + ctx->valid = true; + return; + } + ctx->format.has_negative = ctx->format.has_negative || format.has_negative; + ctx->format.digits_before = std::max(ctx->format.digits_before, format.digits_before); + ctx->format.decimals = std::max(ctx->format.decimals, format.decimals); + ctx->format.integer_like = ctx->format.decimals == 0; + const int sign_width = ctx->format.has_negative ? 1 : 0; + const int dot_width = ctx->format.decimals > 0 ? 1 : 0; + ctx->format.total_width = sign_width + ctx->format.digits_before + dot_width + ctx->format.decimals; + std::snprintf(ctx->format.fmt, sizeof(ctx->format.fmt), "%%%d.%df", + ctx->format.total_width, ctx->format.decimals); +} + +std::string curve_legend_label(const PreparedCurve &curve, bool has_cursor_time, size_t label_width) { + if (!has_cursor_time) return curve.label; + if (!curve.legend_value.has_value()) return curve.label; + const std::string value_text = format_display_value(*curve.legend_value, curve.display_info, curve.enum_info); + if (value_text.empty()) return curve.label; + const size_t padded_width = std::max(label_width, curve.label.size()); + return curve.label + std::string(padded_width - curve.label.size() + 2, ' ') + value_text; +} + +bool build_curve_series(const AppSession &session, + const Curve &curve, + const UiState &state, + int max_points, + PreparedCurve *prepared) { + std::vector xs; + std::vector ys; + if (curve_has_local_samples(curve)) { + xs = curve.xs; + ys = curve.ys; + } else { + const RouteSeries *series = app_find_route_series(session, curve.name); + if (series == nullptr || series->times.size() < 2 || series->times.size() != series->values.size()) { + return false; + } + + size_t begin_index = 0; + size_t end_index = series->times.size(); + if (state.has_shared_range && state.x_view_max > state.x_view_min) { + auto begin_it = std::lower_bound(series->times.begin(), series->times.end(), state.x_view_min); + auto end_it = std::upper_bound(series->times.begin(), series->times.end(), state.x_view_max); + begin_index = begin_it == series->times.begin() ? 0 : static_cast(std::distance(series->times.begin(), begin_it - 1)); + end_index = end_it == series->times.end() ? series->times.size() : static_cast(std::distance(series->times.begin(), end_it + 1)); + end_index = std::min(end_index, series->times.size()); + } + if (end_index <= begin_index + 1) return false; + xs.assign(series->times.begin() + begin_index, series->times.begin() + end_index); + ys.assign(series->values.begin() + begin_index, series->values.begin() + end_index); + } + + std::vector transformed_xs; + std::vector transformed_ys; + if (curve.derivative) { + if (xs.size() < 2) return false; + transformed_xs.reserve(xs.size() - 1); + transformed_ys.reserve(ys.size() - 1); + for (size_t i = 1; i < xs.size(); ++i) { + const double dt = curve.derivative_dt > 0.0 ? curve.derivative_dt : (xs[i] - xs[i - 1]); + if (dt <= 0.0) continue; + transformed_xs.push_back(xs[i]); + transformed_ys.push_back((ys[i] - ys[i - 1]) / dt); + } + } else { + transformed_xs = std::move(xs); + transformed_ys = std::move(ys); + } + + if (transformed_xs.size() < 2 || transformed_xs.size() != transformed_ys.size()) { + return false; + } + + for (double &value : transformed_ys) { + value = value * curve.value_scale + curve.value_offset; + } + + prepared->label = app_curve_display_name(curve); + prepared->color = curve.color; + prepared->line_weight = curve.derivative ? 1.8f : 2.25f; + if (!curve.derivative + && curve.value_scale == 1.0 + && curve.value_offset == 0.0 + && !curve_has_local_samples(curve) + && !curve.name.empty() + && curve.name.front() == '/') { + auto it = session.route_data.enum_info.find(curve.name); + if (it != session.route_data.enum_info.end()) { + prepared->enum_info = &it->second; + } + } + if (prepared->enum_info != nullptr) { + prepared->display_info = compute_series_format(transformed_ys, true); + } else if (!curve_has_local_samples(curve) + && !curve.derivative + && curve.value_scale == 1.0 + && curve.value_offset == 0.0 + && !curve.name.empty() + && curve.name.front() == '/') { + auto display_it = session.route_data.series_formats.find(curve.name); + if (display_it != session.route_data.series_formats.end()) { + prepared->display_info = display_it->second; + } else { + prepared->display_info = compute_series_format(transformed_ys, false); + } + } else { + prepared->display_info = compute_series_format(transformed_ys, false); + } + const bool stairs = !curve.derivative && prepared->display_info.integer_like; + if (state.has_tracker_time) { + prepared->legend_value = app_sample_xy_value_at_time(transformed_xs, transformed_ys, stairs, state.tracker_time); + } + if (stairs) { + prepared->xs = std::move(transformed_xs); + prepared->ys = std::move(transformed_ys); + } else { + app_decimate_samples(std::move(transformed_xs), std::move(transformed_ys), max_points, &prepared->xs, &prepared->ys); + } + prepared->stairs = stairs; + return prepared->xs.size() > 1 && prepared->xs.size() == prepared->ys.size(); +} + +bool draw_pane_close_button_overlay() { + const ImVec2 window_pos = ImGui::GetWindowPos(); + const ImVec2 content_min = ImGui::GetWindowContentRegionMin(); + const ImVec2 content_max = ImGui::GetWindowContentRegionMax(); + const ImRect rect(ImVec2(window_pos.x + content_max.x - 42.0f, window_pos.y + content_min.y + 4.0f), + ImVec2(window_pos.x + content_max.x - 4.0f, window_pos.y + content_min.y + 42.0f)); + const bool hovered = ImGui::IsMouseHoveringRect(rect.Min, rect.Max, false); + const bool held = hovered && ImGui::IsMouseDown(ImGuiMouseButton_Left); + if (hovered) { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + const float pad = 11.0f; + const ImU32 color = hovered || held + ? ImGui::GetColorU32(color_rgb(72, 79, 88)) + : ImGui::GetColorU32(color_rgb(138, 146, 156)); + draw_list->AddLine(ImVec2(rect.Min.x + pad, rect.Min.y + pad), + ImVec2(rect.Max.x - pad, rect.Max.y - pad), + color, + 2.4f); + draw_list->AddLine(ImVec2(rect.Min.x + pad, rect.Max.y - pad), + ImVec2(rect.Max.x - pad, rect.Min.y + pad), + color, + 2.4f); + return hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left); +} + +void draw_pane_frame_overlay() { + const ImVec2 window_pos = ImGui::GetWindowPos(); + const ImVec2 content_min = ImGui::GetWindowContentRegionMin(); + const ImVec2 content_max = ImGui::GetWindowContentRegionMax(); + const ImRect frame_rect(ImVec2(window_pos.x + content_min.x, window_pos.y + content_min.y), + ImVec2(window_pos.x + content_max.x, window_pos.y + content_max.y)); + ImGui::GetWindowDrawList()->AddRect(frame_rect.Min, + frame_rect.Max, + ImGui::GetColorU32(color_rgb(186, 190, 196)), + 0.0f, + 0, + 1.0f); +} + +PlotBounds compute_plot_bounds(const Pane &pane, + const std::vector &prepared_curves, + const UiState &state) { + PlotBounds bounds; + bounds.x_min = state.has_shared_range ? state.x_view_min : 0.0; + bounds.x_max = state.has_shared_range ? state.x_view_max : 1.0; + if (bounds.x_max <= bounds.x_min) { + bounds.x_max = bounds.x_min + 1.0; + } + + bool found = false; + double min_value = 0.0; + double max_value = 1.0; + for (const PreparedCurve &curve : prepared_curves) { + extend_range(curve.ys, &found, &min_value, &max_value); + } + if (!found) { + min_value = 0.0; + max_value = 1.0; + } + if (curves_are_bool_like(prepared_curves)) { + min_value = std::min(min_value, 0.0); + max_value = std::max(max_value, 1.0); + } + ensure_non_degenerate_range(&min_value, &max_value, PLOT_Y_PAD_FRACTION, 0.1); + if (pane.range.has_y_limit_min) { + min_value = pane.range.y_limit_min; + } + if (pane.range.has_y_limit_max) { + max_value = pane.range.y_limit_max; + } + ensure_non_degenerate_range(&min_value, &max_value, 0.0, 0.1); + bounds.y_min = min_value; + bounds.y_max = max_value; + return bounds; +} + +void draw_state_blocks_pane(const std::vector &prepared_curves, UiState *state) { + if (prepared_curves.empty() || !state->has_shared_range || state->x_view_max <= state->x_view_min) { + return; + } + + ImDrawList *draw_list = ImPlot::GetPlotDrawList(); + const ImVec2 plot_min = ImPlot::GetPlotPos(); + const ImVec2 plot_size = ImPlot::GetPlotSize(); + const int curve_count = static_cast(prepared_curves.size()); + if (plot_size.x <= 2.0f || plot_size.y <= 2.0f || curve_count <= 0) { + return; + } + + float label_width = 0.0f; + if (curve_count > 1) { + for (const PreparedCurve &curve : prepared_curves) { + label_width = std::max(label_width, ImGui::CalcTextSize(curve.label.c_str()).x); + } + label_width = std::clamp(label_width + 14.0f, 72.0f, std::min(160.0f, plot_size.x * 0.35f)); + } + + const float row_height = plot_size.y / static_cast(curve_count); + const float blocks_min_x = plot_min.x + label_width; + const float blocks_max_x = plot_min.x + plot_size.x; + const float blocks_width = std::max(1.0f, blocks_max_x - blocks_min_x); + const double x_span = std::max(1.0e-9, state->x_view_max - state->x_view_min); + + struct HoveredBlock { + int curve_index = -1; + StateBlock block; + }; + std::optional hovered; + + const ImVec2 mouse_pos = ImGui::GetMousePos(); + const bool plot_hovered = ImPlot::IsPlotHovered(); + + for (int curve_index = 0; curve_index < curve_count; ++curve_index) { + const PreparedCurve &curve = prepared_curves[static_cast(curve_index)]; + const float y0 = plot_min.y + row_height * static_cast(curve_index); + const float y1 = y0 + row_height; + const std::vector blocks = build_state_blocks(curve); + + if (curve_index > 0) { + draw_list->AddLine(ImVec2(plot_min.x, y0), ImVec2(plot_min.x + plot_size.x, y0), + IM_COL32(210, 214, 220, 255), 1.0f); + } + if (curve_count > 1) { + draw_list->AddLine(ImVec2(blocks_min_x, y0), ImVec2(blocks_min_x, y1), + IM_COL32(210, 214, 220, 255), 1.0f); + const float label_left = plot_min.x + 6.0f; + const float label_right = std::max(label_left + 12.0f, blocks_min_x - 6.0f); + ImGui::PushStyleColor(ImGuiCol_Text, color_rgb(120, 128, 138)); + ImGui::RenderTextEllipsis(draw_list, + ImVec2(label_left, y0 + 4.0f), + ImVec2(label_right, y1 - 4.0f), + label_right, + curve.label.c_str(), + nullptr, + nullptr); + ImGui::PopStyleColor(); + } + + for (const StateBlock &block : blocks) { + const double visible_t0 = std::max(block.t0, state->x_view_min); + const double visible_t1 = std::min(block.t1, state->x_view_max); + if (visible_t1 <= visible_t0) { + continue; + } + const float x0 = blocks_min_x + static_cast((visible_t0 - state->x_view_min) / x_span) * blocks_width; + const float x1 = blocks_min_x + static_cast((visible_t1 - state->x_view_min) / x_span) * blocks_width; + const ImU32 fill_color = state_block_color(block.value, 0.15f); + const ImU32 line_color = state_block_color(block.value, 0.90f); + draw_list->AddRectFilled(ImVec2(x0, y0), ImVec2(std::max(x1, x0 + 1.0f), y1), fill_color); + draw_list->AddLine(ImVec2(x0, y0), ImVec2(x0, y1), line_color, 2.0f); + + const float block_width = x1 - x0; + if (block_width > 14.0f) { + const float text_left = x0 + 6.0f; + const float text_right = x1 - 6.0f; + if (text_right > text_left) { + ImGui::PushStyleColor(ImGuiCol_Text, ImGui::ColorConvertU32ToFloat4(state_block_color(block.value, 0.80f))); + ImGui::RenderTextEllipsis(draw_list, + ImVec2(text_left, y0 + 4.0f), + ImVec2(text_right, y1 - 4.0f), + text_right, + block.label.c_str(), + nullptr, + nullptr); + ImGui::PopStyleColor(); + } + } + + if (plot_hovered && mouse_pos.x >= blocks_min_x && mouse_pos.x <= blocks_max_x && mouse_pos.y >= y0 && mouse_pos.y <= y1) { + const double hover_time = state->x_view_min + static_cast((mouse_pos.x - blocks_min_x) / blocks_width) * x_span; + if (hover_time >= block.t0 && hover_time <= block.t1) { + hovered = HoveredBlock{ + .curve_index = curve_index, + .block = block, + }; + } + } + } + } + + if (hovered.has_value()) { + const HoveredBlock &info = *hovered; + ImGui::BeginTooltip(); + if (curve_count > 1) { + ImGui::Text("%s: %s (%d)", prepared_curves[static_cast(info.curve_index)].label.c_str(), + info.block.label.c_str(), info.block.value); + } else { + ImGui::Text("%s (%d)", info.block.label.c_str(), info.block.value); + } + ImGui::Separator(); + ImGui::Text("%.3fs -> %.3fs", info.block.t0, info.block.t1); + ImGui::Text("duration: %.3fs", info.block.t1 - info.block.t0); + ImGui::EndTooltip(); + } +} + +void persist_shared_range_to_tab(WorkspaceTab *tab, const UiState &state) { + if (tab == nullptr || !state.has_shared_range) { + return; + } + const double x_min = state.x_view_min; + const double x_max = state.x_view_max > state.x_view_min ? state.x_view_max : state.x_view_min + 1.0; + for (Pane &pane : tab->panes) { + pane.range.valid = true; + pane.range.left = x_min; + pane.range.right = x_max; + } +} + +void clear_pane_vertical_limits(Pane *pane) { + if (pane == nullptr) { + return; + } + pane->range.has_y_limit_min = false; + pane->range.has_y_limit_max = false; +} + +PlotBounds current_plot_bounds_for_pane(const AppSession &session, const Pane &pane, const UiState &state) { + std::vector prepared_curves; + prepared_curves.reserve(pane.curves.size()); + constexpr int kAxisEditorMaxPoints = 2048; + for (size_t curve_index = 0; curve_index < pane.curves.size(); ++curve_index) { + const Curve &curve = pane.curves[curve_index]; + if (!curve.visible || !curve_has_samples(session, curve)) continue; + PreparedCurve prepared; + if (build_curve_series(session, curve, state, kAxisEditorMaxPoints, &prepared)) { + prepared.pane_curve_index = static_cast(curve_index); + prepared_curves.push_back(std::move(prepared)); + } + } + return compute_plot_bounds(pane, prepared_curves, state); +} + +void open_axis_limits_editor(const AppSession &session, UiState *state, int pane_index) { + ensure_shared_range(state, session); + clamp_shared_range(state, session); + const WorkspaceTab *tab = app_active_tab(session.layout, *state); + if (tab == nullptr || pane_index < 0 || pane_index >= static_cast(tab->panes.size())) { + return; + } + + const Pane &pane = tab->panes[static_cast(pane_index)]; + const PlotBounds bounds = current_plot_bounds_for_pane(session, pane, *state); + AxisLimitsEditorState &editor = state->axis_limits; + editor.open = true; + editor.pane_index = pane_index; + editor.x_min = state->x_view_min; + editor.x_max = state->x_view_max; + editor.y_min_enabled = pane.range.has_y_limit_min; + editor.y_max_enabled = pane.range.has_y_limit_max; + editor.y_min = pane.range.has_y_limit_min ? pane.range.y_limit_min : bounds.y_min; + editor.y_max = pane.range.has_y_limit_max ? pane.range.y_limit_max : bounds.y_max; +} + +bool apply_axis_limits_editor(AppSession *session, UiState *state) { + WorkspaceTab *tab = app_active_tab(&session->layout, *state); + if (tab == nullptr) return false; + + AxisLimitsEditorState &editor = state->axis_limits; + if (editor.pane_index < 0 || editor.pane_index >= static_cast(tab->panes.size())) { + state->error_text = "The selected pane is no longer available."; + state->open_error_popup = true; + return false; + } + if (!std::isfinite(editor.x_min) || !std::isfinite(editor.x_max)) { + state->error_text = "Axis limits must be finite numbers."; + state->open_error_popup = true; + return false; + } + if (editor.x_max <= editor.x_min) { + state->error_text = "X max must be greater than X min."; + state->open_error_popup = true; + return false; + } + if (editor.y_min_enabled && !std::isfinite(editor.y_min)) { + state->error_text = "Y min must be a finite number."; + state->open_error_popup = true; + return false; + } + if (editor.y_max_enabled && !std::isfinite(editor.y_max)) { + state->error_text = "Y max must be a finite number."; + state->open_error_popup = true; + return false; + } + if (editor.y_min_enabled && editor.y_max_enabled && editor.y_max <= editor.y_min) { + state->error_text = "Y max must be greater than Y min."; + state->open_error_popup = true; + return false; + } + + const SketchLayout before_layout = session->layout; + state->has_shared_range = true; + state->x_view_min = editor.x_min; + state->x_view_max = editor.x_max; + if (session->data_mode == SessionDataMode::Stream) { + state->follow_latest = infer_stream_follow_state(*state, *session); + } else { + state->follow_latest = false; + } + state->suppress_range_side_effects = true; + clamp_shared_range(state, *session); + persist_shared_range_to_tab(tab, *state); + + Pane &pane = tab->panes[static_cast(editor.pane_index)]; + pane.range.has_y_limit_min = editor.y_min_enabled; + pane.range.has_y_limit_max = editor.y_max_enabled; + if (editor.y_min_enabled) { + pane.range.y_limit_min = editor.y_min; + } + if (editor.y_max_enabled) { + pane.range.y_limit_max = editor.y_max; + } + + const PlotBounds bounds = current_plot_bounds_for_pane(*session, pane, *state); + pane.range.valid = true; + pane.range.left = state->x_view_min; + pane.range.right = state->x_view_max; + pane.range.bottom = bounds.y_min; + pane.range.top = bounds.y_max; + + state->undo.push(before_layout); + const bool ok = mark_layout_dirty(session, state); + if (ok) { + state->status_text = "Axis limits updated"; + } + return ok; +} + +void draw_plot(const AppSession &session, Pane *pane, UiState *state) { + std::vector prepared_curves; + prepared_curves.reserve(pane->curves.size()); + const int max_points = std::max(256, static_cast(ImGui::GetContentRegionAvail().x) * 2); + for (size_t curve_index = 0; curve_index < pane->curves.size(); ++curve_index) { + const Curve &curve = pane->curves[curve_index]; + if (!curve.visible || !curve_has_samples(session, curve)) continue; + PreparedCurve prepared; + if (build_curve_series(session, curve, *state, max_points, &prepared)) { + prepared.pane_curve_index = static_cast(curve_index); + prepared_curves.push_back(std::move(prepared)); + } + } + + const PlotBounds bounds = compute_plot_bounds(*pane, prepared_curves, *state); + PaneEnumContext enum_context; + PaneValueFormatContext pane_value_format; + const bool state_block_mode = curves_use_state_blocks(prepared_curves); + bool all_enum_curves = !prepared_curves.empty(); + size_t max_legend_label_width = 0; + for (const PreparedCurve &curve : prepared_curves) { + max_legend_label_width = std::max(max_legend_label_width, curve.label.size()); + if (curve.enum_info != nullptr) { + enum_context.enums.push_back(curve.enum_info); + } else { + all_enum_curves = false; + merge_pane_value_format(&pane_value_format, curve.display_info); + } + } + if (prepared_curves.empty()) { + all_enum_curves = false; + } + const int supported_count = static_cast(prepared_curves.size()); + const ImVec2 plot_size = ImGui::GetContentRegionAvail(); + const bool has_cursor_time = state->has_tracker_time; + const double cursor_time = state->tracker_time; + + ImPlot::PushStyleColor(ImPlotCol_PlotBg, color_rgb(255, 255, 255)); + ImPlot::PushStyleColor(ImPlotCol_PlotBorder, color_rgb(186, 190, 196)); + ImPlot::PushStyleColor(ImPlotCol_LegendBg, color_rgb(248, 249, 251, 0.92f)); + ImPlot::PushStyleColor(ImPlotCol_LegendBorder, color_rgb(168, 175, 184)); + ImPlot::PushStyleColor(ImPlotCol_LegendText, color_rgb(57, 62, 69)); + ImPlot::PushStyleColor(ImPlotCol_TitleText, color_rgb(57, 62, 69)); + ImPlot::PushStyleColor(ImPlotCol_InlayText, color_rgb(95, 103, 112)); + ImPlot::PushStyleColor(ImPlotCol_AxisGrid, color_rgb(188, 196, 206)); + ImPlot::PushStyleColor(ImPlotCol_AxisText, color_rgb(95, 103, 112)); + ImPlot::PushStyleColor(ImPlotCol_AxisBg, color_rgb(255, 255, 255, 0.0f)); + ImPlot::PushStyleColor(ImPlotCol_AxisBgHovered, color_rgb(214, 220, 228, 0.45f)); + ImPlot::PushStyleColor(ImPlotCol_AxisBgActive, color_rgb(199, 209, 222, 0.55f)); + ImPlot::PushStyleColor(ImPlotCol_Selection, color_rgb(252, 211, 77, 0.28f)); + ImPlot::PushStyleColor(ImPlotCol_Crosshairs, color_rgb(120, 128, 138, 0.70f)); + ImPlot::PushStyleVar(ImPlotStyleVar_LegendPadding, ImVec2(56.0f, 10.0f)); + + ImPlotFlags plot_flags = ImPlotFlags_NoTitle | ImPlotFlags_NoMenus; + if (state_block_mode) { + plot_flags |= ImPlotFlags_NoLegend | ImPlotFlags_NoMouseText; + } + if (supported_count == 0) { + plot_flags |= ImPlotFlags_NoLegend; + } + + const ImPlotAxisFlags x_axis_flags = ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_NoHighlight; + ImPlotAxisFlags y_axis_flags = ImPlotAxisFlags_NoMenus | ImPlotAxisFlags_NoHighlight; + if (state_block_mode) { + y_axis_flags |= ImPlotAxisFlags_NoDecorations; + } + const bool explicit_y = pane->range.has_y_limit_min || pane->range.has_y_limit_max; + if (!state_block_mode && !explicit_y && supported_count > 0) { + y_axis_flags |= ImPlotAxisFlags_AutoFit | ImPlotAxisFlags_RangeFit; + } + + const double previous_x_min = state->x_view_min; + const double previous_x_max = state->x_view_max; + app_push_mono_font(); + if (ImPlot::BeginPlot("##plot", plot_size, plot_flags)) { + ImPlot::SetupAxes(nullptr, nullptr, x_axis_flags, y_axis_flags); + ImPlot::SetupAxisFormat(ImAxis_X1, "%.1f"); + if (state_block_mode) { + ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, 1.0, ImPlotCond_Always); + } else if (all_enum_curves && !enum_context.enums.empty()) { + ImPlot::SetupAxisFormat(ImAxis_Y1, format_enum_axis_tick, &enum_context); + } else if (pane_value_format.valid) { + ImPlot::SetupAxisFormat(ImAxis_Y1, format_numeric_axis_tick, &pane_value_format); + } else { + ImPlot::SetupAxisFormat(ImAxis_Y1, "%.6g"); + } + ImPlot::SetupAxisLinks(ImAxis_X1, &state->x_view_min, &state->x_view_max); + if (state->route_x_max > state->route_x_min) { + const double x_constraint_min = session.data_mode == SessionDataMode::Stream + ? state->route_x_min - std::max(MIN_HORIZONTAL_ZOOM_SECONDS, session.stream_buffer_seconds) + : state->route_x_min; + ImPlot::SetupAxisLimitsConstraints(ImAxis_X1, x_constraint_min, state->route_x_max); + } + if (!state_block_mode) { + ImPlot::SetupMouseText(ImPlotLocation_SouthEast, ImPlotMouseTextFlags_NoAuxAxes); + } + if (!state_block_mode && (explicit_y || supported_count == 0)) { + ImPlot::SetupAxisLimits(ImAxis_Y1, bounds.y_min, bounds.y_max, ImPlotCond_Always); + } + if (!state_block_mode && supported_count > 0) { + ImPlot::SetupLegend(ImPlotLocation_NorthEast); + } + + if (state_block_mode) { + draw_state_blocks_pane(prepared_curves, state); + } else { + for (size_t i = 0; i < prepared_curves.size(); ++i) { + const PreparedCurve &curve = prepared_curves[i]; + std::string series_id = curve_legend_label(curve, has_cursor_time, max_legend_label_width) + "##curve" + std::to_string(i); + ImPlotSpec spec; + spec.LineColor = color_rgb(curve.color); + spec.LineWeight = curve.line_weight; + spec.Flags = ImPlotLineFlags_SkipNaN; + if (!curve.xs.empty() && curve.xs.size() == curve.ys.size()) { + if (curve.stairs) { + spec.Flags = ImPlotStairsFlags_PreStep; + ImPlot::PlotStairs(series_id.c_str(), curve.xs.data(), curve.ys.data(), static_cast(curve.xs.size()), spec); + } else { + ImPlot::PlotLine(series_id.c_str(), curve.xs.data(), curve.ys.data(), static_cast(curve.xs.size()), spec); + } + } + } + } + if (has_cursor_time) { + const double clamped_cursor_time = std::clamp(cursor_time, state->route_x_min, state->route_x_max); + ImPlotSpec cursor_spec; + cursor_spec.LineColor = color_rgb(108, 118, 128, 0.7f); + cursor_spec.LineWeight = 1.0f; + cursor_spec.Flags = ImPlotItemFlags_NoLegend; + ImPlot::PlotInfLines("##tracker_cursor", &clamped_cursor_time, 1, cursor_spec); + } + if (ImPlot::IsPlotHovered() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + state->tracker_time = std::clamp(ImPlot::GetPlotMousePos().x, state->route_x_min, state->route_x_max); + state->has_tracker_time = true; + } + ImPlot::EndPlot(); + } + app_pop_mono_font(); + clamp_shared_range(state, session); + if (std::abs(state->x_view_min - previous_x_min) > 1.0e-6 + || std::abs(state->x_view_max - previous_x_max) > 1.0e-6) { + if (!state->suppress_range_side_effects) { + if (session.data_mode == SessionDataMode::Stream) { + state->follow_latest = infer_stream_follow_state(*state, session); + } else { + state->follow_latest = false; + } + } + } + ImPlot::PopStyleVar(); + ImPlot::PopStyleColor(12); +} + +std::optional draw_pane_context_menu(const WorkspaceTab &tab, int pane_index) { + if (!ImGui::BeginPopupContextWindow("##pane_context")) return std::nullopt; + + PaneMenuAction action; + action.pane_index = pane_index; + const Pane *pane = pane_index >= 0 && pane_index < static_cast(tab.panes.size()) + ? &tab.panes[static_cast(pane_index)] + : nullptr; + const bool has_curves = pane_index >= 0 + && pane_index < static_cast(tab.panes.size()) + && !tab.panes[static_cast(pane_index)].curves.empty(); + const bool is_plot = pane != nullptr && pane->kind == PaneKind::Plot; + if (icon_menu_item(icon::SLIDERS, "Edit Axis Limits...", nullptr, false, is_plot)) { + action.kind = PaneMenuActionKind::OpenAxisLimits; + } + icon_menu_item(icon::PALETTE, "Edit Curve Style...", nullptr, false, false && is_plot); + if (action.kind == PaneMenuActionKind::None + && icon_menu_item(icon::PLUS_SLASH_MINUS, "Apply filter to data...", nullptr, false, has_curves && is_plot)) { + action.kind = PaneMenuActionKind::OpenCustomSeries; + } + ImGui::Separator(); + if (action.kind == PaneMenuActionKind::None && icon_menu_item(icon::DISTRIBUTE_HORIZONTAL, "Split Left / Right")) { + action.kind = PaneMenuActionKind::SplitRight; + } else if (action.kind == PaneMenuActionKind::None + && icon_menu_item(icon::DISTRIBUTE_VERTICAL, "Split Top / Bottom")) { + action.kind = PaneMenuActionKind::SplitBottom; + } + ImGui::Separator(); + if (icon_menu_item(icon::ZOOM_OUT, "Zoom Out", nullptr, false, is_plot)) { + action.kind = PaneMenuActionKind::ResetView; + } else if (icon_menu_item(icon::ARROW_LEFT_RIGHT, "Zoom Out Horizontally", nullptr, false, is_plot)) { + action.kind = PaneMenuActionKind::ResetHorizontal; + } else if (icon_menu_item(icon::ARROW_DOWN_UP, "Zoom Out Vertically", nullptr, false, is_plot)) { + action.kind = PaneMenuActionKind::ResetVertical; + } + ImGui::Separator(); + if (icon_menu_item(icon::TRASH, "Remove ALL curves", nullptr, false, is_plot)) { + action.kind = PaneMenuActionKind::Clear; + } + ImGui::Separator(); + icon_menu_item(icon::ARROW_LEFT_RIGHT, "Flip Horizontal Axis", nullptr, false, false); + icon_menu_item(icon::ARROW_DOWN_UP, "Flip Vertical Axis", nullptr, false, false); + ImGui::Separator(); + icon_menu_item(icon::FILES, "Copy", nullptr, false, false); + icon_menu_item(icon::CLIPBOARD2, "Paste", nullptr, false, false); + icon_menu_item(icon::FILE_EARMARK_IMAGE, "Copy image to clipboard", nullptr, false, false); + icon_menu_item(icon::SAVE, "Save plot to file", nullptr, false, false); + icon_menu_item(icon::BAR_CHART, "Show data statistics", nullptr, false, false); + ImGui::Separator(); + if (icon_menu_item(icon::X_SQUARE, "Close Pane")) { + action.kind = PaneMenuActionKind::Close; + } + ImGui::EndPopup(); + if (action.kind == PaneMenuActionKind::None) return std::nullopt; + return action; +} diff --git a/tools/jotpluggler/render.cc b/tools/jotpluggler/render.cc new file mode 100644 index 00000000000..54f0c16cc39 --- /dev/null +++ b/tools/jotpluggler/render.cc @@ -0,0 +1,173 @@ +#include "tools/jotpluggler/internal.h" + +#include "imgui_impl_glfw.h" +#include "imgui_impl_opengl3.h" +#include "imgui_impl_opengl3_loader.h" + +#include + +namespace fs = std::filesystem; + +void draw_fps_overlay(const UiState &state, float top_offset) { + if (!state.show_fps_overlay) { + return; + } + ImGuiViewport *viewport = ImGui::GetMainViewport(); + const ImGuiIO &io = ImGui::GetIO(); + const float fps = io.Framerate; + const std::string label = util::string_format("%.1f fps", fps); + + const ImVec2 padding(10.0f, 8.0f); + const ImVec2 margin(12.0f, 10.0f); + app_push_mono_font(); + ImFont *font = ImGui::GetFont(); + const float font_size = ImGui::GetFontSize(); + const ImVec2 text_size = ImGui::CalcTextSize(label.c_str()); + app_pop_mono_font(); + const ImVec2 size(text_size.x + padding.x * 2.0f, text_size.y + padding.y * 2.0f); + const ImVec2 pos(viewport->Pos.x + viewport->Size.x - size.x - margin.x, + viewport->Pos.y + top_offset + margin.y); + ImDrawList *draw_list = ImGui::GetForegroundDrawList(viewport); + const ImVec2 max(pos.x + size.x, pos.y + size.y); + draw_list->AddRectFilled(pos, max, ImGui::GetColorU32(color_rgb(248, 249, 251, 0.92f)), 4.0f); + draw_list->AddRect(pos, max, ImGui::GetColorU32(color_rgb(182, 188, 196, 0.95f)), 4.0f); + draw_list->AddText(font, font_size, ImVec2(pos.x + padding.x, pos.y + padding.y), + ImGui::GetColorU32(color_rgb(57, 62, 69)), label.c_str(), nullptr); +} + +void render_layout(AppSession *session, UiState *state, bool show_camera_feed) { + if (!state->fps_overlay_initialized) { + state->show_fps_overlay = false; + state->fps_overlay_initialized = true; + } + ensure_shared_range(state, *session); + if (state->follow_latest) { + update_follow_range(state, *session); + state->suppress_range_side_effects = true; + } else { + clamp_shared_range(state, *session); + } + const bool ctrl = ImGui::GetIO().KeyCtrl || ImGui::GetIO().KeySuper; + const bool shift = ImGui::GetIO().KeyShift; + if (!ImGui::GetIO().WantTextInput && ctrl && ImGui::IsKeyPressed(ImGuiKey_Z, false)) { + if (shift) { + apply_redo(session, state); + } else { + apply_undo(session, state); + } + } + if (!ImGui::GetIO().WantTextInput && ctrl && ImGui::IsKeyPressed(ImGuiKey_F, false)) { + state->open_find_signal = true; + } + if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow, false)) { + step_tracker(state, -1.0); + } + if (ImGui::IsKeyPressed(ImGuiKey_RightArrow, false)) { + step_tracker(state, 1.0); + } + if (!ImGui::GetIO().WantTextInput && ImGui::IsKeyPressed(ImGuiKey_Space, false)) { + state->playback_playing = !state->playback_playing; + } + advance_playback(state, *session); + CameraFeedView *sidebar_camera = session->pane_camera_feeds[static_cast(sidebar_preview_camera_view(*session))].get(); + if (show_camera_feed && sidebar_camera != nullptr && state->has_tracker_time) { + sidebar_camera->update(state->tracker_time); + } + const float menu_height = draw_main_menu_bar(session, state); + UiMetrics ui = compute_ui_metrics(ImGui::GetMainViewport()->Size, menu_height, state->sidebar_width); + if (state->browser_nodes_dirty) { + rebuild_browser_nodes(session, state); + state->browser_nodes_dirty = false; + } + state->sidebar_width = ui.sidebar_width; + draw_sidebar(session, ui, state, show_camera_feed); + draw_workspace(session, ui, state); + draw_sidebar_resizer(ui, state); + if (!state->custom_series.selected && !state->logs.selected) { + draw_pane_windows(session, state); + } + draw_status_bar(*session, ui, state); + draw_popups(session, state); + draw_fps_overlay(*state, menu_height); +} + +void save_framebuffer_png(const fs::path &output_path, int width, int height) { + ensure_parent_dir(output_path); + if (width <= 0 || height <= 0) throw std::runtime_error("Invalid framebuffer size"); + + std::vector pixels(static_cast(width) * static_cast(height) * 4U, 0); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data()); + + const fs::path ppm_path = output_path.parent_path() / (output_path.stem().string() + ".ppm"); + std::string ppm = util::string_format("P6\n%d %d\n255\n", width, height); + ppm.reserve(ppm.size() + static_cast(width) * static_cast(height) * 3U); + for (int y = height - 1; y >= 0; --y) { + for (int x = 0; x < width; ++x) { + const size_t index = static_cast((y * width + x) * 4); + ppm.append(reinterpret_cast(&pixels[index]), 3); + } + } + write_file_or_throw(ppm_path, ppm.data(), ppm.size()); + + const std::string command = "convert " + shell_quote(ppm_path.string()) + " " + shell_quote(output_path.string()); + run_system_or_throw(command, "image conversion"); + fs::remove(ppm_path); +} + +void render_frame(GLFWwindow *window, AppSession *session, UiState *state, const fs::path *capture_path) { + glfwPollEvents(); + + int framebuffer_width = 0; + int framebuffer_height = 0; + glfwGetFramebufferSize(window, &framebuffer_width, &framebuffer_height); + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + if (state->request_save_layout) { + if (session->layout_path.empty()) { + state->open_save_layout = true; + } else { + save_layout(session, state, session->layout_path.string()); + } + state->request_save_layout = false; + } + if (state->request_reset_layout) { + reset_layout(session, state); + state->request_reset_layout = false; + } + poll_async_route_load(session, state); + if (session->data_mode == SessionDataMode::Stream && session->stream_poller) { + StreamExtractBatch batch; + std::string error_text; + if (session->stream_poller->consume(&batch, &error_text)) { + if (!error_text.empty()) { + state->error_text = error_text; + state->open_error_popup = true; + state->status_text = "Stream disconnected"; + } else { + apply_stream_batch(session, state, std::move(batch)); + } + } + } + + const bool show_camera = capture_path == nullptr && session->data_mode != SessionDataMode::Stream; + render_layout(session, state, show_camera); + ImGui::Render(); + if (state->request_close) { + glfwSetWindowShouldClose(window, GLFW_TRUE); + state->request_close = false; + } + + glViewport(0, 0, framebuffer_width, framebuffer_height); + glClearColor(227.0f / 255.0f, 229.0f / 255.0f, 233.0f / 255.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + if (capture_path != nullptr) { + save_framebuffer_png(*capture_path, framebuffer_width, framebuffer_height); + } + glfwSwapBuffers(window); + state->suppress_range_side_effects = false; +} diff --git a/tools/jotpluggler/runtime.cc b/tools/jotpluggler/runtime.cc new file mode 100644 index 00000000000..47344c5519a --- /dev/null +++ b/tools/jotpluggler/runtime.cc @@ -0,0 +1,1280 @@ +#include "tools/jotpluggler/app.h" +#include "tools/jotpluggler/common.h" + +#include "cereal/services.h" +#include "common/timing.h" +#include "imgui_impl_glfw.h" +#include "imgui_impl_opengl3.h" +#include "imgui_impl_opengl3_loader.h" +#include "implot.h" +#include "libyuv.h" +#include "msgq_repo/msgq/ipc.h" +#include "tools/replay/framereader.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "system/camerad/cameras/nv12_info.h" + +namespace { + +std::atomic g_glfw_alive{false}; +const bool kLogCameraTimings = env_flag_enabled("JOTP_CAMERA_TIMINGS"); + +CameraType decoder_camera_type(CameraViewKind view) { + switch (view) { + case CameraViewKind::Driver: return DriverCam; + case CameraViewKind::WideRoad: return WideRoadCam; + case CameraViewKind::QRoad: return RoadCam; + case CameraViewKind::Road: + default: return RoadCam; + } +} + +bool stream_batch_has_data(const StreamExtractBatch &batch) { + return !batch.series.empty() + || !batch.can_messages.empty() + || !batch.logs.empty() + || !batch.timeline.empty() + || !batch.enum_info.empty() + || !batch.car_fingerprint.empty() + || !batch.dbc_name.empty(); +} + +bool should_subscribe_stream_service(const std::string &name) { + static const std::array kSkippedServices = {{ + "roadEncodeIdx", + "driverEncodeIdx", + "wideRoadEncodeIdx", + "qRoadEncodeIdx", + "roadEncodeData", + "driverEncodeData", + "wideRoadEncodeData", + "qRoadEncodeData", + "livestreamWideRoadEncodeIdx", + "livestreamRoadEncodeIdx", + "livestreamDriverEncodeIdx", + "thumbnail", + "navThumbnail", + }}; + if (name == "rawAudioData") return false; + for (std::string_view skipped : kSkippedServices) { + if (name == skipped) return false; + } + return true; +} + +void glfw_error_callback(int error, const char *description) { + const std::string_view desc = description != nullptr ? description : "unknown"; + if (error == 65539 && desc.find("Invalid window attribute 0x0002000D") != std::string_view::npos) { + return; + } + std::cerr << "GLFW error " << error << ": " << desc << "\n"; +} + +} // namespace + +GlfwRuntime::GlfwRuntime(const Options &options) { + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) throw std::runtime_error("glfwInit failed"); + g_glfw_alive.store(true); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +#ifdef __APPLE__ + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); +#endif + const bool fixed_size = !options.show; + glfwWindowHint(GLFW_RESIZABLE, fixed_size ? GLFW_FALSE : GLFW_TRUE); + glfwWindowHint(GLFW_VISIBLE, options.show ? GLFW_TRUE : GLFW_FALSE); + + window_ = glfwCreateWindow(options.width, options.height, "jotpluggler", nullptr, nullptr); + if (window_ == nullptr) { + glfwTerminate(); + throw std::runtime_error("glfwCreateWindow failed"); + } + + if (fixed_size) { + glfwSetWindowSizeLimits(window_, options.width, options.height, options.width, options.height); + } + glfwMakeContextCurrent(window_); + glfwSwapInterval(options.show ? 1 : 0); +} + +GlfwRuntime::~GlfwRuntime() { + if (window_ != nullptr) { + glfwDestroyWindow(window_); + } + g_glfw_alive.store(false); + glfwTerminate(); +} + +GLFWwindow *GlfwRuntime::window() const { + return window_; +} + +ImGuiRuntime::ImGuiRuntime(GLFWwindow *window) { + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImPlot::CreateContext(); + + ImGuiIO &io = ImGui::GetIO(); + io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; + io.IniFilename = nullptr; + io.LogFilename = nullptr; + + if (!ImGui_ImplGlfw_InitForOpenGL(window, true)) { + ImPlot::DestroyContext(); + ImGui::DestroyContext(); + throw std::runtime_error("ImGui_ImplGlfw_InitForOpenGL failed"); + } + if (!ImGui_ImplOpenGL3_Init("#version 330")) { + ImGui_ImplGlfw_Shutdown(); + ImPlot::DestroyContext(); + ImGui::DestroyContext(); + throw std::runtime_error("ImGui_ImplOpenGL3_Init failed"); + } +} + +ImGuiRuntime::~ImGuiRuntime() { + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImPlot::DestroyContext(); + ImGui::DestroyContext(); +} + +struct TerminalRouteProgress::Impl { + explicit Impl(bool enabled) : enabled_(enabled) {} + + void update(const RouteLoadProgress &progress) { + std::lock_guard lock(mutex_); + if (!enabled_) { + return; + } + + double overall = 0.0; + std::string detail = "Resolving route"; + if (progress.stage == RouteLoadStage::Finished) { + overall = 1.0; + detail = "Ready"; + } else if (progress.total_segments > 0) { + const bool finalizing = progress.segments_downloaded >= progress.total_segments + && progress.segments_parsed >= progress.total_segments; + if (finalizing) { + overall = 0.99; + detail = "Finalizing route data"; + } else { + const double total_work = static_cast(progress.total_segments) * 2.0; + const double complete_work = static_cast(progress.segments_downloaded + progress.segments_parsed); + overall = total_work <= 0.0 ? 0.0 : std::clamp(complete_work / total_work, 0.0, 0.99); + std::ostringstream desc; + desc << "Downloaded " << progress.segments_downloaded << "/" << progress.total_segments + << " Parsed " << progress.segments_parsed << "/" << progress.total_segments; + detail = desc.str(); + } + } + + render(overall, detail); + } + + void finish() { + std::lock_guard lock(mutex_); + if (!enabled_ || !rendered_) { + return; + } + render(1.0, "Ready"); + std::fputc('\n', stderr); + std::fflush(stderr); + rendered_ = false; + } + + void render(double progress, const std::string &detail) { + const int percent = std::clamp(static_cast(std::round(progress * 100.0)), 0, 100); + if (percent == last_percent_ && detail == last_detail_) { + return; + } + + constexpr int kWidth = 20; + const int filled = std::clamp(static_cast(std::round(progress * kWidth)), 0, kWidth); + const std::string bar = std::string(static_cast(filled), '=') + std::string(static_cast(kWidth - filled), ' '); + std::ostringstream line; + line << "\r[" << bar << "] " << percent << "% " << detail; + + const std::string text = line.str(); + std::fprintf(stderr, "%s", text.c_str()); + if (text.size() < last_line_width_) { + std::fprintf(stderr, "%s", std::string(last_line_width_ - text.size(), ' ').c_str()); + } + std::fflush(stderr); + + rendered_ = true; + last_percent_ = percent; + last_detail_ = detail; + last_line_width_ = text.size(); + } + + bool enabled_ = false; + bool rendered_ = false; + int last_percent_ = -1; + size_t last_line_width_ = 0; + std::string last_detail_; + std::mutex mutex_; +}; + +TerminalRouteProgress::TerminalRouteProgress(bool enabled) + : impl_(std::make_unique(enabled)) {} + +TerminalRouteProgress::~TerminalRouteProgress() { + finish(); +} + +void TerminalRouteProgress::update(const RouteLoadProgress &progress) { + impl_->update(progress); +} + +void TerminalRouteProgress::finish() { + impl_->finish(); +} + +struct AsyncRouteLoader::Impl { + explicit Impl(bool enable_terminal_progress) + : terminal_progress(enable_terminal_progress) {} + + ~Impl() { + join(); + } + + void start(const std::string &route_name_value, const std::string &data_dir_value, const std::string &dbc_name_value) { + join(); + { + std::lock_guard lock(mutex); + route_name = route_name_value; + data_dir = data_dir_value; + dbc_name = dbc_name_value; + result.reset(); + error_text.clear(); + } + active.store(!route_name_value.empty()); + completed.store(route_name_value.empty()); + success.store(route_name_value.empty()); + total_segments.store(0); + segments_downloaded.store(0); + segments_parsed.store(0); + if (route_name_value.empty()) { + return; + } + + worker = std::thread([this]() { + try { + RouteData route_data = load_route_data(route_name, data_dir, dbc_name, [this](const RouteLoadProgress &progress) { + total_segments.store(progress.total_segments > 0 ? progress.total_segments : progress.segment_count); + segments_downloaded.store(progress.segments_downloaded); + segments_parsed.store(progress.segments_parsed); + terminal_progress.update(progress); + }); + { + std::lock_guard lock(mutex); + result = std::make_unique(std::move(route_data)); + error_text.clear(); + } + success.store(true); + } catch (const std::exception &err) { + std::lock_guard lock(mutex); + result.reset(); + error_text = err.what(); + success.store(false); + } + active.store(false); + completed.store(true); + terminal_progress.finish(); + }); + } + + RouteLoadSnapshot snapshot() const { + RouteLoadSnapshot snapshot; + snapshot.active = active.load(); + snapshot.total_segments = total_segments.load(); + snapshot.segments_downloaded = segments_downloaded.load(); + snapshot.segments_parsed = segments_parsed.load(); + return snapshot; + } + + bool consume(RouteData *route_data, std::string *error_text_out) { + if (!completed.load()) return false; + join(); + std::lock_guard lock(mutex); + completed.store(false); + if (result) { + *route_data = std::move(*result); + result.reset(); + if (error_text_out != nullptr) { + error_text_out->clear(); + } + return true; + } + if (error_text_out != nullptr) { + *error_text_out = error_text; + } + return true; + } + + void join() { + if (worker.joinable()) { + worker.join(); + } + } + + mutable std::mutex mutex; + std::thread worker; + std::unique_ptr result; + std::string route_name; + std::string data_dir; + std::string dbc_name; + std::string error_text; + std::atomic active{false}; + std::atomic completed{false}; + std::atomic success{false}; + std::atomic total_segments{0}; + std::atomic segments_downloaded{0}; + std::atomic segments_parsed{0}; + TerminalRouteProgress terminal_progress; +}; + +AsyncRouteLoader::AsyncRouteLoader(bool enable_terminal_progress) + : impl_(std::make_unique(enable_terminal_progress)) {} + +AsyncRouteLoader::~AsyncRouteLoader() = default; + +void AsyncRouteLoader::start(const std::string &route_name, const std::string &data_dir, const std::string &dbc_name) { + impl_->start(route_name, data_dir, dbc_name); +} + +RouteLoadSnapshot AsyncRouteLoader::snapshot() const { + return impl_->snapshot(); +} + +bool AsyncRouteLoader::consume(RouteData *route_data, std::string *error_text) { + return impl_->consume(route_data, error_text); +} + +struct StreamPoller::Impl { + ~Impl() { + stop(); + } + + void start(const StreamSourceConfig &requested_source, + double requested_buffer_seconds, + const std::string &dbc_name, + std::optional time_offset) { + stop(); + { + std::lock_guard lock(mutex); + pending = {}; + pending_series_slots.clear(); + pending_can_slots.clear(); + error_text.clear(); + source = requested_source; + if (source.kind == StreamSourceKind::CerealLocal) { + source.address = "127.0.0.1"; + } else if (source.kind == StreamSourceKind::CerealRemote) { + source.address = normalize_stream_address(source.address); + } + buffer_seconds = std::max(1.0, requested_buffer_seconds); + latest_dbc_name = dbc_name; + latest_car_fingerprint.clear(); + } + received_messages.store(0); + connected.store(false); + paused.store(false); + running.store(true); + worker = std::thread([this, dbc_name, time_offset]() { + try { + StreamAccumulator accumulator(dbc_name, time_offset); + switch (source.kind) { + case StreamSourceKind::CerealLocal: + case StreamSourceKind::CerealRemote: + run_cereal_source(&accumulator); + break; + } + } catch (const std::exception &err) { + std::lock_guard lock(mutex); + error_text = err.what(); + } + connected.store(false); + running.store(false); + }); + } + + void setPaused(bool paused_value) { + paused.store(paused_value); + if (paused_value) { + std::lock_guard lock(mutex); + pending = {}; + pending_series_slots.clear(); + pending_can_slots.clear(); + error_text.clear(); + } + } + + void set_error_text(std::string text) { + std::lock_guard lock(mutex); + error_text = std::move(text); + } + + void clear_error_text() { + std::lock_guard lock(mutex); + error_text.clear(); + } + + void stop() { + running.store(false); + paused.store(false); + if (worker.joinable()) { + worker.join(); + } + connected.store(false); + } + + StreamPollSnapshot snapshot() const { + StreamPollSnapshot out; + out.active = running.load(); + out.connected = connected.load(); + out.paused = paused.load(); + out.source_kind = source.kind; + out.source_label = stream_source_target_label(source); + out.buffer_seconds = buffer_seconds; + out.received_messages = received_messages.load(); + std::lock_guard lock(mutex); + out.dbc_name = latest_dbc_name; + out.car_fingerprint = latest_car_fingerprint; + return out; + } + + bool consume(StreamExtractBatch *batch, std::string *out_error_text) { + std::lock_guard lock(mutex); + const bool has_error = !error_text.empty(); + const bool has_batch = stream_batch_has_data(pending); + if (!has_error && !has_batch) return false; + if (batch != nullptr) { + *batch = std::move(pending); + pending = {}; + pending_series_slots.clear(); + pending_can_slots.clear(); + } + if (out_error_text != nullptr) { + *out_error_text = error_text; + error_text.clear(); + } + return true; + } + + static void merge_route_series(RouteSeries *dst, RouteSeries *src) { + if (src->times.empty()) { + return; + } + if (dst->path.empty()) { + dst->path = src->path; + } + dst->times.insert(dst->times.end(), src->times.begin(), src->times.end()); + dst->values.insert(dst->values.end(), src->values.begin(), src->values.end()); + } + + static void merge_can_message_data(CanMessageData *dst, CanMessageData *src) { + if (src->samples.empty()) { + return; + } + if (dst->samples.empty()) { + *dst = std::move(*src); + return; + } + dst->samples.insert(dst->samples.end(), + std::make_move_iterator(src->samples.begin()), + std::make_move_iterator(src->samples.end())); + } + + static void merge_batch(StreamExtractBatch *dst, + std::unordered_map *series_slots, + std::unordered_map *can_slots, + StreamExtractBatch *src) { + for (RouteSeries &series : src->series) { + auto [it, inserted] = series_slots->try_emplace(series.path, dst->series.size()); + if (inserted) { + dst->series.push_back(RouteSeries{.path = series.path}); + } + merge_route_series(&dst->series[it->second], &series); + } + for (CanMessageData &message : src->can_messages) { + auto [it, inserted] = can_slots->try_emplace(message.id, dst->can_messages.size()); + if (inserted) { + dst->can_messages.push_back(CanMessageData{.id = message.id}); + } + merge_can_message_data(&dst->can_messages[it->second], &message); + } + if (!src->logs.empty()) { + dst->logs.insert(dst->logs.end(), + std::make_move_iterator(src->logs.begin()), + std::make_move_iterator(src->logs.end())); + } + if (!src->timeline.empty()) { + dst->timeline.insert(dst->timeline.end(), + std::make_move_iterator(src->timeline.begin()), + std::make_move_iterator(src->timeline.end())); + } + for (auto &[path, info] : src->enum_info) { + dst->enum_info[path] = std::move(info); + } + if (!src->car_fingerprint.empty()) { + dst->car_fingerprint = src->car_fingerprint; + } + if (!src->dbc_name.empty()) { + dst->dbc_name = src->dbc_name; + } + } + + void publish_batch(StreamAccumulator *accumulator) { + StreamExtractBatch batch = accumulator->takeBatch(); + if (!stream_batch_has_data(batch)) { + return; + } + std::lock_guard lock(mutex); + merge_batch(&pending, &pending_series_slots, &pending_can_slots, &batch); + latest_dbc_name = pending.dbc_name; + latest_car_fingerprint = pending.car_fingerprint; + } + + void run_cereal_source(StreamAccumulator *accumulator) { + if (source.kind == StreamSourceKind::CerealRemote) { + setenv("ZMQ", "1", 1); + } else { + unsetenv("ZMQ"); + } + + std::unique_ptr context(Context::create()); + std::unique_ptr poller(Poller::create()); + std::vector> sockets; + sockets.reserve(services.size()); + for (const auto &[name, info] : services) { + if (!should_subscribe_stream_service(name)) continue; + std::unique_ptr socket( + SubSocket::create(context.get(), name.c_str(), source.address.c_str(), false, true, info.queue_size)); + if (socket == nullptr) continue; + socket->setTimeout(0); + poller->registerSocket(socket.get()); + sockets.push_back(std::move(socket)); + } + if (sockets.empty()) throw std::runtime_error("Failed to connect to any cereal service"); + connected.store(true); + + while (running.load()) { + std::vector ready = poller->poll(1); + for (SubSocket *socket : ready) { + while (running.load()) { + std::unique_ptr msg(socket->receive(true)); + if (!msg) break; + const size_t size = msg->getSize(); + if (size < sizeof(capnp::word) || (size % sizeof(capnp::word)) != 0) { + continue; + } + if (paused.load()) { + received_messages.fetch_add(1); + continue; + } + kj::ArrayPtr data(reinterpret_cast(msg->getData()), + size / sizeof(capnp::word)); + capnp::FlatArrayMessageReader event_reader(data); + const cereal::Event::Reader event = event_reader.getRoot(); + accumulator->appendEvent(event.which(), data); + received_messages.fetch_add(1); + } + } + publish_batch(accumulator); + } + } + + mutable std::mutex mutex; + std::thread worker; + std::atomic running{false}; + std::atomic connected{false}; + std::atomic paused{false}; + std::atomic received_messages{0}; + StreamExtractBatch pending; + std::unordered_map pending_series_slots; + std::unordered_map pending_can_slots; + std::string error_text; + StreamSourceConfig source; + std::string latest_dbc_name; + std::string latest_car_fingerprint; + double buffer_seconds = 30.0; +}; + +StreamPoller::StreamPoller() + : impl_(std::make_unique()) {} + +StreamPoller::~StreamPoller() = default; + +void StreamPoller::start(const StreamSourceConfig &source, + double buffer_seconds, + const std::string &dbc_name, + std::optional time_offset) { + impl_->start(source, buffer_seconds, dbc_name, time_offset); +} + +void StreamPoller::setPaused(bool paused) { + impl_->setPaused(paused); +} + +void StreamPoller::stop() { + impl_->stop(); +} + +StreamPollSnapshot StreamPoller::snapshot() const { + return impl_->snapshot(); +} + +bool StreamPoller::consume(StreamExtractBatch *batch, std::string *error_text) { + return impl_->consume(batch, error_text); +} + +struct CameraFeedView::Impl { + struct RequestKey { + int segment = -1; + int decode_index = -1; + }; + + struct DecodeRequest { + RequestKey key; + std::string path; + uint64_t serial = 0; + uint64_t generation = 0; + }; + + struct PreloadTask { + int segment = -1; + std::string path; + uint64_t generation = 0; + }; + + struct DecodeResult { + RequestKey key; + bool success = false; + int width = 0; + int height = 0; + double decode_ms = 0.0; + std::vector rgba; + }; + + static constexpr float kDefaultAspect = 1208.0f / 1928.0f; + static constexpr size_t kCachedFrames = 8; + static constexpr int kPrefetchAhead = 2; + static constexpr int kImmediateNearbyFrameDistance = 8; + static constexpr int kPreloadWorkerCount = 2; + + Impl() { + demand_worker = std::thread([this]() { demand_worker_loop(); }); + for (int i = 0; i < kPreloadWorkerCount; ++i) { + preload_workers.emplace_back([this]() { preload_worker_loop(); }); + } + } + + ~Impl() { + stop.store(true); + cv.notify_all(); + if (demand_worker.joinable()) { + demand_worker.join(); + } + for (std::thread &worker : preload_workers) { + if (worker.joinable()) { + worker.join(); + } + } + destroy_texture(); + } + + void setRouteData(const RouteData &route_data) { + setCameraIndex(route_data.road_camera, CameraViewKind::Road); + } + + void setCameraIndex(const CameraFeedIndex &camera_index, CameraViewKind view) { + destroy_texture(); + { + std::lock_guard lock(mutex); + route_index = camera_index; + camera_view = view; + pending_request.reset(); + pending_result.reset(); + cached_results.clear(); + preload_queue.clear(); + preload_focus_segment = -1; + ++route_generation; + latest_request_serial = 0; + int initial_focus_segment = -1; + if (!route_index.entries.empty()) { + initial_focus_segment = route_index.entries.front().segment; + } else { + for (const CameraSegmentFile &segment_file : route_index.segment_files) { + if (!segment_file.path.empty()) { + initial_focus_segment = segment_file.segment; + break; + } + } + } + if (initial_focus_segment >= 0) { + rebuild_preload_queue_locked(initial_focus_segment); + } + } + abort_demand_work.store(true); + abort_preload_work.store(true); + active_request.reset(); + displayed_request.reset(); + failed_request.reset(); + frame_width = 0; + frame_height = 0; + cv.notify_all(); + } + + void update(double tracker_time) { + upload_pending_result(); + std::optional request = request_for_time(tracker_time); + if (!request.has_value()) { + return; + } + if (same_request(active_request, request->key) || same_request(displayed_request, request->key) || + same_request(failed_request, request->key)) { + return; + } + if (try_upload_cached_result(request->key)) { + return; + } + try_upload_nearby_cached_result(request->key); + + bool focus_changed = false; + { + std::lock_guard lock(mutex); + if (preload_focus_segment != request->key.segment) { + rebuild_preload_queue_locked(request->key.segment); + focus_changed = true; + } + request->serial = ++latest_request_serial; + request->generation = route_generation; + pending_request = request; + } + abort_demand_work.store(true); + if (focus_changed) { + abort_preload_work.store(true); + } + active_request = request->key; + failed_request.reset(); + cv.notify_all(); + } + + void draw(float width, bool loading) { + const float preview_width = std::max(1.0f, width); + const float preview_height = preview_width * preview_aspect(); + drawSized(ImVec2(preview_width, preview_height), loading, false); + ImGui::Spacing(); + } + + void drawSized(ImVec2 size, bool loading, bool fit_to_pane) { + size.x = std::max(1.0f, size.x); + size.y = std::max(1.0f, size.y); + const float aspect = preview_aspect(); + ImVec2 frame_size = size; + ImVec2 top_left = ImGui::GetCursorScreenPos(); + ImVec2 uv0(0.0f, 0.0f); + ImVec2 uv1(1.0f, 1.0f); + if (aspect > 0.0f && !fit_to_pane) { + frame_size.y = std::min(size.y, size.x * aspect); + frame_size.x = std::min(size.x, frame_size.y / aspect); + top_left = ImVec2(top_left.x + (size.x - frame_size.x) * 0.5f, + top_left.y + (size.y - frame_size.y) * 0.5f); + } else if (aspect > 0.0f && fit_to_pane) { + const float src_aspect = 1.0f / aspect; + const float dst_aspect = size.x / size.y; + if (dst_aspect > src_aspect) { + const float visible_v = std::clamp(src_aspect / dst_aspect, 0.0f, 1.0f); + const float v_pad = (1.0f - visible_v) * 0.5f; + uv0.y = v_pad; + uv1.y = 1.0f - v_pad; + } else if (dst_aspect < src_aspect) { + const float visible_u = std::clamp(dst_aspect / src_aspect, 0.0f, 1.0f); + const float u_pad = (1.0f - visible_u) * 0.5f; + uv0.x = u_pad; + uv1.x = 1.0f - u_pad; + } + } + ImGui::InvisibleButton("##camera_feed_sized", size); + if (texture != 0) { + ImGui::GetWindowDrawList()->AddImage(static_cast(texture), + top_left, + ImVec2(top_left.x + frame_size.x, top_left.y + frame_size.y), + uv0, + uv1); + } else { + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + draw_list->AddRectFilled(top_left, ImVec2(top_left.x + frame_size.x, top_left.y + frame_size.y), IM_COL32(45, 47, 50, 255)); + draw_list->AddRect(top_left, ImVec2(top_left.x + frame_size.x, top_left.y + frame_size.y), IM_COL32(95, 100, 106, 255)); + + const char *label = (loading || has_video_source()) ? "loading" : "no video"; + const ImVec2 text_size = ImGui::CalcTextSize(label); + const ImVec2 text_pos(top_left.x + (frame_size.x - text_size.x) * 0.5f, + top_left.y + (frame_size.y - text_size.y) * 0.5f); + draw_list->AddText(text_pos, IM_COL32(187, 187, 187, 255), label); + } + } + + static bool same_request(const std::optional &lhs, const RequestKey &rhs) { + return lhs.has_value() && lhs->segment == rhs.segment && lhs->decode_index == rhs.decode_index; + } + + bool has_video_source() const { + std::lock_guard lock(mutex); + return !route_index.entries.empty() && !route_index.segment_files.empty(); + } + + float preview_aspect() const { + if (frame_width > 0 && frame_height > 0) return static_cast(frame_height) / static_cast(frame_width); + return kDefaultAspect; + } + + std::optional request_for_time(double tracker_time) const { + std::lock_guard lock(mutex); + if (route_index.entries.empty()) return std::nullopt; + + auto it = std::lower_bound(route_index.entries.begin(), route_index.entries.end(), tracker_time, + [](const CameraFrameIndexEntry &entry, double tm) { + return entry.timestamp < tm; + }); + if (it == route_index.entries.end()) { + it = std::prev(route_index.entries.end()); + } else if (it != route_index.entries.begin()) { + const auto prev = std::prev(it); + if (std::abs(prev->timestamp - tracker_time) <= std::abs(it->timestamp - tracker_time)) { + it = prev; + } + } + + auto path_it = std::find_if(route_index.segment_files.begin(), route_index.segment_files.end(), + [&](const CameraSegmentFile &segment) { + return segment.segment == it->segment && !segment.path.empty(); + }); + if (path_it == route_index.segment_files.end()) return std::nullopt; + + return DecodeRequest{ + .key = RequestKey{.segment = it->segment, .decode_index = it->decode_index}, + .path = path_it->path, + }; + } + + void upload_pending_result() { + std::optional result; + { + std::lock_guard lock(mutex); + if (!pending_result.has_value()) { + return; + } + result = std::move(pending_result); + pending_result.reset(); + } + + active_request.reset(); + if (!result->success || result->rgba.empty() || result->width <= 0 || result->height <= 0) { + failed_request = result->key; + return; + } + + upload_result(*result); + } + + void upload_result(const DecodeResult &result) { + remember_cached_result(result); + + const bool new_size = texture_width != result.width || texture_height != result.height; + if (texture == 0) { + glGenTextures(1, &texture); + } + glBindTexture(GL_TEXTURE_2D, texture); + if (new_size) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, result.width, result.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, result.rgba.data()); + texture_width = result.width; + texture_height = result.height; + } else { + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, result.width, result.height, GL_RGBA, GL_UNSIGNED_BYTE, result.rgba.data()); + } + glBindTexture(GL_TEXTURE_2D, 0); + + frame_width = result.width; + frame_height = result.height; + displayed_request = result.key; + failed_request.reset(); + } + + bool try_upload_cached_result(const RequestKey &key) { + std::optional result; + { + std::lock_guard lock(mutex); + auto it = std::find_if(cached_results.begin(), cached_results.end(), [&](const DecodeResult &cached) { + return cached.key.segment == key.segment && cached.key.decode_index == key.decode_index; + }); + if (it == cached_results.end()) { + return false; + } + result = *it; + } + active_request.reset(); + upload_result(*result); + return true; + } + + bool try_upload_nearby_cached_result(const RequestKey &key) { + std::optional result; + int best_distance = std::numeric_limits::max(); + { + std::lock_guard lock(mutex); + for (const DecodeResult &cached : cached_results) { + if (cached.key.segment != key.segment) continue; + const int distance = std::abs(cached.key.decode_index - key.decode_index); + if (distance == 0 || distance > kImmediateNearbyFrameDistance || distance >= best_distance) continue; + best_distance = distance; + result = cached; + } + } + if (!result.has_value()) { + return false; + } + upload_result(*result); + return true; + } + + void remember_cached_result(const DecodeResult &result) { + std::lock_guard lock(mutex); + auto it = std::find_if(cached_results.begin(), cached_results.end(), [&](const DecodeResult &cached) { + return cached.key.segment == result.key.segment && cached.key.decode_index == result.key.decode_index; + }); + if (it != cached_results.end()) { + cached_results.erase(it); + } + cached_results.push_front(result); + while (cached_results.size() > kCachedFrames) { + cached_results.pop_back(); + } + } + + void destroy_texture() { + if (texture != 0 && g_glfw_alive.load() && glfwGetCurrentContext() != nullptr) { + glDeleteTextures(1, &texture); + } + texture = 0; + texture_width = 0; + texture_height = 0; + frame_width = 0; + frame_height = 0; + } + + static bool ensure_decode_buffer(FrameReader *reader, VisionBuf *buf, bool &allocated, int &w, int &h) { + if (!reader) return false; + if (allocated && w == reader->width && h == reader->height) return true; + if (allocated) { buf->free(); allocated = false; } + const auto [stride, y_height, _uv_height, size] = get_nv12_info(reader->width, reader->height); + buf->allocate(size); + buf->init_yuv(reader->width, reader->height, stride, stride * y_height); + w = reader->width; + h = reader->height; + allocated = true; + return true; + } + + void publish_result(const DecodeRequest &request, DecodeResult result) { + std::lock_guard lock(mutex); + if (!pending_request.has_value() || pending_request->serial != request.serial || + pending_request->generation != request.generation) { + return; + } + pending_result = std::move(result); + } + + bool has_newer_pending_request(uint64_t serial) const { + std::lock_guard lock(mutex); + return pending_request.has_value() && pending_request->serial != serial; + } + + void rebuild_preload_queue_locked(int focus_segment) { + std::vector ordered; + ordered.reserve(route_index.segment_files.size()); + for (const CameraSegmentFile &segment_file : route_index.segment_files) { + if (segment_file.path.empty()) continue; + if (segment_file.segment == focus_segment) continue; + ordered.push_back(PreloadTask{ + .segment = segment_file.segment, + .path = segment_file.path, + .generation = route_generation, + }); + } + std::sort(ordered.begin(), ordered.end(), [&](const PreloadTask &a, const PreloadTask &b) { + const int distance_a = std::abs(a.segment - focus_segment); + const int distance_b = std::abs(b.segment - focus_segment); + if (distance_a != distance_b) return distance_a < distance_b; + return a.segment < b.segment; + }); + preload_queue.assign(ordered.begin(), ordered.end()); + preload_focus_segment = focus_segment; + } + + std::shared_ptr find_loaded_reader_locked(int segment, uint64_t generation) { + if (readers_generation != generation) { + readers.clear(); + loading_segments.clear(); + readers_generation = generation; + } + auto it = readers.find(segment); + return it != readers.end() ? it->second : nullptr; + } + + std::shared_ptr ensure_reader_loaded(int segment, + const std::string &path, + uint64_t generation, + const char *reason, + std::atomic *abort_flag, + bool wait_for_inflight) { + while (!stop.load()) { + { + std::unique_lock lock(readers_mutex); + if (std::shared_ptr cached = find_loaded_reader_locked(segment, generation)) { + return cached; + } + if (loading_segments.find(segment) != loading_segments.end()) { + if (!wait_for_inflight) { + return nullptr; + } + readers_cv.wait(lock, [&]() { + return stop.load() + || readers_generation != generation + || loading_segments.find(segment) == loading_segments.end(); + }); + continue; + } + loading_segments.insert(segment); + } + + auto reader = std::make_shared(); + const auto load_begin = std::chrono::steady_clock::now(); + const bool loaded = reader->load(decoder_camera_type(camera_view), path, false, abort_flag, true); + + { + std::lock_guard lock(readers_mutex); + if (readers_generation != generation) { + readers.clear(); + loading_segments.clear(); + readers_generation = generation; + } + loading_segments.erase(segment); + if (loaded) { + readers[segment] = reader; + } + } + readers_cv.notify_all(); + + if (!loaded) { + return nullptr; + } + if (kLogCameraTimings) { + const double load_ms = std::chrono::duration(std::chrono::steady_clock::now() - load_begin).count(); + std::fprintf(stderr, "camera[%s] %s-load seg=%d %.1fms\n", + camera_view_spec(camera_view).runtime_name, reason, segment, load_ms); + } + return reader; + } + return nullptr; + } + + void preload_worker_loop() { + while (true) { + std::optional preload; + { + std::unique_lock lock(mutex); + cv.wait(lock, [&]() { return stop.load() || !preload_queue.empty(); }); + if (stop.load()) { + break; + } + preload = preload_queue.front(); + preload_queue.pop_front(); + } + + abort_preload_work.store(false); + { + std::lock_guard lock(readers_mutex); + if (find_loaded_reader_locked(preload->segment, preload->generation)) { + continue; + } + } + ensure_reader_loaded(preload->segment, preload->path, preload->generation, "preload", + &abort_preload_work, false); + } + } + + void demand_worker_loop() { + uint64_t handled_serial = 0; + VisionBuf decode_buffer; + bool buffer_allocated = false; + int buffer_width = 0; + int buffer_height = 0; + + while (true) { + std::optional request; + { + std::unique_lock lock(mutex); + cv.wait(lock, [&]() { + return stop.load() || (pending_request.has_value() && pending_request->serial != handled_serial); + }); + if (stop.load()) break; + request = *pending_request; + handled_serial = request->serial; + } + + abort_demand_work.store(false); + + DecodeResult result{.key = request->key}; + std::shared_ptr reader = ensure_reader_loaded(request->key.segment, request->path, + request->generation, "demand", + &abort_demand_work, true); + if (!reader) { + publish_result(*request, std::move(result)); + continue; + } + if (has_newer_pending_request(request->serial)) { + continue; + } + + const auto decode_begin = std::chrono::steady_clock::now(); + if (!ensure_decode_buffer(reader.get(), &decode_buffer, buffer_allocated, buffer_width, buffer_height) || + !reader->get(request->key.decode_index, &decode_buffer)) { + publish_result(*request, std::move(result)); + continue; + } + + result.width = reader->width; + result.height = reader->height; + result.rgba.resize(static_cast(result.width) * static_cast(result.height) * 4U, 0); + libyuv::NV12ToABGR(decode_buffer.y, + static_cast(decode_buffer.stride), + decode_buffer.uv, + static_cast(decode_buffer.stride), + result.rgba.data(), + result.width * 4, + result.width, + result.height); + result.success = true; + result.decode_ms = std::chrono::duration(std::chrono::steady_clock::now() - decode_begin).count(); + publish_result(*request, std::move(result)); + + for (int offset = 1; offset <= kPrefetchAhead; ++offset) { + if (stop.load() || has_newer_pending_request(request->serial)) { + break; + } + const int next_index = request->key.decode_index + offset; + if (next_index < 0 || next_index >= static_cast(reader->getFrameCount())) { + break; + } + if (!reader->get(next_index, &decode_buffer)) { + break; + } + DecodeResult prefetched{ + .key = RequestKey{.segment = request->key.segment, .decode_index = next_index}, + .success = true, + .width = reader->width, + .height = reader->height, + }; + prefetched.rgba.resize(static_cast(prefetched.width) * static_cast(prefetched.height) * 4U, 0); + libyuv::NV12ToABGR(decode_buffer.y, + static_cast(decode_buffer.stride), + decode_buffer.uv, + static_cast(decode_buffer.stride), + prefetched.rgba.data(), + prefetched.width * 4, + prefetched.width, + prefetched.height); + remember_cached_result(prefetched); + } + } + + if (buffer_allocated) { + decode_buffer.free(); + } + } + + mutable std::mutex mutex; + std::condition_variable cv; + std::thread demand_worker; + std::vector preload_workers; + std::atomic stop{false}; + std::atomic abort_demand_work{false}; + std::atomic abort_preload_work{false}; + CameraFeedIndex route_index; + CameraViewKind camera_view = CameraViewKind::Road; + std::optional pending_request; + std::optional pending_result; + std::deque preload_queue; + int preload_focus_segment = -1; + std::deque cached_results; + uint64_t latest_request_serial = 0; + uint64_t route_generation = 1; + std::optional active_request; + std::optional displayed_request; + std::optional failed_request; + std::mutex readers_mutex; + std::condition_variable readers_cv; + std::unordered_map> readers; + std::unordered_set loading_segments; + uint64_t readers_generation = 0; + GLuint texture = 0; + int texture_width = 0; + int texture_height = 0; + int frame_width = 0; + int frame_height = 0; +}; + +CameraFeedView::CameraFeedView() + : impl_(std::make_unique()) {} + +CameraFeedView::~CameraFeedView() = default; + +void CameraFeedView::setRouteData(const RouteData &route_data) { + impl_->setRouteData(route_data); +} + +void CameraFeedView::setCameraIndex(const CameraFeedIndex &camera_index, CameraViewKind view) { + impl_->setCameraIndex(camera_index, view); +} + +void CameraFeedView::update(double tracker_time) { + impl_->update(tracker_time); +} + +void CameraFeedView::draw(float width, bool loading) { + impl_->draw(width, loading); +} + +void CameraFeedView::drawSized(ImVec2 size, bool loading, bool fit_to_pane) { + impl_->drawSized(size, loading, fit_to_pane); +} diff --git a/tools/jotpluggler/session.cc b/tools/jotpluggler/session.cc new file mode 100644 index 00000000000..22dd7dd4638 --- /dev/null +++ b/tools/jotpluggler/session.cc @@ -0,0 +1,773 @@ +#include "tools/jotpluggler/internal.h" + +#include "imgui_internal.h" + +#include +#include +#include + +namespace fs = std::filesystem; + +const RouteSeries *app_find_route_series(const AppSession &session, const std::string &path) { + auto it = session.series_by_path.find(path); + return it == session.series_by_path.end() ? nullptr : it->second; +} + +void sync_camera_feeds(AppSession *session) { + for (size_t i = 0; i < kCameraViewSpecs.size(); ++i) { + if (session->pane_camera_feeds[i]) { + session->pane_camera_feeds[i]->setCameraIndex(session->route_data.*(kCameraViewSpecs[i].route_member), kCameraViewSpecs[i].view); + } + } +} + +void apply_route_data(AppSession *session, UiState *state, RouteData route_data) { + if (!route_data.route_id.empty()) { + session->route_id = route_data.route_id; + } else if (session->route_name.empty() && session->data_mode == SessionDataMode::Route) { + session->route_id = {}; + } + session->route_data = std::move(route_data); + rebuild_route_index(session); + rebuild_browser_nodes(session, state); + state->browser_nodes_dirty = false; + refresh_all_custom_curves(session, state); + sync_camera_feeds(session); + state->has_shared_range = false; + state->has_tracker_time = false; + reset_shared_range(state, *session); +} + +bool restore_undo_layout(AppSession *session, UiState *state, const SketchLayout &layout, const char *status_text) { + session->layout = layout; + cancel_rename_tab(state); + state->custom_series.request_select = false; + state->active_tab_index = std::clamp(layout.current_tab_index, 0, std::max(0, static_cast(layout.tabs.size()) - 1)); + state->requested_tab_index = state->active_tab_index; + sync_ui_state(state, session->layout); + mark_all_docks_dirty(state); + const bool autosave_ok = autosave_layout(session, state); + if (autosave_ok) { + state->status_text = status_text; + } + return autosave_ok; +} + +bool apply_undo(AppSession *session, UiState *state) { + if (!state->undo.can_undo()) { + return false; + } + return restore_undo_layout(session, state, state->undo.undo(), "Undo"); +} + +bool apply_redo(AppSession *session, UiState *state) { + if (!state->undo.can_redo()) { + return false; + } + return restore_undo_layout(session, state, state->undo.redo(), "Redo"); +} + +std::optional> tab_default_x_range(const WorkspaceTab &tab) { + bool found = false; + double min_value = 0.0; + double max_value = 1.0; + for (const Pane &pane : tab.panes) { + if (!pane.range.valid || pane.range.right <= pane.range.left) continue; + if (!found) { + min_value = pane.range.left; + max_value = pane.range.right; + found = true; + } else { + min_value = std::min(min_value, pane.range.left); + max_value = std::max(max_value, pane.range.right); + } + } + if (!found) return std::nullopt; + return std::make_pair(min_value, max_value); +} + +bool infer_stream_follow_state(const UiState &state, const AppSession &session) { + if (session.data_mode != SessionDataMode::Stream || !state.has_shared_range || !session.route_data.has_time_range) { + return false; + } + const double target_span = std::max(MIN_HORIZONTAL_ZOOM_SECONDS, session.stream_buffer_seconds); + const double current_span = std::max(0.0, state.x_view_max - state.x_view_min); + const double edge_epsilon = std::max(0.05, target_span * 0.02); + return std::abs(state.x_view_max - state.route_x_max) <= edge_epsilon + && std::abs(current_span - target_span) <= edge_epsilon; +} + +void ensure_shared_range(UiState *state, const AppSession &session) { + if (session.route_data.has_time_range) { + state->route_x_min = session.route_data.x_min; + state->route_x_max = session.route_data.x_max; + } else { + state->route_x_min = 0.0; + state->route_x_max = 1.0; + } + + if (state->has_shared_range) { + return; + } + + if (session.data_mode == SessionDataMode::Stream) { + const double target_span = std::max(MIN_HORIZONTAL_ZOOM_SECONDS, session.stream_buffer_seconds); + if (session.route_data.has_time_range) { + state->x_view_max = state->route_x_max; + state->x_view_min = state->x_view_max - target_span; + } else { + state->x_view_min = 0.0; + state->x_view_max = target_span; + } + if (state->x_view_max <= state->x_view_min) { + state->x_view_max = state->x_view_min + 1.0; + } + state->has_shared_range = true; + if (!state->has_tracker_time || state->tracker_time < state->route_x_min || state->tracker_time > state->route_x_max) { + state->tracker_time = state->route_x_max; + state->has_tracker_time = session.route_data.has_time_range; + } + return; + } + + if (const WorkspaceTab *tab = app_active_tab(session.layout, *state); tab != nullptr) { + if (std::optional> tab_range = tab_default_x_range(*tab); tab_range.has_value()) { + state->x_view_min = tab_range->first; + state->x_view_max = tab_range->second; + state->has_shared_range = true; + if (!state->has_tracker_time || state->tracker_time < state->route_x_min || state->tracker_time > state->route_x_max) { + state->tracker_time = state->route_x_min; + state->has_tracker_time = true; + } + return; + } + } + + state->x_view_min = state->route_x_min; + state->x_view_max = state->route_x_max; + if (state->x_view_max <= state->x_view_min) { + state->x_view_max = state->x_view_min + 1.0; + } + state->has_shared_range = true; + if (!state->has_tracker_time || state->tracker_time < state->route_x_min || state->tracker_time > state->route_x_max) { + state->tracker_time = state->route_x_min; + state->has_tracker_time = true; + } +} + +void clamp_shared_range(UiState *state, const AppSession &session) { + if (!state->has_shared_range) { + return; + } + const double min_span = MIN_HORIZONTAL_ZOOM_SECONDS; + double span = state->x_view_max - state->x_view_min; + if (span < min_span) { + const double center = 0.5 * (state->x_view_min + state->x_view_max); + span = min_span; + state->x_view_min = center - span * 0.5; + state->x_view_max = center + span * 0.5; + } + if (session.data_mode == SessionDataMode::Stream) { + if (session.route_data.has_time_range && state->x_view_max > state->route_x_max) { + state->x_view_min -= state->x_view_max - state->route_x_max; + state->x_view_max = state->route_x_max; + } + if (state->x_view_max <= state->x_view_min) { + state->x_view_max = state->x_view_min + min_span; + } + if (state->has_tracker_time && session.route_data.has_time_range) { + state->tracker_time = std::clamp(state->tracker_time, state->route_x_min, state->route_x_max); + } + if (session.route_data.has_time_range) { + state->follow_latest = infer_stream_follow_state(*state, session); + } + return; + } + if (state->route_x_max > state->route_x_min) { + if (state->x_view_min < state->route_x_min) { + state->x_view_max += state->route_x_min - state->x_view_min; + state->x_view_min = state->route_x_min; + } + if (state->x_view_max > state->route_x_max) { + state->x_view_min -= state->x_view_max - state->route_x_max; + state->x_view_max = state->route_x_max; + } + if (state->x_view_min < state->route_x_min) { + state->x_view_min = state->route_x_min; + } + if (state->x_view_max <= state->x_view_min) { + state->x_view_max = std::min(state->route_x_max, state->x_view_min + min_span); + } + } + if (state->has_tracker_time) { + state->tracker_time = std::clamp(state->tracker_time, state->route_x_min, state->route_x_max); + } +} + +void reset_shared_range(UiState *state, const AppSession &session) { + state->has_shared_range = false; + ensure_shared_range(state, session); + clamp_shared_range(state, session); +} + +void update_follow_range(UiState *state, const AppSession &session) { + if (!state->follow_latest || !state->has_shared_range) { + return; + } + const double span = session.data_mode == SessionDataMode::Stream + ? std::max(MIN_HORIZONTAL_ZOOM_SECONDS, session.stream_buffer_seconds) + : std::max(MIN_HORIZONTAL_ZOOM_SECONDS, state->x_view_max - state->x_view_min); + const double route_span = state->route_x_max - state->route_x_min; + if (route_span <= 0.0) { + return; + } + state->x_view_max = state->route_x_max; + state->x_view_min = state->x_view_max - span; + clamp_shared_range(state, session); +} + +void advance_playback(UiState *state, const AppSession &session) { + if (!state->playback_playing || !state->has_shared_range || state->route_x_max <= state->route_x_min) { + return; + } + + const double delta = std::max(0.0, static_cast(ImGui::GetIO().DeltaTime)) * state->playback_rate; + const double view_span = std::max(MIN_HORIZONTAL_ZOOM_SECONDS, state->x_view_max - state->x_view_min); + const double loop_min = state->playback_loop + ? std::clamp(state->x_view_min, state->route_x_min, state->route_x_max) + : state->route_x_min; + const double loop_max = state->playback_loop + ? std::clamp(state->x_view_max, state->route_x_min, state->route_x_max) + : state->route_x_max; + + state->tracker_time += delta; + if (state->tracker_time >= loop_max) { + if (state->playback_loop) { + state->tracker_time = loop_min; + } else { + state->tracker_time = state->route_x_max; + state->playback_playing = false; + } + } + + if (!state->playback_loop) { + constexpr double kScrollStartFraction = 0.70; + const double scroll_anchor = state->x_view_min + view_span * kScrollStartFraction; + if (state->tracker_time > scroll_anchor && state->x_view_max < state->route_x_max) { + state->x_view_min = state->tracker_time - view_span * kScrollStartFraction; + state->x_view_max = state->x_view_min + view_span; + clamp_shared_range(state, session); + } else if (state->tracker_time < state->x_view_min || state->tracker_time > state->x_view_max) { + state->x_view_min = state->tracker_time - view_span * 0.5; + state->x_view_max = state->x_view_min + view_span; + clamp_shared_range(state, session); + } + } +} + +void step_tracker(UiState *state, double direction) { + if (!state->has_shared_range) { + return; + } + state->tracker_time += direction * std::max(0.001, state->playback_step); + state->tracker_time = std::clamp(state->tracker_time, state->route_x_min, state->route_x_max); +} + +const char *log_selector_name(LogSelector selector) { + static constexpr const char *kLabels[] = {"a", "r", "q"}; + const size_t index = static_cast(selector); + return index < std::size(kLabels) ? kLabels[index] : kLabels[0]; +} + +const char *log_selector_description(LogSelector selector) { + static constexpr const char *kLabels[] = { + "any of rlog or qlog", + "rlog only", + "qlog only", + }; + const size_t index = static_cast(selector); + return index < std::size(kLabels) ? kLabels[index] : kLabels[0]; +} + +std::string shorten_route_part(std::string_view text, size_t keep) { + if (text.size() <= keep) { + return std::string(text); + } + return std::string(text.substr(0, keep)); +} + +bool parse_slice_spec(std::string_view text, int *begin, int *end) { + const auto parse_nonnegative = [](std::string_view value, int *out) { + if (value.empty()) return false; + char *end_ptr = nullptr; + const long parsed = std::strtol(std::string(value).c_str(), &end_ptr, 10); + if (end_ptr == nullptr || *end_ptr != '\0' || parsed < 0) { + return false; + } + *out = static_cast(parsed); + return true; + }; + const std::string trimmed = util::strip(std::string(text)); + if (trimmed.empty()) { + return false; + } + const size_t colon = trimmed.find(':'); + int parsed_begin = 0; + if (!parse_nonnegative(trimmed.substr(0, colon), &parsed_begin)) { + return false; + } + int parsed_end = parsed_begin; + if (colon != std::string::npos) { + const std::string end_text = trimmed.substr(colon + 1); + if (end_text.empty()) { + parsed_end = -1; + } else if (!parse_nonnegative(end_text, &parsed_end) || parsed_end < parsed_begin) { + return false; + } + } + *begin = parsed_begin; + *end = parsed_end; + return true; +} + +std::string format_duration_short(double seconds) { + const double clamped = std::max(0.0, seconds); + const int total_ms = static_cast(std::round(clamped * 1000.0)); + const int minutes = total_ms / 60000; + const int rem_ms = total_ms % 60000; + const int secs = rem_ms / 1000; + const int millis = rem_ms % 1000; + return util::string_format("%d:%02d.%03d", minutes, secs, millis); +} + +bool apply_route_identifier(AppSession *session, UiState *state, const RouteIdentifier &route_id, const char *status_text) { + if (route_id.empty()) { + return false; + } + if (!reload_session(session, state, route_id.full_spec(), session->data_dir)) { + return false; + } + state->status_text = status_text; + return true; +} + +bool apply_route_slice_change(AppSession *session, UiState *state, std::string_view slice_text) { + int begin = 0; + int end = 0; + if (!parse_slice_spec(slice_text, &begin, &end)) { + state->error_text = "Slice must be N or N:M."; + state->open_error_popup = true; + return false; + } + RouteIdentifier next = session->route_id; + next.slice_begin = begin; + next.slice_end = end; + next.slice_explicit = true; + return apply_route_identifier(session, state, next, "Updated route slice"); +} + +bool apply_route_selector_change(AppSession *session, UiState *state, LogSelector selector) { + RouteIdentifier next = session->route_id; + next.selector = selector; + next.selector_explicit = true; + return apply_route_identifier(session, state, next, "Updated log selector"); +} + +ImU32 route_chip_part_color(int part_index, bool explicit_part) { + constexpr std::array, 4> BASE = {{ + {70, 96, 126}, // dongle + {100, 86, 148}, // log id + {72, 112, 86}, // slice + {156, 104, 38}, // selector + }}; + const std::array &base = BASE[static_cast(std::clamp(part_index, 0, 3))]; + if (explicit_part) { + return ImGui::GetColorU32(color_rgb(base[0], base[1], base[2])); + } + const int gray = 144; + return ImGui::GetColorU32(color_rgb((base[0] + gray) / 2, (base[1] + gray) / 2, (base[2] + gray) / 2)); +} + +bool draw_route_chip_text_button(const char *id, + std::string_view text, + ImVec2 pos, + ImU32 color, + ImDrawList *draw_list, + const char *tooltip = nullptr) { + const ImVec2 size = ImGui::CalcTextSize(text.data(), text.data() + text.size()); + ImGui::SetCursorScreenPos(pos); + ImGui::InvisibleButton(id, size); + const bool hovered = ImGui::IsItemHovered(); + if (hovered) { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + draw_list->AddRectFilled(ImVec2(pos.x - 5.0f, pos.y - 1.0f), + ImVec2(pos.x + size.x + 5.0f, pos.y + size.y + 2.0f), + ImGui::GetColorU32(color_rgb(225, 231, 239, 0.95f)), 0.0f); + } + draw_list->AddText(pos, color, text.data(), text.data() + text.size()); + if (tooltip != nullptr && ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(tooltip); + ImGui::EndTooltip(); + } + return ImGui::IsItemClicked(ImGuiMouseButton_Left); +} + +void draw_route_copy_feedback(UiState *state, ImDrawList *draw_list, ImVec2 chip_max) { + if (state->route_copy_feedback_text.empty()) { + return; + } + const double now = ImGui::GetTime(); + if (now >= state->route_copy_feedback_until) { + state->route_copy_feedback_text.clear(); + state->route_copy_feedback_until = 0.0; + return; + } + + const float alpha = static_cast(std::clamp((state->route_copy_feedback_until - now) / 1.1, 0.0, 1.0)); + const ImVec2 text_size = ImGui::CalcTextSize(state->route_copy_feedback_text.c_str()); + const ImVec2 pad(9.0f, 5.0f); + const ImVec2 bubble_min(chip_max.x - text_size.x - pad.x * 2.0f, chip_max.y + 7.0f); + const ImVec2 bubble_max(chip_max.x, bubble_min.y + text_size.y + pad.y * 2.0f); + draw_list->AddRectFilled(bubble_min, bubble_max, + ImGui::GetColorU32(color_rgb(46, 125, 80, 0.96f * alpha)), 7.0f); + draw_list->AddRect(bubble_min, bubble_max, + ImGui::GetColorU32(color_rgb(35, 96, 61, 0.9f * alpha)), 7.0f, 0, 1.0f); + draw_list->AddText(ImVec2(std::floor(bubble_min.x + pad.x), std::floor(bubble_min.y + pad.y)), + ImGui::GetColorU32(color_rgb(247, 251, 248, alpha)), + state->route_copy_feedback_text.c_str()); +} + +void draw_route_info_popup(AppSession *session, UiState *state, ImVec2 anchor) { + if (session->route_id.empty()) { + return; + } + ImGui::SetNextWindowPos(anchor, ImGuiCond_Appearing); + ImGui::SetNextWindowSizeConstraints(ImVec2(300.0f, 0.0f), ImVec2(420.0f, FLT_MAX)); + if (!ImGui::BeginPopup("##route_info_popup", + ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings)) { + return; + } + + ImGui::TextUnformatted("Route Info"); + ImGui::Separator(); + app_push_mono_font(); + ImGui::TextUnformatted(session->route_id.canonical().c_str()); + app_pop_mono_font(); + + const char *copy_icon = icon::CLIPBOARD; + const char *link_icon = icon::BOX_ARROW_UP_RIGHT; + const std::string useradmin_label = std::string("Useradmin ") + link_icon; + const std::string connect_label = std::string("comma connect ") + link_icon; + if (ImGui::Button(copy_icon, ImVec2(34.0f, 26.0f))) { + ImGui::SetClipboardText(session->route_id.canonical().c_str()); + state->status_text = "Copied route to clipboard"; + state->route_copy_feedback_text = "Copied"; + state->route_copy_feedback_until = ImGui::GetTime() + 1.1; + } + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted("Copy route"); + ImGui::EndTooltip(); + } + ImGui::SameLine(); + if (ImGui::Button(useradmin_label.c_str(), ImVec2(132.0f, 26.0f))) { + open_external_url(route_useradmin_url(session->route_id)); + state->status_text = "Opened useradmin"; + } + ImGui::SameLine(); + if (ImGui::Button(connect_label.c_str(), ImVec2(156.0f, 26.0f))) { + open_external_url(route_connect_url(session->route_id)); + state->status_text = "Opened comma connect"; + } + + ImGui::Spacing(); + const int loaded_begin = session->route_id.available_begin; + const int loaded_end = session->route_id.available_end; + const int loaded_count = loaded_end >= loaded_begin ? (loaded_end - loaded_begin + 1) : 0; + ImGui::Text("Duration %s", format_duration_short(session->route_data.x_max - session->route_data.x_min).c_str()); + ImGui::Text("Segments %s (%d)", session->route_id.display_slice().c_str(), loaded_count); + ImGui::Text("Selector %s", log_selector_description(session->route_id.selector)); + if (!session->route_data.car_fingerprint.empty()) { + ImGui::TextWrapped("Car %s", session->route_data.car_fingerprint.c_str()); + } + if (!session->route_data.dbc_name.empty()) { + ImGui::TextWrapped("DBC %s", session->route_data.dbc_name.c_str()); + } + + ImGui::EndPopup(); +} + +void draw_route_id_chip(AppSession *session, UiState *state) { + if (session->data_mode != SessionDataMode::Route || session->route_id.empty()) { + return; + } + + ImGuiWindow *window = ImGui::GetCurrentWindow(); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + const RouteIdentifier &route_id = session->route_id; + app_push_bold_font(); + const std::string dongle_text = shorten_route_part(route_id.dongle_id, 8); + const std::string log_text = shorten_route_part(route_id.log_id, 16); + const std::string slice_text = route_id.display_slice(); + const std::string selector_text(1, route_id.selector_char()); + const std::string sep_text = " / "; + + const ImVec2 dongle_size = ImGui::CalcTextSize(dongle_text.c_str()); + const ImVec2 log_size = ImGui::CalcTextSize(log_text.c_str()); + const ImVec2 slice_size = state->editing_route_slice + ? ImVec2(68.0f, ImGui::GetFrameHeight()) + : ImGui::CalcTextSize(slice_text.c_str()); + const ImVec2 selector_size = ImGui::CalcTextSize(selector_text.c_str()); + const ImVec2 sep_size = ImGui::CalcTextSize(sep_text.c_str()); + constexpr float chip_pad_x = 12.0f; + constexpr float info_size = 18.0f; + const float chip_h = 28.0f; + const float chip_w = chip_pad_x * 2.0f + dongle_size.x + sep_size.x + log_size.x + sep_size.x + + slice_size.x + sep_size.x + selector_size.x + 10.0f + info_size; + const float menu_right = window->Pos.x + window->Size.x - 8.0f; + const float cursor_x = ImGui::GetCursorScreenPos().x + 4.0f; + const float chip_x = std::clamp(cursor_x, window->Pos.x + 48.0f, std::max(window->Pos.x + 48.0f, menu_right - chip_w)); + const float chip_y = std::floor(window->Pos.y + std::max(0.0f, (window->Size.y - chip_h) * 0.5f)); + const ImVec2 chip_min(chip_x, chip_y); + const ImVec2 chip_max(chip_x + chip_w, chip_y + chip_h); + const float text_y = std::floor(chip_y + std::max(0.0f, (chip_h - ImGui::GetTextLineHeight()) * 0.5f)); + const ImU32 chip_bg = ImGui::GetColorU32(color_rgb(247, 249, 252)); + const ImU32 chip_border = ImGui::GetColorU32(color_rgb(184, 191, 200)); + const ImU32 sep = ImGui::GetColorU32(color_rgb(162, 170, 178)); + draw_list->AddRectFilled(chip_min, chip_max, chip_bg, 0.0f); + draw_list->AddRect(chip_min, chip_max, chip_border, 0.0f, 0, 1.0f); + + float x = chip_x + chip_pad_x; + const bool dongle_click = draw_route_chip_text_button( + "##route_dongle", dongle_text, ImVec2(x, text_y), route_chip_part_color(0, true), draw_list, + "Device identifier"); + x += dongle_size.x; + draw_list->AddText(ImVec2(x, text_y), sep, sep_text.c_str()); + x += sep_size.x; + const bool log_click = draw_route_chip_text_button( + "##route_log", log_text, ImVec2(x, text_y), route_chip_part_color(1, true), draw_list, + "Route identifier"); + x += log_size.x; + draw_list->AddText(ImVec2(x, text_y), sep, sep_text.c_str()); + x += sep_size.x; + + if (state->editing_route_slice) { + ImGui::SetCursorScreenPos(ImVec2(x - 4.0f, chip_y + 1.0f)); + ImGui::SetNextItemWidth(76.0f); + if (state->focus_route_slice_input) { + ImGui::SetKeyboardFocusHere(); + state->focus_route_slice_input = false; + } + const bool applied = input_text_string("##route_slice_edit", &state->route_slice_buffer, + ImGuiInputTextFlags_EnterReturnsTrue); + const bool deactivated = ImGui::IsItemDeactivated(); + const bool clicked_elsewhere = ImGui::IsMouseClicked(ImGuiMouseButton_Left) + && !ImGui::IsItemHovered() + && !ImGui::IsItemActive(); + if (applied) { + if (apply_route_slice_change(session, state, state->route_slice_buffer)) { + state->editing_route_slice = false; + } + } else if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + state->editing_route_slice = false; + } else if (deactivated || clicked_elsewhere) { + const std::string trimmed = util::strip(state->route_slice_buffer); + if (trimmed != route_id.display_slice()) { + int begin = 0; + int end = 0; + if (parse_slice_spec(trimmed, &begin, &end)) { + apply_route_slice_change(session, state, trimmed); + } else { + state->status_text = "Canceled route slice edit"; + } + } + state->editing_route_slice = false; + } + x += slice_size.x; + } else { + const bool slice_click = draw_route_chip_text_button( + "##route_slice", slice_text, ImVec2(x, text_y), + route_chip_part_color(2, route_id.slice_explicit), draw_list, + "Segment range"); + if (slice_click) { + state->editing_route_slice = true; + state->focus_route_slice_input = true; + state->route_slice_buffer = route_id.display_slice(); + } + x += slice_size.x; + } + + draw_list->AddText(ImVec2(x, text_y), sep, sep_text.c_str()); + x += sep_size.x; + const bool selector_click = draw_route_chip_text_button( + "##route_selector", selector_text, ImVec2(x, text_y), + route_chip_part_color(3, route_id.selector_explicit), draw_list, + "Log selector"); + if (selector_click) { + ImGui::OpenPopup("##route_selector_popup"); + } + x += selector_size.x + 10.0f; + + const ImVec2 info_center(x + info_size * 0.5f, chip_y + chip_h * 0.5f); + ImGui::SetCursorScreenPos(ImVec2(x, chip_y + (chip_h - info_size) * 0.5f)); + ImGui::InvisibleButton("##route_info_button", ImVec2(info_size, info_size)); + const bool info_hovered = ImGui::IsItemHovered(); + if (info_hovered) { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + draw_list->AddCircleFilled(info_center, info_size * 0.5f, + ImGui::GetColorU32(info_hovered ? color_rgb(220, 229, 240) : color_rgb(239, 243, 248))); + draw_list->AddCircle(info_center, info_size * 0.5f, chip_border, 20, 1.0f); + const char *info_text = icon::INFO_CIRCLE; + const ImVec2 info_text_size = ImGui::CalcTextSize(info_text); + draw_list->AddText(ImVec2(std::floor(info_center.x - info_text_size.x * 0.5f), + std::floor(info_center.y - info_text_size.y * 0.5f)), + route_chip_part_color(0, true), info_text); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted("Route details"); + ImGui::EndTooltip(); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + ImGui::OpenPopup("##route_info_popup"); + } + + app_pop_bold_font(); + + if (dongle_click || log_click) { + ImGui::SetClipboardText(route_id.canonical().c_str()); + state->status_text = "Copied route to clipboard"; + state->route_copy_feedback_text = "Copied"; + state->route_copy_feedback_until = ImGui::GetTime() + 1.1; + } + + ImGui::SetNextWindowPos(ImVec2(chip_max.x - 60.0f, chip_max.y + 4.0f), ImGuiCond_Appearing); + if (ImGui::BeginPopup("##route_selector_popup")) { + for (LogSelector selector : {LogSelector::Auto, LogSelector::RLog, LogSelector::QLog}) { + const bool selected = route_id.selector == selector; + const std::string label = std::string(log_selector_name(selector)) + " " + log_selector_description(selector); + if (ImGui::Selectable(label.c_str(), selected) && !selected) { + apply_route_selector_change(session, state, selector); + } + if (selected) { + ImGui::SetItemDefaultFocus(); + } + } + ImGui::EndPopup(); + } + + draw_route_copy_feedback(state, draw_list, chip_max); + draw_route_info_popup(session, state, ImVec2(std::max(window->Pos.x + 16.0f, chip_max.x - 360.0f), chip_max.y + 6.0f)); +} + +std::string format_cache_bytes(uint64_t bytes) { + if (bytes >= (1ULL << 30)) { + return util::string_format("%.1f GiB", static_cast(bytes) / static_cast(1ULL << 30)); + } else if (bytes >= (1ULL << 20)) { + return util::string_format("%.1f MiB", static_cast(bytes) / static_cast(1ULL << 20)); + } else if (bytes >= (1ULL << 10)) { + return util::string_format("%.1f KiB", static_cast(bytes) / static_cast(1ULL << 10)); + } + return util::string_format("%llu B", static_cast(bytes)); +} + +MapCacheStats directory_cache_stats(const fs::path &root) { + MapCacheStats stats; + std::error_code ec; + if (!fs::exists(root, ec)) { + return stats; + } + fs::recursive_directory_iterator it(root, fs::directory_options::skip_permission_denied, ec); + for (const fs::directory_entry &entry : it) { + if (ec) { + ec.clear(); + continue; + } + const fs::file_status status = entry.symlink_status(ec); + if (ec || !fs::is_regular_file(status)) { + ec.clear(); + continue; + } + const uintmax_t size = entry.file_size(ec); + if (!ec) { + stats.bytes += static_cast(size); + ++stats.files; + } else { + ec.clear(); + } + } + return stats; +} + +float draw_main_menu_bar(AppSession *session, UiState *state) { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(7.0f, 5.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(9.0f, 6.0f)); + float height = ImGui::GetFrameHeight(); + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Undo", "Ctrl+Z", false, state->undo.can_undo())) { + apply_undo(session, state); + } + if (ImGui::MenuItem("Redo", "Ctrl+Shift+Z", false, state->undo.can_redo())) { + apply_redo(session, state); + } + ImGui::Separator(); + if (ImGui::MenuItem("Open Route...")) { + state->open_open_route = true; + } + if (ImGui::MenuItem("Stream...")) { + state->open_stream = true; + } + if (ImGui::MenuItem("Find Signal...", "Ctrl+F")) { + state->open_find_signal = true; + } + ImGui::Separator(); + if (ImGui::MenuItem("New Layout")) { + start_new_layout(session, state); + } + if (ImGui::MenuItem("Load Layout...")) { + state->open_load_layout = true; + } + if (ImGui::MenuItem("Save Layout")) { + state->request_save_layout = true; + } + if (ImGui::MenuItem("Save Layout As...")) { + state->open_save_layout = true; + } + if (ImGui::MenuItem("Reset Layout")) { + state->request_reset_layout = true; + } + ImGui::Separator(); + if (ImGui::MenuItem("Show DEPRECATED Fields", nullptr, state->show_deprecated_fields)) { + state->show_deprecated_fields = !state->show_deprecated_fields; + rebuild_browser_nodes(session, state); + } + if (ImGui::MenuItem("Show FPS", nullptr, state->show_fps_overlay)) { + state->show_fps_overlay = !state->show_fps_overlay; + } + if (ImGui::MenuItem("Preferences...")) { + state->open_preferences = true; + } + ImGui::Separator(); + if (ImGui::MenuItem("Reset Plot View")) { + reset_shared_range(state, *session); + state->follow_latest = session->data_mode == SessionDataMode::Stream; + clamp_shared_range(state, *session); + state->suppress_range_side_effects = true; + state->status_text = "Plot view reset"; + } + ImGui::Separator(); + if (ImGui::MenuItem("Close")) { + state->request_close = true; + } + ImGui::EndMenu(); + } + ImGui::SameLine(0.0f, 8.0f); + draw_route_id_chip(session, state); + height = ImGui::GetWindowSize().y; + ImGui::EndMainMenuBar(); + } + ImGui::PopStyleVar(2); + return height; +} diff --git a/tools/jotpluggler/sidebar.cc b/tools/jotpluggler/sidebar.cc new file mode 100644 index 00000000000..c120b47908e --- /dev/null +++ b/tools/jotpluggler/sidebar.cc @@ -0,0 +1,215 @@ +#include "tools/jotpluggler/internal.h" + +std::string dbc_combo_label(const AppSession &session) { + if (!session.dbc_override.empty()) return session.dbc_override; + if (!session.route_data.dbc_name.empty()) return "Auto: " + session.route_data.dbc_name; + return "Auto"; +} + +float timeline_time_to_x(double time_value, double route_min, double route_max, float x_min, float x_max) { + const double span = route_max - route_min; + if (span <= 0.0) { + return x_min; + } + const double ratio = (time_value - route_min) / span; + return x_min + static_cast(ratio * static_cast(x_max - x_min)); +} + +double timeline_x_to_time(float x, double route_min, double route_max, float x_min, float x_max) { + const float width = std::max(1.0f, x_max - x_min); + const float clamped_x = std::clamp(x, x_min, x_max); + const double ratio = static_cast((clamped_x - x_min) / width); + return route_min + ratio * (route_max - route_min); +} + +void reset_timeline_view(UiState *state, const AppSession &session) { + state->follow_latest = session.data_mode == SessionDataMode::Stream; + reset_shared_range(state, session); +} + +void draw_timeline_bar_contents(const AppSession &session, UiState *state, float width) { + if (!session.route_data.has_time_range) { + ImGui::Dummy(ImVec2(width, TIMELINE_BAR_HEIGHT)); + return; + } + + const ImVec2 cursor = ImGui::GetCursorScreenPos(); + const ImVec2 size(width, TIMELINE_BAR_HEIGHT); + const ImVec2 bar_min(cursor.x + 1.0f, cursor.y + 1.0f); + const ImVec2 bar_max(cursor.x + size.x - 1.0f, cursor.y + size.y - 1.0f); + const double route_min = state->route_x_min; + const double route_max = state->route_x_max; + const float vp_left = timeline_time_to_x(std::clamp(state->x_view_min, route_min, route_max), + route_min, route_max, bar_min.x, bar_max.x); + const float vp_right = timeline_time_to_x(std::clamp(state->x_view_max, route_min, route_max), + route_min, route_max, bar_min.x, bar_max.x); + + ImGui::InvisibleButton("##timeline_button", size); + const bool hovered = ImGui::IsItemHovered(); + const bool active = ImGui::IsItemActive(); + const bool double_clicked = hovered && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left); + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + + draw_list->AddRectFilled(bar_min, bar_max, timeline_entry_color(TimelineEntry::Type::None, 0.2f)); + if (session.route_data.timeline.empty()) { + draw_list->AddRectFilled(ImVec2(vp_left, bar_min.y), ImVec2(vp_right, bar_max.y), + timeline_entry_color(TimelineEntry::Type::None, 1.0f)); + } else { + for (const TimelineEntry &entry : session.route_data.timeline) { + float x0 = timeline_time_to_x(entry.start_time, route_min, route_max, bar_min.x, bar_max.x); + float x1 = timeline_time_to_x(entry.end_time, route_min, route_max, bar_min.x, bar_max.x); + x1 = std::max(x1, x0 + 1.0f); + draw_list->AddRectFilled(ImVec2(x0, bar_min.y), ImVec2(x1, bar_max.y), + timeline_entry_color(entry.type, 0.25f)); + } + for (const TimelineEntry &entry : session.route_data.timeline) { + float x0 = std::max(timeline_time_to_x(entry.start_time, route_min, route_max, bar_min.x, bar_max.x), vp_left); + float x1 = std::min(std::max(timeline_time_to_x(entry.end_time, route_min, route_max, bar_min.x, bar_max.x), x0 + 1.0f), vp_right); + if (x1 <= x0) { + continue; + } + draw_list->AddRectFilled(ImVec2(x0, bar_min.y), ImVec2(x1, bar_max.y), + timeline_entry_color(entry.type, 1.0f)); + } + } + + draw_list->AddLine(ImVec2(vp_left, bar_min.y), ImVec2(vp_left, bar_max.y), IM_COL32(60, 70, 80, 200), 1.0f); + draw_list->AddLine(ImVec2(vp_right, bar_min.y), ImVec2(vp_right, bar_max.y), IM_COL32(60, 70, 80, 200), 1.0f); + if (state->has_tracker_time) { + const float cx = timeline_time_to_x(std::clamp(state->tracker_time, route_min, route_max), + route_min, route_max, bar_min.x, bar_max.x); + draw_list->AddLine(ImVec2(cx, bar_min.y), ImVec2(cx, bar_max.y), IM_COL32(220, 60, 50, 255), 1.5f); + } + draw_list->AddRect(bar_min, bar_max, IM_COL32(170, 178, 186, 255), 0.0f, 0, 1.0f); + + const float edge_grab = 4.0f; + const float mouse_x = ImGui::GetIO().MousePos.x; + const double mouse_time = timeline_x_to_time(mouse_x, route_min, route_max, bar_min.x, bar_max.x); + if (double_clicked) { + reset_timeline_view(state, session); + } else if (hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { + state->timeline_drag_anchor_time = mouse_time; + state->timeline_drag_anchor_x_min = state->x_view_min; + state->timeline_drag_anchor_x_max = state->x_view_max; + if (std::abs(mouse_x - vp_left) <= edge_grab) { + state->timeline_drag_mode = TimelineDragMode::ResizeLeft; + } else if (std::abs(mouse_x - vp_right) <= edge_grab) { + state->timeline_drag_mode = TimelineDragMode::ResizeRight; + } else if (mouse_x >= vp_left && mouse_x <= vp_right) { + state->timeline_drag_mode = TimelineDragMode::PanViewport; + } else { + state->timeline_drag_mode = TimelineDragMode::ScrubCursor; + state->tracker_time = std::clamp(mouse_time, route_min, route_max); + state->has_tracker_time = true; + } + } + + if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + state->timeline_drag_mode = TimelineDragMode::None; + } else if (active || state->timeline_drag_mode != TimelineDragMode::None) { + switch (state->timeline_drag_mode) { + case TimelineDragMode::ScrubCursor: + state->tracker_time = std::clamp(mouse_time, route_min, route_max); + state->has_tracker_time = true; + break; + case TimelineDragMode::PanViewport: { + const double delta = mouse_time - state->timeline_drag_anchor_time; + state->x_view_min = state->timeline_drag_anchor_x_min + delta; + state->x_view_max = state->timeline_drag_anchor_x_max + delta; + clamp_shared_range(state, session); + break; + } + case TimelineDragMode::ResizeLeft: + state->x_view_min = std::min(mouse_time, state->x_view_max - MIN_HORIZONTAL_ZOOM_SECONDS); + clamp_shared_range(state, session); + break; + case TimelineDragMode::ResizeRight: + state->x_view_max = std::max(mouse_time, state->x_view_min + MIN_HORIZONTAL_ZOOM_SECONDS); + clamp_shared_range(state, session); + break; + case TimelineDragMode::None: + break; + } + } + + if (hovered) { + if (std::abs(mouse_x - vp_left) <= edge_grab || std::abs(mouse_x - vp_right) <= edge_grab) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } else if (mouse_x >= vp_left && mouse_x <= vp_right) { + ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); + } + ImGui::BeginTooltip(); + ImGui::Text("t=%.1fs — %s", mouse_time, timeline_entry_label(timeline_type_at_time(session.route_data.timeline, mouse_time))); + ImGui::EndTooltip(); + } +} + +void draw_status_bar(const AppSession &session, const UiMetrics &ui, UiState *state) { + ImGui::SetNextWindowPos(ImVec2(ui.content_x, ui.status_bar_y)); + ImGui::SetNextWindowSize(ImVec2(ui.content_w, STATUS_BAR_HEIGHT)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + ImGui::PushStyleColor(ImGuiCol_WindowBg, color_rgb(247, 248, 250)); + ImGui::PushStyleColor(ImGuiCol_Border, color_rgb(188, 193, 199)); + const ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings; + if (ImGui::Begin("##status_bar", nullptr, flags)) { + draw_timeline_bar_contents(session, state, ui.content_w); + const float row_y = TIMELINE_BAR_HEIGHT + 8.0f; + ImGui::SetCursorPos(ImVec2(8.0f, row_y)); + ImGui::BeginDisabled(!session.route_data.has_time_range); + ImGui::Checkbox("Loop", &state->playback_loop); + ImGui::SameLine(0.0f, 10.0f); + if (ImGui::Button(state->playback_playing ? "Pause" : "Play", ImVec2(56.0f, 0.0f))) { + state->playback_playing = !state->playback_playing; + } + ImGui::SameLine(0.0f, 10.0f); + if (ImGui::Button("Reset View", ImVec2(86.0f, 0.0f))) { + reset_timeline_view(state, session); + } + const float controls_end_x = ImGui::GetItemRectMax().x - ImGui::GetWindowPos().x; + ImGui::EndDisabled(); + + const char *status_text = state->status_text.empty() ? "Ready" : state->status_text.c_str(); + const float status_x = controls_end_x + 16.0f; + ImGui::SetCursorPos(ImVec2(status_x, row_y + 2.0f)); + ImGui::PushStyleColor(ImGuiCol_Text, color_rgb(102, 110, 118)); + ImGui::TextUnformatted(status_text); + ImGui::PopStyleColor(); + + } + ImGui::End(); + ImGui::PopStyleColor(2); + ImGui::PopStyleVar(); +} + +void draw_sidebar_resizer(const UiMetrics &ui, UiState *state) { + constexpr float kHandleWidth = 14.0f; + ImGui::SetNextWindowPos(ImVec2(ui.sidebar_width - kHandleWidth * 0.5f, ui.top_offset)); + ImGui::SetNextWindowSize(ImVec2(kHandleWidth, std::max(1.0f, ui.height - ui.top_offset))); + const ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoMove | + ImGuiWindowFlags_NoResize | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoBackground; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f)); + if (ImGui::Begin("##sidebar_resizer", nullptr, flags)) { + ImGui::InvisibleButton("##sidebar_resizer_button", ImVec2(kHandleWidth, std::max(1.0f, ui.height - ui.top_offset))); + if (ImGui::IsItemHovered() || ImGui::IsItemActive()) { + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + } + if (ImGui::IsItemActive()) { + const float max_width = std::min(SIDEBAR_MAX_WIDTH, ui.width * 0.6f); + state->sidebar_width = std::clamp(ImGui::GetIO().MousePos.x, SIDEBAR_MIN_WIDTH, max_width); + } + + ImDrawList *draw_list = ImGui::GetWindowDrawList(); + const ImVec2 origin = ImGui::GetWindowPos(); + draw_list->AddLine(ImVec2(origin.x + kHandleWidth * 0.5f, origin.y), + ImVec2(origin.x + kHandleWidth * 0.5f, origin.y + std::max(1.0f, ui.height - ui.top_offset)), + IM_COL32(194, 198, 204, 255)); + } + ImGui::End(); + ImGui::PopStyleVar(); +} diff --git a/tools/jotpluggler/sketch_layout.cc b/tools/jotpluggler/sketch_layout.cc new file mode 100644 index 00000000000..cd0bf510152 --- /dev/null +++ b/tools/jotpluggler/sketch_layout.cc @@ -0,0 +1,2202 @@ +#include "tools/jotpluggler/app.h" +#include "tools/jotpluggler/car_fingerprint_to_dbc.h" +#include "tools/jotpluggler/common.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/util.h" +#include "third_party/json11/json11.hpp" +#include "tools/replay/logreader.h" +#include "tools/replay/py_downloader.h" + +namespace fs = std::filesystem; + +namespace { + +struct RouteSelection { + std::string dongle_id; + std::string timestamp; + int begin_segment = 0; + int end_segment = -1; + bool slice_explicit = false; + LogSelector selector = LogSelector::Auto; + bool selector_explicit = false; + std::string canonical_name; +}; + +struct SegmentLogs { + std::string rlog; + std::string qlog; + std::string fcamera; + std::string dcamera; + std::string ecamera; + std::string qcamera; +}; + +enum class ScalarKind { + None, + Bool, + Int, + UInt, + Float, + Enum, +}; + +enum class ResolvedNodeKind { + Ignore, + Scalar, + Struct, + List, +}; + +struct ResolvedNode { + ResolvedNodeKind kind = ResolvedNodeKind::Ignore; + ScalarKind scalar_kind = ScalarKind::None; + int fixed_slot = -1; + bool has_field = false; + capnp::StructSchema::Field field; + std::string segment; + std::string path; + bool skip_large_scalar_list = false; + std::vector children; + std::unique_ptr element; +}; + +struct ResolvedService { + uint16_t event_which = 0; + capnp::StructSchema::Field union_field; + std::string service_name; + int valid_slot = -1; + int log_mono_time_slot = -1; + int seconds_slot = -1; + ResolvedNode payload; +}; + +struct SchemaIndex { + std::vector> by_which; + size_t fixed_series_count = 0; + std::vector fixed_paths; + + static const SchemaIndex &instance(); +}; + +constexpr size_t INVALID_DYNAMIC_SLOT = std::numeric_limits::max(); + +struct SeriesAccumulator { + explicit SeriesAccumulator(size_t fixed_count = 0) : fixed_series(fixed_count) {} + + std::vector fixed_series; + std::vector dynamic_series; + std::vector can_messages; + std::unordered_map dynamic_slots; + std::unordered_map> list_scalar_slots; + std::unordered_map can_message_slots; + std::unordered_map enum_info; +}; + +struct LoadedRouteArtifacts { + std::vector series; + std::vector can_messages; + std::vector logs; + std::vector timeline; + std::unordered_map enum_info; +}; + +struct RouteMetadata { + std::string car_fingerprint; +}; + +struct LoadStats { + using Clock = std::chrono::steady_clock; + using TimePoint = Clock::time_point; + + struct SegmentStats { + int segment_number = -1; + std::string log_path; + double download_seconds = 0.0; + double decompress_seconds = 0.0; + double parse_seconds = 0.0; + double extract_seconds = 0.0; + size_t compressed_bytes = 0; + size_t decompressed_bytes = 0; + size_t event_count = 0; + size_t series_count = 0; + bool failed = false; + }; + + explicit LoadStats(const RouteLoadProgressCallback &callback) : progress(callback) {} + + void publish(RouteLoadStage stage, size_t segment_index, const std::string &segment_name) { + if (!progress) { + return; + } + RouteLoadProgress update; + update.stage = stage; + update.segment_index = segment_index; + update.segment_count = segment_count; + update.current = stage == RouteLoadStage::DownloadingSegment + ? segments_downloaded.load() + : segments_parsed.load(); + update.total = total_segments.load(); + update.segments_downloaded = segments_downloaded.load(); + update.segments_parsed = segments_parsed.load(); + update.total_segments = total_segments.load(); + update.bytes_downloaded = bytes_downloaded.load(); + update.num_workers = num_workers; + update.segment_name = segment_name; + std::lock_guard lock(progress_mutex); + progress(update); + } + + void print_summary(size_t final_series_count) const { + const auto secs = [](TimePoint a, TimePoint b) { return std::chrono::duration(b - a).count(); }; + const auto mb = [](size_t bytes) { return static_cast(bytes) / (1024.0 * 1024.0); }; + double dl = 0, dc = 0, pa = 0, ex = 0; + size_t ev = 0, cb = 0, db = 0; + for (const auto &s : segments) { + dl += s.download_seconds; dc += s.decompress_seconds; + pa += s.parse_seconds; ex += s.extract_seconds; + ev += s.event_count; cb += s.compressed_bytes; db += s.decompressed_bytes; + } + std::cerr << std::fixed << std::setprecision(1) + << "route loaded in " << secs(load_start, load_end) << "s (" << segment_count << " segments, " << num_workers << " workers)\n" + << " resolve: " << secs(load_start, resolve_end) << "s fetch: " << dl << "s (" << mb(cb) << " MB)" + << " decompress: " << dc << "s (" << mb(db) << " MB)\n" + << " parse: " << pa << "s (" << ev << " events) extract: " << ex << "s merge: " << secs(merge_start, merge_end) << "s" + << " series: " << final_series_count << " paths\n"; + for (const auto &s : segments) { + std::cerr << " seg " << std::setw(2) << s.segment_number << ": " + << (s.failed ? "FAILED" : std::to_string(s.download_seconds) + "s + " + std::to_string(s.parse_seconds) + + "s (" + std::to_string(s.event_count) + " ev, " + std::to_string(s.series_count) + " series)") << "\n"; + } + std::cerr.unsetf(std::ios::floatfield); + } + + TimePoint load_start; + TimePoint resolve_end; + TimePoint merge_start; + TimePoint merge_end; + TimePoint load_end; + size_t segment_count = 0; + int num_workers = 1; + std::vector segments; + std::atomic segments_downloaded{0}; + std::atomic segments_parsed{0}; + std::atomic total_segments{0}; + std::atomic bytes_downloaded{0}; + RouteLoadProgressCallback progress; + mutable std::mutex progress_mutex; +}; + +std::string curve_label(std::string_view series_name) { + return std::string(series_name.empty() ? std::string_view{"plot"} : series_name); +} + +bool parse_segment_number(std::string_view value, int *out) { + if (value.empty()) return false; + char *end = nullptr; + const long parsed = std::strtol(std::string(value).c_str(), &end, 10); + if (end == nullptr || *end != '\0') return false; + *out = static_cast(parsed); + return true; +} + +bool is_log_selector_char(char c) { + return c == 'a' || c == 'r' || c == 'q'; +} + +LogSelector parse_log_selector_char(char c) { + switch (c) { + case 'r': return LogSelector::RLog; + case 'q': return LogSelector::QLog; + case 'a': + default: return LogSelector::Auto; + } +} + +const std::string &selected_log_path(const SegmentLogs &segment, LogSelector selector) { + switch (selector) { + case LogSelector::RLog: + return segment.rlog; + case LogSelector::QLog: + return segment.qlog; + case LogSelector::Auto: + default: + return !segment.rlog.empty() ? segment.rlog : segment.qlog; + } +} + +RouteSelection parse_route_selection(std::string route_name) { + RouteSelection route = {}; + route_name = util::strip(route_name); + if (route_name.size() >= 2 && route_name[route_name.size() - 2] == '/' + && is_log_selector_char(static_cast(std::tolower(route_name.back())))) { + route.selector = parse_log_selector_char(static_cast(std::tolower(route_name.back()))); + route.selector_explicit = true; + route_name.resize(route_name.size() - 2); + } + static const std::regex pattern(R"(^(([a-z0-9]{16})[|_/])?(.{20})((--|/)((-?\d+(:(-?\d+)?)?)|(:-?\d+)))?$)"); + std::smatch match; + if (!std::regex_match(route_name, match, pattern)) return route; + + route.dongle_id = match[2].str(); + route.timestamp = match[3].str(); + route.canonical_name = route.dongle_id + "|" + route.timestamp; + + const std::string separator = match[5].str(); + const std::string range_str = match[6].str(); + if (!range_str.empty()) { + route.slice_explicit = true; + if (separator == "/") { + size_t pos = range_str.find(':'); + int begin_segment = 0; + if (!parse_segment_number(range_str.substr(0, pos), &begin_segment)) { + return {}; + } + route.begin_segment = begin_segment; + route.end_segment = begin_segment; + if (pos != std::string::npos) { + int end_segment = -1; + const std::string end_str = range_str.substr(pos + 1); + if (!end_str.empty() && !parse_segment_number(end_str, &end_segment)) { + return {}; + } + route.end_segment = end_str.empty() ? -1 : end_segment; + } + } else if (separator == "--") { + int begin_segment = 0; + if (!parse_segment_number(range_str, &begin_segment)) return {}; + route.begin_segment = begin_segment; + } + } + return route; +} + +void add_log_file_to_segments(std::map *segments, int segment_number, const std::string &file) { + std::string name = extractFileName(file); + const size_t pos = name.find_last_of("--"); + name = pos != std::string::npos ? name.substr(pos + 2) : name; + SegmentLogs &segment = (*segments)[segment_number]; + if (name == "rlog.bz2" || name == "rlog.zst" || name == "rlog") { + segment.rlog = file; + } else if (name == "qlog.bz2" || name == "qlog.zst" || name == "qlog") { + segment.qlog = file; + } else if (name == "fcamera.hevc") { + segment.fcamera = file; + } else if (name == "dcamera.hevc") { + segment.dcamera = file; + } else if (name == "ecamera.hevc") { + segment.ecamera = file; + } else if (name == "qcamera.ts") { + segment.qcamera = file; + } +} + +std::map trim_segments(std::map segments, const RouteSelection &route) { + if (route.begin_segment > 0) { + segments.erase(segments.begin(), segments.lower_bound(route.begin_segment)); + } + if (route.end_segment >= 0) { + segments.erase(segments.upper_bound(route.end_segment), segments.end()); + } + return segments; +} + +std::map load_segments_from_json(const json11::Json &json) { + std::map segments; + static const std::regex rx(R"(\/(\d+)\/)"); + for (const auto &value : json.object_items()) { + for (const auto &url : value.second.array_items()) { + const std::string url_str = url.string_value(); + std::smatch match; + if (!std::regex_search(url_str, match, rx)) continue; + add_log_file_to_segments(&segments, std::stoi(match[1].str()), url_str); + } + } + return segments; +} + +std::map load_segments_from_server(const RouteSelection &route) { + const std::string result = PyDownloader::getRouteFiles(route.canonical_name); + if (result.empty()) throw std::runtime_error("Failed to fetch route files for " + route.canonical_name); + + std::string parse_error; + const auto json = json11::Json::parse(result, parse_error); + if (!parse_error.empty()) throw std::runtime_error("Failed to parse route file list for " + route.canonical_name); + if (json.is_object() && json["error"].is_string()) { + throw std::runtime_error("Route API error for " + route.canonical_name + ": " + json["error"].string_value()); + } + return load_segments_from_json(json); +} + +std::map load_segments_from_local(const RouteSelection &route, const std::string &data_dir) { + std::map segments; + const std::string pattern = route.timestamp + "--"; + for (const auto &entry : fs::directory_iterator(data_dir)) { + if (!entry.is_directory()) continue; + const std::string dirname = entry.path().filename().string(); + if (dirname.find(pattern) == std::string::npos) continue; + const size_t marker = dirname.rfind("--"); + if (marker == std::string::npos) continue; + int segment_number = 0; + if (!parse_segment_number(dirname.substr(marker + 2), &segment_number)) { + continue; + } + for (const auto &file : fs::directory_iterator(entry.path())) { + if (file.is_regular_file()) { + add_log_file_to_segments(&segments, segment_number, file.path().string()); + } + } + } + return segments; +} + +RouteIdentifier make_route_identifier(const RouteSelection &route, const std::map &segments) { + RouteIdentifier route_id; + route_id.dongle_id = route.dongle_id; + route_id.log_id = route.timestamp; + route_id.slice_begin = route.begin_segment; + route_id.slice_end = route.end_segment; + route_id.slice_explicit = route.slice_explicit; + route_id.selector = route.selector; + route_id.selector_explicit = route.selector_explicit; + if (!segments.empty()) { + route_id.available_begin = segments.begin()->first; + route_id.available_end = segments.rbegin()->first; + } + return route_id; +} + +std::string detect_dbc_for_fingerprint(std::string_view car_fingerprint) { + return std::string(dbc_for_car_fingerprint(car_fingerprint)); +} + +std::vector available_dbc_names_impl() { + std::set names; + for (const fs::path &dbc_dir : { + repo_root() / "opendbc" / "dbc", + repo_root() / "tools" / "jotpluggler" / "generated_dbcs", + }) { + if (fs::exists(dbc_dir) && fs::is_directory(dbc_dir)) { + for (const auto &entry : fs::directory_iterator(dbc_dir)) { + if (!entry.is_regular_file() || entry.path().extension() != ".dbc") { + continue; + } + names.insert(entry.path().stem().string()); + } + } + } + for (const auto &[_, dbc_name] : kCarFingerprintToDbc) { + if (!dbc_name.empty()) { + names.insert(std::string(dbc_name)); + } + } + return std::vector(names.begin(), names.end()); +} + +fs::path resolve_dbc_path(const std::string &dbc_name) { + for (const fs::path &candidate : { + repo_root() / "opendbc" / "dbc" / (dbc_name + ".dbc"), + repo_root() / "tools" / "jotpluggler" / "generated_dbcs" / (dbc_name + ".dbc"), + }) { + if (fs::exists(candidate)) return candidate; + } + throw std::runtime_error("DBC not found: " + dbc_name); +} + +std::array parse_color(std::string_view color) { + if (!color.empty() && color.front() == '#') { + color.remove_prefix(1); + } + if (color.size() != 6) return {160, 170, 180}; + + std::array out = {}; + for (size_t i = 0; i < 3; ++i) { + const std::string byte(color.substr(i * 2, 2)); + char *end = nullptr; + const long parsed = std::strtol(byte.c_str(), &end, 16); + if (end == nullptr || *end != '\0' || parsed < 0 || parsed > 255) return {160, 170, 180}; + out[i] = static_cast(parsed); + } + return out; +} + +uint8_t android_priority_to_level(uint8_t priority) { + switch (priority) { + case 2: + case 3: + return 10; + case 4: + return 20; + case 5: + return 30; + case 6: + return 40; + case 7: + default: + return 50; + } +} + +uint8_t alert_status_to_level(cereal::SelfdriveState::AlertStatus status) { + switch (status) { + case cereal::SelfdriveState::AlertStatus::NORMAL: + return 20; + case cereal::SelfdriveState::AlertStatus::USER_PROMPT: + return 30; + case cereal::SelfdriveState::AlertStatus::CRITICAL: + return 40; + } + return 20; +} + +TimelineEntry::Type alert_status_to_timeline_type(cereal::SelfdriveState::AlertStatus status, bool enabled) { + if (!enabled) { + return TimelineEntry::Type::None; + } + switch (status) { + case cereal::SelfdriveState::AlertStatus::NORMAL: + return TimelineEntry::Type::Engaged; + case cereal::SelfdriveState::AlertStatus::USER_PROMPT: + return TimelineEntry::Type::AlertInfo; + case cereal::SelfdriveState::AlertStatus::CRITICAL: + return TimelineEntry::Type::AlertCritical; + } + return TimelineEntry::Type::Engaged; +} + +void append_timeline_entry(std::vector *timeline, double mono_time, TimelineEntry::Type type) { + if (timeline == nullptr) { + return; + } + if (!timeline->empty() && timeline->back().type == type) { + timeline->back().end_time = std::max(timeline->back().end_time, mono_time); + return; + } + timeline->push_back(TimelineEntry{ + .start_time = mono_time, + .end_time = mono_time, + .type = type, + }); +} + +double android_wall_time_seconds(uint64_t timestamp) { + if (timestamp == 0) return 0.0; + if (timestamp > 1000000000000ULL) return static_cast(timestamp) / 1.0e9; + if (timestamp > 1000000000ULL) return static_cast(timestamp) / 1.0e6; + return static_cast(timestamp); +} + +std::optional json_u64_value(const json11::Json &value) { + if (value.is_number()) { + const double number = value.number_value(); + if (number >= 0.0) return static_cast(number); + } + if (value.is_string()) { + try { + return static_cast(std::stoull(value.string_value())); + } catch (...) { + } + } + return std::nullopt; +} + +std::optional json_int_value(const json11::Json &value) { + if (value.is_number()) return value.int_value(); + if (value.is_string()) { + try { + return std::stoi(value.string_value()); + } catch (...) { + } + } + return std::nullopt; +} + +std::string json_value_for_log(const json11::Json &value) { + if (value.is_string()) return value.string_value(); + if (value.is_bool()) return value.bool_value() ? "true" : "false"; + return value.dump(); +} + +std::string format_journal_context(const json11::Json &parsed, int pid, int tid) { + std::vector lines; + if (pid != 0 || tid != 0) { + lines.push_back("pid=" + std::to_string(pid) + ", tid=" + std::to_string(tid)); + } + + const std::array preferred_keys = { + "_HOSTNAME", + "_TRANSPORT", + "PRIORITY", + "SYSLOG_FACILITY", + "__MONOTONIC_TIMESTAMP", + }; + for (const char *key : preferred_keys) { + const json11::Json &value = parsed[key]; + if (!value.is_null()) { + lines.push_back(std::string(key) + "=" + json_value_for_log(value)); + } + } + return join(lines, "\n"); +} + +std::string alert_message_text(const cereal::SelfdriveState::Reader &state) { + std::string text = state.getAlertText1().cStr(); + const std::string text2 = state.getAlertText2().cStr(); + if (!text2.empty()) { + text += " - " + text2; + } + return text; +} + +bool same_log_entry(const LogEntry &a, const LogEntry &b) { + return a.mono_time == b.mono_time + && a.level == b.level + && a.source == b.source + && a.func == b.func + && a.message == b.message + && a.context == b.context + && a.origin == b.origin; +} + +void append_log_event(cereal::Event::Which which, + const cereal::Event::Reader &event, + double time_offset, + std::vector *logs, + std::string *last_alert_key) { + const double boot_time = static_cast(event.getLogMonoTime()) / 1.0e9; + const double mono_time = boot_time - time_offset; + + auto make_entry = [&](LogOrigin origin, uint8_t level = 20) { + LogEntry e; + e.mono_time = mono_time; + e.boot_time = boot_time; + e.origin = origin; + e.level = level; + return e; + }; + + switch (which) { + case cereal::Event::Which::LOG_MESSAGE: + case cereal::Event::Which::ERROR_LOG_MESSAGE: { + const std::string raw = which == cereal::Event::Which::LOG_MESSAGE + ? event.getLogMessage().cStr() : event.getErrorLogMessage().cStr(); + auto entry = make_entry(LogOrigin::Log, which == cereal::Event::Which::ERROR_LOG_MESSAGE ? 40 : 20); + entry.source = "log"; + entry.message = raw; + std::string err; + if (const auto p = json11::Json::parse(raw, err); err.empty() && p.is_object()) { + entry.wall_time = p["created"].number_value(); + if (p["levelnum"].is_number()) entry.level = static_cast(p["levelnum"].int_value()); + const std::string fn = p["filename"].string_value(); + const int ln = p["lineno"].is_number() ? p["lineno"].int_value() : 0; + entry.source = fn.empty() ? "log" : fn + (ln > 0 ? ":" + std::to_string(ln) : ""); + entry.func = p["funcname"].string_value(); + if (p["msg"].is_string()) entry.message = p["msg"].string_value(); + if (!p["ctx"].is_null()) entry.context = p["ctx"].dump(); + } + logs->push_back(std::move(entry)); + break; + } + case cereal::Event::Which::ANDROID_LOG: { + const auto android = event.getAndroidLog(); + auto entry = make_entry(LogOrigin::Android, android_priority_to_level(android.getPriority())); + entry.wall_time = android_wall_time_seconds(android.getTs()); + entry.source = android.hasTag() ? android.getTag().cStr() : "android"; + entry.message = android.hasMessage() ? android.getMessage().cStr() : std::string(); + entry.context = "pid=" + std::to_string(android.getPid()) + ", tid=" + std::to_string(android.getTid()); + if (!entry.message.empty()) { + std::string err; + if (const auto p = json11::Json::parse(entry.message, err); err.empty() && p.is_object()) { + if (p["MESSAGE"].is_string()) entry.message = p["MESSAGE"].string_value(); + if (p["SYSLOG_IDENTIFIER"].is_string() && !p["SYSLOG_IDENTIFIER"].string_value().empty()) + entry.source = p["SYSLOG_IDENTIFIER"].string_value(); + if (auto pri = json_int_value(p["PRIORITY"]); pri.has_value()) + entry.level = android_priority_to_level(*pri); + if (auto ts = json_u64_value(p["__REALTIME_TIMESTAMP"]); ts.has_value()) + entry.wall_time = android_wall_time_seconds(*ts); + entry.context = format_journal_context(p, android.getPid(), android.getTid()); + } + } + logs->push_back(std::move(entry)); + break; + } + case cereal::Event::Which::SELFDRIVE_STATE: { + const auto sd = event.getSelfdriveState(); + const std::string alert_type = sd.getAlertType().cStr(); + const std::string alert_text1 = sd.getAlertText1().cStr(); + if (alert_text1.empty() && alert_type.empty()) break; + const std::string key = alert_type + "\n" + alert_text1 + "\n" + std::string(sd.getAlertText2().cStr()); + if (last_alert_key != nullptr && key == *last_alert_key) break; + if (last_alert_key != nullptr) *last_alert_key = key; + auto entry = make_entry(LogOrigin::Alert, alert_status_to_level(sd.getAlertStatus())); + entry.source = "alert"; + entry.func = alert_type; + entry.message = alert_message_text(sd); + logs->push_back(std::move(entry)); + break; + } + default: + break; + } +} + +std::vector extract_segment_timeline(const std::vector &events) { + std::vector timeline; + timeline.reserve(events.size() / 16); + + for (const Event &event_record : events) { + if (event_record.which != cereal::Event::Which::SELFDRIVE_STATE) { + continue; + } + capnp::FlatArrayMessageReader event_reader(event_record.data); + const cereal::Event::Reader event = event_reader.getRoot(); + const auto sd = event.getSelfdriveState(); + const double mono_time = static_cast(event.getLogMonoTime()) / 1.0e9; + append_timeline_entry(&timeline, mono_time, alert_status_to_timeline_type(sd.getAlertStatus(), sd.getEnabled())); + } + + return timeline; +} + +std::vector extract_segment_logs(const std::vector &events) { + std::vector logs; + logs.reserve(events.size() / 8); + std::string last_alert_key; + + for (const Event &event_record : events) { + capnp::FlatArrayMessageReader event_reader(event_record.data); + const cereal::Event::Reader event = event_reader.getRoot(); + append_log_event(event_record.which, event, 0.0, &logs, &last_alert_key); + } + + return logs; +} + +RouteMetadata extract_segment_metadata(const std::vector &events) { + RouteMetadata metadata; + for (const Event &event_record : events) { + if (event_record.which != cereal::Event::Which::CAR_PARAMS) continue; + capnp::FlatArrayMessageReader event_reader(event_record.data); + const cereal::Event::Reader event = event_reader.getRoot(); + metadata.car_fingerprint = event.getCarParams().getCarFingerprint().cStr(); + if (!metadata.car_fingerprint.empty()) break; + } + return metadata; +} + +RouteMetadata detect_route_metadata(const std::map &segments, LogSelector selector) { + for (const auto &[_, segment] : segments) { + const std::string &log_path = selector == LogSelector::Auto + ? (!segment.qlog.empty() ? segment.qlog : segment.rlog) + : selected_log_path(segment, selector); + if (log_path.empty()) { + continue; + } + LogReader reader; + if (!reader.load(log_path, nullptr, true)) continue; + RouteMetadata metadata = extract_segment_metadata(reader.events); + if (!metadata.car_fingerprint.empty()) return metadata; + } + return {}; +} + +std::vector normalize_sizes(const json11::Json &sizes_json, size_t child_count) { + std::vector parsed; + if (sizes_json.is_array()) { + for (const json11::Json &value : sizes_json.array_items()) { + if (value.is_number()) { + parsed.push_back(std::max(value.number_value(), 0.0)); + } + } + } + + if (parsed.size() != child_count || child_count == 0) return std::vector(child_count, child_count == 0 ? 0.0 : 1.0 / static_cast(child_count)); + + const double total = std::accumulate(parsed.begin(), parsed.end(), 0.0); + if (total <= 0.0) return std::vector(child_count, 1.0 / static_cast(child_count)); + for (double &value : parsed) { + value /= total; + } + return parsed; +} + +PlotRange parse_range(const json11::Json &pane_node) { + PlotRange range; + const json11::Json &range_node = pane_node["range"]; + if (range_node.is_object()) { + range.valid = true; + range.left = range_node["left"].number_value(); + range.right = range_node["right"].number_value(); + range.bottom = range_node["bottom"].number_value(); + range.top = range_node["top"].is_number() ? range_node["top"].number_value() : 1.0; + } + const json11::Json &limit_y_node = pane_node["y_limits"]; + if (limit_y_node.is_object()) { + if (limit_y_node["min"].is_number()) { + range.has_y_limit_min = true; + range.y_limit_min = limit_y_node["min"].number_value(); + } + if (limit_y_node["max"].is_number()) { + range.has_y_limit_max = true; + range.y_limit_max = limit_y_node["max"].number_value(); + } + } + return range; +} + +Curve parse_curve(const json11::Json &curve_node) { + Curve curve; + curve.name = curve_node["name"].string_value(); + curve.label = curve_label(curve.name); + curve.color = parse_color(curve_node["color"].string_value()); + + const std::string transform_name = curve_node["transform"].string_value(); + if (transform_name == "derivative") { + curve.derivative = true; + curve.derivative_dt = curve_node["derivative_dt"].is_number() ? curve_node["derivative_dt"].number_value() : 0.0; + } else if (transform_name == "scale") { + curve.value_scale = curve_node["scale"].is_number() ? curve_node["scale"].number_value() : 1.0; + curve.value_offset = curve_node["offset"].is_number() ? curve_node["offset"].number_value() : 0.0; + } + const json11::Json &custom_node = curve_node["custom_python"]; + if (custom_node.is_object()) { + CustomPythonSeries spec; + spec.linked_source = custom_node["linked_source"].string_value(); + spec.globals_code = custom_node["globals_code"].string_value(); + spec.function_code = custom_node["function_code"].string_value(); + for (const json11::Json &source : custom_node["additional_sources"].array_items()) { + if (source.is_string()) { + spec.additional_sources.push_back(source.string_value()); + } + } + curve.custom_python = std::move(spec); + } + return curve; +} + +std::string pane_title(const json11::Json &dock_area_node) { + const std::string raw = dock_area_node["title"].string_value(); + return raw.empty() ? "..." : raw; +} + +Pane parse_dock_area(const json11::Json &dock_area_node) { + Pane pane; + const std::string kind = dock_area_node["kind"].string_value(); + if (kind == "map") { + pane.kind = PaneKind::Map; + } else if (kind == "camera") { + pane.kind = PaneKind::Camera; + const std::string camera_view = dock_area_node["camera_view"].string_value(); + if (const CameraViewSpec *spec = camera_view_spec_from_layout_name(camera_view)) { + pane.camera_view = spec->view; + } else { + pane.camera_view = CameraViewKind::Road; + } + } + pane.range = parse_range(dock_area_node); + const json11::Json &curves_node = dock_area_node["curves"]; + if (curves_node.is_array()) { + for (const json11::Json &curve_node : curves_node.array_items()) { + if (curve_node.is_object()) { + pane.curves.push_back(parse_curve(curve_node)); + } + } + } + pane.title = pane_title(dock_area_node); + return pane; +} + +WorkspaceNode parse_workspace_node(const json11::Json &node, WorkspaceTab *tab) { + WorkspaceNode workspace_node; + if (!node.is_object()) return workspace_node; + + if (node["curves"].is_array()) { + workspace_node.is_pane = true; + workspace_node.pane_index = static_cast(tab->panes.size()); + tab->panes.push_back(parse_dock_area(node)); + return workspace_node; + } + + const json11::Json &children_node = node["children"]; + if (!children_node.is_array()) return workspace_node; + + const std::vector children = children_node.array_items(); + if (children.empty()) return workspace_node; + + const std::string split = node["split"].string_value(); + workspace_node.orientation = split == "vertical" ? SplitOrientation::Vertical : SplitOrientation::Horizontal; + const std::vector sizes = normalize_sizes(node["sizes"], children.size()); + workspace_node.sizes.reserve(sizes.size()); + workspace_node.children.reserve(children.size()); + for (size_t i = 0; i < children.size(); ++i) { + workspace_node.sizes.push_back(static_cast(sizes[i])); + workspace_node.children.push_back(parse_workspace_node(children[i], tab)); + } + return workspace_node; +} + +WorkspaceTab parse_tab(const json11::Json &tab, const fs::path &layout_path) { + WorkspaceTab workspace_tab; + workspace_tab.tab_name = tab["name"].string_value().empty() ? "tab1" : tab["name"].string_value(); + const json11::Json &dock_root = tab["root"]; + if (!dock_root.is_object()) throw std::runtime_error("Layout tab has no dock content: " + layout_path.string()); + workspace_tab.root = parse_workspace_node(dock_root, &workspace_tab); + return workspace_tab; +} + +SketchLayout parse_layout(const fs::path &layout_path) { + const std::string text = util::read_file(layout_path.string()); + if (text.empty()) throw std::runtime_error("Failed to read layout JSON: " + layout_path.string()); + + std::string parse_error; + const json11::Json root = json11::Json::parse(text, parse_error); + if (!parse_error.empty() || !root.is_object()) { + throw std::runtime_error("Failed to parse layout JSON: " + layout_path.string()); + } + SketchLayout layout; + for (const json11::Json &tab : root["tabs"].array_items()) { + if (tab.is_object()) { + layout.tabs.push_back(parse_tab(tab, layout_path)); + } + } + if (layout.tabs.empty()) throw std::runtime_error("Layout has no tabs: " + layout_path.string()); + const json11::Json &tab_index = root["current_tab_index"].is_number() ? root["current_tab_index"] : root["currentTabIndex"]; + layout.current_tab_index = std::clamp(tab_index.is_number() ? tab_index.int_value() : 0, + 0, + static_cast(layout.tabs.size()) - 1); + return layout; +} + +ScalarKind scalar_kind_for_type(const capnp::Type &type) { + if (type.isBool()) return ScalarKind::Bool; + if (type.isInt8() || type.isInt16() || type.isInt32() || type.isInt64()) { + return ScalarKind::Int; + } + if (type.isUInt8() || type.isUInt16() || type.isUInt32() || type.isUInt64()) { + return ScalarKind::UInt; + } + if (type.isFloat32() || type.isFloat64()) { + return ScalarKind::Float; + } + if (type.isEnum()) return ScalarKind::Enum; + return ScalarKind::None; +} + +ResolvedNode build_resolved_type(const capnp::Type &type, + bool has_field, + capnp::StructSchema::Field field, + std::string segment, + std::string path, + size_t *next_fixed_slot, + std::vector *fixed_paths, + bool dynamic_path = false) { + ResolvedNode node; + node.has_field = has_field; + node.field = field; + node.segment = std::move(segment); + node.path = std::move(path); + node.scalar_kind = scalar_kind_for_type(type); + if (node.scalar_kind != ScalarKind::None) { + node.kind = ResolvedNodeKind::Scalar; + if (!dynamic_path) { + node.fixed_slot = static_cast((*next_fixed_slot)++); + fixed_paths->push_back(node.path); + } + return node; + } + + if (type.isStruct()) { + node.kind = ResolvedNodeKind::Struct; + for (auto child : type.asStruct().getFields()) { + const std::string child_segment = child.getProto().getName().cStr(); + node.children.push_back(build_resolved_type( + child.getType(), + true, + child, + child_segment, + node.path + "/" + child_segment, + next_fixed_slot, + fixed_paths, + dynamic_path)); + } + return node; + } + + if (type.isList()) { + const capnp::Type element_type = type.asList().getElementType(); + if (element_type.isText() || element_type.isData() || element_type.isInterface() || element_type.isAnyPointer()) { + node.kind = ResolvedNodeKind::Ignore; + return node; + } + node.kind = ResolvedNodeKind::List; + node.skip_large_scalar_list = scalar_kind_for_type(element_type) != ScalarKind::None; + node.element = std::make_unique( + build_resolved_type(element_type, + false, + capnp::StructSchema::Field(), + "", + node.path, + next_fixed_slot, + fixed_paths, + true)); + return node; + } + + node.kind = ResolvedNodeKind::Ignore; + return node; +} + +int register_fixed_series_path(const std::string &path, + size_t *next_fixed_slot, + std::vector *fixed_paths) { + const int slot = static_cast((*next_fixed_slot)++); + fixed_paths->push_back(path); + return slot; +} + +const SchemaIndex &SchemaIndex::instance() { + static const SchemaIndex index = [] { + SchemaIndex out; + const auto event_schema = capnp::Schema::from().asStruct(); + uint16_t max_discriminant = 0; + for (auto union_field : event_schema.getUnionFields()) { + max_discriminant = std::max(max_discriminant, union_field.getProto().getDiscriminantValue()); + } + out.by_which.resize(static_cast(max_discriminant) + 1); + size_t next_fixed_slot = 0; + for (auto union_field : event_schema.getUnionFields()) { + ResolvedService service; + service.event_which = union_field.getProto().getDiscriminantValue(); + service.union_field = union_field; + service.service_name = union_field.getProto().getName().cStr(); + service.valid_slot = register_fixed_series_path( + "/" + service.service_name + "/valid", &next_fixed_slot, &out.fixed_paths); + service.log_mono_time_slot = register_fixed_series_path( + "/" + service.service_name + "/logMonoTime", &next_fixed_slot, &out.fixed_paths); + service.seconds_slot = register_fixed_series_path( + "/" + service.service_name + "/t", &next_fixed_slot, &out.fixed_paths); + service.payload = build_resolved_type( + union_field.getType(), + false, + capnp::StructSchema::Field(), + service.service_name, + "/" + service.service_name, + &next_fixed_slot, + &out.fixed_paths); + out.by_which[service.event_which] = std::move(service); + } + out.fixed_series_count = next_fixed_slot; + return out; + }(); + return index; +} + +bool is_absolute_curve(const std::string &name) { + return !name.empty() && name.front() == '/'; +} + +std::optional scalar_value_to_double(const capnp::DynamicValue::Reader &value, ScalarKind kind) { + switch (kind) { + case ScalarKind::Bool: + return value.as() ? 1.0 : 0.0; + case ScalarKind::Int: + return static_cast(value.as()); + case ScalarKind::UInt: + return static_cast(value.as()); + case ScalarKind::Float: + return value.as(); + case ScalarKind::Enum: + return static_cast(value.as().getRaw()); + case ScalarKind::None: + return std::nullopt; + } + return std::nullopt; +} + +void capture_enum_info(const std::string &path, + const capnp::DynamicValue::Reader &value, + SeriesAccumulator *series) { + if (series->enum_info.find(path) != series->enum_info.end()) { + return; + } + + const auto dynamic_enum = value.as(); + EnumInfo info; + for (auto enumerant : dynamic_enum.getSchema().getEnumerants()) { + const uint16_t ordinal = enumerant.getOrdinal(); + if (ordinal >= info.names.size()) { + info.names.resize(static_cast(ordinal) + 1); + } + info.names[ordinal] = enumerant.getProto().getName().cStr(); + } + if (!info.names.empty()) { + series->enum_info.emplace(path, std::move(info)); + } +} + +void append_scalar_point(RouteSeries *series, + const std::string &path, + double tm, + double value) { + if (series->path.empty()) { + series->path = path; + } + series->times.push_back(tm); + series->values.push_back(value); +} + +void append_fixed_scalar_point(RouteSeries *series, double tm, double value) { + series->times.push_back(tm); + series->values.push_back(value); +} + +CanMessageData *ensure_can_message(CanServiceKind service, uint8_t bus, uint32_t address, SeriesAccumulator *series) { + const CanMessageId id{service, bus, address}; + auto [it, inserted] = series->can_message_slots.try_emplace(id, series->can_messages.size()); + if (inserted) { + series->can_messages.push_back(CanMessageData{.id = id}); + } + return &series->can_messages[it->second]; +} + +void append_can_frame(CanServiceKind service, + uint8_t bus, + uint32_t address, + uint16_t bus_time, + capnp::Data::Reader dat, + double tm, + SeriesAccumulator *series) { + CanMessageData *message = ensure_can_message(service, bus, address, series); + message->samples.push_back(CanFrameSample{ + .mono_time = tm, + .bus_time = bus_time, + .data = std::string(reinterpret_cast(dat.begin()), dat.size()), + }); +} + +void append_dynamic_scalar_point(const std::string &path, double tm, double value, SeriesAccumulator *series); + +void decode_can_frame(const dbc::Database *can_dbc, + const std::string &service_name, + uint8_t bus, + uint32_t address, + const uint8_t *raw, + size_t data_size, + double tm, + SeriesAccumulator *series) { + if (can_dbc == nullptr) { + return; + } + const dbc::Message *message = can_dbc->message(address); + if (message == nullptr) { + return; + } + const std::string base_path = "/" + service_name + "/" + std::to_string(bus) + "/" + message->name; + for (const dbc::Signal &signal : message->signals) { + std::optional value = dbc::signalValue(signal, *message, raw, data_size); + if (!value.has_value()) continue; + const std::string path = base_path + "/" + signal.name; + append_dynamic_scalar_point(path, tm, *value, series); + if (series->enum_info.find(path) == series->enum_info.end()) { + std::vector enum_names = can_dbc->enumNames(signal); + if (!enum_names.empty()) { + series->enum_info.emplace(path, EnumInfo{.names = std::move(enum_names)}); + } + } + } +} + +void append_live_can_frame(CanServiceKind service, + const LiveCanFrame &frame, + double time_offset, + const dbc::Database *can_dbc, + SeriesAccumulator *series) { + const double tm = frame.mono_time - time_offset; + CanMessageData *message = ensure_can_message(service, frame.bus, frame.address, series); + message->samples.push_back(CanFrameSample{ + .mono_time = tm, + .bus_time = frame.bus_time, + .data = frame.data, + }); + decode_can_frame(can_dbc, + service == CanServiceKind::Can ? "can" : "sendcan", + frame.bus, + frame.address, + reinterpret_cast(frame.data.data()), + frame.data.size(), + tm, + series); +} + +SeriesAccumulator make_series_accumulator(const SchemaIndex &schema) { + SeriesAccumulator out(schema.fixed_series_count); + for (size_t i = 0; i < schema.fixed_paths.size(); ++i) { + out.fixed_series[i].path = schema.fixed_paths[i]; + } + return out; +} + +size_t ensure_dynamic_slot(const std::string &path, SeriesAccumulator *series) { + auto [it, inserted] = series->dynamic_slots.try_emplace(path, series->dynamic_series.size()); + if (inserted) { + series->dynamic_series.push_back(RouteSeries{it->first}); + } + return it->second; +} + +RouteSeries *ensure_dynamic_series(const std::string &path, SeriesAccumulator *series) { + return &series->dynamic_series[ensure_dynamic_slot(path, series)]; +} + +RouteSeries *ensure_list_scalar_series(const std::string &base_path, size_t index, SeriesAccumulator *series) { + auto [it, _] = series->list_scalar_slots.try_emplace(base_path); + std::vector &slots = it->second; + if (slots.size() <= index) { + slots.resize(index + 1, INVALID_DYNAMIC_SLOT); + } + if (slots[index] == INVALID_DYNAMIC_SLOT) { + slots[index] = ensure_dynamic_slot(base_path + "/" + std::to_string(index), series); + } + return &series->dynamic_series[slots[index]]; +} + +void append_dynamic_scalar_point(const std::string &path, double tm, double value, SeriesAccumulator *series) { + append_scalar_point(ensure_dynamic_series(path, series), path, tm, value); +} + +void append_scalar_value(const ResolvedNode &node, + const std::string *path_override, + const capnp::DynamicValue::Reader &raw_value, + double tm, + double value, + SeriesAccumulator *series) { + if (path_override == nullptr && node.fixed_slot >= 0) { + if (node.scalar_kind == ScalarKind::Enum) { + capture_enum_info(node.path, raw_value, series); + } + append_fixed_scalar_point(&series->fixed_series[static_cast(node.fixed_slot)], tm, value); + return; + } + + const std::string &path = path_override != nullptr ? *path_override : node.path; + if (node.scalar_kind == ScalarKind::Enum) { + capture_enum_info(path, raw_value, series); + } + append_dynamic_scalar_point(path, tm, value, series); +} + +void append_fast_node(const ResolvedNode &node, + const capnp::DynamicValue::Reader &value, + double tm, + SeriesAccumulator *series, + const std::string *path_override = nullptr) { + switch (node.kind) { + case ResolvedNodeKind::Scalar: { + if (std::optional scalar = scalar_value_to_double(value, node.scalar_kind); scalar.has_value()) { + append_scalar_value(node, path_override, value, tm, *scalar, series); + } + return; + } + case ResolvedNodeKind::Struct: { + const capnp::DynamicStruct::Reader reader = value.as(); + for (const ResolvedNode &child : node.children) { + if (!child.has_field || !reader.has(child.field)) continue; + if (path_override == nullptr) { + append_fast_node(child, reader.get(child.field), tm, series, nullptr); + } else { + const std::string child_path = child.segment.empty() ? *path_override : (*path_override + "/" + child.segment); + append_fast_node(child, reader.get(child.field), tm, series, &child_path); + } + } + return; + } + case ResolvedNodeKind::List: { + if (!node.element) { + return; + } + const capnp::DynamicList::Reader list = value.as(); + if (list.size() == 0) { + return; + } + if (node.skip_large_scalar_list && list.size() > 16) { + return; + } + const std::string &base_path = path_override != nullptr ? *path_override : node.path; + if (node.element->kind == ResolvedNodeKind::Scalar) { + for (uint i = 0; i < list.size(); ++i) { + if (std::optional scalar = scalar_value_to_double(list[i], node.element->scalar_kind); scalar.has_value()) { + RouteSeries *item_series = ensure_list_scalar_series(base_path, i, series); + if (node.element->scalar_kind == ScalarKind::Enum && !item_series->path.empty()) { + capture_enum_info(item_series->path, list[i], series); + } + append_fixed_scalar_point(item_series, tm, *scalar); + } + } + return; + } + for (uint i = 0; i < list.size(); ++i) { + const std::string item_path = base_path + "/" + std::to_string(i); + append_fast_node(*node.element, list[i], tm, series, &item_path); + } + return; + } + case ResolvedNodeKind::Ignore: + return; + } +} + +void append_event_fast(cereal::Event::Which which, + int32_t eidx_segnum, + kj::ArrayPtr data, + const SchemaIndex &schema, + const dbc::Database *can_dbc, + bool skip_raw_can, + double time_offset, + SeriesAccumulator *series) { + if (eidx_segnum != -1) { + return; + } + const uint16_t which_index = static_cast(which); + if (which_index >= schema.by_which.size() || !schema.by_which[which_index].has_value()) { + return; + } + const ResolvedService &service = *schema.by_which[which_index]; + capnp::FlatArrayMessageReader event_reader(data); + const cereal::Event::Reader event = event_reader.getRoot(); + const double tm = static_cast(event.getLogMonoTime()) / 1.0e9 - time_offset; + append_fixed_scalar_point(&series->fixed_series[static_cast(service.valid_slot)], + tm, + event.getValid() ? 1.0 : 0.0); + append_fixed_scalar_point(&series->fixed_series[static_cast(service.log_mono_time_slot)], + tm, + static_cast(event.getLogMonoTime())); + append_fixed_scalar_point(&series->fixed_series[static_cast(service.seconds_slot)], + tm, + tm); + if (service.service_name == "can" || service.service_name == "sendcan") { + const CanServiceKind can_service = service.service_name == "can" + ? CanServiceKind::Can + : CanServiceKind::Sendcan; + auto decode_message = [&](uint8_t bus, uint32_t address, const auto &dat_reader) { + const auto bytes = dat_reader.begin(); + decode_can_frame(can_dbc, service.service_name, bus, address, bytes, dat_reader.size(), tm, series); + }; + if (service.service_name == "can") { + for (const auto &msg : event.getCan()) { + append_can_frame(can_service, + static_cast(msg.getSrc()), + msg.getAddress(), + msg.getBusTimeDEPRECATED(), + msg.getDat(), + tm, + series); + if (!skip_raw_can) continue; + decode_message(static_cast(msg.getSrc()), msg.getAddress(), msg.getDat()); + } + } else { + for (const auto &msg : event.getSendcan()) { + append_can_frame(can_service, + static_cast(msg.getSrc()), + msg.getAddress(), + msg.getBusTimeDEPRECATED(), + msg.getDat(), + tm, + series); + if (!skip_raw_can) continue; + decode_message(static_cast(msg.getSrc()), msg.getAddress(), msg.getDat()); + } + } + if (skip_raw_can) { + return; + } + } + + const capnp::DynamicStruct::Reader dynamic_event(event); + append_fast_node(service.payload, dynamic_event.get(service.union_field), tm, series); +} + +void append_events_fast_range(const std::vector &events, + size_t begin, + size_t end, + const SchemaIndex &schema, + const dbc::Database *can_dbc, + bool skip_raw_can, + SeriesAccumulator *series) { + for (size_t i = begin; i < end; ++i) { + const Event &event_record = events[i]; + append_event_fast(event_record.which, + event_record.eidx_segnum, + event_record.data, + schema, + can_dbc, + skip_raw_can, + 0.0, + series); + } +} + +void merge_route_series(RouteSeries *dst, RouteSeries *src) { + if (src->times.empty()) { + return; + } + if (dst->times.empty()) { + *dst = std::move(*src); + return; + } + + dst->times.reserve(dst->times.size() + src->times.size()); + dst->values.reserve(dst->values.size() + src->values.size()); + dst->times.insert(dst->times.end(), src->times.begin(), src->times.end()); + dst->values.insert(dst->values.end(), src->values.begin(), src->values.end()); +} + +void merge_can_message_data(CanMessageData *dst, CanMessageData *src) { + if (src->samples.empty()) { + return; + } + if (dst->samples.empty()) { + *dst = std::move(*src); + return; + } + dst->samples.reserve(dst->samples.size() + src->samples.size()); + dst->samples.insert(dst->samples.end(), + std::make_move_iterator(src->samples.begin()), + std::make_move_iterator(src->samples.end())); +} + +void merge_series_accumulator(SeriesAccumulator *dst, SeriesAccumulator *src) { + if (dst->fixed_series.size() != src->fixed_series.size()) { + throw std::runtime_error("Fixed-series slot count mismatch during merge"); + } + + for (size_t i = 0; i < dst->fixed_series.size(); ++i) { + merge_route_series(&dst->fixed_series[i], &src->fixed_series[i]); + } + for (auto &series : src->dynamic_series) { + if (series.path.empty()) continue; + RouteSeries &dst_series = dst->dynamic_series[ensure_dynamic_slot(series.path, dst)]; + merge_route_series(&dst_series, &series); + } + for (auto &message : src->can_messages) { + CanMessageData &dst_message = *ensure_can_message(message.id.service, message.id.bus, message.id.address, dst); + merge_can_message_data(&dst_message, &message); + } + for (auto &[path, info] : src->enum_info) { + dst->enum_info.try_emplace(path, std::move(info)); + } +} + +size_t populated_series_count(const SeriesAccumulator &series) { + size_t count = 0; + for (const RouteSeries &fixed : series.fixed_series) { + count += !fixed.times.empty(); + } + for (const RouteSeries &dynamic : series.dynamic_series) { + count += !dynamic.times.empty(); + } + return count; +} + +bool series_is_sorted(const RouteSeries &series) { + for (size_t i = 1; i < series.times.size(); ++i) { + if (series.times[i] < series.times[i - 1]) return false; + } + return true; +} + +void sort_series_by_time(RouteSeries *series) { + if (series->times.size() <= 1 || series_is_sorted(*series)) { + return; + } + std::vector order(series->times.size()); + std::iota(order.begin(), order.end(), 0); + std::sort(order.begin(), order.end(), [&](size_t a, size_t b) { + return series->times[a] < series->times[b]; + }); + + std::vector sorted_times(series->times.size()); + std::vector sorted_values(series->values.size()); + for (size_t i = 0; i < order.size(); ++i) { + sorted_times[i] = series->times[order[i]]; + sorted_values[i] = series->values[order[i]]; + } + series->times = std::move(sorted_times); + series->values = std::move(sorted_values); +} + +std::vector collect_series(SeriesAccumulator &&series) { + std::vector out; + out.reserve(series.fixed_series.size() + series.dynamic_series.size()); + for (auto &fixed : series.fixed_series) { + sort_series_by_time(&fixed); + if (!fixed.times.empty()) { + out.push_back(std::move(fixed)); + } + } + for (auto &dynamic : series.dynamic_series) { + sort_series_by_time(&dynamic); + if (!dynamic.times.empty()) { + out.push_back(std::move(dynamic)); + } + } + return out; +} + +RouteData build_route_data(std::vector &&series_list, + std::vector &&can_messages, + std::vector &&logs, + std::vector &&timeline, + std::unordered_map &&enum_info, + std::string car_fingerprint, + std::string dbc_name) { + RouteData route_data; + route_data.series.reserve(series_list.size()); + route_data.paths.reserve(series_list.size()); + for (RouteSeries &series : series_list) { + if (series.times.empty()) continue; + route_data.has_time_range = true; + route_data.x_min = route_data.series.empty() ? series.times.front() : std::min(route_data.x_min, series.times.front()); + route_data.x_max = route_data.series.empty() ? series.times.back() : std::max(route_data.x_max, series.times.back()); + route_data.paths.push_back(series.path); + route_data.series.push_back(std::move(series)); + } + + std::sort(route_data.paths.begin(), route_data.paths.end()); + std::sort(route_data.series.begin(), route_data.series.end(), [](const RouteSeries &a, const RouteSeries &b) { + return a.path < b.path; + }); + std::sort(logs.begin(), logs.end(), [](const LogEntry &a, const LogEntry &b) { + return a.mono_time < b.mono_time; + }); + logs.erase(std::unique(logs.begin(), logs.end(), [](const LogEntry &a, const LogEntry &b) { + return same_log_entry(a, b); + }), + logs.end()); + + std::vector deduped_logs; + deduped_logs.reserve(logs.size()); + for (LogEntry &entry : logs) { + if (!deduped_logs.empty() + && entry.origin == LogOrigin::Alert + && deduped_logs.back().origin == LogOrigin::Alert + && deduped_logs.back().func == entry.func + && deduped_logs.back().message == entry.message) { + continue; + } + deduped_logs.push_back(std::move(entry)); + } + route_data.logs = std::move(deduped_logs); + + if (!route_data.has_time_range && !route_data.logs.empty()) { + route_data.has_time_range = true; + route_data.x_min = route_data.logs.front().mono_time; + route_data.x_max = route_data.logs.back().mono_time; + } + if (!route_data.has_time_range) { + bool initialized = false; + for (const CanMessageData &message : can_messages) { + if (message.samples.empty()) continue; + if (!initialized) { + route_data.x_min = message.samples.front().mono_time; + route_data.x_max = message.samples.back().mono_time; + initialized = true; + } else { + route_data.x_min = std::min(route_data.x_min, message.samples.front().mono_time); + route_data.x_max = std::max(route_data.x_max, message.samples.back().mono_time); + } + } + route_data.has_time_range = initialized; + } + if (!route_data.has_time_range && !timeline.empty()) { + route_data.has_time_range = true; + route_data.x_min = timeline.front().start_time; + route_data.x_max = timeline.back().end_time; + } + + if (route_data.has_time_range) { + const double time_offset = route_data.x_min; + for (RouteSeries &series : route_data.series) { + for (double &tm : series.times) { + tm -= time_offset; + } + } + for (LogEntry &entry : route_data.logs) { + entry.boot_time = entry.mono_time; + entry.mono_time -= time_offset; + } + for (CanMessageData &message : can_messages) { + for (CanFrameSample &sample : message.samples) { + sample.mono_time -= time_offset; + } + } + for (TimelineEntry &entry : timeline) { + entry.start_time -= time_offset; + entry.end_time -= time_offset; + } + route_data.x_max -= time_offset; + route_data.x_min = 0.0; + } + + std::sort(timeline.begin(), timeline.end(), [](const TimelineEntry &a, const TimelineEntry &b) { + return a.start_time < b.start_time; + }); + std::vector merged_timeline; + merged_timeline.reserve(timeline.size()); + for (TimelineEntry &entry : timeline) { + if (!merged_timeline.empty() && merged_timeline.back().type == entry.type) { + merged_timeline.back().end_time = std::max(merged_timeline.back().end_time, entry.end_time); + continue; + } + merged_timeline.push_back(std::move(entry)); + } + route_data.timeline = std::move(merged_timeline); + std::sort(can_messages.begin(), can_messages.end(), [](const CanMessageData &a, const CanMessageData &b) { + return std::make_tuple(a.id.service, a.id.bus, a.id.address) + < std::make_tuple(b.id.service, b.id.bus, b.id.address); + }); + route_data.can_messages = std::move(can_messages); + + route_data.enum_info = std::move(enum_info); + route_data.car_fingerprint = std::move(car_fingerprint); + route_data.dbc_name = std::move(dbc_name); + rebuild_gps_trace(&route_data); + route_data.roots = collect_route_roots_for_paths(route_data.paths); + return route_data; +} + +const RouteSeries *find_route_series(const RouteData &route_data, std::string_view path) { + auto it = std::lower_bound(route_data.series.begin(), route_data.series.end(), path, + [](const RouteSeries &series, std::string_view target) { + return series.path < target; + }); + if (it == route_data.series.end() || it->path != path) return nullptr; + return &(*it); +} + +std::optional sample_series_at_time(const RouteSeries &series, double tm) { + if (series.times.empty() || series.times.size() != series.values.size()) { + return std::nullopt; + } + if (tm <= series.times.front()) { + return series.values.front(); + } + if (tm >= series.times.back()) { + return series.values.back(); + } + auto upper = std::lower_bound(series.times.begin(), series.times.end(), tm); + if (upper == series.times.begin()) { + return series.values.front(); + } + if (upper == series.times.end()) { + return series.values.back(); + } + const size_t upper_index = static_cast(std::distance(series.times.begin(), upper)); + const size_t lower_index = upper_index - 1; + const double t0 = series.times[lower_index]; + const double t1 = series.times[upper_index]; + const double v0 = series.values[lower_index]; + const double v1 = series.values[upper_index]; + if (t1 <= t0) { + return v0; + } + const double alpha = (tm - t0) / (t1 - t0); + return v0 + (v1 - v0) * alpha; +} + +} // namespace + +void rebuild_gps_trace(RouteData *route_data) { + route_data->gps_trace = {}; + const RouteSeries *latitude = find_route_series(*route_data, "/gpsLocationExternal/latitude"); + const RouteSeries *longitude = find_route_series(*route_data, "/gpsLocationExternal/longitude"); + const RouteSeries *has_fix = find_route_series(*route_data, "/gpsLocationExternal/hasFix"); + if (latitude == nullptr || longitude == nullptr || has_fix == nullptr) { + return; + } + + const RouteSeries *bearing = find_route_series(*route_data, "/gpsLocationExternal/bearingDeg"); + size_t count = std::min({latitude->times.size(), latitude->values.size(), + longitude->times.size(), longitude->values.size(), + has_fix->times.size(), has_fix->values.size()}); + if (count == 0) { + return; + } + + bool found = false; + route_data->gps_trace.points.reserve(count); + for (size_t i = 0; i < count; ++i) { + if (has_fix->values[i] < 0.5) { + continue; + } + const double lat = latitude->values[i]; + const double lon = longitude->values[i]; + if (!std::isfinite(lat) || !std::isfinite(lon)) { + continue; + } + const double tm = latitude->times[i]; + const float bearing_value = bearing != nullptr + ? static_cast(sample_series_at_time(*bearing, tm).value_or(0.0)) + : 0.0f; + route_data->gps_trace.points.push_back(GpsPoint{ + .time = tm, + .lat = lat, + .lon = lon, + .bearing = bearing_value, + .type = timeline_type_at_time(route_data->timeline, tm), + }); + if (!found) { + route_data->gps_trace.min_lat = route_data->gps_trace.max_lat = lat; + route_data->gps_trace.min_lon = route_data->gps_trace.max_lon = lon; + found = true; + } else { + route_data->gps_trace.min_lat = std::min(route_data->gps_trace.min_lat, lat); + route_data->gps_trace.max_lat = std::max(route_data->gps_trace.max_lat, lat); + route_data->gps_trace.min_lon = std::min(route_data->gps_trace.min_lon, lon); + route_data->gps_trace.max_lon = std::max(route_data->gps_trace.max_lon, lon); + } + } + if (!found) { + route_data->gps_trace = {}; + } +} + +namespace { + +void build_camera_index(const std::map &segments, + const RouteData &route_data, + std::string SegmentLogs::*file_member, + std::string_view index_name, + CameraFeedIndex *out) { + *out = {}; + out->segment_files.reserve(segments.size()); + + std::unordered_set available_segments; + available_segments.reserve(segments.size()); + for (const auto &[segment_number, segment] : segments) { + const std::string &path = segment.*file_member; + if (path.empty()) continue; + out->segment_files.push_back(CameraSegmentFile{ + .segment = segment_number, + .path = path, + }); + available_segments.insert(segment_number); + } + if (out->segment_files.empty()) { + return; + } + + const std::string prefix = "/" + std::string(index_name); + const RouteSeries *segment_numbers = find_route_series(route_data, prefix + "/segmentNum"); + const RouteSeries *decode_indices = find_route_series(route_data, prefix + "/segmentId"); + if (decode_indices == nullptr) { + decode_indices = find_route_series(route_data, prefix + "/segmentIdEncode"); + } + const RouteSeries *frame_ids = find_route_series(route_data, prefix + "/frameId"); + if (segment_numbers == nullptr || decode_indices == nullptr) { + return; + } + + size_t count = std::min(segment_numbers->times.size(), segment_numbers->values.size()); + count = std::min(count, decode_indices->values.size()); + if (frame_ids != nullptr) { + count = std::min(count, frame_ids->values.size()); + } + out->entries.reserve(count); + for (size_t i = 0; i < count; ++i) { + const int segment_number = static_cast(std::llround(segment_numbers->values[i])); + if (available_segments.find(segment_number) == available_segments.end()) { + continue; + } + const int decode_index = static_cast(std::llround(decode_indices->values[i])); + const uint32_t frame_id = frame_ids != nullptr + ? static_cast(std::llround(frame_ids->values[i])) + : static_cast(std::max(0, decode_index)); + out->entries.push_back(CameraFrameIndexEntry{ + .timestamp = segment_numbers->times[i], + .segment = segment_number, + .decode_index = decode_index, + .frame_id = frame_id, + }); + } + + std::sort(out->entries.begin(), out->entries.end(), + [](const CameraFrameIndexEntry &a, const CameraFrameIndexEntry &b) { + return a.timestamp < b.timestamp; + }); +} + +size_t load_worker_budget() { + size_t worker_count = std::thread::hardware_concurrency(); + if (worker_count == 0) { + worker_count = 1; + } + if (const char *raw = std::getenv("JOTP_LOAD_WORKERS"); raw != nullptr && std::strlen(raw) > 0) { + char *end = nullptr; + const unsigned long parsed = std::strtoul(raw, &end, 10); + if (end != nullptr && *end == '\0' && parsed > 0) { + worker_count = static_cast(parsed); + } + } + return std::max(1, worker_count); +} + +size_t segment_worker_count(size_t segment_count, size_t worker_budget) { + return std::max(1, std::min(worker_budget, segment_count)); +} + +size_t extract_chunk_count(size_t event_count, size_t worker_budget, size_t segment_workers) { + if (event_count < 4096) return 1; + const size_t per_segment_budget = std::max(1, worker_budget / std::max(1, segment_workers)); + const size_t chunk_target = std::max(1, (event_count + 14999) / 15000); + return std::max(1, std::min({per_segment_budget, chunk_target, static_cast(8)})); +} + +SeriesAccumulator extract_segment_series(const std::vector &events, + const SchemaIndex &schema, + const dbc::Database *can_dbc, + bool skip_raw_can, + size_t worker_budget, + size_t segment_workers) { + const size_t chunk_count = extract_chunk_count(events.size(), worker_budget, segment_workers); + if (chunk_count <= 1 || events.empty()) { + SeriesAccumulator series = make_series_accumulator(schema); + append_events_fast_range(events, 0, events.size(), schema, can_dbc, skip_raw_can, &series); + return series; + } + + const size_t events_per_chunk = (events.size() + chunk_count - 1) / chunk_count; + std::vector chunk_results; + chunk_results.reserve(chunk_count); + for (size_t i = 0; i < chunk_count; ++i) { + chunk_results.push_back(make_series_accumulator(schema)); + } + + std::vector workers; + workers.reserve(chunk_count > 0 ? chunk_count - 1 : 0); + for (size_t chunk = 1; chunk < chunk_count; ++chunk) { + workers.emplace_back([&, chunk]() { + const size_t begin = chunk * events_per_chunk; + const size_t end = std::min(events.size(), begin + events_per_chunk); + append_events_fast_range(events, begin, end, schema, can_dbc, skip_raw_can, &chunk_results[chunk]); + }); + } + append_events_fast_range(events, 0, std::min(events.size(), events_per_chunk), schema, can_dbc, skip_raw_can, &chunk_results[0]); + for (std::thread &worker : workers) { + worker.join(); + } + + SeriesAccumulator merged = make_series_accumulator(schema); + for (SeriesAccumulator &chunk : chunk_results) { + merge_series_accumulator(&merged, &chunk); + } + return merged; +} + +LoadedRouteArtifacts load_route_series_parallel( + const std::map &segments, + const SchemaIndex &schema, + const dbc::Database *can_dbc, + LogSelector selector, + bool skip_raw_can, + LoadStats *stats) { + struct SegmentResult { + SeriesAccumulator series; + std::vector logs; + std::vector timeline; + }; + + const std::vector> segment_list(segments.begin(), segments.end()); + std::vector results; + results.reserve(segment_list.size()); + for (size_t i = 0; i < segment_list.size(); ++i) { + results.emplace_back(SegmentResult{make_series_accumulator(schema)}); + } + std::atomic next_segment{0}; + std::mutex error_mutex; + std::string first_error; + const size_t worker_budget = static_cast(stats->num_workers); + const size_t segment_workers = segment_worker_count(segment_list.size(), worker_budget); + + auto worker = [&]() { + while (true) { + const size_t index = next_segment.fetch_add(1); + if (index >= segment_list.size()) { + return; + } + + const auto &[segment_number, segment] = segment_list[index]; + const std::string &log_path = selected_log_path(segment, selector); + LoadStats::SegmentStats &segment_stats = stats->segments[index]; + segment_stats.segment_number = segment_number; + segment_stats.log_path = log_path; + if (log_path.empty()) { + segment_stats.failed = true; + std::lock_guard lock(error_mutex); + if (first_error.empty()) { + first_error = "Missing log path for segment " + std::to_string(segment_number); + } + stats->publish(RouteLoadStage::DownloadingSegment, index, std::to_string(segment_number)); + stats->publish(RouteLoadStage::ParsingSegment, index, std::to_string(segment_number)); + continue; + } + + LogReader reader; + if (!reader.load(log_path, nullptr, true)) { + segment_stats.failed = true; + std::lock_guard lock(error_mutex); + if (first_error.empty()) { + first_error = "Failed to load log segment: " + log_path; + } + stats->publish(RouteLoadStage::DownloadingSegment, index, std::to_string(segment_number)); + stats->publish(RouteLoadStage::ParsingSegment, index, std::to_string(segment_number)); + continue; + } + + segment_stats.download_seconds = reader.download_seconds(); + segment_stats.decompress_seconds = reader.decompress_seconds(); + segment_stats.parse_seconds = reader.parse_seconds(); + segment_stats.compressed_bytes = reader.compressed_size(); + segment_stats.decompressed_bytes = reader.decompressed_size(); + stats->bytes_downloaded.fetch_add(reader.compressed_size()); + stats->segments_downloaded.fetch_add(1); + stats->publish(RouteLoadStage::DownloadingSegment, index, std::to_string(segment_number)); + + const auto extract_start = LoadStats::Clock::now(); + results[index].series = extract_segment_series(reader.events, schema, can_dbc, skip_raw_can, worker_budget, segment_workers); + results[index].logs = extract_segment_logs(reader.events); + results[index].timeline = extract_segment_timeline(reader.events); + segment_stats.extract_seconds = std::chrono::duration(LoadStats::Clock::now() - extract_start).count(); + segment_stats.event_count = reader.events.size(); + segment_stats.series_count = populated_series_count(results[index].series); + stats->segments_parsed.fetch_add(1); + stats->publish(RouteLoadStage::ParsingSegment, index, std::to_string(segment_number)); + } + }; + + std::vector workers; + workers.reserve(segment_workers); + for (size_t i = 0; i < segment_workers; ++i) { + workers.emplace_back(worker); + } + for (std::thread &thread : workers) { + thread.join(); + } + + if (!first_error.empty()) throw std::runtime_error(first_error); + + stats->merge_start = LoadStats::Clock::now(); + SeriesAccumulator merged = make_series_accumulator(schema); + for (size_t i = 0; i < results.size(); ++i) { + merge_series_accumulator(&merged, &results[i].series); + } + std::vector logs; + std::vector timeline; + for (SegmentResult &result : results) { + if (!result.logs.empty()) { + logs.insert(logs.end(), + std::make_move_iterator(result.logs.begin()), + std::make_move_iterator(result.logs.end())); + } + if (!result.timeline.empty()) { + timeline.insert(timeline.end(), + std::make_move_iterator(result.timeline.begin()), + std::make_move_iterator(result.timeline.end())); + } + } + LoadedRouteArtifacts artifacts; + artifacts.series = collect_series(std::move(merged)); + artifacts.can_messages = std::move(merged.can_messages); + artifacts.logs = std::move(logs); + artifacts.timeline = std::move(timeline); + artifacts.enum_info = std::move(merged.enum_info); + stats->merge_end = LoadStats::Clock::now(); + return artifacts; +} + +std::vector collect_layout_roots(const SketchLayout &layout) { + std::vector roots; + for (const auto &tab : layout.tabs) { + for (const auto &pane : tab.panes) { + for (const auto &curve : pane.curves) { + std::string root = "custom"; + if (is_absolute_curve(curve.name)) { + const size_t slash = curve.name.find('/', 1); + root = curve.name.substr(1, slash == std::string::npos ? std::string::npos : slash - 1); + } + if (std::find(roots.begin(), roots.end(), root) == roots.end()) { + roots.push_back(root); + } + } + } + } + if (roots.empty()) { + roots.push_back("layout"); + } + return roots; +} + +} // namespace + +std::vector collect_route_roots_for_paths(const std::vector &paths) { + std::vector roots; + for (const std::string &path : paths) { + if (!is_absolute_curve(path)) continue; + const size_t slash = path.find('/', 1); + const std::string root = path.substr(1, slash == std::string::npos ? std::string::npos : slash - 1); + if (!root.empty() && std::find(roots.begin(), roots.end(), root) == roots.end()) { + roots.push_back(root); + } + } + std::sort(roots.begin(), roots.end()); + return roots; +} + +struct StreamAccumulator::Impl { + const SchemaIndex &schema = SchemaIndex::instance(); + SeriesAccumulator series = make_series_accumulator(schema); + std::vector logs; + std::vector timeline; + std::string last_alert_key; + std::string manual_dbc_name; + std::string detected_dbc_name; + std::string car_fingerprint; + std::optional can_dbc; + std::optional time_offset; + + void refresh_dbc() { + const std::string next_dbc = !manual_dbc_name.empty() ? manual_dbc_name : detect_dbc_for_fingerprint(car_fingerprint); + if (next_dbc == detected_dbc_name) { + return; + } + detected_dbc_name = next_dbc; + can_dbc.reset(); + if (!detected_dbc_name.empty()) { + can_dbc.emplace(resolve_dbc_path(detected_dbc_name)); + } + } +}; + +StreamAccumulator::StreamAccumulator(const std::string &dbc_name, std::optional time_offset) + : impl_(std::make_unique()) { + impl_->manual_dbc_name = dbc_name; + impl_->time_offset = time_offset; + impl_->refresh_dbc(); +} + +StreamAccumulator::~StreamAccumulator() = default; + +void StreamAccumulator::setDbcName(const std::string &dbc_name) { + impl_->manual_dbc_name = dbc_name; + impl_->refresh_dbc(); +} + +void StreamAccumulator::appendEvent(cereal::Event::Which which, kj::ArrayPtr data) { + capnp::FlatArrayMessageReader event_reader(data); + const cereal::Event::Reader event = event_reader.getRoot(); + const double boot_time = static_cast(event.getLogMonoTime()) / 1.0e9; + if (!impl_->time_offset.has_value()) { + impl_->time_offset = boot_time; + } + if (which == cereal::Event::Which::CAR_PARAMS) { + const std::string fingerprint = event.getCarParams().getCarFingerprint().cStr(); + if (!fingerprint.empty() && fingerprint != impl_->car_fingerprint) { + impl_->car_fingerprint = fingerprint; + impl_->refresh_dbc(); + } + } + + append_event_fast(which, + -1, + data, + impl_->schema, + impl_->can_dbc ? &*impl_->can_dbc : nullptr, + true, + *impl_->time_offset, + &impl_->series); + append_log_event(which, event, *impl_->time_offset, &impl_->logs, &impl_->last_alert_key); + if (which == cereal::Event::Which::SELFDRIVE_STATE) { + const auto sd = event.getSelfdriveState(); + append_timeline_entry(&impl_->timeline, boot_time - *impl_->time_offset, + alert_status_to_timeline_type(sd.getAlertStatus(), sd.getEnabled())); + } +} + +void StreamAccumulator::appendCanFrames(CanServiceKind service, const std::vector &frames) { + if (frames.empty()) { + return; + } + if (!impl_->time_offset.has_value()) { + impl_->time_offset = frames.front().mono_time; + } + for (const LiveCanFrame &frame : frames) { + append_live_can_frame(service, + frame, + *impl_->time_offset, + impl_->can_dbc ? &*impl_->can_dbc : nullptr, + &impl_->series); + } +} + +StreamExtractBatch StreamAccumulator::takeBatch() { + StreamExtractBatch batch; + batch.car_fingerprint = impl_->car_fingerprint; + batch.dbc_name = impl_->detected_dbc_name; + if (impl_->time_offset.has_value()) { + batch.has_time_offset = true; + batch.time_offset = *impl_->time_offset; + } + if (impl_->logs.empty() && impl_->timeline.empty() + && populated_series_count(impl_->series) == 0 + && impl_->series.enum_info.empty() + && impl_->series.can_messages.empty()) { + return batch; + } + + SeriesAccumulator emitted = std::move(impl_->series); + batch.can_messages = std::move(emitted.can_messages); + batch.enum_info = std::move(emitted.enum_info); + batch.series = collect_series(std::move(emitted)); + batch.logs = std::move(impl_->logs); + batch.timeline = std::move(impl_->timeline); + impl_->series = make_series_accumulator(impl_->schema); + impl_->logs.clear(); + impl_->timeline.clear(); + return batch; +} + +const std::string &StreamAccumulator::carFingerprint() const { + return impl_->car_fingerprint; +} + +const std::string &StreamAccumulator::dbc_name() const { + return impl_->detected_dbc_name; +} + +std::optional StreamAccumulator::timeOffset() const { + return impl_->time_offset; +} + +SketchLayout load_sketch_layout(const fs::path &layout_path) { + SketchLayout layout = parse_layout(layout_path); + layout.roots = collect_layout_roots(layout); + return layout; +} + +RouteData load_route_data(const std::string &route_name, + const std::string &data_dir, + const std::string &dbc_name, + const RouteLoadProgressCallback &progress) { + if (route_name.empty()) return RouteData{}; + + const RouteSelection route = parse_route_selection(route_name); + if (route.canonical_name.empty() || (data_dir.empty() && route.dongle_id.empty())) { + throw std::runtime_error("Invalid route format: " + route_name); + } + LoadStats stats(progress); + stats.load_start = LoadStats::Clock::now(); + std::map segments = data_dir.empty() + ? load_segments_from_server(route) + : load_segments_from_local(route, data_dir); + segments = trim_segments(std::move(segments), route); + if (segments.empty()) throw std::runtime_error("No log segments found for " + route_name); + stats.resolve_end = LoadStats::Clock::now(); + stats.segment_count = segments.size(); + stats.total_segments.store(segments.size()); + stats.num_workers = static_cast(load_worker_budget()); + stats.segments.resize(segments.size()); + stats.publish(RouteLoadStage::Resolving, 0, {}); + + const RouteMetadata metadata = detect_route_metadata(segments, route.selector); + const std::string resolved_dbc = !dbc_name.empty() ? dbc_name : detect_dbc_for_fingerprint(metadata.car_fingerprint); + const std::optional can_dbc = resolved_dbc.empty() + ? std::nullopt + : std::optional(std::in_place, resolve_dbc_path(resolved_dbc)); + + const SchemaIndex &schema = SchemaIndex::instance(); + LoadedRouteArtifacts artifacts = load_route_series_parallel(segments, schema, can_dbc ? &*can_dbc : nullptr, + route.selector, !resolved_dbc.empty(), &stats); + RouteData route_data = build_route_data(std::move(artifacts.series), + std::move(artifacts.can_messages), + std::move(artifacts.logs), + std::move(artifacts.timeline), + std::move(artifacts.enum_info), + metadata.car_fingerprint, + resolved_dbc); + route_data.route_id = make_route_identifier(route, segments); + build_camera_index(segments, route_data, &SegmentLogs::fcamera, "roadEncodeIdx", &route_data.road_camera); + build_camera_index(segments, route_data, &SegmentLogs::dcamera, "driverEncodeIdx", &route_data.driver_camera); + build_camera_index(segments, route_data, &SegmentLogs::ecamera, "wideRoadEncodeIdx", &route_data.wide_road_camera); + build_camera_index(segments, route_data, &SegmentLogs::qcamera, "qRoadEncodeIdx", &route_data.qroad_camera); + stats.load_end = LoadStats::Clock::now(); + stats.publish(RouteLoadStage::Finished, segments.size(), {}); + stats.print_summary(route_data.series.size()); + return route_data; +} + +RouteIdentifier parse_route_identifier(std::string_view route_name) { + return make_route_identifier(parse_route_selection(std::string(route_name)), {}); +} + +std::vector available_dbc_names() { + return available_dbc_names_impl(); +} + +std::optional load_dbc_by_name(const std::string &dbc_name) { + if (dbc_name.empty()) { + return std::nullopt; + } + try { + return std::optional(std::in_place, resolve_dbc_path(dbc_name)); + } catch (...) { + return std::nullopt; + } +} + +std::vector decode_can_messages(const std::vector &can_messages, + const std::string &dbc_name, + std::unordered_map *enum_info) { + if (enum_info != nullptr) { + enum_info->clear(); + } + const std::optional can_dbc = load_dbc_by_name(dbc_name); + if (!can_dbc.has_value()) { + return {}; + } + + SeriesAccumulator series; + for (const CanMessageData &message : can_messages) { + const char *service_name = message.id.service == CanServiceKind::Can ? "can" : "sendcan"; + for (const CanFrameSample &sample : message.samples) { + decode_can_frame(&*can_dbc, + service_name, + message.id.bus, + message.id.address, + reinterpret_cast(sample.data.data()), + sample.data.size(), + sample.mono_time, + &series); + } + } + if (enum_info != nullptr) { + *enum_info = std::move(series.enum_info); + } + return collect_series(std::move(series)); +} diff --git a/tools/jotpluggler/stream.cc b/tools/jotpluggler/stream.cc new file mode 100644 index 00000000000..fcfa6585bbf --- /dev/null +++ b/tools/jotpluggler/stream.cc @@ -0,0 +1,207 @@ +#include "tools/jotpluggler/internal.h" + +#include + +template +std::optional stream_batch_extreme_time(const StreamExtractBatch &batch, + Cmp cmp, + SeriesAccessor series_time, + LogAccessor log_time_fn) { + std::optional result; + for (const RouteSeries &series : batch.series) { + if (!series.times.empty()) { + const double t = series_time(series); + result = result.has_value() ? cmp(*result, t) : t; + } + } + if (!batch.logs.empty()) { + const double t = log_time_fn(batch); + result = result.has_value() ? cmp(*result, t) : t; + } + if (!batch.timeline.empty()) { + const double t = cmp(batch.timeline.front().start_time, batch.timeline.back().end_time); + result = result.has_value() ? cmp(*result, t) : t; + } + for (const CanMessageData &message : batch.can_messages) { + if (!message.samples.empty()) { + const double t = cmp(message.samples.front().mono_time, message.samples.back().mono_time); + result = result.has_value() ? cmp(*result, t) : t; + } + } + return result; +} + +std::optional earliest_stream_batch_time(const StreamExtractBatch &batch) { + return stream_batch_extreme_time(batch, + [](double a, double b) { return std::min(a, b); }, + [](const RouteSeries &s) { return s.times.front(); }, + [](const StreamExtractBatch &b) { return b.logs.front().mono_time; }); +} + +std::optional latest_stream_batch_time(const StreamExtractBatch &batch) { + return stream_batch_extreme_time(batch, + [](double a, double b) { return std::max(a, b); }, + [](const RouteSeries &s) { return s.times.back(); }, + [](const StreamExtractBatch &b) { return b.logs.back().mono_time; }); +} + +bool layout_has_custom_curves(const SketchLayout &layout) { + for (const WorkspaceTab &tab : layout.tabs) { + for (const Pane &pane : tab.panes) { + for (const Curve &curve : pane.curves) { + if (curve.custom_python.has_value()) return true; + } + } + } + return false; +} + +void append_stream_timeline_entries(std::vector *timeline, std::vector entries) { + for (TimelineEntry &entry : entries) { + if (!timeline->empty() && timeline->back().type == entry.type) { + timeline->back().end_time = std::max(timeline->back().end_time, entry.end_time); + } else { + timeline->push_back(std::move(entry)); + } + } +} + +bool can_message_less(const CanMessageData &a, const CanMessageData &b) { + return std::make_tuple(a.id.service, a.id.bus, a.id.address) + < std::make_tuple(b.id.service, b.id.bus, b.id.address); +} + +void apply_stream_batch(AppSession *session, UiState *state, StreamExtractBatch batch) { + if (batch.has_time_offset) { + session->stream_time_offset = batch.time_offset; + } + if (!batch.car_fingerprint.empty()) { + session->route_data.car_fingerprint = batch.car_fingerprint; + } + if (!batch.dbc_name.empty()) { + session->route_data.dbc_name = batch.dbc_name; + } + if (!batch.enum_info.empty()) { + for (auto &[path, info] : batch.enum_info) { + session->route_data.enum_info[path] = std::move(info); + } + } + + bool new_paths = false; + std::vector new_series; + std::vector touched_paths; + touched_paths.reserve(batch.series.size()); + for (RouteSeries &incoming : batch.series) { + touched_paths.push_back(incoming.path); + auto existing_it = session->series_by_path.find(incoming.path); + if (existing_it == session->series_by_path.end()) { + new_series.push_back(std::move(incoming)); + new_paths = true; + continue; + } + RouteSeries &existing = *existing_it->second; + existing.times.insert(existing.times.end(), incoming.times.begin(), incoming.times.end()); + existing.values.insert(existing.values.end(), incoming.values.begin(), incoming.values.end()); + } + for (RouteSeries &series : new_series) { + session->route_data.paths.push_back(series.path); + session->route_data.series.push_back(std::move(series)); + } + + if (!batch.logs.empty()) { + std::sort(batch.logs.begin(), batch.logs.end(), [](const LogEntry &a, const LogEntry &b) { + return a.mono_time < b.mono_time; + }); + const size_t old_size = session->route_data.logs.size(); + session->route_data.logs.insert(session->route_data.logs.end(), + std::make_move_iterator(batch.logs.begin()), + std::make_move_iterator(batch.logs.end())); + if (old_size > 0 && session->route_data.logs.size() > old_size + && session->route_data.logs[old_size - 1].mono_time > session->route_data.logs[old_size].mono_time) { + std::inplace_merge(session->route_data.logs.begin(), + session->route_data.logs.begin() + static_cast(old_size), + session->route_data.logs.end(), + [](const LogEntry &a, const LogEntry &b) { + return a.mono_time < b.mono_time; + }); + } + } + if (!batch.timeline.empty()) { + append_stream_timeline_entries(&session->route_data.timeline, std::move(batch.timeline)); + } + + for (CanMessageData &incoming : batch.can_messages) { + auto it = std::lower_bound(session->route_data.can_messages.begin(), + session->route_data.can_messages.end(), + incoming, + can_message_less); + if (it == session->route_data.can_messages.end() + || can_message_less(incoming, *it) + || can_message_less(*it, incoming)) { + session->route_data.can_messages.insert(it, std::move(incoming)); + } else { + it->samples.insert(it->samples.end(), + std::make_move_iterator(incoming.samples.begin()), + std::make_move_iterator(incoming.samples.end())); + } + } + + if (new_paths) { + const size_t old_path_count = session->route_data.paths.size() - new_series.size(); + std::sort(session->route_data.paths.begin() + static_cast(old_path_count), session->route_data.paths.end()); + std::inplace_merge(session->route_data.paths.begin(), + session->route_data.paths.begin() + static_cast(old_path_count), + session->route_data.paths.end()); + const size_t old_series_count = session->route_data.series.size() - new_series.size(); + auto series_cmp = [](const RouteSeries &a, const RouteSeries &b) { return a.path < b.path; }; + std::sort(session->route_data.series.begin() + static_cast(old_series_count), + session->route_data.series.end(), series_cmp); + std::inplace_merge(session->route_data.series.begin(), + session->route_data.series.begin() + static_cast(old_series_count), + session->route_data.series.end(), series_cmp); + session->route_data.roots = collect_route_roots_for_paths(session->route_data.paths); + rebuild_route_index(session); + rebuild_browser_nodes(session, state); + state->browser_nodes_dirty = false; + } else { + for (const std::string &path : touched_paths) { + auto series_it = session->series_by_path.find(path); + if (series_it == session->series_by_path.end() || series_it->second == nullptr) continue; + const bool enum_like = session->route_data.enum_info.find(path) != session->route_data.enum_info.end(); + session->route_data.series_formats[path] = compute_series_format(series_it->second->values, enum_like); + } + } + const std::optional earliest_time = earliest_stream_batch_time(batch); + const std::optional latest_time = latest_stream_batch_time(batch); + if (earliest_time.has_value() && latest_time.has_value()) { + if (!session->route_data.has_time_range) { + session->route_data.x_min = *earliest_time; + session->route_data.x_max = *latest_time; + } else { + session->route_data.x_min = std::min(session->route_data.x_min, *earliest_time); + session->route_data.x_max = std::max(session->route_data.x_max, *latest_time); + } + session->route_data.has_time_range = true; + } + + if (new_paths + || std::find(touched_paths.begin(), touched_paths.end(), "/gpsLocationExternal/latitude") != touched_paths.end() + || std::find(touched_paths.begin(), touched_paths.end(), "/gpsLocationExternal/longitude") != touched_paths.end() + || std::find(touched_paths.begin(), touched_paths.end(), "/gpsLocationExternal/hasFix") != touched_paths.end() + || std::find(touched_paths.begin(), touched_paths.end(), "/gpsLocationExternal/bearingDeg") != touched_paths.end()) { + rebuild_gps_trace(&session->route_data); + } + + if (latest_time.has_value() && layout_has_custom_curves(session->layout) + && *latest_time >= session->next_stream_custom_refresh_time) { + refresh_all_custom_curves(session, state); + session->next_stream_custom_refresh_time = *latest_time + 0.1; + } + if (state->follow_latest || !state->has_tracker_time) { + state->tracker_time = session->route_data.x_max; + state->has_tracker_time = session->route_data.has_time_range; + } + if (!state->has_shared_range) { + reset_shared_range(state, *session); + } +} diff --git a/tools/jotpluggler/util.cc b/tools/jotpluggler/util.cc new file mode 100644 index 00000000000..5c20e795f6b --- /dev/null +++ b/tools/jotpluggler/util.cc @@ -0,0 +1,59 @@ +#include "tools/jotpluggler/util.h" + +#include +#include +#include +#include + +std::string read_file_or_throw(const std::filesystem::path &path) { + const std::string contents = util::read_file(path.string()); + if (!contents.empty() || std::filesystem::exists(path)) { + return contents; + } + throw std::runtime_error("Failed to read " + path.string()); +} + +void write_file_or_throw(const std::filesystem::path &path, const void *data, size_t size) { + ensure_parent_dir(path); + const std::string path_string = path.string(); + const void *bytes = size == 0 ? static_cast("") : data; + if (util::write_file(path_string.c_str(), bytes, size, O_WRONLY | O_CREAT | O_TRUNC) != 0) { + throw std::runtime_error("Failed to write " + path_string); + } +} + +void write_file_or_throw(const std::filesystem::path &path, std::string_view contents) { + write_file_or_throw(path, contents.data(), contents.size()); +} + +void run_system_or_throw(const std::string &command, std::string_view action) { + const int ret = std::system(command.c_str()); + if (ret != 0) { + throw std::runtime_error(util::string_format("%.*s failed with exit code %d", + static_cast(action.size()), action.data(), ret)); + } +} + +CommandResult run_process_capture_output(const std::vector &args) { + std::string command; + for (const std::string &arg : args) { + if (!command.empty()) command += ' '; + command += shell_quote(arg); + } + command += " 2>&1"; + + FILE *pipe = popen(command.c_str(), "r"); + if (pipe == nullptr) { + throw std::runtime_error("popen() failed"); + } + + CommandResult result; + std::array buf = {}; + while (fgets(buf.data(), static_cast(buf.size()), pipe) != nullptr) { + result.output += buf.data(); + } + + const int status = pclose(pipe); + result.exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : 1; + return result; +} diff --git a/tools/jotpluggler/util.h b/tools/jotpluggler/util.h new file mode 100644 index 00000000000..ea77a236f0e --- /dev/null +++ b/tools/jotpluggler/util.h @@ -0,0 +1,103 @@ +#pragma once + +#include "common/util.h" +#include "imgui.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +inline ImVec4 color_rgb(int r, int g, int b, float alpha = 1.0f) { + return ImVec4(static_cast(r) / 255.0f, + static_cast(g) / 255.0f, + static_cast(b) / 255.0f, + alpha); +} + +inline ImVec4 color_rgb(const std::array &color, float alpha = 1.0f) { + return color_rgb(color[0], color[1], color[2], alpha); +} + +inline std::string lowercase_copy(std::string_view value) { + std::string out(value); + std::transform(out.begin(), out.end(), out.begin(), [](unsigned char c) { + return static_cast(std::tolower(c)); + }); + return out; +} + +inline int imgui_resize_callback(ImGuiInputTextCallbackData *data) { + if (data->EventFlag != ImGuiInputTextFlags_CallbackResize || data->UserData == nullptr) return 0; + auto *text = static_cast(data->UserData); + text->resize(static_cast(data->BufTextLen)); + data->Buf = text->data(); + return 0; +} + +inline bool input_text_string(const char *label, + std::string *text, + ImGuiInputTextFlags flags = 0) { + flags |= ImGuiInputTextFlags_CallbackResize; + return ImGui::InputText(label, text->data(), text->capacity() + 1, + flags, imgui_resize_callback, text); +} + +inline bool input_text_with_hint_string(const char *label, + const char *hint, + std::string *text, + ImGuiInputTextFlags flags = 0) { + flags |= ImGuiInputTextFlags_CallbackResize; + return ImGui::InputTextWithHint(label, hint, text->data(), text->capacity() + 1, + flags, imgui_resize_callback, text); +} + +inline bool input_text_multiline_string(const char *label, + std::string *text, + const ImVec2 &size = ImVec2(0.0f, 0.0f), + ImGuiInputTextFlags flags = 0) { + flags |= ImGuiInputTextFlags_CallbackResize; + return ImGui::InputTextMultiline(label, text->data(), text->capacity() + 1, + size, flags, imgui_resize_callback, text); +} + +inline bool is_local_stream_address(std::string_view address) { + return address.empty() || address == "127.0.0.1" || address == "localhost"; +} + +inline void ensure_parent_dir(const std::filesystem::path &path) { + if (path.has_parent_path()) { + std::filesystem::create_directories(path.parent_path()); + } +} + +inline std::string shell_quote(std::string_view value) { + std::string quoted; + quoted.reserve(value.size() + 8); + quoted.push_back('\''); + for (char c : value) { + if (c == '\'') { + quoted += "'\\''"; + } else { + quoted.push_back(c); + } + } + quoted.push_back('\''); + return quoted; +} + +struct CommandResult { + int exit_code = 0; + std::string output; +}; + +std::string read_file_or_throw(const std::filesystem::path &path); +void write_file_or_throw(const std::filesystem::path &path, std::string_view contents); +void write_file_or_throw(const std::filesystem::path &path, const void *data, size_t size); +void run_system_or_throw(const std::string &command, std::string_view action); +CommandResult run_process_capture_output(const std::vector &args); diff --git a/tools/replay/logreader.cc b/tools/replay/logreader.cc index 997a4bc00f0..aba67bcdf8c 100644 --- a/tools/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -1,31 +1,68 @@ #include "tools/replay/logreader.h" #include +#include #include #include "tools/replay/filereader.h" +#include "tools/replay/py_downloader.h" #include "tools/replay/util.h" #include "common/util.h" -bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache) { +bool LogReader::load(const std::string &url, std::atomic *abort, bool local_cache, + const ProgressCallback &progress) { + using Clock = std::chrono::steady_clock; + compressed_size_ = 0; + decompressed_size_ = 0; + download_seconds_ = 0.0; + decompress_seconds_ = 0.0; + parse_seconds_ = 0.0; + + if (progress) { + installDownloadProgressHandler([progress](uint64_t cur, uint64_t total, bool success) { + if (success) { + progress(ProgressStage::Downloading, cur, total); + } + }); + } + const auto download_start = Clock::now(); std::string data = FileReader(local_cache).read(url, abort); + const auto download_end = Clock::now(); + if (progress) { + installDownloadProgressHandler(nullptr); + } + compressed_size_ = data.size(); + download_seconds_ = std::chrono::duration(download_end - download_start).count(); if (!data.empty()) { + const auto decompress_start = Clock::now(); if (url.find(".bz2") != std::string::npos || util::starts_with(data, "BZh9")) { data = decompressBZ2(data, abort); } else if (url.find(".zst") != std::string::npos || util::starts_with(data, "\x28\xB5\x2F\xFD")) { data = decompressZST(data, abort); } + const auto decompress_end = Clock::now(); + decompress_seconds_ = std::chrono::duration(decompress_end - decompress_start).count(); } + decompressed_size_ = data.size(); - bool success = !data.empty() && load(data.data(), data.size(), abort); + bool success = !data.empty() && load(data.data(), data.size(), abort, progress); if (filters_.empty()) raw_ = std::move(data); return success; } -bool LogReader::load(const char *data, size_t size, std::atomic *abort) { +bool LogReader::load(const char *data, size_t size, std::atomic *abort, + const ProgressCallback &progress) { + using Clock = std::chrono::steady_clock; + const auto parse_start = Clock::now(); try { events.reserve(65000); kj::ArrayPtr words((const capnp::word *)data, size / sizeof(capnp::word)); + const uint64_t total_bytes = size; + const uint64_t report_step = std::max(1, total_bytes / 200); + uint64_t last_reported = 0; + if (progress) { + progress(ProgressStage::Parsing, 0, total_bytes); + } while (words.size() > 0 && !(abort && *abort)) { capnp::FlatArrayMessageReader reader(words); auto event = reader.getRoot(); @@ -56,15 +93,30 @@ bool LogReader::load(const char *data, size_t size, std::atomic *abort) { events.emplace_back(which, sof ? sof : mono_time, event_data, idx.getSegmentNum()); } } + + if (progress) { + const uint64_t current_bytes = + total_bytes - static_cast(words.size() * sizeof(capnp::word)); + if (current_bytes >= total_bytes || current_bytes - last_reported >= report_step) { + progress(ProgressStage::Parsing, current_bytes, total_bytes); + last_reported = current_bytes; + } + } } } catch (const kj::Exception &e) { rWarning("Failed to parse log : %s.\nRetrieved %zu events from corrupt log", e.getDescription().cStr(), events.size()); } + if (progress) { + progress(ProgressStage::Parsing, size, size); + } + if (requires_migration) { migrateOldEvents(); } + parse_seconds_ = std::chrono::duration(Clock::now() - parse_start).count(); + if (!events.empty() && !(abort && *abort)) { events.shrink_to_fit(); std::sort(events.begin(), events.end()); diff --git a/tools/replay/logreader.h b/tools/replay/logreader.h index 9219878ace0..fe11ab0f77d 100644 --- a/tools/replay/logreader.h +++ b/tools/replay/logreader.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include @@ -26,12 +28,26 @@ class Event { class LogReader { public: + enum class ProgressStage { + Downloading, + Parsing, + }; + + using ProgressCallback = std::function; + LogReader(const std::vector &filters = {}) { filters_ = filters; } bool load(const std::string &url, std::atomic *abort = nullptr, - bool local_cache = false); - bool load(const char *data, size_t size, std::atomic *abort = nullptr); + bool local_cache = false, const ProgressCallback &progress = {}); + bool load(const char *data, size_t size, std::atomic *abort = nullptr, + const ProgressCallback &progress = {}); std::vector events; + uint64_t compressed_size() const { return compressed_size_; } + uint64_t decompressed_size() const { return decompressed_size_; } + double download_seconds() const { return download_seconds_; } + double decompress_seconds() const { return decompress_seconds_; } + double parse_seconds() const { return parse_seconds_; } + private: void migrateOldEvents(); @@ -39,4 +55,9 @@ class LogReader { bool requires_migration = true; std::vector filters_; MonotonicBuffer buffer_{1024 * 1024}; + uint64_t compressed_size_ = 0; + uint64_t decompressed_size_ = 0; + double download_seconds_ = 0.0; + double decompress_seconds_ = 0.0; + double parse_seconds_ = 0.0; }; diff --git a/tools/replay/py_downloader.cc b/tools/replay/py_downloader.cc index efaf3c93a25..5063d6947c5 100644 --- a/tools/replay/py_downloader.cc +++ b/tools/replay/py_downloader.cc @@ -149,11 +149,16 @@ std::string runPython(const std::vector &args, std::atomic *a int status; waitpid(pid, &status, 0); - bool failed = (abort && *abort) || + const bool aborted = abort && *abort; + const bool expected_sigterm = aborted && WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM; + bool failed = aborted || (WIFEXITED(status) && WEXITSTATUS(status) != 0) || WIFSIGNALED(status); if (failed) { - if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { + if (expected_sigterm) { + // Route/camera teardown cancels outstanding downloader subprocesses. + // Keep that expected shutdown path quiet. + } else if (WIFEXITED(status) && WEXITSTATUS(status) != 0) { rWarning("py_downloader: process exited with code %d", WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { rWarning("py_downloader: process killed by signal %d", WTERMSIG(status)); From 6b94c47c6aa68a71ee24c9300ce094a97d7acef2 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Fri, 27 Mar 2026 13:31:00 -0700 Subject: [PATCH 589/685] Lateral maneuver report (#37562) * lateral report * mutually exclude buttons * gating * set maneuver * add timer * timer text * fix plot * use curvature * more curves * fix gating * rm delay * highway speed only * msg * add sine * add step-down * use relative * text * stabilize * tuning * windup * text * winddown * no windup * tuning * more tuning * more * formatting * test faster * extend sine * report crossings * add readme * clean report * fix lint * gating * fix * straighter * compensate roll * rm abs roll * len * Revert "rm abs roll" This reverts commit a22d6bb136f90d2bf997e6b9aeee2f784398ef42. * Revert "compensate roll" This reverts commit dfda52119cc4a2e29ac2854b9154c08459086fea. * print actuators * show curve and roll * tune roll * text * slower * timer * too much banked streets in US * readme * filter incomplete * plot jerk * plot angle jerk * lil edits * fix lint * apply suggestions * better table * apply comments * clean * shane comments * deflicker --------- Co-authored-by: Adeeb Shihadeh --- cereal/log.capnp | 7 + cereal/services.py | 1 + common/params_keys.h | 1 + selfdrive/controls/controlsd.py | 7 +- selfdrive/selfdrived/events.py | 5 + selfdrive/selfdrived/selfdrived.py | 10 +- selfdrive/ui/layouts/settings/developer.py | 27 +- .../ui/mici/layouts/settings/developer.py | 26 +- system/manager/process_config.py | 4 + tools/lateral_maneuvers/.gitignore | 1 + tools/lateral_maneuvers/README.md | 42 +++ tools/lateral_maneuvers/generate_report.py | 249 ++++++++++++++++++ tools/lateral_maneuvers/lateral_maneuversd.py | 180 +++++++++++++ tools/longitudinal_maneuvers/maneuversd.py | 40 +-- 14 files changed, 577 insertions(+), 23 deletions(-) create mode 100644 tools/lateral_maneuvers/.gitignore create mode 100644 tools/lateral_maneuvers/README.md create mode 100755 tools/lateral_maneuvers/generate_report.py create mode 100755 tools/lateral_maneuvers/lateral_maneuversd.py diff --git a/cereal/log.capnp b/cereal/log.capnp index c26c1f9d3a2..d8e9f563165 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -88,6 +88,7 @@ struct OnroadEvent @0xc4fa6047f024e718 { lowMemory @51; stockAeb @52; stockLkas @98; + lateralManeuver @99; ldw @53; carUnrecognized @54; invalidLkasSetting @55; @@ -1241,6 +1242,10 @@ struct DriverAssistance { # FCW, AEB, etc. will go here } +struct LateralManeuverPlan { + desiredCurvature @0 :Float32; # 1/m +} + struct LongitudinalPlan @0xe00b5b3eba12876c { modelMonoTime @9 :UInt64; hasLead @7 :Bool; @@ -2612,6 +2617,8 @@ struct Event { bookmarkButton @148 :UserBookmark; audioFeedback @149 :AudioFeedback; + lateralManeuverPlan @150 :LateralManeuverPlan; + # *********** debug *********** testJoystick @52 :Joystick; roadEncodeData @86 :EncodeData; diff --git a/cereal/services.py b/cereal/services.py index e7350aceac0..6b98128d64b 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -49,6 +49,7 @@ def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] "carControl": (True, 100., 10), "carOutput": (True, 100., 10), "longitudinalPlan": (True, 20., 10), + "lateralManeuverPlan": (True, 20.), "driverAssistance": (True, 20., 20), "procLog": (True, 0.5, 15, QueueSize.BIG), "gpsLocationExternal": (True, 10., 10), diff --git a/common/params_keys.h b/common/params_keys.h index d6104e74977..b81a373d087 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -82,6 +82,7 @@ inline static std::unordered_map keys = { {"LiveParametersV2", {PERSISTENT, BYTES}}, {"LiveTorqueParameters", {PERSISTENT | DONT_LOG, BYTES}}, {"LocationFilterInitialState", {PERSISTENT, BYTES}}, + {"LateralManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"LongitudinalManeuverMode", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"LongitudinalPersonality", {PERSISTENT, INT, std::to_string(static_cast(cereal::LongitudinalPersonality::STANDARD))}}, {"NetworkMetered", {PERSISTENT, BOOL}}, diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 9e31ac15268..b49e46604a4 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -37,7 +37,7 @@ def __init__(self) -> None: self.CI = interfaces[self.CP.carFingerprint](self.CP) self.sm = messaging.SubMaster(['liveDelay', 'liveParameters', 'liveTorqueParameters', 'modelV2', 'selfdriveState', - 'liveCalibration', 'livePose', 'longitudinalPlan', 'carState', 'carOutput', + 'liveCalibration', 'livePose', 'longitudinalPlan', 'lateralManeuverPlan', 'carState', 'carOutput', 'driverMonitoringState', 'onroadEvents', 'driverAssistance'], poll='selfdriveState') self.pm = messaging.PubMaster(['carControl', 'controlsState']) @@ -116,7 +116,10 @@ def state_control(self): # Steering PID loop and lateral MPC # Reset desired curvature to current to avoid violating the limits on engage - new_desired_curvature = model_v2.action.desiredCurvature if CC.latActive else self.curvature + if self.sm.valid['lateralManeuverPlan']: + new_desired_curvature = self.sm['lateralManeuverPlan'].desiredCurvature if CC.latActive else self.curvature + else: + new_desired_curvature = model_v2.action.desiredCurvature if CC.latActive else self.curvature self.desired_curvature, curvature_limited = clip_curvature(CS.vEgo, self.desired_curvature, new_desired_curvature, lp.roll) lat_delay = self.sm["liveDelay"].lateralDelay + LAT_SMOOTH_SECONDS diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index e10f67fa459..55af93c42b1 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -409,6 +409,11 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin "Ensure road ahead is clear"), }, + EventName.lateralManeuver: { + ET.WARNING: longitudinal_maneuver_alert, + ET.PERMANENT: NormalPermanentAlert("Lateral Maneuver Mode"), + }, + EventName.selfdriveInitializing: { ET.NO_ENTRY: NoEntryAlert("System Initializing"), }, diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 997c7e37701..6a294ca8d82 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -74,7 +74,7 @@ def __init__(self, CP=None): # TODO: de-couple selfdrived with card/conflate on carState without introducing controls mismatches self.car_state_sock = messaging.sub_sock('carState', timeout=20) - ignore = self.sensor_packets + self.gps_packets + ['alertDebug'] + ignore = self.sensor_packets + self.gps_packets + ['alertDebug', 'lateralManeuverPlan'] if SIMULATION: ignore += ['driverCameraState', 'managerState'] if REPLAY: @@ -83,7 +83,8 @@ def __init__(self, CP=None): self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', 'carOutput', 'driverMonitoringState', 'longitudinalPlan', 'livePose', 'liveDelay', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', - 'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback'] + \ + 'controlsState', 'carControl', 'driverAssistance', 'alertDebug', 'userBookmark', 'audioFeedback', + 'lateralManeuverPlan'] + \ self.camera_packets + self.sensor_packets + self.gps_packets, ignore_alive=ignore, ignore_avg_freq=ignore, ignore_valid=ignore, frequency=int(1/DT_CTRL)) @@ -148,7 +149,10 @@ def update_events(self, CS): self.events.add(EventName.joystickDebug) self.startup_event = None - if self.sm.recv_frame['alertDebug'] > 0: + if self.sm.recv_frame['lateralManeuverPlan'] > 0: + self.events.add(EventName.lateralManeuver) + self.startup_event = None + elif self.sm.recv_frame['alertDebug'] > 0: self.events.add(EventName.longitudinalManeuver) self.startup_event = None diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index bd064ab834b..c61a4068586 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -67,6 +67,13 @@ def __init__(self): callback=self._on_long_maneuver_mode, ) + self._lat_maneuver_toggle = toggle_item( + lambda: tr("Lateral Maneuver Mode"), + description="", + initial_state=self._params.get_bool("LateralManeuverMode"), + callback=self._on_lat_maneuver_mode, + ) + self._alpha_long_toggle = toggle_item( lambda: tr("openpilot Longitudinal Control (Alpha)"), description=lambda: tr(DESCRIPTIONS["alpha_longitudinal"]), @@ -89,6 +96,7 @@ def __init__(self): self._ssh_keys, self._joystick_toggle, self._long_maneuver_toggle, + self._lat_maneuver_toggle, self._alpha_long_toggle, self._ui_debug_toggle, ], line_separator=True, spacing=0) @@ -109,7 +117,7 @@ def _update_toggles(self): # Hide non-release toggles on release builds # TODO: we can do an onroad cycle, but alpha long toggle requires a deinit function to re-enable radar and not fault - for item in (self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle): + for item in (self._joystick_toggle, self._long_maneuver_toggle, self._lat_maneuver_toggle, self._alpha_long_toggle): item.set_visible(not self._is_release) # CP gating @@ -126,8 +134,12 @@ def _update_toggles(self): if not long_man_enabled: self._long_maneuver_toggle.action_item.set_state(False) self._params.put_bool("LongitudinalManeuverMode", False) + + lat_man_enabled = ui_state.is_offroad() + self._lat_maneuver_toggle.action_item.set_enabled(lat_man_enabled) else: self._long_maneuver_toggle.action_item.set_enabled(False) + self._lat_maneuver_toggle.action_item.set_enabled(False) self._alpha_long_toggle.set_visible(False) # TODO: make a param control list item so we don't need to manage internal state as much here @@ -137,6 +149,7 @@ def _update_toggles(self): ("SshEnabled", self._ssh_toggle), ("JoystickDebugMode", self._joystick_toggle), ("LongitudinalManeuverMode", self._long_maneuver_toggle), + ("LateralManeuverMode", self._lat_maneuver_toggle), ("AlphaLongitudinalEnabled", self._alpha_long_toggle), ("ShowDebugInfo", self._ui_debug_toggle), ): @@ -157,11 +170,23 @@ def _on_joystick_debug_mode(self, state: bool): self._params.put_bool("JoystickDebugMode", state) self._params.put_bool("LongitudinalManeuverMode", False) self._long_maneuver_toggle.action_item.set_state(False) + self._params.put_bool("LateralManeuverMode", False) + self._lat_maneuver_toggle.action_item.set_state(False) def _on_long_maneuver_mode(self, state: bool): self._params.put_bool("LongitudinalManeuverMode", state) self._params.put_bool("JoystickDebugMode", False) self._joystick_toggle.action_item.set_state(False) + self._params.put_bool("LateralManeuverMode", False) + self._lat_maneuver_toggle.action_item.set_state(False) + + def _on_lat_maneuver_mode(self, state: bool): + self._params.put_bool("LateralManeuverMode", state) + self._params.put_bool("ExperimentalMode", False) + self._params.put_bool("JoystickDebugMode", False) + self._joystick_toggle.action_item.set_state(False) + self._params.put_bool("LongitudinalManeuverMode", False) + self._long_maneuver_toggle.action_item.set_state(False) def _on_alpha_long_enabled(self, state: bool): if state: diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index 386b4689280..f47614c073e 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -55,6 +55,9 @@ def ssh_keys_callback(): self._long_maneuver_toggle = BigToggle("longitudinal maneuver mode", initial_state=ui_state.params.get_bool("LongitudinalManeuverMode"), toggle_callback=self._on_long_maneuver_mode) + self._lat_maneuver_toggle = BigToggle("lateral maneuver mode", + initial_state=ui_state.params.get_bool("LateralManeuverMode"), + toggle_callback=self._on_lat_maneuver_mode) self._alpha_long_toggle = BigToggle("alpha longitudinal", initial_state=ui_state.params.get_bool("AlphaLongitudinalEnabled"), toggle_callback=self._on_alpha_long_enabled) @@ -68,6 +71,7 @@ def ssh_keys_callback(): self._ssh_keys_btn, self._joystick_toggle, self._long_maneuver_toggle, + self._lat_maneuver_toggle, self._alpha_long_toggle, self._debug_mode_toggle, ]) @@ -78,12 +82,13 @@ def ssh_keys_callback(): ("SshEnabled", self._ssh_toggle), ("JoystickDebugMode", self._joystick_toggle), ("LongitudinalManeuverMode", self._long_maneuver_toggle), + ("LateralManeuverMode", self._lat_maneuver_toggle), ("AlphaLongitudinalEnabled", self._alpha_long_toggle), ("ShowDebugInfo", self._debug_mode_toggle), ) onroad_blocked_toggles = (self._adb_toggle, self._joystick_toggle) - release_blocked_toggles = (self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle) - engaged_blocked_toggles = (self._long_maneuver_toggle, self._alpha_long_toggle) + release_blocked_toggles = (self._joystick_toggle, self._long_maneuver_toggle, self._lat_maneuver_toggle, self._alpha_long_toggle) + engaged_blocked_toggles = (self._long_maneuver_toggle, self._lat_maneuver_toggle, self._alpha_long_toggle) # Hide non-release toggles on release builds for item in release_blocked_toggles: @@ -129,8 +134,12 @@ def _update_toggles(self): if not long_man_enabled: self._long_maneuver_toggle.set_checked(False) ui_state.params.put_bool("LongitudinalManeuverMode", False) + + lat_man_enabled = ui_state.is_offroad() + self._lat_maneuver_toggle.set_enabled(lat_man_enabled) else: self._long_maneuver_toggle.set_enabled(False) + self._lat_maneuver_toggle.set_enabled(False) self._alpha_long_toggle.set_visible(False) # Refresh toggles from params to mirror external changes @@ -141,11 +150,24 @@ def _on_joystick_debug_mode(self, state: bool): ui_state.params.put_bool("JoystickDebugMode", state) ui_state.params.put_bool("LongitudinalManeuverMode", False) self._long_maneuver_toggle.set_checked(False) + ui_state.params.put_bool("LateralManeuverMode", False) + self._lat_maneuver_toggle.set_checked(False) def _on_long_maneuver_mode(self, state: bool): ui_state.params.put_bool("LongitudinalManeuverMode", state) ui_state.params.put_bool("JoystickDebugMode", False) self._joystick_toggle.set_checked(False) + ui_state.params.put_bool("LateralManeuverMode", False) + self._lat_maneuver_toggle.set_checked(False) + restart_needed_callback(state) + + def _on_lat_maneuver_mode(self, state: bool): + ui_state.params.put_bool("LateralManeuverMode", state) + ui_state.params.put_bool("ExperimentalMode", False) + ui_state.params.put_bool("JoystickDebugMode", False) + self._joystick_toggle.set_checked(False) + ui_state.params.put_bool("LongitudinalManeuverMode", False) + self._long_maneuver_toggle.set_checked(False) restart_needed_callback(state) def _on_alpha_long_enabled(self, state: bool): diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 0b99183193e..7e96b7776a4 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -40,6 +40,9 @@ def not_joystick(started: bool, params: Params, CP: car.CarParams) -> bool: def long_maneuver(started: bool, params: Params, CP: car.CarParams) -> bool: return started and params.get_bool("LongitudinalManeuverMode") +def lat_maneuver(started: bool, params: Params, CP: car.CarParams) -> bool: + return started and params.get_bool("LateralManeuverMode") + def not_long_maneuver(started: bool, params: Params, CP: car.CarParams) -> bool: return started and not params.get_bool("LongitudinalManeuverMode") @@ -100,6 +103,7 @@ def and_(*fns): PythonProcess("pigeond", "system.ubloxd.pigeond", ublox, enabled=TICI), PythonProcess("plannerd", "selfdrive.controls.plannerd", not_long_maneuver), PythonProcess("maneuversd", "tools.longitudinal_maneuvers.maneuversd", long_maneuver), + PythonProcess("lateral_maneuversd", "tools.lateral_maneuvers.lateral_maneuversd", lat_maneuver), PythonProcess("radard", "selfdrive.controls.radard", only_onroad), PythonProcess("hardwared", "system.hardware.hardwared", always_run), PythonProcess("tombstoned", "system.tombstoned", always_run, enabled=not PC), diff --git a/tools/lateral_maneuvers/.gitignore b/tools/lateral_maneuvers/.gitignore new file mode 100644 index 00000000000..a0b6efe6b3f --- /dev/null +++ b/tools/lateral_maneuvers/.gitignore @@ -0,0 +1 @@ +/lateral_reports/ diff --git a/tools/lateral_maneuvers/README.md b/tools/lateral_maneuvers/README.md new file mode 100644 index 00000000000..3a54bc74099 --- /dev/null +++ b/tools/lateral_maneuvers/README.md @@ -0,0 +1,42 @@ +# Lateral Maneuvers Testing Tool + +> [!WARNING] +> Use caution when using this tool. + +Test your vehicle's lateral control tuning with this tool. The tool will test the vehicle's ability to follow a few lateral maneuvers and includes a tool to generate a report from the route. + +## Instructions + +1. Check out a development branch such as `master` on your comma device. +2. The full maneuver suite runs at 20 and 30 mph. +3. Enable "Lateral Maneuver Mode" in Settings > Developer on the device while offroad. Alternatively, set the parameter manually: + + ```sh + echo -n 1 > /data/params/d/LateralManeuverMode + ``` + +4. Turn your vehicle back on. You will see "Lateral Maneuver Mode". + +5. Ensure the area ahead is clear, as openpilot will command lateral acceleration steps in this mode. Once you are ready, set ACC manually to the target speed shown on screen and let openpilot stabilize lateral. After 1 seconds of steady straight driving, the maneuver will begin automatically. openpilot lateral control stays engaged between maneuvers normally while waiting for the next maneuver's readiness conditions. The maneuver will be aborted and repeated if speed is out of range, steering is touched or openpilot disengages. + +6. When the testing is complete, you'll see an alert that says "Maneuvers Finished." Complete the route by pulling over and turning off the vehicle. + +7. Visit https://connect.comma.ai and locate the route(s). They will stand out with lots of orange intervals in their timeline. Ensure "All logs" show as "uploaded." + + ![image](https://github.com/user-attachments/assets/cfe4c6d9-752f-4b24-b421-4b90a01933dc) + +8. Gather the route ID and then run the report generator. The file will be exported to the same directory: + + ```sh + $ python tools/lateral_maneuvers/generate_report.py 98395b7c5b27882e/000001cc--5a73bde686 + + processing report for KIA_EV6 + plotting maneuver: step right 20mph, runs: 3 + plotting maneuver: step left 20mph, runs: 3 + plotting maneuver: sine 0.5Hz 20mph, runs: 3 + plotting maneuver: step right 30mph, runs: 3 + + Opening report: /home/batman/openpilot/tools/lateral_maneuvers/lateral_reports/KIA_EV6_98395b7c5b27882e_000001cc--5a73bde686.html + ``` + +You can reach out on [Discord](https://discord.comma.ai) if you have any questions about these instructions or the tool itself. diff --git a/tools/lateral_maneuvers/generate_report.py b/tools/lateral_maneuvers/generate_report.py new file mode 100755 index 00000000000..9a6fe1b979a --- /dev/null +++ b/tools/lateral_maneuvers/generate_report.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +import argparse +import base64 +import io +import math +import numpy as np +import os +import webbrowser +from collections import defaultdict +from pathlib import Path +import matplotlib.pyplot as plt +from openpilot.common.utils import tabulate + +from cereal import car +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.selfdrive.controls.lib.latcontrol_torque import LP_FILTER_CUTOFF_HZ +from openpilot.tools.lib.logreader import LogReader +from openpilot.system.hardware.hw import Paths +from openpilot.common.constants import CV +from openpilot.tools.longitudinal_maneuvers.generate_report import format_car_params + + +def lat_accel(curvature, v): + return curvature * max(v, 1.0) ** 2 + + +def report(platform, route, _description, CP, ID, maneuvers): + output_path = Path(__file__).resolve().parent / "lateral_reports" + output_fn = output_path / f"{platform}_{route.replace('/', '_')}.html" + output_path.mkdir(exist_ok=True) + target_cross_times = defaultdict(list) + + builder = [ + "\n", + "

Lateral maneuver report

\n", + f"

{platform}

\n", + f"

{route}

\n", + f"

{ID.gitCommit}, {ID.gitBranch}, {ID.gitRemote}

\n", + ] + if _description is not None: + builder.append(f"

Description: {_description}

\n") + builder.append(f"

CarParams

{format_car_params(CP)}
\n") + builder.append('{ summary }') # to be replaced below + for description, runs in maneuvers: + # filter incomplete runs + completed_runs = [msgs for msgs in runs + if any(m.alertDebug.alertText1 == 'Complete' for m in msgs if m.which() == 'alertDebug')] + print(f'plotting maneuver: {description}') + if not completed_runs: + continue + builder.append("
\n") + builder.append(f"

{description}

\n") + for run, msgs in enumerate(completed_runs): + t_carControl, carControl = zip(*[(m.logMonoTime, m.carControl) for m in msgs if m.which() == 'carControl'], strict=True) + t_carState, carState = zip(*[(m.logMonoTime, m.carState) for m in msgs if m.which() == 'carState'], strict=True) + t_controlsState, controlsState = zip(*[(m.logMonoTime, m.controlsState) for m in msgs if m.which() == 'controlsState'], strict=True) + t_lateralPlan, lateralPlan = zip(*[(m.logMonoTime, m.lateralManeuverPlan) for m in msgs if m.which() == 'lateralManeuverPlan' and m.valid], strict=True) + t_carOutput, carOutput = zip(*[(m.logMonoTime, m.carOutput) for m in msgs if m.which() == 'carOutput'], strict=True) + + # make time relative seconds + t_carControl = [(t - t_carControl[0]) / 1e9 for t in t_carControl] + t_carState = [(t - t_carState[0]) / 1e9 for t in t_carState] + t_controlsState = [(t - t_controlsState[0]) / 1e9 for t in t_controlsState] + t_lateralPlan = [(t - t_lateralPlan[0]) / 1e9 for t in t_lateralPlan] + t_carOutput = [(t - t_carOutput[0]) / 1e9 for t in t_carOutput] + + # maneuver validity + latActive = [m.latActive for m in carControl] + maneuver_valid = all(latActive) and not any(cs.steeringPressed for cs in carState) + + _open = 'open' if maneuver_valid else '' + title = f'Run #{int(run)+1}' + (' (invalid maneuver!)' if not maneuver_valid else '') + + builder.append(f"

{title}

\n") + + baseline_accel = lat_accel(controlsState[0].curvature, carState[0].vEgo) + v_ego = [m.vEgo for m in carState] + cross_markers = [] + + if description.startswith('sine'): + amplitude = max(abs(lat_accel(lp.desiredCurvature, v) - baseline_accel) + for lp, v in zip(lateralPlan, v_ego, strict=False)) + threshold = amplitude * 0.5 + builder.append('

50% peak') + for t, cs, v in zip(t_controlsState, controlsState, v_ego, strict=False): + actual = lat_accel(cs.curvature, v) - baseline_accel + if abs(actual) > threshold: + builder.append(f', crossed in {t:.3f}s') + cross_markers.append((t, actual + baseline_accel)) + if maneuver_valid: + target_cross_times[description].append(t) + break + else: + builder.append(', not crossed') + builder.append('

') + if maneuver_valid: + target_cross_times.setdefault(description, []) + else: + action_targets = [(0, lat_accel(lateralPlan[0].desiredCurvature, v_ego[0]) - baseline_accel)] + for i in range(1, min(len(lateralPlan), len(v_ego))): + if abs(lateralPlan[i].desiredCurvature - lateralPlan[i - 1].desiredCurvature) > 0.001: + desired = lat_accel(lateralPlan[i].desiredCurvature, v_ego[i]) - baseline_accel + action_targets.append((i, desired)) + + for j, (start_i, act_target) in enumerate(action_targets): + start_time = t_lateralPlan[start_i] + end_time = t_lateralPlan[action_targets[j + 1][0]] if j + 1 < len(action_targets) else t_controlsState[-1] + + builder.append(f'

aTarget: {round(act_target, 1)} m/s^2') + prev_crossed = False + for t, cs, v in zip(t_controlsState, controlsState, v_ego, strict=False): + if not (start_time <= t <= end_time): + continue + actual_accel = lat_accel(cs.curvature, v) - baseline_accel + crossed = (0 < act_target < actual_accel) or (0 > act_target > actual_accel) + if crossed and prev_crossed: + cross_time = t - start_time + builder.append(f', crossed in {cross_time:.3f}s') + cross_markers.append((t, act_target + baseline_accel)) + if maneuver_valid: + target_cross_times[description].append(cross_time) + break + prev_crossed = crossed + else: + builder.append(', not crossed') + builder.append('

') + if maneuver_valid: + target_cross_times.setdefault(description, []) + + plt.rcParams['font.size'] = 40 + fig = plt.figure(figsize=(30, 30)) + ax = fig.subplots(4, 1, sharex=True, gridspec_kw={'height_ratios': [5, 3, 3, 3]}) + + ax[0].grid(linewidth=4) + desired_lat_accel = [lat_accel(m.desiredCurvature, v) for m, v in zip(lateralPlan, v_ego, strict=False)] + if description.startswith('sine'): + ax[0].plot(t_lateralPlan[:len(desired_lat_accel)], desired_lat_accel, label='desired lat accel', linewidth=6) + else: + t_desired = [t_lateralPlan[0]] + t_lateralPlan[:len(desired_lat_accel)] + desired_lat_accel = [baseline_accel] + desired_lat_accel + ax[0].step(t_desired, desired_lat_accel, label='desired lat accel', linewidth=6, where='post') + actual_lat_accel = [lat_accel(cs.curvature, v) for cs, v in zip(controlsState, v_ego, strict=False)] + ax[0].plot(t_controlsState[:len(actual_lat_accel)], actual_lat_accel, label='actual lat accel', linewidth=6) + ax[0].set_ylabel('Lateral Accel (m/s^2)') + + for ct, cv in cross_markers: + ax[0].plot(ct, cv, marker='o', markersize=50, markeredgewidth=7, markeredgecolor='black', markerfacecolor='None') + + ax2 = ax[0].twinx() + if CP.steerControlType == car.CarParams.SteerControlType.angle: + ax2.plot(t_carOutput, [-m.actuatorsOutput.steeringAngleDeg for m in carOutput], 'C2', label='steer angle', linewidth=6) + else: + ax2.plot(t_carOutput, [-m.actuatorsOutput.torque for m in carOutput], 'C2', label='steer torque', linewidth=6) + + h1, l1 = ax[0].get_legend_handles_labels() + h2, l2 = ax2.get_legend_handles_labels() + ax[0].legend(h1 + h2, l1 + l2, prop={'size': 30}) + + ax[1].grid(linewidth=4) + ax[1].plot(t_carState, [v * CV.MS_TO_MPH for v in v_ego], label='vEgo', linewidth=6) + ax[1].set_ylabel('Velocity (mph)') + ax[1].yaxis.set_major_formatter(plt.FormatStrFormatter('%.1f')) + ax[1].legend() + + t_accel = np.array(t_controlsState[:len(actual_lat_accel)]) + raw_jerk = np.gradient(actual_lat_accel, t_accel) + dt_avg = np.mean(np.diff(t_accel)) + jerk_filter = FirstOrderFilter(0.0, 1 / (2 * np.pi * LP_FILTER_CUTOFF_HZ), dt_avg) + filtered_jerk = [jerk_filter.update(j) for j in raw_jerk] + ax[2].grid(linewidth=4) + ax[2].plot(t_accel, filtered_jerk, label='actual jerk', linewidth=6) + if CP.steerControlType == car.CarParams.SteerControlType.torque: + desired_jerk = [cs.lateralControlState.torqueState.desiredLateralJerk for cs in controlsState] + ax[2].plot(t_controlsState[:len(controlsState)], desired_jerk, label='desired jerk', linewidth=6) + ax[2].set_ylabel('Jerk (m/s^3)') + ax[2].legend() + + ax[3].grid(linewidth=4) + ax[3].plot(t_carControl, [math.degrees(m.orientationNED[0]) for m in carControl], label='roll', linewidth=6) + ax[3].set_ylabel('Roll (deg)') + ax[3].legend() + + ax[-1].set_xlabel("Time (s)") + fig.tight_layout() + + buffer = io.BytesIO() + fig.savefig(buffer, format='webp') + plt.close(fig) + buffer.seek(0) + builder.append(f"\n") + builder.append("
\n") + + summary = ["

Summary

\n"] + cols = ['maneuver', 'crossed', 'mean', 'min', 'max'] + table = [] + for description, times in target_cross_times.items(): + l = [description, len(times)] + if len(times): + l.extend([round(sum(times) / len(times), 2), round(min(times), 2), round(max(times), 2)]) + table.append(l) + summary.append(tabulate(table, headers=cols, tablefmt='html', numalign='left') + '\n') + + sum_idx = builder.index('{ summary }') + builder[sum_idx:sum_idx + 1] = summary + + with open(output_fn, "w") as f: + f.write(''.join(builder)) + + print(f"\nOpening report: {output_fn}\n") + webbrowser.open_new_tab(str(output_fn)) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Generate lateral maneuver report from route') + parser.add_argument('route', type=str, help='Route name (e.g. 00000000--5f742174be)') + parser.add_argument('description', type=str, nargs='?') + + args = parser.parse_args() + + if '/' in args.route or '|' in args.route: + lr = LogReader(args.route, only_union_types=True) + else: + segs = [seg for seg in os.listdir(Paths.log_root()) if args.route in seg] + lr = LogReader([os.path.join(Paths.log_root(), seg, 'rlog.zst') for seg in segs], only_union_types=True) + + CP = lr.first('carParams') + ID = lr.first('initData') + platform = CP.carFingerprint + print('processing report for', platform) + + maneuvers: list[tuple[str, list[list]]] = [] + active_prev = False + description_prev = None + + for msg in lr: + if msg.which() == 'alertDebug': + active = 'Active' in msg.alertDebug.alertText1 or msg.alertDebug.alertText1 == 'Complete' + if active and not active_prev: + if msg.alertDebug.alertText2 == description_prev: + maneuvers[-1][1].append([]) + else: + maneuvers.append((msg.alertDebug.alertText2, [[]])) + description_prev = maneuvers[-1][0] + active_prev = active + + if active_prev: + maneuvers[-1][1][-1].append(msg) + + report(platform, args.route, args.description, CP, ID, maneuvers) diff --git a/tools/lateral_maneuvers/lateral_maneuversd.py b/tools/lateral_maneuvers/lateral_maneuversd.py new file mode 100755 index 00000000000..d8a7185410b --- /dev/null +++ b/tools/lateral_maneuvers/lateral_maneuversd.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python3 +import numpy as np +from dataclasses import dataclass + +from cereal import messaging, car +from openpilot.common.constants import CV +from openpilot.common.realtime import DT_MDL +from openpilot.common.params import Params +from openpilot.common.swaglog import cloudlog +from openpilot.selfdrive.controls.lib.drive_helpers import MIN_SPEED +from openpilot.tools.longitudinal_maneuvers.maneuversd import Action, Maneuver as _Maneuver + +# thresholds for starting maneuvers +MAX_SPEED_DEV = 0.7 # deviation in m/s +MAX_CURV = 0.002 # 500 m radius +MAX_ROLL = 0.08 # 4.56° +TIMER = 2.0 # sec stable conditions before starting maneuver + +@dataclass +class Maneuver(_Maneuver): + _baseline_curvature: float = 0.0 + + def get_accel(self, v_ego: float, lat_active: bool, curvature: float, roll: float) -> float: + self._run_completed = False + # only start maneuver on straight, flat roads + ready = abs(v_ego - self.initial_speed) < MAX_SPEED_DEV and lat_active and abs(curvature) < MAX_CURV and abs(roll) < MAX_ROLL + self._ready_cnt = (self._ready_cnt + 1) if ready else max(self._ready_cnt - 1, 0) + + if self._ready_cnt > (TIMER / DT_MDL): + if not self._active: + self._baseline_curvature = curvature + self._active = True + + if not self._active: + return 0.0 + + return self._step() + + def reset(self): + super().reset() + self._ready_cnt = 0 + + +def _sine_action(amplitude, period, duration): + t = np.linspace(0, duration, int(duration / DT_MDL) + 1) + a = amplitude * np.sin(2 * np.pi * t / period) + return Action(a.tolist(), t.tolist()) + + +MANEUVERS = [ + Maneuver( + "step right 20mph", + [Action([0.5], [1.0]), Action([-0.5], [1.5])], + repeat=2, + initial_speed=20. * CV.MPH_TO_MS, + ), + Maneuver( + "step left 20mph", + [Action([-0.5], [1.0]), Action([0.5], [1.5])], + repeat=2, + initial_speed=20. * CV.MPH_TO_MS, + ), + Maneuver( + "sine 0.5Hz 20mph", + [_sine_action(1.0, 2.0, 2.0), Action([0.0], [0.5])], + repeat=2, + initial_speed=20. * CV.MPH_TO_MS, + ), + Maneuver( + "step right 30mph", + [Action([0.5], [1.0]), Action([-0.5], [1.5])], + repeat=2, + initial_speed=30. * CV.MPH_TO_MS, + ), + Maneuver( + "step left 30mph", + [Action([-0.5], [1.0]), Action([0.5], [1.5])], + repeat=2, + initial_speed=30. * CV.MPH_TO_MS, + ), + Maneuver( + "sine 0.5Hz 30mph", + [_sine_action(1.0, 2.0, 2.0), Action([0.0], [0.5])], + repeat=2, + initial_speed=30. * CV.MPH_TO_MS, + ), +] + + +def main(): + params = Params() + cloudlog.info("lateral_maneuversd is waiting for CarParams") + messaging.log_from_bytes(params.get("CarParams", block=True), car.CarParams) + + sm = messaging.SubMaster(['carState', 'carControl', 'controlsState', 'selfdriveState', 'modelV2'], poll='modelV2') + pm = messaging.PubMaster(['lateralManeuverPlan', 'alertDebug']) + + maneuvers = iter(MANEUVERS) + maneuver = None + complete_cnt = 0 + display_holdoff = 0 + prev_text = '' + + while True: + sm.update() + + if maneuver is None: + maneuver = next(maneuvers, None) + + alert_msg = messaging.new_message('alertDebug') + alert_msg.valid = True + + plan_send = messaging.new_message('lateralManeuverPlan') + + accel = 0 + v_ego = max(sm['carState'].vEgo, 0) + curvature = sm['controlsState'].desiredCurvature + + if complete_cnt > 0: + complete_cnt -= 1 + alert_msg.alertDebug.alertText1 = 'Completed' + alert_msg.alertDebug.alertText2 = maneuver.description + elif maneuver is not None: + # reset maneuver on steering override or out of range speed + if sm['carState'].steeringPressed or (maneuver.active and abs(v_ego - maneuver.initial_speed) > MAX_SPEED_DEV): + maneuver.reset() + + roll = sm['carControl'].orientationNED[0] if len(sm['carControl'].orientationNED) == 3 else 0.0 + accel = maneuver.get_accel(v_ego, sm['carControl'].latActive, curvature, roll) + + if maneuver._run_completed: + complete_cnt = int(1.0 / DT_MDL) + alert_msg.alertDebug.alertText1 = 'Complete' + alert_msg.alertDebug.alertText2 = maneuver.description + elif maneuver.active: + action_remaining = maneuver.actions[maneuver._action_index].time_bp[-1] - maneuver._action_frames * DT_MDL + if maneuver.description.startswith('sine'): + freq = maneuver.description.split()[1] + alert_msg.alertDebug.alertText1 = f'Active sine {freq} {max(action_remaining, 0):.1f}s' + else: + alert_msg.alertDebug.alertText1 = f'Active {accel:+.1f}m/s² {max(action_remaining, 0):.1f}s' + alert_msg.alertDebug.alertText2 = maneuver.description + elif not (abs(v_ego - maneuver.initial_speed) < MAX_SPEED_DEV and sm['carControl'].latActive): + alert_msg.alertDebug.alertText1 = f'Set speed to {maneuver.initial_speed * CV.MS_TO_MPH:0.0f} mph' + elif maneuver._ready_cnt > 0: + ready_time = max(TIMER - maneuver._ready_cnt * DT_MDL, 0) + alert_msg.alertDebug.alertText1 = f'Starting: {int(ready_time) + 1}' + alert_msg.alertDebug.alertText2 = maneuver.description + else: + curv_ok = abs(curvature) < MAX_CURV + reason = 'road not straight' if not curv_ok else 'road not flat' + alert_msg.alertDebug.alertText1 = f'Waiting: {reason}' + alert_msg.alertDebug.alertText2 = maneuver.description + else: + alert_msg.alertDebug.alertText1 = 'Maneuvers Finished' + + # prevent flickering text + setup = ('Set speed', 'Starting', 'Waiting') + text = alert_msg.alertDebug.alertText1 + same = text == prev_text or (text.startswith('Starting') and prev_text.startswith('Starting')) + if not same and text.startswith(setup) and prev_text.startswith(setup) and display_holdoff > 0: + alert_msg.alertDebug.alertText1 = prev_text + display_holdoff -= 1 + else: + prev_text = text + display_holdoff = int(0.5 / DT_MDL) if text.startswith(setup) else 0 + + pm.send('alertDebug', alert_msg) + + plan_send.valid = maneuver is not None and maneuver.active and complete_cnt == 0 + if plan_send.valid: + plan_send.lateralManeuverPlan.desiredCurvature = maneuver._baseline_curvature + accel / max(v_ego, MIN_SPEED) ** 2 + pm.send('lateralManeuverPlan', plan_send) + + if maneuver is not None and maneuver.finished and complete_cnt == 0: + maneuver = None + + +if __name__ == "__main__": + main() diff --git a/tools/longitudinal_maneuvers/maneuversd.py b/tools/longitudinal_maneuvers/maneuversd.py index c17ae23757c..f8dc6787cc4 100755 --- a/tools/longitudinal_maneuvers/maneuversd.py +++ b/tools/longitudinal_maneuvers/maneuversd.py @@ -27,23 +27,14 @@ class Maneuver: _active: bool = False _finished: bool = False + _run_completed: bool = False _action_index: int = 0 _action_frames: int = 0 _ready_cnt: int = 0 _repeated: int = 0 - def get_accel(self, v_ego: float, long_active: bool, standstill: bool, cruise_standstill: bool) -> float: - ready = abs(v_ego - self.initial_speed) < 0.3 and long_active and not cruise_standstill - if self.initial_speed < 0.01: - ready = ready and standstill - self._ready_cnt = (self._ready_cnt + 1) if ready else 0 - - if self._ready_cnt > (3. / DT_MDL): - self._active = True - - if not self._active: - return min(max(self.initial_speed - v_ego, -2.), 2.) - + def _step(self) -> float: + self._run_completed = False action = self.actions[self._action_index] action_accel = np.interp(self._action_frames * DT_MDL, action.time_bp, action.accel_bp) @@ -58,15 +49,34 @@ def get_accel(self, v_ego: float, long_active: bool, standstill: bool, cruise_st # repeat maneuver elif self._repeated < self.repeat: self._repeated += 1 - self._action_index = 0 - self._action_frames = 0 - self._active = False + self._run_completed = True + self.reset() # finish maneuver else: + self._run_completed = True self._finished = True return float(action_accel) + def get_accel(self, v_ego: float, long_active: bool, standstill: bool, cruise_standstill: bool) -> float: + ready = abs(v_ego - self.initial_speed) < 0.3 and long_active and not cruise_standstill + if self.initial_speed < 0.01: + ready = ready and standstill + self._ready_cnt = (self._ready_cnt + 1) if ready else 0 + + if self._ready_cnt > (3. / DT_MDL): + self._active = True + + if not self._active: + return min(max(self.initial_speed - v_ego, -2.), 2.) + + return self._step() + + def reset(self): + self._active = False + self._action_frames = 0 + self._action_index = 0 + @property def finished(self): return self._finished From 9be7a48ccd4dca7c59c5e107ca0d8a5baaee8a7b Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:45:34 -0700 Subject: [PATCH 590/685] bump opendbc (#37738) * bump opendbc * regen CARS.md * bump opendbc * regen CARS.md --- docs/CARS.md | 127 ++++++++++++++++++++++++++------------------------- opendbc_repo | 2 +- 2 files changed, 65 insertions(+), 64 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 097f45e34f9..56f80d16063 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -13,14 +13,14 @@ A supported vehicle is one that just works when you install a comma device. All |Acura|MDX 2025-26|All except Type S|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Acura|TLX 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|TLX 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|TLX 2025|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi[11](#footnotes)|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi[11](#footnotes)|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi[11](#footnotes)|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi[11](#footnotes)|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi[11](#footnotes)|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Audi[11](#footnotes)|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| @@ -32,7 +32,7 @@ A supported vehicle is one that just works when you install a comma device. All |Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| -|CUPRA|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|CUPRA[11](#footnotes)|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -176,7 +176,7 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Niro Hybrid 2018|Smart Cruise Control (SCC)|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Niro Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro Hybrid 2023-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -220,8 +220,8 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|MAN|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|MAN|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN[11](#footnotes)|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN[11](#footnotes)|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Nissan[5](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| @@ -231,8 +231,8 @@ A supported vehicle is one that just works when you install a comma device. All |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|SEAT|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT[11](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|SEAT[11](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Subaru|Ascent 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| @@ -243,15 +243,15 @@ A supported vehicle is one that just works when you install a comma device. All |Subaru|Outback 2020-22|All[6](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|XV 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|XV 2020-21|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Škoda|Fabia 2022-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Škoda|Kamiq 2021-23[11,13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Škoda|Karoq 2019-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Kodiaq 2017-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia 2015-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia RS 2016[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Octavia Scout 2017-19[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Škoda|Scala 2020-23[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Škoda|Superb 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Fabia 2022-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Škoda|Kamiq 2021-23[12,14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Škoda[11](#footnotes)|Karoq 2019-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda[11](#footnotes)|Kodiaq 2017-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda[11](#footnotes)|Octavia 2015-19[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda[11](#footnotes)|Octavia RS 2016[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda[11](#footnotes)|Octavia Scout 2017-19[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Škoda|Scala 2020-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Škoda[11](#footnotes)|Superb 2015-22[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Tesla[9](#footnotes)|Model 3 (with HW3) 2019-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Tesla[9](#footnotes)|Model 3 (with HW4) 2024-25[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla B connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Tesla[9](#footnotes)|Model Y (with HW3) 2020-23[8](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Tesla A connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| @@ -301,42 +301,42 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat 2015-22[12](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[15](#footnotes)||| -|Volkswagen|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,14](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Passat 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen|Polo 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Volkswagen|Polo GTI 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| +|Volkswagen[11](#footnotes)|T-Roc 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Taos 2022-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| ### Footnotes 1openpilot Longitudinal Control (Alpha) is available behind a toggle; the toggle is only available in non-release branches such as `nightly-dev`.
@@ -349,11 +349,12 @@ A supported vehicle is one that just works when you install a comma device. All 8Some 2023 model years have HW4. To check which hardware type your vehicle has, look for Autopilot computer under Software -> Additional Vehicle Information on your vehicle's touchscreen. See this page for more information.
9See more setup details for Tesla.
10openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-11Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-12Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-13Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality.
-14Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
-15Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
+11The J533 harness plugs in at the CAN gateway under the dashboard, just above the steering column. More information can be found at this guide.
+12Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+13Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+14Some Škoda vehicles are equipped with heated windshields, which are known to block GPS signal needed for some comma four functionality.
+15Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+16Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). diff --git a/opendbc_repo b/opendbc_repo index e72e18e113b..00431e091fd 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit e72e18e113b8649fdda3e78b85110586751f1a81 +Subproject commit 00431e091fdbd2301a0104a44a40e5bffe37b917 From 8badc7d8133497f8b89e57e1e3afae454bb4f32f Mon Sep 17 00:00:00 2001 From: Jason Young <46612682+jyoung8607@users.noreply.github.com> Date: Sat, 28 Mar 2026 21:20:35 -0700 Subject: [PATCH 591/685] controls: HKG angle control saturation from car port safety (#37746) --- selfdrive/controls/lib/latcontrol_angle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index 808c9a659aa..a7d04032487 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -11,7 +11,7 @@ class LatControlAngle(LatControl): def __init__(self, CP, CI, dt): super().__init__(CP, CI, dt) self.sat_check_min_speed = 5. - self.use_steer_limited_by_safety = CP.brand == "tesla" + self.use_steer_limited_by_safety = CP.brand in ("tesla", "hyundai") def update(self, active, CS, VM, params, steer_limited_by_safety, desired_curvature, curvature_limited, lat_delay): angle_log = log.ControlsState.LateralAngleState.new_message() From 1dec68014f73eccb4570821d6bfe2a1f6f8126e4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 30 Mar 2026 15:12:34 -0700 Subject: [PATCH 592/685] rivian gen2! --- RELEASES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index a3a95ae6a86..55141710d07 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,7 +1,8 @@ -Version 0.11.1 (2026-04-08) +Version 0.11.1 (2026-04-22) ======================== * New driver monitoring model * Improved image processing pipeline for driver camera +* Rivian R1S and R1T 2025 support thanks to lukasloetkolben! Version 0.11.0 (2026-03-17) ======================== From bf43c7e8c7f3033786c91ba49dacfa524badf127 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 31 Mar 2026 18:22:56 -0700 Subject: [PATCH 593/685] fix scaled exclamation point --- selfdrive/ui/mici/onroad/hud_renderer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 35d04fe702f..56d83992ffb 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -120,7 +120,7 @@ def __init__(self): self._txt_wheel: rl.Texture = gui_app.texture('icons_mici/wheel.png', 50, 50) self._txt_wheel_critical: rl.Texture = gui_app.texture('icons_mici/wheel_critical.png', 50, 50) - self._txt_exclamation_point: rl.Texture = gui_app.texture('icons_mici/exclamation_point.png', 44, 44) + self._txt_exclamation_point: rl.Texture = gui_app.texture('icons_mici/exclamation_point.png', 9, 44) self._wheel_alpha_filter = FirstOrderFilter(0, 0.05, 1 / gui_app.target_fps) self._wheel_y_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) From 5dcaf3bef877a5ddc9fa7f89f185624b81a82ab7 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Wed, 1 Apr 2026 00:40:13 -0700 Subject: [PATCH 594/685] DM: fewer alerts during maneuvers (#37751) * 2in1 * clip * drop aodm lowspeed * cleanup * add lower bd * that was random --- selfdrive/monitoring/helpers.py | 29 +++++++++++++++++-------- selfdrive/monitoring/test_monitoring.py | 8 +++---- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 0b54504b647..19d169da428 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -1,4 +1,4 @@ -from math import atan2 +from math import atan2, radians import numpy as np from cereal import car, log @@ -43,6 +43,9 @@ def __init__(self, device_type): self._POSE_YAW_THRESHOLD = 0.4020 self._POSE_YAW_THRESHOLD_SLACK = 0.5042 self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD + self._POSE_YAW_MIN_STEER_DEG = 30 + self._POSE_YAW_STEER_FACTOR = 0.15 + self._POSE_YAW_STEER_MAX_OFFSET = 0.3927 self._PITCH_NATURAL_OFFSET = 0.011 # initial value before offset is learned self._PITCH_NATURAL_THRESHOLD = 0.449 self._YAW_NATURAL_OFFSET = 0.075 # initial value before offset is learned @@ -59,7 +62,6 @@ def __init__(self, device_type): self._POSESTD_THRESHOLD = 0.3 self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz - self._ALWAYS_ON_ALERT_MIN_SPEED = 11 self._POSE_CALIB_MIN_SPEED = 13 # 30 mph self._POSE_OFFSET_MIN_COUNT = int(60 / self._DT_DMON) # valid data counts before calibration completes, 1min cumulative @@ -101,6 +103,7 @@ def __init__(self, settings): self.low_std = True self.cfactor_pitch = 1. self.cfactor_yaw = 1. + self.steer_yaw_offset = 0. class DriverProb: def __init__(self, raw_priors, max_trackable): @@ -238,7 +241,11 @@ def _get_distracted_types(self): yaw_error = self.pose.yaw - min(max(self.pose.yaw_offseter.filtered_stat.mean(), self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET) pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit - yaw_error = abs(yaw_error) + + if yaw_error * self.pose.steer_yaw_offset > 0: # unidirectional + yaw_error = max(abs(yaw_error) - min(abs(self.pose.steer_yaw_offset), self.settings._POSE_YAW_STEER_MAX_OFFSET), 0.) + else: + yaw_error = abs(yaw_error) pitch_threshold = self.settings._POSE_PITCH_THRESHOLD * self.pose.cfactor_pitch if self.pose.calibrated else self.settings._PITCH_NATURAL_THRESHOLD yaw_threshold = self.settings._POSE_YAW_THRESHOLD * self.pose.cfactor_yaw @@ -254,7 +261,7 @@ def _get_distracted_types(self): return distracted_types - def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstill, demo_mode=False): + def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstill, demo_mode=False, steering_angle_deg=0.): rhd_pred = driver_state.wheelOnRightProb # calibrates only when there's movement and either face detected if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or @@ -277,8 +284,11 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil self.face_detected = driver_data.faceProb > self.settings._FACE_THRESHOLD self.pose.roll, self.pose.pitch, self.pose.yaw = face_orientation_from_net(driver_data.faceOrientation, driver_data.facePosition, cal_rpy) + steer_d = max(abs(steering_angle_deg) - self.settings._POSE_YAW_MIN_STEER_DEG, 0.) + self.pose.steer_yaw_offset = radians(steer_d) * -np.sign(steering_angle_deg) * self.settings._POSE_YAW_STEER_FACTOR if self.wheel_on_right: self.pose.yaw *= -1 + self.pose.steer_yaw_offset *= -1 self.wheel_on_right_last = self.wheel_on_right self.pose.pitch_std = driver_data.faceOrientationStd[0] self.pose.yaw_std = driver_data.faceOrientationStd[1] @@ -360,19 +370,19 @@ def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car if self.awareness > self.threshold_prompt: return + _reaching_pre = self.awareness - self.step_change <= self.threshold_pre _reaching_audible = self.awareness - self.step_change <= self.threshold_prompt _reaching_terminal = self.awareness - self.step_change <= 0 - standstill_orange_exemption = standstill and _reaching_audible + standstill_exemption = standstill and _reaching_pre always_on_red_exemption = always_on_valid and not op_engaged and _reaching_terminal - always_on_lowspeed_exemption = always_on_valid and not op_engaged and car_speed < self.settings._ALWAYS_ON_ALERT_MIN_SPEED certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected if certainly_distracted or maybe_distracted: - # should always be counting if distracted unless at standstill (lowspeed for always-on) and reaching orange + # should always be counting if distracted unless at standstill and reaching green # also will not be reaching 0 if DM is active when not engaged - if not (standstill_orange_exemption or always_on_red_exemption or (always_on_lowspeed_exemption and _reaching_audible)): + if not (standstill_exemption or always_on_red_exemption): self.awareness = max(self.awareness - self.step_change, -0.1) alert = None @@ -385,7 +395,7 @@ def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car elif self.awareness <= self.threshold_prompt: # prompt orange alert alert = EventName.promptDriverDistracted if self.active_monitoring_mode else EventName.promptDriverUnresponsive - elif self.awareness <= self.threshold_pre and not always_on_lowspeed_exemption: + elif self.awareness <= self.threshold_pre: # pre green alert alert = EventName.preDriverDistracted if self.active_monitoring_mode else EventName.preDriverUnresponsive @@ -451,6 +461,7 @@ def run_step(self, sm, demo=False): op_engaged=enabled, standstill=standstill, demo_mode=demo, + steering_angle_deg=sm['carState'].steeringAngleDeg, ) # Update distraction events diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 6ea9b80283f..15eb2c86054 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -186,10 +186,10 @@ def test_long_traffic_light_victim(self): standstill_vector = always_true[:] standstill_vector[int(_redlight_time/DT_DMON):] = [False] * int((TEST_TIMESPAN-_redlight_time)/DT_DMON) events, d_status = self._run_seq(always_distracted, always_false, always_true, standstill_vector) - assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL+1)/DT_DMON)].names[0] == \ - EventName.preDriverDistracted - assert events[int((_redlight_time-0.1)/DT_DMON)].names[0] == EventName.preDriverDistracted - assert events[int((_redlight_time+0.5)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert len(events[int((_redlight_time-0.1)/DT_DMON)]) == 0 + _pre_to_prompt = d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL - d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + assert events[int((_redlight_time+0.5)/DT_DMON)].names[0] == EventName.preDriverDistracted + assert events[int((_redlight_time+_pre_to_prompt+0.5)/DT_DMON)].names[0] == EventName.promptDriverDistracted # engaged, model is somehow uncertain and driver is distracted # - should fall back to wheel touch after uncertain alert From efd5301f6534d6dcb8bcc22bda462450c7afc8d7 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Wed, 1 Apr 2026 16:11:07 -0700 Subject: [PATCH 595/685] bump opendbc (#37750) * bump opendbc --- opendbc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opendbc_repo b/opendbc_repo index 00431e091fd..ef70686afee 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 00431e091fdbd2301a0104a44a40e5bffe37b917 +Subproject commit ef70686afee3e0fe5e6be4938eaafc52e9e77935 From d8569b07ebeed5fcb2b466fcd3824b968b4a8271 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Wed, 1 Apr 2026 16:14:15 -0700 Subject: [PATCH 596/685] DM: Lancia Delta HF Integrale model (#37696) * 00c00ac7-7b6e-4546-b86f-7ddd5f0596b4 * mici cleanup * update msg * rename --- cereal/log.capnp | 12 +++++----- .../assets/icons_mici/onroad/glasses.png | 3 --- selfdrive/modeld/dmonitoringmodeld.py | 9 +++----- .../modeld/models/dmonitoring_model.onnx | 4 ++-- selfdrive/monitoring/helpers.py | 19 +++++----------- selfdrive/monitoring/test_monitoring.py | 6 ++--- selfdrive/test/process_replay/model_replay.py | 2 +- .../ui/mici/onroad/driver_camera_dialog.py | 22 ++++--------------- 8 files changed, 24 insertions(+), 53 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/onroad/glasses.png diff --git a/cereal/log.capnp b/cereal/log.capnp index d8e9f563165..56af5ae5f63 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2176,12 +2176,14 @@ struct DriverStateV2 { facePosition @2 :List(Float32); facePositionStd @3 :List(Float32); faceProb @4 :Float32; - leftEyeProb @5 :Float32; - rightEyeProb @6 :Float32; - leftBlinkProb @7 :Float32; - rightBlinkProb @8 :Float32; - sunglassesProb @9 :Float32; + eyesVisibleProb @14 :Float32; + eyesClosedProb @15 :Float32; phoneProb @13 :Float32; + leftEyeProbDEPRECATED @5 :Float32; + rightEyeProbDEPRECATED @6 :Float32; + leftBlinkProbDEPRECATED @7 :Float32; + rightBlinkProbDEPRECATED @8 :Float32; + sunglassesProbDEPRECATED @9 :Float32; notReadyProbDEPRECATED @12 :List(Float32); occludedProbDEPRECATED @10 :Float32; readyProbDEPRECATED @11 :List(Float32); diff --git a/selfdrive/assets/icons_mici/onroad/glasses.png b/selfdrive/assets/icons_mici/onroad/glasses.png deleted file mode 100644 index 006972fd397..00000000000 --- a/selfdrive/assets/icons_mici/onroad/glasses.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:56de402482b5987ed9a0ff3f793a1c89f857304b34fbb8a3deb5b5d4a332be1c -size 3688 diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 28190db3e68..efd8214b9fa 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -80,7 +80,7 @@ def parse_model_output(model_output): face_descs = model_output[f'face_descs_{ds_suffix}'] parsed[f'face_descs_{ds_suffix}'] = face_descs[:, :-6] parsed[f'face_descs_{ds_suffix}_std'] = safe_exp(face_descs[:, -6:]) - for key in ['face_prob', 'left_eye_prob', 'right_eye_prob','left_blink_prob', 'right_blink_prob', 'sunglasses_prob', 'using_phone_prob']: + for key in ['face_prob', 'eyes_visible_prob', 'eyes_closed_prob', 'using_phone_prob']: parsed[f'{key}_{ds_suffix}'] = sigmoid(model_output[f'{key}_{ds_suffix}']) return parsed @@ -90,11 +90,8 @@ def fill_driver_data(msg, model_output, ds_suffix): msg.facePosition = model_output[f'face_descs_{ds_suffix}'][0, 3:5].tolist() msg.facePositionStd = model_output[f'face_descs_{ds_suffix}_std'][0, 3:5].tolist() msg.faceProb = model_output[f'face_prob_{ds_suffix}'][0, 0].item() - msg.leftEyeProb = model_output[f'left_eye_prob_{ds_suffix}'][0, 0].item() - msg.rightEyeProb = model_output[f'right_eye_prob_{ds_suffix}'][0, 0].item() - msg.leftBlinkProb = model_output[f'left_blink_prob_{ds_suffix}'][0, 0].item() - msg.rightBlinkProb = model_output[f'right_blink_prob_{ds_suffix}'][0, 0].item() - msg.sunglassesProb = model_output[f'sunglasses_prob_{ds_suffix}'][0, 0].item() + msg.eyesVisibleProb = model_output[f'eyes_visible_prob_{ds_suffix}'][0, 0].item() + msg.eyesClosedProb = model_output[f'eyes_closed_prob_{ds_suffix}'][0, 0].item() msg.phoneProb = model_output[f'using_phone_prob_{ds_suffix}'][0, 0].item() def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_time: float, gpu_exec_time: float): diff --git a/selfdrive/modeld/models/dmonitoring_model.onnx b/selfdrive/modeld/models/dmonitoring_model.onnx index dc621bed03a..628f3857969 100644 --- a/selfdrive/modeld/models/dmonitoring_model.onnx +++ b/selfdrive/modeld/models/dmonitoring_model.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7aff7ff1dc08bbaf562a8f77380ab5e5914f8557dba2f760d87e4d953f5604b0 -size 7307246 +oid sha256:2fd471febb6e973313ac0d0c6755f6410c1937ba92230b58a433761e8c883072 +size 7364290 diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 19d169da428..90cc565802d 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -32,9 +32,8 @@ def __init__(self, device_type): self._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6. self._FACE_THRESHOLD = 0.7 - self._EYE_THRESHOLD = 0.65 - self._SG_THRESHOLD = 0.9 - self._BLINK_THRESHOLD = 0.865 + self._EYE_THRESHOLD = 0.5 + self._BLINK_THRESHOLD = 0.5 self._PHONE_THRESH = 0.5 self._POSE_PITCH_THRESHOLD = 0.3133 @@ -111,11 +110,6 @@ def __init__(self, raw_priors, max_trackable): self.prob_offseter = RunningStatFilter(raw_priors=raw_priors, max_trackable=max_trackable) self.prob_calibrated = False -class DriverBlink: - def __init__(self): - self.left = 0. - self.right = 0. - # model output refers to center of undistorted+leveled image EFL = 598.0 # focal length in K @@ -150,7 +144,7 @@ def __init__(self, rhd_saved=False, settings=None, always_on=False): wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) self.pose = DriverPose(settings=self.settings) - self.blink = DriverBlink() + self.blink_prob = 0. self.phone_prob = 0. self.always_on = always_on @@ -253,7 +247,7 @@ def _get_distracted_types(self): if pitch_error > pitch_threshold or yaw_error > yaw_threshold: distracted_types.append(DistractedType.DISTRACTED_POSE) - if (self.blink.left + self.blink.right)*0.5 > self.settings._BLINK_THRESHOLD: + if self.blink_prob > self.settings._BLINK_THRESHOLD: distracted_types.append(DistractedType.DISTRACTED_BLINK) if self.phone_prob > self.settings._PHONE_THRESH: @@ -294,10 +288,7 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil self.pose.yaw_std = driver_data.faceOrientationStd[1] model_std_max = max(self.pose.pitch_std, self.pose.yaw_std) self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD - self.blink.left = driver_data.leftBlinkProb * (driver_data.leftEyeProb > self.settings._EYE_THRESHOLD) \ - * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) - self.blink.right = driver_data.rightBlinkProb * (driver_data.rightEyeProb > self.settings._EYE_THRESHOLD) \ - * (driver_data.sunglassesProb < self.settings._SG_THRESHOLD) + self.blink_prob = driver_data.eyesClosedProb * (driver_data.eyesVisibleProb > self.settings._EYE_THRESHOLD) self.phone_prob = driver_data.phoneProb self.distracted_types = self._get_distracted_types() diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 15eb2c86054..733ea85bc04 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -19,10 +19,8 @@ def make_msg(face_detected, distracted=False, model_uncertain=False): ds.leftDriverData.faceOrientation = [0., 0., 0.] ds.leftDriverData.facePosition = [0., 0.] ds.leftDriverData.faceProb = 1. * face_detected - ds.leftDriverData.leftEyeProb = 1. - ds.leftDriverData.rightEyeProb = 1. - ds.leftDriverData.leftBlinkProb = 1. * distracted - ds.leftDriverData.rightBlinkProb = 1. * distracted + ds.leftDriverData.eyesVisibleProb = 1. + ds.leftDriverData.eyesClosedProb = 1. * distracted ds.leftDriverData.faceOrientationStd = [1.*model_uncertain, 1.*model_uncertain, 1.*model_uncertain] ds.leftDriverData.facePositionStd = [1.*model_uncertain, 1.*model_uncertain] # TODO: test both separately when e2e is used diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index a6ccaa10477..eb7cdbe34ac 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -76,7 +76,7 @@ def generate_report(proposed, master, tmp, commit): (lambda x: get_idx_if_non_empty(x.wheelOnRightProb), "wheelOnRightProb"), (lambda x: get_idx_if_non_empty(x.leftDriverData.faceProb), "leftDriverData.faceProb"), (lambda x: get_idx_if_non_empty(x.leftDriverData.faceOrientation, 0), "leftDriverData.faceOrientation0"), - (lambda x: get_idx_if_non_empty(x.leftDriverData.leftBlinkProb), "leftDriverData.leftBlinkProb"), + (lambda x: get_idx_if_non_empty(x.leftDriverData.eyesClosedProb), "leftDriverData.eyesClosedProb"), (lambda x: get_idx_if_non_empty(x.leftDriverData.phoneProb), "leftDriverData.phoneProb"), (lambda x: get_idx_if_non_empty(x.rightDriverData.faceProb), "rightDriverData.faceProb"), ], "driverStateV2") diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index e8321b099c7..e8b8abb7f5c 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -39,8 +39,6 @@ def __init__(self): self._eye_fill_texture = None self._eye_orange_texture = None self._eye_size = 74 - self._glasses_texture = None - self._glasses_size = 171 self._load_eye_textures() @@ -154,8 +152,6 @@ def _load_eye_textures(self): self._eye_fill_texture = gui_app.texture("icons_mici/onroad/eye_fill.png", self._eye_size, self._eye_size) if self._eye_orange_texture is None: self._eye_orange_texture = gui_app.texture("icons_mici/onroad/eye_orange.png", self._eye_size, self._eye_size) - if self._glasses_texture is None: - self._glasses_texture = gui_app.texture("icons_mici/onroad/glasses.png", self._glasses_size, self._glasses_size) def _draw_face_detection(self, rect: rl.Rectangle): dm_state = ui_state.sm["driverMonitoringState"] @@ -202,31 +198,21 @@ def _draw_eyes(self, rect: rl.Rectangle, driver_data): eye_offset_x = 10 eye_offset_y = 10 eye_spacing = self._eye_size + 15 + eyes_prob = driver_data.eyesVisibleProb left_eye_x = rect.x + eye_offset_x left_eye_y = rect.y + eye_offset_y - left_eye_prob = driver_data.leftEyeProb right_eye_x = rect.x + eye_offset_x + eye_spacing right_eye_y = rect.y + eye_offset_y - right_eye_prob = driver_data.rightEyeProb # Draw eyes with opacity based on probability - for eye_x, eye_y, eye_prob in [(left_eye_x, left_eye_y, left_eye_prob), (right_eye_x, right_eye_y, right_eye_prob)]: - fill_opacity = eye_prob - orange_opacity = 1.0 - eye_prob - + fill_opacity = eyes_prob + orange_opacity = 1.0 - eyes_prob + for eye_x, eye_y in [(left_eye_x, left_eye_y), (right_eye_x, right_eye_y)]: rl.draw_texture_v(self._eye_orange_texture, (eye_x, eye_y), rl.Color(255, 255, 255, int(255 * orange_opacity))) rl.draw_texture_v(self._eye_fill_texture, (eye_x, eye_y), rl.Color(255, 255, 255, int(255 * fill_opacity))) - # Draw sunglasses indicator based on sunglasses probability - # Position glasses centered between the two eyes at top left - glasses_x = rect.x + eye_offset_x - 4 - glasses_y = rect.y - glasses_pos = rl.Vector2(glasses_x, glasses_y) - glasses_prob = driver_data.sunglassesProb - rl.draw_texture_v(self._glasses_texture, glasses_pos, rl.Color(70, 80, 161, int(255 * glasses_prob))) - class DriverCameraDialog(NavWidget, BaseDriverCameraDialog): def __init__(self): From cb327933002bc1a00bf60a3c20af2eb7a5f653e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 1 Apr 2026 16:24:50 -0700 Subject: [PATCH 597/685] OP model (#37740) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Off policy model * 2f70b996-c604-4a46-9ac9-13ce7534605b/100 * misc fixes * 1cc1791b-4555-41ce-a5cb-ce046967075a/100 * fix model * 6ab6fae5-fbbd-4ad0-928a-b33794f60dba/100 * recomp * update models * qxfinally correct * b8b96ac6-7918-401a-a862-eaf1fdbba88d/100 * wrong plan * wrong plan * Vf9b3fb5f-4d0d-4dcb-bc3a-5e94d1fdcdaa/200 * bump dbc * ready to merge * rename to on-policy * Just cleanup big models for now --------- Co-authored-by: Kacper Rączy --- scripts/reporter.py | 9 +++-- selfdrive/modeld/SConscript | 18 ++-------- selfdrive/modeld/modeld.py | 34 ++++++++++++++----- .../modeld/models/big_driving_policy.onnx | 1 - .../modeld/models/big_driving_vision.onnx | 1 - .../modeld/models/driving_off_policy.onnx | 3 ++ .../modeld/models/driving_on_policy.onnx | 3 ++ selfdrive/modeld/models/driving_policy.onnx | 3 -- selfdrive/modeld/models/driving_vision.onnx | 4 +-- selfdrive/modeld/parse_model_outputs.py | 11 ++++-- 10 files changed, 52 insertions(+), 35 deletions(-) delete mode 120000 selfdrive/modeld/models/big_driving_policy.onnx delete mode 120000 selfdrive/modeld/models/big_driving_vision.onnx create mode 100644 selfdrive/modeld/models/driving_off_policy.onnx create mode 100644 selfdrive/modeld/models/driving_on_policy.onnx delete mode 100644 selfdrive/modeld/models/driving_policy.onnx diff --git a/scripts/reporter.py b/scripts/reporter.py index d894b8af48a..64f6cb99b83 100755 --- a/scripts/reporter.py +++ b/scripts/reporter.py @@ -38,6 +38,11 @@ def get_checkpoint(f): continue fn = os.path.basename(f) - master = get_checkpoint(MASTER_PATH + MODEL_PATH + fn) + master_path = MASTER_PATH + MODEL_PATH + fn + if os.path.exists(master_path): + master = get_checkpoint(master_path) + master_col = f"[{master}](https://reporter.comma.life/experiment/{master})" + else: + master_col = "N/A (new model)" pr = get_checkpoint(BASEDIR + MODEL_PATH + fn) - print("|", fn, "|", f"[{master}](https://reporter.comma.life/experiment/{master})", "|", f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|") + print("|", fn, "|", master_col, "|", f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|") diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 3f38324a6e9..7a82ff88b8e 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -21,7 +21,7 @@ tg_flags = { }.get(arch, 'DEV=CPU CPU_LLVM=1 THREADS=0') # Get model metadata -for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: +for model_name in ['driving_vision', 'driving_off_policy', 'driving_on_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)] cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' @@ -59,19 +59,5 @@ def tg_compile(flags, model_name): ) # Compile small models -for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: +for model_name in ['driving_vision', 'driving_off_policy', 'driving_on_policy', 'dmonitoring_model']: tg_compile(tg_flags, model_name) - -# Compile BIG model if USB GPU is available -if "USBGPU" in os.environ: - import subprocess - # because tg doesn't support multi-process - devs = subprocess.check_output('python3 -c "from tinygrad import Device; print(list(Device.get_available_devices()))"', shell=True, cwd=env.Dir('#').abspath) - if b"AMD" in devs: - print("USB GPU detected... building") - flags = "DEV=AMD AMD_IFACE=USB AMD_LLVM=1 NOLOCALS=0 IMAGE=0" - bp = tg_compile(flags, "big_driving_policy") - bv = tg_compile(flags, "big_driving_vision") - lenv.SideEffect('lock', [bp, bv]) # tg doesn't support multi-process so build serially - else: - print("USB GPU not detected... skipping") diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index bff59366d6e..6421ecfd216 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -34,11 +34,13 @@ PROCESS_NAME = "selfdrive.modeld.modeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') -VISION_PKL_PATH = Path(__file__).parent / 'models/driving_vision_tinygrad.pkl' -POLICY_PKL_PATH = Path(__file__).parent / 'models/driving_policy_tinygrad.pkl' -VISION_METADATA_PATH = Path(__file__).parent / 'models/driving_vision_metadata.pkl' -POLICY_METADATA_PATH = Path(__file__).parent / 'models/driving_policy_metadata.pkl' MODELS_DIR = Path(__file__).parent / 'models' +VISION_PKL_PATH = MODELS_DIR / 'driving_vision_tinygrad.pkl' +VISION_METADATA_PATH = MODELS_DIR / 'driving_vision_metadata.pkl' +ON_POLICY_PKL_PATH = MODELS_DIR / 'driving_on_policy_tinygrad.pkl' +ON_POLICY_METADATA_PATH = MODELS_DIR / 'driving_on_policy_metadata.pkl' +OFF_POLICY_PKL_PATH = MODELS_DIR / 'driving_off_policy_tinygrad.pkl' +OFF_POLICY_METADATA_PATH = MODELS_DIR / 'driving_off_policy_metadata.pkl' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 @@ -151,7 +153,13 @@ def __init__(self): self.vision_output_slices = vision_metadata['output_slices'] vision_output_size = vision_metadata['output_shapes']['outputs'][1] - with open(POLICY_METADATA_PATH, 'rb') as f: + with open(OFF_POLICY_METADATA_PATH, 'rb') as f: + off_policy_metadata = pickle.load(f) + self.off_policy_input_shapes = off_policy_metadata['input_shapes'] + self.off_policy_output_slices = off_policy_metadata['output_slices'] + off_policy_output_size = off_policy_metadata['output_shapes']['outputs'][1] + + with open(ON_POLICY_METADATA_PATH, 'rb') as f: policy_metadata = pickle.load(f) self.policy_input_shapes = policy_metadata['input_shapes'] self.policy_output_slices = policy_metadata['output_slices'] @@ -175,11 +183,13 @@ def __init__(self): self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) + self.off_policy_output = np.zeros(off_policy_output_size, dtype=np.float32) self.parser = Parser() self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) - self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) + self.policy_run = pickle.loads(read_file_chunked(str(ON_POLICY_PKL_PATH))) + self.off_policy_run = pickle.loads(read_file_chunked(str(OFF_POLICY_PKL_PATH))) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -228,9 +238,17 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) - combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} + + self.off_policy_output = self.off_policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() + off_policy_outputs_dict = self.parser.parse_off_policy_outputs(self.slice_outputs(self.off_policy_output, self.off_policy_output_slices)) + off_policy_outputs_dict.pop('plan') + + + combined_outputs_dict = {**vision_outputs_dict, **off_policy_outputs_dict, **policy_outputs_dict} + if 'planplus' in combined_outputs_dict and 'plan' in combined_outputs_dict: + combined_outputs_dict['plan'] = combined_outputs_dict['plan'] + combined_outputs_dict['planplus'] if SEND_RAW_PRED: - combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) + combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy(), self.off_policy_output.copy()]) return combined_outputs_dict diff --git a/selfdrive/modeld/models/big_driving_policy.onnx b/selfdrive/modeld/models/big_driving_policy.onnx deleted file mode 120000 index e1b653a14a0..00000000000 --- a/selfdrive/modeld/models/big_driving_policy.onnx +++ /dev/null @@ -1 +0,0 @@ -driving_policy.onnx \ No newline at end of file diff --git a/selfdrive/modeld/models/big_driving_vision.onnx b/selfdrive/modeld/models/big_driving_vision.onnx deleted file mode 120000 index 28ee71dd746..00000000000 --- a/selfdrive/modeld/models/big_driving_vision.onnx +++ /dev/null @@ -1 +0,0 @@ -driving_vision.onnx \ No newline at end of file diff --git a/selfdrive/modeld/models/driving_off_policy.onnx b/selfdrive/modeld/models/driving_off_policy.onnx new file mode 100644 index 00000000000..33f6e0a2c1e --- /dev/null +++ b/selfdrive/modeld/models/driving_off_policy.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eb6992bd60bada6162fea298e1a414b6b3d6a326db4eda46b9de62bcd8554754 +size 13393859 diff --git a/selfdrive/modeld/models/driving_on_policy.onnx b/selfdrive/modeld/models/driving_on_policy.onnx new file mode 100644 index 00000000000..cbc072409a1 --- /dev/null +++ b/selfdrive/modeld/models/driving_on_policy.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:86680a657bbb34f997034d1930bb2cb65c38b9222cea199732f72bd45791cfad +size 13022803 diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx deleted file mode 100644 index 7c71bc94714..00000000000 --- a/selfdrive/modeld/models/driving_policy.onnx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:853c6634746ff439a848349d00e4d5581cd941f13f7c1862c31b72a31cc24858 -size 14061595 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index afd617667c6..383cde35e43 100644 --- a/selfdrive/modeld/models/driving_vision.onnx +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:940e9006a25f27f0b6e85da798e6a8fd1f6dd492dd7d0b9ff1a9436460f46129 -size 46887794 +oid sha256:7af05e03fd170653ff5771baf373a2c57b363da12c4c411cd416dee067b4cf58 +size 23266366 diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 5c11e8ca185..802f0ad8599 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -96,11 +96,17 @@ def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndar self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) + self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) + self.parse_binary_crossentropy('meta', outs) + return outs + + def parse_off_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: + plan_mhp = self.is_mhp(outs, 'plan', ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH) + plan_in_N, plan_out_N = (ModelConstants.PLAN_MHP_N, ModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0) + self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)) self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) self.parse_binary_crossentropy('lane_lines_prob', outs) - self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) - self.parse_binary_crossentropy('meta', outs) self.parse_binary_crossentropy('lead_prob', outs) lead_mhp = self.is_mhp(outs, 'lead', ModelConstants.LEAD_MHP_SELECTION * ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH) lead_in_N, lead_out_N = (ModelConstants.LEAD_MHP_N, ModelConstants.LEAD_MHP_SELECTION) if lead_mhp else (0, 0) @@ -120,5 +126,6 @@ def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndar def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: outs = self.parse_vision_outputs(outs) + outs = self.parse_off_policy_outputs(outs) outs = self.parse_policy_outputs(outs) return outs From 55c3885742bd13272bff2eadfa98ba010867c0cd Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Thu, 2 Apr 2026 09:16:11 -0700 Subject: [PATCH 598/685] bump tg (#37700) * bump tg * bump tg * assign * bump * cpu llvm * frame buffer updated in place, no need to return * don't bake in stale pointers * fix update image output indices * lint * bump --- selfdrive/modeld/SConscript | 2 +- selfdrive/modeld/compile_warp.py | 31 ++++++++++--------------------- selfdrive/modeld/modeld.py | 3 +-- tinygrad_repo | 2 +- 4 files changed, 13 insertions(+), 25 deletions(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 7a82ff88b8e..bad1cdd5003 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -18,7 +18,7 @@ def estimate_pickle_max_size(onnx_size): tg_flags = { 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', 'Darwin': f'DEV=CPU THREADS=0 HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env -}.get(arch, 'DEV=CPU CPU_LLVM=1 THREADS=0') +}.get(arch, 'DEV=CPU:LLVM THREADS=0') # Get model metadata for model_name in ['driving_vision', 'driving_off_policy', 'driving_on_policy', 'dmonitoring_model']: diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py index 75cc65f84cb..47511f2a2b6 100755 --- a/selfdrive/modeld/compile_warp.py +++ b/selfdrive/modeld/compile_warp.py @@ -94,11 +94,11 @@ def frame_prepare_tinygrad(input_frame, M_inv): def make_update_img_input(frame_prepare, model_w, model_h): - def update_img_input_tinygrad(tensor, frame, M_inv): + def update_img_input_tinygrad(frame_buffer, frame, M_inv): M_inv = M_inv.to(Device.DEFAULT) new_img = frame_prepare(frame, M_inv) - full_buffer = tensor[6:].cat(new_img, dim=0).contiguous() - return full_buffer, Tensor.cat(full_buffer[:6], full_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) + frame_buffer.assign(frame_buffer[6:].cat(new_img, dim=0).contiguous()) + return Tensor.cat(frame_buffer[:6], frame_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) return update_img_input_tinygrad @@ -107,9 +107,9 @@ def make_update_both_imgs(frame_prepare, model_w, model_h): def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, calib_big_img_buffer, new_big_img, M_inv_big): - calib_img_buffer, calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) - calib_big_img_buffer, calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) - return calib_img_buffer, calib_img_pair, calib_big_img_buffer, calib_big_img_pair + calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) + calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) + return calib_img_pair, calib_big_img_pair return update_both_imgs_tinygrad @@ -136,29 +136,18 @@ def compile_modeld_warp(cam_w, cam_h): full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - big_full_buffer_np = np.zeros(IMG_BUFFER_SHAPE, dtype=np.uint8) - for i in range(10): - new_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) img_inputs = [full_buffer, - Tensor.from_blob(new_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize(), Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - new_big_frame_np = (32 * np.random.randn(yuv_size).astype(np.float32) + 128).clip(0, 255).astype(np.uint8) big_img_inputs = [big_full_buffer, - Tensor.from_blob(new_big_frame_np.ctypes.data, (yuv_size,), dtype='uint8').realize(), + Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize(), Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] inputs = img_inputs + big_img_inputs Device.default.synchronize() - inputs_np = [x.numpy() for x in inputs] - inputs_np[0] = full_buffer_np - inputs_np[3] = big_full_buffer_np - st = time.perf_counter() - out = update_img_jit(*inputs) - full_buffer = out[0].contiguous().realize().clone() - big_full_buffer = out[2].contiguous().realize().clone() + _ = update_img_jit(*inputs) mt = time.perf_counter() Device.default.synchronize() et = time.perf_counter() @@ -183,7 +172,7 @@ def compile_dm_warp(cam_w, cam_h): warp_dm_jit = TinyJit(warp_dm, prune=True) for i in range(10): - inputs = [Tensor.from_blob((32 * Tensor.randn(yuv_size,) + 128).cast(dtype='uint8').realize().numpy().ctypes.data, (yuv_size,), dtype='uint8'), + inputs = [Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize(), Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] Device.default.synchronize() st = time.perf_counter() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 6421ecfd216..07c3af4b7e3 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -222,8 +222,7 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) - self.img_queues['img'], self.img_queues['big_img'] = out[0].realize(), out[2].realize() - vision_inputs = {'img': out[1], 'big_img': out[3]} + vision_inputs = {'img': out[0], 'big_img': out[1]} if prepare_only: return None diff --git a/tinygrad_repo b/tinygrad_repo index 2f55005ad93..1aa04eab086 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 2f55005ad93c777cca69b20dddc28c7f02f0eb01 +Subproject commit 1aa04eab086d9c22855cfe50f746235200d28867 From 052692b25d63c5ddda276b5c2271383b6aff129f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Fri, 3 Apr 2026 09:34:03 -0700 Subject: [PATCH 599/685] OP model 7 (#37760) * a76ae294-e61a-43b8-b07e-c3496dbfc5ff/100 * recompile * unused * correct naming --- selfdrive/modeld/models/driving_off_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_on_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/selfdrive/modeld/models/driving_off_policy.onnx b/selfdrive/modeld/models/driving_off_policy.onnx index 33f6e0a2c1e..5b0effc1003 100644 --- a/selfdrive/modeld/models/driving_off_policy.onnx +++ b/selfdrive/modeld/models/driving_off_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eb6992bd60bada6162fea298e1a414b6b3d6a326db4eda46b9de62bcd8554754 -size 13393859 +oid sha256:e53f4e0527766082ba7bde38e275def0fe3c14f6c59ae2854439e239884d3ecc +size 13393365 diff --git a/selfdrive/modeld/models/driving_on_policy.onnx b/selfdrive/modeld/models/driving_on_policy.onnx index cbc072409a1..bfe10ca185b 100644 --- a/selfdrive/modeld/models/driving_on_policy.onnx +++ b/selfdrive/modeld/models/driving_on_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:86680a657bbb34f997034d1930bb2cb65c38b9222cea199732f72bd45791cfad -size 13022803 +oid sha256:ea89c50da3a16e710da292f97c81b083a982cfdee5c28eca0d37ed2fb99af6c5 +size 13022642 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 383cde35e43..335e9dbcf8e 100644 --- a/selfdrive/modeld/models/driving_vision.onnx +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7af05e03fd170653ff5771baf373a2c57b363da12c4c411cd416dee067b4cf58 -size 23266366 +oid sha256:6263aa3fbb44cde6c68a34cdb7cd8c389789dbc02b15c1911afdac4e018281ae +size 23267151 From f0053d46194bb75dc8f277d7ba6f91ea02effae7 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Fri, 3 Apr 2026 14:52:35 -0700 Subject: [PATCH 600/685] jotpluggler: state transition view is only for enums (#37761) * jotpluggler: state transition view is only for enums * cleaner --- tools/jotpluggler/plot.cc | 82 ++------------------------------------- 1 file changed, 3 insertions(+), 79 deletions(-) diff --git a/tools/jotpluggler/plot.cc b/tools/jotpluggler/plot.cc index a3c68ddcefa..5d9d644808c 100644 --- a/tools/jotpluggler/plot.cc +++ b/tools/jotpluggler/plot.cc @@ -6,7 +6,6 @@ #include #include #include -#include constexpr double PLOT_Y_PAD_FRACTION = 0.4; @@ -74,10 +73,6 @@ struct StateBlock { std::string label; }; -struct PaneEnumContext { - std::vector enums; -}; - struct PaneValueFormatContext { SeriesFormat format; bool valid = false; @@ -106,38 +101,6 @@ bool curves_are_bool_like(const std::vector &prepared_curves) { return true; } -bool curve_is_state_like(const PreparedCurve &curve) { - if (!curve.display_info.integer_like || curve.xs.size() < 2 || curve.xs.size() != curve.ys.size()) { - return false; - } - if (curve.enum_info != nullptr) { - return true; - } - std::unordered_set distinct_values; - for (double value : curve.ys) { - if (!std::isfinite(value)) { - continue; - } - distinct_values.insert(static_cast(std::llround(value))); - if (distinct_values.size() > 12) { - return false; - } - } - return !distinct_values.empty(); -} - -bool curves_use_state_blocks(const std::vector &prepared_curves) { - if (prepared_curves.empty()) { - return false; - } - for (const PreparedCurve &curve : prepared_curves) { - if (!curve_is_state_like(curve)) { - return false; - } - } - return true; -} - ImU32 state_block_color(int value, float alpha = 1.0f) { static constexpr std::array, 8> kPalette = {{ {{111, 143, 175}}, @@ -307,36 +270,6 @@ std::optional app_sample_xy_value_at_time(const std::vector &xs, return y0 + (y1 - y0) * alpha; } -int format_enum_axis_tick(double value, char *buf, int size, void *user_data) { - const auto *ctx = static_cast(user_data); - const int idx = static_cast(std::llround(value)); - if (ctx != nullptr && idx >= 0 && std::abs(value - static_cast(idx)) < 0.01) { - std::vector names; - names.reserve(ctx->enums.size()); - for (const EnumInfo *info : ctx->enums) { - if (info == nullptr || static_cast(idx) >= info->names.size()) { - continue; - } - const std::string &name = info->names[static_cast(idx)]; - if (name.empty()) continue; - if (std::find(names.begin(), names.end(), std::string_view(name)) == names.end()) { - names.emplace_back(name); - } - } - if (!names.empty()) { - std::string joined; - for (size_t i = 0; i < names.size(); ++i) { - if (i != 0) { - joined += ", "; - } - joined += names[i]; - } - return std::snprintf(buf, size, "%d (%s)", idx, joined.c_str()); - } - } - return std::snprintf(buf, size, "%.6g", value); -} - int format_numeric_axis_tick(double value, char *buf, int size, void *user_data) { const auto *ctx = static_cast(user_data); if (ctx == nullptr || !ctx->valid) { @@ -831,23 +764,16 @@ void draw_plot(const AppSession &session, Pane *pane, UiState *state) { } const PlotBounds bounds = compute_plot_bounds(*pane, prepared_curves, *state); - PaneEnumContext enum_context; PaneValueFormatContext pane_value_format; - const bool state_block_mode = curves_use_state_blocks(prepared_curves); - bool all_enum_curves = !prepared_curves.empty(); + bool state_block_mode = !prepared_curves.empty(); size_t max_legend_label_width = 0; for (const PreparedCurve &curve : prepared_curves) { max_legend_label_width = std::max(max_legend_label_width, curve.label.size()); - if (curve.enum_info != nullptr) { - enum_context.enums.push_back(curve.enum_info); - } else { - all_enum_curves = false; + if (curve.enum_info == nullptr) { + state_block_mode = false; merge_pane_value_format(&pane_value_format, curve.display_info); } } - if (prepared_curves.empty()) { - all_enum_curves = false; - } const int supported_count = static_cast(prepared_curves.size()); const ImVec2 plot_size = ImGui::GetContentRegionAvail(); const bool has_cursor_time = state->has_tracker_time; @@ -895,8 +821,6 @@ void draw_plot(const AppSession &session, Pane *pane, UiState *state) { ImPlot::SetupAxisFormat(ImAxis_X1, "%.1f"); if (state_block_mode) { ImPlot::SetupAxisLimits(ImAxis_Y1, 0.0, 1.0, ImPlotCond_Always); - } else if (all_enum_curves && !enum_context.enums.empty()) { - ImPlot::SetupAxisFormat(ImAxis_Y1, format_enum_axis_tick, &enum_context); } else if (pane_value_format.valid) { ImPlot::SetupAxisFormat(ImAxis_Y1, format_numeric_axis_tick, &pane_value_format); } else { From 310ba9d2c01eccf127f3005037bd98e717b616ad Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Fri, 3 Apr 2026 20:01:33 -0700 Subject: [PATCH 601/685] replay/ui: fix Qt threading issue (#37762) * fix ui * fix * clean up --- tools/replay/ui.py | 67 +++++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 7fe2f405a16..3f6eb1fdb73 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -3,7 +3,6 @@ import os import sys -import cv2 import numpy as np import pyray as rl @@ -22,7 +21,8 @@ plot_lead, plot_model, ) -from msgq.visionipc import VisionIpcClient, VisionStreamType +from msgq.visionipc import VisionStreamType +from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView os.environ['BASEDIR'] = BASEDIR @@ -30,8 +30,6 @@ def ui_thread(addr): - cv2.setNumThreads(1) - # Get monitor info before creating window rl.set_config_flags(rl.ConfigFlags.FLAG_MSAA_4X_HINT) rl.init_window(1, 1, "") @@ -59,14 +57,15 @@ def ui_thread(addr): font_path = os.path.join(BASEDIR, "selfdrive/assets/fonts/JetBrainsMono-Medium.ttf") font = rl.load_font_ex(font_path, 32, None, 0) - # Create textures for camera and top-down view - camera_image = rl.gen_image_color(640, 480, rl.BLACK) - camera_texture = rl.load_texture_from_image(camera_image) - rl.unload_image(camera_image) + camera_view = CameraView("camerad", VisionStreamType.VISION_STREAM_ROAD) + + # Overlay texture for model/lane line drawing + overlay_img = np.zeros((480, 640, 4), dtype='uint8') + overlay_image = rl.gen_image_color(640, 480, rl.BLANK) + overlay_texture = rl.load_texture_from_image(overlay_image) + rl.unload_image(overlay_image) # lid_overlay array is (lidar_x, lidar_y) = (384, 960) - # pygame treats first axis as width, so texture is 384 wide x 960 tall - # For raylib, we need to transpose to get (height, width) = (960, 384) for the RGBA array top_down_image = rl.gen_image_color(UP.lidar_x, UP.lidar_y, rl.BLACK) top_down_texture = rl.load_texture_from_image(top_down_image) rl.unload_image(top_down_image) @@ -89,7 +88,6 @@ def ui_thread(addr): ) img = np.zeros((480, 640, 3), dtype='uint8') - imgff = None num_px = 0 calibration = None @@ -138,20 +136,16 @@ def ui_thread(addr): palette[110] = [110, 110, 110, 255] # car_color (gray) palette[255] = [255, 255, 255, 255] # WHITE - vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_ROAD, True) while not rl.window_should_close(): - # ***** frame ***** - if not vipc_client.is_connected(): - vipc_client.connect(False) - rl.begin_drawing() rl.clear_background(rl.Color(64, 64, 64, 255)) - yuv_img_raw = vipc_client.recv() - if yuv_img_raw is None or not yuv_img_raw.data.any(): - rl.draw_text_ex(font, "waiting for frames", rl.Vector2(200, 200), 30, 0, rl.WHITE) - rl.end_drawing() - continue + # Render camera (NV12->RGB on GPU via shader) + if camera_view.frame: + cam_h = 640.0 * camera_view.frame.height / camera_view.frame.width + else: + cam_h = 480.0 + camera_view.render(rl.Rectangle(0, 0, 640, cam_h)) lid_overlay = lid_overlay_blank.copy() top_down = top_down_texture, lid_overlay @@ -159,19 +153,10 @@ def ui_thread(addr): sm.update(0) camera = DEVICE_CAMERAS[("tici", str(sm['roadCameraState'].sensor))] - - # Use received buffer dimensions (full HEVC can have stride != buffer_len/rows due to VENUS padding) - h, w, stride = yuv_img_raw.height, yuv_img_raw.width, yuv_img_raw.stride - nv12_size = h * 3 // 2 * stride - imgff = np.frombuffer(yuv_img_raw.data, dtype=np.uint8, count=nv12_size).reshape((h * 3 // 2, stride)) - num_px = w * h - rgb = cv2.cvtColor(imgff[: h * 3 // 2, : w], cv2.COLOR_YUV2RGB_NV12) - - qcam = "QCAM" in os.environ - bb_scale = (528 if qcam else camera.fcam.width) / 640.0 calib_scale = camera.fcam.width / 640.0 - zoom_matrix = np.asarray([[bb_scale, 0.0, 0.0], [0.0, bb_scale, 0.0], [0.0, 0.0, 1.0]]) - cv2.warpAffine(rgb, zoom_matrix[:2], (img.shape[1], img.shape[0]), dst=img, flags=cv2.WARP_INVERSE_MAP) + + if camera_view.frame: + num_px = camera_view.frame.width * camera_view.frame.height intrinsic_matrix = camera.fcam.intrinsics @@ -201,6 +186,8 @@ def ui_thread(addr): if len(sm['longitudinalPlan'].accels): plot_arr[-1, name_to_arr_idx['a_target']] = sm['longitudinalPlan'].accels[0] + # Draw model overlays onto img, then blit as transparent overlay + img[:] = 0 if sm.recv_frame['modelV2']: plot_model(sm['modelV2'], img, calibration, top_down) @@ -214,11 +201,12 @@ def ui_thread(addr): rpyCalib = np.asarray(sm['liveCalibration'].rpyCalib) calibration = Calibration(num_px, rpyCalib, intrinsic_matrix, calib_scale) - # *** blits *** - # Update camera texture from numpy array - img_rgba = cv2.cvtColor(img, cv2.COLOR_RGB2RGBA) - rl.update_texture(camera_texture, rl.ffi.cast("void *", img_rgba.ctypes.data)) - rl.draw_texture(camera_texture, 0, 0, rl.WHITE) # noqa: TID251 + # Update overlay texture (RGB img -> RGBA with non-black pixels visible) + mask = np.any(img > 0, axis=2) + overlay_img[:, :, :3] = img + overlay_img[:, :, 3] = mask * 255 + rl.update_texture(overlay_texture, rl.ffi.cast("void *", overlay_img.ctypes.data)) + rl.draw_texture(overlay_texture, 0, 0, rl.WHITE) # noqa: TID251 # display alerts rl.draw_text_ex(font, sm['selfdriveState'].alertText1, rl.Vector2(180, 150), 30, 0, rl.RED) @@ -257,9 +245,10 @@ def ui_thread(addr): rl.end_drawing() - rl.unload_texture(camera_texture) + rl.unload_texture(overlay_texture) rl.unload_texture(top_down_texture) rl.unload_font(font) + camera_view.close() rl.close_window() From f170440f4ac49d4804eaa9079adf30fa291e49e7 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 4 Apr 2026 18:30:34 -0400 Subject: [PATCH 602/685] safety: add reserved controls_allowed fields for forks (like MADS) (#37747) --- cereal/log.capnp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cereal/log.capnp b/cereal/log.capnp index 56af5ae5f63..7e47ab171d4 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -613,6 +613,11 @@ struct PandaState @0xa7649e2575e4591e { voltage @0 :UInt32; current @1 :UInt32; + # these fields are not used by openpilot, but they're + # reserved for forks building alternate experiences. + controlsAllowedRESERVED1 @38 :Bool; + controlsAllowedRESERVED2 @39 :Bool; + enum FaultStatus { none @0; faultTemp @1; From dc4dae6794c724be50edbe2fa1edd2046ff3b813 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Sat, 4 Apr 2026 20:28:16 -0700 Subject: [PATCH 603/685] replay/ui: color lines, use aTarget (#37764) * color lines, use aTarget * only scroll when updated --- tools/replay/lib/ui_helpers.py | 14 ++++++++++++-- tools/replay/ui.py | 8 ++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/tools/replay/lib/ui_helpers.py b/tools/replay/lib/ui_helpers.py index 7c95e9cad40..039dd4f2359 100644 --- a/tools/replay/lib/ui_helpers.py +++ b/tools/replay/lib/ui_helpers.py @@ -5,6 +5,7 @@ import pyray as rl from matplotlib.backends.backend_agg import FigureCanvasAgg +from matplotlib.offsetbox import AnchoredOffsetbox, HPacker, TextArea from openpilot.common.transformations.camera import get_view_frame_from_calib_frame from openpilot.selfdrive.controls.radard import RADAR_TO_CAMERA @@ -94,6 +95,7 @@ def draw_path(path, color, img, calibration, top_down, lid_color=None, z_off=0): def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_colors, plot_styles): color_palette = {"r": (1, 0, 0), "g": (0, 1, 0), "b": (0, 0, 1), "k": (0, 0, 0), "y": (1, 1, 0), "p": (0, 1, 1), "m": (1, 0, 1)} + label_palette = {**color_palette, "b": (43/255, 114/255, 1.0)} dpi = 90 fig = plt.figure(figsize=(575 / dpi, 600 / dpi), dpi=dpi) @@ -116,10 +118,18 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co plots.append(plot) idxs.append(name_to_arr_idx[item]) plot_select.append(i) - axs[i].set_title(", ".join(f"{nm} ({cl})" for (nm, cl) in zip(pl_list, plot_colors[i], strict=False)), fontsize=10) + # Build colored title: each label colored to match its plot line + title_texts = [] + for j2, (nm, cl) in enumerate(zip(pl_list, plot_colors[i], strict=False)): + if j2 > 0: + title_texts.append(TextArea(", ", textprops=dict(color="white", fontsize=10))) + title_texts.append(TextArea(nm, textprops=dict(color=label_palette[cl], fontsize=10))) + packed = HPacker(children=title_texts, pad=0, sep=0) + ab = AnchoredOffsetbox(loc='lower center', child=packed, bbox_to_anchor=(0.5, 1.0), + bbox_transform=axs[i].transAxes, frameon=False, pad=0) + axs[i].add_artist(ab) axs[i].tick_params(axis="x", colors="white") axs[i].tick_params(axis="y", colors="white") - axs[i].title.set_color("white") if i < len(plot_ylims) - 1: axs[i].set_xticks([]) diff --git a/tools/replay/ui.py b/tools/replay/ui.py index 3f6eb1fdb73..405c79f4250 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -114,7 +114,7 @@ def ui_thread(addr): plot_arr = np.zeros((100, len(name_to_arr_idx.values()))) plot_xlims = [(0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0]), (0, plot_arr.shape[0])] - plot_ylims = [(-0.1, 1.1), (-ANGLE_SCALE, ANGLE_SCALE), (0.0, 75.0), (-3.0, 2.0)] + plot_ylims = [(-0.1, 1.1), (-ANGLE_SCALE, ANGLE_SCALE), (0.0, 75.0), (-3.5, 2.0)] plot_names = [ ["gas", "computer_gas", "user_brake", "computer_brake"], ["angle_steers", "angle_steers_des", "angle_steers_k", "steer_torque"], @@ -168,7 +168,8 @@ def ui_thread(addr): else: angle_steers_k = np.inf - plot_arr[:-1] = plot_arr[1:] + if sm.updated['carState']: + plot_arr[:-1] = plot_arr[1:] plot_arr[-1, name_to_arr_idx['angle_steers']] = sm['carState'].steeringAngleDeg plot_arr[-1, name_to_arr_idx['angle_steers_des']] = sm['carControl'].actuators.steeringAngleDeg plot_arr[-1, name_to_arr_idx['angle_steers_k']] = angle_steers_k @@ -183,8 +184,7 @@ def ui_thread(addr): plot_arr[-1, name_to_arr_idx['v_cruise']] = sm['carState'].cruiseState.speed plot_arr[-1, name_to_arr_idx['a_ego']] = sm['carState'].aEgo - if len(sm['longitudinalPlan'].accels): - plot_arr[-1, name_to_arr_idx['a_target']] = sm['longitudinalPlan'].accels[0] + plot_arr[-1, name_to_arr_idx['a_target']] = sm['longitudinalPlan'].aTarget # Draw model overlays onto img, then blit as transparent overlay img[:] = 0 From f37fd3ea34301bfb3fb08db305c5e182e7c23f0d Mon Sep 17 00:00:00 2001 From: DevTekVE Date: Mon, 6 Apr 2026 18:46:14 +0200 Subject: [PATCH 604/685] Fixes the debugging of safety after scons removal (#37769) Fixes the debugging after scons removal --- .vscode/launch.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index f090061c42b..151b757dabb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -52,6 +52,9 @@ "type": "lldb", "request": "attach", "pid": "${command:pickMyProcess}", + "sourceMap": { + ".": "${workspaceFolder}/opendbc/safety" + }, "initCommands": [ "script import time; time.sleep(3)" ] From 08401a96c2e2141cef8a4d60111f64da3ffd6403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20R=C4=85czy?= Date: Mon, 6 Apr 2026 14:15:38 -0700 Subject: [PATCH 605/685] modeld: frame delay (#37731) * Frame delay * Multiply * If replay * For long too * DT_MDL / 2 * Shorten comment * Just 50ms * Remove REPLAY const --- selfdrive/modeld/modeld.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 07c3af4b7e3..82e750cf8b3 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -405,7 +405,9 @@ def main(demo=False): drivingdata_send = messaging.new_message('drivingModelData') posenet_send = messaging.new_message('cameraOdometry') - action = get_action_from_model(model_output, prev_action, lat_delay + DT_MDL, long_delay + DT_MDL, v_ego) + frame_delay = DT_MDL # compensate for time passed since the frame was captured: current_time - timestamp_eof is 50ms on average + action_delay = DT_MDL / 2 # middle of the interval between model output (current state) and next frame (expected state) + action = get_action_from_model(model_output, prev_action, lat_delay + frame_delay + action_delay, long_delay + frame_delay + action_delay, v_ego) prev_action = action fill_model_msg(drivingdata_send, modelv2_send, model_output, action, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, From c7382f8258a95161e4be999e3c8632d4760e72c9 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 7 Apr 2026 14:33:49 -0700 Subject: [PATCH 606/685] esim: harden AtClient with retry loops and reconnect (#37771) --- system/hardware/esim.py | 9 --- system/hardware/tici/lpa.py | 119 +++++++++++++++++++++++++----------- 2 files changed, 85 insertions(+), 43 deletions(-) diff --git a/system/hardware/esim.py b/system/hardware/esim.py index 9b7d4f9ec0b..40600d26b56 100755 --- a/system/hardware/esim.py +++ b/system/hardware/esim.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import argparse -import time from openpilot.system.hardware import HARDWARE @@ -13,16 +12,13 @@ parser.add_argument('--nickname', nargs=2, metavar=('iccid', 'name'), help='update the nickname for a profile') args = parser.parse_args() - mutated = False lpa = HARDWARE.get_sim_lpa() if args.switch: lpa.switch_profile(args.switch) - mutated = True elif args.delete: confirm = input('are you sure you want to delete this profile? (y/N) ') if confirm == 'y': lpa.delete_profile(args.delete) - mutated = True else: print('cancelled') exit(0) @@ -33,11 +29,6 @@ else: parser.print_help() - if mutated: - HARDWARE.reboot_modem() - # eUICC needs a small delay post-reboot before querying profiles - time.sleep(.5) - profiles = lpa.list_profiles() print(f'\n{len(profiles)} profile{"s" if len(profiles) > 1 else ""}:') for p in profiles: diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index 2e7e6a0ba97..93cbca2479a 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -5,14 +5,17 @@ import math import os import serial +import subprocess import sys +import termios +import time from collections.abc import Generator from openpilot.system.hardware.base import LPABase, Profile -DEFAULT_DEVICE = "/dev/ttyUSB2" +DEFAULT_DEVICE = "/dev/modem_at0" DEFAULT_BAUD = 9600 DEFAULT_TIMEOUT = 5.0 # https://euicc-manual.osmocom.org/docs/lpa/applet-id/ @@ -20,6 +23,10 @@ MM = "org.freedesktop.ModemManager1" MM_MODEM = MM + ".Modem" ES10X_MSS = 120 +OPEN_ISDR_RETRIES = 10 +OPEN_ISDR_RETRY_DELAY_S = 0.25 +OPEN_ISDR_RESET_ATTEMPT = 5 +SEND_APDU_RETRIES = 3 DEBUG = os.environ.get("DEBUG") == "1" # TLV Tags @@ -36,28 +43,28 @@ def b64e(data: bytes) -> str: class AtClient: - def __init__(self, device: str, baud: int, timeout: float, debug: bool) -> None: - self.debug = debug + def __init__(self, device: str, baud: int, timeout: float) -> None: self.channel: str | None = None + self._device = device + self._baud = baud self._timeout = timeout self._serial: serial.Serial | None = None - try: - self._serial = serial.Serial(device, baudrate=baud, timeout=timeout) - self._serial.reset_input_buffer() - except (serial.SerialException, PermissionError, OSError): - pass + self._use_dbus = not os.path.exists(device) def close(self) -> None: try: if self.channel: - self.query(f"AT+CCHC={self.channel}") + try: + self.query(f"AT+CCHC={self.channel}") + except (RuntimeError, TimeoutError): + pass self.channel = None finally: if self._serial: self._serial.close() def _send(self, cmd: str) -> None: - if self.debug: + if DEBUG: print(f"SER >> {cmd}", file=sys.stderr) self._serial.write((cmd + "\r").encode("ascii")) @@ -70,7 +77,7 @@ def _expect(self) -> list[str]: line = raw.decode(errors="ignore").strip() if not line: continue - if self.debug: + if DEBUG: print(f"SER << {line}", file=sys.stderr) if line == "OK": return lines @@ -78,6 +85,18 @@ def _expect(self) -> list[str]: raise RuntimeError(f"AT command failed: {line}") lines.append(line) + def _ensure_serial(self, reconnect: bool = False) -> None: + if reconnect: + self.channel = None + try: + if self._serial: + self._serial.close() + except Exception: + pass + self._serial = None + if self._serial is None: + self._serial = serial.Serial(self._device, baudrate=self._baud, timeout=self._timeout) + def _get_modem(self): import dbus bus = dbus.SystemBus() @@ -87,48 +106,81 @@ def _get_modem(self): return bus.get_object(MM, modem_path) def _dbus_query(self, cmd: str) -> list[str]: - if self.debug: + if DEBUG: print(f"DBUS >> {cmd}", file=sys.stderr) try: result = str(self._get_modem().Command(cmd, math.ceil(self._timeout), dbus_interface=MM_MODEM, timeout=self._timeout)) except Exception as e: raise RuntimeError(f"AT command failed: {e}") from e lines = [line.strip() for line in result.splitlines() if line.strip()] - if self.debug: + if DEBUG: for line in lines: print(f"DBUS << {line}", file=sys.stderr) return lines def query(self, cmd: str) -> list[str]: - if self._serial: + if self._use_dbus: + return self._dbus_query(cmd) + self._ensure_serial() + try: + self._send(cmd) + return self._expect() + except serial.SerialException: + self._ensure_serial(reconnect=True) self._send(cmd) return self._expect() - return self._dbus_query(cmd) - def open_isdr(self) -> None: - # close any stale logical channel from a previous crashed session - try: - self.query("AT+CCHC=1") - except RuntimeError: - pass + def _open_isdr_once(self) -> None: + if self.channel: + try: + self.query(f"AT+CCHC={self.channel}") + except RuntimeError: + pass + self.channel = None + # drain any unsolicited responses before opening + if self._serial and not self._use_dbus: + try: + self._serial.reset_input_buffer() + except (OSError, serial.SerialException, termios.error): + self._ensure_serial(reconnect=True) for line in self.query(f'AT+CCHO="{ISDR_AID}"'): if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()): self.channel = ch return raise RuntimeError("Failed to open ISD-R application") + def open_isdr(self) -> None: + for attempt in range(OPEN_ISDR_RETRIES): + try: + self._open_isdr_once() + return + except (RuntimeError, TimeoutError, termios.error, serial.SerialException): + time.sleep(OPEN_ISDR_RETRY_DELAY_S) + if attempt == OPEN_ISDR_RESET_ATTEMPT: + # reset modem via lte.sh + subprocess.run(['/usr/comma/lte/lte.sh', 'start'], capture_output=True) + self._serial = None # serial port will be re-opened on next attempt + raise RuntimeError("Failed to open ISD-R after retries") + def send_apdu(self, apdu: bytes) -> tuple[bytes, int, int]: - if not self.channel: - raise RuntimeError("Logical channel is not open") - hex_payload = apdu.hex().upper() - for line in self.query(f'AT+CGLA={self.channel},{len(hex_payload)},"{hex_payload}"'): - if line.startswith("+CGLA:"): - parts = line.split(":", 1)[1].split(",", 1) - if len(parts) == 2: - data = bytes.fromhex(parts[1].strip().strip('"')) - if len(data) >= 2: - return data[:-2], data[-2], data[-1] - raise RuntimeError("Missing +CGLA response") + for attempt in range(SEND_APDU_RETRIES): + try: + if not self.channel: + self.open_isdr() + hex_payload = apdu.hex().upper() + for line in self.query(f'AT+CGLA={self.channel},{len(hex_payload)},"{hex_payload}"'): + if line.startswith("+CGLA:"): + parts = line.split(":", 1)[1].split(",", 1) + if len(parts) == 2: + data = bytes.fromhex(parts[1].strip().strip('"')) + if len(data) >= 2: + return data[:-2], data[-2], data[-1] + raise RuntimeError("Missing +CGLA response") + except (RuntimeError, ValueError): + self.channel = None + if attempt == SEND_APDU_RETRIES - 1: + raise + raise RuntimeError("send_apdu failed") # --- TLV utilities --- @@ -250,8 +302,7 @@ def __new__(cls): def __init__(self): if hasattr(self, '_client'): return - self._client = AtClient(DEFAULT_DEVICE, DEFAULT_BAUD, DEFAULT_TIMEOUT, debug=DEBUG) - self._client.open_isdr() + self._client = AtClient(DEFAULT_DEVICE, DEFAULT_BAUD, DEFAULT_TIMEOUT) atexit.register(self._client.close) def list_profiles(self) -> list[Profile]: From b0b9079437b49c58dae366355f47b95cd2cce296 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Tue, 7 Apr 2026 17:57:44 -0700 Subject: [PATCH 607/685] bump opendbc (#37775) fix opendbc --- opendbc_repo | 2 +- selfdrive/test/process_replay/migration.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index ef70686afee..ebaca24c02b 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit ef70686afee3e0fe5e6be4938eaafc52e9e77935 +Subproject commit ebaca24c02b2d5eb9765ca6f63d5b11e67f52eca diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 14b38e0481a..87988edbc15 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -292,7 +292,7 @@ def migrate_pandaStates(msgs): safety_param_migration = { "TOYOTA_PRIUS": EPS_SCALE["TOYOTA_PRIUS"] | ToyotaSafetyFlags.STOCK_LONGITUDINAL, "TOYOTA_RAV4": EPS_SCALE["TOYOTA_RAV4"] | ToyotaSafetyFlags.ALT_BRAKE, - "KIA_EV6": HyundaiSafetyFlags.EV_GAS | HyundaiSafetyFlags.CANFD_LKA_STEERING, + "KIA_EV6": HyundaiSafetyFlags.EV_GAS | HyundaiSafetyFlags.CANFD_LKA_STEER_MSG, "CHEVROLET_VOLT": GMSafetyFlags.EV, "CHEVROLET_BOLT_EUV": GMSafetyFlags.EV | GMSafetyFlags.HW_CAM, } From 54a2d31e97542373d7a5bc6b3646792fff17f088 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 7 Apr 2026 18:14:45 -0700 Subject: [PATCH 608/685] esim: TLV helpers, acquire_channel, and TiciLPA rework (#37776) --- system/hardware/tici/lpa.py | 108 ++++++++++++++++++++++++++---------- 1 file changed, 79 insertions(+), 29 deletions(-) diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index 93cbca2479a..ca93b3e4178 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -2,6 +2,7 @@ import atexit import base64 +import fcntl import math import os import serial @@ -10,7 +11,9 @@ import termios import time -from collections.abc import Generator +from collections.abc import Callable, Generator +from contextlib import contextmanager +from typing import Any from openpilot.system.hardware.base import LPABase, Profile @@ -27,21 +30,35 @@ OPEN_ISDR_RETRY_DELAY_S = 0.25 OPEN_ISDR_RESET_ATTEMPT = 5 SEND_APDU_RETRIES = 3 +LOCK_FILE = '/dev/shm/modem_lpa.lock' DEBUG = os.environ.get("DEBUG") == "1" # TLV Tags TAG_ICCID = 0x5A +TAG_STATUS = 0x80 TAG_PROFILE_INFO_LIST = 0xBF2D +TAG_OK = 0xA0 STATE_LABELS = {0: "disabled", 1: "enabled", 255: "unknown"} ICON_LABELS = {0: "jpeg", 1: "png", 255: "unknown"} CLASS_LABELS = {0: "test", 1: "provisioning", 2: "operational", 255: "unknown"} +# TLV tag -> (field_name, decoder) +FieldMap = dict[int, tuple[str, Callable[[bytes], Any]]] + def b64e(data: bytes) -> str: return base64.b64encode(data).decode("ascii") +def base64_trim(s: str) -> str: + return "".join(c for c in s if c not in "\n\r \t") + + +def b64d(s: str) -> bytes: + return base64.b64decode(base64_trim(s)) + + class AtClient: def __init__(self, device: str, baud: int, timeout: float) -> None: self.channel: str | None = None @@ -222,12 +239,37 @@ def find_tag(data: bytes, target: int) -> bytes | None: return next((v for t, v in iter_tlv(data) if t == target), None) +def require_tag(data: bytes, target: int, label: str = "") -> bytes: + v = find_tag(data, target) + if v is None: + raise RuntimeError(f"Missing {label or f'tag 0x{target:X}'}") + return v + + def tbcd_to_string(raw: bytes) -> str: return "".join(str(n) for b in raw for n in (b & 0x0F, b >> 4) if n <= 9) -# Profile field decoders: TLV tag -> (field_name, decoder) -_PROFILE_FIELDS = { +def string_to_tbcd(s: str) -> bytes: + digits = [int(c) for c in s if c.isdigit()] + return bytes(digits[i] | ((digits[i + 1] if i + 1 < len(digits) else 0xF) << 4) for i in range(0, len(digits), 2)) + + +def encode_tlv(tag: int, value: bytes) -> bytes: + tag_bytes = bytes([(tag >> 8) & 0xFF, tag & 0xFF]) if tag > 255 else bytes([tag]) + vlen = len(value) + if vlen <= 127: + return tag_bytes + bytes([vlen]) + value + length_bytes = vlen.to_bytes((vlen.bit_length() + 7) // 8, "big") + return tag_bytes + bytes([0x80 | len(length_bytes)]) + length_bytes + value + + +def int_bytes(n: int) -> bytes: + """Encode a positive integer as minimal big-endian bytes (at least 1 byte).""" + return n.to_bytes((n.bit_length() + 7) // 8 or 1, "big") + + +PROFILE: FieldMap = { TAG_ICCID: ("iccid", tbcd_to_string), 0x4F: ("isdpAid", lambda v: v.hex().upper()), 0x9F70: ("profileState", lambda v: STATE_LABELS.get(v[0], "unknown")), @@ -240,11 +282,11 @@ def tbcd_to_string(raw: bytes) -> str: } -def _decode_profile_fields(data: bytes) -> dict: - """Parse known profile metadata TLV fields into a dict.""" - result = {} +def decode_struct(data: bytes, field_map: FieldMap) -> dict[str, Any]: + """Parse TLV data using a {tag: (field_name, decoder)} map into a dict.""" + result: dict[str, Any] = {name: None for name, _ in field_map.values()} for tag, value in iter_tlv(data): - if (field := _PROFILE_FIELDS.get(tag)): + if (field := field_map.get(tag)): result[field[0]] = field[1](value) return result @@ -277,14 +319,11 @@ def es10x_command(client: AtClient, data: bytes) -> bytes: # --- Profile operations --- def decode_profiles(blob: bytes) -> list[dict]: - root = find_tag(blob, TAG_PROFILE_INFO_LIST) - if root is None: - raise RuntimeError("Missing ProfileInfoList") - list_ok = find_tag(root, 0xA0) + root = require_tag(blob, TAG_PROFILE_INFO_LIST, "ProfileInfoList") + list_ok = find_tag(root, TAG_OK) if list_ok is None: return [] - defaults = {name: None for name, _ in _PROFILE_FIELDS.values()} - return [{**defaults, **_decode_profile_fields(value)} for tag, value in iter_tlv(list_ok) if tag == 0xE3] + return [decode_struct(value, PROFILE) for tag, value in iter_tlv(list_ok) if tag == 0xE3] def list_profiles(client: AtClient) -> list[dict]: @@ -292,29 +331,40 @@ def list_profiles(client: AtClient) -> list[dict]: class TiciLPA(LPABase): - _instance = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - def __init__(self): if hasattr(self, '_client'): return self._client = AtClient(DEFAULT_DEVICE, DEFAULT_BAUD, DEFAULT_TIMEOUT) atexit.register(self._client.close) + @contextmanager + def _acquire_channel(self): + fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR) + try: + fcntl.flock(fd, fcntl.LOCK_EX) + self._client.open_isdr() + yield + finally: + if self._client.channel: + try: + self._client.query(f"AT+CCHC={self._client.channel}") + except (RuntimeError, TimeoutError): + pass + self._client.channel = None + fcntl.flock(fd, fcntl.LOCK_UN) + os.close(fd) + def list_profiles(self) -> list[Profile]: - return [ - Profile( - iccid=p.get("iccid", ""), - nickname=p.get("profileNickname") or "", - enabled=p.get("profileState") == "enabled", - provider=p.get("serviceProviderName") or "", - ) - for p in list_profiles(self._client) - ] + with self._acquire_channel(): + return [ + Profile( + iccid=p.get("iccid", ""), + nickname=p.get("profileNickname") or "", + enabled=p.get("profileState") == "enabled", + provider=p.get("serviceProviderName") or "", + ) + for p in list_profiles(self._client) + ] def get_active_profile(self) -> Profile | None: return None From f28d6fb6bc0ce653e119490c26ada7f8f1f5f075 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Tue, 7 Apr 2026 19:17:09 -0700 Subject: [PATCH 609/685] esim: implement profile nickname (#37777) --- system/hardware/tici/lpa.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index ca93b3e4178..34b0a62ed2e 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -15,7 +15,7 @@ from contextlib import contextmanager from typing import Any -from openpilot.system.hardware.base import LPABase, Profile +from openpilot.system.hardware.base import LPABase, LPAError, Profile DEFAULT_DEVICE = "/dev/modem_at0" @@ -37,6 +37,7 @@ TAG_ICCID = 0x5A TAG_STATUS = 0x80 TAG_PROFILE_INFO_LIST = 0xBF2D +TAG_SET_NICKNAME = 0xBF29 TAG_OK = 0xA0 STATE_LABELS = {0: "disabled", 1: "enabled", 255: "unknown"} @@ -330,6 +331,20 @@ def list_profiles(client: AtClient) -> list[dict]: return decode_profiles(es10x_command(client, TAG_PROFILE_INFO_LIST.to_bytes(2, "big") + b"\x00")) +def set_profile_nickname(client: AtClient, iccid: str, nickname: str) -> None: + nickname_bytes = nickname.encode("utf-8") + if len(nickname_bytes) > 64: + raise ValueError("Profile nickname must be 64 bytes or less") + content = encode_tlv(TAG_ICCID, string_to_tbcd(iccid)) + encode_tlv(0x90, nickname_bytes) + response = es10x_command(client, encode_tlv(TAG_SET_NICKNAME, content)) + root = require_tag(response, TAG_SET_NICKNAME, "SetNicknameResponse") + code = require_tag(root, TAG_STATUS, "status in SetNicknameResponse")[0] + if code == 0x01: + raise LPAError(f"profile {iccid} not found") + if code != 0x00: + raise RuntimeError(f"SetNickname failed with status 0x{code:02X}") + + class TiciLPA(LPABase): def __init__(self): if hasattr(self, '_client'): @@ -376,7 +391,8 @@ def download_profile(self, qr: str, nickname: str | None = None) -> None: return None def nickname_profile(self, iccid: str, nickname: str) -> None: - return None + with self._acquire_channel(): + set_profile_nickname(self._client, iccid, nickname) def switch_profile(self, iccid: str) -> None: return None From 508863e5a82b24e82c1cda13ce3d7a6ba069a6e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 7 Apr 2026 21:49:31 -0700 Subject: [PATCH 610/685] Long policy: less creepy (#37755) * fix * better --- selfdrive/controls/lib/drive_helpers.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index bf6dd04f603..1e2fb27b51d 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -39,19 +39,17 @@ def clip_curvature(v_ego, prev_curvature, new_curvature, roll) -> tuple[float, b return float(new_curvature), limited_accel or limited_max_curv -def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.05): +def get_accel_from_plan(speeds, accels, t_idxs, action_t=DT_MDL, vEgoStopping=0.3): if len(speeds) == len(t_idxs): v_now = speeds[0] a_now = accels[0] v_target = np.interp(action_t, t_idxs, speeds) a_target = 2 * (v_target - v_now) / (action_t) - a_now - v_target_1sec = np.interp(action_t + 1.0, t_idxs, speeds) else: + v_now = 0.0 v_target = 0.0 - v_target_1sec = 0.0 a_target = 0.0 - should_stop = (v_target < vEgoStopping and - v_target_1sec < vEgoStopping) + should_stop = (v_now < vEgoStopping and a_target < 0.1) return a_target, should_stop def curv_from_psis(psi_target, psi_rate, vego, action_t): From 19d56f685b4152ad9497c94295f0d6351ed72dfb Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Wed, 8 Apr 2026 08:11:00 +0300 Subject: [PATCH 611/685] DM: auto reset audible alert coming to a stop (#37071) * dm: suppress audible alert at standstill * test: driver distracted then stops at standstill * use recovery instead * add back * fix comment --------- Co-authored-by: ZwX1616 --- selfdrive/monitoring/helpers.py | 16 +++++++--------- selfdrive/monitoring/test_monitoring.py | 11 +++++++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index 90cc565802d..f195c2ff847 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -345,10 +345,14 @@ def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car self._reset_awareness() return - driver_attentive = self.driver_distraction_filter.x < 0.37 awareness_prev = self.awareness + _reaching_pre = self.awareness - self.step_change <= self.threshold_pre + _reaching_terminal = self.awareness - self.step_change <= 0 + standstill_orange_exemption = standstill and _reaching_pre + always_on_red_exemption = always_on_valid and not op_engaged and _reaching_terminal - if (driver_attentive and self.face_detected and self.pose.low_std and self.awareness > 0): + if self.awareness > 0 and \ + ((self.driver_distraction_filter.x < 0.37 and self.face_detected and self.pose.low_std) or standstill_orange_exemption): if driver_engaged: self._reset_awareness() return @@ -361,19 +365,13 @@ def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car if self.awareness > self.threshold_prompt: return - _reaching_pre = self.awareness - self.step_change <= self.threshold_pre - _reaching_audible = self.awareness - self.step_change <= self.threshold_prompt - _reaching_terminal = self.awareness - self.step_change <= 0 - standstill_exemption = standstill and _reaching_pre - always_on_red_exemption = always_on_valid and not op_engaged and _reaching_terminal - certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected if certainly_distracted or maybe_distracted: # should always be counting if distracted unless at standstill and reaching green # also will not be reaching 0 if DM is active when not engaged - if not (standstill_exemption or always_on_red_exemption): + if not (standstill_orange_exemption or always_on_red_exemption): self.awareness = max(self.awareness - self.step_change, -0.1) alert = None diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 733ea85bc04..50a381502e6 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -189,6 +189,17 @@ def test_long_traffic_light_victim(self): assert events[int((_redlight_time+0.5)/DT_DMON)].names[0] == EventName.preDriverDistracted assert events[int((_redlight_time+_pre_to_prompt+0.5)/DT_DMON)].names[0] == EventName.promptDriverDistracted + # engaged, distracted while moving, then car stops after reaching orange + # - should reset timer to pre green at standstill + def test_distracted_then_stops(self): + _stop_time = DISTRACTED_SECONDS_TO_ORANGE + 1 # stop 1 second after reaching orange + standstill_vector = always_false[:] + standstill_vector[int(_stop_time/DT_DMON):] = [True] * int((TEST_TIMESPAN-_stop_time)/DT_DMON) + events, _ = self._run_seq(always_distracted, always_false, always_true, standstill_vector) + # just before and briefly after stopping: orange alert; goes away quickly after stopped + assert events[int((_stop_time+0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert len(events[int((_stop_time+0.5)/DT_DMON)]) == 0 + # engaged, model is somehow uncertain and driver is distracted # - should fall back to wheel touch after uncertain alert def test_somehow_indecisive_model(self): From 21538e5a099f525c42b6f5cca440de2c9a22b069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 8 Apr 2026 13:55:00 -0700 Subject: [PATCH 612/685] autodetect tg backend (#37778) * pick fastest * save config * fix * Ignore generated tg_compiled_flags file * helper * cleaner * whitespace not needed * no shebang * whitespace --- .gitignore | 1 + selfdrive/modeld/SConscript | 45 ++++++++++++++++++++------- selfdrive/modeld/dmonitoringmodeld.py | 11 +++---- selfdrive/modeld/modeld.py | 7 ++--- selfdrive/modeld/tinygrad_helpers.py | 11 +++++++ 5 files changed, 54 insertions(+), 21 deletions(-) create mode 100644 selfdrive/modeld/tinygrad_helpers.py diff --git a/.gitignore b/.gitignore index 1f58a371e0b..c4022a86535 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ bin/ config.json compile_commands.json compare_runtime*.html +selfdrive/modeld/models/tg_compiled_flags.json # build artifacts selfdrive/pandad/pandad diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index bad1cdd5003..f45dd4e7c6d 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,6 +1,9 @@ -import os import glob +import json +import os +from SCons.Script import Value from openpilot.common.file_chunker import chunk_file, get_chunk_paths +from tinygrad import Device Import('env', 'arch') chunker_file = File("#common/file_chunker.py") @@ -13,31 +16,51 @@ tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + " def estimate_pickle_max_size(onnx_size): return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty -# compile warp # THREADS=0 is need to prevent bug: https://github.com/tinygrad/tinygrad/issues/14689 -tg_flags = { - 'larch64': 'DEV=QCOM FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0', - 'Darwin': f'DEV=CPU THREADS=0 HOME={os.path.expanduser("~")}', # tinygrad calls brew which needs a $HOME in the env -}.get(arch, 'DEV=CPU:LLVM THREADS=0') +# get fastest TG config +available = set(Device.get_available_devices()) +if 'CUDA' in available: + tg_backend = 'CUDA' + tg_flags = f'DEV={tg_backend}' +elif 'QCOM' in available: + tg_backend = 'QCOM' + tg_flags = f'DEV={tg_backend} FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0' +else: + tg_backend = 'CPU' + tg_flags = f'DEV={tg_backend} THREADS=0' + +def write_tg_compiled_flags(target, source, env): + with open(str(target[0]), "w") as f: + json.dump({"DEV": tg_backend}, f) + f.write("\n") + +compiled_flags_node = lenv.Command( + File("models/tg_compiled_flags.json").abspath, + tinygrad_files + [Value(tg_backend)], + write_tg_compiled_flags, +) + +# tinygrad calls brew which needs a $HOME in the env +mac_brew_string = f'HOME={os.path.expanduser("~")}' if arch == 'Darwin' else '' # Get model metadata for model_name in ['driving_vision', 'driving_off_policy', 'driving_on_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)] - cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' - lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files, cmd) + cmd = f'{tg_flags} {mac_brew_string} python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' + lenv.Command(fn + "_metadata.pkl", [fn + ".onnx"] + tinygrad_files + script_files + [compiled_flags_node], cmd) image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -compile_warp_cmd = f'{tg_flags} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' +compile_warp_cmd = f'{tg_flags} {mac_brew_string} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye warp_targets = [] for cam in [_ar_ox_fisheye, _os_fisheye]: w, h = cam.width, cam.height warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command(warp_targets, tinygrad_files + script_files, compile_warp_cmd) +lenv.Command(warp_targets, tinygrad_files + script_files + [compiled_flags_node], compile_warp_cmd) def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' @@ -47,7 +70,7 @@ def tg_compile(flags, model_name): chunk_targets = get_chunk_paths(pkl, estimate_pickle_max_size(os.path.getsize(onnx_path))) compile_node = lenv.Command( pkl, - [onnx_path] + tinygrad_files + [chunker_file], + [onnx_path] + tinygrad_files + [chunker_file, compiled_flags_node], f'{pythonpath_string} {flags} {image_flag} python3 {Dir("#tinygrad_repo").abspath}/examples/openpilot/compile3.py {fn}.onnx {pkl}', ) def do_chunk(target, source, env): diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index efd8214b9fa..d5901e89439 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 import os -from openpilot.system.hardware import TICI -os.environ['DEV'] = 'QCOM' if TICI else 'CPU' +from openpilot.selfdrive.modeld.tinygrad_helpers import MODELS_DIR, set_tinygrad_backend_from_compiled_flags +set_tinygrad_backend_from_compiled_flags() + from tinygrad.tensor import Tensor import time import pickle import numpy as np -from pathlib import Path from cereal import messaging from cereal.messaging import PubMaster, SubMaster @@ -21,9 +21,8 @@ PROCESS_NAME = "selfdrive.modeld.dmonitoringmodeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') -MODEL_PKL_PATH = Path(__file__).parent / 'models/dmonitoring_model_tinygrad.pkl' -METADATA_PATH = Path(__file__).parent / 'models/dmonitoring_model_metadata.pkl' -MODELS_DIR = Path(__file__).parent / 'models' +MODEL_PKL_PATH = MODELS_DIR / 'dmonitoring_model_tinygrad.pkl' +METADATA_PATH = MODELS_DIR / 'dmonitoring_model_metadata.pkl' class ModelState: inputs: dict[str, np.ndarray] diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 82e750cf8b3..c61e417e1ac 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 import os -from openpilot.system.hardware import TICI -os.environ['DEV'] = 'QCOM' if TICI else 'CPU' +from openpilot.selfdrive.modeld.tinygrad_helpers import MODELS_DIR, set_tinygrad_backend_from_compiled_flags +set_tinygrad_backend_from_compiled_flags() + USBGPU = "USBGPU" in os.environ if USBGPU: os.environ['DEV'] = 'AMD' @@ -12,7 +13,6 @@ import numpy as np import cereal.messaging as messaging from cereal import car, log -from pathlib import Path from cereal.messaging import PubMaster, SubMaster from msgq.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from opendbc.car.car_helpers import get_demo_car_params @@ -34,7 +34,6 @@ PROCESS_NAME = "selfdrive.modeld.modeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') -MODELS_DIR = Path(__file__).parent / 'models' VISION_PKL_PATH = MODELS_DIR / 'driving_vision_tinygrad.pkl' VISION_METADATA_PATH = MODELS_DIR / 'driving_vision_metadata.pkl' ON_POLICY_PKL_PATH = MODELS_DIR / 'driving_on_policy_tinygrad.pkl' diff --git a/selfdrive/modeld/tinygrad_helpers.py b/selfdrive/modeld/tinygrad_helpers.py new file mode 100644 index 00000000000..e833c20968a --- /dev/null +++ b/selfdrive/modeld/tinygrad_helpers.py @@ -0,0 +1,11 @@ +import json +import os +from pathlib import Path + +MODELS_DIR = Path(__file__).parent / 'models' +COMPILED_FLAGS_PATH = MODELS_DIR / 'tg_compiled_flags.json' + + +def set_tinygrad_backend_from_compiled_flags() -> None: + with open(COMPILED_FLAGS_PATH) as f: + os.environ['DEV'] = str(json.load(f)['DEV']) From 8b53f9158d09a2a12f6eb04bd793e18f425257cd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 8 Apr 2026 14:29:32 -0700 Subject: [PATCH 613/685] Rename DM alerts to numbered stages (#37783) * Rename DM alerts to numbered stages * Handle renamed DM events in replay migration * Remove replay migration test * Skip unknown replay event names --- cereal/log.capnp | 12 ++--- selfdrive/debug/cycle_alerts.py | 6 +-- selfdrive/monitoring/helpers.py | 6 +-- selfdrive/monitoring/test_monitoring.py | 53 +++++++++++----------- selfdrive/selfdrived/events.py | 16 +++---- selfdrive/test/process_replay/migration.py | 23 ++++++++-- 6 files changed, 64 insertions(+), 52 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 7e47ab171d4..56502606990 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -68,12 +68,12 @@ struct OnroadEvent @0xc4fa6047f024e718 { longitudinalManeuver @30; steerTempUnavailableSilent @31; resumeRequired @32; - preDriverDistracted @33; - promptDriverDistracted @34; - driverDistracted @35; - preDriverUnresponsive @36; - promptDriverUnresponsive @37; - driverUnresponsive @38; + driverDistracted1 @33; + driverDistracted2 @34; + driverDistracted3 @35; + driverUnresponsive1 @36; + driverUnresponsive2 @37; + driverUnresponsive3 @38; belowSteerSpeed @39; lowBattery @40; accFaulted @41; diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index 00fa33ac63a..4b1def4fc64 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -30,9 +30,9 @@ def cycle_alerts(duration=200, is_metric=False): (EventName.accFaulted, ET.IMMEDIATE_DISABLE), # DM sequence - (EventName.preDriverDistracted, ET.WARNING), - (EventName.promptDriverDistracted, ET.WARNING), - (EventName.driverDistracted, ET.WARNING), + (EventName.driverDistracted1, ET.WARNING), + (EventName.driverDistracted2, ET.WARNING), + (EventName.driverDistracted3, ET.WARNING), ] # debug alerts diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index f195c2ff847..a9d8f5b11ae 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -377,16 +377,16 @@ def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car alert = None if self.awareness <= 0.: # terminal red alert: disengagement required - alert = EventName.driverDistracted if self.active_monitoring_mode else EventName.driverUnresponsive + alert = EventName.driverDistracted3 if self.active_monitoring_mode else EventName.driverUnresponsive3 self.terminal_time += 1 if awareness_prev > 0.: self.terminal_alert_cnt += 1 elif self.awareness <= self.threshold_prompt: # prompt orange alert - alert = EventName.promptDriverDistracted if self.active_monitoring_mode else EventName.promptDriverUnresponsive + alert = EventName.driverDistracted2 if self.active_monitoring_mode else EventName.driverUnresponsive2 elif self.awareness <= self.threshold_pre: # pre green alert - alert = EventName.preDriverDistracted if self.active_monitoring_mode else EventName.preDriverUnresponsive + alert = EventName.driverDistracted1 if self.active_monitoring_mode else EventName.driverUnresponsive1 if alert is not None: self.current_events.add(alert) diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 50a381502e6..d391d3f755e 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -75,11 +75,11 @@ def test_fully_distracted_driver(self): assert len(events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0 assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL + \ ((d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \ - EventName.preDriverDistracted + EventName.driverDistracted1 assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + \ - ((d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.promptDriverDistracted + ((d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.driverDistracted2 assert events[int((d_status.settings._DISTRACTED_TIME + \ - ((TEST_TIMESPAN-10-d_status.settings._DISTRACTED_TIME)/2))/DT_DMON)].names[0] == EventName.driverDistracted + ((TEST_TIMESPAN-10-d_status.settings._DISTRACTED_TIME)/2))/DT_DMON)].names[0] == EventName.driverDistracted3 assert isinstance(d_status.awareness, float) # engaged, no face detected the whole time, no action @@ -88,11 +88,11 @@ def test_fully_invisible_driver(self): assert len(events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0 assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL + \ ((d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \ - EventName.preDriverUnresponsive + EventName.driverUnresponsive1 assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL + \ - ((d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.promptDriverUnresponsive + ((d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.driverUnresponsive2 assert events[int((d_status.settings._AWARENESS_TIME + \ - ((TEST_TIMESPAN-10-d_status.settings._AWARENESS_TIME)/2))/DT_DMON)].names[0] == EventName.driverUnresponsive + ((TEST_TIMESPAN-10-d_status.settings._AWARENESS_TIME)/2))/DT_DMON)].names[0] == EventName.driverUnresponsive3 # engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel # - should have short orange recovery time and no green afterwards; wheel touch only recovers when paying attention @@ -105,10 +105,10 @@ def test_normal_driver(self): [car_interaction_DETECTED] * (int(TEST_TIMESPAN/DT_DMON)-int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON)) events, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false) assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 - assert events[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert events[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.driverDistracted2 assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)]) == 0 - assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted - assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0] == EventName.driverDistracted2 + assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)].names[0] == EventName.driverDistracted2 assert len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)]) == 0 # engaged, down to orange, driver dodges camera, then comes back still distracted, down to red, \ @@ -128,9 +128,9 @@ def test_biggest_comma_fan(self): op_vector[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+2.5)/DT_DMON):int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3)/DT_DMON)] \ = [False] * int(0.5/DT_DMON) events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) - assert events[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)].names[0] == EventName.promptDriverDistracted - assert events[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)].names[0] == EventName.driverDistracted - assert events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)].names[0] == EventName.driverDistracted + assert events[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)].names[0] == EventName.driverDistracted2 + assert events[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)].names[0] == EventName.driverDistracted3 + assert events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)].names[0] == EventName.driverDistracted3 assert len(events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)]) == 0 # engaged, invisible driver, down to orange, driver touches wheel; then down to orange again, driver appears @@ -144,13 +144,13 @@ def test_sometimes_transparent_commuter(self): interaction_vector[int((INVISIBLE_SECONDS_TO_ORANGE)/DT_DMON):int((INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON)] = [True] * int(1/DT_DMON) events, _ = self._run_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false) assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 - assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive + assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2 assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)]) == 0 if _visible_time == 0.5: - assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive - assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)].names[0] == EventName.preDriverUnresponsive + assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2 + assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)].names[0] == EventName.driverUnresponsive1 elif _visible_time == 10: - assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive + assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2 assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)]) == 0 # engaged, invisible driver, down to red, driver appears and then touches wheel, then disengages/reengages @@ -165,10 +165,10 @@ def test_last_second_responder(self): op_vector[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] = [False] * int(0.5/DT_DMON) events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 - assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.promptDriverUnresponsive - assert events[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive - assert events[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)].names[0] == EventName.driverUnresponsive - assert events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)].names[0] == EventName.driverUnresponsive + assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2 + assert events[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive3 + assert events[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)].names[0] == EventName.driverUnresponsive3 + assert events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)].names[0] == EventName.driverUnresponsive3 assert len(events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)]) == 0 # disengaged, always distracted driver @@ -186,8 +186,8 @@ def test_long_traffic_light_victim(self): events, d_status = self._run_seq(always_distracted, always_false, always_true, standstill_vector) assert len(events[int((_redlight_time-0.1)/DT_DMON)]) == 0 _pre_to_prompt = d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL - d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL - assert events[int((_redlight_time+0.5)/DT_DMON)].names[0] == EventName.preDriverDistracted - assert events[int((_redlight_time+_pre_to_prompt+0.5)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert events[int((_redlight_time+0.5)/DT_DMON)].names[0] == EventName.driverDistracted1 + assert events[int((_redlight_time+_pre_to_prompt+0.5)/DT_DMON)].names[0] == EventName.driverDistracted2 # engaged, distracted while moving, then car stops after reaching orange # - should reset timer to pre green at standstill @@ -197,7 +197,7 @@ def test_distracted_then_stops(self): standstill_vector[int(_stop_time/DT_DMON):] = [True] * int((TEST_TIMESPAN-_stop_time)/DT_DMON) events, _ = self._run_seq(always_distracted, always_false, always_true, standstill_vector) # just before and briefly after stopping: orange alert; goes away quickly after stopped - assert events[int((_stop_time+0.1)/DT_DMON)].names[0] == EventName.promptDriverDistracted + assert events[int((_stop_time+0.1)/DT_DMON)].names[0] == EventName.driverDistracted2 assert len(events[int((_stop_time+0.5)/DT_DMON)]) == 0 # engaged, model is somehow uncertain and driver is distracted @@ -206,10 +206,9 @@ def test_somehow_indecisive_model(self): ds_vector = [msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN] * int(TEST_TIMESPAN/DT_DMON) interaction_vector = always_false[:] events, d_status = self._run_seq(ds_vector, interaction_vector, always_true, always_false) - assert EventName.preDriverUnresponsive in \ + assert EventName.driverUnresponsive1 in \ events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME-0.1)/DT_DMON)].names - assert EventName.promptDriverUnresponsive in \ + assert EventName.driverUnresponsive2 in \ events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names - assert EventName.driverUnresponsive in \ + assert EventName.driverUnresponsive3 in \ events[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names - diff --git a/selfdrive/selfdrived/events.py b/selfdrive/selfdrived/events.py index 55af93c42b1..99e25571bd1 100755 --- a/selfdrive/selfdrived/events.py +++ b/selfdrive/selfdrived/events.py @@ -512,7 +512,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin Priority.LOW, VisualAlert.steerRequired, AudibleAlert.prompt, 1.8), }, - EventName.preDriverDistracted: { + EventName.driverDistracted1: { ET.PERMANENT: Alert( "Pay Attention", "", @@ -520,7 +520,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), }, - EventName.promptDriverDistracted: { + EventName.driverDistracted2: { ET.PERMANENT: Alert( "Pay Attention", "Driver Distracted", @@ -528,7 +528,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), }, - EventName.driverDistracted: { + EventName.driverDistracted3: { ET.PERMANENT: Alert( "DISENGAGE IMMEDIATELY", "Driver Distracted", @@ -536,7 +536,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.warningImmediate, .1), }, - EventName.preDriverUnresponsive: { + EventName.driverUnresponsive1: { ET.PERMANENT: Alert( "Touch Steering Wheel: No Face Detected", "", @@ -544,7 +544,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .1), }, - EventName.promptDriverUnresponsive: { + EventName.driverUnresponsive2: { ET.PERMANENT: Alert( "Touch Steering Wheel", "Driver Unresponsive", @@ -552,7 +552,7 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), }, - EventName.driverUnresponsive: { + EventName.driverUnresponsive3: { ET.PERMANENT: Alert( "DISENGAGE IMMEDIATELY", "Driver Unresponsive", @@ -1032,14 +1032,14 @@ def invalid_lkas_setting_alert(CP: car.CarParams, CS: car.CarState, sm: messagin if HARDWARE.get_device_type() == 'mici': EVENTS.update({ - EventName.preDriverDistracted: { + EventName.driverDistracted1: { ET.PERMANENT: Alert( "Pay Attention", "", AlertStatus.normal, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlert.none, 2), }, - EventName.promptDriverDistracted: { + EventName.driverDistracted2: { ET.PERMANENT: Alert( "Pay Attention", "Driver Distracted", diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 87988edbc15..11eb2419874 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -98,6 +98,17 @@ def wrapper(*args, **kwargs): return decorator +def migrate_onroad_event(event: capnp.lib.capnp._DynamicStructReader): + event_dict = event.to_dict() + try: + return log.OnroadEvent(**event_dict) + except capnp.lib.capnp.KjException as e: + # Ignore legacy events the current schema no longer defines. + if "enum has no such enumerant" in str(e): + return None + raise + + @migration(inputs=["longitudinalPlan", "carParams"]) def migrate_longitudinalPlan(msgs): ops = [] @@ -456,12 +467,13 @@ def migrate_onroadEvents(msgs): for event in msg.onroadEventsDEPRECATED: try: if not str(event.name).endswith('DEPRECATED'): - # dict converts name enum into string representation - onroadEvents.append(log.OnroadEvent(**event.to_dict())) + migrated_event = migrate_onroad_event(event) + if migrated_event is not None: + onroadEvents.append(migrated_event) except RuntimeError: # Member was null traceback.print_exc() - new_msg = messaging.new_message('onroadEvents', len(msg.onroadEventsDEPRECATED)) + new_msg = messaging.new_message('onroadEvents', len(onroadEvents)) new_msg.valid = msg.valid new_msg.logMonoTime = msg.logMonoTime new_msg.onroadEvents = onroadEvents @@ -479,8 +491,9 @@ def migrate_driverMonitoringState(msgs): for event in msg.driverMonitoringState.eventsDEPRECATED: try: if not str(event.name).endswith('DEPRECATED'): - # dict converts name enum into string representation - events.append(log.OnroadEvent(**event.to_dict())) + migrated_event = migrate_onroad_event(event) + if migrated_event is not None: + events.append(migrated_event) except RuntimeError: # Member was null traceback.print_exc() From 8abfbc56a16bf7cd24097d4b1007d120cdcef017 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:34:23 -0700 Subject: [PATCH 614/685] [bot] Update Python packages (#37749) Update Python packages Co-authored-by: Vehicle Researcher --- docs/CARS.md | 6 +- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 328 +++++++++++++++++++++++++------------------------- 5 files changed, 172 insertions(+), 168 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index 56f80d16063..cceff62cd92 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,12 +4,13 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 329 Supported Cars +# 333 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video|Setup Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |Acura|ILX 2016-18|Technology Plus Package or AcuraWatch Plus|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|ILX 2019|All|openpilot|26 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Acura|MDX 2022-24|All|openpilot available[1](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|MDX 2025-26|All except Type S|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2016-18|AcuraWatch Plus or Advance Package|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Acura|RDX 2019-21|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -103,6 +104,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|N-Box 2018|All|openpilot available[1](#footnotes)|0 mph|11 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Odyssey 2018-20|Honda Sensing|openpilot|26 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Odyssey 2021-26|All|openpilot available[1](#footnotes)|0 mph|43 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Odyssey (Singapore) 2021|Honda Sensing|openpilot|19 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Odyssey (Taiwan) 2018-19|Honda Sensing|openpilot|19 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Passport 2019-25|All|openpilot|26 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Passport 2026|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -230,7 +232,9 @@ A supported vehicle is one that just works when you install a comma device. All |Nissan[5](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1S 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1T 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |SEAT[11](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |SEAT[11](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Subaru|Ascent 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| diff --git a/opendbc_repo b/opendbc_repo index ebaca24c02b..37c8ae0c2d6 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit ebaca24c02b2d5eb9765ca6f63d5b11e67f52eca +Subproject commit 37c8ae0c2d6830b70d11e1a4e849721cedfe3f03 diff --git a/panda b/panda index d079b0958b5..18f37937cc0 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit d079b0958b51ce33fc313def95317ef52b54b2ec +Subproject commit 18f37937cc0edb0151d17ee7d15a142e88ec3a5b diff --git a/tinygrad_repo b/tinygrad_repo index 1aa04eab086..4cf2759fc8a 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 1aa04eab086d9c22855cfe50f746235200d28867 +Subproject commit 4cf2759fc8a3759abea89fa2805cce1f788d4367 diff --git a/uv.lock b/uv.lock index 272421934ce..7ecb51f5d12 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.13.3" +version = "3.13.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -24,25 +24,25 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/50/42/32cf8e7704ceb4481406eb87161349abb46a57fee3f008ba9cb610968646/aiohttp-3.13.3.tar.gz", hash = "sha256:a949eee43d3782f2daae4f4a2819b2cb9b0c5d3b7f7a927067cc84dafdbb9f88", size = 7844556, upload-time = "2026-01-03T17:33:05.204Z" } +sdist = { url = "https://files.pythonhosted.org/packages/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/be/4fc11f202955a69e0db803a12a062b8379c970c7c84f4882b6da17337cc1/aiohttp-3.13.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:b903a4dfee7d347e2d87697d0713be59e0b87925be030c9178c5faa58ea58d5c", size = 739732, upload-time = "2026-01-03T17:30:14.23Z" }, - { url = "https://files.pythonhosted.org/packages/97/2c/621d5b851f94fa0bb7430d6089b3aa970a9d9b75196bc93bb624b0db237a/aiohttp-3.13.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a45530014d7a1e09f4a55f4f43097ba0fd155089372e105e4bff4ca76cb1b168", size = 494293, upload-time = "2026-01-03T17:30:15.96Z" }, - { url = "https://files.pythonhosted.org/packages/5d/43/4be01406b78e1be8320bb8316dc9c42dbab553d281c40364e0f862d5661c/aiohttp-3.13.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:27234ef6d85c914f9efeb77ff616dbf4ad2380be0cda40b4db086ffc7ddd1b7d", size = 493533, upload-time = "2026-01-03T17:30:17.431Z" }, - { url = "https://files.pythonhosted.org/packages/8d/a8/5a35dc56a06a2c90d4742cbf35294396907027f80eea696637945a106f25/aiohttp-3.13.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d32764c6c9aafb7fb55366a224756387cd50bfa720f32b88e0e6fa45b27dcf29", size = 1737839, upload-time = "2026-01-03T17:30:19.422Z" }, - { url = "https://files.pythonhosted.org/packages/bf/62/4b9eeb331da56530bf2e198a297e5303e1c1ebdceeb00fe9b568a65c5a0c/aiohttp-3.13.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b1a6102b4d3ebc07dad44fbf07b45bb600300f15b552ddf1851b5390202ea2e3", size = 1703932, upload-time = "2026-01-03T17:30:21.756Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f6/af16887b5d419e6a367095994c0b1332d154f647e7dc2bd50e61876e8e3d/aiohttp-3.13.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c014c7ea7fb775dd015b2d3137378b7be0249a448a1612268b5a90c2d81de04d", size = 1771906, upload-time = "2026-01-03T17:30:23.932Z" }, - { url = "https://files.pythonhosted.org/packages/ce/83/397c634b1bcc24292fa1e0c7822800f9f6569e32934bdeef09dae7992dfb/aiohttp-3.13.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2b8d8ddba8f95ba17582226f80e2de99c7a7948e66490ef8d947e272a93e9463", size = 1871020, upload-time = "2026-01-03T17:30:26Z" }, - { url = "https://files.pythonhosted.org/packages/86/f6/a62cbbf13f0ac80a70f71b1672feba90fdb21fd7abd8dbf25c0105fb6fa3/aiohttp-3.13.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9ae8dd55c8e6c4257eae3a20fd2c8f41edaea5992ed67156642493b8daf3cecc", size = 1755181, upload-time = "2026-01-03T17:30:27.554Z" }, - { url = "https://files.pythonhosted.org/packages/0a/87/20a35ad487efdd3fba93d5843efdfaa62d2f1479eaafa7453398a44faf13/aiohttp-3.13.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:01ad2529d4b5035578f5081606a465f3b814c542882804e2e8cda61adf5c71bf", size = 1561794, upload-time = "2026-01-03T17:30:29.254Z" }, - { url = "https://files.pythonhosted.org/packages/de/95/8fd69a66682012f6716e1bc09ef8a1a2a91922c5725cb904689f112309c4/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bb4f7475e359992b580559e008c598091c45b5088f28614e855e42d39c2f1033", size = 1697900, upload-time = "2026-01-03T17:30:31.033Z" }, - { url = "https://files.pythonhosted.org/packages/e5/66/7b94b3b5ba70e955ff597672dad1691333080e37f50280178967aff68657/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c19b90316ad3b24c69cd78d5c9b4f3aa4497643685901185b65166293d36a00f", size = 1728239, upload-time = "2026-01-03T17:30:32.703Z" }, - { url = "https://files.pythonhosted.org/packages/47/71/6f72f77f9f7d74719692ab65a2a0252584bf8d5f301e2ecb4c0da734530a/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:96d604498a7c782cb15a51c406acaea70d8c027ee6b90c569baa6e7b93073679", size = 1740527, upload-time = "2026-01-03T17:30:34.695Z" }, - { url = "https://files.pythonhosted.org/packages/fa/b4/75ec16cbbd5c01bdaf4a05b19e103e78d7ce1ef7c80867eb0ace42ff4488/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:084911a532763e9d3dd95adf78a78f4096cd5f58cdc18e6fdbc1b58417a45423", size = 1554489, upload-time = "2026-01-03T17:30:36.864Z" }, - { url = "https://files.pythonhosted.org/packages/52/8f/bc518c0eea29f8406dcf7ed1f96c9b48e3bc3995a96159b3fc11f9e08321/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7a4a94eb787e606d0a09404b9c38c113d3b099d508021faa615d70a0131907ce", size = 1767852, upload-time = "2026-01-03T17:30:39.433Z" }, - { url = "https://files.pythonhosted.org/packages/9d/f2/a07a75173124f31f11ea6f863dc44e6f09afe2bca45dd4e64979490deab1/aiohttp-3.13.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:87797e645d9d8e222e04160ee32aa06bc5c163e8499f24db719e7852ec23093a", size = 1722379, upload-time = "2026-01-03T17:30:41.081Z" }, - { url = "https://files.pythonhosted.org/packages/3c/4a/1a3fee7c21350cac78e5c5cef711bac1b94feca07399f3d406972e2d8fcd/aiohttp-3.13.3-cp312-cp312-win32.whl", hash = "sha256:b04be762396457bef43f3597c991e192ee7da460a4953d7e647ee4b1c28e7046", size = 428253, upload-time = "2026-01-03T17:30:42.644Z" }, - { url = "https://files.pythonhosted.org/packages/d9/b7/76175c7cb4eb73d91ad63c34e29fc4f77c9386bba4a65b53ba8e05ee3c39/aiohttp-3.13.3-cp312-cp312-win_amd64.whl", hash = "sha256:e3531d63d3bdfa7e3ac5e9b27b2dd7ec9df3206a98e0b3445fa906f233264c57", size = 455407, upload-time = "2026-01-03T17:30:44.195Z" }, + { url = "https://files.pythonhosted.org/packages/be/6f/353954c29e7dcce7cf00280a02c75f30e133c00793c7a2ed3776d7b2f426/aiohttp-3.13.5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:023ecba036ddd840b0b19bf195bfae970083fd7024ce1ac22e9bba90464620e9", size = 748876, upload-time = "2026-03-31T21:57:36.319Z" }, + { url = "https://files.pythonhosted.org/packages/f5/1b/428a7c64687b3b2e9cd293186695affc0e1e54a445d0361743b231f11066/aiohttp-3.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15c933ad7920b7d9a20de151efcd05a6e38302cbf0e10c9b2acb9a42210a2416", size = 499557, upload-time = "2026-03-31T21:57:38.236Z" }, + { url = "https://files.pythonhosted.org/packages/29/47/7be41556bfbb6917069d6a6634bb7dd5e163ba445b783a90d40f5ac7e3a7/aiohttp-3.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab2899f9fa2f9f741896ebb6fa07c4c883bfa5c7f2ddd8cf2aafa86fa981b2d2", size = 500258, upload-time = "2026-03-31T21:57:39.923Z" }, + { url = "https://files.pythonhosted.org/packages/67/84/c9ecc5828cb0b3695856c07c0a6817a99d51e2473400f705275a2b3d9239/aiohttp-3.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a60eaa2d440cd4707696b52e40ed3e2b0f73f65be07fd0ef23b6b539c9c0b0b4", size = 1749199, upload-time = "2026-03-31T21:57:41.938Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d3/3c6d610e66b495657622edb6ae7c7fd31b2e9086b4ec50b47897ad6042a9/aiohttp-3.13.5-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:55b3bdd3292283295774ab585160c4004f4f2f203946997f49aac032c84649e9", size = 1721013, upload-time = "2026-03-31T21:57:43.904Z" }, + { url = "https://files.pythonhosted.org/packages/49/a0/24409c12217456df0bae7babe3b014e460b0b38a8e60753d6cb339f6556d/aiohttp-3.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c2b2355dc094e5f7d45a7bb262fe7207aa0460b37a0d87027dcf21b5d890e7d5", size = 1781501, upload-time = "2026-03-31T21:57:46.285Z" }, + { url = "https://files.pythonhosted.org/packages/98/9d/b65ec649adc5bccc008b0957a9a9c691070aeac4e41cea18559fef49958b/aiohttp-3.13.5-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b38765950832f7d728297689ad78f5f2cf79ff82487131c4d26fe6ceecdc5f8e", size = 1878981, upload-time = "2026-03-31T21:57:48.734Z" }, + { url = "https://files.pythonhosted.org/packages/57/d8/8d44036d7eb7b6a8ec4c5494ea0c8c8b94fbc0ed3991c1a7adf230df03bf/aiohttp-3.13.5-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b18f31b80d5a33661e08c89e202edabf1986e9b49c42b4504371daeaa11b47c1", size = 1767934, upload-time = "2026-03-31T21:57:51.171Z" }, + { url = "https://files.pythonhosted.org/packages/31/04/d3f8211f273356f158e3464e9e45484d3fb8c4ce5eb2f6fe9405c3273983/aiohttp-3.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:33add2463dde55c4f2d9635c6ab33ce154e5ecf322bd26d09af95c5f81cfa286", size = 1566671, upload-time = "2026-03-31T21:57:53.326Z" }, + { url = "https://files.pythonhosted.org/packages/41/db/073e4ebe00b78e2dfcacff734291651729a62953b48933d765dc513bf798/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:327cc432fdf1356fb4fbc6fe833ad4e9f6aacb71a8acaa5f1855e4b25910e4a9", size = 1705219, upload-time = "2026-03-31T21:57:55.385Z" }, + { url = "https://files.pythonhosted.org/packages/48/45/7dfba71a2f9fd97b15c95c06819de7eb38113d2cdb6319669195a7d64270/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7c35b0bf0b48a70b4cb4fc5d7bed9b932532728e124874355de1a0af8ec4bc88", size = 1743049, upload-time = "2026-03-31T21:57:57.341Z" }, + { url = "https://files.pythonhosted.org/packages/18/71/901db0061e0f717d226386a7f471bb59b19566f2cae5f0d93874b017271f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:df23d57718f24badef8656c49743e11a89fd6f5358fa8a7b96e728fda2abf7d3", size = 1749557, upload-time = "2026-03-31T21:57:59.626Z" }, + { url = "https://files.pythonhosted.org/packages/08/d5/41eebd16066e59cd43728fe74bce953d7402f2b4ddfdfef2c0e9f17ca274/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:02e048037a6501a5ec1f6fc9736135aec6eb8a004ce48838cb951c515f32c80b", size = 1558931, upload-time = "2026-03-31T21:58:01.972Z" }, + { url = "https://files.pythonhosted.org/packages/30/e6/4a799798bf05740e66c3a1161079bda7a3dd8e22ca392481d7a7f9af82a6/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:31cebae8b26f8a615d2b546fee45d5ffb76852ae6450e2a03f42c9102260d6fe", size = 1774125, upload-time = "2026-03-31T21:58:04.007Z" }, + { url = "https://files.pythonhosted.org/packages/84/63/7749337c90f92bc2cb18f9560d67aa6258c7060d1397d21529b8004fcf6f/aiohttp-3.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:888e78eb5ca55a615d285c3c09a7a91b42e9dd6fc699b166ebd5dee87c9ccf14", size = 1732427, upload-time = "2026-03-31T21:58:06.337Z" }, + { url = "https://files.pythonhosted.org/packages/98/de/cf2f44ff98d307e72fb97d5f5bbae3bfcb442f0ea9790c0bf5c5c2331404/aiohttp-3.13.5-cp312-cp312-win32.whl", hash = "sha256:8bd3ec6376e68a41f9f95f5ed170e2fcf22d4eb27a1f8cb361d0508f6e0557f3", size = 433534, upload-time = "2026-03-31T21:58:08.712Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ca/eadf6f9c8fa5e31d40993e3db153fb5ed0b11008ad5d9de98a95045bed84/aiohttp-3.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:110e448e02c729bcebb18c60b9214a87ba33bac4a9fa5e9a5f139938b56c6cb1", size = 460446, upload-time = "2026-03-31T21:58:10.945Z" }, ] [[package]] @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#90b7fefbe37fc2ca26597e6e9e0035dd386effa1" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#1ddfd3eb7b9e30a957c263930e1b0660e5dce6d1" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#05582563f2fdf6638a550fef61b129a2fb288d05" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#6e99db11a1dc5dfa74be40d1e0666ebe10c8e0d7" } [[package]] name = "casadi" @@ -174,39 +174,39 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, - { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, - { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, - { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, - { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, - { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, - { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, - { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, - { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, - { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, +version = "3.4.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/a1/67fe25fac3c7642725500a3f6cfe5821ad557c3abb11c9d20d12c7008d3e/charset_normalizer-3.4.7.tar.gz", hash = "sha256:ae89db9e5f98a11a4bf50407d4363e7b09b31e55bc117b4f7d80aab97ba009e5", size = 144271, upload-time = "2026-04-02T09:28:39.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/eb/4fc8d0a7110eb5fc9cc161723a34a8a6c200ce3b4fbf681bc86feee22308/charset_normalizer-3.4.7-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:eca9705049ad3c7345d574e3510665cb2cf844c2f2dcfe675332677f081cbd46", size = 311328, upload-time = "2026-04-02T09:26:24.331Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e3/0fadc706008ac9d7b9b5be6dc767c05f9d3e5df51744ce4cc9605de7b9f4/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6178f72c5508bfc5fd446a5905e698c6212932f25bcdd4b47a757a50605a90e2", size = 208061, upload-time = "2026-04-02T09:26:25.568Z" }, + { url = "https://files.pythonhosted.org/packages/42/f0/3dd1045c47f4a4604df85ec18ad093912ae1344ac706993aff91d38773a2/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1421b502d83040e6d7fb2fb18dff63957f720da3d77b2fbd3187ceb63755d7b", size = 229031, upload-time = "2026-04-02T09:26:26.865Z" }, + { url = "https://files.pythonhosted.org/packages/dc/67/675a46eb016118a2fbde5a277a5d15f4f69d5f3f5f338e5ee2f8948fcf43/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:edac0f1ab77644605be2cbba52e6b7f630731fc42b34cb0f634be1a6eface56a", size = 225239, upload-time = "2026-04-02T09:26:28.044Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f8/d0118a2f5f23b02cd166fa385c60f9b0d4f9194f574e2b31cef350ad7223/charset_normalizer-3.4.7-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5649fd1c7bade02f320a462fdefd0b4bd3ce036065836d4f42e0de958038e116", size = 216589, upload-time = "2026-04-02T09:26:29.239Z" }, + { url = "https://files.pythonhosted.org/packages/b1/f1/6d2b0b261b6c4ceef0fcb0d17a01cc5bc53586c2d4796fa04b5c540bc13d/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:203104ed3e428044fd943bc4bf45fa73c0730391f9621e37fe39ecf477b128cb", size = 202733, upload-time = "2026-04-02T09:26:30.5Z" }, + { url = "https://files.pythonhosted.org/packages/6f/c0/7b1f943f7e87cc3db9626ba17807d042c38645f0a1d4415c7a14afb5591f/charset_normalizer-3.4.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:298930cec56029e05497a76988377cbd7457ba864beeea92ad7e844fe74cd1f1", size = 212652, upload-time = "2026-04-02T09:26:31.709Z" }, + { url = "https://files.pythonhosted.org/packages/38/dd/5a9ab159fe45c6e72079398f277b7d2b523e7f716acc489726115a910097/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:708838739abf24b2ceb208d0e22403dd018faeef86ddac04319a62ae884c4f15", size = 211229, upload-time = "2026-04-02T09:26:33.282Z" }, + { url = "https://files.pythonhosted.org/packages/d5/ff/531a1cad5ca855d1c1a8b69cb71abfd6d85c0291580146fda7c82857caa1/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0f7eb884681e3938906ed0434f20c63046eacd0111c4ba96f27b76084cd679f5", size = 203552, upload-time = "2026-04-02T09:26:34.845Z" }, + { url = "https://files.pythonhosted.org/packages/c1/4c/a5fb52d528a8ca41f7598cb619409ece30a169fbdf9cdce592e53b46c3a6/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4dc1e73c36828f982bfe79fadf5919923f8a6f4df2860804db9a98c48824ce8d", size = 230806, upload-time = "2026-04-02T09:26:36.152Z" }, + { url = "https://files.pythonhosted.org/packages/59/7a/071feed8124111a32b316b33ae4de83d36923039ef8cf48120266844285b/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:aed52fea0513bac0ccde438c188c8a471c4e0f457c2dd20cdbf6ea7a450046c7", size = 212316, upload-time = "2026-04-02T09:26:37.672Z" }, + { url = "https://files.pythonhosted.org/packages/fd/35/f7dba3994312d7ba508e041eaac39a36b120f32d4c8662b8814dab876431/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fea24543955a6a729c45a73fe90e08c743f0b3334bbf3201e6c4bc1b0c7fa464", size = 227274, upload-time = "2026-04-02T09:26:38.93Z" }, + { url = "https://files.pythonhosted.org/packages/8a/2d/a572df5c9204ab7688ec1edc895a73ebded3b023bb07364710b05dd1c9be/charset_normalizer-3.4.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:bb6d88045545b26da47aa879dd4a89a71d1dce0f0e549b1abcb31dfe4a8eac49", size = 218468, upload-time = "2026-04-02T09:26:40.17Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/890922a8b03a568ca2f336c36585a4713c55d4d67bf0f0c78924be6315ca/charset_normalizer-3.4.7-cp312-cp312-win32.whl", hash = "sha256:2257141f39fe65a3fdf38aeccae4b953e5f3b3324f4ff0daf9f15b8518666a2c", size = 148460, upload-time = "2026-04-02T09:26:41.416Z" }, + { url = "https://files.pythonhosted.org/packages/35/d9/0e7dffa06c5ab081f75b1b786f0aefc88365825dfcd0ac544bdb7b2b6853/charset_normalizer-3.4.7-cp312-cp312-win_amd64.whl", hash = "sha256:5ed6ab538499c8644b8a3e18debabcd7ce684f3fa91cf867521a7a0279cab2d6", size = 159330, upload-time = "2026-04-02T09:26:42.554Z" }, + { url = "https://files.pythonhosted.org/packages/9e/5d/481bcc2a7c88ea6b0878c299547843b2521ccbc40980cb406267088bc701/charset_normalizer-3.4.7-cp312-cp312-win_arm64.whl", hash = "sha256:56be790f86bfb2c98fb742ce566dfb4816e5a83384616ab59c49e0604d49c51d", size = 147828, upload-time = "2026-04-02T09:26:44.075Z" }, + { url = "https://files.pythonhosted.org/packages/db/8f/61959034484a4a7c527811f4721e75d02d653a35afb0b6054474d8185d4c/charset_normalizer-3.4.7-py3-none-any.whl", hash = "sha256:3dce51d0f5e7951f8bb4900c257dad282f49190fdbebecd4ba99bcc41fef404d", size = 61958, upload-time = "2026-04-02T09:28:37.794Z" }, ] [[package]] name = "click" -version = "8.3.1" +version = "8.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, ] [[package]] @@ -291,41 +291,41 @@ wheels = [ [[package]] name = "cryptography" -version = "46.0.5" +version = "46.0.7" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, - { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, - { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, - { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, - { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, - { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, - { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, - { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, - { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, - { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, - { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, - { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, - { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, - { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, - { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, - { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, - { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, - { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, - { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, - { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, - { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, - { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, - { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, - { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, - { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, - { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, + { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, + { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, + { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, + { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, + { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, + { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, + { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, + { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, + { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, + { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, + { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, + { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, + { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, + { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, + { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, + { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, + { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, + { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, + { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, + { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, ] [[package]] @@ -371,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#40e5d76de1b33a86c5181b63db6782d8f06da1da" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#891c42d8029b2a633f3aca7f60cc7aa4b5305405" } [[package]] name = "execnet" @@ -385,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#b9732165bcf5a3fab83b05994187802a0d115b6e" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#8261317427e81a0fa1f53a7ef77f15004ec78889" } [[package]] name = "fonttools" @@ -432,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#15a616d4f08f6b8ecaa9b2390c75d2fe0c0fffb8" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#fd995de677db114e2862cf4ed245ca9a17536668" } [[package]] name = "ghp-import" @@ -449,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#f77417aad13a05b03bb2696a0b5a124f339d117b" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#9fdbe7eb0257d7a13851ed4baa52fbccbe7e2e9d" } [[package]] name = "google-crc32c" @@ -498,7 +498,7 @@ wheels = [ [[package]] name = "imgui" version = "1.92.7" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui#c5c108b23a2e0346480d7f4c4981bf6ec7ba9054" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui#f3d874be2f3aa44869ffd4775e0957e986a30a68" } [[package]] name = "iniconfig" @@ -578,12 +578,12 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#2d69723fe445dadc68ceb9072510a505111b64a7" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#d90bc630661092de49428bfc3a82a371ee35a889" } [[package]] name = "libusb" version = "1.0.29" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#8daf8079f98809ef4674177bca915a0a81eac52f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#6562b0138726a380368d68a6ac5f6e36d6aea2da" } [[package]] name = "libusb1" @@ -599,7 +599,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#28c3c2a2444232aeeaf989c33fd333ce74e6fc90" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#22b976c39a3f2607ef5458056b1a10558da0e85f" } [[package]] name = "markdown" @@ -751,25 +751,25 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#e33e7f648009ad97638b1a0a373a06a05526c040" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#b733e08a93873e8d8ac47caabc2eb64a425f7146" } [[package]] name = "numpy" -version = "2.4.3" +version = "2.4.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/8b/c265f4823726ab832de836cdd184d0986dcf94480f81e8739692a7ac7af2/numpy-2.4.3.tar.gz", hash = "sha256:483a201202b73495f00dbc83796c6ae63137a9bdade074f7648b3e32613412dd", size = 20727743, upload-time = "2026-03-09T07:58:53.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587, upload-time = "2026-03-29T13:22:01.298Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a9/ed/6388632536f9788cea23a3a1b629f25b43eaacd7d7377e5d6bc7b9deb69b/numpy-2.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:61b0cbabbb6126c8df63b9a3a0c4b1f44ebca5e12ff6997b80fcf267fb3150ef", size = 16669628, upload-time = "2026-03-09T07:56:24.252Z" }, - { url = "https://files.pythonhosted.org/packages/74/1b/ee2abfc68e1ce728b2958b6ba831d65c62e1b13ce3017c13943f8f9b5b2e/numpy-2.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7395e69ff32526710748f92cd8c9849b361830968ea3e24a676f272653e8983e", size = 14696872, upload-time = "2026-03-09T07:56:26.991Z" }, - { url = "https://files.pythonhosted.org/packages/ba/d1/780400e915ff5638166f11ca9dc2c5815189f3d7cf6f8759a1685e586413/numpy-2.4.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:abdce0f71dcb4a00e4e77f3faf05e4616ceccfe72ccaa07f47ee79cda3b7b0f4", size = 5203489, upload-time = "2026-03-09T07:56:29.414Z" }, - { url = "https://files.pythonhosted.org/packages/0b/bb/baffa907e9da4cc34a6e556d6d90e032f6d7a75ea47968ea92b4858826c4/numpy-2.4.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:48da3a4ee1336454b07497ff7ec83903efa5505792c4e6d9bf83d99dc07a1e18", size = 6550814, upload-time = "2026-03-09T07:56:32.225Z" }, - { url = "https://files.pythonhosted.org/packages/7b/12/8c9f0c6c95f76aeb20fc4a699c33e9f827fa0d0f857747c73bb7b17af945/numpy-2.4.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:32e3bef222ad6b052280311d1d60db8e259e4947052c3ae7dd6817451fc8a4c5", size = 15666601, upload-time = "2026-03-09T07:56:34.461Z" }, - { url = "https://files.pythonhosted.org/packages/bd/79/cc665495e4d57d0aa6fbcc0aa57aa82671dfc78fbf95fe733ed86d98f52a/numpy-2.4.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e7dd01a46700b1967487141a66ac1a3cf0dd8ebf1f08db37d46389401512ca97", size = 16621358, upload-time = "2026-03-09T07:56:36.852Z" }, - { url = "https://files.pythonhosted.org/packages/a8/40/b4ecb7224af1065c3539f5ecfff879d090de09608ad1008f02c05c770cb3/numpy-2.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:76f0f283506c28b12bba319c0fab98217e9f9b54e6160e9c79e9f7348ba32e9c", size = 17016135, upload-time = "2026-03-09T07:56:39.337Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b1/6a88e888052eed951afed7a142dcdf3b149a030ca59b4c71eef085858e43/numpy-2.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737f630a337364665aba3b5a77e56a68cc42d350edd010c345d65a3efa3addcc", size = 18345816, upload-time = "2026-03-09T07:56:42.31Z" }, - { url = "https://files.pythonhosted.org/packages/f3/8f/103a60c5f8c3d7fc678c19cd7b2476110da689ccb80bc18050efbaeae183/numpy-2.4.3-cp312-cp312-win32.whl", hash = "sha256:26952e18d82a1dbbc2f008d402021baa8d6fc8e84347a2072a25e08b46d698b9", size = 5960132, upload-time = "2026-03-09T07:56:44.851Z" }, - { url = "https://files.pythonhosted.org/packages/d7/7c/f5ee1bf6ed888494978046a809df2882aad35d414b622893322df7286879/numpy-2.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:65f3c2455188f09678355f5cae1f959a06b778bc66d535da07bf2ef20cd319d5", size = 12316144, upload-time = "2026-03-09T07:56:47.057Z" }, - { url = "https://files.pythonhosted.org/packages/71/46/8d1cb3f7a00f2fb6394140e7e6623696e54c6318a9d9691bb4904672cf42/numpy-2.4.3-cp312-cp312-win_arm64.whl", hash = "sha256:2abad5c7fef172b3377502bde47892439bae394a71bc329f31df0fd829b41a9e", size = 10220364, upload-time = "2026-03-09T07:56:49.849Z" }, + { url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272, upload-time = "2026-03-29T13:18:49.223Z" }, + { url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573, upload-time = "2026-03-29T13:18:52.629Z" }, + { url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782, upload-time = "2026-03-29T13:18:55.579Z" }, + { url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038, upload-time = "2026-03-29T13:18:57.769Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666, upload-time = "2026-03-29T13:19:00.341Z" }, + { url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480, upload-time = "2026-03-29T13:19:03.63Z" }, + { url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036, upload-time = "2026-03-29T13:19:07.428Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643, upload-time = "2026-03-29T13:19:10.775Z" }, + { url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117, upload-time = "2026-03-29T13:19:13.464Z" }, + { url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584, upload-time = "2026-03-29T13:19:16.155Z" }, + { url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450, upload-time = "2026-03-29T13:19:18.994Z" }, ] [[package]] @@ -997,21 +997,21 @@ wheels = [ [[package]] name = "pillow" -version = "12.1.1" +version = "12.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/d3/8df65da0d4df36b094351dce696f2989bec731d4f10e743b1c5f4da4d3bf/pillow-12.1.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ab323b787d6e18b3d91a72fc99b1a2c28651e4358749842b8f8dfacd28ef2052", size = 5262803, upload-time = "2026-02-11T04:20:47.653Z" }, - { url = "https://files.pythonhosted.org/packages/d6/71/5026395b290ff404b836e636f51d7297e6c83beceaa87c592718747e670f/pillow-12.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:adebb5bee0f0af4909c30db0d890c773d1a92ffe83da908e2e9e720f8edf3984", size = 4657601, upload-time = "2026-02-11T04:20:49.328Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2e/1001613d941c67442f745aff0f7cc66dd8df9a9c084eb497e6a543ee6f7e/pillow-12.1.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bb66b7cc26f50977108790e2456b7921e773f23db5630261102233eb355a3b79", size = 6234995, upload-time = "2026-02-11T04:20:51.032Z" }, - { url = "https://files.pythonhosted.org/packages/07/26/246ab11455b2549b9233dbd44d358d033a2f780fa9007b61a913c5b2d24e/pillow-12.1.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aee2810642b2898bb187ced9b349e95d2a7272930796e022efaf12e99dccd293", size = 8045012, upload-time = "2026-02-11T04:20:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/b2/8b/07587069c27be7535ac1fe33874e32de118fbd34e2a73b7f83436a88368c/pillow-12.1.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a0b1cd6232e2b618adcc54d9882e4e662a089d5768cd188f7c245b4c8c44a397", size = 6349638, upload-time = "2026-02-11T04:20:54.444Z" }, - { url = "https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7aac39bcf8d4770d089588a2e1dd111cbaa42df5a94be3114222057d68336bd0", size = 7041540, upload-time = "2026-02-11T04:20:55.97Z" }, - { url = "https://files.pythonhosted.org/packages/2c/5e/2ba19e7e7236d7529f4d873bdaf317a318896bac289abebd4bb00ef247f0/pillow-12.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ab174cd7d29a62dd139c44bf74b698039328f45cb03b4596c43473a46656b2f3", size = 6462613, upload-time = "2026-02-11T04:20:57.542Z" }, - { url = "https://files.pythonhosted.org/packages/03/03/31216ec124bb5c3dacd74ce8efff4cc7f52643653bad4825f8f08c697743/pillow-12.1.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:339ffdcb7cbeaa08221cd401d517d4b1fe7a9ed5d400e4a8039719238620ca35", size = 7166745, upload-time = "2026-02-11T04:20:59.196Z" }, - { url = "https://files.pythonhosted.org/packages/1f/e7/7c4552d80052337eb28653b617eafdef39adfb137c49dd7e831b8dc13bc5/pillow-12.1.1-cp312-cp312-win32.whl", hash = "sha256:5d1f9575a12bed9e9eedd9a4972834b08c97a352bd17955ccdebfeca5913fa0a", size = 6328823, upload-time = "2026-02-11T04:21:01.385Z" }, - { url = "https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:21329ec8c96c6e979cd0dfd29406c40c1d52521a90544463057d2aaa937d66a6", size = 7033367, upload-time = "2026-02-11T04:21:03.536Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/a0ef1f73f939b0eca03ee2c108d0043a87468664770612602c63266a43c4/pillow-12.1.1-cp312-cp312-win_arm64.whl", hash = "sha256:af9a332e572978f0218686636610555ae3defd1633597be015ed50289a03c523", size = 2453811, upload-time = "2026-02-11T04:21:05.116Z" }, + { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, ] [[package]] @@ -1148,11 +1148,11 @@ wheels = [ [[package]] name = "pygments" -version = "2.19.2" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] @@ -1219,7 +1219,7 @@ wheels = [ [[package]] name = "pytest" -version = "9.0.2" +version = "9.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -1228,9 +1228,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] @@ -1387,7 +1387,7 @@ wheels = [ [[package]] name = "requests" -version = "2.32.5" +version = "2.33.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -1395,9 +1395,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5f/a4/98b9c7c6428a668bf7e42ebb7c79d576a1c3c1e3ae2d47e674b468388871/requests-2.33.1.tar.gz", hash = "sha256:18817f8c57c6263968bc123d237e3b8b08ac046f5456bd1e307ee8f4250d3517", size = 134120, upload-time = "2026-03-30T16:09:15.531Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, + { url = "https://files.pythonhosted.org/packages/d7/8e/7540e8a2036f79a125c1d2ebadf69ed7901608859186c856fa0388ef4197/requests-2.33.1-py3-none-any.whl", hash = "sha256:4e6d1ef462f3626a1f0a0a9c42dd93c63bad33f9f1c1937509b8c5c8718ab56a", size = 64947, upload-time = "2026-03-30T16:09:13.83Z" }, ] [[package]] @@ -1411,27 +1411,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.7" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/22/9e4f66ee588588dc6c9af6a994e12d26e19efbe874d1a909d09a6dac7a59/ruff-0.15.7.tar.gz", hash = "sha256:04f1ae61fc20fe0b148617c324d9d009b5f63412c0b16474f3d5f1a1a665f7ac", size = 4601277, upload-time = "2026-03-19T16:26:22.605Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/2f/0b08ced94412af091807b6119ca03755d651d3d93a242682bf020189db94/ruff-0.15.7-py3-none-linux_armv6l.whl", hash = "sha256:a81cc5b6910fb7dfc7c32d20652e50fa05963f6e13ead3c5915c41ac5d16668e", size = 10489037, upload-time = "2026-03-19T16:26:32.47Z" }, - { url = "https://files.pythonhosted.org/packages/91/4a/82e0fa632e5c8b1eba5ee86ecd929e8ff327bbdbfb3c6ac5d81631bef605/ruff-0.15.7-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:722d165bd52403f3bdabc0ce9e41fc47070ac56d7a91b4e0d097b516a53a3477", size = 10955433, upload-time = "2026-03-19T16:27:00.205Z" }, - { url = "https://files.pythonhosted.org/packages/ab/10/12586735d0ff42526ad78c049bf51d7428618c8b5c467e72508c694119df/ruff-0.15.7-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7fbc2448094262552146cbe1b9643a92f66559d3761f1ad0656d4991491af49e", size = 10269302, upload-time = "2026-03-19T16:26:26.183Z" }, - { url = "https://files.pythonhosted.org/packages/eb/5d/32b5c44ccf149a26623671df49cbfbd0a0ae511ff3df9d9d2426966a8d57/ruff-0.15.7-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b39329b60eba44156d138275323cc726bbfbddcec3063da57caa8a8b1d50adf", size = 10607625, upload-time = "2026-03-19T16:27:03.263Z" }, - { url = "https://files.pythonhosted.org/packages/5d/f1/f0001cabe86173aaacb6eb9bb734aa0605f9a6aa6fa7d43cb49cbc4af9c9/ruff-0.15.7-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87768c151808505f2bfc93ae44e5f9e7c8518943e5074f76ac21558ef5627c85", size = 10324743, upload-time = "2026-03-19T16:27:09.791Z" }, - { url = "https://files.pythonhosted.org/packages/7a/87/b8a8f3d56b8d848008559e7c9d8bf367934d5367f6d932ba779456e2f73b/ruff-0.15.7-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb0511670002c6c529ec66c0e30641c976c8963de26a113f3a30456b702468b0", size = 11138536, upload-time = "2026-03-19T16:27:06.101Z" }, - { url = "https://files.pythonhosted.org/packages/e4/f2/4fd0d05aab0c5934b2e1464784f85ba2eab9d54bffc53fb5430d1ed8b829/ruff-0.15.7-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0d19644f801849229db8345180a71bee5407b429dd217f853ec515e968a6912", size = 11994292, upload-time = "2026-03-19T16:26:48.718Z" }, - { url = "https://files.pythonhosted.org/packages/64/22/fc4483871e767e5e95d1622ad83dad5ebb830f762ed0420fde7dfa9d9b08/ruff-0.15.7-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4806d8e09ef5e84eb19ba833d0442f7e300b23fe3f0981cae159a248a10f0036", size = 11398981, upload-time = "2026-03-19T16:26:54.513Z" }, - { url = "https://files.pythonhosted.org/packages/b0/99/66f0343176d5eab02c3f7fcd2de7a8e0dd7a41f0d982bee56cd1c24db62b/ruff-0.15.7-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dce0896488562f09a27b9c91b1f58a097457143931f3c4d519690dea54e624c5", size = 11242422, upload-time = "2026-03-19T16:26:29.277Z" }, - { url = "https://files.pythonhosted.org/packages/5d/3a/a7060f145bfdcce4c987ea27788b30c60e2c81d6e9a65157ca8afe646328/ruff-0.15.7-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:1852ce241d2bc89e5dc823e03cff4ce73d816b5c6cdadd27dbfe7b03217d2a12", size = 11232158, upload-time = "2026-03-19T16:26:42.321Z" }, - { url = "https://files.pythonhosted.org/packages/a7/53/90fbb9e08b29c048c403558d3cdd0adf2668b02ce9d50602452e187cd4af/ruff-0.15.7-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5f3e4b221fb4bd293f79912fc5e93a9063ebd6d0dcbd528f91b89172a9b8436c", size = 10577861, upload-time = "2026-03-19T16:26:57.459Z" }, - { url = "https://files.pythonhosted.org/packages/2f/aa/5f486226538fe4d0f0439e2da1716e1acf895e2a232b26f2459c55f8ddad/ruff-0.15.7-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:b15e48602c9c1d9bdc504b472e90b90c97dc7d46c7028011ae67f3861ceba7b4", size = 10327310, upload-time = "2026-03-19T16:26:35.909Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/271afdffb81fe7bfc8c43ba079e9d96238f674380099457a74ccb3863857/ruff-0.15.7-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1b4705e0e85cedc74b0a23cf6a179dbb3df184cb227761979cc76c0440b5ab0d", size = 10840752, upload-time = "2026-03-19T16:26:45.723Z" }, - { url = "https://files.pythonhosted.org/packages/bf/29/a4ae78394f76c7759953c47884eb44de271b03a66634148d9f7d11e721bd/ruff-0.15.7-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:112c1fa316a558bb34319282c1200a8bf0495f1b735aeb78bfcb2991e6087580", size = 11336961, upload-time = "2026-03-19T16:26:39.076Z" }, - { url = "https://files.pythonhosted.org/packages/26/6b/8786ba5736562220d588a2f6653e6c17e90c59ced34a2d7b512ef8956103/ruff-0.15.7-py3-none-win32.whl", hash = "sha256:6d39e2d3505b082323352f733599f28169d12e891f7dd407f2d4f54b4c2886de", size = 10582538, upload-time = "2026-03-19T16:26:15.992Z" }, - { url = "https://files.pythonhosted.org/packages/2b/e9/346d4d3fffc6871125e877dae8d9a1966b254fbd92a50f8561078b88b099/ruff-0.15.7-py3-none-win_amd64.whl", hash = "sha256:4d53d712ddebcd7dace1bc395367aec12c057aacfe9adbb6d832302575f4d3a1", size = 11755839, upload-time = "2026-03-19T16:26:19.897Z" }, - { url = "https://files.pythonhosted.org/packages/8f/e8/726643a3ea68c727da31570bde48c7a10f1aa60eddd628d94078fec586ff/ruff-0.15.7-py3-none-win_arm64.whl", hash = "sha256:18e8d73f1c3fdf27931497972250340f92e8c861722161a9caeb89a58ead6ed2", size = 11023304, upload-time = "2026-03-19T16:26:51.669Z" }, +version = "0.15.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, + { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, + { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, + { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, + { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, + { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, + { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, + { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, + { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, + { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, + { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, + { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, ] [[package]] @@ -1445,15 +1445,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.55.0" +version = "2.57.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e9/b8/285293dc60fc198fffc3fcdbc7c6d4e646e0f74e61461c355d40faa64ceb/sentry_sdk-2.55.0.tar.gz", hash = "sha256:3774c4d8820720ca4101548131b9c162f4c9426eb7f4d24aca453012a7470f69", size = 424505, upload-time = "2026-03-17T14:15:51.707Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4f/87/46c0406d8b5ddd026f73adaf5ab75ce144219c41a4830b52df4b9ab55f7f/sentry_sdk-2.57.0.tar.gz", hash = "sha256:4be8d1e71c32fb27f79c577a337ac8912137bba4bcbc64a4ec1da4d6d8dc5199", size = 435288, upload-time = "2026-03-31T09:39:29.264Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/66/20465097782d7e1e742d846407ea7262d338c6e876ddddad38ca8907b38f/sentry_sdk-2.55.0-py2.py3-none-any.whl", hash = "sha256:97026981cb15699394474a196b88503a393cbc58d182ece0d3abe12b9bd978d4", size = 449284, upload-time = "2026-03-17T14:15:49.604Z" }, + { url = "https://files.pythonhosted.org/packages/c9/64/982e07b93219cb52e1cca5d272cb579e2f3eb001956c9e7a9a6d106c9473/sentry_sdk-2.57.0-py2.py3-none-any.whl", hash = "sha256:812c8bf5ff3d2f0e89c82f5ce80ab3a6423e102729c4706af7413fd1eb480585", size = 456489, upload-time = "2026-03-31T09:39:27.524Z" }, ] [[package]] @@ -1549,26 +1549,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.24" +version = "0.0.29" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7a/96/652a425030f95dc2c9548d9019e52502e17079e1daeefbc4036f1c0905b4/ty-0.0.24.tar.gz", hash = "sha256:9fe42f6b98207bdaef51f71487d6d087f2cb02555ee3939884d779b2b3cc8bfc", size = 5354286, upload-time = "2026-03-19T16:55:57.035Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d5/853561de49fae38c519e905b2d8da9c531219608f1fccc47a0fc2c896980/ty-0.0.29.tar.gz", hash = "sha256:e7936cca2f691eeda631876c92809688dbbab68687c3473f526cd83b6a9228d8", size = 5469221, upload-time = "2026-04-05T15:01:21.328Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/e5/34457ee11708e734ba81ad65723af83030e484f961e281d57d1eecf08951/ty-0.0.24-py3-none-linux_armv6l.whl", hash = "sha256:1ab4f1f61334d533a3fdf5d9772b51b1300ac5da4f3cdb0be9657a3ccb2ce3e7", size = 10394877, upload-time = "2026-03-19T16:55:54.246Z" }, - { url = "https://files.pythonhosted.org/packages/44/81/bc9a1b1a87f43db15ab64ad781a4f999734ec3b470ad042624fa875b20e6/ty-0.0.24-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:facbf2c4aaa6985229e08f8f9bf152215eb078212f22b5c2411f35386688ab42", size = 10211109, upload-time = "2026-03-19T16:55:28.554Z" }, - { url = "https://files.pythonhosted.org/packages/e4/63/cfc805adeaa61d63ba3ea71127efa7d97c40ba36d97ee7bd957341d05107/ty-0.0.24-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b6d2a3b6d4470c483552a31e9b368c86f154dcc964bccb5406159dc9cd362246", size = 9694769, upload-time = "2026-03-19T16:55:34.309Z" }, - { url = "https://files.pythonhosted.org/packages/33/09/edc220726b6ec44a58900401f6b27140997ef15026b791e26b69a6e69eb5/ty-0.0.24-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c94c25d0500939fd5f8f16ce41cbed5b20528702c1d649bf80300253813f0a2", size = 10176287, upload-time = "2026-03-19T16:55:37.17Z" }, - { url = "https://files.pythonhosted.org/packages/f8/bf/cbe2227be711e65017655d8ee4d050f4c92b113fb4dc4c3bd6a19d3a86d8/ty-0.0.24-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:89cbe7bc7df0fab02dbd8cda79b737df83f1ef7fb573b08c0ee043dc68cffb08", size = 10214832, upload-time = "2026-03-19T16:56:08.518Z" }, - { url = "https://files.pythonhosted.org/packages/af/1d/d15803ee47e9143d10e10bd81ccc14761d08758082bda402950685f0ddfe/ty-0.0.24-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db2c5d269bcc9b764850c99f457b5018a79b3ef40ecfbc03344e65effd6cf743", size = 10709892, upload-time = "2026-03-19T16:56:05.727Z" }, - { url = "https://files.pythonhosted.org/packages/36/12/6db0d86c477147f67b9052de209421d76c3e855197b000c25fcbbe86b3a2/ty-0.0.24-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba44512db5b97c3bbd59d93e11296e8548d0c9a3bdd1280de36d7ff22d351896", size = 11280872, upload-time = "2026-03-19T16:56:02.899Z" }, - { url = "https://files.pythonhosted.org/packages/1b/fc/155fe83a97c06d33ccc9e0f428258b32df2e08a428300c715d34757f0111/ty-0.0.24-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a52b7f589c3205512a9c50ba5b2b1e8c0698b72e51b8b9285c90420c06f1cae8", size = 11060520, upload-time = "2026-03-19T16:55:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/ac/f1/32c05a1c4c3c2a95c5b7361dee03a9bf1231d4ad096b161c838b45bce5a0/ty-0.0.24-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7981df5c709c054da4ac5d7c93f8feb8f45e69e829e4461df4d5f0988fe67d04", size = 10791455, upload-time = "2026-03-19T16:55:25.728Z" }, - { url = "https://files.pythonhosted.org/packages/17/2c/53c1ea6bedfa4d4ab64d4de262d8f5e405ecbffefd364459c628c0310d33/ty-0.0.24-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2860151ad95a00d0f0280b8fef79900d08dcd63276b57e6e5774f2c055979c5", size = 10156708, upload-time = "2026-03-19T16:55:45.563Z" }, - { url = "https://files.pythonhosted.org/packages/45/39/7d2919cf194707169474d80720a5f3d793e983416f25e7ffcf80504c9df2/ty-0.0.24-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5674a1146d927ab77ff198a88e0c4505134ced342a0e7d1beb4a076a728b7496", size = 10236263, upload-time = "2026-03-19T16:55:31.474Z" }, - { url = "https://files.pythonhosted.org/packages/cf/7f/48eac722f2fd12a5b7aae0effdcb75c46053f94b783d989e3ef0d7380082/ty-0.0.24-py3-none-musllinux_1_2_i686.whl", hash = "sha256:438ecbf1608a9b16dd84502f3f1b23ef2ef32bbd0ab3e0ca5a82f0e0d1cd41ea", size = 10402559, upload-time = "2026-03-19T16:55:39.602Z" }, - { url = "https://files.pythonhosted.org/packages/75/e0/8cf868b9749ce1e5166462759545964e95b02353243594062b927d8bff2a/ty-0.0.24-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ddeed3098dd92a83964e7aa7b41e509ba3530eb539fc4cd8322ff64a09daf1f5", size = 10893684, upload-time = "2026-03-19T16:55:51.439Z" }, - { url = "https://files.pythonhosted.org/packages/17/9f/f54bf3be01d2c2ed731d10a5afa3324dc66f987a6ae0a4a6cbfa2323d080/ty-0.0.24-py3-none-win32.whl", hash = "sha256:83013fb3a4764a8f8bcc6ca11ff8bdfd8c5f719fc249241cb2b8916e80778eb1", size = 9781542, upload-time = "2026-03-19T16:56:11.588Z" }, - { url = "https://files.pythonhosted.org/packages/fb/49/c004c5cc258b10b3a145666e9a9c28ae7678bc958c8926e8078d5d769081/ty-0.0.24-py3-none-win_amd64.whl", hash = "sha256:748a60eb6912d1cf27aaab105ffadb6f4d2e458a3fcadfbd3cf26db0d8062eeb", size = 10764801, upload-time = "2026-03-19T16:55:42.752Z" }, - { url = "https://files.pythonhosted.org/packages/e2/59/006a074e185bfccf5e4c026015245ab4fcd2362b13a8d24cf37a277909a9/ty-0.0.24-py3-none-win_arm64.whl", hash = "sha256:280a3d31e86d0721947238f17030c33f0911cae851d108ea9f4e3ab12a5ed01f", size = 10194093, upload-time = "2026-03-19T16:55:48.303Z" }, + { url = "https://files.pythonhosted.org/packages/03/b7/911f9962115acfa24e3b2ec9d4992dd994c38e8769e1b1d7680bb4d28a51/ty-0.0.29-py3-none-linux_armv6l.whl", hash = "sha256:b8a40955f7660d3eaceb0d964affc81b790c0765e7052921a5f861ff8a471c30", size = 10568206, upload-time = "2026-04-05T15:01:19.165Z" }, + { url = "https://files.pythonhosted.org/packages/fe/c3/fcae2167d4c77a97269f92f11d1b43b03617f81de1283d5d05b43432110c/ty-0.0.29-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6b6849adae15b00bbe2d3c5b078967dcb62eba37d38936b8eeb4c81a82d2e3b8", size = 10442530, upload-time = "2026-04-05T15:01:28.471Z" }, + { url = "https://files.pythonhosted.org/packages/97/33/5a6bfa240cfcb9c36046ae2459fa9ea23238d20130d8656ff5ac4d6c012a/ty-0.0.29-py3-none-macosx_11_0_arm64.whl", hash = "sha256:dcdd9b17209788152f7b7ea815eda07989152325052fe690013537cc7904ce49", size = 9915735, upload-time = "2026-04-05T15:01:10.365Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1e/318f45fae232118e81a6306c30f50de42c509c412128d5bd231eab699ffb/ty-0.0.29-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d8ed4789bae78ffaf94462c0d25589a734cab0366b86f2bbcb1bb90e1a7a169", size = 10419748, upload-time = "2026-04-05T15:01:32.375Z" }, + { url = "https://files.pythonhosted.org/packages/a9/a8/5687872e2ab5a0f7dd4fd8456eac31e9381ad4dc74961f6f29965ad4dd91/ty-0.0.29-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91ec374b8565e0ad0900011c24641ebbef2da51adbd4fb69ff3280c8a7eceb02", size = 10394738, upload-time = "2026-04-05T15:01:06.473Z" }, + { url = "https://files.pythonhosted.org/packages/de/68/015d118097eeb95e6a44c4abce4c0a28b7b9dfb3085b7f0ee48e4f099633/ty-0.0.29-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:298a8d5faa2502d3810bbbb47a030b9455495b9921594206043c785dd61548cf", size = 10910613, upload-time = "2026-04-05T15:01:17.17Z" }, + { url = "https://files.pythonhosted.org/packages/1c/01/47ce3c6c53e0670eadbe80756b167bf80ed6681d1ba57cfde2e8065a13d1/ty-0.0.29-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c8fba1a3524c6109d1e020d92301c79d41bf442fa8d335b9fa366239339cb70", size = 11475750, upload-time = "2026-04-05T15:01:30.461Z" }, + { url = "https://files.pythonhosted.org/packages/c4/cf/e361845b1081c9264ad5b7c963231bab03f2666865a9f2a115c4233f2137/ty-0.0.29-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c48adf88a70d264128c39ee922ed14a947817fced1e93c08c1a89c9244edcde", size = 11190055, upload-time = "2026-04-05T15:01:12.369Z" }, + { url = "https://files.pythonhosted.org/packages/79/12/0fb0857e9a62cb11586e9a712103877bbf717f5fb570d16634408cfdefee/ty-0.0.29-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ce0a7a0e96bc7b42518cd3a1a6a6298ef64ff40ca4614355c1aa807059b5c6f", size = 11020539, upload-time = "2026-04-05T15:01:37.022Z" }, + { url = "https://files.pythonhosted.org/packages/20/36/5a26753802083f80cd125db6c4348ad42b3c982ec36e718e0bf4c18f75e5/ty-0.0.29-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6ac86a05b4a3731d45365ab97780acc7b8146fa62fccb3cbe94fe6546c67a97", size = 10396399, upload-time = "2026-04-05T15:01:26.167Z" }, + { url = "https://files.pythonhosted.org/packages/00/e6/b4e75b5752239ab3ab400f19faef4dbef81d05aab5d3419fda0c062a3765/ty-0.0.29-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6bbbf53141af0f3150bf288d716263f1a3550054e4b3551ca866d38192ba9891", size = 10421461, upload-time = "2026-04-05T15:01:08.367Z" }, + { url = "https://files.pythonhosted.org/packages/c0/21/1084b5b609f9abed62070ec0b31c283a403832a6310c8bbc208bd45ee1e6/ty-0.0.29-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1c9e06b770c1d0ff5efc51e34312390db31d53fcf3088163f413030b42b74f84", size = 10599187, upload-time = "2026-04-05T15:01:23.52Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a1/ce19a2ca717bbcc1ee11378aba52ef70b6ce5b87245162a729d9fdc2360f/ty-0.0.29-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0307fe37e3f000ef1a4ae230bbaf511508a78d24a5e51b40902a21b09d5e6037", size = 11121198, upload-time = "2026-04-05T15:01:15.22Z" }, + { url = "https://files.pythonhosted.org/packages/6b/6b/f1430b279af704321566ce7ec2725d3d8258c2f815ebd93e474c64cd4543/ty-0.0.29-py3-none-win32.whl", hash = "sha256:7a2a898217960a825f8bc0087e1fdbaf379606175e98f9807187221d53a4a8ed", size = 9995331, upload-time = "2026-04-05T15:01:01.32Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ef/3ef01c17785ff9a69378465c7d0faccd48a07b163554db0995e5d65a5a23/ty-0.0.29-py3-none-win_amd64.whl", hash = "sha256:fc1294200226b91615acbf34e0a9ad81caf98c081e9c6a912a31b0a7b603bc3f", size = 11023644, upload-time = "2026-04-05T15:01:04.432Z" }, + { url = "https://files.pythonhosted.org/packages/2c/55/87280a994d6a2d2647c65e12abbc997ed49835794366153c04c4d9304d76/ty-0.0.29-py3-none-win_arm64.whl", hash = "sha256:f9794bbd1bb3ce13f78c191d0c89ae4c63f52c12b6daa0c6fe220b90d019d12c", size = 10428165, upload-time = "2026-04-05T15:01:34.665Z" }, ] [[package]] @@ -1672,7 +1672,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#0f7d2b9121cc30c0e377717fc1db52205a8e4c80" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#250faf500a3d101b91f4c85a4618fe1882c9cf61" } [[package]] name = "zstandard" @@ -1702,4 +1702,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#b2b10636beba0384eada30979651b4ca7cf919ff" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#6896f3e5ea22d632c5ea3bc6e5f3b773c144f43b" } From 09a55a7833dd2ac1ae4f99eb1928125d2c6a867a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Wed, 8 Apr 2026 16:16:39 -0700 Subject: [PATCH 615/685] autodetect tg backend: use CPU:LLVM on Linux (#37785) autodetect tg backend: use CPU:LLVM on Linux, CPU on Darwin --- selfdrive/modeld/SConscript | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index f45dd4e7c6d..05045d09847 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -26,7 +26,7 @@ elif 'QCOM' in available: tg_backend = 'QCOM' tg_flags = f'DEV={tg_backend} FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0' else: - tg_backend = 'CPU' + tg_backend = 'CPU' if arch == 'Darwin' else 'CPU:LLVM' tg_flags = f'DEV={tg_backend} THREADS=0' def write_tg_compiled_flags(target, source, env): From 58090f5f7e3afe3d56740ab3d31f215af2f4903f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 8 Apr 2026 18:07:59 -0700 Subject: [PATCH 616/685] cereal: legacy -> deprecated (#37787) --- cereal/SConscript | 2 +- cereal/{legacy.capnp => deprecated.capnp} | 2 +- cereal/log.capnp | 60 +++++++++++------------ 3 files changed, 32 insertions(+), 32 deletions(-) rename cereal/{legacy.capnp => deprecated.capnp} (99%) diff --git a/cereal/SConscript b/cereal/SConscript index 73dc61844b3..de7ca0d7214 100644 --- a/cereal/SConscript +++ b/cereal/SConscript @@ -4,7 +4,7 @@ cereal_dir = Dir('.') gen_dir = Dir('gen') # Build cereal -schema_files = ['log.capnp', 'car.capnp', 'legacy.capnp', 'custom.capnp'] +schema_files = ['log.capnp', 'car.capnp', 'deprecated.capnp', 'custom.capnp'] env.Command([f'gen/cpp/{s}.c++' for s in schema_files] + [f'gen/cpp/{s}.h' for s in schema_files], schema_files, f"capnpc --src-prefix={cereal_dir.path} $SOURCES -o c++:{gen_dir.path}/cpp/") diff --git a/cereal/legacy.capnp b/cereal/deprecated.capnp similarity index 99% rename from cereal/legacy.capnp rename to cereal/deprecated.capnp index a8fa5e4a1f7..82abdb18f7a 100644 --- a/cereal/legacy.capnp +++ b/cereal/deprecated.capnp @@ -3,7 +3,7 @@ $Cxx.namespace("cereal"); @0x80ef1ec4889c2a63; -# legacy.capnp: a home for deprecated structs +# deprecated.capnp: a home for deprecated structs struct LogRotate @0x9811e1f38f62f2d1 { segmentNum @0 :Int32; diff --git a/cereal/log.capnp b/cereal/log.capnp index 56502606990..bc036d52c87 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2,7 +2,7 @@ using Cxx = import "./include/c++.capnp"; $Cxx.namespace("cereal"); using Car = import "car.capnp"; -using Legacy = import "legacy.capnp"; +using Deprecated = import "deprecated.capnp"; using Custom = import "custom.capnp"; @0xf3b1f17e25a4285b; @@ -2671,40 +2671,40 @@ struct Event { customReserved19 @145 :Custom.CustomReserved19; # *********** legacy + deprecated *********** - model @9 :Legacy.ModelData; # TODO: rename modelV2 and mark this as deprecated + model @9 :Deprecated.ModelData; # TODO: rename modelV2 and mark this as deprecated liveMpcDEPRECATED @36 :LiveMpcData; liveLongitudinalMpcDEPRECATED @37 :LiveLongitudinalMpcData; - liveLocationKalmanLegacyDEPRECATED @51 :Legacy.LiveLocationData; - orbslamCorrectionDEPRECATED @45 :Legacy.OrbslamCorrection; - liveUIDEPRECATED @14 :Legacy.LiveUI; + liveLocationKalmanDeprecatedDEPRECATED @51 :Deprecated.LiveLocationData; + orbslamCorrectionDEPRECATED @45 :Deprecated.OrbslamCorrection; + liveUIDEPRECATED @14 :Deprecated.LiveUI; sensorEventDEPRECATED @4 :SensorEventData; - liveEventDEPRECATED @8 :List(Legacy.LiveEventData); - liveLocationDEPRECATED @25 :Legacy.LiveLocationData; - ethernetDataDEPRECATED @26 :List(Legacy.EthernetPacket); - cellInfoDEPRECATED @28 :List(Legacy.CellInfo); - wifiScanDEPRECATED @29 :List(Legacy.WifiScan); - uiNavigationEventDEPRECATED @50 :Legacy.UiNavigationEvent; + liveEventDEPRECATED @8 :List(Deprecated.LiveEventData); + liveLocationDEPRECATED @25 :Deprecated.LiveLocationData; + ethernetDataDEPRECATED @26 :List(Deprecated.EthernetPacket); + cellInfoDEPRECATED @28 :List(Deprecated.CellInfo); + wifiScanDEPRECATED @29 :List(Deprecated.WifiScan); + uiNavigationEventDEPRECATED @50 :Deprecated.UiNavigationEvent; liveMapDataDEPRECATED @62 :LiveMapDataDEPRECATED; - gpsPlannerPointsDEPRECATED @40 :Legacy.GPSPlannerPoints; - gpsPlannerPlanDEPRECATED @41 :Legacy.GPSPlannerPlan; + gpsPlannerPointsDEPRECATED @40 :Deprecated.GPSPlannerPoints; + gpsPlannerPlanDEPRECATED @41 :Deprecated.GPSPlannerPlan; applanixRawDEPRECATED @42 :Data; - androidGnssDEPRECATED @30 :Legacy.AndroidGnss; - lidarPtsDEPRECATED @32 :Legacy.LidarPts; - navStatusDEPRECATED @38 :Legacy.NavStatus; - trafficEventsDEPRECATED @43 :List(Legacy.TrafficEvent); - liveLocationTimingDEPRECATED @44 :Legacy.LiveLocationData; - liveLocationCorrectedDEPRECATED @46 :Legacy.LiveLocationData; - navUpdateDEPRECATED @27 :Legacy.NavUpdate; - orbObservationDEPRECATED @47 :List(Legacy.OrbObservation); - locationDEPRECATED @49 :Legacy.LiveLocationData; - orbOdometryDEPRECATED @53 :Legacy.OrbOdometry; - orbFeaturesDEPRECATED @54 :Legacy.OrbFeatures; - applanixLocationDEPRECATED @55 :Legacy.LiveLocationData; - orbKeyFrameDEPRECATED @56 :Legacy.OrbKeyFrame; - orbFeaturesSummaryDEPRECATED @58 :Legacy.OrbFeaturesSummary; - featuresDEPRECATED @10 :Legacy.CalibrationFeatures; - kalmanOdometryDEPRECATED @65 :Legacy.KalmanOdometry; - uiLayoutStateDEPRECATED @57 :Legacy.UiLayoutState; + androidGnssDEPRECATED @30 :Deprecated.AndroidGnss; + lidarPtsDEPRECATED @32 :Deprecated.LidarPts; + navStatusDEPRECATED @38 :Deprecated.NavStatus; + trafficEventsDEPRECATED @43 :List(Deprecated.TrafficEvent); + liveLocationTimingDEPRECATED @44 :Deprecated.LiveLocationData; + liveLocationCorrectedDEPRECATED @46 :Deprecated.LiveLocationData; + navUpdateDEPRECATED @27 :Deprecated.NavUpdate; + orbObservationDEPRECATED @47 :List(Deprecated.OrbObservation); + locationDEPRECATED @49 :Deprecated.LiveLocationData; + orbOdometryDEPRECATED @53 :Deprecated.OrbOdometry; + orbFeaturesDEPRECATED @54 :Deprecated.OrbFeatures; + applanixLocationDEPRECATED @55 :Deprecated.LiveLocationData; + orbKeyFrameDEPRECATED @56 :Deprecated.OrbKeyFrame; + orbFeaturesSummaryDEPRECATED @58 :Deprecated.OrbFeaturesSummary; + featuresDEPRECATED @10 :Deprecated.CalibrationFeatures; + kalmanOdometryDEPRECATED @65 :Deprecated.KalmanOdometry; + uiLayoutStateDEPRECATED @57 :Deprecated.UiLayoutState; pandaStateDEPRECATED @12 :PandaState; driverStateDEPRECATED @59 :DriverStateDEPRECATED; sensorEventsDEPRECATED @11 :List(SensorEventData); From b5576de97efb8695522d22b566c6b90ba5b4a65a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 8 Apr 2026 23:56:24 -0700 Subject: [PATCH 617/685] soundd: update volume while playing alert (#37789) lways --- selfdrive/ui/soundd.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 6a203d3afcb..a99e5874b18 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -155,7 +155,8 @@ def soundd_thread(self): while True: sm.update(0) - if sm.updated['soundPressure'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert + # Always update volume, even when alert is playing + if sm.updated['soundPressure']: self.spl_filter_weighted.update(sm["soundPressure"].soundPressureWeightedDb) self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x)) From 934fd5a5a88d7833655a4742c96e42eba1601fd5 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 9 Apr 2026 00:13:27 -0700 Subject: [PATCH 618/685] soundd: ramp immediate alert volume (#37788) * raise volume * stuff * fix * 4s * revert * clean up * lean up --- selfdrive/ui/soundd.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index a99e5874b18..8225efabf9a 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -18,6 +18,7 @@ SAMPLE_BUFFER = 4096 # (approx 100ms) MAX_VOLUME = 1.0 MIN_VOLUME = 0.1 +ALERT_RAMP_TIME = 4 # seconds to ramp to max volume for warningImmediate SELFDRIVE_STATE_TIMEOUT = 5 # 5 seconds FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES) @@ -69,6 +70,9 @@ def __init__(self): self.current_volume = MIN_VOLUME self.current_sound_frame = 0 + self.ramp_start_volume = MIN_VOLUME + self.ramp_start_time = 0. + self.selfdrive_timeout_alert = False self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) @@ -117,6 +121,9 @@ def callback(self, data_out: np.ndarray, frames: int, time, status) -> None: def update_alert(self, new_alert): current_alert_played_once = self.current_alert == AudibleAlert.none or self.current_sound_frame > len(self.loaded_sounds[self.current_alert]) if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once): + if new_alert == AudibleAlert.warningImmediate: + self.ramp_start_volume = self.current_volume + self.ramp_start_time = time.monotonic() self.current_alert = new_alert self.current_sound_frame = 0 @@ -162,6 +169,12 @@ def soundd_thread(self): self.get_audible_alert(sm) + # Ramp up immediate warning sound over 4s + if self.current_alert == AudibleAlert.warningImmediate: + elapsed = time.monotonic() - self.ramp_start_time + ramp_vol = float(np.interp(elapsed, [0, ALERT_RAMP_TIME], [self.ramp_start_volume, MAX_VOLUME])) + self.current_volume = max(self.current_volume, ramp_vol) + rk.keep_time() assert stream.active From f17a0aac94380238e266131d81ebbadf7239fd0c Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Thu, 9 Apr 2026 01:41:40 -0700 Subject: [PATCH 619/685] services: increase logMessage queue size (#37790) * services: increase logMessage queue size logMessage and errorLogMessage defaulted to SMALL (250KB) after bcdeec3133, but logmessaged allows messages up to 2MB. The msgq assert requires 3 messages to fit in the queue, so any log message over ~83KB crashes logmessaged. This happens on dirty devices when updated logs the full git diff output. Co-Authored-By: Claude Opus 4.6 (1M context) * use BIG queue size to fully cover 2MB cap Co-Authored-By: Claude Opus 4.6 (1M context) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- cereal/services.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cereal/services.py b/cereal/services.py index 6b98128d64b..c8525c0dd30 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -39,8 +39,8 @@ def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] "roadEncodeIdx": (False, 20., 1), "liveTracks": (True, 20.), "sendcan": (True, 100., 139, QueueSize.MEDIUM), - "logMessage": (True, 0.), - "errorLogMessage": (True, 0., 1), + "logMessage": (True, 0., None, QueueSize.BIG), + "errorLogMessage": (True, 0., 1, QueueSize.BIG), "liveCalibration": (True, 4., 4), "liveTorqueParameters": (True, 4., 1), "liveDelay": (True, 4., 1), From 9dc4986e216c910fdeedb2bc6bd9e7d2ef27b94d Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 9 Apr 2026 10:03:22 -0700 Subject: [PATCH 620/685] cereal: move deprecated structs to deprecated.capnp (#37786) * lil more * cleanup --- cereal/deprecated.capnp | 87 ++++++++++++++++++++++++++++++++++++ cereal/log.capnp | 98 +++-------------------------------------- 2 files changed, 92 insertions(+), 93 deletions(-) diff --git a/cereal/deprecated.capnp b/cereal/deprecated.capnp index 82abdb18f7a..02ed918375e 100644 --- a/cereal/deprecated.capnp +++ b/cereal/deprecated.capnp @@ -571,4 +571,91 @@ struct LidarPts @0xe3d6685d4e9d8f7a { pkt @4 :Data; } +struct LiveTracksDEPRECATED @0xb16f60103159415a { + trackId @0 :Int32; + dRel @1 :Float32; + yRel @2 :Float32; + vRel @3 :Float32; + aRel @4 :Float32; + timeStamp @5 :Float32; + status @6 :Float32; + currentTime @7 :Float32; + stationary @8 :Bool; + oncoming @9 :Bool; +} + +struct LiveMpcData @0x92a5e332a85f32a0 { + x @0 :List(Float32); + y @1 :List(Float32); + psi @2 :List(Float32); + curvature @3 :List(Float32); + qpIterations @4 :UInt32; + calculationTime @5 :UInt64; + cost @6 :Float64; +} + +struct LiveLongitudinalMpcData @0xe7e17c434f865ae2 { + xEgo @0 :List(Float32); + vEgo @1 :List(Float32); + aEgo @2 :List(Float32); + xLead @3 :List(Float32); + vLead @4 :List(Float32); + aLead @5 :List(Float32); + aLeadTau @6 :Float32; # lead accel time constant + qpIterations @7 :UInt32; + mpcId @8 :UInt32; + calculationTime @9 :UInt64; + cost @10 :Float64; +} + +struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 { + frameId @0 :UInt32; + modelExecutionTime @14 :Float32; + dspExecutionTime @16 :Float32; + rawPredictions @15 :Data; + + faceOrientation @3 :List(Float32); + facePosition @4 :List(Float32); + faceProb @5 :Float32; + leftEyeProb @6 :Float32; + rightEyeProb @7 :Float32; + leftBlinkProb @8 :Float32; + rightBlinkProb @9 :Float32; + faceOrientationStd @11 :List(Float32); + facePositionStd @12 :List(Float32); + sunglassesProb @13 :Float32; + poorVision @17 :Float32; + partialFace @18 :Float32; + distractedPose @19 :Float32; + distractedEyes @20 :Float32; + eyesOnRoad @21 :Float32; + phoneUse @22 :Float32; + occludedProb @23 :Float32; + + readyProb @24 :List(Float32); + notReadyProb @25 :List(Float32); + + irPwrDEPRECATED @10 :Float32; + descriptorDEPRECATED @1 :List(Float32); + stdDEPRECATED @2 :Float32; +} + +struct NavModelData @0xac3de5c437be057a { + frameId @0 :UInt32; + locationMonoTime @6 :UInt64; + modelExecutionTime @1 :Float32; + dspExecutionTime @2 :Float32; + features @3 :List(Float32); + # predicted future position + position @4 :XYData; + desirePrediction @5 :List(Float32); + + # All SI units and in device frame + struct XYData @0xbe09e615b2507e26 { + x @0 :List(Float32); + y @1 :List(Float32); + xStd @2 :List(Float32); + yStd @3 :List(Float32); + } +} diff --git a/cereal/log.capnp b/cereal/log.capnp index bc036d52c87..c04e6f7b079 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -808,19 +808,6 @@ struct LiveCalibrationData { } } -struct LiveTracksDEPRECATED { - trackId @0 :Int32; - dRel @1 :Float32; - yRel @2 :Float32; - vRel @3 :Float32; - aRel @4 :Float32; - timeStamp @5 :Float32; - status @6 :Float32; - currentTime @7 :Float32; - stationary @8 :Bool; - oncoming @9 :Bool; -} - struct SelfdriveState { # high level system state state @0 :OpenpilotState; @@ -2135,30 +2122,6 @@ struct Clocks { modemUptimeMillisDEPRECATED @4 :UInt64; } -struct LiveMpcData { - x @0 :List(Float32); - y @1 :List(Float32); - psi @2 :List(Float32); - curvature @3 :List(Float32); - qpIterations @4 :UInt32; - calculationTime @5 :UInt64; - cost @6 :Float64; -} - -struct LiveLongitudinalMpcData { - xEgo @0 :List(Float32); - vEgo @1 :List(Float32); - aEgo @2 :List(Float32); - xLead @3 :List(Float32); - vLead @4 :List(Float32); - aLead @5 :List(Float32); - aLeadTau @6 :Float32; # lead accel time constant - qpIterations @7 :UInt32; - mpcId @8 :UInt32; - calculationTime @9 :UInt64; - cost @10 :Float64; -} - struct Joystick { # convenient for debug and live tuning axes @0: List(Float32); @@ -2198,38 +2161,6 @@ struct DriverStateV2 { poorVisionProbDEPRECATED @4 :Float32; } -struct DriverStateDEPRECATED @0xb83c6cc593ed0a00 { - frameId @0 :UInt32; - modelExecutionTime @14 :Float32; - dspExecutionTime @16 :Float32; - rawPredictions @15 :Data; - - faceOrientation @3 :List(Float32); - facePosition @4 :List(Float32); - faceProb @5 :Float32; - leftEyeProb @6 :Float32; - rightEyeProb @7 :Float32; - leftBlinkProb @8 :Float32; - rightBlinkProb @9 :Float32; - faceOrientationStd @11 :List(Float32); - facePositionStd @12 :List(Float32); - sunglassesProb @13 :Float32; - poorVision @17 :Float32; - partialFace @18 :Float32; - distractedPose @19 :Float32; - distractedEyes @20 :Float32; - eyesOnRoad @21 :Float32; - phoneUse @22 :Float32; - occludedProb @23 :Float32; - - readyProb @24 :List(Float32); - notReadyProb @25 :List(Float32); - - irPwrDEPRECATED @10 :Float32; - descriptorDEPRECATED @1 :List(Float32); - stdDEPRECATED @2 :Float32; -} - struct DriverMonitoringState @0xb83cda094a1da284 { events @18 :List(OnroadEvent); faceDetected @1 :Bool; @@ -2464,25 +2395,6 @@ struct MapRenderState { frameId @2: UInt32; } -struct NavModelData { - frameId @0 :UInt32; - locationMonoTime @6 :UInt64; - modelExecutionTime @1 :Float32; - dspExecutionTime @2 :Float32; - features @3 :List(Float32); - # predicted future position - position @4 :XYData; - desirePrediction @5 :List(Float32); - - # All SI units and in device frame - struct XYData { - x @0 :List(Float32); - y @1 :List(Float32); - xStd @2 :List(Float32); - yStd @3 :List(Float32); - } -} - struct EncodeData { idx @0 :EncodeIndex; data @1 :Data; @@ -2672,8 +2584,8 @@ struct Event { # *********** legacy + deprecated *********** model @9 :Deprecated.ModelData; # TODO: rename modelV2 and mark this as deprecated - liveMpcDEPRECATED @36 :LiveMpcData; - liveLongitudinalMpcDEPRECATED @37 :LiveLongitudinalMpcData; + liveMpcDEPRECATED @36 :Deprecated.LiveMpcData; + liveLongitudinalMpcDEPRECATED @37 :Deprecated.LiveLongitudinalMpcData; liveLocationKalmanDeprecatedDEPRECATED @51 :Deprecated.LiveLocationData; orbslamCorrectionDEPRECATED @45 :Deprecated.OrbslamCorrection; liveUIDEPRECATED @14 :Deprecated.LiveUI; @@ -2706,13 +2618,13 @@ struct Event { kalmanOdometryDEPRECATED @65 :Deprecated.KalmanOdometry; uiLayoutStateDEPRECATED @57 :Deprecated.UiLayoutState; pandaStateDEPRECATED @12 :PandaState; - driverStateDEPRECATED @59 :DriverStateDEPRECATED; + driverStateDEPRECATED @59 :Deprecated.DriverStateDEPRECATED; sensorEventsDEPRECATED @11 :List(SensorEventData); lateralPlanDEPRECATED @64 :LateralPlan; - navModelDEPRECATED @104 :NavModelData; + navModelDEPRECATED @104 :Deprecated.NavModelData; uiPlanDEPRECATED @106 :UiPlan; liveLocationKalmanDEPRECATED @72 :LiveLocationKalman; - liveTracksDEPRECATED @16 :List(LiveTracksDEPRECATED); + liveTracksDEPRECATED @16 :List(Deprecated.LiveTracksDEPRECATED); onroadEventsDEPRECATED @68: List(Car.OnroadEventDEPRECATED); gyroscope2DEPRECATED @100 :SensorEventData; accelerometer2DEPRECATED @101 :SensorEventData; From a89ed55b65e0d43ff4048ae0766b013d47c43baf Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 9 Apr 2026 10:41:04 -0700 Subject: [PATCH 621/685] cereal: group inline deprecated fields (#37791) --- cereal/deprecated.capnp | 128 +++++ cereal/log.capnp | 592 +++++++++------------ common/utils.py | 4 +- selfdrive/test/fuzzy_generation.py | 4 +- selfdrive/test/process_replay/migration.py | 10 +- selfdrive/ui/mici/onroad/hud_renderer.py | 2 +- selfdrive/ui/onroad/hud_renderer.py | 2 +- system/ubloxd/ubloxd.py | 2 +- system/webrtc/schema.py | 2 +- tools/jotpluggler/browser.cc | 2 +- tools/jotpluggler/sketch_layout.cc | 4 +- tools/replay/logreader.cc | 25 +- 12 files changed, 415 insertions(+), 362 deletions(-) diff --git a/cereal/deprecated.capnp b/cereal/deprecated.capnp index 02ed918375e..45ce25c6827 100644 --- a/cereal/deprecated.capnp +++ b/cereal/deprecated.capnp @@ -659,3 +659,131 @@ struct NavModelData @0xac3de5c437be057a { } } +struct AndroidBuildInfo @0xfe2919d5c21f426c { + board @0 :Text; + bootloader @1 :Text; + brand @2 :Text; + device @3 :Text; + display @4 :Text; + fingerprint @5 :Text; + hardware @6 :Text; + host @7 :Text; + id @8 :Text; + manufacturer @9 :Text; + model @10 :Text; + product @11 :Text; + radioVersion @12 :Text; + serial @13 :Text; + supportedAbis @14 :List(Text); + tags @15 :Text; + time @16 :Int64; + type @17 :Text; + user @18 :Text; + + versionCodename @19 :Text; + versionRelease @20 :Text; + versionSdk @21 :Int32; + versionSecurityPatch @22 :Text; +} + +struct AndroidSensor @0x9b513b93a887dbcd { + id @0 :Int32; + name @1 :Text; + vendor @2 :Text; + version @3 :Int32; + handle @4 :Int32; + type @5 :Int32; + maxRange @6 :Float32; + resolution @7 :Float32; + power @8 :Float32; + minDelay @9 :Int32; + fifoReservedEventCount @10 :UInt32; + fifoMaxEventCount @11 :UInt32; + stringType @12 :Text; + maxDelay @13 :Int32; +} + +struct IosBuildInfo @0xd97e3b28239f5580 { + appVersion @0 :Text; + appBuild @1 :UInt32; + osVersion @2 :Text; + deviceModel @3 :Text; +} + +enum FrameTypeDEPRECATED @0xa37f0d8558e193fd { + unknown @0; + neo @1; + chffrAndroid @2; + front @3; +} + +struct AndroidCaptureResult @0xbcc3efbac41d2048 { + sensitivity @0 :Int32; + frameDuration @1 :Int64; + exposureTime @2 :Int64; + rollingShutterSkew @3 :UInt64; + colorCorrectionTransform @4 :List(Int32); + colorCorrectionGains @5 :List(Float32); + displayRotation @6 :Int8; +} + +enum UsbPowerModeDEPRECATED @0xa8883583b32c9877 { + none @0; + client @1; + cdp @2; + dcp @3; +} + +struct LateralINDIState @0x939463348632375e { + active @0 :Bool; + steeringAngleDeg @1 :Float32; + steeringRateDeg @2 :Float32; + steeringAccelDeg @3 :Float32; + rateSetPoint @4 :Float32; + accelSetPoint @5 :Float32; + accelError @6 :Float32; + delayedOutput @7 :Float32; + delta @8 :Float32; + output @9 :Float32; + saturated @10 :Bool; + steeringAngleDesiredDeg @11 :Float32; + steeringRateDesiredDeg @12 :Float32; +} + +struct LateralLQRState @0x9024e2d790c82ade { + active @0 :Bool; + steeringAngleDeg @1 :Float32; + i @2 :Float32; + output @3 :Float32; + lqrOutput @4 :Float32; + saturated @5 :Bool; + steeringAngleDesiredDeg @6 :Float32; +} + +struct LateralCurvatureState @0xad9d8095c06f7c61 { + active @0 :Bool; + actualCurvature @1 :Float32; + desiredCurvature @2 :Float32; + error @3 :Float32; + p @4 :Float32; + i @5 :Float32; + f @6 :Float32; + output @7 :Float32; + saturated @8 :Bool; +} + +struct LateralPlannerSolution @0x84caeca5a6b4acfe { + x @0 :List(Float32); + y @1 :List(Float32); + yaw @2 :List(Float32); + yawRate @3 :List(Float32); + xStd @4 :List(Float32); + yStd @5 :List(Float32); + yawStd @6 :List(Float32); + yawRateStd @7 :List(Float32); +} + +struct GpsTrajectory @0x8cfeb072f5301000 { + x @0 :List(Float32); + y @1 :List(Float32); +} diff --git a/cereal/log.capnp b/cereal/log.capnp index c04e6f7b079..0f8b5470a44 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -192,66 +192,16 @@ struct InitData { espVersion @3 :Text; } - # ***** deprecated stuff ***** - gctxDEPRECATED @1 :Text; - androidBuildInfo @5 :AndroidBuildInfo; - androidSensorsDEPRECATED @6 :List(AndroidSensor); - chffrAndroidExtraDEPRECATED @7 :ChffrAndroidExtra; - iosBuildInfoDEPRECATED @14 :IosBuildInfo; - - struct AndroidBuildInfo { - board @0 :Text; - bootloader @1 :Text; - brand @2 :Text; - device @3 :Text; - display @4 :Text; - fingerprint @5 :Text; - hardware @6 :Text; - host @7 :Text; - id @8 :Text; - manufacturer @9 :Text; - model @10 :Text; - product @11 :Text; - radioVersion @12 :Text; - serial @13 :Text; - supportedAbis @14 :List(Text); - tags @15 :Text; - time @16 :Int64; - type @17 :Text; - user @18 :Text; - - versionCodename @19 :Text; - versionRelease @20 :Text; - versionSdk @21 :Int32; - versionSecurityPatch @22 :Text; - } - - struct AndroidSensor { - id @0 :Int32; - name @1 :Text; - vendor @2 :Text; - version @3 :Int32; - handle @4 :Int32; - type @5 :Int32; - maxRange @6 :Float32; - resolution @7 :Float32; - power @8 :Float32; - minDelay @9 :Int32; - fifoReservedEventCount @10 :UInt32; - fifoMaxEventCount @11 :UInt32; - stringType @12 :Text; - maxDelay @13 :Int32; - } - struct ChffrAndroidExtra { allCameraCharacteristics @0 :Map(Text, Text); } - struct IosBuildInfo { - appVersion @0 :Text; - appBuild @1 :UInt32; - osVersion @2 :Text; - deviceModel @3 :Text; + deprecated :group { + gctx @1 :Text; + androidBuildInfo @5 :Deprecated.AndroidBuildInfo; + androidSensors @6 :List(Deprecated.AndroidSensor); + chffrAndroidExtra @7 :ChffrAndroidExtra; + iosBuildInfo @14 :Deprecated.IosBuildInfo; } } @@ -280,13 +230,6 @@ struct FrameData { temperaturesC @24 :List(Float32); - enum FrameTypeDEPRECATED { - unknown @0; - neo @1; - chffrAndroid @2; - front @3; - } - sensor @26 :ImageSensor; enum ImageSensor { unknown @0; @@ -295,26 +238,19 @@ struct FrameData { os04c10 @3; } - frameLengthDEPRECATED @3 :Int32; - globalGainDEPRECATED @5 :Int32; - frameTypeDEPRECATED @7 :FrameTypeDEPRECATED; - androidCaptureResultDEPRECATED @9 :AndroidCaptureResult; - lensPosDEPRECATED @11 :Int32; - lensSagDEPRECATED @12 :Float32; - lensErrDEPRECATED @13 :Float32; - lensTruePosDEPRECATED @14 :Float32; - focusValDEPRECATED @16 :List(Int16); - focusConfDEPRECATED @17 :List(UInt8); - sharpnessScoreDEPRECATED @18 :List(UInt16); - recoverStateDEPRECATED @19 :Int32; - struct AndroidCaptureResult { - sensitivity @0 :Int32; - frameDuration @1 :Int64; - exposureTime @2 :Int64; - rollingShutterSkew @3 :UInt64; - colorCorrectionTransform @4 :List(Int32); - colorCorrectionGains @5 :List(Float32); - displayRotation @6 :Int8; + deprecated :group { + frameLength @3 :Int32; + globalGain @5 :Int32; + frameType @7 :Deprecated.FrameTypeDEPRECATED; + androidCaptureResult @9 :Deprecated.AndroidCaptureResult; + lensPos @11 :Int32; + lensSag @12 :Float32; + lensErr @13 :Float32; + lensTruePos @14 :Float32; + focusVal @16 :List(Int16); + focusConf @17 :List(UInt8); + sharpnessScore @18 :List(UInt16); + recoverState @19 :Int32; } } @@ -343,7 +279,6 @@ struct SensorEventData { sensor @1 :Int32; type @2 :Int32; timestamp @3 :Int64; - uncalibratedDEPRECATED @10 :Bool; union { acceleration @4 :SensorVec; @@ -378,6 +313,10 @@ struct SensorEventData { lsm6ds3trc @10; mmc5603nj @11; } + + deprecated :group { + uncalibrated @10 :Bool; + } } # android struct GpsLocation @@ -463,7 +402,10 @@ struct CanData { address @0 :UInt32; dat @2 :Data; src @3 :UInt8; - busTimeDEPRECATED @1 :UInt16; + + deprecated :group { + busTime @1 :UInt16; + } } struct DeviceState @0xa4d8b5af2aa492eb { @@ -553,26 +495,27 @@ struct DeviceState @0xa4d8b5af2aa492eb { wwanRx @1 :Int64; } - # deprecated - cpu0DEPRECATED @0 :UInt16; - cpu1DEPRECATED @1 :UInt16; - cpu2DEPRECATED @2 :UInt16; - cpu3DEPRECATED @3 :UInt16; - memDEPRECATED @4 :UInt16; - gpuDEPRECATED @5 :UInt16; - batDEPRECATED @6 :UInt32; - pa0DEPRECATED @21 :UInt16; - cpuUsagePercentDEPRECATED @20 :Int8; - batteryStatusDEPRECATED @9 :Text; - batteryVoltageDEPRECATED @16 :Int32; - batteryTempCDEPRECATED @29 :Float32; - batteryPercentDEPRECATED @8 :Int16; - batteryCurrentDEPRECATED @15 :Int32; - chargingErrorDEPRECATED @17 :Bool; - chargingDisabledDEPRECATED @18 :Bool; - usbOnlineDEPRECATED @12 :Bool; - ambientTempCDEPRECATED @30 :Float32; - nvmeTempCDEPRECATED @35 :List(Float32); + deprecated :group { + cpu0 @0 :UInt16; + cpu1 @1 :UInt16; + cpu2 @2 :UInt16; + cpu3 @3 :UInt16; + mem @4 :UInt16; + gpu @5 :UInt16; + bat @6 :UInt32; + pa0 @21 :UInt16; + cpuUsagePercent @20 :Int8; + batteryStatus @9 :Text; + batteryVoltage @16 :Int32; + batteryTempC @29 :Float32; + batteryPercent @8 :Int16; + batteryCurrent @15 :Int32; + chargingError @17 :Bool; + chargingDisabled @18 :Bool; + usbOnline @12 :Bool; + ambientTempC @30 :Float32; + nvmeTempC @35 :List(Float32); + } } struct PandaState @0xa7649e2575e4591e { @@ -714,15 +657,17 @@ struct PandaState @0xa7649e2575e4591e { } } - gasInterceptorDetectedDEPRECATED @4 :Bool; - startedSignalDetectedDEPRECATED @5 :Bool; - hasGpsDEPRECATED @6 :Bool; - gmlanSendErrsDEPRECATED @9 :UInt32; - fanSpeedRpmDEPRECATED @11 :UInt16; - usbPowerModeDEPRECATED @12 :PeripheralState.UsbPowerModeDEPRECATED; - safetyParamDEPRECATED @20 :Int16; - safetyParam2DEPRECATED @26 :UInt32; - fanStallCountDEPRECATED @34 :UInt8; + deprecated :group { + gasInterceptorDetected @4 :Bool; + startedSignalDetected @5 :Bool; + hasGps @6 :Bool; + gmlanSendErrs @9 :UInt32; + fanSpeedRpm @11 :UInt16; + usbPowerMode @12 :Deprecated.UsbPowerModeDEPRECATED; + safetyParam @20 :Int16; + safetyParam2 @26 :UInt32; + fanStallCount @34 :UInt8; + } } struct PeripheralState { @@ -731,12 +676,8 @@ struct PeripheralState { current @2 :UInt32; fanSpeedRpm @3 :UInt16; - usbPowerModeDEPRECATED @4 :UsbPowerModeDEPRECATED; - enum UsbPowerModeDEPRECATED @0xa8883583b32c9877 { - none @0; - client @1; - cdp @2; - dcp @3; + deprecated :group { + usbPowerMode @4 :Deprecated.UsbPowerModeDEPRECATED; } } @@ -765,19 +706,22 @@ struct RadarState @0x9a185389d6fdd05f { radar @14 :Bool; radarTrackId @15 :Int32 = -1; - aLeadDEPRECATED @5 :Float32; + deprecated :group { + aLead @5 :Float32; + } } - # deprecated - ftMonoTimeDEPRECATED @7 :UInt64; - warpMatrixDEPRECATED @0 :List(Float32); - angleOffsetDEPRECATED @1 :Float32; - calStatusDEPRECATED @2 :Int8; - calCycleDEPRECATED @8 :Int32; - calPercDEPRECATED @9 :Int8; - canMonoTimesDEPRECATED @10 :List(UInt64); - cumLagMsDEPRECATED @5 :Float32; - radarErrorsDEPRECATED @12 :List(Car.RadarData.ErrorDEPRECATED); + deprecated :group { + ftMonoTime @7 :UInt64; + warpMatrix @0 :List(Float32); + angleOffset @1 :Float32; + calStatus @2 :Int8; + calCycle @8 :Int32; + calPerc @9 :Int8; + canMonoTimes @10 :List(UInt64); + cumLagMs @5 :Float32; + radarErrors @12 :List(Car.RadarData.ErrorDEPRECATED); + } } struct LiveCalibrationData { @@ -795,10 +739,6 @@ struct LiveCalibrationData { wideFromDeviceEuler @10 :List(Float32); height @12 :List(Float32); - warpMatrixDEPRECATED @0 :List(Float32); - calStatusDEPRECATED @1 :Int8; - warpMatrix2DEPRECATED @5 :List(Float32); - warpMatrixBigDEPRECATED @6 :List(Float32); enum Status { uncalibrated @0; @@ -806,6 +746,13 @@ struct LiveCalibrationData { invalid @2; recalibrating @3; } + + deprecated :group { + warpMatrix @0 :List(Float32); + calStatus @1 :Int8; + warpMatrix2 @5 :List(Float32); + warpMatrixBig @6 :List(Float32); + } } struct SelfdriveState { @@ -868,25 +815,9 @@ struct ControlsState @0x97ff69c53601abf1 { debugState @59 :LateralDebugState; torqueState @60 :LateralTorqueState; - curvatureStateDEPRECATED @65 :LateralCurvatureState; - lqrStateDEPRECATED @55 :LateralLQRState; - indiStateDEPRECATED @52 :LateralINDIState; - } - - struct LateralINDIState { - active @0 :Bool; - steeringAngleDeg @1 :Float32; - steeringRateDeg @2 :Float32; - steeringAccelDeg @3 :Float32; - rateSetPoint @4 :Float32; - accelSetPoint @5 :Float32; - accelError @6 :Float32; - delayedOutput @7 :Float32; - delta @8 :Float32; - output @9 :Float32; - saturated @10 :Bool; - steeringAngleDesiredDeg @11 :Float32; - steeringRateDesiredDeg @12 :Float32; + curvatureStateDEPRECATED @65 :Deprecated.LateralCurvatureState; + lqrStateDEPRECATED @55 :Deprecated.LateralLQRState; + indiStateDEPRECATED @52 :Deprecated.LateralINDIState; } struct LateralPIDState { @@ -918,16 +849,6 @@ struct ControlsState @0x97ff69c53601abf1 { version @12 :Int32; } - struct LateralLQRState { - active @0 :Bool; - steeringAngleDeg @1 :Float32; - i @2 :Float32; - output @3 :Float32; - lqrOutput @4 :Float32; - saturated @5 :Bool; - steeringAngleDesiredDeg @6 :Float32; - } - struct LateralAngleState { active @0 :Bool; steeringAngleDeg @1 :Float32; @@ -936,18 +857,6 @@ struct ControlsState @0x97ff69c53601abf1 { steeringAngleDesiredDeg @4 :Float32; } - struct LateralCurvatureState { - active @0 :Bool; - actualCurvature @1 :Float32; - desiredCurvature @2 :Float32; - error @3 :Float32; - p @4 :Float32; - i @5 :Float32; - f @6 :Float32; - output @7 :Float32; - saturated @8 :Bool; - } - struct LateralDebugState { active @0 :Bool; steeringAngleDeg @1 :Float32; @@ -955,58 +864,59 @@ struct ControlsState @0x97ff69c53601abf1 { saturated @3 :Bool; } - # deprecated - vEgoDEPRECATED @0 :Float32; - vEgoRawDEPRECATED @32 :Float32; - aEgoDEPRECATED @1 :Float32; - canMonoTimeDEPRECATED @16 :UInt64; - radarStateMonoTimeDEPRECATED @17 :UInt64; - mdMonoTimeDEPRECATED @18 :UInt64; - yActualDEPRECATED @6 :Float32; - yDesDEPRECATED @7 :Float32; - upSteerDEPRECATED @8 :Float32; - uiSteerDEPRECATED @9 :Float32; - ufSteerDEPRECATED @34 :Float32; - aTargetMinDEPRECATED @10 :Float32; - aTargetMaxDEPRECATED @11 :Float32; - rearViewCamDEPRECATED @23 :Bool; - driverMonitoringOnDEPRECATED @43 :Bool; - hudLeadDEPRECATED @14 :Int32; - alertSoundDEPRECATED @45 :Text; - angleModelBiasDEPRECATED @27 :Float32; - gpsPlannerActiveDEPRECATED @40 :Bool; - decelForTurnDEPRECATED @47 :Bool; - decelForModelDEPRECATED @54 :Bool; - awarenessStatusDEPRECATED @26 :Float32; - angleSteersDEPRECATED @13 :Float32; - vCurvatureDEPRECATED @46 :Float32; - mapValidDEPRECATED @49 :Bool; - jerkFactorDEPRECATED @12 :Float32; - steerOverrideDEPRECATED @20 :Bool; - steeringAngleDesiredDegDEPRECATED @29 :Float32; - canMonoTimesDEPRECATED @21 :List(UInt64); - desiredCurvatureRateDEPRECATED @62 :Float32; - canErrorCounterDEPRECATED @57 :UInt32; - vPidDEPRECATED @2 :Float32; - alertBlinkingRateDEPRECATED @42 :Float32; - alertText1DEPRECATED @24 :Text; - alertText2DEPRECATED @25 :Text; - alertStatusDEPRECATED @38 :SelfdriveState.AlertStatus; - alertSizeDEPRECATED @39 :SelfdriveState.AlertSize; - alertTypeDEPRECATED @44 :Text; - alertSound2DEPRECATED @56 :Car.CarControl.HUDControl.AudibleAlert; - engageableDEPRECATED @41 :Bool; # can OP be engaged? - stateDEPRECATED @31 :SelfdriveState.OpenpilotState; - enabledDEPRECATED @19 :Bool; - activeDEPRECATED @36 :Bool; - experimentalModeDEPRECATED @64 :Bool; - personalityDEPRECATED @66 :LongitudinalPersonality; - vCruiseDEPRECATED @22 :Float32; # actual set speed - vCruiseClusterDEPRECATED @63 :Float32; # set speed to display in the UI - startMonoTimeDEPRECATED @48 :UInt64; - cumLagMsDEPRECATED @15 :Float32; - aTargetDEPRECATED @35 :Float32; - vTargetLeadDEPRECATED @3 :Float32; + deprecated :group { + vEgo @0 :Float32; + vEgoRaw @32 :Float32; + aEgo @1 :Float32; + canMonoTime @16 :UInt64; + radarStateMonoTime @17 :UInt64; + mdMonoTime @18 :UInt64; + yActual @6 :Float32; + yDes @7 :Float32; + upSteer @8 :Float32; + uiSteer @9 :Float32; + ufSteer @34 :Float32; + aTargetMin @10 :Float32; + aTargetMax @11 :Float32; + rearViewCam @23 :Bool; + driverMonitoringOn @43 :Bool; + hudLead @14 :Int32; + alertSound @45 :Text; + angleModelBias @27 :Float32; + gpsPlannerActive @40 :Bool; + decelForTurn @47 :Bool; + decelForModel @54 :Bool; + awarenessStatus @26 :Float32; + angleSteers @13 :Float32; + vCurvature @46 :Float32; + mapValid @49 :Bool; + jerkFactor @12 :Float32; + steerOverride @20 :Bool; + steeringAngleDesiredDeg @29 :Float32; + canMonoTimes @21 :List(UInt64); + desiredCurvatureRate @62 :Float32; + canErrorCounter @57 :UInt32; + vPid @2 :Float32; + alertBlinkingRate @42 :Float32; + alertText1 @24 :Text; + alertText2 @25 :Text; + alertStatus @38 :SelfdriveState.AlertStatus; + alertSize @39 :SelfdriveState.AlertSize; + alertType @44 :Text; + alertSound2 @56 :Car.CarControl.HUDControl.AudibleAlert; + engageable @41 :Bool; # can OP be engaged? + state @31 :SelfdriveState.OpenpilotState; + enabled @19 :Bool; + active @36 :Bool; + experimentalMode @64 :Bool; + personality @66 :LongitudinalPersonality; + vCruise @22 :Float32; # actual set speed + vCruiseCluster @63 :Float32; # set speed to display in the UI + startMonoTime @48 :UInt64; + cumLagMs @15 :Float32; + aTarget @35 :Float32; + vTargetLead @3 :Float32; + } } struct DrivingModelData { @@ -1082,16 +992,10 @@ struct ModelDataV2 { meta @12 :MetaData; confidence @23: ConfidenceClass; - # Model perceived motion - temporalPoseDEPRECATED @21 :Pose; - # e2e lateral planner action @26: Action; - gpuExecutionTimeDEPRECATED @17 :Float32; - navEnabledDEPRECATED @22 :Bool; - locationMonoTimeDEPRECATED @24 :UInt64; - lateralPlannerSolutionDEPRECATED @25: LateralPlannerSolution; + lateralPlannerSolutionDEPRECATED @25: Deprecated.LateralPlannerSolution; struct LeadDataV2 { prob @0 :Float32; # probability that car is your lead at time t @@ -1133,10 +1037,11 @@ struct ModelDataV2 { laneChangeDirection @9 :LaneChangeDirection; - # deprecated - brakeDisengageProbDEPRECATED @2 :Float32; - gasDisengageProbDEPRECATED @3 :Float32; - steerOverrideProbDEPRECATED @4 :Float32; + deprecated :group { + brakeDisengageProb @2 :Float32; + gasDisengageProb @3 :Float32; + steerOverrideProb @4 :Float32; + } } enum ConfidenceClass { @@ -1164,22 +1069,18 @@ struct ModelDataV2 { rotStd @3 :List(Float32); # std rad/s in device frame } - struct LateralPlannerSolution { - x @0 :List(Float32); - y @1 :List(Float32); - yaw @2 :List(Float32); - yawRate @3 :List(Float32); - xStd @4 :List(Float32); - yStd @5 :List(Float32); - yawStd @6 :List(Float32); - yawRateStd @7 :List(Float32); - } - struct Action { desiredCurvature @0 :Float32; desiredAcceleration @1 :Float32; shouldStop @2 :Bool; } + + deprecated :group { + temporalPose @21 :Pose; + gpuExecutionTime @17 :Float32; + navEnabled @22 :Bool; + locationMonoTime @24 :UInt64; + } } struct EncodeIndex { @@ -1265,38 +1166,35 @@ struct LongitudinalPlan @0xe00b5b3eba12876c { e2e @4; } - # deprecated - vCruiseDEPRECATED @16 :Float32; - aCruiseDEPRECATED @17 :Float32; - vTargetDEPRECATED @3 :Float32; - vTargetFutureDEPRECATED @14 :Float32; - vStartDEPRECATED @26 :Float32; - aStartDEPRECATED @27 :Float32; - vMaxDEPRECATED @20 :Float32; - radarStateMonoTimeDEPRECATED @10 :UInt64; - jerkFactorDEPRECATED @6 :Float32; - hasLeftLaneDEPRECATED @23 :Bool; - hasRightLaneDEPRECATED @24 :Bool; - aTargetMinDEPRECATED @4 :Float32; - aTargetMaxDEPRECATED @5 :Float32; - lateralValidDEPRECATED @0 :Bool; - longitudinalValidDEPRECATED @2 :Bool; - dPolyDEPRECATED @1 :List(Float32); - laneWidthDEPRECATED @11 :Float32; - vCurvatureDEPRECATED @21 :Float32; - decelForTurnDEPRECATED @22 :Bool; - mapValidDEPRECATED @25 :Bool; - radarValidDEPRECATED @28 :Bool; - radarCanErrorDEPRECATED @30 :Bool; - commIssueDEPRECATED @31 :Bool; - eventsDEPRECATED @13 :List(Car.OnroadEventDEPRECATED); - gpsTrajectoryDEPRECATED @12 :GpsTrajectory; - gpsPlannerActiveDEPRECATED @19 :Bool; - personalityDEPRECATED @36 :LongitudinalPersonality; - - struct GpsTrajectory { - x @0 :List(Float32); - y @1 :List(Float32); + + deprecated :group { + vCruise @16 :Float32; + aCruise @17 :Float32; + vTarget @3 :Float32; + vTargetFuture @14 :Float32; + vStart @26 :Float32; + aStart @27 :Float32; + vMax @20 :Float32; + radarStateMonoTime @10 :UInt64; + jerkFactor @6 :Float32; + hasLeftLane @23 :Bool; + hasRightLane @24 :Bool; + aTargetMin @4 :Float32; + aTargetMax @5 :Float32; + lateralValid @0 :Bool; + longitudinalValid @2 :Bool; + dPoly @1 :List(Float32); + laneWidth @11 :Float32; + vCurvature @21 :Float32; + decelForTurn @22 :Bool; + mapValid @25 :Bool; + radarValid @28 :Bool; + radarCanError @30 :Bool; + commIssue @31 :Bool; + events @13 :List(Car.OnroadEventDEPRECATED); + gpsTrajectory @12 :Deprecated.GpsTrajectory; + gpsPlannerActive @19 :Bool; + personality @36 :LongitudinalPersonality; } } struct UiPlan { @@ -1307,11 +1205,7 @@ struct UiPlan { struct LateralPlan @0xe1e9318e2ae8b51e { modelMonoTime @31 :UInt64; - laneWidthDEPRECATED @0 :Float32; - lProbDEPRECATED @5 :Float32; - rProbDEPRECATED @7 :Float32; dPathPoints @20 :List(Float32); - dProbDEPRECATED @21 :Float32; mpcSolutionValid @9 :Bool; desire @17 :Desire; @@ -1333,24 +1227,29 @@ struct LateralPlan @0xe1e9318e2ae8b51e { u @1 :List(Float32); } - # deprecated - curvatureDEPRECATED @22 :Float32; - curvatureRateDEPRECATED @23 :Float32; - rawCurvatureDEPRECATED @24 :Float32; - rawCurvatureRateDEPRECATED @25 :Float32; - cProbDEPRECATED @3 :Float32; - dPolyDEPRECATED @1 :List(Float32); - cPolyDEPRECATED @2 :List(Float32); - lPolyDEPRECATED @4 :List(Float32); - rPolyDEPRECATED @6 :List(Float32); - modelValidDEPRECATED @12 :Bool; - commIssueDEPRECATED @15 :Bool; - posenetValidDEPRECATED @16 :Bool; - sensorValidDEPRECATED @14 :Bool; - paramsValidDEPRECATED @10 :Bool; - steeringAngleDegDEPRECATED @8 :Float32; # deg - steeringRateDegDEPRECATED @13 :Float32; # deg/s - angleOffsetDegDEPRECATED @11 :Float32; + deprecated :group { + laneWidth @0 :Float32; + lProb @5 :Float32; + rProb @7 :Float32; + dProb @21 :Float32; + curvature @22 :Float32; + curvatureRate @23 :Float32; + rawCurvature @24 :Float32; + rawCurvatureRate @25 :Float32; + cProb @3 :Float32; + dPoly @1 :List(Float32); + cPoly @2 :List(Float32); + lPoly @4 :List(Float32); + rPoly @6 :List(Float32); + modelValid @12 :Bool; + commIssue @15 :Bool; + posenetValid @16 :Bool; + sensorValid @14 :Bool; + paramsValid @10 :Bool; + steeringAngleDeg @8 :Float32; # deg + steeringRateDeg @13 :Float32; # deg/s + angleOffsetDeg @11 :Float32; + } } struct LiveLocationKalman { @@ -1545,7 +1444,10 @@ struct GnssMeasurements { # Satellite position and velocity [x,y,z] satPos @7 :List(Float64); satVel @8 :List(Float64); - ephemerisSourceDEPRECATED @9 :EphemerisSourceDEPRECATED; + + deprecated :group { + ephemerisSource @9 :EphemerisSourceDEPRECATED; + } } struct EphemerisSourceDEPRECATED { @@ -1700,7 +1602,6 @@ struct UbloxGnss { iDot @26 :Float64; codesL2 @27 :Float64; - gpsWeekDEPRECATED @28 :Float64; l2 @29 :Float64; svAcc @30 :Float64; @@ -1720,6 +1621,10 @@ struct UbloxGnss { towCount @40 :UInt32; toeWeek @41 :UInt16; tocWeek @42 :UInt16; + + deprecated :group { + gpsWeek @28 :Float64; + } } struct IonoData { @@ -1798,7 +1703,6 @@ struct UbloxGnss { age @17 :UInt8; svHealth @18 :UInt8; - tkDEPRECATED @19 :UInt16; tb @20 :UInt16; tauN @21 :Float64; @@ -1810,12 +1714,16 @@ struct UbloxGnss { p3 @26 :UInt8; p4 @27 :UInt8; - freqNumDEPRECATED @28 :UInt32; n4 @29 :UInt8; nt @30 :UInt16; freqNum @31 :Int16; tkSeconds @32 :UInt32; + + deprecated :group { + tk @19 :UInt16; + freqNum @28 :UInt32; + } } } @@ -2116,10 +2024,12 @@ struct QcomGnss @0xde94674b07ae51c1 { struct Clocks { wallTimeNanos @3 :UInt64; # unix epoch time - bootTimeNanosDEPRECATED @0 :UInt64; - monotonicNanosDEPRECATED @1 :UInt64; - monotonicRawNanosDEPRECATD @2 :UInt64; - modemUptimeMillisDEPRECATED @4 :UInt64; + deprecated :group { + bootTimeNanos @0 :UInt64; + monotonicNanos @1 :UInt64; + monotonicRawNanos @2 :UInt64; + modemUptimeMillis @4 :UInt64; + } } struct Joystick { @@ -2147,18 +2057,23 @@ struct DriverStateV2 { eyesVisibleProb @14 :Float32; eyesClosedProb @15 :Float32; phoneProb @13 :Float32; - leftEyeProbDEPRECATED @5 :Float32; - rightEyeProbDEPRECATED @6 :Float32; - leftBlinkProbDEPRECATED @7 :Float32; - rightBlinkProbDEPRECATED @8 :Float32; - sunglassesProbDEPRECATED @9 :Float32; - notReadyProbDEPRECATED @12 :List(Float32); - occludedProbDEPRECATED @10 :Float32; - readyProbDEPRECATED @11 :List(Float32); - } - - dspExecutionTimeDEPRECATED @2 :Float32; - poorVisionProbDEPRECATED @4 :Float32; + + deprecated :group { + leftEyeProb @5 :Float32; + rightEyeProb @6 :Float32; + leftBlinkProb @7 :Float32; + rightBlinkProb @8 :Float32; + sunglassesProb @9 :Float32; + notReadyProb @12 :List(Float32); + occludedProb @10 :Float32; + readyProb @11 :List(Float32); + } + } + + deprecated :group { + dspExecutionTime @2 :Float32; + poorVisionProb @4 :Float32; + } } struct DriverMonitoringState @0xb83cda094a1da284 { @@ -2180,11 +2095,13 @@ struct DriverMonitoringState @0xb83cda094a1da284 { isRHD @4 :Bool; uncertainCount @19 :UInt32; - phoneProbOffsetDEPRECATED @20 :Float32; - phoneProbValidCountDEPRECATED @21 :UInt32; - isPreviewDEPRECATED @15 :Bool; - rhdCheckedDEPRECATED @5 :Bool; - eventsDEPRECATED @0 :List(Car.OnroadEventDEPRECATED); + deprecated :group { + phoneProbOffset @20 :Float32; + phoneProbValidCount @21 :UInt32; + isPreview @15 :Bool; + rhdChecked @5 :Bool; + events @0 :List(Car.OnroadEventDEPRECATED); + } } struct Boot { @@ -2193,8 +2110,10 @@ struct Boot { commands @5 :Map(Text, Data); launchLog @3 :Text; - lastKmsgDEPRECATED @1 :Data; - lastPmsgDEPRECATED @2 :Data; + deprecated :group { + lastKmsg @1 :Data; + lastPmsg @2 :Data; + } } struct LiveParametersData { @@ -2219,13 +2138,16 @@ struct LiveParametersData { steerRatioValid @19 :Bool = true; stiffnessFactorValid @20 :Bool = true; - yawRateDEPRECATED @7 :Float32; - filterStateDEPRECATED @15 :LiveLocationKalman.Measurement; struct FilterState { value @0 : List(Float64); std @1 : List(Float64); } + + deprecated :group { + yawRate @7 :Float32; + filterState @15 :LiveLocationKalman.Measurement; + } } struct LiveTorqueParametersData { @@ -2419,7 +2341,9 @@ struct SoundPressure @0xdc24138990726023 { soundPressureWeighted @3 :Float32; soundPressureWeightedDb @1 :Float32; - filteredSoundPressureWeightedDbDEPRECATED @2 :Float32; + deprecated :group { + filteredSoundPressureWeightedDb @2 :Float32; + } } struct AudioData { diff --git a/common/utils.py b/common/utils.py index faaa96ecbcd..28b9274d825 100644 --- a/common/utils.py +++ b/common/utils.py @@ -131,11 +131,11 @@ def get_upload_stream(filepath: str, should_compress: bool) -> tuple[io.Buffered return compressed_stream, compressed_size -# remove all keys that end in DEPRECATED +# remove all keys that end in DEPRECATED, plus any "deprecated" group def strip_deprecated_keys(d): for k in list(d.keys()): if isinstance(k, str): - if k.endswith('DEPRECATED'): + if k.endswith('DEPRECATED') or k == 'deprecated': d.pop(k) elif isinstance(d[k], dict): strip_deprecated_keys(d[k]) diff --git a/selfdrive/test/fuzzy_generation.py b/selfdrive/test/fuzzy_generation.py index 131dab47b23..9f028a8fc8d 100644 --- a/selfdrive/test/fuzzy_generation.py +++ b/selfdrive/test/fuzzy_generation.py @@ -46,8 +46,8 @@ def rec(field_type: capnp.lib.capnp._DynamicStructReader) -> st.SearchStrategy: def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str | None = None) -> st.SearchStrategy[dict[str, Any]]: single_fill: tuple[str, ...] = (event,) if event else (self.draw(st.sampled_from(schema.union_fields)),) if schema.union_fields else () - fields_to_generate = schema.non_union_fields + single_fill - return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate if not field.endswith('DEPRECATED')}) + fields_to_generate = [f for f in schema.non_union_fields + single_fill if not f.endswith('DEPRECATED') and f != 'deprecated'] + return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in fields_to_generate}) @staticmethod @cache diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 11eb2419874..03e59e53675 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -225,7 +225,7 @@ def migrate_controlsState(msgs): for field in ("enabled", "active", "state", "engageable", "alertText1", "alertText2", "alertStatus", "alertSize", "alertType", "experimentalMode", "personality"): - setattr(ss, field, getattr(msg.controlsState, field+"DEPRECATED")) + setattr(ss, field, getattr(msg.controlsState.deprecated, field)) add_ops.append(m.as_reader()) return [], add_ops, [] @@ -238,10 +238,10 @@ def migrate_carState(msgs): if msg.which() == 'controlsState': last_cs = msg elif msg.which() == 'carState' and last_cs is not None: - if last_cs.controlsState.vCruiseDEPRECATED - msg.carState.vCruise > 0.1: + if last_cs.controlsState.deprecated.vCruise - msg.carState.vCruise > 0.1: msg = msg.as_builder() - msg.carState.vCruise = last_cs.controlsState.vCruiseDEPRECATED - msg.carState.vCruiseCluster = last_cs.controlsState.vCruiseClusterDEPRECATED + msg.carState.vCruise = last_cs.controlsState.deprecated.vCruise + msg.carState.vCruiseCluster = last_cs.controlsState.deprecated.vCruiseCluster ops.append((index, msg.as_reader())) return ops, [], [] @@ -488,7 +488,7 @@ def migrate_driverMonitoringState(msgs): for index, msg in msgs: msg = msg.as_builder() events = [] - for event in msg.driverMonitoringState.eventsDEPRECATED: + for event in msg.driverMonitoringState.deprecated.events: try: if not str(event.name).endswith('DEPRECATED'): migrated_event = migrate_onroad_event(event) diff --git a/selfdrive/ui/mici/onroad/hud_renderer.py b/selfdrive/ui/mici/onroad/hud_renderer.py index 56d83992ffb..7f69ea94331 100644 --- a/selfdrive/ui/mici/onroad/hud_renderer.py +++ b/selfdrive/ui/mici/onroad/hud_renderer.py @@ -153,7 +153,7 @@ def _update_state(self) -> None: v_cruise_cluster = car_state.vCruiseCluster set_speed = ( - controls_state.vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster + controls_state.deprecated.vCruise if v_cruise_cluster == 0.0 else v_cruise_cluster ) engaged = sm['selfdriveState'].enabled if (set_speed != self.set_speed and engaged) or (engaged and not self._engaged): diff --git a/selfdrive/ui/onroad/hud_renderer.py b/selfdrive/ui/onroad/hud_renderer.py index 79f150deea0..73df8b39618 100644 --- a/selfdrive/ui/onroad/hud_renderer.py +++ b/selfdrive/ui/onroad/hud_renderer.py @@ -86,7 +86,7 @@ def _update_state(self) -> None: v_cruise_cluster = car_state.vCruiseCluster self.set_speed = ( - controls_state.vCruiseDEPRECATED if v_cruise_cluster == 0.0 else v_cruise_cluster + controls_state.deprecated.vCruise if v_cruise_cluster == 0.0 else v_cruise_cluster ) self.is_cruise_set = 0 < self.set_speed < SET_SPEED_NA self.is_cruise_available = self.set_speed != -1 diff --git a/system/ubloxd/ubloxd.py b/system/ubloxd/ubloxd.py index e55cadcf78b..78429a847b7 100755 --- a/system/ubloxd/ubloxd.py +++ b/system/ubloxd/ubloxd.py @@ -365,7 +365,7 @@ def _parse_glonass_ephemeris(self, msg: Ubx.RxmSfrbx) -> tuple[str, capnp.lib.ca assert isinstance(s1, Glonass.String1) eph.p1 = int(s1.p1) tk = int(s1.t_k) - eph.tkDEPRECATED = tk + eph.deprecated.tk = tk eph.xVel = float(s1.x_vel) * math.pow(2, -20) eph.xAccel = float(s1.x_accel) * math.pow(2, -30) eph.x = float(s1.x) * math.pow(2, -11) diff --git a/system/webrtc/schema.py b/system/webrtc/schema.py index d80986ebf25..4876198eb06 100644 --- a/system/webrtc/schema.py +++ b/system/webrtc/schema.py @@ -16,7 +16,7 @@ def generate_type(type_walker, schema_walker) -> str | list[Any] | dict[str, Any def generate_struct(schema: capnp.lib.capnp._StructSchema) -> dict[str, Any]: - return {field: generate_field(schema.fields[field]) for field in schema.fields if not field.endswith("DEPRECATED")} + return {field: generate_field(schema.fields[field]) for field in schema.fields if not field.endswith("DEPRECATED") and field != "deprecated"} def generate_field(field: capnp.lib.capnp._StructSchemaField) -> str | list[Any] | dict[str, Any]: diff --git a/tools/jotpluggler/browser.cc b/tools/jotpluggler/browser.cc index 0d1b5a2c1ba..27378b4b6bf 100644 --- a/tools/jotpluggler/browser.cc +++ b/tools/jotpluggler/browser.cc @@ -73,7 +73,7 @@ std::vector build_browser_tree(const std::vector &path } bool is_deprecated_browser_path(const std::string &path) { - return path.find("DEPRECATED") != std::string::npos; + return path.find("DEPRECATED") != std::string::npos || path.find("/deprecated/") != std::string::npos; } std::vector visible_browser_paths(const RouteData &route_data, bool show_deprecated_fields) { diff --git a/tools/jotpluggler/sketch_layout.cc b/tools/jotpluggler/sketch_layout.cc index cd0bf510152..bc110b534f4 100644 --- a/tools/jotpluggler/sketch_layout.cc +++ b/tools/jotpluggler/sketch_layout.cc @@ -1304,7 +1304,7 @@ void append_event_fast(cereal::Event::Which which, append_can_frame(can_service, static_cast(msg.getSrc()), msg.getAddress(), - msg.getBusTimeDEPRECATED(), + msg.getDeprecated().getBusTime(), msg.getDat(), tm, series); @@ -1316,7 +1316,7 @@ void append_event_fast(cereal::Event::Which which, append_can_frame(can_service, static_cast(msg.getSrc()), msg.getAddress(), - msg.getBusTimeDEPRECATED(), + msg.getDeprecated().getBusTime(), msg.getDat(), tm, series); diff --git a/tools/replay/logreader.cc b/tools/replay/logreader.cc index aba67bcdf8c..54b69dc168c 100644 --- a/tools/replay/logreader.cc +++ b/tools/replay/logreader.cc @@ -142,18 +142,19 @@ void LogReader::migrateOldEvents() { new_evt.setLogMonoTime(old_evt.getLogMonoTime()); auto new_state = new_evt.initSelfdriveState(); - new_state.setActive(old_state.getActiveDEPRECATED()); - new_state.setAlertSize(old_state.getAlertSizeDEPRECATED()); - new_state.setAlertSound(old_state.getAlertSound2DEPRECATED()); - new_state.setAlertStatus(old_state.getAlertStatusDEPRECATED()); - new_state.setAlertText1(old_state.getAlertText1DEPRECATED()); - new_state.setAlertText2(old_state.getAlertText2DEPRECATED()); - new_state.setAlertType(old_state.getAlertTypeDEPRECATED()); - new_state.setEnabled(old_state.getEnabledDEPRECATED()); - new_state.setEngageable(old_state.getEngageableDEPRECATED()); - new_state.setExperimentalMode(old_state.getExperimentalModeDEPRECATED()); - new_state.setPersonality(old_state.getPersonalityDEPRECATED()); - new_state.setState(old_state.getStateDEPRECATED()); + auto old_dep = old_state.getDeprecated(); + new_state.setActive(old_dep.getActive()); + new_state.setAlertSize(old_dep.getAlertSize()); + new_state.setAlertSound(old_dep.getAlertSound2()); + new_state.setAlertStatus(old_dep.getAlertStatus()); + new_state.setAlertText1(old_dep.getAlertText1()); + new_state.setAlertText2(old_dep.getAlertText2()); + new_state.setAlertType(old_dep.getAlertType()); + new_state.setEnabled(old_dep.getEnabled()); + new_state.setEngageable(old_dep.getEngageable()); + new_state.setExperimentalMode(old_dep.getExperimentalMode()); + new_state.setPersonality(old_dep.getPersonality()); + new_state.setState(old_dep.getState()); // Serialize the new event to the buffer auto buf_size = msg.getSerializedSize(); From 6ce156ee161558a724a72c3ea6e53115b4867c49 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 9 Apr 2026 20:02:17 -0700 Subject: [PATCH 622/685] op switch: clear update after switch (#37793) --- tools/op.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/op.sh b/tools/op.sh index f21a285d175..3b02602619c 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -383,6 +383,9 @@ function op_switch() { git submodule update --init --recursive git submodule foreach git reset --hard git submodule foreach git clean -df + + # remove openpilot update flag if present + rm -f .overlay_init } function op_start() { From 9db432e8fb4b90d0ee6d817dbd0e2be1b3d37539 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Fri, 10 Apr 2026 04:33:47 -0400 Subject: [PATCH 623/685] installer: update cached remote URL during fork installs (#37797) --- selfdrive/ui/installer/installer.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index fb661b966da..0832fbb628d 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -144,6 +144,7 @@ int cachedFetch(const std::string &cache) { LOGD("Fetching with cache: %s", cache.c_str()); run(util::string_format("cp -rp %s %s", cache.c_str(), TMP_INSTALL_PATH).c_str()); + run(util::string_format("cd %s && git remote set-url origin %s", TMP_INSTALL_PATH, GIT_URL.c_str()).c_str()); run(util::string_format("cd %s && git remote set-branches --add origin %s", TMP_INSTALL_PATH, migrated_branch.c_str()).c_str()); renderProgress(10); From 64e785d00f14bf440d3e6836058085f0bad50195 Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Fri, 10 Apr 2026 09:57:22 -0700 Subject: [PATCH 624/685] remove webjoystick components that no longer work (#37798) * remove webjoystick components that no longer work * remove audio track request --- tools/bodyteleop/static/index.html | 67 +++++--------------------- tools/bodyteleop/static/js/controls.js | 34 ------------- tools/bodyteleop/static/js/jsmain.js | 10 +--- tools/bodyteleop/static/js/webrtc.js | 42 ++-------------- tools/bodyteleop/static/main.css | 7 --- 5 files changed, 18 insertions(+), 142 deletions(-) diff --git a/tools/bodyteleop/static/index.html b/tools/bodyteleop/static/index.html index 36547697562..48672dbbf0c 100644 --- a/tools/bodyteleop/static/index.html +++ b/tools/bodyteleop/static/index.html @@ -15,29 +15,23 @@

comma body

- -
-
-
-
-
- - - -
-

body

+
+
+
+
+
W
-
-
- - -
-

you

+
+
A
+
S
+
D
-
+
+
+
@@ -53,43 +47,6 @@
- -
-
-
-
-
-
W
-
0,0x,y
-
-
-
A
-
S
-
D
-
-
-
- - - - -
-
-
-
-
-
-

Play Sounds

-
-
- - - -
-
diff --git a/tools/bodyteleop/static/js/controls.js b/tools/bodyteleop/static/js/controls.js index b1e0e7ee70e..3a11f78b9ef 100644 --- a/tools/bodyteleop/static/js/controls.js +++ b/tools/bodyteleop/static/js/controls.js @@ -18,37 +18,3 @@ export const handleKeyX = (key, setValue) => { $("#pos-vals").text(x+","+y); } }; - -export async function executePlan() { - let plan = $("#plan-text").val(); - const planList = []; - plan.split("\n").forEach(function(e){ - let line = e.split(",").map(k=>parseInt(k)); - if (line.length != 5 || line.slice(0, 4).map(e=>[1, 0].includes(e)).includes(false) || line[4] < 0 || line[4] > 10){ - console.log("invalid plan"); - } - else{ - planList.push(line) - } - }); - - async function execute() { - for (var i = 0; i < planList.length; i++) { - let [w, a, s, d, t] = planList[i]; - while(t > 0){ - console.log(w, a, s, d, t); - if(w==1){$("#key-w").mousedown();} - if(a==1){$("#key-a").mousedown();} - if(s==1){$("#key-s").mousedown();} - if(d==1){$("#key-d").mousedown();} - await sleep(50); - $("#key-w").mouseup(); - $("#key-a").mouseup(); - $("#key-s").mouseup(); - $("#key-d").mouseup(); - t = t - 0.05; - } - } - } - execute(); -} \ No newline at end of file diff --git a/tools/bodyteleop/static/js/jsmain.js b/tools/bodyteleop/static/js/jsmain.js index 83205a876bd..0db1dcd9b30 100644 --- a/tools/bodyteleop/static/js/jsmain.js +++ b/tools/bodyteleop/static/js/jsmain.js @@ -1,5 +1,5 @@ -import { handleKeyX, executePlan } from "./controls.js"; -import { start, stop, lastChannelMessageTime, playSoundRequest } from "./webrtc.js"; +import { handleKeyX } from "./controls.js"; +import { start, stop, lastChannelMessageTime } from "./webrtc.js"; export var pc = null; export var dc = null; @@ -8,12 +8,6 @@ document.addEventListener('keydown', (e)=>(handleKeyX(e.key.toLowerCase(), 1))); document.addEventListener('keyup', (e)=>(handleKeyX(e.key.toLowerCase(), 0))); $(".keys").bind("mousedown touchstart", (e)=>handleKeyX($(e.target).attr('id').replace('key-', ''), 1)); $(".keys").bind("mouseup touchend", (e)=>handleKeyX($(e.target).attr('id').replace('key-', ''), 0)); -$("#plan-button").click(executePlan); -$(".sound").click((e)=>{ - const sound = $(e.target).attr('id').replace('sound-', '') - return playSoundRequest(sound); -}); - setInterval( () => { const dt = new Date().getTime(); if ((dt - lastChannelMessageTime) > 1000) { diff --git a/tools/bodyteleop/static/js/webrtc.js b/tools/bodyteleop/static/js/webrtc.js index 165a2ce6c4b..28bea238e62 100644 --- a/tools/bodyteleop/static/js/webrtc.js +++ b/tools/bodyteleop/static/js/webrtc.js @@ -15,15 +15,6 @@ export function offerRtcRequest(sdp, type) { } -export function playSoundRequest(sound) { - return fetch('/sound', { - body: JSON.stringify({sound}), - headers: {'Content-Type': 'application/json'}, - method: 'POST' - }); -} - - export function pingHeadRequest() { return fetch('/', { method: 'HEAD' @@ -38,20 +29,18 @@ export function createPeerConnection(pc) { pc = new RTCPeerConnection(config); - // connect audio / video + // connect video pc.addEventListener('track', function(evt) { console.log("Adding Tracks!") if (evt.track.kind == 'video') document.getElementById('video').srcObject = evt.streams[0]; - else - document.getElementById('audio').srcObject = evt.streams[0]; }); return pc; } export function negotiate(pc) { - return pc.createOffer({offerToReceiveAudio:true, offerToReceiveVideo:true}).then(function(offer) { + return pc.createOffer({offerToReceiveVideo:true}).then(function(offer) { return pc.setLocalDescription(offer); }).then(function() { return new Promise(function(resolve) { @@ -90,14 +79,6 @@ function isMobile() { export const constraints = { - audio: { - autoGainControl: false, - sampleRate: 48000, - sampleSize: 16, - echoCancellation: true, - noiseSuppression: true, - channelCount: 1 - }, video: isMobile() }; @@ -105,23 +86,8 @@ export const constraints = { export function start(pc, dc) { pc = createPeerConnection(pc); - // add audio track - navigator.mediaDevices.enumerateDevices() - .then(function(devices) { - const hasAudioInput = devices.find((device) => device.kind === "audioinput"); - var modifiedConstraints = {}; - modifiedConstraints.video = constraints.video; - modifiedConstraints.audio = hasAudioInput ? constraints.audio : false; - - return Promise.resolve(modifiedConstraints); - }) - .then(function(constraints) { - if (constraints.audio || constraints.video) { - return navigator.mediaDevices.getUserMedia(constraints); - } else{ - return Promise.resolve(null); - } - }) + // add a local video track on mobile + (constraints.video ? navigator.mediaDevices.getUserMedia(constraints) : Promise.resolve(null)) .then(function(stream) { if (stream) { stream.getTracks().forEach(function(track) { diff --git a/tools/bodyteleop/static/main.css b/tools/bodyteleop/static/main.css index 1bfb5982b4e..79fe8052ff9 100644 --- a/tools/bodyteleop/static/main.css +++ b/tools/bodyteleop/static/main.css @@ -172,13 +172,6 @@ video { display: none; } -.plan-form { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; -} - .details { display: flex; padding: 0px 10px 0px 10px; From 0a58d8b761a7a5b072953dd1a542a1e3dffec9cd Mon Sep 17 00:00:00 2001 From: Bruce Wayne Date: Fri, 10 Apr 2026 19:28:41 -0700 Subject: [PATCH 625/685] Modeld: support uncompiled --- selfdrive/modeld/tinygrad_helpers.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/selfdrive/modeld/tinygrad_helpers.py b/selfdrive/modeld/tinygrad_helpers.py index e833c20968a..49a6ed61618 100644 --- a/selfdrive/modeld/tinygrad_helpers.py +++ b/selfdrive/modeld/tinygrad_helpers.py @@ -7,5 +7,6 @@ def set_tinygrad_backend_from_compiled_flags() -> None: - with open(COMPILED_FLAGS_PATH) as f: - os.environ['DEV'] = str(json.load(f)['DEV']) + if os.path.isfile(COMPILED_FLAGS_PATH): + with open(COMPILED_FLAGS_PATH) as f: + os.environ['DEV'] = str(json.load(f)['DEV']) From b930f5c3f145342263e6b74bee868e43b778c74a Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Fri, 10 Apr 2026 22:01:49 -0700 Subject: [PATCH 626/685] esim: implement profile switching and deletion (#37779) --- system/hardware/tici/lpa.py | 63 ++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index 34b0a62ed2e..ceb901e2f72 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -33,13 +33,26 @@ LOCK_FILE = '/dev/shm/modem_lpa.lock' DEBUG = os.environ.get("DEBUG") == "1" + # TLV Tags TAG_ICCID = 0x5A TAG_STATUS = 0x80 TAG_PROFILE_INFO_LIST = 0xBF2D TAG_SET_NICKNAME = 0xBF29 +TAG_ENABLE_PROFILE = 0xBF31 +TAG_DELETE_PROFILE = 0xBF33 TAG_OK = 0xA0 +PROFILE_OK = 0x00 +PROFILE_NOT_IN_DISABLED_STATE = 0x02 +PROFILE_CAT_BUSY = 0x05 + +PROFILE_ERROR_CODES = { + 0x01: "iccidOrAidNotFound", PROFILE_NOT_IN_DISABLED_STATE: "profileNotInDisabledState", + 0x03: "disallowedByPolicy", 0x04: "wrongProfileReenabling", + PROFILE_CAT_BUSY: "catBusy", 0x06: "undefinedError", +} + STATE_LABELS = {0: "disabled", 1: "enabled", 255: "unknown"} ICON_LABELS = {0: "jpeg", 1: "png", 255: "unknown"} CLASS_LABELS = {0: "test", 1: "provisioning", 2: "operational", 255: "unknown"} @@ -69,6 +82,12 @@ def __init__(self, device: str, baud: int, timeout: float) -> None: self._serial: serial.Serial | None = None self._use_dbus = not os.path.exists(device) + def send_raw(self, data: bytes) -> None: + self._ensure_serial() + self._serial.reset_input_buffer() + self._serial.write(data) + self._serial.flush() + def close(self) -> None: try: if self.channel: @@ -167,6 +186,15 @@ def _open_isdr_once(self) -> None: return raise RuntimeError("Failed to open ISD-R application") + def _reset_modem(self) -> None: + if self._serial: + try: + self._serial.close() + except Exception: + pass + self._serial = None + subprocess.run(['/usr/comma/lte/lte.sh', 'start'], capture_output=True) + def open_isdr(self) -> None: for attempt in range(OPEN_ISDR_RETRIES): try: @@ -175,9 +203,7 @@ def open_isdr(self) -> None: except (RuntimeError, TimeoutError, termios.error, serial.SerialException): time.sleep(OPEN_ISDR_RETRY_DELAY_S) if attempt == OPEN_ISDR_RESET_ATTEMPT: - # reset modem via lte.sh - subprocess.run(['/usr/comma/lte/lte.sh', 'start'], capture_output=True) - self._serial = None # serial port will be re-opened on next attempt + self._reset_modem() raise RuntimeError("Failed to open ISD-R after retries") def send_apdu(self, apdu: bytes) -> tuple[bytes, int, int]: @@ -337,8 +363,7 @@ def set_profile_nickname(client: AtClient, iccid: str, nickname: str) -> None: raise ValueError("Profile nickname must be 64 bytes or less") content = encode_tlv(TAG_ICCID, string_to_tbcd(iccid)) + encode_tlv(0x90, nickname_bytes) response = es10x_command(client, encode_tlv(TAG_SET_NICKNAME, content)) - root = require_tag(response, TAG_SET_NICKNAME, "SetNicknameResponse") - code = require_tag(root, TAG_STATUS, "status in SetNicknameResponse")[0] + code = require_tag(require_tag(response, TAG_SET_NICKNAME, "SetNicknameResponse"), TAG_STATUS, "SetNickname status")[0] if code == 0x01: raise LPAError(f"profile {iccid} not found") if code != 0x00: @@ -385,7 +410,14 @@ def get_active_profile(self) -> Profile | None: return None def delete_profile(self, iccid: str) -> None: - return None + if self.is_comma_profile(iccid): + raise LPAError("refusing to delete a comma profile") + with self._acquire_channel(): + request = encode_tlv(TAG_DELETE_PROFILE, encode_tlv(TAG_ICCID, string_to_tbcd(iccid))) + response = es10x_command(self._client, request) + code = require_tag(require_tag(response, TAG_DELETE_PROFILE, "DeleteProfileResponse"), TAG_STATUS, "DeleteProfile status")[0] + if code != PROFILE_OK: + raise LPAError(f"DeleteProfile failed: {PROFILE_ERROR_CODES.get(code, 'unknown')} (0x{code:02X})") def download_profile(self, qr: str, nickname: str | None = None) -> None: return None @@ -394,5 +426,22 @@ def nickname_profile(self, iccid: str, nickname: str) -> None: with self._acquire_channel(): set_profile_nickname(self._client, iccid, nickname) + def _enable_profile(self, iccid: str) -> int: + inner = encode_tlv(TAG_OK, encode_tlv(TAG_ICCID, string_to_tbcd(iccid))) + inner += b'\x01\x01\x01' # refreshFlag=1 + response = es10x_command(self._client, encode_tlv(TAG_ENABLE_PROFILE, inner)) + return require_tag(require_tag(response, TAG_ENABLE_PROFILE, "EnableProfileResponse"), TAG_STATUS, "EnableProfile status")[0] + def switch_profile(self, iccid: str) -> None: - return None + with self._acquire_channel(): + code = self._enable_profile(iccid) + if code == PROFILE_CAT_BUSY: # stale eUICC transaction, reset and retry + self._client._reset_modem() + self._client.open_isdr() + code = self._enable_profile(iccid) + if code not in (PROFILE_OK, PROFILE_NOT_IN_DISABLED_STATE): + raise LPAError(f"EnableProfile failed: {PROFILE_ERROR_CODES.get(code, 'unknown')} (0x{code:02X})") + from openpilot.system.hardware import HARDWARE + if HARDWARE.get_device_type() == "mici": + self._client.send_raw(b'AT+CFUN=0\rAT+CFUN=1\r') # mici has no SIM presence pin; raw because CFUN=0 drops serial + self._client._ensure_serial(reconnect=True) From f83b749ec2ecd93bb4f1ceb7daafcfeefe4d3091 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sun, 12 Apr 2026 15:30:29 -0400 Subject: [PATCH 627/685] github_utils: use exact-match ref lookup in get_bucket_sha (#37813) The plural `git/refs/heads/{bucket}` endpoint does prefix matching and returns a list when multiple refs share the prefix, which makes `r.json()['object']` raise TypeError. Switch to the singular `git/ref/heads/{bucket}` endpoint so we only match the exact bucket and get a clean 404 otherwise. --- tools/lib/github_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/lib/github_utils.py b/tools/lib/github_utils.py index 46a0dcf3cb1..6a443b4155b 100644 --- a/tools/lib/github_utils.py +++ b/tools/lib/github_utils.py @@ -62,7 +62,7 @@ def create_bucket(self, bucket): self.api_call(github_path, data=data, method=HTTPMethod.POST, data_call=True) def get_bucket_sha(self, bucket): - github_path = f"git/refs/heads/{bucket}" + github_path = f"git/ref/heads/{bucket}" r = self.api_call(github_path, data_call=True, raise_on_failure=False) return r.json()['object']['sha'] if r.ok else None From 0584a5f5ebd2b800aa79980ab74d1a9bbd75a6b2 Mon Sep 17 00:00:00 2001 From: John Belmonte Date: Sun, 12 Apr 2026 14:29:22 -0700 Subject: [PATCH 628/685] add bridge target to cabana run script (#37814) The cabana run script builds for convenience, but omitted the cereal/messaging/bridge dependency needed for streaming. --- tools/cabana/cabana | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cabana/cabana b/tools/cabana/cabana index 128e49400e8..cd9bf1dd797 100755 --- a/tools/cabana/cabana +++ b/tools/cabana/cabana @@ -33,6 +33,6 @@ fi # Build _cabana cd "$ROOT" -scons -j4 tools/cabana/_cabana +scons -j4 tools/cabana/_cabana cereal/messaging/bridge exec "$DIR/_cabana" "$@" From c91a0a83f61d6502153adf0628ab001cd77ffcab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Sun, 12 Apr 2026 23:47:43 -0400 Subject: [PATCH 629/685] Revert OP (#37812) * Revert "OP model 7 (#37760)" This reverts commit 052692b25d63c5ddda276b5c2271383b6aff129f. * Revert "OP model (#37740)" This reverts commit cb327933002bc1a00bf60a3c20af2eb7a5f653e5. * dead * parse_model_outputs: drop extra space --- scripts/reporter.py | 9 ++---- selfdrive/modeld/SConscript | 5 ++-- selfdrive/modeld/modeld.py | 30 ++++--------------- .../modeld/models/big_driving_policy.onnx | 1 + .../modeld/models/big_driving_vision.onnx | 1 + .../modeld/models/driving_off_policy.onnx | 3 -- .../modeld/models/driving_on_policy.onnx | 3 -- selfdrive/modeld/models/driving_policy.onnx | 3 ++ selfdrive/modeld/models/driving_vision.onnx | 4 +-- selfdrive/modeld/parse_model_outputs.py | 13 ++------ 10 files changed, 21 insertions(+), 51 deletions(-) create mode 120000 selfdrive/modeld/models/big_driving_policy.onnx create mode 120000 selfdrive/modeld/models/big_driving_vision.onnx delete mode 100644 selfdrive/modeld/models/driving_off_policy.onnx delete mode 100644 selfdrive/modeld/models/driving_on_policy.onnx create mode 100644 selfdrive/modeld/models/driving_policy.onnx diff --git a/scripts/reporter.py b/scripts/reporter.py index 64f6cb99b83..d894b8af48a 100755 --- a/scripts/reporter.py +++ b/scripts/reporter.py @@ -38,11 +38,6 @@ def get_checkpoint(f): continue fn = os.path.basename(f) - master_path = MASTER_PATH + MODEL_PATH + fn - if os.path.exists(master_path): - master = get_checkpoint(master_path) - master_col = f"[{master}](https://reporter.comma.life/experiment/{master})" - else: - master_col = "N/A (new model)" + master = get_checkpoint(MASTER_PATH + MODEL_PATH + fn) pr = get_checkpoint(BASEDIR + MODEL_PATH + fn) - print("|", fn, "|", master_col, "|", f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|") + print("|", fn, "|", f"[{master}](https://reporter.comma.life/experiment/{master})", "|", f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|") diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 05045d09847..86b62d2c170 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -44,7 +44,7 @@ compiled_flags_node = lenv.Command( mac_brew_string = f'HOME={os.path.expanduser("~")}' if arch == 'Darwin' else '' # Get model metadata -for model_name in ['driving_vision', 'driving_off_policy', 'driving_on_policy', 'dmonitoring_model']: +for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: fn = File(f"models/{model_name}").abspath script_files = [File(Dir("#selfdrive/modeld").File("get_model_metadata.py").abspath)] cmd = f'{tg_flags} {mac_brew_string} python3 {Dir("#selfdrive/modeld").abspath}/get_model_metadata.py {fn}.onnx' @@ -82,5 +82,6 @@ def tg_compile(flags, model_name): ) # Compile small models -for model_name in ['driving_vision', 'driving_off_policy', 'driving_on_policy', 'dmonitoring_model']: +for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: tg_compile(tg_flags, model_name) + diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index c61e417e1ac..0c50d8beb51 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -36,10 +36,8 @@ VISION_PKL_PATH = MODELS_DIR / 'driving_vision_tinygrad.pkl' VISION_METADATA_PATH = MODELS_DIR / 'driving_vision_metadata.pkl' -ON_POLICY_PKL_PATH = MODELS_DIR / 'driving_on_policy_tinygrad.pkl' -ON_POLICY_METADATA_PATH = MODELS_DIR / 'driving_on_policy_metadata.pkl' -OFF_POLICY_PKL_PATH = MODELS_DIR / 'driving_off_policy_tinygrad.pkl' -OFF_POLICY_METADATA_PATH = MODELS_DIR / 'driving_off_policy_metadata.pkl' +POLICY_PKL_PATH = MODELS_DIR / 'driving_policy_tinygrad.pkl' +POLICY_METADATA_PATH = MODELS_DIR / 'driving_policy_metadata.pkl' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 @@ -152,13 +150,7 @@ def __init__(self): self.vision_output_slices = vision_metadata['output_slices'] vision_output_size = vision_metadata['output_shapes']['outputs'][1] - with open(OFF_POLICY_METADATA_PATH, 'rb') as f: - off_policy_metadata = pickle.load(f) - self.off_policy_input_shapes = off_policy_metadata['input_shapes'] - self.off_policy_output_slices = off_policy_metadata['output_slices'] - off_policy_output_size = off_policy_metadata['output_shapes']['outputs'][1] - - with open(ON_POLICY_METADATA_PATH, 'rb') as f: + with open(POLICY_METADATA_PATH, 'rb') as f: policy_metadata = pickle.load(f) self.policy_input_shapes = policy_metadata['input_shapes'] self.policy_output_slices = policy_metadata['output_slices'] @@ -182,13 +174,11 @@ def __init__(self): self.vision_output = np.zeros(vision_output_size, dtype=np.float32) self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self.policy_output = np.zeros(policy_output_size, dtype=np.float32) - self.off_policy_output = np.zeros(off_policy_output_size, dtype=np.float32) self.parser = Parser() self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} self.update_imgs = None self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) - self.policy_run = pickle.loads(read_file_chunked(str(ON_POLICY_PKL_PATH))) - self.off_policy_run = pickle.loads(read_file_chunked(str(OFF_POLICY_PKL_PATH))) + self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -236,17 +226,9 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) - - self.off_policy_output = self.off_policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy() - off_policy_outputs_dict = self.parser.parse_off_policy_outputs(self.slice_outputs(self.off_policy_output, self.off_policy_output_slices)) - off_policy_outputs_dict.pop('plan') - - - combined_outputs_dict = {**vision_outputs_dict, **off_policy_outputs_dict, **policy_outputs_dict} - if 'planplus' in combined_outputs_dict and 'plan' in combined_outputs_dict: - combined_outputs_dict['plan'] = combined_outputs_dict['plan'] + combined_outputs_dict['planplus'] + combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} if SEND_RAW_PRED: - combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy(), self.off_policy_output.copy()]) + combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) return combined_outputs_dict diff --git a/selfdrive/modeld/models/big_driving_policy.onnx b/selfdrive/modeld/models/big_driving_policy.onnx new file mode 120000 index 00000000000..e1b653a14a0 --- /dev/null +++ b/selfdrive/modeld/models/big_driving_policy.onnx @@ -0,0 +1 @@ +driving_policy.onnx \ No newline at end of file diff --git a/selfdrive/modeld/models/big_driving_vision.onnx b/selfdrive/modeld/models/big_driving_vision.onnx new file mode 120000 index 00000000000..28ee71dd746 --- /dev/null +++ b/selfdrive/modeld/models/big_driving_vision.onnx @@ -0,0 +1 @@ +driving_vision.onnx \ No newline at end of file diff --git a/selfdrive/modeld/models/driving_off_policy.onnx b/selfdrive/modeld/models/driving_off_policy.onnx deleted file mode 100644 index 5b0effc1003..00000000000 --- a/selfdrive/modeld/models/driving_off_policy.onnx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e53f4e0527766082ba7bde38e275def0fe3c14f6c59ae2854439e239884d3ecc -size 13393365 diff --git a/selfdrive/modeld/models/driving_on_policy.onnx b/selfdrive/modeld/models/driving_on_policy.onnx deleted file mode 100644 index bfe10ca185b..00000000000 --- a/selfdrive/modeld/models/driving_on_policy.onnx +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ea89c50da3a16e710da292f97c81b083a982cfdee5c28eca0d37ed2fb99af6c5 -size 13022642 diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx new file mode 100644 index 00000000000..7c71bc94714 --- /dev/null +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:853c6634746ff439a848349d00e4d5581cd941f13f7c1862c31b72a31cc24858 +size 14061595 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index 335e9dbcf8e..afd617667c6 100644 --- a/selfdrive/modeld/models/driving_vision.onnx +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6263aa3fbb44cde6c68a34cdb7cd8c389789dbc02b15c1911afdac4e018281ae -size 23267151 +oid sha256:940e9006a25f27f0b6e85da798e6a8fd1f6dd492dd7d0b9ff1a9436460f46129 +size 46887794 diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 802f0ad8599..a0b45d2a981 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -96,17 +96,11 @@ def parse_vision_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndar self.parse_mdn('pose', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) self.parse_mdn('wide_from_device_euler', outs, in_N=0, out_N=0, out_shape=(ModelConstants.WIDE_FROM_DEVICE_WIDTH,)) self.parse_mdn('road_transform', outs, in_N=0, out_N=0, out_shape=(ModelConstants.POSE_WIDTH,)) - self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) - self.parse_binary_crossentropy('meta', outs) - return outs - - def parse_off_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: - plan_mhp = self.is_mhp(outs, 'plan', ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH) - plan_in_N, plan_out_N = (ModelConstants.PLAN_MHP_N, ModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0) - self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)) self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) self.parse_mdn('road_edges', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_ROAD_EDGES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) self.parse_binary_crossentropy('lane_lines_prob', outs) + self.parse_categorical_crossentropy('desire_pred', outs, out_shape=(ModelConstants.DESIRE_PRED_LEN,ModelConstants.DESIRE_PRED_WIDTH)) + self.parse_binary_crossentropy('meta', outs) self.parse_binary_crossentropy('lead_prob', outs) lead_mhp = self.is_mhp(outs, 'lead', ModelConstants.LEAD_MHP_SELECTION * ModelConstants.LEAD_TRAJ_LEN * ModelConstants.LEAD_WIDTH) lead_in_N, lead_out_N = (ModelConstants.LEAD_MHP_N, ModelConstants.LEAD_MHP_SELECTION) if lead_mhp else (0, 0) @@ -116,7 +110,7 @@ def parse_off_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np. return outs def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: - plan_mhp = self.is_mhp(outs, 'plan', ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH) + plan_mhp = self.is_mhp(outs, 'plan', ModelConstants.IDX_N * ModelConstants.PLAN_WIDTH) plan_in_N, plan_out_N = (ModelConstants.PLAN_MHP_N, ModelConstants.PLAN_MHP_SELECTION) if plan_mhp else (0, 0) self.parse_mdn('plan', outs, in_N=plan_in_N, out_N=plan_out_N, out_shape=(ModelConstants.IDX_N, ModelConstants.PLAN_WIDTH)) if 'planplus' in outs: @@ -126,6 +120,5 @@ def parse_policy_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndar def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: outs = self.parse_vision_outputs(outs) - outs = self.parse_off_policy_outputs(outs) outs = self.parse_policy_outputs(outs) return outs From fcb0a496edf22851309dca7ae523272a710717f3 Mon Sep 17 00:00:00 2001 From: YassineYousfi Date: Mon, 13 Apr 2026 12:38:02 -0700 Subject: [PATCH 630/685] model reporter links (#37817) --- scripts/reporter.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/reporter.py b/scripts/reporter.py index d894b8af48a..93b71761a9e 100755 --- a/scripts/reporter.py +++ b/scripts/reporter.py @@ -33,11 +33,7 @@ def get_checkpoint(f): print("|-| ----- | --------- |") for f in glob.glob(BASEDIR + MODEL_PATH + "/*.onnx"): - # TODO: add checkpoint to DM - if "dmonitoring" in f: - continue - fn = os.path.basename(f) master = get_checkpoint(MASTER_PATH + MODEL_PATH + fn) pr = get_checkpoint(BASEDIR + MODEL_PATH + fn) - print("|", fn, "|", f"[{master}](https://reporter.comma.life/experiment/{master})", "|", f"[{pr}](https://reporter.comma.life/experiment/{pr})", "|") + print("|", fn, "|", f"[{master}](https://reporterv2.comma.life/{master})", "|", f"[{pr}](https://reporterv2.comma.life/{pr})", "|") From bf2294dee24c8be4be5d058fe695635784a0489f Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Mon, 13 Apr 2026 13:51:50 -0700 Subject: [PATCH 631/685] Set fan to 100% when onroad is thermally blocked (#37804) set fan to 100% when onroad is thermally blocked --- system/hardware/hardwared.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py index aad30f77b2b..4324e90c75f 100755 --- a/system/hardware/hardwared.py +++ b/system/hardware/hardwared.py @@ -323,6 +323,9 @@ def hardware_thread(end_event, hw_queue) -> None: show_alert = (not onroad_conditions["device_temp_good"] or not startup_conditions["device_temp_engageable"]) and onroad_conditions["ignition"] set_offroad_alert_if_changed("Offroad_TemperatureTooHigh", show_alert, extra_text=extra_text) + if show_alert: + msg.deviceState.fanSpeedPercentDesired = 100 + # *** registration check *** if not PC: # we enforce this for our software, but you are welcome @@ -421,9 +424,10 @@ def hardware_thread(end_event, hw_queue) -> None: statlog.gauge("fan_speed_percent_desired", msg.deviceState.fanSpeedPercentDesired) statlog.gauge("screen_brightness_percent", msg.deviceState.screenBrightnessPercent) - # report to server once every 10 minutes + # report to server once every 10 minutes, or every 1s when thermally blocked rising_edge_started = should_start and not should_start_prev - if rising_edge_started or (count % int(600. / DT_HW)) == 0: + status_packet_interval = 1. if show_alert else 600. + if rising_edge_started or (count % int(status_packet_interval / DT_HW)) == 0: dat = { 'count': count, 'pandaStates': [strip_deprecated_keys(p.to_dict()) for p in pandaStates], From 4a15bdcdae141a430811b5f5a2105eeeb2df4257 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 14 Apr 2026 12:02:30 -0700 Subject: [PATCH 632/685] Add alerts pill (#37821) * add notification pill * add update * comment * lfs upload * rename * fix * try widget * white is banned! * same for bell * need everywhere? --------- Co-authored-by: Shane Smiskol --- selfdrive/assets/icons_mici/alerts_bell.png | 3 ++ selfdrive/assets/icons_mici/alerts_pill.png | 3 ++ selfdrive/ui/mici/layouts/home.py | 43 +++++++++++++++++++-- selfdrive/ui/mici/layouts/main.py | 10 ++++- 4 files changed, 55 insertions(+), 4 deletions(-) create mode 100644 selfdrive/assets/icons_mici/alerts_bell.png create mode 100644 selfdrive/assets/icons_mici/alerts_pill.png diff --git a/selfdrive/assets/icons_mici/alerts_bell.png b/selfdrive/assets/icons_mici/alerts_bell.png new file mode 100644 index 00000000000..5d775425ebf --- /dev/null +++ b/selfdrive/assets/icons_mici/alerts_bell.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ce1d357acadd798939b398cce1761ceb05564b44f2a5bc6865c7842e60e79f2 +size 1474 diff --git a/selfdrive/assets/icons_mici/alerts_pill.png b/selfdrive/assets/icons_mici/alerts_pill.png new file mode 100644 index 00000000000..29ab2ad5b3f --- /dev/null +++ b/selfdrive/assets/icons_mici/alerts_pill.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b3fe73cd1a24c05346a9b4a02e4f900a314c83a422beb38b0f88f91389582cd4 +size 3960 diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 77b665f5d8b..16a40b6d4ca 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -7,8 +7,8 @@ from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.layouts import HBoxLayout from openpilot.system.ui.widgets.icon_widget import IconWidget -from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos +from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, DEFAULT_TEXT_COLOR from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.version import RELEASE_BRANCHES @@ -28,6 +28,34 @@ } +class AlertsPill(Widget): + def __init__(self): + super().__init__() + self.set_rect(rl.Rectangle(0, 0, 104, 52)) + + self._pill_bg_txt = gui_app.texture("icons_mici/alerts_pill.png", 104, 52) + self._bell_txt = gui_app.texture("icons_mici/alerts_bell.png", 28, 30) + self._alert_count_callback: Callable[[], int] | None = None + + def set_alert_count_callback(self, callback: Callable[[], int] | None): + self._alert_count_callback = callback + + def _render(self, _): + alert_count = self._alert_count_callback() if self._alert_count_callback else 0 + if alert_count > 0: + pill_w, pill_h = self._pill_bg_txt.width, self._pill_bg_txt.height + rl.draw_texture_ex(self._pill_bg_txt, rl.Vector2(self.rect.x, self.rect.y), 0.0, 1.0, rl.WHITE) + + bell_x = self.rect.x + 20 + bell_y = self.rect.y + (pill_h - self._bell_txt.height) / 2 + rl.draw_texture_ex(self._bell_txt, rl.Vector2(bell_x, bell_y), 0.0, 1.0, DEFAULT_TEXT_COLOR) + + count_rect = rl.Rectangle(self.rect.x, self.rect.y, pill_w - 20, pill_h) + gui_label(count_rect, str(alert_count), font_size=36, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + class NetworkIcon(Widget): def __init__(self): super().__init__() @@ -84,6 +112,7 @@ class MiciHomeLayout(Widget): def __init__(self): super().__init__() self._on_settings_click: Callable | None = None + self._alert_count_callback: Callable[[], int] | None = None self._last_refresh = 0 self._mouse_down_t: None | float = None @@ -96,6 +125,8 @@ def __init__(self): self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48)) self._mic_icon = IconWidget("icons_mici/microphone.png", (32, 46)) + self._alerts_pill = AlertsPill() + self._status_bar_layout = HBoxLayout([ IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9), NetworkIcon(), @@ -141,8 +172,9 @@ def _update_state(self): self._last_refresh = rl.get_time() self._update_params() - def set_callbacks(self, on_settings: Callable | None = None): + def set_callbacks(self, on_settings: Callable | None = None, alert_count_callback: Callable[[], int] | None = None): self._on_settings_click = on_settings + self._alerts_pill.set_alert_count_callback(alert_count_callback) def _handle_mouse_release(self, mouse_pos: MousePos): if not self._did_long_press: @@ -203,3 +235,8 @@ def _render(self, _): footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48) self._status_bar_layout.render(footer_rect) + + # TODO: add alignment to hboxlayout and add to there + self._alerts_pill.set_position(self.rect.x + self.rect.width - self._alerts_pill.rect.width - HOME_PADDING, + self.rect.y + self.rect.height - self._alerts_pill.rect.height) + self._alerts_pill.render() diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 95258e2795d..b25c4af7b2b 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -58,7 +58,10 @@ def __init__(self): gui_app.push_widget(self._onboarding_window) def _setup_callbacks(self): - self._home_layout.set_callbacks(on_settings=lambda: gui_app.push_widget(self._settings_layout)) + self._home_layout.set_callbacks( + on_settings=lambda: gui_app.push_widget(self._settings_layout), + alert_count_callback=self._alerts_layout.active_alerts, + ) self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) device.add_interactive_timeout_callback(self._on_interactive_timeout) @@ -66,6 +69,11 @@ def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) self._scroller.scroll_to(layout_x, smooth=True) + def _update_state(self): + super()._update_state() + # TODO: Hack to run alert updates while not in view. Add a nav stack tick? + self._alerts_layout._update_state() + def _render(self, _): if not self._setup: if self._alerts_layout.active_alerts() > 0: From d204d626bd7ac79c060242614a02639cf10383dd Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 14 Apr 2026 14:53:49 -0700 Subject: [PATCH 633/685] jp: skip unparsable messages (#37816) --- tools/jotpluggler/app.h | 2 +- tools/jotpluggler/runtime.cc | 4 +- tools/jotpluggler/sketch_layout.cc | 130 +++++++++++++++++------------ 3 files changed, 79 insertions(+), 57 deletions(-) diff --git a/tools/jotpluggler/app.h b/tools/jotpluggler/app.h index 71f71f2d9f3..55f6d3ef9f6 100644 --- a/tools/jotpluggler/app.h +++ b/tools/jotpluggler/app.h @@ -378,7 +378,7 @@ class StreamAccumulator { StreamAccumulator &operator=(const StreamAccumulator &) = delete; void setDbcName(const std::string &dbc_name); - void appendEvent(cereal::Event::Which which, kj::ArrayPtr data); + void appendEvent(kj::ArrayPtr data); void appendCanFrames(CanServiceKind service, const std::vector &frames); StreamExtractBatch takeBatch(); const std::string &carFingerprint() const; diff --git a/tools/jotpluggler/runtime.cc b/tools/jotpluggler/runtime.cc index 47344c5519a..6eb2be80e8e 100644 --- a/tools/jotpluggler/runtime.cc +++ b/tools/jotpluggler/runtime.cc @@ -598,9 +598,7 @@ struct StreamPoller::Impl { } kj::ArrayPtr data(reinterpret_cast(msg->getData()), size / sizeof(capnp::word)); - capnp::FlatArrayMessageReader event_reader(data); - const cereal::Event::Reader event = event_reader.getRoot(); - accumulator->appendEvent(event.which(), data); + accumulator->appendEvent(data); received_messages.fetch_add(1); } } diff --git a/tools/jotpluggler/sketch_layout.cc b/tools/jotpluggler/sketch_layout.cc index bc110b534f4..fd15e3d9164 100644 --- a/tools/jotpluggler/sketch_layout.cc +++ b/tools/jotpluggler/sketch_layout.cc @@ -3,6 +3,7 @@ #include "tools/jotpluggler/common.h" #include +#include #include #include @@ -205,6 +206,18 @@ struct LoadStats { mutable std::mutex progress_mutex; }; +// Skip individual messages that our local Cap'n Proto schema can't project, +// such as logs recorded by a newer build. +template +void with_parseable_event(kj::ArrayPtr data, Fn &&fn) { + try { + capnp::FlatArrayMessageReader event_reader(data); + fn(event_reader.getRoot()); + } catch (const kj::Exception &) { + return; + } +} + std::string curve_label(std::string_view series_name) { return std::string(series_name.empty() ? std::string_view{"plot"} : series_name); } @@ -666,11 +679,11 @@ std::vector extract_segment_timeline(const std::vector &ev if (event_record.which != cereal::Event::Which::SELFDRIVE_STATE) { continue; } - capnp::FlatArrayMessageReader event_reader(event_record.data); - const cereal::Event::Reader event = event_reader.getRoot(); - const auto sd = event.getSelfdriveState(); - const double mono_time = static_cast(event.getLogMonoTime()) / 1.0e9; - append_timeline_entry(&timeline, mono_time, alert_status_to_timeline_type(sd.getAlertStatus(), sd.getEnabled())); + with_parseable_event(event_record.data, [&](const cereal::Event::Reader &event) { + const auto sd = event.getSelfdriveState(); + const double mono_time = static_cast(event.getLogMonoTime()) / 1.0e9; + append_timeline_entry(&timeline, mono_time, alert_status_to_timeline_type(sd.getAlertStatus(), sd.getEnabled())); + }); } return timeline; @@ -682,9 +695,9 @@ std::vector extract_segment_logs(const std::vector &events) { std::string last_alert_key; for (const Event &event_record : events) { - capnp::FlatArrayMessageReader event_reader(event_record.data); - const cereal::Event::Reader event = event_reader.getRoot(); - append_log_event(event_record.which, event, 0.0, &logs, &last_alert_key); + with_parseable_event(event_record.data, [&](const cereal::Event::Reader &event) { + append_log_event(event_record.which, event, 0.0, &logs, &last_alert_key); + }); } return logs; @@ -694,9 +707,9 @@ RouteMetadata extract_segment_metadata(const std::vector &events) { RouteMetadata metadata; for (const Event &event_record : events) { if (event_record.which != cereal::Event::Which::CAR_PARAMS) continue; - capnp::FlatArrayMessageReader event_reader(event_record.data); - const cereal::Event::Reader event = event_reader.getRoot(); - metadata.car_fingerprint = event.getCarParams().getCarFingerprint().cStr(); + with_parseable_event(event_record.data, [&](const cereal::Event::Reader &event) { + metadata.car_fingerprint = event.getCarParams().getCarFingerprint().cStr(); + }); if (!metadata.car_fingerprint.empty()) break; } return metadata; @@ -1263,24 +1276,20 @@ void append_fast_node(const ResolvedNode &node, } } -void append_event_fast(cereal::Event::Which which, - int32_t eidx_segnum, - kj::ArrayPtr data, - const SchemaIndex &schema, - const dbc::Database *can_dbc, - bool skip_raw_can, - double time_offset, - SeriesAccumulator *series) { - if (eidx_segnum != -1) { - return; - } +void append_event_fast_reader(cereal::Event::Which which, + const cereal::Event::Reader &event, + const SchemaIndex &schema, + const dbc::Database *can_dbc, + bool skip_raw_can, + double time_offset, + SeriesAccumulator *series) { const uint16_t which_index = static_cast(which); if (which_index >= schema.by_which.size() || !schema.by_which[which_index].has_value()) { return; } const ResolvedService &service = *schema.by_which[which_index]; - capnp::FlatArrayMessageReader event_reader(data); - const cereal::Event::Reader event = event_reader.getRoot(); + const capnp::DynamicStruct::Reader dynamic_event(event); + const capnp::DynamicValue::Reader payload = dynamic_event.get(service.union_field); const double tm = static_cast(event.getLogMonoTime()) / 1.0e9 - time_offset; append_fixed_scalar_point(&series->fixed_series[static_cast(service.valid_slot)], tm, @@ -1329,8 +1338,23 @@ void append_event_fast(cereal::Event::Which which, } } - const capnp::DynamicStruct::Reader dynamic_event(event); - append_fast_node(service.payload, dynamic_event.get(service.union_field), tm, series); + append_fast_node(service.payload, payload, tm, series); +} + +void append_event_fast(cereal::Event::Which which, + int32_t eidx_segnum, + kj::ArrayPtr data, + const SchemaIndex &schema, + const dbc::Database *can_dbc, + bool skip_raw_can, + double time_offset, + SeriesAccumulator *series) { + if (eidx_segnum != -1) { + return; + } + with_parseable_event(data, [&](const cereal::Event::Reader &event) { + append_event_fast_reader(which, event, schema, can_dbc, skip_raw_can, time_offset, series); + }); } void append_events_fast_range(const std::vector &events, @@ -2008,35 +2032,35 @@ void StreamAccumulator::setDbcName(const std::string &dbc_name) { impl_->refresh_dbc(); } -void StreamAccumulator::appendEvent(cereal::Event::Which which, kj::ArrayPtr data) { - capnp::FlatArrayMessageReader event_reader(data); - const cereal::Event::Reader event = event_reader.getRoot(); - const double boot_time = static_cast(event.getLogMonoTime()) / 1.0e9; - if (!impl_->time_offset.has_value()) { - impl_->time_offset = boot_time; - } - if (which == cereal::Event::Which::CAR_PARAMS) { - const std::string fingerprint = event.getCarParams().getCarFingerprint().cStr(); - if (!fingerprint.empty() && fingerprint != impl_->car_fingerprint) { - impl_->car_fingerprint = fingerprint; - impl_->refresh_dbc(); +void StreamAccumulator::appendEvent(kj::ArrayPtr data) { + with_parseable_event(data, [&](const cereal::Event::Reader &event) { + const cereal::Event::Which which = event.which(); + const double boot_time = static_cast(event.getLogMonoTime()) / 1.0e9; + if (!impl_->time_offset.has_value()) { + impl_->time_offset = boot_time; + } + if (which == cereal::Event::Which::CAR_PARAMS) { + const std::string fingerprint = event.getCarParams().getCarFingerprint().cStr(); + if (!fingerprint.empty() && fingerprint != impl_->car_fingerprint) { + impl_->car_fingerprint = fingerprint; + impl_->refresh_dbc(); + } } - } - append_event_fast(which, - -1, - data, - impl_->schema, - impl_->can_dbc ? &*impl_->can_dbc : nullptr, - true, - *impl_->time_offset, - &impl_->series); - append_log_event(which, event, *impl_->time_offset, &impl_->logs, &impl_->last_alert_key); - if (which == cereal::Event::Which::SELFDRIVE_STATE) { - const auto sd = event.getSelfdriveState(); - append_timeline_entry(&impl_->timeline, boot_time - *impl_->time_offset, - alert_status_to_timeline_type(sd.getAlertStatus(), sd.getEnabled())); - } + append_event_fast_reader(which, + event, + impl_->schema, + impl_->can_dbc ? &*impl_->can_dbc : nullptr, + true, + *impl_->time_offset, + &impl_->series); + append_log_event(which, event, *impl_->time_offset, &impl_->logs, &impl_->last_alert_key); + if (which == cereal::Event::Which::SELFDRIVE_STATE) { + const auto sd = event.getSelfdriveState(); + append_timeline_entry(&impl_->timeline, boot_time - *impl_->time_offset, + alert_status_to_timeline_type(sd.getAlertStatus(), sd.getEnabled())); + } + }); } void StreamAccumulator::appendCanFrames(CanServiceKind service, const std::vector &frames) { From f70a156c7e608e57024b37eb6c639a1f2328a570 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 14 Apr 2026 16:35:34 -0700 Subject: [PATCH 634/685] docs: fix invalid img tags in car docs (#37828) --- docs/CARS.md | 134 ++++++++++++++--------------- selfdrive/car/CARS_template.md | 2 +- selfdrive/debug/print_docs_diff.py | 2 +- 3 files changed, 69 insertions(+), 69 deletions(-) diff --git a/docs/CARS.md b/docs/CARS.md index cceff62cd92..f50ed2a7ef5 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -22,7 +22,7 @@ A supported vehicle is one that just works when you install a comma device. All |Audi[11](#footnotes)|Q3 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Audi[11](#footnotes)|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Audi[11](#footnotes)|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim, without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| |Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| @@ -32,34 +32,34 @@ A supported vehicle is one that just works when you install a comma device. All |Chrysler|Pacifica 2021-23|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Chrysler|Pacifica Hybrid 2019-25|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| +|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||| |CUPRA[11](#footnotes)|Ateca 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Bronco Sport 2021-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Escape Plug-in Hybrid 2023-24|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Expedition 2022-24|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Explorer 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Explorer Hybrid 2020-24|Co-Pilot360 Assist+|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|F-150 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|F-150 Hybrid 2021-23|Co-Pilot360 Assist 2.0|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Focus 2018[2](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Focus Hybrid 2018[2](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Kuga 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Kuga Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Kuga Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Kuga Plug-in Hybrid 2020-23|Adaptive Cruise Control with Lane Centering|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Kuga Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ford|Maverick 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Maverick 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Ford|Maverick Hybrid 2023-24|Co-Pilot360 Assist|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Mustang Mach-E 2021-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Ford|Ranger 2024|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q4 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Genesis|G70 2018|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Genesis|G70 2019-21|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Genesis|G70 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -74,18 +74,18 @@ A supported vehicle is one that just works when you install a comma device. All |Genesis|GV70 Electrified (Australia Only) 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Genesis|GV70 Electrified (with HDA II) 2023-24|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Genesis|GV80 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai M connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Accord 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Accord Hybrid 2023-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|City (Brazil only) 2023|All|openpilot available[1](#footnotes)|0 mph|14 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|Civic 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback 2017-18|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback 2019-21|All|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Honda|Civic Hatchback 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Honda|Civic Hatchback 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hatchback Hybrid (Europe only) 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Honda|Civic Hybrid 2025-26|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Honda Bosch B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -117,9 +117,9 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Elantra 2019|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Elantra GT 2017-20|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai J connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Ioniq 5 (Southeast Asia and Europe only) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -135,17 +135,17 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai O connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Nexo 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Santa Cruz 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Staria 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -155,8 +155,8 @@ A supported vehicle is one that just works when you install a comma device. All |Hyundai|Tucson Hybrid 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -169,10 +169,10 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|K5 Hybrid 2020-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|K7 2017|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|K8 Hybrid (with HDA II) 2023|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro EV (with HDA II) 2024-25|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro EV (without HDA II) 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Niro Hybrid 2018|Smart Cruise Control (SCC)|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -187,21 +187,21 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento 2018|Advanced Smart Cruise Control & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Sorento 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Sorento Hybrid 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Sorento Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Sportage 2023-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Stinger 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|CT Hybrid 2017-18|Lexus Safety System+|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|ES 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|ES 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|ES Hybrid 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Lexus|ES Hybrid 2019-25|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lexus|IS 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| @@ -222,30 +222,30 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|UX Hybrid 2019-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Lincoln|Aviator Plug-in Hybrid 2020-24|Co-Pilot360 Plus|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|MAN[11](#footnotes)|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|MAN[11](#footnotes)|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN[11](#footnotes)|eTGE 2020-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|MAN[11](#footnotes)|TGE 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Mazda|CX-5 2022-25|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Nissan[5](#footnotes)|Altima 2019-20, 2024|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Nissan[5](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Nissan[5](#footnotes)|Leaf 2018-23|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Nissan[5](#footnotes)|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Nissan[5](#footnotes)|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Nissan A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Ram|1500 2019-24|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Ram connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1S 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Rivian|R1S 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Rivian|R1T 2022-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Rivian|R1T 2025|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Rivian B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |SEAT[11](#footnotes)|Ateca 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |SEAT[11](#footnotes)|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Subaru|Ascent 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Crosstrek 2020-23|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Forester 2019-21|All[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Impreza 2017-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Impreza 2020-22|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Legacy 2020-22|All[6](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|Outback 2020-22|All[6](#footnotes)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru B connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| -|Subaru|XV 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| +|Subaru|XV 2018-19|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Subaru|XV 2020-21|EyeSight Driver Assistance[6](#footnotes)|openpilot available[1,7](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Subaru A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
Tools- 1 Pry Tool
- 1 Socket Wrench 8mm or 5/16" (deep)
||| |Škoda|Fabia 2022-23[14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| |Škoda|Kamiq 2021-23[12,14](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
[16](#footnotes)||| @@ -272,50 +272,50 @@ A supported vehicle is one that just works when you install a comma device. All |Toyota|C-HR 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|C-HR Hybrid 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Camry 2018-20|All|Stock|0 mph[10](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry 2018-20|All|Stock|0 mph[10](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Camry 2021-24|All|openpilot|0 mph[10](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Camry Hybrid 2021-24|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Corolla Hybrid (South America only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Highlander 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Highlander 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Highlander Hybrid 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Prius 2016|Toyota Safety Sense P|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Prius 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Prius Prime 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2016|Toyota Safety Sense P|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius Prime 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|Prius v 2017|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|RAV4 Hybrid 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2017-18|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| -|Volkswagen[11](#footnotes)|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen[11](#footnotes)|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen[11](#footnotes)|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen[11](#footnotes)|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|RAV4 Hybrid 2023-25|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Toyota|Sienna 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 Toyota A connector
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Arteon R 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Arteon Shooting Brake 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|Atlas Cross Sport 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|California 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen[11](#footnotes)|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen[11](#footnotes)|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen[11](#footnotes)|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Crafter 2017-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|e-Crafter 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| @@ -324,7 +324,7 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen[11](#footnotes)|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| -|Volkswagen[11](#footnotes)|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| +|Volkswagen[11](#footnotes)|Grand California 2019-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|Jetta 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen[11](#footnotes)|Jetta GLI 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| |Volkswagen|Passat 2015-22[13](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,15](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 OBD-C cable (2 ft)
- 1 VW J533 connector
- 1 comma four
- 1 harness box
- 1 long OBD-C cable (9.5 ft)
- 1 mount
Buy Here
||| diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md index cd352b2edeb..bc335b6bd33 100644 --- a/selfdrive/car/CARS_template.md +++ b/selfdrive/car/CARS_template.md @@ -1,6 +1,6 @@ {% set footnote_tag = '[{}](#footnotes)' %} {% set star_icon = '[![star](assets/icon-star-{}.svg)](##)' %} -{% set video_icon = '' %} +{% set video_icon = '' %} {# Force hardware column wider by using a blank image with max width. #} {% set width_tag = '%s
 ' %} {% set hardware_col_name = 'Hardware Needed' %} diff --git a/selfdrive/debug/print_docs_diff.py b/selfdrive/debug/print_docs_diff.py index 388acf3af58..c7850939f07 100755 --- a/selfdrive/debug/print_docs_diff.py +++ b/selfdrive/debug/print_docs_diff.py @@ -11,7 +11,7 @@ STAR_ICON = '' VIDEO_ICON = '' + \ - '' + '' COLUMNS = "|" + "|".join([column.value for column in Column]) + "|" COLUMN_HEADER = "|---|---|---|{}|".format("|".join([":---:"] * (len(Column) - 3))) ARROW_SYMBOL = "➡️" From e16d422cf4499c57efdb546eeda33dbd3d60304f Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Tue, 14 Apr 2026 19:13:57 -0700 Subject: [PATCH 635/685] body quality of life (#37803) * turn off ir leds on notCar * reduce startup time on notCar * fix: check notCar once after onroad is true * save a disk read by using existing is_onroad --- selfdrive/pandad/pandad.cc | 25 +++++++++++++++++++++---- selfdrive/selfdrived/selfdrived.py | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/selfdrive/pandad/pandad.cc b/selfdrive/pandad/pandad.cc index 28d459f458a..f0f13d32e74 100644 --- a/selfdrive/pandad/pandad.cc +++ b/selfdrive/pandad/pandad.cc @@ -285,7 +285,7 @@ void process_panda_state(Panda *panda, PubMaster *pm, bool engaged, bool is_onro panda->send_heartbeat(engaged); } -void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) { +void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control, bool is_onroad) { static Params params; static SubMaster sm({"deviceState", "driverCameraState"}); @@ -295,6 +295,8 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) static int prev_ir_pwr = 999; static uint32_t prev_frame_id = UINT32_MAX; static bool driver_view = false; + static bool not_car = false; + static bool not_car_checked = false; // TODO: can we merge these? static FirstOrderFilter integ_lines_filter(0, 30.0, 0.05); @@ -340,10 +342,25 @@ void process_peripheral_state(Panda *panda, PubMaster *pm, bool no_fan_control) ir_pwr = 0; } + // turn off IR leds if body + if (!not_car_checked && is_onroad) { + std::string cp_bytes = params.get("CarParams"); + if (cp_bytes.size() > 0) { + AlignedBuffer aligned_buf; + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); + cereal::CarParams::Reader CP = cmsg.getRoot(); + not_car = CP.getNotCar(); + not_car_checked = true; + } + } + if (not_car) { + ir_pwr = 0; + } + if (ir_pwr != prev_ir_pwr || sm.frame % 100 == 0) { - int16_t ir_panda = util::map_val(ir_pwr, 0, 100, 0, MAX_IR_PANDA_VAL); + int16_t ir_panda = util::map_val(ir_pwr, 0, 100, 0, MAX_IR_PANDA_VAL); panda->set_ir_pwr(ir_panda); - Hardware::set_ir_power(ir_pwr); + Hardware::set_ir_power(ir_pwr); prev_ir_pwr = ir_pwr; } } @@ -371,7 +388,7 @@ void pandad_run(Panda *panda) { // Process peripheral state at 20 Hz if (rk.frame() % 5 == 0) { - process_peripheral_state(panda, &pm, no_fan_control); + process_peripheral_state(panda, &pm, no_fan_control, is_onroad); } // Process panda state at 10 Hz diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 6a294ca8d82..dbc3174af74 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -192,7 +192,7 @@ def update_events(self, CS): if self.CP.notCar: # wait for everything to init first - if self.sm.frame > int(5. / DT_CTRL) and self.initialized: + if self.sm.frame > int(2. / DT_CTRL) and self.initialized: # body always wants to enable self.events.add(EventName.pcmEnable) From d5f1d8c33ad085414d3ea70914ec195ae0184da2 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 14 Apr 2026 20:18:31 -0700 Subject: [PATCH 636/685] fix compare_logs crash on list field size changes (#37829) fix crash on list-length changes --- selfdrive/test/process_replay/compare_logs.py | 2 +- selfdrive/test/process_replay/diff_report.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index e2d912a8339..4c522c9150d 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -76,7 +76,7 @@ def _diff_capnp_values(v1, v2, path, tolerance): for i in range(n): yield from _diff_capnp_values(v1[i], v2[i], path + (str(i),), tolerance) if n2 > n: - yield 'add', dot, list(enumerate(v2[n:], n)) + yield 'add', dot, [(i, v2[i]) for i in range(n, n2)] if n1 > n: yield 'remove', dot, list(reversed([(i, v1[i]) for i in range(n, n1)])) diff --git a/selfdrive/test/process_replay/diff_report.py b/selfdrive/test/process_replay/diff_report.py index 32f058f8ee9..5da78657f48 100644 --- a/selfdrive/test/process_replay/diff_report.py +++ b/selfdrive/test/process_replay/diff_report.py @@ -49,6 +49,8 @@ def diff_format(diffs, ref, new, field) -> list[str]: msg_type = field.split(".")[0] ref_ts = [(m.logMonoTime, MsgWrap(m)) for m in ref.get(msg_type, [])] new_wrapped = [MsgWrap(m) for m in new.get(msg_type, [])] + if not ref_ts or not new_wrapped: + return format_numeric_diffs(diffs) return format_diff(diffs, ref_ts, new_wrapped, field) From e78e6261ca986d3be4845de772e4105393b06f44 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Tue, 14 Apr 2026 23:19:40 -0700 Subject: [PATCH 637/685] Add touch zone for alerts (#37819) * add touch zone for alerts * adjust touch zone * 200 px zones * 50/30 split * adjust zones --- selfdrive/ui/mici/layouts/home.py | 18 +++++++++++++++--- selfdrive/ui/mici/layouts/main.py | 1 + 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 16a40b6d4ca..19cca5f3589 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -14,6 +14,8 @@ HEAD_BUTTON_FONT_SIZE = 40 HOME_PADDING = 8 +SETTINGS_ZONE_WIDTH = 280 +ALERTS_ZONE_WIDTH = 180 NetworkType = log.DeviceState.NetworkType @@ -112,6 +114,7 @@ class MiciHomeLayout(Widget): def __init__(self): super().__init__() self._on_settings_click: Callable | None = None + self._on_alerts_click: Callable | None = None self._alert_count_callback: Callable[[], int] | None = None self._last_refresh = 0 @@ -172,14 +175,23 @@ def _update_state(self): self._last_refresh = rl.get_time() self._update_params() - def set_callbacks(self, on_settings: Callable | None = None, alert_count_callback: Callable[[], int] | None = None): + def set_callbacks(self, on_settings: Callable | None = None, on_alerts: Callable | None = None, + alert_count_callback: Callable[[], int] | None = None): self._on_settings_click = on_settings + self._on_alerts_click = on_alerts + self._alert_count_callback = alert_count_callback self._alerts_pill.set_alert_count_callback(alert_count_callback) def _handle_mouse_release(self, mouse_pos: MousePos): if not self._did_long_press: - if self._on_settings_click: - self._on_settings_click() + relative_x = mouse_pos.x - self.rect.x + has_alerts = self._alert_count_callback and self._alert_count_callback() > 0 + if relative_x < SETTINGS_ZONE_WIDTH: + if self._on_settings_click: + self._on_settings_click() + elif has_alerts and relative_x > self.rect.width - ALERTS_ZONE_WIDTH: + if self._on_alerts_click: + self._on_alerts_click() self._did_long_press = False def _get_version_text(self) -> tuple[str, str, str, str] | None: diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index b25c4af7b2b..0d0f6308f23 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -60,6 +60,7 @@ def __init__(self): def _setup_callbacks(self): self._home_layout.set_callbacks( on_settings=lambda: gui_app.push_widget(self._settings_layout), + on_alerts=lambda: self._scroll_to(self._alerts_layout), alert_count_callback=self._alerts_layout.active_alerts, ) self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) From 8b2eac4d1fd6db36aa1fe32edc6c500d9f0d64bf Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Wed, 15 Apr 2026 00:09:59 -0700 Subject: [PATCH 638/685] use warning as alert icon (#37832) replace bell with warning icon --- selfdrive/ui/mici/layouts/home.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 19cca5f3589..4bdff388b0d 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -8,7 +8,7 @@ from openpilot.system.ui.widgets.layouts import HBoxLayout from openpilot.system.ui.widgets.icon_widget import IconWidget from openpilot.system.ui.widgets.label import UnifiedLabel, gui_label -from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos, DEFAULT_TEXT_COLOR +from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.version import RELEASE_BRANCHES @@ -31,12 +31,15 @@ class AlertsPill(Widget): + ICON_OFFSET = 12 + COUNT_OFFSET = 40 + def __init__(self): super().__init__() self.set_rect(rl.Rectangle(0, 0, 104, 52)) self._pill_bg_txt = gui_app.texture("icons_mici/alerts_pill.png", 104, 52) - self._bell_txt = gui_app.texture("icons_mici/alerts_bell.png", 28, 30) + self._warning_txt = gui_app.texture("icons_mici/offroad_alerts/red_warning.png", 36, 36) self._alert_count_callback: Callable[[], int] | None = None def set_alert_count_callback(self, callback: Callable[[], int] | None): @@ -48,13 +51,13 @@ def _render(self, _): pill_w, pill_h = self._pill_bg_txt.width, self._pill_bg_txt.height rl.draw_texture_ex(self._pill_bg_txt, rl.Vector2(self.rect.x, self.rect.y), 0.0, 1.0, rl.WHITE) - bell_x = self.rect.x + 20 - bell_y = self.rect.y + (pill_h - self._bell_txt.height) / 2 - rl.draw_texture_ex(self._bell_txt, rl.Vector2(bell_x, bell_y), 0.0, 1.0, DEFAULT_TEXT_COLOR) + warn_x = self.rect.x + self.ICON_OFFSET + warn_y = self.rect.y + (pill_h - self._warning_txt.height) / 2 + rl.draw_texture_ex(self._warning_txt, rl.Vector2(warn_x, warn_y), 0.0, 1.0, rl.WHITE) - count_rect = rl.Rectangle(self.rect.x, self.rect.y, pill_w - 20, pill_h) + count_rect = rl.Rectangle(self.rect.x + self.COUNT_OFFSET, self.rect.y, pill_w - self.COUNT_OFFSET, pill_h) gui_label(count_rect, str(alert_count), font_size=36, - alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) From 1276452cfc76fb1a14b94993680db68ffd02a866 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Wed, 15 Apr 2026 00:15:57 -0700 Subject: [PATCH 639/685] add blocked-startup offroad label (#37833) add blocked-startup offroad label --- selfdrive/ui/mici/onroad/augmented_road_view.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 09d5d57fbba..63e976caee7 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -174,6 +174,8 @@ def _update_state(self): # update offroad label if ui_state.panda_type == log.PandaState.PandaType.unknown: self._offroad_label.set_text("system booting") + elif ui_state.ignition and not ui_state.started: + self._offroad_label.set_text("openpilot can't start\ncheck alerts") else: self._offroad_label.set_text("start the car to\nuse openpilot") From 00b7c8e8ad5502fe76a24d649203ab2bed0a1813 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:44:28 -0700 Subject: [PATCH 640/685] esim: implement ES9P HTTP and notification processing (#37807) --- pyproject.toml | 2 +- system/hardware/base.py | 3 + system/hardware/tici/gsma_ci_bundle.pem | 133 ++++++++++++++++++++++++ system/hardware/tici/lpa.py | 95 +++++++++++++++++ 4 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 system/hardware/tici/gsma_ci_bundle.pem diff --git a/pyproject.toml b/pyproject.toml index a1123234001..e595106b5c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,7 +149,7 @@ quiet-level = 3 # if you've got a short variable name that's getting flagged, add it here ignore-words-list = "bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints,whit,indexIn,ws,uint,grey,deque,stdio,amin,BA,LITE,atEnd,UIs,errorString,arange,FocusIn,od,tim,relA,hist,copyable,jupyter,thead,TGE,abl,lite,ser" builtin = "clear,rare,informal,code,names,en-GB_to_en-US" -skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" +skip = "./third_party/*, ./tinygrad/*, ./tinygrad_repo/*, ./msgq/*, ./panda/*, ./opendbc/*, ./opendbc_repo/*, ./rednose/*, ./rednose_repo/*, ./teleoprtc/*, ./teleoprtc_repo/*, *.po, uv.lock, *.onnx, *.pem, ./cereal/gen/*, */c_generated_code/*, docs/assets/*, tools/plotjuggler/layouts/*, selfdrive/assets/offroad/mici_fcc.html" # https://docs.astral.sh/ruff/configuration/#using-pyprojecttoml [tool.ruff] diff --git a/system/hardware/base.py b/system/hardware/base.py index 1a19f908c62..ef2b1460434 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -91,6 +91,9 @@ def nickname_profile(self, iccid: str, nickname: str) -> None: def switch_profile(self, iccid: str) -> None: pass + def process_notifications(self) -> None: + pass + def is_comma_profile(self, iccid: str) -> bool: return any(iccid.startswith(prefix) for prefix in ('8985235',)) diff --git a/system/hardware/tici/gsma_ci_bundle.pem b/system/hardware/tici/gsma_ci_bundle.pem new file mode 100644 index 00000000000..3ee7fd12521 --- /dev/null +++ b/system/hardware/tici/gsma_ci_bundle.pem @@ -0,0 +1,133 @@ +# GSMA Certificate Issuer (CI) bundle for eSIM RSP +# Source: https://euicc-manual.osmocom.org/docs/pki/ci/bundle.pem + +issuer= + countryName = CH + organizationName = OISTE Foundation + commonName = OISTE GSMA CI G1 +notBefore=2024-01-16 23:17:39Z +notAfter=2059-01-07 23:17:38Z +-----BEGIN CERTIFICATE----- +MIIB9zCCAZ2gAwIBAgIUSpBSCCDYPOEG/IFHUCKpZ2pIAQMwCgYIKoZIzj0EAwIw +QzELMAkGA1UEBhMCQ0gxGTAXBgNVBAoMEE9JU1RFIEZvdW5kYXRpb24xGTAXBgNV +BAMMEE9JU1RFIEdTTUEgQ0kgRzEwIBcNMjQwMTE2MjMxNzM5WhgPMjA1OTAxMDcy +MzE3MzhaMEMxCzAJBgNVBAYTAkNIMRkwFwYDVQQKDBBPSVNURSBGb3VuZGF0aW9u +MRkwFwYDVQQDDBBPSVNURSBHU01BIENJIEcxMFkwEwYHKoZIzj0CAQYIKoZIzj0D +AQcDQgAEvZ3s3PFC4NgrCcCMmHJ6DJ66uzAHuLcvjJnOn+TtBNThS7YHLDyHCa2v +7D+zTP+XTtgqgcLoB56Gha9EQQQ4xKNtMGswDwYDVR0TAQH/BAUwAwEB/zAQBgNV +HREECTAHiAVghXQFDjAXBgNVHSABAf8EDTALMAkGB2eBEgECAQAwHQYDVR0OBBYE +FEwnlnrSDBSzkelgHkHmBK1XwCIvMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQD +AgNIADBFAiBVcywTj017jKpAQ+gwy4MqK2hQvzve6lkvQkgSP6ykHwIhAI0KFwCD +jnPbmcJsG41hUrWNlf+IcrMvFuYii0DasBNi +-----END CERTIFICATE----- +issuer= + organizationName = GSM Association + commonName = GSM Association - RSP2 Root CI1 +notBefore=2017-02-22 00:00:00Z +notAfter=2052-02-21 23:59:59Z +-----BEGIN CERTIFICATE----- +MIICSTCCAe+gAwIBAgIQbmhWeneg7nyF7hg5Y9+qejAKBggqhkjOPQQDAjBEMRgw +FgYDVQQKEw9HU00gQXNzb2NpYXRpb24xKDAmBgNVBAMTH0dTTSBBc3NvY2lhdGlv +biAtIFJTUDIgUm9vdCBDSTEwIBcNMTcwMjIyMDAwMDAwWhgPMjA1MjAyMjEyMzU5 +NTlaMEQxGDAWBgNVBAoTD0dTTSBBc3NvY2lhdGlvbjEoMCYGA1UEAxMfR1NNIEFz +c29jaWF0aW9uIC0gUlNQMiBSb290IENJMTBZMBMGByqGSM49AgEGCCqGSM49AwEH +A0IABJ1qutL0HCMX52GJ6/jeibsAqZfULWj/X10p/Min6seZN+hf5llovbCNuB2n +unLz+O8UD0SUCBUVo8e6n9X1TuajgcAwgb0wDgYDVR0PAQH/BAQDAgEGMA8GA1Ud +EwEB/wQFMAMBAf8wEwYDVR0RBAwwCogIKwYBBAGC6WAwFwYDVR0gAQH/BA0wCzAJ +BgdngRIBAgEAME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9nc21hLWNybC5zeW1h +dXRoLmNvbS9vZmZsaW5lY2EvZ3NtYS1yc3AyLXJvb3QtY2kxLmNybDAdBgNVHQ4E +FgQUgTcPUSXQsdQI1MOyMubSXnlb6/swCgYIKoZIzj0EAwIDSAAwRQIgIJdYsOMF +WziPK7l8nh5mu0qiRiVf25oa9ullG/OIASwCIQDqCmDrYf+GziHXBOiwJwnBaeBO +aFsiLzIEOaUuZwdNUw== +-----END CERTIFICATE----- +issuer= + countryName = US + organizationName = Entrust, Inc. + organizationalUnitName = See www.entrust.net/legal-terms + organizationalUnitName = (c) 2016 Entrust, Inc. - for authorized use only + commonName = Entrust eSIM Certification Authority +notBefore=2016-11-16 16:04:02Z +notAfter=2051-10-16 16:34:02Z +-----BEGIN CERTIFICATE----- +MIIC6DCCAo2gAwIBAgIRAIy4GT7M5nHsAAAAAFgsinowCgYIKoZIzj0EAwIwgbkx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9T +ZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAx +NiBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxLTArBgNV +BAMTJEVudHJ1c3QgZVNJTSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAgFw0xNjEx +MTYxNjA0MDJaGA8yMDUxMTAxNjE2MzQwMlowgbkxCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxLTArBgNVBAMTJEVudHJ1c3QgZVNJTSBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IA +BAdzwGHeQ1Wb2f4DmHTByR5/IWL3JugQ1U3908a++bHdlt+TTA7K4c5cYZ+51Yz/ +hg/bacxguPDh9uQUK6Wg3a6jcjBwMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgEGMBcGA1UdIAEB/wQNMAswCQYHZ4ESAQIBADAVBgNVHREEDjAMiApghkgB +hvpsFAoAMB0GA1UdDgQWBBQWcEt/NR42B/GMS3AAXDoAPf1BSjAKBggqhkjOPQQD +AgNJADBGAiEAspjXMvaBZyAg86Z0AAtT0yBRAi1EyaAfNz9kDJeAE04CIQC3efj8 +ATL7/tDBOhANy3cK8PS/1NIlu9vqMLCZsZvJ0Q== +-----END CERTIFICATE----- +issuer= + countryName = FR + organizationName = OBERTHUR TECHNOLOGIES + organizationalUnitName = TELECOM + commonName = MC4 OT ROOT CI v1 +notBefore=2016-11-15 00:00:01Z +notAfter=2046-11-08 23:59:59Z +-----BEGIN CERTIFICATE----- +MIICOjCCAeGgAwIBAgIBATAKBggqhkjOPQQDAjBbMQswCQYDVQQGEwJGUjEeMBwG +A1UEChMVT0JFUlRIVVIgVEVDSE5PTE9HSUVTMRAwDgYDVQQLEwdURUxFQ09NMRow +GAYDVQQDExFNQzQgT1QgUk9PVCBDSSB2MTAeFw0xNjExMTUwMDAwMDFaFw00NjEx +MDgyMzU5NTlaMFsxCzAJBgNVBAYTAkZSMR4wHAYDVQQKExVPQkVSVEhVUiBURUNI +Tk9MT0dJRVMxEDAOBgNVBAsTB1RFTEVDT00xGjAYBgNVBAMTEU1DNCBPVCBST09U +IENJIHYxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHb/Gajt3OZxuaDSklBQE +D4lOd6PGPLSvtfkM952ubdyy45tJwAeA0eEii0CLrFT6tcfXkW+H/5mQyMRXaAUk +T6OBlTCBkjAfBgNVHSMEGDAWgBTNbmC3LXoGPLyEYluR6A/jBAbhPjAdBgNVHQ4E +FgQUzW5gty16Bjy8hGJbkegP4wQG4T4wDgYDVR0PAQH/BAQDAgAGMBcGA1UdIAEB +/wQNMAswCQYHZ4ESAQIBADAWBgNVHREEDzANiAsrBgEEAYHvb7OITTAPBgNVHRMB +Af8EBTADAQH/MAoGCCqGSM49BAMCA0cAMEQCIEw4Nc7f2fDtoH+6ON/bknfDQxmT +ikThXjhpLtSrSKN2AiAxHxgC87L0FDnH8dJNlkdGX9c0JIx6oLheIplfS6k+jg== +-----END CERTIFICATE----- +issuer= + commonName = SubMan V4.2 CI Google Pixel + organizationName = Giesecke and Devrient GmbH + organizationalUnitName = Mobile Security + countryName = DE +notBefore=2017-05-10 00:00:00Z +notAfter=2027-05-10 00:00:00Z +-----BEGIN CERTIFICATE----- +MIICaTCCAg6gAwIBAgICASwwCgYIKoZIzj0EAwIwczElMCMGA1UEAxMcIFN1Yk1h +biBWNC4yIENJIEdvb2dsZSBQaXhlbDEjMCEGA1UEChMaR2llc2Vja2UgYW5kIERl +dnJpZW50IEdtYkgxGDAWBgNVBAsTD01vYmlsZSBTZWN1cml0eTELMAkGA1UEBhMC +REUwHhcNMTcwNTEwMDAwMDAwWhcNMjcwNTEwMDAwMDAwWjBzMSUwIwYDVQQDExwg +U3ViTWFuIFY0LjIgQ0kgR29vZ2xlIFBpeGVsMSMwIQYDVQQKExpHaWVzZWNrZSBh +bmQgRGV2cmllbnQgR21iSDEYMBYGA1UECxMPTW9iaWxlIFNlY3VyaXR5MQswCQYD +VQQGEwJERTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHNorfaJsGzqWNawyAhl +IAv9QL2/+b9RsUoso06t/dKX1MRr5CUJ51acvv5TAFhQKIml+dwLbFnV5aO+8W6Z +wxajgZEwgY4wHwYDVR0jBBgwFoAUtg8LiX/WMLiM/tYWH46oCMU4KsMwHQYDVR0O +BBYEFLYPC4l/1jC4jP7WFh+OqAjFOCrDMA4GA1UdDwEB/wQEAwIBBjAXBgNVHSAB +Af8EDTALMAkGB2eBEgECAQAwDwYDVR0TAQH/BAUwAwEB/zASBgNVHREECzAJiAcr +BgEEAdwPMAoGCCqGSM49BAMCA0kAMEYCIQDpoZcuAQrjATW8U+AWqMUJ0dY6nWW1 +R1QmFzVZ1yMXSwIhALCvRqkCtgiavdeFeSgsSNbY5Fhd+QoCltuSh1U4TE7A +-----END CERTIFICATE----- +issuer= + countryName = DE + commonName = SubMan V4.2 CI + organizationName = Giesecke and Devrient + organizationalUnitName = Mobile Security +notBefore=2016-08-12 13:51:48Z +notAfter=2026-08-12 13:51:48Z +-----BEGIN CERTIFICATE----- +MIICUjCCAfigAwIBAgIDQgAAMAoGCCqGSM49BAMCMGAxCzAJBgNVBAYTAkRFMRcw +FQYDVQQDEw5TdWJNYW4gVjQuMiBDSTEeMBwGA1UEChMVR2llc2Vja2UgYW5kIERl +dnJpZW50MRgwFgYDVQQLEw9Nb2JpbGUgU2VjdXJpdHkwHhcNMTYwODEyMTM1MTQ4 +WhcNMjYwODEyMTM1MTQ4WjBgMQswCQYDVQQGEwJERTEXMBUGA1UEAxMOU3ViTWFu +IFY0LjIgQ0kxHjAcBgNVBAoTFUdpZXNlY2tlIGFuZCBEZXZyaWVudDEYMBYGA1UE +CxMPTW9iaWxlIFNlY3VyaXR5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYIgl +VQr9wbXOlwPp8qMg5Df08Cli9Mc+lpr3Lwa9PlVA3QWlLeX4GfD4H3phLBqVIa17 +yHttmtheTxi0KoEqhKOBoDCBnTAdBgNVHQ4EFgQU6lOt7zMpuVCa/XVf1Ei4LcG8 +7P8wDgYDVR0PAQH/BAQDAgEGMBcGA1UdIAEB/wQNMAswCQYHZ4ESAQIBADAPBgNV +HRMBAf8EBTADAQH/MBIGA1UdEQQLMAmIBysGAQQB3A8wLgYDVR0fBCcwJTAjoCGg +H4YdaHR0cDovL2dpLWRlLmNvbS90ZXN0LmNybC5wZW0wCgYIKoZIzj0EAwIDSAAw +RQIhAMMx2L/VHDiOW+Fl/OuFmhCdizYM17Yn9zAVieKO2T0iAiANWtCMmY+DzkqK +yHxBFX0U2tBd682zP4DpgRt8j3Ylew== +-----END CERTIFICATE----- diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index ceb901e2f72..04cac139272 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -5,6 +5,7 @@ import fcntl import math import os +import requests import serial import subprocess import sys @@ -15,8 +16,12 @@ from contextlib import contextmanager from typing import Any +from pathlib import Path + +from openpilot.common.time_helpers import system_time_valid from openpilot.system.hardware.base import LPABase, LPAError, Profile +GSMA_CI_BUNDLE = str(Path(__file__).parent / "gsma_ci_bundle.pem") DEFAULT_DEVICE = "/dev/modem_at0" DEFAULT_BAUD = 9600 @@ -26,6 +31,7 @@ MM = "org.freedesktop.ModemManager1" MM_MODEM = MM + ".Modem" ES10X_MSS = 120 +HTTP_TIMEOUT = 30 OPEN_ISDR_RETRIES = 10 OPEN_ISDR_RETRY_DELAY_S = 0.25 OPEN_ISDR_RESET_ATTEMPT = 5 @@ -41,6 +47,11 @@ TAG_SET_NICKNAME = 0xBF29 TAG_ENABLE_PROFILE = 0xBF31 TAG_DELETE_PROFILE = 0xBF33 +TAG_LIST_NOTIFICATION = 0xBF28 +TAG_RETRIEVE_NOTIFICATION = 0xBF2B +TAG_NOTIFICATION_METADATA = 0xBF2F +TAG_NOTIFICATION_SENT = 0xBF30 +TAG_PROFILE_INSTALL_RESULT = 0xBF37 TAG_OK = 0xA0 PROFILE_OK = 0x00 @@ -53,6 +64,19 @@ PROFILE_CAT_BUSY: "catBusy", 0x06: "undefinedError", } +# SGP.22 §5.2.6 — SM-DP+ reason/subject codes mapped to user-friendly messages +ES9P_ERROR_MESSAGES: dict[tuple[str, str], str] = { + ('3.8', '8.2.6'): "This eSIM profile is already installed on another device. Please use a new QR code.", + ('3.8', '8.2.1'): "This eSIM profile has expired. Please request a new QR code.", + ('3.8', '8.1'): "The SM-DP+ server refused this request.", + ('3.1', '8.2.6'): "This eSIM profile has been revoked by the carrier.", + ('3.9', '8.2.6'): "This eSIM profile download has already been completed.", + ('2.1', '8.8'): "The device is not compatible with this eSIM profile.", + ('1.2', '8.1'): "The SM-DP+ server is temporarily unavailable. Try again later.", +} + +NOTIFICATION_OPERATIONS = {0x80: "install", 0x40: "enable", 0x20: "disable", 0x10: "delete"} + STATE_LABELS = {0: "disabled", 1: "enabled", 255: "unknown"} ICON_LABELS = {0: "jpeg", 1: "png", 255: "unknown"} CLASS_LABELS = {0: "test", 1: "provisioning", 2: "operational", 255: "unknown"} @@ -345,6 +369,15 @@ def es10x_command(client: AtClient, data: bytes) -> bytes: # --- Profile operations --- +NOTIFICATION: FieldMap = { + TAG_STATUS: ("seqNumber", lambda v: int.from_bytes(v, "big")), + 0x81: ("profileManagementOperation", + lambda v: NOTIFICATION_OPERATIONS.get(next((m for m in NOTIFICATION_OPERATIONS if len(v) >= 2 and v[1] & m), 0), "unknown")), + 0x0C: ("notificationAddress", lambda v: v.decode("utf-8", errors="ignore")), + TAG_ICCID: ("iccid", tbcd_to_string), +} + + def decode_profiles(blob: bytes) -> list[dict]: root = require_tag(blob, TAG_PROFILE_INFO_LIST, "ProfileInfoList") list_ok = find_tag(root, TAG_OK) @@ -370,6 +403,62 @@ def set_profile_nickname(client: AtClient, iccid: str, nickname: str) -> None: raise RuntimeError(f"SetNickname failed with status 0x{code:02X}") +# --- ES9P HTTP --- + +def es9p_request(smdp_address: str, endpoint: str, payload: dict, error_prefix: str = "Request", session: requests.Session | None = None) -> dict: + url = f"https://{smdp_address}/gsma/rsp2/es9plus/{endpoint}" + headers = {"User-Agent": "gsma-rsp-lpad", "X-Admin-Protocol": "gsma/rsp/v2.3.0", "Content-Type": "application/json"} + http = session or requests + resp = http.post(url, json=payload, headers=headers, timeout=HTTP_TIMEOUT, verify=GSMA_CI_BUNDLE) + resp.raise_for_status() + if not resp.content: + return {} + data = resp.json() + if "header" in data and "functionExecutionStatus" in data["header"]: + status = data["header"]["functionExecutionStatus"] + if status.get("status") == "Failed": + sd = status.get("statusCodeData", {}) + reason = sd.get("reasonCode", "unknown") + subject = sd.get("subjectCode", "unknown") + msg = ES9P_ERROR_MESSAGES.get((reason, subject), + f"{error_prefix} failed: {reason}/{subject} - {sd.get('message', 'unknown')}") + raise RuntimeError(msg) + return data + + +# --- Notifications --- + +def list_notifications(client: AtClient) -> list[dict]: + response = es10x_command(client, encode_tlv(TAG_LIST_NOTIFICATION, b"")) + root = require_tag(response, TAG_LIST_NOTIFICATION, "ListNotificationResponse") + metadata_list = find_tag(root, TAG_OK) + if metadata_list is None: + return [] + return [decode_struct(value, NOTIFICATION) for tag, value in iter_tlv(metadata_list) if tag == TAG_NOTIFICATION_METADATA] + + +def process_notifications(client: AtClient) -> None: + for notification in list_notifications(client): + seq_number, smdp_address = notification["seqNumber"], notification["notificationAddress"] + try: + request = encode_tlv(TAG_RETRIEVE_NOTIFICATION, encode_tlv(TAG_OK, encode_tlv(TAG_STATUS, int_bytes(seq_number)))) + response = es10x_command(client, request) + content = require_tag(require_tag(response, TAG_RETRIEVE_NOTIFICATION, "RetrieveNotificationsListResponse"), + TAG_OK, "RetrieveNotificationsListResponse") + pending_notif = next((v for t, v in iter_tlv(content) if t in (TAG_PROFILE_INSTALL_RESULT, 0x30)), None) + if pending_notif is None: + raise RuntimeError("Missing PendingNotification") + + es9p_request(smdp_address, "handleNotification", {"pendingNotification": b64e(pending_notif)}, "HandleNotification") + + response = es10x_command(client, encode_tlv(TAG_NOTIFICATION_SENT, encode_tlv(TAG_STATUS, int_bytes(seq_number)))) + root = require_tag(response, TAG_NOTIFICATION_SENT, "NotificationSentResponse") + if int.from_bytes(require_tag(root, TAG_STATUS, "RemoveNotificationFromList status"), "big") != 0: + raise RuntimeError("RemoveNotificationFromList failed") + except Exception as e: + print(f"notification {seq_number} failed: {e}", file=sys.stderr) + + class TiciLPA(LPABase): def __init__(self): if hasattr(self, '_client'): @@ -409,6 +498,12 @@ def list_profiles(self) -> list[Profile]: def get_active_profile(self) -> Profile | None: return None + def process_notifications(self) -> None: + if not system_time_valid(): + raise RuntimeError("System time is not set; TLS certificate validation requires a valid clock") + with self._acquire_channel(): + process_notifications(self._client) + def delete_profile(self, iccid: str) -> None: if self.is_comma_profile(iccid): raise LPAError("refusing to delete a comma profile") From 97f1bac71d491b571e4588dfa481d5cb03e54355 Mon Sep 17 00:00:00 2001 From: commaci-public <60409688+commaci-public@users.noreply.github.com> Date: Wed, 15 Apr 2026 09:54:46 -0700 Subject: [PATCH 641/685] [bot] Update Python packages (#37815) Update Python packages Co-authored-by: Vehicle Researcher --- opendbc_repo | 2 +- panda | 2 +- tinygrad_repo | 2 +- uv.lock | 122 +++++++++++++++++++++++++------------------------- 4 files changed, 64 insertions(+), 64 deletions(-) diff --git a/opendbc_repo b/opendbc_repo index 37c8ae0c2d6..6123dde9f67 160000 --- a/opendbc_repo +++ b/opendbc_repo @@ -1 +1 @@ -Subproject commit 37c8ae0c2d6830b70d11e1a4e849721cedfe3f03 +Subproject commit 6123dde9f67259aa3c8b1fa962f242f522bdd5a4 diff --git a/panda b/panda index 18f37937cc0..ac89f0c92d0 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 18f37937cc0edb0151d17ee7d15a142e88ec3a5b +Subproject commit ac89f0c92d02405c66498b8e3086a310c547e7ea diff --git a/tinygrad_repo b/tinygrad_repo index 4cf2759fc8a..7cbfa1896ae 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 4cf2759fc8a3759abea89fa2805cce1f788d4367 +Subproject commit 7cbfa1896aebd6e1210d626db239fa54ba6d802c diff --git a/uv.lock b/uv.lock index 7ecb51f5d12..cde094a85c9 100644 --- a/uv.lock +++ b/uv.lock @@ -116,12 +116,12 @@ wheels = [ [[package]] name = "bzip2" version = "1.0.8" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#1ddfd3eb7b9e30a957c263930e1b0660e5dce6d1" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=bzip2&rev=release-bzip2#13755b73dbcda1b186641fcccce90d55f815d6bc" } [[package]] name = "capnproto" version = "1.0.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#6e99db11a1dc5dfa74be40d1e0666ebe10c8e0d7" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=capnproto&rev=release-capnproto#eba2fe8b8208b5408fbda1bc0104a91e4375aee3" } [[package]] name = "casadi" @@ -371,7 +371,7 @@ wheels = [ [[package]] name = "eigen" version = "3.4.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#891c42d8029b2a633f3aca7f60cc7aa4b5305405" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=eigen&rev=release-eigen#9157467a9e343d876e85f6187eae8c974fe3d83f" } [[package]] name = "execnet" @@ -385,7 +385,7 @@ wheels = [ [[package]] name = "ffmpeg" version = "7.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#8261317427e81a0fa1f53a7ef77f15004ec78889" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ffmpeg&rev=release-ffmpeg#4be3ad687902199df76b78cc8cf07f61e69ec266" } [[package]] name = "fonttools" @@ -432,7 +432,7 @@ wheels = [ [[package]] name = "gcc-arm-none-eabi" version = "13.2.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#fd995de677db114e2862cf4ed245ca9a17536668" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#0e1ae2548977f6cd78c51d4d0c16ebd1863241b8" } [[package]] name = "ghp-import" @@ -449,7 +449,7 @@ wheels = [ [[package]] name = "git-lfs" version = "3.6.1" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#9fdbe7eb0257d7a13851ed4baa52fbccbe7e2e9d" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=git-lfs&rev=release-git-lfs#ab3064b6e7df110e32aa7748689cb43b26f07b54" } [[package]] name = "google-crc32c" @@ -498,7 +498,7 @@ wheels = [ [[package]] name = "imgui" version = "1.92.7" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui#f3d874be2f3aa44869ffd4775e0957e986a30a68" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=imgui&rev=release-imgui#58d66087adacabb2bb4e56e74ebdea7d55c78e34" } [[package]] name = "iniconfig" @@ -578,12 +578,12 @@ wheels = [ [[package]] name = "libjpeg" version = "3.1.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#d90bc630661092de49428bfc3a82a371ee35a889" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libjpeg&rev=release-libjpeg#71f7a3f2aaccdc0612d93fac858b78f35bc2a565" } [[package]] name = "libusb" version = "1.0.29" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#6562b0138726a380368d68a6ac5f6e36d6aea2da" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libusb&rev=release-libusb#222120c19c857d6d0a681aff2e335c829ffcf89c" } [[package]] name = "libusb1" @@ -599,7 +599,7 @@ wheels = [ [[package]] name = "libyuv" version = "1922.0" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#22b976c39a3f2607ef5458056b1a10558da0e85f" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv#febc42742ebf25429575caf784adecc6e516b892" } [[package]] name = "markdown" @@ -751,7 +751,7 @@ wheels = [ [[package]] name = "ncurses" version = "6.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#b733e08a93873e8d8ac47caabc2eb64a425f7146" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses#e78a693655261b101325aaa5b3cd9f1eb35f496b" } [[package]] name = "numpy" @@ -940,11 +940,11 @@ provides-extras = ["docs", "testing", "dev", "tools"] [[package]] name = "packaging" -version = "26.0" +version = "26.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, ] [[package]] @@ -1016,11 +1016,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.9.4" +version = "4.9.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, + { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, ] [[package]] @@ -1411,27 +1411,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.15.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/97/e9f1ca355108ef7194e38c812ef40ba98c7208f47b13ad78d023caa583da/ruff-0.15.9.tar.gz", hash = "sha256:29cbb1255a9797903f6dde5ba0188c707907ff44a9006eb273b5a17bfa0739a2", size = 4617361, upload-time = "2026-04-02T18:17:20.829Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/1f/9cdfd0ac4b9d1e5a6cf09bedabdf0b56306ab5e333c85c87281273e7b041/ruff-0.15.9-py3-none-linux_armv6l.whl", hash = "sha256:6efbe303983441c51975c243e26dff328aca11f94b70992f35b093c2e71801e1", size = 10511206, upload-time = "2026-04-02T18:16:41.574Z" }, - { url = "https://files.pythonhosted.org/packages/3d/f6/32bfe3e9c136b35f02e489778d94384118bb80fd92c6d92e7ccd97db12ce/ruff-0.15.9-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:4965bac6ac9ea86772f4e23587746f0b7a395eccabb823eb8bfacc3fa06069f7", size = 10923307, upload-time = "2026-04-02T18:17:08.645Z" }, - { url = "https://files.pythonhosted.org/packages/ca/25/de55f52ab5535d12e7aaba1de37a84be6179fb20bddcbe71ec091b4a3243/ruff-0.15.9-py3-none-macosx_11_0_arm64.whl", hash = "sha256:eaf05aad70ca5b5a0a4b0e080df3a6b699803916d88f006efd1f5b46302daab8", size = 10316722, upload-time = "2026-04-02T18:16:44.206Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/690d75f3fd6278fe55fff7c9eb429c92d207e14b25d1cae4064a32677029/ruff-0.15.9-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9439a342adb8725f32f92732e2bafb6d5246bd7a5021101166b223d312e8fc59", size = 10623674, upload-time = "2026-04-02T18:16:50.951Z" }, - { url = "https://files.pythonhosted.org/packages/bd/ec/176f6987be248fc5404199255522f57af1b4a5a1b57727e942479fec98ad/ruff-0.15.9-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c5e6faf9d97c8edc43877c3f406f47446fc48c40e1442d58cfcdaba2acea745", size = 10351516, upload-time = "2026-04-02T18:16:57.206Z" }, - { url = "https://files.pythonhosted.org/packages/b2/fc/51cffbd2b3f240accc380171d51446a32aa2ea43a40d4a45ada67368fbd2/ruff-0.15.9-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b34a9766aeec27a222373d0b055722900fbc0582b24f39661aa96f3fe6ad901", size = 11150202, upload-time = "2026-04-02T18:17:06.452Z" }, - { url = "https://files.pythonhosted.org/packages/d6/d4/25292a6dfc125f6b6528fe6af31f5e996e19bf73ca8e3ce6eb7fa5b95885/ruff-0.15.9-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89dd695bc72ae76ff484ae54b7e8b0f6b50f49046e198355e44ea656e521fef9", size = 11988891, upload-time = "2026-04-02T18:17:18.575Z" }, - { url = "https://files.pythonhosted.org/packages/13/e1/1eebcb885c10e19f969dcb93d8413dfee8172578709d7ee933640f5e7147/ruff-0.15.9-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ce187224ef1de1bd225bc9a152ac7102a6171107f026e81f317e4257052916d5", size = 11480576, upload-time = "2026-04-02T18:16:52.986Z" }, - { url = "https://files.pythonhosted.org/packages/ff/6b/a1548ac378a78332a4c3dcf4a134c2475a36d2a22ddfa272acd574140b50/ruff-0.15.9-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b0c7c341f68adb01c488c3b7d4b49aa8ea97409eae6462d860a79cf55f431b6", size = 11254525, upload-time = "2026-04-02T18:17:02.041Z" }, - { url = "https://files.pythonhosted.org/packages/42/aa/4bb3af8e61acd9b1281db2ab77e8b2c3c5e5599bf2a29d4a942f1c62b8d6/ruff-0.15.9-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:55cc15eee27dc0eebdfcb0d185a6153420efbedc15eb1d38fe5e685657b0f840", size = 11204072, upload-time = "2026-04-02T18:17:13.581Z" }, - { url = "https://files.pythonhosted.org/packages/69/48/d550dc2aa6e423ea0bcc1d0ff0699325ffe8a811e2dba156bd80750b86dc/ruff-0.15.9-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6537f6eed5cda688c81073d46ffdfb962a5f29ecb6f7e770b2dc920598997ed", size = 10594998, upload-time = "2026-04-02T18:16:46.369Z" }, - { url = "https://files.pythonhosted.org/packages/63/47/321167e17f5344ed5ec6b0aa2cff64efef5f9e985af8f5622cfa6536043f/ruff-0.15.9-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6d3fcbca7388b066139c523bda744c822258ebdcfbba7d24410c3f454cc9af71", size = 10359769, upload-time = "2026-04-02T18:17:10.994Z" }, - { url = "https://files.pythonhosted.org/packages/67/5e/074f00b9785d1d2c6f8c22a21e023d0c2c1817838cfca4c8243200a1fa87/ruff-0.15.9-py3-none-musllinux_1_2_i686.whl", hash = "sha256:058d8e99e1bfe79d8a0def0b481c56059ee6716214f7e425d8e737e412d69677", size = 10850236, upload-time = "2026-04-02T18:16:48.749Z" }, - { url = "https://files.pythonhosted.org/packages/76/37/804c4135a2a2caf042925d30d5f68181bdbd4461fd0d7739da28305df593/ruff-0.15.9-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8e1ddb11dbd61d5983fa2d7d6370ef3eb210951e443cace19594c01c72abab4c", size = 11358343, upload-time = "2026-04-02T18:16:55.068Z" }, - { url = "https://files.pythonhosted.org/packages/88/3d/1364fcde8656962782aa9ea93c92d98682b1ecec2f184e625a965ad3b4a6/ruff-0.15.9-py3-none-win32.whl", hash = "sha256:bde6ff36eaf72b700f32b7196088970bf8fdb2b917b7accd8c371bfc0fd573ec", size = 10583382, upload-time = "2026-04-02T18:17:04.261Z" }, - { url = "https://files.pythonhosted.org/packages/4c/56/5c7084299bd2cacaa07ae63a91c6f4ba66edc08bf28f356b24f6b717c799/ruff-0.15.9-py3-none-win_amd64.whl", hash = "sha256:45a70921b80e1c10cf0b734ef09421f71b5aa11d27404edc89d7e8a69505e43d", size = 11744969, upload-time = "2026-04-02T18:16:59.611Z" }, - { url = "https://files.pythonhosted.org/packages/03/36/76704c4f312257d6dbaae3c959add2a622f63fcca9d864659ce6d8d97d3d/ruff-0.15.9-py3-none-win_arm64.whl", hash = "sha256:0694e601c028fd97dc5c6ee244675bc241aeefced7ef80cd9c6935a871078f53", size = 11005870, upload-time = "2026-04-02T18:17:15.773Z" }, +version = "0.15.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/d9/aa3f7d59a10ef6b14fe3431706f854dbf03c5976be614a9796d36326810c/ruff-0.15.10.tar.gz", hash = "sha256:d1f86e67ebfdef88e00faefa1552b5e510e1d35f3be7d423dc7e84e63788c94e", size = 4631728, upload-time = "2026-04-09T14:06:09.884Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/eb/00/a1c2fdc9939b2c03691edbda290afcd297f1f389196172826b03d6b6a595/ruff-0.15.10-py3-none-linux_armv6l.whl", hash = "sha256:0744e31482f8f7d0d10a11fcbf897af272fefdfcb10f5af907b18c2813ff4d5f", size = 10563362, upload-time = "2026-04-09T14:06:21.189Z" }, + { url = "https://files.pythonhosted.org/packages/5c/15/006990029aea0bebe9d33c73c3e28c80c391ebdba408d1b08496f00d422d/ruff-0.15.10-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b1e7c16ea0ff5a53b7c2df52d947e685973049be1cdfe2b59a9c43601897b22e", size = 10951122, upload-time = "2026-04-09T14:06:02.236Z" }, + { url = "https://files.pythonhosted.org/packages/f2/c0/4ac978fe874d0618c7da647862afe697b281c2806f13ce904ad652fa87e4/ruff-0.15.10-py3-none-macosx_11_0_arm64.whl", hash = "sha256:93cc06a19e5155b4441dd72808fdf84290d84ad8a39ca3b0f994363ade4cebb1", size = 10314005, upload-time = "2026-04-09T14:06:00.026Z" }, + { url = "https://files.pythonhosted.org/packages/da/73/c209138a5c98c0d321266372fc4e33ad43d506d7e5dd817dd89b60a8548f/ruff-0.15.10-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e1dd04312997c99ea6965df66a14fb4f03ba978564574ffc68b0d61fd3989e", size = 10643450, upload-time = "2026-04-09T14:05:42.137Z" }, + { url = "https://files.pythonhosted.org/packages/ec/76/0deec355d8ec10709653635b1f90856735302cb8e149acfdf6f82a5feb70/ruff-0.15.10-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8154d43684e4333360fedd11aaa40b1b08a4e37d8ffa9d95fee6fa5b37b6fab1", size = 10379597, upload-time = "2026-04-09T14:05:49.984Z" }, + { url = "https://files.pythonhosted.org/packages/dc/be/86bba8fc8798c081e28a4b3bb6d143ccad3fd5f6f024f02002b8f08a9fa3/ruff-0.15.10-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ab88715f3a6deb6bde6c227f3a123410bec7b855c3ae331b4c006189e895cef", size = 11146645, upload-time = "2026-04-09T14:06:12.246Z" }, + { url = "https://files.pythonhosted.org/packages/a8/89/140025e65911b281c57be1d385ba1d932c2366ca88ae6663685aed8d4881/ruff-0.15.10-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a768ff5969b4f44c349d48edf4ab4f91eddb27fd9d77799598e130fb628aa158", size = 12030289, upload-time = "2026-04-09T14:06:04.776Z" }, + { url = "https://files.pythonhosted.org/packages/88/de/ddacca9545a5e01332567db01d44bd8cf725f2db3b3d61a80550b48308ea/ruff-0.15.10-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ee3ef42dab7078bda5ff6a1bcba8539e9857deb447132ad5566a038674540d0", size = 11496266, upload-time = "2026-04-09T14:05:55.485Z" }, + { url = "https://files.pythonhosted.org/packages/bc/bb/7ddb00a83760ff4a83c4e2fc231fd63937cc7317c10c82f583302e0f6586/ruff-0.15.10-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51cb8cc943e891ba99989dd92d61e29b1d231e14811db9be6440ecf25d5c1609", size = 11256418, upload-time = "2026-04-09T14:05:57.69Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/55de0d35aacf6cd50b6ee91ee0f291672080021896543776f4170fc5c454/ruff-0.15.10-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:e59c9bdc056a320fb9ea1700a8d591718b8faf78af065484e801258d3a76bc3f", size = 11288416, upload-time = "2026-04-09T14:05:44.695Z" }, + { url = "https://files.pythonhosted.org/packages/68/cf/9438b1a27426ec46a80e0a718093c7f958ef72f43eb3111862949ead3cc1/ruff-0.15.10-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:136c00ca2f47b0018b073f28cb5c1506642a830ea941a60354b0e8bc8076b151", size = 10621053, upload-time = "2026-04-09T14:05:52.782Z" }, + { url = "https://files.pythonhosted.org/packages/4c/50/e29be6e2c135e9cd4cb15fbade49d6a2717e009dff3766dd080fcb82e251/ruff-0.15.10-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8b80a2f3c9c8a950d6237f2ca12b206bccff626139be9fa005f14feb881a1ae8", size = 10378302, upload-time = "2026-04-09T14:06:14.361Z" }, + { url = "https://files.pythonhosted.org/packages/18/2f/e0b36a6f99c51bb89f3a30239bc7bf97e87a37ae80aa2d6542d6e5150364/ruff-0.15.10-py3-none-musllinux_1_2_i686.whl", hash = "sha256:e3e53c588164dc025b671c9df2462429d60357ea91af7e92e9d56c565a9f1b07", size = 10850074, upload-time = "2026-04-09T14:06:16.581Z" }, + { url = "https://files.pythonhosted.org/packages/11/08/874da392558ce087a0f9b709dc6ec0d60cbc694c1c772dab8d5f31efe8cb/ruff-0.15.10-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b0c52744cf9f143a393e284125d2576140b68264a93c6716464e129a3e9adb48", size = 11358051, upload-time = "2026-04-09T14:06:18.948Z" }, + { url = "https://files.pythonhosted.org/packages/e4/46/602938f030adfa043e67112b73821024dc79f3ab4df5474c25fa4c1d2d14/ruff-0.15.10-py3-none-win32.whl", hash = "sha256:d4272e87e801e9a27a2e8df7b21011c909d9ddd82f4f3281d269b6ba19789ca5", size = 10588964, upload-time = "2026-04-09T14:06:07.14Z" }, + { url = "https://files.pythonhosted.org/packages/25/b6/261225b875d7a13b33a6d02508c39c28450b2041bb01d0f7f1a83d569512/ruff-0.15.10-py3-none-win_amd64.whl", hash = "sha256:28cb32d53203242d403d819fd6983152489b12e4a3ae44993543d6fe62ab42ed", size = 11745044, upload-time = "2026-04-09T14:05:39.473Z" }, + { url = "https://files.pythonhosted.org/packages/58/ed/dea90a65b7d9e69888890fb14c90d7f51bf0c1e82ad800aeb0160e4bacfd/ruff-0.15.10-py3-none-win_arm64.whl", hash = "sha256:601d1610a9e1f1c2165a4f561eeaa2e2ea1e97f3287c5aa258d3dab8b57c6188", size = 11035607, upload-time = "2026-04-09T14:05:47.593Z" }, ] [[package]] @@ -1445,15 +1445,15 @@ wheels = [ [[package]] name = "sentry-sdk" -version = "2.57.0" +version = "2.58.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4f/87/46c0406d8b5ddd026f73adaf5ab75ce144219c41a4830b52df4b9ab55f7f/sentry_sdk-2.57.0.tar.gz", hash = "sha256:4be8d1e71c32fb27f79c577a337ac8912137bba4bcbc64a4ec1da4d6d8dc5199", size = 435288, upload-time = "2026-03-31T09:39:29.264Z" } +sdist = { url = "https://files.pythonhosted.org/packages/26/b3/fb8291170d0e844173164709fc0fa0c221ed75a5da740c8746f2a83b4eb1/sentry_sdk-2.58.0.tar.gz", hash = "sha256:c1144d947352d54e5b7daa63596d9f848adf684989c06c4f5a659f0c85a18f6f", size = 438764, upload-time = "2026-04-13T17:23:26.265Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c9/64/982e07b93219cb52e1cca5d272cb579e2f3eb001956c9e7a9a6d106c9473/sentry_sdk-2.57.0-py2.py3-none-any.whl", hash = "sha256:812c8bf5ff3d2f0e89c82f5ce80ab3a6423e102729c4706af7413fd1eb480585", size = 456489, upload-time = "2026-03-31T09:39:27.524Z" }, + { url = "https://files.pythonhosted.org/packages/fa/eb/d875669993b762556ae8b2efd86219943b4c0864d22204d622a9aee3052b/sentry_sdk-2.58.0-py2.py3-none-any.whl", hash = "sha256:688d1c704ddecf382ea3326f21a67453d4caa95592d722b7c780a36a9d23109e", size = 460919, upload-time = "2026-04-13T17:23:24.675Z" }, ] [[package]] @@ -1549,26 +1549,26 @@ wheels = [ [[package]] name = "ty" -version = "0.0.29" +version = "0.0.31" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d5/853561de49fae38c519e905b2d8da9c531219608f1fccc47a0fc2c896980/ty-0.0.29.tar.gz", hash = "sha256:e7936cca2f691eeda631876c92809688dbbab68687c3473f526cd83b6a9228d8", size = 5469221, upload-time = "2026-04-05T15:01:21.328Z" } +sdist = { url = "https://files.pythonhosted.org/packages/31/cc/5ea5d3a72216c8c2bf77d83066dd4f3553532d0aacc03d4a8397dd9845e1/ty-0.0.31.tar.gz", hash = "sha256:4a4094292d9671caf3b510c7edf36991acd9c962bb5d97205374ffed9f541c45", size = 5516619, upload-time = "2026-04-15T15:47:59.87Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/b7/911f9962115acfa24e3b2ec9d4992dd994c38e8769e1b1d7680bb4d28a51/ty-0.0.29-py3-none-linux_armv6l.whl", hash = "sha256:b8a40955f7660d3eaceb0d964affc81b790c0765e7052921a5f861ff8a471c30", size = 10568206, upload-time = "2026-04-05T15:01:19.165Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c3/fcae2167d4c77a97269f92f11d1b43b03617f81de1283d5d05b43432110c/ty-0.0.29-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6b6849adae15b00bbe2d3c5b078967dcb62eba37d38936b8eeb4c81a82d2e3b8", size = 10442530, upload-time = "2026-04-05T15:01:28.471Z" }, - { url = "https://files.pythonhosted.org/packages/97/33/5a6bfa240cfcb9c36046ae2459fa9ea23238d20130d8656ff5ac4d6c012a/ty-0.0.29-py3-none-macosx_11_0_arm64.whl", hash = "sha256:dcdd9b17209788152f7b7ea815eda07989152325052fe690013537cc7904ce49", size = 9915735, upload-time = "2026-04-05T15:01:10.365Z" }, - { url = "https://files.pythonhosted.org/packages/b3/1e/318f45fae232118e81a6306c30f50de42c509c412128d5bd231eab699ffb/ty-0.0.29-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d8ed4789bae78ffaf94462c0d25589a734cab0366b86f2bbcb1bb90e1a7a169", size = 10419748, upload-time = "2026-04-05T15:01:32.375Z" }, - { url = "https://files.pythonhosted.org/packages/a9/a8/5687872e2ab5a0f7dd4fd8456eac31e9381ad4dc74961f6f29965ad4dd91/ty-0.0.29-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:91ec374b8565e0ad0900011c24641ebbef2da51adbd4fb69ff3280c8a7eceb02", size = 10394738, upload-time = "2026-04-05T15:01:06.473Z" }, - { url = "https://files.pythonhosted.org/packages/de/68/015d118097eeb95e6a44c4abce4c0a28b7b9dfb3085b7f0ee48e4f099633/ty-0.0.29-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:298a8d5faa2502d3810bbbb47a030b9455495b9921594206043c785dd61548cf", size = 10910613, upload-time = "2026-04-05T15:01:17.17Z" }, - { url = "https://files.pythonhosted.org/packages/1c/01/47ce3c6c53e0670eadbe80756b167bf80ed6681d1ba57cfde2e8065a13d1/ty-0.0.29-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3c8fba1a3524c6109d1e020d92301c79d41bf442fa8d335b9fa366239339cb70", size = 11475750, upload-time = "2026-04-05T15:01:30.461Z" }, - { url = "https://files.pythonhosted.org/packages/c4/cf/e361845b1081c9264ad5b7c963231bab03f2666865a9f2a115c4233f2137/ty-0.0.29-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c48adf88a70d264128c39ee922ed14a947817fced1e93c08c1a89c9244edcde", size = 11190055, upload-time = "2026-04-05T15:01:12.369Z" }, - { url = "https://files.pythonhosted.org/packages/79/12/0fb0857e9a62cb11586e9a712103877bbf717f5fb570d16634408cfdefee/ty-0.0.29-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ce0a7a0e96bc7b42518cd3a1a6a6298ef64ff40ca4614355c1aa807059b5c6f", size = 11020539, upload-time = "2026-04-05T15:01:37.022Z" }, - { url = "https://files.pythonhosted.org/packages/20/36/5a26753802083f80cd125db6c4348ad42b3c982ec36e718e0bf4c18f75e5/ty-0.0.29-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a6ac86a05b4a3731d45365ab97780acc7b8146fa62fccb3cbe94fe6546c67a97", size = 10396399, upload-time = "2026-04-05T15:01:26.167Z" }, - { url = "https://files.pythonhosted.org/packages/00/e6/b4e75b5752239ab3ab400f19faef4dbef81d05aab5d3419fda0c062a3765/ty-0.0.29-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:6bbbf53141af0f3150bf288d716263f1a3550054e4b3551ca866d38192ba9891", size = 10421461, upload-time = "2026-04-05T15:01:08.367Z" }, - { url = "https://files.pythonhosted.org/packages/c0/21/1084b5b609f9abed62070ec0b31c283a403832a6310c8bbc208bd45ee1e6/ty-0.0.29-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1c9e06b770c1d0ff5efc51e34312390db31d53fcf3088163f413030b42b74f84", size = 10599187, upload-time = "2026-04-05T15:01:23.52Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a1/ce19a2ca717bbcc1ee11378aba52ef70b6ce5b87245162a729d9fdc2360f/ty-0.0.29-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:0307fe37e3f000ef1a4ae230bbaf511508a78d24a5e51b40902a21b09d5e6037", size = 11121198, upload-time = "2026-04-05T15:01:15.22Z" }, - { url = "https://files.pythonhosted.org/packages/6b/6b/f1430b279af704321566ce7ec2725d3d8258c2f815ebd93e474c64cd4543/ty-0.0.29-py3-none-win32.whl", hash = "sha256:7a2a898217960a825f8bc0087e1fdbaf379606175e98f9807187221d53a4a8ed", size = 9995331, upload-time = "2026-04-05T15:01:01.32Z" }, - { url = "https://files.pythonhosted.org/packages/d2/ef/3ef01c17785ff9a69378465c7d0faccd48a07b163554db0995e5d65a5a23/ty-0.0.29-py3-none-win_amd64.whl", hash = "sha256:fc1294200226b91615acbf34e0a9ad81caf98c081e9c6a912a31b0a7b603bc3f", size = 11023644, upload-time = "2026-04-05T15:01:04.432Z" }, - { url = "https://files.pythonhosted.org/packages/2c/55/87280a994d6a2d2647c65e12abbc997ed49835794366153c04c4d9304d76/ty-0.0.29-py3-none-win_arm64.whl", hash = "sha256:f9794bbd1bb3ce13f78c191d0c89ae4c63f52c12b6daa0c6fe220b90d019d12c", size = 10428165, upload-time = "2026-04-05T15:01:34.665Z" }, + { url = "https://files.pythonhosted.org/packages/b0/10/ea805cbbd75d5d50792551a2b383de8521eeab0c44f38c73e12819ced65e/ty-0.0.31-py3-none-linux_armv6l.whl", hash = "sha256:761651dc17ad7bc0abfc1b04b3f0e84df263ed435d34f29760b3da739ab02d35", size = 10834749, upload-time = "2026-04-15T15:48:14.877Z" }, + { url = "https://files.pythonhosted.org/packages/d9/4c/fabf951850401d24d36b21bced088a366c6827e1c37dab4523afff84c4b2/ty-0.0.31-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c529922395a07231c27488f0290651e05d27d149f7e0aa807678f1f7e9c58a5e", size = 10626012, upload-time = "2026-04-15T15:48:22.554Z" }, + { url = "https://files.pythonhosted.org/packages/04/b0/4a5aff88d2544f19514a59c8f693d63144aa7307fe2ee5df608333ab5460/ty-0.0.31-py3-none-macosx_11_0_arm64.whl", hash = "sha256:5f345df2b87d747859e72c2cbc9be607ea1bbc8bc93dd32fa3d03ea091cb4fee", size = 10075790, upload-time = "2026-04-15T15:47:46.959Z" }, + { url = "https://files.pythonhosted.org/packages/d5/73/9d4dcad12cd4e85274014f2c0510ef93f590b2a1e5148de3a9f276098dad/ty-0.0.31-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4b207eddcfbafd376132689d3435b14efcb531289cb59cd961c6a611133bd54", size = 10590286, upload-time = "2026-04-15T15:48:06.222Z" }, + { url = "https://files.pythonhosted.org/packages/47/45/fe40adde18692359ded174ae7ddbfac056e876eb0f43b65be74fde7f6072/ty-0.0.31-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:663778b220f357067488ce68bfc52335ccbd161549776f70dcbde6bbde82f77a", size = 10623824, upload-time = "2026-04-15T15:48:12.965Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e8/0ffa2e09b548e6daa9ebc368d68b767dc2405ca4cbeadb7ede0e2cb21059/ty-0.0.31-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3506cfe87dfade0fb2960dd4fffd4fd8089003587b3445c0a1a295c9d83764fb", size = 11156864, upload-time = "2026-04-15T15:48:08.473Z" }, + { url = "https://files.pythonhosted.org/packages/08/e9/fd44c2075115d569593ee9473d7e2a38b750fd7e783421c95eb528c15df5/ty-0.0.31-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b3f3d8492f08e81916026354c1d1599e9ddfa1241804141a74d5662fc710085", size = 11696401, upload-time = "2026-04-15T15:48:17.355Z" }, + { url = "https://files.pythonhosted.org/packages/4e/50/35aad8eadf964d23e2a4faa5b38a206aa85c78833c8ce335dddd2c34ba63/ty-0.0.31-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a97de32ee6a619393a4c495e056a1c547de7877510f3152e61345c71d774d2d0", size = 11374903, upload-time = "2026-04-15T15:47:55.893Z" }, + { url = "https://files.pythonhosted.org/packages/c8/37/01eccd25d23f5aaa7f7ff1a87b5b215469f6b202cf689a1812b71c1e7f6b/ty-0.0.31-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c906354ce441e342646582bc9b8f48a676f79f3d061e25de15ff870e015ca14e", size = 11206624, upload-time = "2026-04-15T15:47:51.778Z" }, + { url = "https://files.pythonhosted.org/packages/f4/70/baad2914cb097453f127a221f8addb2b41926098059cd773c75e6a662fc4/ty-0.0.31-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:275bb7c82afcbf89fe2dbef1b2692f2bc98451f1ee2c8eb809ddd91317822388", size = 10575089, upload-time = "2026-04-15T15:47:49.448Z" }, + { url = "https://files.pythonhosted.org/packages/83/12/bae3a7bba2e785eb72ce00f9da70eedcb8c5e8299efecbd16e6e436abd82/ty-0.0.31-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:405da247027c6efd1e264886b6ac4a86ab3a4f09200b02e33630efe85f119e53", size = 10642315, upload-time = "2026-04-15T15:48:19.661Z" }, + { url = "https://files.pythonhosted.org/packages/93/9e/cad04d5d839bc60355cea98c7e09d724ea65f47184def0fae8b90dc54591/ty-0.0.31-py3-none-musllinux_1_2_i686.whl", hash = "sha256:54d9835608eed196853d6643f645c50ce83bcc7fe546cdb3e210c1bcf7c58c09", size = 10834473, upload-time = "2026-04-15T15:48:02.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/ba/84112d280182d37690d3d2b4018b2667e42bc281585e607015635310016a/ty-0.0.31-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5ee11be9b07e8c0c6b455ff075a0abe4f194de9476f57624db98eec9df618355", size = 11315785, upload-time = "2026-04-15T15:48:10.754Z" }, + { url = "https://files.pythonhosted.org/packages/50/9f/ac42dc223d7e0950e97a1854567a8b3e7fe09ad7375adbf91bfb43290482/ty-0.0.31-py3-none-win32.whl", hash = "sha256:7286587aacf3eef0956062d6492b893b02f82b0f22c5e230008e13ff0d216a8b", size = 10187657, upload-time = "2026-04-15T15:48:04.264Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/57ba7ea7ecb2f4751644ba91756e2be70e33ef5952c0c41a256a0e4c2437/ty-0.0.31-py3-none-win_amd64.whl", hash = "sha256:81134e25d2a2562ab372f24de8f9bd05034d27d30377a5d7540f259791c6234c", size = 11205258, upload-time = "2026-04-15T15:47:53.759Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/bca669095ccf0a400af941fdf741578d4c2d6719f1b7f10e6dbec10aa862/ty-0.0.31-py3-none-win_arm64.whl", hash = "sha256:e9cb15fad26545c6a608f40f227af3a5513cb376998ca6feddd47ca7d93ffafa", size = 10590392, upload-time = "2026-04-15T15:47:57.968Z" }, ] [[package]] @@ -1672,7 +1672,7 @@ wheels = [ [[package]] name = "zeromq" version = "4.3.5" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#250faf500a3d101b91f4c85a4618fe1882c9cf61" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq#173fe8e9a0b8cf666bac5363c3376e866a386568" } [[package]] name = "zstandard" @@ -1702,4 +1702,4 @@ wheels = [ [[package]] name = "zstd" version = "1.5.6" -source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#6896f3e5ea22d632c5ea3bc6e5f3b773c144f43b" } +source = { git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd#c4b1fdec74010075965d68e2c743055c6ef18d48" } From 63ab2fb1b369a01776f051c98decbc87c6723dd6 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 15 Apr 2026 10:15:32 -0700 Subject: [PATCH 642/685] four: calibrate DM orientation (#37149) * calib face * clean up * fix double neg * cmt * local changes * keep for dm preview for now * comments * comments * Update selfdrive/ui/mici/onroad/driver_state.py * Update selfdrive/ui/mici/onroad/driver_state.py --- .../onroad/driver_monitoring/dm_center.png | 3 -- selfdrive/ui/mici/onroad/driver_state.py | 33 +++++++++---------- 2 files changed, 16 insertions(+), 20 deletions(-) delete mode 100644 selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png diff --git a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png b/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png deleted file mode 100644 index a8a68b372c2..00000000000 --- a/selfdrive/assets/icons_mici/onroad/driver_monitoring/dm_center.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b5aee9f6cec03f1967014cd2ea2a23982b262e7d86dadca602ecfa8875b38101 -size 5875 diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index 92ff07c1e9b..b2be5a8e340 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -6,11 +6,13 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.monitoring.helpers import face_orientation_from_net AlertSize = log.SelfdriveState.AlertSize DEBUG = False +# TODO: Only left for DM preview, remove LOOKING_CENTER_THRESHOLD_UPPER = math.radians(6) LOOKING_CENTER_THRESHOLD_LOWER = math.radians(3) @@ -59,8 +61,6 @@ def load_icons(self): self._dm_person = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_person.png", cone_and_person_size, cone_and_person_size) self._dm_cone = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_cone.png", cone_and_person_size, cone_and_person_size) - center_size = round(36 / self.BASE_SIZE * self._rect.width) - self._dm_center = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_center.png", center_size, center_size) self._dm_background = gui_app.texture("icons_mici/onroad/driver_monitoring/dm_background.png", int(self._rect.width), int(self._rect.height)) def set_should_draw(self, should_draw: bool): @@ -113,16 +113,7 @@ def _render(self, _): dest_rect, rl.Vector2(dest_rect.width / 2, dest_rect.height / 2), self._rotation_filter.x - 90, - rl.Color(255, 255, 255, int(255 * self._fade_filter.x * (1 - self._looking_center_filter.x))), - ) - - rl.draw_texture_ex( - self._dm_center, - (int(self._rect.x + (self._rect.width - self._dm_center.width) / 2), - int(self._rect.y + (self._rect.height - self._dm_center.height) / 2)), - 0, - 1.0, - rl.Color(255, 255, 255, int(255 * self._fade_filter.x * self._looking_center_filter.x)), + rl.Color(255, 255, 255, int(255 * self._fade_filter.x)), ) else: @@ -174,11 +165,22 @@ def _update_state(self): # Get monitoring state driver_data = self.get_driver_data() driver_orient = driver_data.faceOrientation + driver_position = driver_data.facePosition if len(driver_orient) != 3: return - pitch, yaw, roll = driver_orient + # Calibrate orientation so looking straight ahead at road (instead of at device) is (0, 0, 0) + sm = ui_state.sm + if sm.valid['liveCalibration'] and len(sm['liveCalibration'].rpyCalib) == 3: + cal_rpy = sm['liveCalibration'].rpyCalib + else: + cal_rpy = [0.0, 0.0, 0.0] + + _, pitch, yaw = face_orientation_from_net(driver_orient, driver_position, cal_rpy) + pitch += math.radians(6) # calib or DM pose is not accurate, add a fake upward pitch to bias forward + yaw = -yaw # undo sign flip in face_orientation_from_net to match UI convention + pitch = self._pitch_filter.update(pitch) yaw = self._yaw_filter.update(yaw) @@ -192,7 +194,6 @@ def _update_state(self): if DEBUG: pitchd = math.degrees(pitch) yawd = math.degrees(yaw) - rolld = math.degrees(roll) rl.draw_line_ex((0, 100), (200, 100), 3, rl.RED) rl.draw_line_ex((0, 120), (200, 120), 3, rl.RED) @@ -200,13 +201,11 @@ def _update_state(self): pitch_x = 100 + pitchd yaw_x = 100 + yawd - roll_x = 100 + rolld rl.draw_circle(int(pitch_x), 100, 5, rl.GREEN) rl.draw_circle(int(yaw_x), 120, 5, rl.GREEN) - rl.draw_circle(int(roll_x), 140, 5, rl.GREEN) # filter head rotation, handling wrap-around - rotation = math.degrees(math.atan2(pitch, yaw)) + rotation = math.degrees(math.atan2(pitch * 2, yaw)) # reduce yaw sensitivity angle_diff = rotation - self._rotation_filter.x angle_diff = ((angle_diff + 180) % 360) - 180 self._rotation_filter.update(self._rotation_filter.x + angle_diff) From 75e352e5d03519d159d0c61945d143804996726f Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 15 Apr 2026 10:24:37 -0700 Subject: [PATCH 643/685] jotpluggler: CAN parsing is best effort (#37835) --- tools/jotpluggler/sketch_layout.cc | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tools/jotpluggler/sketch_layout.cc b/tools/jotpluggler/sketch_layout.cc index fd15e3d9164..d9622dde6d3 100644 --- a/tools/jotpluggler/sketch_layout.cc +++ b/tools/jotpluggler/sketch_layout.cc @@ -2011,10 +2011,7 @@ struct StreamAccumulator::Impl { return; } detected_dbc_name = next_dbc; - can_dbc.reset(); - if (!detected_dbc_name.empty()) { - can_dbc.emplace(resolve_dbc_path(detected_dbc_name)); - } + can_dbc = load_dbc_by_name(detected_dbc_name); } }; @@ -2051,7 +2048,7 @@ void StreamAccumulator::appendEvent(kj::ArrayPtr data) { event, impl_->schema, impl_->can_dbc ? &*impl_->can_dbc : nullptr, - true, + impl_->can_dbc.has_value(), *impl_->time_offset, &impl_->series); append_log_event(which, event, *impl_->time_offset, &impl_->logs, &impl_->last_alert_key); @@ -2150,13 +2147,11 @@ RouteData load_route_data(const std::string &route_name, const RouteMetadata metadata = detect_route_metadata(segments, route.selector); const std::string resolved_dbc = !dbc_name.empty() ? dbc_name : detect_dbc_for_fingerprint(metadata.car_fingerprint); - const std::optional can_dbc = resolved_dbc.empty() - ? std::nullopt - : std::optional(std::in_place, resolve_dbc_path(resolved_dbc)); + const std::optional can_dbc = load_dbc_by_name(resolved_dbc); const SchemaIndex &schema = SchemaIndex::instance(); LoadedRouteArtifacts artifacts = load_route_series_parallel(segments, schema, can_dbc ? &*can_dbc : nullptr, - route.selector, !resolved_dbc.empty(), &stats); + route.selector, can_dbc.has_value(), &stats); RouteData route_data = build_route_data(std::move(artifacts.series), std::move(artifacts.can_messages), std::move(artifacts.logs), From fa18e6395c7296a10f3e0fc85fa143c2208b616b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 15 Apr 2026 10:26:12 -0700 Subject: [PATCH 644/685] tools: remove sentry logging (#37836) --- tools/op.sh | 16 ---------------- tools/setup.sh | 46 +--------------------------------------------- 2 files changed, 1 insertion(+), 61 deletions(-) diff --git a/tools/op.sh b/tools/op.sh index 3b02602619c..dccf080829f 100755 --- a/tools/op.sh +++ b/tools/op.sh @@ -26,15 +26,6 @@ function op_install() { echo -e " ↳ [${GREEN}✔${NC}] op installed successfully. Open a new shell to use it." } -function loge() { - if [[ -f "$LOG_FILE" ]]; then - # error type - echo "$1" >> $LOG_FILE - # error log - echo "$2" >> $LOG_FILE - fi -} - function retry() { local attempts=$1 shift @@ -148,13 +139,11 @@ function op_check_os() { ;; * ) echo -e " ↳ [${RED}✗${NC}] Incompatible Ubuntu version $VERSION_CODENAME detected!" - loge "ERROR_INCOMPATIBLE_UBUNTU" "$VERSION_CODENAME" return 1 ;; esac else echo -e " ↳ [${RED}✗${NC}] No /etc/os-release on your system. Make sure you're running on Ubuntu, or similar!" - loge "ERROR_UNKNOWN_UBUNTU" return 1 fi @@ -162,7 +151,6 @@ function op_check_os() { echo -e " ↳ [${GREEN}✔${NC}] macOS detected." else echo -e " ↳ [${RED}✗${NC}] OS type $OSTYPE not supported!" - loge "ERROR_UNKNOWN_OS" "$OSTYPE" return 1 fi } @@ -210,7 +198,6 @@ function op_setup() { SETUP_SCRIPT="tools/setup_dependencies.sh" if ! $OPENPILOT_ROOT/$SETUP_SCRIPT; then echo -e " ↳ [${RED}✗${NC}] Dependencies installation failed!" - loge "ERROR_DEPENDENCIES_INSTALLATION" return 1 fi et="$(date +%s)" @@ -222,7 +209,6 @@ function op_setup() { st="$(date +%s)" if ! retry 3 git submodule update --jobs 4 --init --recursive; then echo -e " ↳ [${RED}✗${NC}] Getting git submodules failed!" - loge "ERROR_GIT_SUBMODULES" return 1 fi et="$(date +%s)" @@ -232,7 +218,6 @@ function op_setup() { st="$(date +%s)" if ! retry 3 git lfs pull; then echo -e " ↳ [${RED}✗${NC}] Pulling git lfs files failed!" - loge "ERROR_GIT_LFS" return 1 fi et="$(date +%s)" @@ -468,7 +453,6 @@ function _op() { -d | --dir ) shift 1; OPENPILOT_ROOT="$1"; shift 1 ;; --dry ) shift 1; DRY="1" ;; -n | --no-verify ) shift 1; NO_VERIFY="1" ;; - -l | --log ) shift 1; LOG_FILE="$1" ; shift 1 ;; esac # parse Commands diff --git a/tools/setup.sh b/tools/setup.sh index fd7efcee90e..dafd466ef92 100755 --- a/tools/setup.sh +++ b/tools/setup.sh @@ -33,39 +33,6 @@ cat << 'EOF' EOF } -function sentry_send_event() { - SENTRY_KEY=dd0cba62ba0ac07ff9f388f8f1e6a7f4 - SENTRY_URL=https://sentry.io/api/4507726145781760/store/ - - EVENT=$1 - EVENT_TYPE=${2:-$EVENT} - EVENT_LOG=${3:-"NA"} - - PLATFORM=$(uname -s) - ARCH=$(uname -m) - SYSTEM=$(uname -a) - if [[ $PLATFORM == "Darwin" ]]; then - OS="macos" - elif [[ $PLATFORM == "Linux" ]]; then - OS="linux" - fi - - if [[ $ARCH == armv8* ]] || [[ $ARCH == arm64* ]] || [[ $ARCH == aarch64* ]]; then - ARCH="aarch64" - elif [[ $ARCH == "x86_64" ]] || [[ $ARCH == i686* ]]; then - ARCH="x86" - fi - - PYTHON_VERSION=$(echo $(python3 --version 2> /dev/null || echo "NA")) - BRANCH=$(echo $(git -C $OPENPILOT_ROOT rev-parse --abbrev-ref HEAD 2> /dev/null || echo "NA")) - COMMIT=$(echo $(git -C $OPENPILOT_ROOT rev-parse HEAD 2> /dev/null || echo "NA")) - - curl -s -o /dev/null -X POST -g --data "{ \"exception\": { \"values\": [{ \"type\": \"$EVENT\" }] }, \"tags\" : { \"event_type\" : \"$EVENT_TYPE\", \"event_log\" : \"$EVENT_LOG\", \"os\" : \"$OS\", \"arch\" : \"$ARCH\", \"python_version\" : \"$PYTHON_VERSION\" , \"git_branch\" : \"$BRANCH\", \"git_commit\" : \"$COMMIT\", \"system\" : \"$SYSTEM\" } }" \ - -H 'Content-Type: application/json' \ - -H "X-Sentry-Auth: Sentry sentry_version=7, sentry_key=$SENTRY_KEY, sentry_client=op_setup/0.1" \ - $SENTRY_URL 2> /dev/null -} - function check_stdin() { if [ -t 0 ]; then INTERACTIVE=1 @@ -131,7 +98,6 @@ function check_git() { echo "Checking for git..." if ! command -v "git" > /dev/null 2>&1; then echo -e " ↳ [${RED}✗${NC}] git not found on your system, can't continue!" - sentry_send_event "SETUP_FAILURE" "ERROR_GIT_NOT_FOUND" return 1 else echo -e " ↳ [${GREEN}✔${NC}] git found.\n" @@ -150,7 +116,6 @@ function git_clone() { fi echo -e " ↳ [${RED}✗${NC}] failed to clone openpilot!" - sentry_send_event "SETUP_FAILURE" "ERROR_GIT_CLONE" return 1 } @@ -159,18 +124,9 @@ function install_with_op() { $OPENPILOT_ROOT/tools/op.sh install $OPENPILOT_ROOT/tools/op.sh post-commit - LOG_FILE=$(mktemp) - - if ! $OPENPILOT_ROOT/tools/op.sh --log $LOG_FILE setup; then + if ! $OPENPILOT_ROOT/tools/op.sh setup; then echo -e "\n[${RED}✗${NC}] failed to install openpilot!" - - ERROR_TYPE="$(cat "$LOG_FILE" | sed '1p;d')" - ERROR_LOG="$(cat "$LOG_FILE" | sed '2p;d')" - sentry_send_event "SETUP_FAILURE" "$ERROR_TYPE" "$ERROR_LOG" || true - return 1 - else - sentry_send_event "SETUP_SUCCESS" || true fi echo -e "\n----------------------------------------------------------------------" From 117d5cee4ff336874f529838cbb747debcbd023a Mon Sep 17 00:00:00 2001 From: Ethan Reish Date: Wed, 15 Apr 2026 12:46:15 -0500 Subject: [PATCH 645/685] updated: complete branch migration dict for mici and tizi (#37830) --- system/updated/tests/test_updated.py | 38 ++++++++++++++++++++++++++++ system/updated/updated.py | 3 +++ 2 files changed, 41 insertions(+) create mode 100644 system/updated/tests/test_updated.py diff --git a/system/updated/tests/test_updated.py b/system/updated/tests/test_updated.py new file mode 100644 index 00000000000..d36d4dd4e13 --- /dev/null +++ b/system/updated/tests/test_updated.py @@ -0,0 +1,38 @@ +import pytest + +from openpilot.common.params import Params +from openpilot.system.updated.updated import Updater + + +@pytest.mark.parametrize(("device_type", "branch", "expected"), [ + ("tizi", "release3", "release-tizi"), + ("tizi", "release3-staging", "release-tizi-staging"), + ("mici", "release3", "release-mici"), + ("mici", "release3-staging", "release-mici-staging"), +]) +def test_target_branch_migration_from_current_branch(mocker, device_type, branch, expected): + params = Params() + params.remove("UpdaterTargetBranch") + + mocker.patch("openpilot.system.updated.updated.HARDWARE.get_device_type", return_value=device_type) + mocker.patch.object(Updater, "get_branch", return_value=branch) + + assert Updater().target_branch == expected + + +@pytest.mark.parametrize(("device_type", "branch", "expected"), [ + ("tizi", "release3", "release-tizi"), + ("tizi", "release3-staging", "release-tizi-staging"), + ("mici", "release3", "release-mici"), + ("mici", "release3-staging", "release-mici-staging"), +]) +def test_target_branch_migration_from_param(mocker, device_type, branch, expected): + params = Params() + params.put("UpdaterTargetBranch", branch) + + mocker.patch("openpilot.system.updated.updated.HARDWARE.get_device_type", return_value=device_type) + + try: + assert Updater().target_branch == expected + finally: + params.remove("UpdaterTargetBranch") diff --git a/system/updated/updated.py b/system/updated/updated.py index ffd10e038db..693a31c9f49 100755 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -244,6 +244,9 @@ def target_branch(self) -> str: b = self.get_branch(BASEDIR) b = { ("tizi", "release3"): "release-tizi", + ("tizi", "release3-staging"): "release-tizi-staging", + ("mici", "release3"): "release-mici", + ("mici", "release3-staging"): "release-mici-staging", }.get((HARDWARE.get_device_type(), b), b) return b From 31403f4a5c103a8f78ee1893404abbcf7e6d8cde Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 15 Apr 2026 10:50:24 -0700 Subject: [PATCH 646/685] jotplugger: render segment range slices properly (#37837) --- tools/jotpluggler/app.h | 5 ++++- tools/jotpluggler/session.cc | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/jotpluggler/app.h b/tools/jotpluggler/app.h index 55f6d3ef9f6..872a6973d78 100644 --- a/tools/jotpluggler/app.h +++ b/tools/jotpluggler/app.h @@ -276,7 +276,10 @@ struct RouteIdentifier { std::string display_slice() const { const int begin = slice_explicit ? slice_begin : available_begin; const int end = slice_explicit ? slice_end : available_end; - if (end < 0 || end == begin) { + if (end < 0) { + return std::to_string(begin) + ":"; + } + if (end == begin) { return std::to_string(begin); } return std::to_string(begin) + ":" + std::to_string(end); diff --git a/tools/jotpluggler/session.cc b/tools/jotpluggler/session.cc index 22dd7dd4638..173df7bc04c 100644 --- a/tools/jotpluggler/session.cc +++ b/tools/jotpluggler/session.cc @@ -355,7 +355,7 @@ bool apply_route_slice_change(AppSession *session, UiState *state, std::string_v int begin = 0; int end = 0; if (!parse_slice_spec(slice_text, &begin, &end)) { - state->error_text = "Slice must be N or N:M."; + state->error_text = "Slice must be N, N:, or N:M."; state->open_error_popup = true; return false; } From 83e6e7da93ab5e04dc7b131a82eeba7c4cbeed5a Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 15 Apr 2026 15:46:43 -0700 Subject: [PATCH 647/685] docs: setup zensical (#37838) --- .github/workflows/docs.yaml | 6 +- .gitignore | 1 + docs/DEVELOPMENT.md | 24 ++++ docs/README.md | 26 ----- docs/assets/comma-logo.png | 1 + docs/concepts/glossary.md | 8 +- docs/css/tooltip.css | 44 -------- docs/ext/glossary.py | 215 ++++++++++++++++++++++++++++++++++++ docs/ext/glossary.toml | 8 ++ docs/glossary.toml | 0 docs/hooks/glossary.py | 68 ------------ docs/index.md | 1 - docs/stylesheets/extra.css | 42 +++++++ mkdocs.yml | 44 -------- pyproject.toml | 2 +- scripts/docs.py | 63 +++++++++++ uv.lock | 164 +++++++++------------------ zensical.toml | 67 +++++++++++ 18 files changed, 478 insertions(+), 306 deletions(-) create mode 100644 docs/DEVELOPMENT.md delete mode 100644 docs/README.md create mode 120000 docs/assets/comma-logo.png delete mode 100644 docs/css/tooltip.css create mode 100644 docs/ext/glossary.py create mode 100644 docs/ext/glossary.toml delete mode 100644 docs/glossary.toml delete mode 100644 docs/hooks/glossary.py delete mode 120000 docs/index.md create mode 100644 docs/stylesheets/extra.css delete mode 100644 mkdocs.yml create mode 100644 scripts/docs.py create mode 100644 zensical.toml diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 23a89de1c11..f618c1bdf89 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -29,9 +29,9 @@ jobs: # Build - name: Build docs run: | - # TODO: can we install just the "docs" dependency group without the normal deps? - pip install mkdocs - mkdocs build + git lfs pull + pip install zensical + python scripts/docs.py build # Push to docs.comma.ai - uses: actions/checkout@v6 diff --git a/.gitignore b/.gitignore index c4022a86535..689d1d6c29c 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ compare_runtime*.html selfdrive/modeld/models/tg_compiled_flags.json # build artifacts +docs_site/ selfdrive/pandad/pandad cereal/services.h cereal/gen diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md new file mode 100644 index 00000000000..e803a3fb8a3 --- /dev/null +++ b/docs/DEVELOPMENT.md @@ -0,0 +1,24 @@ +# Docs development + +The `docs/` tree is the source for [docs.comma.ai](https://docs.comma.ai). +The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml). + +Those commands must be run in the root directory of openpilot, **not /docs** + +**1. Install the docs dependencies** +``` bash +uv pip install .[docs] +``` + +**2. Build the new site** +``` bash +docs build +``` + +**3. Run the new site locally** +``` bash +docs serve +``` + +References: +* https://zensical.org/docs/ diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 12d0b6f5dd8..00000000000 --- a/docs/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# openpilot docs - -This is the source for [docs.comma.ai](https://docs.comma.ai). -The site is updated on pushes to master by this [workflow](../.github/workflows/docs.yaml). - -## Development -NOTE: Those commands must be run in the root directory of openpilot, **not /docs** - -**1. Install the docs dependencies** -``` bash -uv pip install .[docs] -``` - -**2. Build the new site** -``` bash -mkdocs build -``` - -**3. Run the new site locally** -``` bash -mkdocs serve -``` - -References: -* https://www.mkdocs.org/getting-started/ -* https://github.com/ntno/mkdocs-terminal diff --git a/docs/assets/comma-logo.png b/docs/assets/comma-logo.png new file mode 120000 index 00000000000..2838d92bfb1 --- /dev/null +++ b/docs/assets/comma-logo.png @@ -0,0 +1 @@ +../../selfdrive/assets/icons_mici/settings/comma_icon.png \ No newline at end of file diff --git a/docs/concepts/glossary.md b/docs/concepts/glossary.md index 3bfe71bcb4a..4f4dd54756c 100644 --- a/docs/concepts/glossary.md +++ b/docs/concepts/glossary.md @@ -1,9 +1,3 @@ # openpilot glossary -* **onroad**: openpilot's system state while ignition is on -* **offroad**: openpilot's system state while ignition is off -* **route**: a route is a recording of an onroad session -* **segment**: routes are split into one minute chunks called segments. -* **comma connect**: the web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai). -* **panda**: this is the secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda). -* **comma four**: the latest hardware by comma.ai for running openpilot. more info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four). +{{GLOSSARY_DEFINITIONS}} diff --git a/docs/css/tooltip.css b/docs/css/tooltip.css deleted file mode 100644 index b9a54f793f9..00000000000 --- a/docs/css/tooltip.css +++ /dev/null @@ -1,44 +0,0 @@ -[data-tooltip] { - position: relative; - display: inline-block; - border-bottom: 1px dotted black; -} - -[data-tooltip] .tooltip-content { - width: max-content; - max-width: 25em; - position: absolute; - top: 100%; - left: 50%; - transform: translateX(-50%); - background-color: white; - color: #404040; - box-shadow: 0 4px 14px 0 rgba(0,0,0,.2), 0 0 0 1px rgba(0,0,0,.05); - padding: 10px; - font: 14px/1.5 Lato, proxima-nova, Helvetica Neue, Arial, sans-serif; - text-decoration: none; - opacity: 0; - visibility: hidden; - transition: opacity 0.1s, visibility 0s; - z-index: 1000; - pointer-events: none; /* Prevent accidental interaction */ -} - -[data-tooltip]:hover .tooltip-content { - opacity: 1; - visibility: visible; - pointer-events: auto; /* Allow interaction when visible */ -} - -.tooltip-content .tooltip-glossary-link { - display: inline-block; - margin-top: 8px; - font-size: 12px; - color: #007bff; - text-decoration: none; -} - -.tooltip-content .tooltip-glossary-link:hover { - color: #0056b3; - text-decoration: underline; -} diff --git a/docs/ext/glossary.py b/docs/ext/glossary.py new file mode 100644 index 00000000000..d4c3ebd8fd2 --- /dev/null +++ b/docs/ext/glossary.py @@ -0,0 +1,215 @@ +import posixpath +import re +import tomllib +import xml.etree.ElementTree as ET +from pathlib import Path + +from markdown.extensions import Extension +from markdown.preprocessors import Preprocessor +from markdown.treeprocessors import Treeprocessor + +from zensical.extensions.links import LinksProcessor + +GlossaryTerm = tuple[str, re.Pattern[str], str] + +GLOSSARY_FILE = Path(__file__).with_name("glossary.toml") +GLOSSARY_PAGE = "concepts/glossary.md" +GLOSSARY_PLACEHOLDER = "{{GLOSSARY_DEFINITIONS}}" + +SKIP_TAGS = { + "a", + "code", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "kbd", + "pre", + "script", + "style", +} + +def clean_tooltip(description: str) -> str: + text = re.sub(r"\[([^\]]+)]\([^)]+\)", r"\1", description) + text = re.sub(r"`([^`]+)`", r"\1", text) + text = re.sub(r"[*_~]", "", text) + return re.sub(r"\s+", " ", text).strip() + + +def load_glossary() -> tuple[list[GlossaryTerm], str]: + with GLOSSARY_FILE.open("rb") as f: + glossary_data = tomllib.load(f).get("glossary", {}) + + glossary: list[GlossaryTerm] = [] + rendered = [] + for key, value in glossary_data.items(): + label = str(key).strip().replace("_", " ") + description = str(value).strip() + if not description: + continue + + slug = label.replace(" ", "-").replace("_", "-").lower() + glossary.append((slug, re.compile(rf"(?**{label}**: {description}') + + return glossary, "\n".join(rendered) + + +class GlossaryPreprocessor(Preprocessor): + def __init__(self, md, glossary: str): + super().__init__(md) + self.glossary = glossary + + def run(self, lines: list[str]) -> list[str]: + markdown = "\n".join(lines) + if GLOSSARY_PLACEHOLDER not in markdown: + return lines + return markdown.replace(GLOSSARY_PLACEHOLDER, self.glossary).splitlines() + + +class GlossaryTreeprocessor(Treeprocessor): + def __init__(self, md, glossary: list[GlossaryTerm]): + super().__init__(md) + self.glossary = glossary + self.seen: set[str] = set() + + def run(self, root: ET.Element) -> None: + at = self.md.treeprocessors.get_index_for_name("zrelpath") + processor = self.md.treeprocessors[at] + if not isinstance(processor, LinksProcessor): + raise TypeError("Links processor not registered") + if processor.path == GLOSSARY_PAGE: + return + + self.seen.clear() + glossary_href = f"{posixpath.relpath(GLOSSARY_PAGE, posixpath.dirname(processor.path) or '.')}#" + self._walk(root, glossary_href) + + def _walk(self, element: ET.Element, glossary_href: str) -> None: + if element.tag in SKIP_TAGS or element.attrib.get("data-glossary-skip") is not None: + return + + self._replace(element, glossary_href) + + idx = 0 + while idx < len(element): + child = element[idx] + self._walk(child, glossary_href) + idx = self._replace(element, glossary_href, idx) + 1 + + def _replace(self, parent: ET.Element, glossary_href: str, index: int | None = None) -> int: + child = None if index is None else parent[index] + text = parent.text if child is None else child.tail + pieces = self._pieces(text or "", glossary_href) + if not pieces: + return -1 if index is None else index + + if child is None: + parent.text = pieces[0] if isinstance(pieces[0], str) else "" + insert_at = 0 if isinstance(pieces[0], str) else -1 + else: + assert index is not None + child.tail = pieces[0] if isinstance(pieces[0], str) else "" + insert_at = index + + start = 1 if isinstance(pieces[0], str) else 0 + previous = child + + for piece in pieces[start:]: + if isinstance(piece, str): + previous.tail = (previous.tail or "") + piece + continue + + insert_at += 1 + parent.insert(insert_at, piece) + previous = piece + + return insert_at + + def _pieces(self, text: str, glossary_href: str) -> list[str | ET.Element]: + if not text.strip(): + return [] + + pieces: list[str | ET.Element] = [] + cursor = 0 + + while True: + best = None + for slug, pattern, tooltip in self.glossary: + if slug in self.seen: + continue + + found = pattern.search(text, cursor) + if found is None: + continue + + candidate = (slug, tooltip, found.start(), found.end()) + if best is None: + best = candidate + continue + + _, best_start, best_end = best + _, current_start, current_end = candidate + if current_start < best_start: + best = candidate + continue + + if current_start == best_start and current_end - current_start > best_end - best_start: + best = candidate + + if best is None: + break + + slug, tooltip, start, end = best + if start > cursor: + pieces.append(text[cursor:start]) + + link = ET.Element( + "a", + { + "class": "glossary-term", + "data-glossary-term": "", + "href": f"{glossary_href}{slug}", + }, + ) + ET.SubElement(link, "span", {"class": "glossary-term__label"}).text = text[start:end] + ET.SubElement( + link, + "span", + { + "class": "glossary-term__tooltip", + "data-search-exclude": "", + }, + ).text = tooltip + pieces.append(link) + self.seen.add(slug) + cursor = end + + if not pieces: + return [] + if cursor < len(text): + pieces.append(text[cursor:]) + return pieces + + +class GlossaryExtension(Extension): + def extendMarkdown(self, md) -> None: + md.registerExtension(self) + glossary, rendered = load_glossary() + + md.preprocessors.register( + GlossaryPreprocessor(md, rendered), + "docs-ext-glossary-preprocessor", + 27, + ) + md.treeprocessors.register( + GlossaryTreeprocessor(md, glossary), + "docs-ext-glossary-treeprocessor", + 0, + ) + + +def makeExtension(**kwargs) -> GlossaryExtension: + return GlossaryExtension(**kwargs) diff --git a/docs/ext/glossary.toml b/docs/ext/glossary.toml new file mode 100644 index 00000000000..62408d9dddf --- /dev/null +++ b/docs/ext/glossary.toml @@ -0,0 +1,8 @@ +[glossary] +onroad = "openpilot's system state while ignition is on." +offroad = "openpilot's system state while ignition is off." +route = "A route is a recording of an onroad session." +segment = "Routes are split into one minute chunks called segments." +"comma connect" = "The web viewer for all your routes; check it out at [connect.comma.ai](https://connect.comma.ai)." +panda = "The secondary processor on the device that implements the functional safety and directly talks to the car over CAN. See the [panda repo](https://github.com/commaai/panda)." +"comma four" = "The latest hardware by comma.ai for running openpilot. More info at [comma.ai/shop/comma-four](https://www.comma.ai/shop/comma-four)." diff --git a/docs/glossary.toml b/docs/glossary.toml deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/docs/hooks/glossary.py b/docs/hooks/glossary.py deleted file mode 100644 index e2fa3d51e04..00000000000 --- a/docs/hooks/glossary.py +++ /dev/null @@ -1,68 +0,0 @@ -import re -import tomllib - -def load_glossary(file_path="docs/glossary.toml"): - with open(file_path, "rb") as f: - glossary_data = tomllib.load(f) - return glossary_data.get("glossary", {}) - -def generate_anchor_id(name): - return name.replace(" ", "-").replace("_", "-").lower() - -def format_markdown_term(name, definition): - anchor_id = generate_anchor_id(name) - markdown = f"* [**{name.replace('_', ' ').title()}**](#{anchor_id})" - if definition.get("abbreviation"): - markdown += f" *({definition['abbreviation']})*" - if definition.get("description"): - markdown += f": {definition['description']}\n" - return markdown - -def glossary_markdown(vocabulary): - markdown = "" - for category, terms in vocabulary.items(): - markdown += f"## {category.replace('_', ' ').title()}\n\n" - for name, definition in terms.items(): - markdown += format_markdown_term(name, definition) - return markdown - -def format_tooltip_html(term_key, definition, html): - display_term = term_key.replace("_", " ").title() - clean_description = re.sub(r"\[(.+)]\(.+\)", r"\1", definition["description"]) - glossary_link = ( - f"Glossary🔗" - ) - return re.sub( - re.escape(display_term), - lambda - match: f"{match.group(0)}{clean_description} {glossary_link}", - html, - flags=re.IGNORECASE, - ) - -def apply_tooltip(_term_key, _definition, pattern, html): - return re.sub( - pattern, - lambda match: format_tooltip_html(_term_key, _definition, match.group(0)), - html, - flags=re.IGNORECASE, - ) - -def tooltip_html(vocabulary, html): - for _category, terms in vocabulary.items(): - for term_key, definition in terms.items(): - if definition.get("description"): - pattern = rf"(?)(?!\([^)]*\))" - html = apply_tooltip(term_key, definition, pattern, html) - return html - -# Page Hooks -def on_page_markdown(markdown, **kwargs): - glossary = load_glossary() - return markdown.replace("{{GLOSSARY_DEFINITIONS}}", glossary_markdown(glossary)) - -def on_page_content(html, **kwargs): - if kwargs.get("page").title == "Glossary": - return html - glossary = load_glossary() - return tooltip_html(glossary, html) diff --git a/docs/index.md b/docs/index.md deleted file mode 120000 index 74ea27aeeb6..00000000000 --- a/docs/index.md +++ /dev/null @@ -1 +0,0 @@ -getting-started/what-is-openpilot.md \ No newline at end of file diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 00000000000..36ce354af10 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,42 @@ +.md-logo img { + filter: invert(1); +} + +.glossary-term { + position: relative; + color: inherit; + text-decoration: none; +} + +.glossary-term__label { + border-bottom: 1px dotted currentColor; +} + +.glossary-term__tooltip { + position: absolute; + top: calc(100% + 0.4rem); + left: 50%; + width: max-content; + max-width: min(30rem, 80vw); + padding: 0.65rem 0.8rem; + border-radius: 0.6rem; + background: rgb(26 26 26 / 96%); + color: white; + box-shadow: 0 0.6rem 1.8rem rgb(0 0 0 / 22%); + font-size: 0.85rem; + line-height: 1.45; + opacity: 0; + pointer-events: none; + transform: translateX(-50%) translateY(-0.15rem); + transition: opacity 120ms ease, transform 120ms ease; + visibility: hidden; + z-index: 20; +} + +.glossary-term:hover .glossary-term__tooltip, +.glossary-term:focus-visible .glossary-term__tooltip, +.glossary-term:focus-within .glossary-term__tooltip { + opacity: 1; + transform: translateX(-50%) translateY(0); + visibility: visible; +} diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index 550f807aca0..00000000000 --- a/mkdocs.yml +++ /dev/null @@ -1,44 +0,0 @@ -site_name: openpilot docs -repo_url: https://github.com/commaai/openpilot/ -site_url: https://docs.comma.ai - -exclude_docs: README.md - -strict: true -docs_dir: docs -site_dir: docs_site/ - -hooks: - - docs/hooks/glossary.py -extra_css: - - css/tooltip.css -theme: - name: readthedocs - navigation_depth: 3 - -nav: - - Getting Started: - - What is openpilot?: getting-started/what-is-openpilot.md - - How-to: - - Turn the speed blue: how-to/turn-the-speed-blue.md - - Connect to a comma 3X: how-to/connect-to-comma.md - # - Make your first pull request: how-to/make-first-pr.md - #- Replay a drive: how-to/replay-a-drive.md - - Concepts: - - Logs: concepts/logs.md - - Safety: concepts/safety.md - - Glossary: concepts/glossary.md - - Car Porting: - - What is a car port?: car-porting/what-is-a-car-port.md - - Porting a car brand: car-porting/brand-port.md - - Porting a car model: car-porting/model-port.md - - Contributing: - - Roadmap: contributing/roadmap.md - #- Architecture: contributing/architecture.md - - Contributing Guide →: https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md - - Links: - - Blog →: https://blog.comma.ai - - Bounties →: https://comma.ai/bounties - - GitHub →: https://github.com/commaai - - Discord →: https://discord.comma.ai - - X →: https://x.com/comma_ai diff --git a/pyproject.toml b/pyproject.toml index e595106b5c1..d7af83d4075 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,7 @@ dependencies = [ [project.optional-dependencies] docs = [ "Jinja2", - "mkdocs", + "zensical", ] testing = [ diff --git a/scripts/docs.py b/scripts/docs.py new file mode 100644 index 00000000000..d60bfb791f6 --- /dev/null +++ b/scripts/docs.py @@ -0,0 +1,63 @@ +""" + wrapper that materializes symlinks in docs/ before build + + we can delete this once zensical supports symlinks: + https://github.com/zensical/backlog/issues/55 +""" +import os +import shutil +import signal +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent +DOCS_DIR = REPO_ROOT / "docs" +SITE_DIR = REPO_ROOT / "docs_site" +sys.path.insert(0, str(REPO_ROOT)) +# Local docs build helpers live under docs/ so they stay near the content +# source. The wrapper prunes them from docs_site/ after build. +sys.path.insert(0, str(DOCS_DIR)) + + +def _materialize(docs: Path) -> dict[Path, str]: + originals: dict[Path, str] = {} + for link in docs.rglob("*"): + if not link.is_symlink(): + continue + target = link.resolve() + if not target.is_file(): + continue + originals[link] = os.readlink(link) + link.unlink() + shutil.copy2(target, link) + return originals + + +def _restore(originals: dict[Path, str]) -> None: + for link, target in originals.items(): + link.unlink(missing_ok=True) + os.symlink(target, link) + + +def _raise_interrupt(*_): + raise KeyboardInterrupt + + +def _prune_site_output() -> None: + shutil.rmtree(SITE_DIR / "ext", ignore_errors=True) + + +def main() -> None: + signal.signal(signal.SIGTERM, _raise_interrupt) + originals = _materialize(DOCS_DIR) + try: + from zensical.main import cli + cli(standalone_mode=False) + if len(sys.argv) > 1 and sys.argv[1] == "build": + _prune_site_output() + finally: + _restore(originals) + + +if __name__ == "__main__": + main() diff --git a/uv.lock b/uv.lock index cde094a85c9..f8278c13681 100644 --- a/uv.lock +++ b/uv.lock @@ -359,6 +359,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ff/fa/d3c15189f7c52aaefbaea76fb012119b04b9013f4bf446cb4eb4c26c4e6b/cython-3.2.4-py3-none-any.whl", hash = "sha256:732fc93bc33ae4b14f6afaca663b916c2fdd5dcbfad7114e17fb2434eeaea45c", size = 1257078, upload-time = "2026-01-04T14:14:12.373Z" }, ] +[[package]] +name = "deepmerge" +version = "2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/3a/b0ba594708f1ad0bc735884b3ad854d3ca3bdc1d741e56e40bbda6263499/deepmerge-2.0.tar.gz", hash = "sha256:5c3d86081fbebd04dd5de03626a0607b809a98fb6ccba5770b62466fe940ff20", size = 19890, upload-time = "2024-08-30T05:31:50.308Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" }, +] + [[package]] name = "dnspython" version = "2.8.0" @@ -434,18 +443,6 @@ name = "gcc-arm-none-eabi" version = "13.2.1" source = { git = "https://github.com/commaai/dependencies.git?subdirectory=gcc-arm-none-eabi&rev=release-gcc-arm-none-eabi#0e1ae2548977f6cd78c51d4d0c16ebd1863241b8" } -[[package]] -name = "ghp-import" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "python-dateutil" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, -] - [[package]] name = "git-lfs" version = "3.6.1" @@ -655,15 +652,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f1/76/934db220026b5fef85f45d51a738b91dea7d70207581063cd9bd8fafcf74/matplotlib-3.10.8-cp312-cp312-win_arm64.whl", hash = "sha256:3c624e43ed56313651bc18a47f838b60d7b8032ed348911c54906b130b20071b", size = 8012751, upload-time = "2025-12-10T22:55:42.684Z" }, ] -[[package]] -name = "mergedeep" -version = "1.3.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, -] - [[package]] name = "metadrive-simulator" version = "0.4.2.3" @@ -674,44 +662,6 @@ dependencies = [ { name = "panda3d-gltf" }, ] -[[package]] -name = "mkdocs" -version = "1.6.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "ghp-import" }, - { name = "jinja2" }, - { name = "markdown" }, - { name = "markupsafe" }, - { name = "mergedeep" }, - { name = "mkdocs-get-deps" }, - { name = "packaging" }, - { name = "pathspec" }, - { name = "pyyaml" }, - { name = "pyyaml-env-tag" }, - { name = "watchdog" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, -] - -[[package]] -name = "mkdocs-get-deps" -version = "0.2.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mergedeep" }, - { name = "platformdirs" }, - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, -] - [[package]] name = "mpmath" version = "1.3.0" @@ -849,7 +799,7 @@ dev = [ ] docs = [ { name = "jinja2" }, - { name = "mkdocs" }, + { name = "zensical" }, ] testing = [ { name = "codespell" }, @@ -899,7 +849,6 @@ requires-dist = [ { name = "libyuv", git = "https://github.com/commaai/dependencies.git?subdirectory=libyuv&rev=release-libyuv" }, { name = "matplotlib", marker = "extra == 'dev'" }, { name = "metadrive-simulator", marker = "platform_machine != 'aarch64' and extra == 'tools'", git = "https://github.com/commaai/metadrive.git?rev=minimal" }, - { name = "mkdocs", marker = "extra == 'docs'" }, { name = "ncurses", git = "https://github.com/commaai/dependencies.git?subdirectory=ncurses&rev=release-ncurses" }, { name = "numpy", specifier = ">=2.0" }, { name = "opencv-python-headless", marker = "extra == 'dev'" }, @@ -932,6 +881,7 @@ requires-dist = [ { name = "ty", marker = "extra == 'testing'" }, { name = "websocket-client" }, { name = "xattr" }, + { name = "zensical", marker = "extra == 'docs'" }, { name = "zeromq", git = "https://github.com/commaai/dependencies.git?subdirectory=zeromq&rev=release-zeromq" }, { name = "zstandard" }, { name = "zstd", git = "https://github.com/commaai/dependencies.git?subdirectory=zstd&rev=release-zstd" }, @@ -986,15 +936,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/11/5d/3744c6550dddf933785a37cdd4a9921fe13284e6d115b5a2637fe390f158/panda3d_simplepbr-0.13.1-py3-none-any.whl", hash = "sha256:cda41cb57cff035b851646956cfbdcc408bee42511dabd4f2d7bd4fbf48c57a9", size = 2457097, upload-time = "2025-03-30T16:57:39.729Z" }, ] -[[package]] -name = "pathspec" -version = "1.0.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, -] - [[package]] name = "pillow" version = "12.2.0" @@ -1014,15 +955,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, ] -[[package]] -name = "platformdirs" -version = "4.9.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9f/4a/0883b8e3802965322523f0b200ecf33d31f10991d0401162f4b23c698b42/platformdirs-4.9.6.tar.gz", hash = "sha256:3bfa75b0ad0db84096ae777218481852c0ebc6c727b3168c1b9e0118e458cf0a", size = 29400, upload-time = "2026-04-09T00:04:10.812Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/a6/a0a304dc33b49145b21f4808d763822111e67d1c3a32b524a1baf947b6e1/platformdirs-4.9.6-py3-none-any.whl", hash = "sha256:e61adb1d5e5cb3441b4b7710bea7e4c12250ca49439228cc1021c00dcfac0917", size = 21348, upload-time = "2026-04-09T00:04:09.463Z" }, -] - [[package]] name = "pluggy" version = "1.6.0" @@ -1186,6 +1118,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/ec/6e02b2561d056ea5b33046e3cad21238e6a9097b97d6ccc0fbe52b50c858/pylibsrtp-1.0.0-cp310-abi3-win_arm64.whl", hash = "sha256:2696bdb2180d53ac55d0eb7b58048a2aa30cd4836dd2ca683669889137a94d2a", size = 1159246, upload-time = "2025-10-13T16:12:30.285Z" }, ] +[[package]] +name = "pymdown-extensions" +version = "10.21.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" }, +] + [[package]] name = "pyopenssl" version = "26.0.0" @@ -1322,18 +1267,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, ] -[[package]] -name = "pyyaml-env-tag" -version = "1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyyaml" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, -] - [[package]] name = "pyzmq" version = "27.1.0" @@ -1589,27 +1522,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, ] -[[package]] -name = "watchdog" -version = "6.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, -] - [[package]] name = "websocket-client" version = "1.9.0" @@ -1669,6 +1581,34 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/69/68/c8739671f5699c7dc470580a4f821ef37c32c4cb0b047ce223a7f115757f/yarl-1.23.0-py3-none-any.whl", hash = "sha256:a2df6afe50dea8ae15fa34c9f824a3ee958d785fd5d089063d960bae1daa0a3f", size = 48288, upload-time = "2026-03-01T22:07:51.388Z" }, ] +[[package]] +name = "zensical" +version = "0.0.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "deepmerge" }, + { name = "markdown" }, + { name = "pygments" }, + { name = "pymdown-extensions" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/59/c2/dea4b86dc1ca2a7b55414017f12cfb12b5cfdf3a1ed7c77a04c271eb523b/zensical-0.0.33.tar.gz", hash = "sha256:05209cb4f80185c533e0d37c25d084ddc2050e3d5a4dd1b1812961c2ee0c3380", size = 3892278, upload-time = "2026-04-14T11:08:19.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/5f/45d5200405420a9d8ac91cf9e7826622ea12f3198e8e6ac4ffb481eb53bf/zensical-0.0.33-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f658e3c241cfbb560bd8811116a9486cff7e04d7d5aed73569dd533c74187450", size = 12416748, upload-time = "2026-04-14T11:07:43.246Z" }, + { url = "https://files.pythonhosted.org/packages/33/1e/aadaf31d6e4d20419ecedaf0b1c804e359ec23dcdb44c8d2bf6d8407080c/zensical-0.0.33-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:f9813ac3256c28e2e2f1ba5c9fab1b4bca62bbe0e0f8e85ac22d33b068b1b08a", size = 12293372, upload-time = "2026-04-14T11:07:46.569Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/838be8451ea8b2aecec39fbec3971060fc705e17f5741249740d9b6a6824/zensical-0.0.33-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bad7ac71028769c5d1f3f84f448dbb7352db28d77095d1b40a8d1b0aa34ec30", size = 12659832, upload-time = "2026-04-14T11:07:50.754Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5c/dd957d7c83efc13a70a6058d4190a3afcf29942aefb391120bca5466347d/zensical-0.0.33-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:06bb039daf044547c9400a52f9493b3cd486ba9baef3324fdcffd2e26e61105f", size = 12603847, upload-time = "2026-04-14T11:07:53.698Z" }, + { url = "https://files.pythonhosted.org/packages/b7/99/dd6ccc392ece1f34fb20ea339a01717badbbeb2fba1d4f3019a5028d0bcc/zensical-0.0.33-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:260238062b3139ece0edab93f4dbe7a12923453091f5aa580dfd73e799388076", size = 12956236, upload-time = "2026-04-14T11:07:56.728Z" }, + { url = "https://files.pythonhosted.org/packages/f4/76/e0a1b884eadf6afa7e2d56c90c268eec36836ac27e96ef250c0129e55417/zensical-0.0.33-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7dff0f4afda7b8586bc4ab2a5684bce5b282232dd4e0cad3be4c73fedd264425", size = 12701944, upload-time = "2026-04-14T11:07:59.928Z" }, + { url = "https://files.pythonhosted.org/packages/38/38/e1ff13461e406864fa2b23fc828822659a7dbac5c79398f724d17f088540/zensical-0.0.33-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:207b4d81b208d75b97dc7bd318804550b886a3e852ef67429ef0e6b9442839d1", size = 12835444, upload-time = "2026-04-14T11:08:02.998Z" }, + { url = "https://files.pythonhosted.org/packages/41/04/7d24d52d6903fc5c511633afe8b5716fef19da09685327665cc127f61648/zensical-0.0.33-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:06d2f57f7bc8cc8fd904386020ea1365eebc411e8698a871e9525c885abca574", size = 12878419, upload-time = "2026-04-14T11:08:06.054Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ec/87fc9e360c694ab006363c7834639eccafd0d26a487cd63dd609bd68f36a/zensical-0.0.33-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:c2851b82d83aa0b2ae4f8e99731cfeedeecebfa04e6b3fc4d375deca629fa240", size = 13022474, upload-time = "2026-04-14T11:08:09.007Z" }, + { url = "https://files.pythonhosted.org/packages/10/b3/0bf174ab6ceedb31d9af462073b5339c894b2084a27d42cb9f0906050d76/zensical-0.0.33-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:90daaf512b0429d7b9147ad5e6085b455d24803eff18b508aed738ca65444683", size = 12975233, upload-time = "2026-04-14T11:08:12.535Z" }, + { url = "https://files.pythonhosted.org/packages/a9/27/7cc3c2d284698647f60f3b823e0101e619c87edf158d47ee11bf4bfb6228/zensical-0.0.33-cp310-abi3-win32.whl", hash = "sha256:2701820597fe19361a12371129927c58c19633dcaa5f6986d610dce58cecd8c4", size = 12012664, upload-time = "2026-04-14T11:08:14.977Z" }, + { url = "https://files.pythonhosted.org/packages/25/0b/6be5c2fdaf9f1600577e7ba5e235d86b72a26f6af389efb146f978f76ac3/zensical-0.0.33-cp310-abi3-win_amd64.whl", hash = "sha256:a5a0911b4247708a55951b74c459f4d5faec5daaf287d23a2e1f0d96be1e647f", size = 12206255, upload-time = "2026-04-14T11:08:17.375Z" }, +] + [[package]] name = "zeromq" version = "4.3.5" diff --git a/zensical.toml b/zensical.toml new file mode 100644 index 00000000000..f08ee05c632 --- /dev/null +++ b/zensical.toml @@ -0,0 +1,67 @@ +[project] +site_name = "openpilot docs" +site_url = "https://docs.comma.ai" +repo_url = "https://github.com/commaai/openpilot/" + +docs_dir = "docs" +site_dir = "docs_site/" + +extra_css = ["stylesheets/extra.css"] + +nav = [ + { "Home" = "index.md" }, + { "Getting Started" = [ + { "What is openpilot?" = "getting-started/what-is-openpilot.md" }, + ] }, + { "How-to" = [ + { "Turn the speed blue" = "how-to/turn-the-speed-blue.md" }, + { "Connect to a comma 3X" = "how-to/connect-to-comma.md" }, + ] }, + { "Concepts" = [ + { "Logs" = "concepts/logs.md" }, + { "Safety" = "concepts/safety.md" }, + { "Glossary" = "concepts/glossary.md" }, + ] }, + { "Car Porting" = [ + { "What is a car port?" = "car-porting/what-is-a-car-port.md" }, + { "Porting a car brand" = "car-porting/brand-port.md" }, + { "Porting a car model" = "car-porting/model-port.md" }, + ] }, + { "Contributing" = [ + { "Roadmap" = "contributing/roadmap.md" }, + { "Contributing Guide →" = "https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md" }, + ] }, + { "Links" = [ + { "Blog →" = "https://blog.comma.ai" }, + { "Bounties →" = "https://comma.ai/bounties" }, + { "GitHub →" = "https://github.com/commaai" }, + { "Discord →" = "https://discord.comma.ai" }, + { "X →" = "https://x.com/comma_ai" }, + ] }, +] + +[project.theme] +logo = "assets/comma-logo.png" +features = [ + "navigation.expand", + "navigation.sections", + "navigation.instant", + "navigation.instant.prefetch", + "content.code.copy", + "content.action.edit", + "content.action.view", +] + +[[project.extra.social]] +icon = "fontawesome/brands/github" +link = "https://github.com/commaai" + +[[project.extra.social]] +icon = "fontawesome/brands/discord" +link = "https://discord.comma.ai" + +[[project.extra.social]] +icon = "fontawesome/brands/x-twitter" +link = "https://x.com/comma_ai" + +[project.markdown_extensions."ext.glossary"] From 9042cfa1ad8879518bf2936aac695ee591e96089 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 15 Apr 2026 15:55:37 -0700 Subject: [PATCH 648/685] rm home --- docs/ext/glossary.py | 4 ++-- zensical.toml | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/ext/glossary.py b/docs/ext/glossary.py index d4c3ebd8fd2..668d777de6e 100644 --- a/docs/ext/glossary.py +++ b/docs/ext/glossary.py @@ -150,8 +150,8 @@ def _pieces(self, text: str, glossary_href: str) -> list[str | ET.Element]: best = candidate continue - _, best_start, best_end = best - _, current_start, current_end = candidate + _, _, best_start, best_end = best + _, _, current_start, current_end = candidate if current_start < best_start: best = candidate continue diff --git a/zensical.toml b/zensical.toml index f08ee05c632..0c897f9a2e1 100644 --- a/zensical.toml +++ b/zensical.toml @@ -9,7 +9,6 @@ site_dir = "docs_site/" extra_css = ["stylesheets/extra.css"] nav = [ - { "Home" = "index.md" }, { "Getting Started" = [ { "What is openpilot?" = "getting-started/what-is-openpilot.md" }, ] }, From 84c276bb6c145a151920943398e2a3ca0de618b2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 15 Apr 2026 18:37:23 -0700 Subject: [PATCH 649/685] misc docs updates (#37839) * that's home * more extensions * lil fixups * lil more * collapse car ports * fix links --- docs/car-porting/brand-port.md | 5 -- docs/car-porting/car-state-signals.md | 65 -------------- docs/car-porting/model-port.md | 5 -- docs/car-porting/reverse-engineering.md | 85 ------------------- docs/concepts/logs.md | 10 +-- docs/contributing/roadmap.md | 14 --- docs/ext/glossary.py | 3 +- .../car-port.md} | 20 ++++- docs/how-to/connect-to-comma.md | 19 +++-- .../what-is-openpilot.md => index.md} | 0 zensical.toml | 32 +++++-- 11 files changed, 55 insertions(+), 203 deletions(-) delete mode 100644 docs/car-porting/brand-port.md delete mode 100644 docs/car-porting/car-state-signals.md delete mode 100644 docs/car-porting/model-port.md delete mode 100644 docs/car-porting/reverse-engineering.md rename docs/{car-porting/what-is-a-car-port.md => how-to/car-port.md} (65%) rename docs/{getting-started/what-is-openpilot.md => index.md} (100%) diff --git a/docs/car-porting/brand-port.md b/docs/car-porting/brand-port.md deleted file mode 100644 index a3daa7a8485..00000000000 --- a/docs/car-porting/brand-port.md +++ /dev/null @@ -1,5 +0,0 @@ -# Developing a car brand port - -A brand port is a port of openpilot to a substantially new car brand or platform within a brand. - -Here's an example of one: https://github.com/commaai/openpilot/pull/23331. diff --git a/docs/car-porting/car-state-signals.md b/docs/car-porting/car-state-signals.md deleted file mode 100644 index 669bd0ee237..00000000000 --- a/docs/car-porting/car-state-signals.md +++ /dev/null @@ -1,65 +0,0 @@ -# CarState signals - -## Required for basic lateral control - -* `brakePressed` -* `cruiseState` -* `doorOpen` -* `espDisabled` -* `gasPressed` -* `gearShifter` -* `leftBlinker` / `rightBlinker` -* `seatbeltUnlatched` -* `standstill` -* `steeringAngleDeg` -* `steeringPressed` -* `steeringTorque` -* `steerFaultPermanent` -* `steerFaultTemporary` -* `vCruise` -* `wheelSpeeds.[fl|fr|rl|rr]`: Speed of each of the car's four wheels, in m/s. The car's CAN bus often broadcasts the -speed in kph, so the helper function `parse_wheel_speeds` performs this conversion by default. - -## Recommended / Required for openpilot longitudinal control - -* `accFaulted` -* `espActive` -* `parkingBrake` - -## Application Dependent - -* `blockPcmEnable` -* `buttonEnable` -* `brakeHoldActive` -* `carFaultedNonCritical` -* `invalidLkasSetting` -* `lowSpeedAlert` -* `regenBraking` -* `steeringAngleOffsetDeg` -* `steeringDisengage` -* `steeringTorqueEps` -* `stockLkas` -* `vCruiseCluster` -* `vEgoCluster` -* `vehicleSensorsInvalid` - -## Automatically populated - -* `buttonEvents` - -These values are populated automatically by `parse_wheel_speeds`: - -* `aEgo`: Acceleration of the ego vehicle, Kalman filtered derivative of `vEgo`. -* `vEgo`: Speed of the ego vehicle, Kalman filtered from `vEgoRaw`. -* `vEgoRaw`: Speed of the ego vehicle, based on the average of all four wheel speeds, unfiltered. - -## Optional - -* `brake` -* `charging` -* `fuelGauge` -* `leftBlindspot` / `rightBlindspot` -* `steeringRateDeg` -* `stockAeb` -* `stockFcw` -* `yawRate` diff --git a/docs/car-porting/model-port.md b/docs/car-porting/model-port.md deleted file mode 100644 index e148a40ecb1..00000000000 --- a/docs/car-porting/model-port.md +++ /dev/null @@ -1,5 +0,0 @@ -# Developing a car model port - -A model port is a port of openpilot to a new car model within an already supported brand. Model ports are easier than brand ports because the car's existing APIs are already known. - -Here's an example of one: https://github.com/commaai/openpilot/pull/30672/. diff --git a/docs/car-porting/reverse-engineering.md b/docs/car-porting/reverse-engineering.md deleted file mode 100644 index 128ec8e7769..00000000000 --- a/docs/car-porting/reverse-engineering.md +++ /dev/null @@ -1,85 +0,0 @@ -# Stimulus-Response Tests - -These are example test drives that can help identify the CAN bus messaging necessary for ADAS control. Each scripted -test should be done in a separate route (ignition cycle). These tests are a guide, not necessarily exhaustive. - -While testing, constant power to the comma device is highly recommended, using [comma power](https://comma.ai/shop/comma-power) if -necessary to make sure all test activity is fully captured and for ease of uploading. If constant power isn't -available, keep the ignition on for at least one minute after your test to make sure power loss doesn't result -in loss of the last minute of testing data. - -## Stationary ignition-only tests, part 1 - -1. Ignition on, but don't start engine, remain in Park -2. Open and close each door in a defined order: driver, passenger, rear left, rear right -3. Re-enter the vehicle, close the driver's door, and fasten the driver's seatbelt -4. Slowly press and release the accelerator pedal 3 times -5. Slowly press and release the brake pedal 3 times -6. Hold the brake and move the gearshift to reverse, then neutral, then drive, then sport/eco/etc if applicable -7. Return to Park, ignition off - -Brake-pressed information may show up in several messages and signals, both as on/off states and as a percentage or -pressure. It may reflect a switch on the driver's brake pedal, or a pressure-threshold state, or signals to turn on -the rear brake lights. Start by identifying all the potential signals, and confirm while driving with ACC later. - -Locate signals for all four door states if possible, but some cars only expose the driver's door state on the ADAS bus. -Driver/passenger door signals may or may not change positions for LHD vs RHD cars. For cars where only the driver's -door signal is available, the same signal may follow the driver. - -## Stationary ignition-only tests, part 2 - -1. Ignition on, but don't start engine, remain in Park -2. Press each ACC button in a defined order: main switch on/off, set, resume, cancel, accel, decel, gap adjust -3. Set the left turn signal for about five seconds -4. Operate the left turn signal one time in its touch-to-pass mode -5. Set the right turn signal for about five seconds -6. Operate the right turn signal one time in its touch-to-pass mode -7. Set the hazard / emergency indicator switch for about five seconds -8. Ignition off - -Your vehicle may have a momentary-press main ACC switch or a physical toggle that remains set. Actual ACC engagement -isn't necessary for purposes of detecting the ACC button presses. - -## Steering angle and steering torque tests - -Power steering should be available. On ICE cars, engine RPM may be present. - -1. Ignition on, start engine if applicable, remain in Park -2. Rotate the steering wheel as follows, with a few seconds pause between each step - * Start as close to exact center as possible - * Turn to 45 degrees right and hold - * Turn to 90 degrees right and hold - * Turn to 180 degrees right and hold - * Turn to full lock right and hold, with firm pressure against lock - * Release the wheel and allow it to bounce back slightly from lock - * Turn to 180 degrees left and hold - * Return to center and release -3. Ignition off - -Performing the full test to the right, followed by an abbreviated test to the left, helps give additional confirmation -of signal scale, and sign/direction for both the steering wheel angle and driver input torque signals. - -## Low speed / parking lot driving tests - -Before this test, drive to a place like an empty parking lot where you are free to drive in a series of curves. - -1. Ignition on, start engine if applicable, prepare to drive -2. Slowly (10-20mph at most) drive a figure-8 if possible, or at least one sharp left and one sharp right. -3. Come to a complete stop -4. When and where safe, drive in reverse for a short distance (10-15 feet) -5. Park the car in a safe place, ignition off - -## High speed / highway driving tests - -Select a place and time where you can safely set cruise control at normal travel speeds with little interference from -traffic ahead, and safely test the response of your factory lane guidance system. - -1. Ignition on, start engine if applicable, prepare to drive -2. When safely able, engage adaptive cruise control below 50 mph -3. When safely able, use the ACC buttons to accelerate to 50mph, then 55mph, then 60mph -4. Disengage adaptive cruise -5. When safely able, allow your factory lane guidance to prevent lane departures, 2-3 times on both the left and right - -The series of setpoints can be adjusted to local traffic regulations, and of course metric units. The specific cruise -setpoints are useful for locating the ACC HUD signals later, and confirming their precise scaling. When the car reaches -and holds the setpoint, that can also provide additional confirmation of wheel speed scaling. diff --git a/docs/concepts/logs.md b/docs/concepts/logs.md index 46ab2897df9..e533d362972 100644 --- a/docs/concepts/logs.md +++ b/docs/concepts/logs.md @@ -6,9 +6,9 @@ Check out our [Python library](https://github.com/commaai/openpilot/blob/master/ For each segment, openpilot records the following log types: -## rlog.bz2 +## rlog.zst -rlogs contain all the messages passed amongst openpilot's processes. See [cereal/services.py](https://github.com/commaai/cereal/blob/master/services.py) for a list of all the logged services. They're a bzip2 archive of the serialized capnproto messages. +rlogs contain all the messages passed amongst openpilot's processes. See [cereal/services.py](https://github.com/commaai/openpilot/blob/master/cereal/services.py) for a list of all the logged services. They're a zstd archive of the serialized [Cap’n Proto](https://capnproto.org/) messages. ## {f,e,d}camera.hevc @@ -18,12 +18,10 @@ Each camera stream is H.265 encoded and written to its respective file. * `ecamera.hevc` is the wide road camera * `dcamera.hevc` is the driver camera -## qlog.bz2 & qcamera.ts +## qlog.zst & qcamera.ts qlogs are a decimated subset of the rlogs. Check out [cereal/services.py](https://github.com/commaai/cereal/blob/master/services.py) for the decimation. - qcameras are H.264 encoded, lower res versions of the fcamera.hevc. The video shown in [comma connect](https://connect.comma.ai/) is from the qcameras. - -qlogs and qcameras are designed to be small enough to upload instantly on slow internet and store forever, yet useful enough for most analysis and debugging. +qlogs and qcameras are designed to be small enough to upload instantly on slow internet, yet useful enough for most analysis and debugging. diff --git a/docs/contributing/roadmap.md b/docs/contributing/roadmap.md index 1262017a0b8..ae27a5461ca 100644 --- a/docs/contributing/roadmap.md +++ b/docs/contributing/roadmap.md @@ -7,25 +7,11 @@ This is the roadmap for the next major openpilot releases. Also check out * [Bounties](https://comma.ai/bounties) for paid individual issues * [#current-projects](https://discord.com/channels/469524606043160576/1249579909739708446) in Discord for discussion on work-in-progress projects -## openpilot 0.10 - -openpilot 0.10 will be the first release with a driving policy trained in -a [learned simulator](https://youtu.be/EqQNZXqzFSI). - -* Driving model trained in a learned simulator -* Always-on driver monitoring (behind a toggle) -* GPS removed from the driving stack -* 100KB qlogs -* `nightly` pushed after 1000 hours of hardware-in-the-loop testing -* Car interface code moved into [opendbc](https://github.com/commaai/opendbc) -* openpilot on PC for Linux x86, Linux arm64, and Mac (Apple Silicon) - ## openpilot 1.0 openpilot 1.0 will feature a fully end-to-end driving policy. * End-to-end longitudinal control in Chill mode -* Automatic Emergency Braking (AEB) * Driver monitoring with sleep detection * Rolling updates/releases pushed out by CI * [panda safety 1.0](https://github.com/orgs/commaai/projects/27) diff --git a/docs/ext/glossary.py b/docs/ext/glossary.py index 668d777de6e..35c92add10b 100644 --- a/docs/ext/glossary.py +++ b/docs/ext/glossary.py @@ -108,7 +108,8 @@ def _replace(self, parent: ET.Element, glossary_href: str, index: int | None = N if child is None: parent.text = pieces[0] if isinstance(pieces[0], str) else "" - insert_at = 0 if isinstance(pieces[0], str) else -1 + # Insert replacements for parent.text before the first existing child. + insert_at = -1 else: assert index is not None child.tail = pieces[0] if isinstance(pieces[0], str) else "" diff --git a/docs/car-porting/what-is-a-car-port.md b/docs/how-to/car-port.md similarity index 65% rename from docs/car-porting/what-is-a-car-port.md rename to docs/how-to/car-port.md index 3480e4e5d5c..ca565e53f6d 100644 --- a/docs/car-porting/what-is-a-car-port.md +++ b/docs/how-to/car-port.md @@ -8,7 +8,7 @@ A car port enables openpilot support on a particular car. Each car model openpil # Structure of a car port -Virtually all car-specific code is contained in two other repositories: [opendbc](https://github.com/commaai/opendbc) and [panda](https://github.com/commaai/panda). +All car-specific code is contained in the [opendbc](https://github.com/commaai/opendbc) project. ## opendbc @@ -23,8 +23,8 @@ Each car brand is supported by a standard interface structure in `opendbc/car/[b ## safety -* `opendbc_repo/opendbc/safety/modes/[brand].h`: Brand-specific safety logic -* `opendbc_repo/opendbc/safety/tests/test_[brand].py`: Brand-specific safety CI tests +* `opendbc/safety/modes/[brand].h`: Brand-specific safety logic +* `opendbc/safety/tests/test_[brand].py`: Brand-specific safety CI tests ## openpilot @@ -32,8 +32,20 @@ For historical reasons, openpilot still contains a small amount of car-specific * `selfdrive/car/car_specific.py`: Brand-specific event logic -# Overview +# How do I port car? [Jason Young](https://github.com/jyoung8607) gave a talk at COMMA_CON with an overview of the car porting process. The talk is available on YouTube: https://www.youtube.com/watch?v=XxPS5TpTUnI + +## Brand Port + +A brand port is a port of openpilot to a substantially new car brand or platform within a brand. + +Here's an example of one: https://github.com/commaai/openpilot/pull/23331. + +## Model Port + +A model port is a port of openpilot to a new car model within an already supported brand. Model ports are easier than brand ports because the car's existing APIs are already known. + +Here's an example of one: https://github.com/commaai/openpilot/pull/30672/. diff --git a/docs/how-to/connect-to-comma.md b/docs/how-to/connect-to-comma.md index 58d4f91bb2b..e4e322f111b 100644 --- a/docs/how-to/connect-to-comma.md +++ b/docs/how-to/connect-to-comma.md @@ -1,15 +1,15 @@ -# connect to a comma four +# connect to a comma 3X or comma four -A comma four is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). +A comma device is a normal [Linux](https://github.com/commaai/agnos-builder) computer that exposes [SSH](https://wiki.archlinux.org/title/Secure_Shell) and a [serial console](https://wiki.archlinux.org/title/Working_with_the_serial_console). ## Serial Console -On both the comma three and comma four, the serial console is accessible from the main OBD-C port. -Connect the comma four to your computer with a normal USB C cable, or use a [comma serial](https://comma.ai/shop/comma-serial) for steady 12V power. +On the comma 3X, the serial console is accessible from the main OBD-C port, forwarded through the panda. +Access it using `panda/scripts/som_debug.sh`. -On the comma three, the serial console is exposed through a UART-to-USB chip, and `tools/scripts/serial.sh` can be used to connect. +comma four also exposes a serial console, albeit through an internal debug connector. Dedicated debug hardware coming soon to the comma shop. -On the comma four, the serial console is accessible through the [panda](https://github.com/commaai/panda) using the `panda/tests/som_debug.sh` script. +Login to the default user with: * Username: `comma` * Password: `comma` @@ -25,7 +25,7 @@ In order to SSH into your device, you'll need a GitHub account with SSH keys. Se * Port: `22` Here's an example command for connecting to your device using its tethered connection:
-`ssh comma@192.168.43.1` +`ssh comma@192.168.43.1 -i ~/.ssh/my_github_key` For doing development work on device, it's recommended to use [SSH agent forwarding](https://docs.github.com/en/developers/overview/using-ssh-agent-forwarding). @@ -45,7 +45,7 @@ In order to use ADB on your device, you'll need to perform the following steps u * Here's an example command for connecting to your device using its tethered connection: `adb connect 192.168.43.1:5555` > [!NOTE] -> The default port for ADB is 5555 on the comma four. +> The default port for ADB is 5555. For more info on ADB, see the [Android Debug Bridge (ADB) documentation](https://developer.android.com/tools/adb). @@ -55,7 +55,7 @@ The public keys are only fetched from your GitHub account once. In order to upda The `id_rsa` key in this directory only works while your device is in the setup state with no software installed. After installation, that default key will be removed. -#### ssh.comma.ai proxy +## ssh.comma.ai proxy With a [comma prime subscription](https://comma.ai/connect), you can SSH into your comma device from anywhere. @@ -79,6 +79,7 @@ Host ssh.comma.ai ``` ssh -i ~/.ssh/my_github_key -o ProxyCommand="ssh -i ~/.ssh/my_github_key -W %h:%p -p %p %h@ssh.comma.ai" comma@ffffffffffffffff ``` + (Replace `ffffffffffffffff` with your dongle_id) ### ssh.comma.ai host key fingerprint diff --git a/docs/getting-started/what-is-openpilot.md b/docs/index.md similarity index 100% rename from docs/getting-started/what-is-openpilot.md rename to docs/index.md diff --git a/zensical.toml b/zensical.toml index 0c897f9a2e1..7e30c547a06 100644 --- a/zensical.toml +++ b/zensical.toml @@ -9,23 +9,17 @@ site_dir = "docs_site/" extra_css = ["stylesheets/extra.css"] nav = [ - { "Getting Started" = [ - { "What is openpilot?" = "getting-started/what-is-openpilot.md" }, - ] }, + { "What is openpilot?" = "index.md" }, { "How-to" = [ { "Turn the speed blue" = "how-to/turn-the-speed-blue.md" }, - { "Connect to a comma 3X" = "how-to/connect-to-comma.md" }, + { "Connect to a comma 3X or four" = "how-to/connect-to-comma.md" }, + { "Add support for a car" = "how-to/car-port.md" }, ] }, { "Concepts" = [ { "Logs" = "concepts/logs.md" }, { "Safety" = "concepts/safety.md" }, { "Glossary" = "concepts/glossary.md" }, ] }, - { "Car Porting" = [ - { "What is a car port?" = "car-porting/what-is-a-car-port.md" }, - { "Porting a car brand" = "car-porting/brand-port.md" }, - { "Porting a car model" = "car-porting/model-port.md" }, - ] }, { "Contributing" = [ { "Roadmap" = "contributing/roadmap.md" }, { "Contributing Guide →" = "https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md" }, @@ -63,4 +57,24 @@ link = "https://discord.comma.ai" icon = "fontawesome/brands/x-twitter" link = "https://x.com/comma_ai" +[project.markdown_extensions.attr_list] + +[project.markdown_extensions.admonition] + +[project.markdown_extensions.md_in_html] + +[project.markdown_extensions.pymdownx.highlight] +anchor_linenums = true +line_spans = "__span" +pygments_lang_class = true + +[project.markdown_extensions.pymdownx.inlinehilite] + +[project.markdown_extensions.pymdownx.magiclink] + +[project.markdown_extensions.pymdownx.superfences] +custom_fences = [{ name = "mermaid", class = "mermaid" }] + +[project.markdown_extensions.pymdownx.details] + [project.markdown_extensions."ext.glossary"] From 752fe031182f5707c2bb7b10676f9b52987e9de0 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 15 Apr 2026 19:13:30 -0700 Subject: [PATCH 650/685] docs: add feedback guide (#37842) * docs: add feedback guide * lil more feedback * the --- docs/contributing/feedback.md | 36 +++++++++++++++++++++++++++++++++++ zensical.toml | 1 + 2 files changed, 37 insertions(+) create mode 100644 docs/contributing/feedback.md diff --git a/docs/contributing/feedback.md b/docs/contributing/feedback.md new file mode 100644 index 00000000000..335d24e13a8 --- /dev/null +++ b/docs/contributing/feedback.md @@ -0,0 +1,36 @@ +# How to Give Feedback + +Feedback is one of the highest leverage ways to contribute to openpilot as a user. + +## Driving + +Got feedback about how your car drives? +Join the community Discord, then use the form in `#submit-feedback`. + +Before posting feedback, please ensure: + +- **openpilot is up to date** you should be on the latest openpilot release or nightly +- **both road-facing cameras have a clear view** your windshield is clean, lenses are clean, etc. +- **your device is mounted properly** your device must be mounted horizontally center and relatively high on the windshield + +## Driver Monitoring + +If you find DM annoying while being perfectly attentive, these are likely false positives and we want to fix them! +In general, driver monitoring feedback is very actionable, and we can fix your complaint within a release cycle. + +To post your feedback: + +1. Join the [community Discord](https://discord.comma.ai). +2. If driver camera recording is toggled off, temporarily enable driver camera recording in the settings until you reproduce the issue. +3. Using comma connect, identify the relevant segment and upload the segment's logs and driver camera. +4. Post the segment in the `#openpilot-experience` channel on Discord with a good description. + +Before posting feedback, please ensure: + +- **openpilot is up to date** you should be on the latest openpilot release or nightly +- **the driver camera has a clear view of the driver** ensure nothing blocks view of the driver (e.g. a cable), the lens is clean, etc. +- **your device is mounted properly** your device must be mounted horizontally center and relatively high on the windshield + +## Other bugs + +Got an issue with something else? Open an issue on our [GitHub issue tracker](https://github.com/commaai/openpilot/issues/new/choose). diff --git a/zensical.toml b/zensical.toml index 7e30c547a06..7e5ca2c5db4 100644 --- a/zensical.toml +++ b/zensical.toml @@ -21,6 +21,7 @@ nav = [ { "Glossary" = "concepts/glossary.md" }, ] }, { "Contributing" = [ + { "Feedback" = "contributing/feedback.md" }, { "Roadmap" = "contributing/roadmap.md" }, { "Contributing Guide →" = "https://github.com/commaai/openpilot/blob/master/docs/CONTRIBUTING.md" }, ] }, From c001f3c9b490a80e69539f0af6022f6e07ceb721 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 15 Apr 2026 22:06:54 -0700 Subject: [PATCH 651/685] esim: implement download profile (#37806) --- system/hardware/tici/lpa.py | 262 +++++++++++++++++++++++++++++++++++- 1 file changed, 257 insertions(+), 5 deletions(-) diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index 04cac139272..e46ac005b80 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -3,6 +3,7 @@ import atexit import base64 import fcntl +import hashlib import math import os import requests @@ -43,15 +44,24 @@ # TLV Tags TAG_ICCID = 0x5A TAG_STATUS = 0x80 -TAG_PROFILE_INFO_LIST = 0xBF2D -TAG_SET_NICKNAME = 0xBF29 -TAG_ENABLE_PROFILE = 0xBF31 -TAG_DELETE_PROFILE = 0xBF33 +TAG_EUICC_INFO = 0xBF20 +TAG_PREPARE_DOWNLOAD = 0xBF21 +TAG_BPP_COMMAND = 0xBF23 +TAG_PROFILE_METADATA = 0xBF25 +TAG_INSTALL_RESULT_DATA = 0xBF27 TAG_LIST_NOTIFICATION = 0xBF28 +TAG_SET_NICKNAME = 0xBF29 TAG_RETRIEVE_NOTIFICATION = 0xBF2B +TAG_PROFILE_INFO_LIST = 0xBF2D +TAG_EUICC_CHALLENGE = 0xBF2E TAG_NOTIFICATION_METADATA = 0xBF2F TAG_NOTIFICATION_SENT = 0xBF30 +TAG_ENABLE_PROFILE = 0xBF31 +TAG_DELETE_PROFILE = 0xBF33 +TAG_BPP = 0xBF36 TAG_PROFILE_INSTALL_RESULT = 0xBF37 +TAG_AUTH_SERVER = 0xBF38 +TAG_CANCEL_SESSION = 0xBF41 TAG_OK = 0xA0 PROFILE_OK = 0x00 @@ -63,6 +73,29 @@ 0x03: "disallowedByPolicy", 0x04: "wrongProfileReenabling", PROFILE_CAT_BUSY: "catBusy", 0x06: "undefinedError", } +AUTH_SERVER_ERROR_CODES = { + 0x01: "eUICCVerificationFailed", 0x02: "eUICCCertificateExpired", + 0x03: "eUICCCertificateRevoked", 0x05: "invalidServerSignature", + 0x06: "euiccCiPKUnknown", 0x0A: "matchingIdRefused", + 0x10: "insufficientMemory", +} +BPP_COMMAND_NAMES = { + 0: "initialiseSecureChannel", 1: "configureISDP", 2: "storeMetadata", + 3: "storeMetadata2", 4: "replaceSessionKeys", 5: "loadProfileElements", +} +BPP_ERROR_REASONS = { + 1: "incorrectInputValues", 2: "invalidSignature", 3: "invalidTransactionId", + 4: "unsupportedCrtValues", 5: "unsupportedRemoteOperationType", + 6: "unsupportedProfileClass", 7: "scp03tStructureError", 8: "scp03tSecurityError", + 9: "iccidAlreadyExistsOnEuicc", 10: "insufficientMemoryForProfile", + 11: "installInterrupted", 12: "peProcessingError", 13: "dataMismatch", + 14: "invalidNAA", +} +BPP_ERROR_MESSAGES = { + 9: "This eSIM profile is already installed on this device.", + 10: "Not enough memory on the eUICC to install this profile.", + 12: "Profile installation failed. The QR code may have already been used.", +} # SGP.22 §5.2.6 — SM-DP+ reason/subject codes mapped to user-friendly messages ES9P_ERROR_MESSAGES: dict[tuple[str, str], str] = { @@ -459,6 +492,222 @@ def process_notifications(client: AtClient) -> None: print(f"notification {seq_number} failed: {e}", file=sys.stderr) +# --- Authentication & Download --- + +def get_challenge_and_info(client: AtClient) -> tuple[bytes, bytes]: + challenge_resp = es10x_command(client, encode_tlv(TAG_EUICC_CHALLENGE, b"")) + challenge = require_tag(require_tag(challenge_resp, TAG_EUICC_CHALLENGE, "GetEuiccDataResponse"), + TAG_STATUS, "challenge in response") + info_resp = es10x_command(client, encode_tlv(TAG_EUICC_INFO, b"")) + require_tag(info_resp, TAG_EUICC_INFO, "GetEuiccInfo1Response") + return challenge, info_resp + + +def authenticate_server(client: AtClient, b64_signed1: str, b64_sig1: str, b64_pk_id: str, b64_cert: str, matching_id: str) -> str: + tac = bytes([0x35, 0x29, 0x06, 0x11]) + device_info = encode_tlv(TAG_STATUS, tac) + encode_tlv(0xA1, b"") + ctx_inner = encode_tlv(TAG_STATUS, matching_id.encode("utf-8")) + encode_tlv(0xA1, device_info) + content = b64d(b64_signed1) + b64d(b64_sig1) + b64d(b64_pk_id) + b64d(b64_cert) + encode_tlv(0xA0, ctx_inner) + response = es10x_command(client, encode_tlv(TAG_AUTH_SERVER, content)) + root = require_tag(response, TAG_AUTH_SERVER, "AuthenticateServerResponse") + error_tag = find_tag(root, 0xA1) + if error_tag is not None: + code = int.from_bytes(error_tag, "big") if error_tag else 0 + raise RuntimeError(f"AuthenticateServer rejected by eUICC: {AUTH_SERVER_ERROR_CODES.get(code, 'unknown')} (0x{code:02X})") + return b64e(response) + + +def prepare_download(client: AtClient, b64_signed2: str, b64_sig2: str, b64_cert: str, cc: str | None = None) -> str: + smdp_signed2 = b64d(b64_signed2) + smdp_signature2 = b64d(b64_sig2) + smdp_certificate = b64d(b64_cert) + smdp_signed2_root = find_tag(smdp_signed2, 0x30) + if smdp_signed2_root is None: + raise RuntimeError("Invalid smdpSigned2") + transaction_id = find_tag(smdp_signed2_root, TAG_STATUS) + cc_required_flag = find_tag(smdp_signed2_root, 0x01) + if transaction_id is None or cc_required_flag is None: + raise RuntimeError("Invalid smdpSigned2") + content = smdp_signed2 + smdp_signature2 + if int.from_bytes(cc_required_flag, "big") != 0: + if not cc: + raise RuntimeError("Confirmation code required but not provided") + content += encode_tlv(0x04, hashlib.sha256(hashlib.sha256(cc.encode("utf-8")).digest() + transaction_id).digest()) + content += smdp_certificate + response = es10x_command(client, encode_tlv(TAG_PREPARE_DOWNLOAD, content)) + require_tag(response, TAG_PREPARE_DOWNLOAD, "PrepareDownloadResponse") + return b64e(response) + + +def _parse_tlv_header_len(data: bytes) -> int: + tag_len = 2 if data[0] & 0x1F == 0x1F else 1 + length_byte = data[tag_len] + return tag_len + (1 + (length_byte & 0x7F) if length_byte & 0x80 else 1) + + +def _split_bpp(bpp: bytes) -> list[bytes]: + """Split a BoundProfilePackage into APDU chunks per SGP.22 §5.7.6.""" + root_value = None + for tag, value, start, end in iter_tlv(bpp, with_positions=True): + if tag == TAG_BPP: + root_value = value + val_start = start + _parse_tlv_header_len(bpp[start:end]) + break + if root_value is None: + raise RuntimeError("Invalid BoundProfilePackage") + + chunks: list[bytes] = [] + for tag, value, start, end in iter_tlv(root_value, with_positions=True): + if tag == TAG_BPP_COMMAND: + chunks.append(bpp[0 : val_start + end]) + elif tag in (0xA0, 0xA2): + chunks.append(bpp[val_start + start : val_start + end]) + elif tag in (0xA1, 0xA3): + hdr_len = _parse_tlv_header_len(root_value[start:end]) + chunks.append(bpp[val_start + start : val_start + start + hdr_len]) + for _, _, cs, ce in iter_tlv(value, with_positions=True): + chunks.append(value[cs:ce]) + return chunks + + +def _parse_install_result(response: bytes) -> dict[str, Any] | None: + """Parse a ProfileInstallResult from an APDU response, or None if not present.""" + root = find_tag(response, TAG_PROFILE_INSTALL_RESULT) + if not root: + return None + result_data = find_tag(root, TAG_INSTALL_RESULT_DATA) + if not result_data: + return None + result: dict[str, Any] = {"seqNumber": 0, "success": False, "bppCommandId": None, "errorReason": None} + notif_meta = find_tag(result_data, TAG_NOTIFICATION_METADATA) + if notif_meta: + seq_num = find_tag(notif_meta, TAG_STATUS) + if seq_num: + result["seqNumber"] = int.from_bytes(seq_num, "big") + final_result = find_tag(result_data, 0xA2) + if final_result: + for tag, value in iter_tlv(final_result): + if tag == 0xA0: + result["success"] = True + elif tag == 0xA1: + bpp_cmd = find_tag(value, TAG_STATUS) + if bpp_cmd: + result["bppCommandId"] = int.from_bytes(bpp_cmd, "big") + err = find_tag(value, 0x81) + if err: + result["errorReason"] = int.from_bytes(err, "big") + return result + + +def load_bpp(client: AtClient, b64_bpp: str) -> dict: + bpp = b64d(b64_bpp) + result = None + for chunk in _split_bpp(bpp): + response = es10x_command(client, chunk) + if response: + result = _parse_install_result(response) or result + + if result is None: + raise RuntimeError("Profile installation failed: no result from eUICC") + if not result["success"] and result["errorReason"] is not None: + msg = BPP_ERROR_MESSAGES.get(result["errorReason"]) + if not msg: + cmd_name = BPP_COMMAND_NAMES.get(result["bppCommandId"], f"unknown({result['bppCommandId']})") + err_name = BPP_ERROR_REASONS.get(result["errorReason"], f"unknown({result['errorReason']})") + msg = f"Profile installation failed at {cmd_name}: {err_name}" + raise RuntimeError(msg) + if not result["success"]: + raise RuntimeError("Profile installation failed: no result from eUICC") + return result + + +def parse_metadata(b64_metadata: str) -> dict: + root = find_tag(b64d(b64_metadata), TAG_PROFILE_METADATA) + if root is None: + raise RuntimeError("Invalid profileMetadata") + return decode_struct(root, PROFILE) + + +def cancel_session(client: AtClient, transaction_id: bytes, reason: int = 127) -> str: + content = encode_tlv(0x80, transaction_id) + encode_tlv(0x81, bytes([reason])) + response = es10x_command(client, encode_tlv(TAG_CANCEL_SESSION, content)) + return b64e(response) + + +def parse_lpa_activation_code(activation_code: str) -> tuple[str, str]: + """Parse 'LPA:1$smdp.example.com$MATCHING-ID' into (smdp_address, matching_id).""" + if not activation_code.startswith("LPA:"): + raise ValueError("Invalid activation code format") + parts = activation_code[4:].split("$") + if len(parts) != 3: + raise ValueError("Invalid activation code format") + return parts[1], parts[2] + + +def _b64_field(data: dict, key: str) -> str: + return base64_trim(data[key]) + + +def _cancel_session_safe(client: AtClient, smdp: str, tx_id: str, session: requests.Session) -> None: + b64_cancel = "" + try: + b64_cancel = cancel_session(client, b64d(tx_id)) + except Exception: + pass + try: + es9p_request(smdp, "cancelSession", {"transactionId": tx_id, "cancelSessionResponse": b64_cancel}, "CancelSession", session=session) + except Exception: + pass + + +def download_profile(client: AtClient, activation_code: str) -> str: + """Download and install an eSIM profile. Returns the ICCID of the installed profile.""" + if not system_time_valid(): + raise RuntimeError("System time is not set; TLS certificate validation requires a valid clock") + smdp, matching_id = parse_lpa_activation_code(activation_code) + challenge, euicc_info = get_challenge_and_info(client) + session = requests.Session() + tx_id = None + + try: + # step 1: initiate authentication + auth = es9p_request(smdp, "initiateAuthentication", { + "smdpAddress": smdp, "euiccChallenge": b64e(challenge), + "euiccInfo1": b64e(euicc_info), "matchingId": matching_id, + }, "Authentication", session=session) + tx_id = _b64_field(auth, "transactionId") + + # step 2: authenticate server + b64_auth = authenticate_server(client, + _b64_field(auth, "serverSigned1"), _b64_field(auth, "serverSignature1"), + _b64_field(auth, "euiccCiPKIdToBeUsed"), _b64_field(auth, "serverCertificate"), + matching_id) + + # step 3: authenticate client + get metadata + cli = es9p_request(smdp, "authenticateClient", { + "transactionId": tx_id, "authenticateServerResponse": b64_auth, + }, "Authentication", session=session) + iccid = parse_metadata(_b64_field(cli, "profileMetadata"))["iccid"] + + # step 4: prepare download + b64_prep = prepare_download(client, + _b64_field(cli, "smdpSigned2"), _b64_field(cli, "smdpSignature2"), + _b64_field(cli, "smdpCertificate")) + + # step 5: get and install bound profile package + bpp = es9p_request(smdp, "getBoundProfilePackage", { + "transactionId": tx_id, "prepareDownloadResponse": b64_prep, + }, "GetBoundProfilePackage", session=session) + load_bpp(client, _b64_field(bpp, "boundProfilePackage")) + return iccid + except Exception: + if tx_id: + _cancel_session_safe(client, smdp, tx_id, session) + raise + finally: + session.close() + + class TiciLPA(LPABase): def __init__(self): if hasattr(self, '_client'): @@ -515,7 +764,10 @@ def delete_profile(self, iccid: str) -> None: raise LPAError(f"DeleteProfile failed: {PROFILE_ERROR_CODES.get(code, 'unknown')} (0x{code:02X})") def download_profile(self, qr: str, nickname: str | None = None) -> None: - return None + with self._acquire_channel(): + iccid = download_profile(self._client, qr) + if nickname and iccid: + set_profile_nickname(self._client, iccid, nickname) def nickname_profile(self, iccid: str, nickname: str) -> None: with self._acquire_channel(): From d81d66193fc9d46291f03fb16557bb32166e4091 Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Fri, 17 Apr 2026 12:37:56 -0700 Subject: [PATCH 652/685] modeld: single jit (#37758) * compile_modeld.py * update estimates * missing image=2? * Revert "missing image=2?" This reverts commit 2f5952eb63ba1e3f24cbf5769e6b5e9170d7f0a6. * Revert "update estimates" This reverts commit 1f72feef2ffdec6126e3c941e899b46ace7b4b65. * Revert "compile_modeld.py" This reverts commit f10541502efca02725f368deda2a21d1f786f57d. * load warp in ModelState init * dead code * prep * compile modeld * update SConscript * tmp save plot locally * Revert "tmp save plot locally" This reverts commit ec22f15161ad3b0241a097546b35860f989219f5. * openpilot hacks? * no float16 * tmp more chunks * Revert "tmp more chunks" This reverts commit 9e1d9b4d0dc36ff530d2a70b565fbfabd7afb00d. * Revert "no float16" This reverts commit 6204956e98e3c0818ed1985ede8eeccb810f63e3. * realize boundaries * Revert "realize boundaries" This reverts commit ffaa19259eba70944e7793e8f51a0f87089531b3. * prune=False? * Reapply "tmp more chunks" This reverts commit 2599c41cea93b4a6b4e946cdffc6a617663a7d23. * tg bug? * load first? * Revert "load first?" This reverts commit f643d082d76a424b23295e254179eb111e936e61. * revert * Reapply "tmp save plot locally" This reverts commit 1b95b82ee58654bd908b1cb04ab0ddbcd1a5955d. * 0 tol pc * warp -> modeld * rename * bypass chunking? * dont chunk * Revert "dont chunk" This reverts commit cc97fc67b3203456e123f02babe5c83b87c7e264. * dont chunk * debug * Revert "debug" This reverts commit b3c2f2e7a095fd32f8d8562a68fd1cca42357eac. * Revert "dont chunk" This reverts commit 42bd9b6f6ad0722c50348ba11ba7e2a64fdf997d. * Revert "bypass chunking?" This reverts commit ad5422a93483ffd8a59ba62e5fb72ced3b5d04d0. * corrupt model outputs * Revert "corrupt model outputs" This reverts commit 245feb94480e02f83a20b65a9488652bcbfc88b0. * image=0 for warp, match master * dedupe enqueue * pass traffic convention * tg buffer for desire * dedupe buffer creation * compile_modeld: nuke stale cached pkl before compiling The UNSAFE CI checkout keeps gitignored files (.pkl, .sconsign.dblite), so stale pkl files from previous commits can persist and be reused instead of being recompiled. Delete them explicitly before compiling. Co-Authored-By: Claude Opus 4.6 (1M context) * test vs compile * all outputs need to be different on different inputs * randomize numpy inputs * randomize on every step * SConscript: nuke stale pkl+chunks before compile_modeld Move the stale artifact cleanup from compile_modeld.py into the SConscript build command. This ensures stale gitignored pkl and chunk files are deleted even if scons decides to skip the compile step (due to a stale .sconsign.dblite from UNSAFE CI checkout). Co-Authored-By: Claude Opus 4.6 (1M context) * compile_modeld: restore Context(IMAGE=0) for warp The warp operations must run under IMAGE=0 to avoid QCOM image texture optimizations that corrupt the output buffer after ~33 frames. This was accidentally commented out in a855173. Co-Authored-By: Claude Opus 4.6 (1M context) * modeld: create SubMaster before model loading Move PubMaster/SubMaster creation before the model loading step. During model loading (3.5s+), process_replay may send liveCalibration. If SubMaster doesn't exist yet, the message is dropped and the warp transform stays as zeros, producing garbage warped images. Co-Authored-By: Claude Opus 4.6 (1M context) * Revert "modeld: create SubMaster before model loading" This reverts commit 968c987c2fbb3fce141c4e345d10ddea559b6c50. * stale metadata? * claude debug * Revert "claude debug" This reverts commit 49e754c6affa45a8ea8834588a00227b8090b17a. * Revert "stale metadata?" This reverts commit 870388513c0d4a67dcf970cd277b6db56cb2b478. * modeld: realize jit outputs before parsing * Update modeld.py * modeld: fix NameError by removing redundant MODELS_DIR definition * test buffers in test vs. compile * 2x inputs before running * fixup 2x inputs test * realize onnx weights? * Revert "realize onnx weights?" This reverts commit 49c8b9a505db38ff22f342db011a3a6b6526d398. * move openpilot_hacks flag to sconscript * stricter test vs compile * correct timings * more run more fail? * Revert "more run more fail?" This reverts commit 9e94bb63940751ec29e81b634c42449113e1f2e5. * numpy shenanigans * correct shapes * dont assert timings for now * Revert "correct shapes" This reverts commit 5b9ff6c84c0022327d21801d179e9e51c39e8f78. * Revert "numpy shenanigans" This reverts commit b4f6fb3078d7e9b09698895b88728fd8eea8c8a8. * no need to nuke * comment unused * don't use NPY device * copy instead of from_blob * to device before jit * Revert "to device before jit" This reverts commit 7a59ed9b1ac88657b5a3917986b6ff92e59a2ee3. * Revert "copy instead of from_blob" This reverts commit 196c4892a06ffba89ef631876372cecf137cc1b4. * Revert "don't use NPY device" This reverts commit 18abf43bbac46ad47a60c03dd8d1ef40b3f59227. * 3 runs is enough * no_memory_planner=1 * lint * restore model_replay.py * on policy -> policy * unused * prepare only enqueues full images * warp with image=2? * unused args * test vs compile, check different inputs different outputs * avoid uop cache collision * dont need realize here * misc * input queues diverged * strict zip * monkey patch for now * memory planner * prev desire correct order * dedupe pkl paths / compile targets * don't change behavior, warp and enqueue frames when skipping model eval * actually prepare only * warm up warp jit * correct path * oops * explicit warmup * need continuous + can't have dupplicate jit inputs * whitespace * bufs -> input_queues * master tg * /N_RUNS * bump tg, remove uop cache patch * more readable * Revert "bump tg, remove uop cache patch" This reverts commit 499acca2591becd389de4025943f9e776a5b337c. * missing dep --------- Co-authored-by: Bruce Wayne Co-authored-by: Claude Opus 4.6 (1M context) --- selfdrive/modeld/SConscript | 34 ++-- selfdrive/modeld/compile_modeld.py | 289 +++++++++++++++++++++++++++ selfdrive/modeld/compile_warp.py | 198 ------------------ selfdrive/modeld/modeld.py | 150 +++----------- selfdrive/modeld/tinygrad_helpers.py | 2 +- 5 files changed, 343 insertions(+), 330 deletions(-) create mode 100755 selfdrive/modeld/compile_modeld.py delete mode 100755 selfdrive/modeld/compile_warp.py diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 86b62d2c170..053eb157b6d 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -16,7 +16,6 @@ tinygrad_files = ["#"+x for x in glob.glob(env.Dir("#tinygrad_repo").relpath + " def estimate_pickle_max_size(onnx_size): return 1.2 * onnx_size + 10 * 1024 * 1024 # 20% + 10MB is plenty -# THREADS=0 is need to prevent bug: https://github.com/tinygrad/tinygrad/issues/14689 # get fastest TG config available = set(Device.get_available_devices()) if 'CUDA' in available: @@ -24,9 +23,10 @@ if 'CUDA' in available: tg_flags = f'DEV={tg_backend}' elif 'QCOM' in available: tg_backend = 'QCOM' - tg_flags = f'DEV={tg_backend} FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0' + tg_flags = f'DEV={tg_backend} FLOAT16=1 NOLOCALS=1 JIT_BATCH_SIZE=0 OPENPILOT_HACKS=1' else: tg_backend = 'CPU' if arch == 'Darwin' else 'CPU:LLVM' + # THREADS=0 is need to prevent bug: https://github.com/tinygrad/tinygrad/issues/14689 tg_flags = f'DEV={tg_backend} THREADS=0' def write_tg_compiled_flags(target, source, env): @@ -53,14 +53,23 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') -script_files = [File(Dir("#selfdrive/modeld").File("compile_warp.py").abspath)] -compile_warp_cmd = f'{tg_flags} {mac_brew_string} python3 {Dir("#selfdrive/modeld").abspath}/compile_warp.py ' -from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye -warp_targets = [] -for cam in [_ar_ox_fisheye, _os_fisheye]: - w, h = cam.width, cam.height - warp_targets += [File(f"models/warp_{w}x{h}_tinygrad.pkl").abspath, File(f"models/dm_warp_{w}x{h}_tinygrad.pkl").abspath] -lenv.Command(warp_targets, tinygrad_files + script_files + [compiled_flags_node], compile_warp_cmd) +script_files = [File(Dir("#selfdrive/modeld").File("compile_modeld.py").abspath)] +compile_modeld_cmd = f'{tg_flags} {mac_brew_string} {image_flag} python3 {Dir("#selfdrive/modeld").abspath}/compile_modeld.py ' +driving_onnx_deps = [File(f"models/{m}.onnx").abspath for m in ['driving_vision', 'driving_policy']] +driving_metadata_deps = [File(f"models/{m}_metadata.pkl").abspath for m in ['driving_vision', 'driving_policy']] + +from openpilot.selfdrive.modeld.compile_modeld import MODELD_CONFIGS, DM_WARP_CONFIGS +policy_pkls = [File(cfg.pkl_path).abspath for cfg in MODELD_CONFIGS] +modeld_targets = policy_pkls + [File(cfg.pkl_path).abspath for cfg in DM_WARP_CONFIGS] +compile_node = lenv.Command(modeld_targets, tinygrad_files + script_files + driving_onnx_deps + driving_metadata_deps + [chunker_file, compiled_flags_node], compile_modeld_cmd) + +# chunk the combined policy pkls +for policy_pkl in policy_pkls: + onnx_sizes_sum = sum(os.path.getsize(f) for f in driving_onnx_deps) + chunk_targets = get_chunk_paths(policy_pkl, estimate_pickle_max_size(onnx_sizes_sum)) + def do_chunk(target, source, env, pkl=policy_pkl, chunks=chunk_targets): + chunk_file(pkl, chunks) + lenv.Command(chunk_targets, compile_node, do_chunk) def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' @@ -81,7 +90,4 @@ def tg_compile(flags, model_name): do_chunk, ) -# Compile small models -for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: - tg_compile(tg_flags, model_name) - +tg_compile(tg_flags, 'dmonitoring_model') diff --git a/selfdrive/modeld/compile_modeld.py b/selfdrive/modeld/compile_modeld.py new file mode 100755 index 00000000000..469110a857c --- /dev/null +++ b/selfdrive/modeld/compile_modeld.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +import time +import pickle +from dataclasses import dataclass +from itertools import product +from functools import partial + +import numpy as np +from tinygrad.tensor import Tensor +from tinygrad.helpers import Context +from tinygrad.device import Device +from tinygrad.engine.jit import TinyJit + +from openpilot.selfdrive.modeld.tinygrad_helpers import MODELS_DIR +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info +from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye + +# https://github.com/tinygrad/tinygrad/issues/15682 +from tinygrad.uop.ops import UOp, Ops +_orig = UOp.__reduce__ +UOp.__reduce__ = lambda self: (UOp.unique, ()) if self.op is Ops.UNIQUE else _orig(self) + + +@dataclass +class CompileConfig: + cam_w: int + cam_h: int + prepare_only: bool + prefix: str + + @property + def pkl_path(self): + return str(MODELS_DIR / f'{self.prefix}{"warp_" if self.prepare_only else ""}{self.cam_w}x{self.cam_h}_tinygrad.pkl') + +CAMERA_CONFIGS = [ + (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 + (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 +] +MODELD_CONFIGS = [CompileConfig(cam_w, cam_h, prepare_only, 'driving_') for (cam_w, cam_h), prepare_only in product(CAMERA_CONFIGS, [True, False])] +DM_WARP_CONFIGS = [CompileConfig(cam_w, cam_h, True, 'dm_') for cam_w, cam_h in CAMERA_CONFIGS] + + +UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) +UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) + + +def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad): + w_dst, h_dst = dst_shape + h_src, w_src = src_shape + + x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst).reshape(-1) + y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst).reshape(-1) + + # inline 3x3 matmul as elementwise to avoid reduce op (enables fusion with gather) + src_x = M_inv[0, 0] * x + M_inv[0, 1] * y + M_inv[0, 2] + src_y = M_inv[1, 0] * x + M_inv[1, 1] * y + M_inv[1, 2] + src_w = M_inv[2, 0] * x + M_inv[2, 1] * y + M_inv[2, 2] + + src_x = src_x / src_w + src_y = src_y / src_w + + x_nn_clipped = Tensor.round(src_x).clip(0, w_src - 1).cast('int') + y_nn_clipped = Tensor.round(src_y).clip(0, h_src - 1).cast('int') + idx = y_nn_clipped * (w_src + stride_pad) + x_nn_clipped + + return src_flat[idx] + + +def frames_to_tensor(frames): + H = (frames.shape[0] * 2) // 3 + W = frames.shape[1] + in_img1 = Tensor.cat(frames[0:H:2, 0::2], + frames[1:H:2, 0::2], + frames[0:H:2, 1::2], + frames[1:H:2, 1::2], + frames[H:H+H//4].reshape((H//2, W//2)), + frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) + return in_img1 + + +def make_frame_prepare(cam_w, cam_h, model_w, model_h): + stride, y_height, uv_height, _ = get_nv12_info(cam_w, cam_h) + uv_offset = stride * y_height + stride_pad = stride - cam_w + + def frame_prepare_tinygrad(input_frame, M_inv): + # UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling + M_inv_uv = M_inv * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]]) + # deinterleave NV12 UV plane (UVUV... -> separate U, V) + uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride) + with Context(SPLIT_REDUCEOP=0): + y = warp_perspective_tinygrad(input_frame[:cam_h*stride], + M_inv, (model_w, model_h), + (cam_h, cam_w), stride_pad).realize() + u = warp_perspective_tinygrad(uv[:cam_h//2, :cam_w:2].flatten(), + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), 0).realize() + v = warp_perspective_tinygrad(uv[:cam_h//2, 1:cam_w:2].flatten(), + M_inv_uv, (model_w//2, model_h//2), + (cam_h//2, cam_w//2), 0).realize() + yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) + tensor = frames_to_tensor(yuv) + return tensor + return frame_prepare_tinygrad + + +def make_input_queues(vision_input_shapes, policy_input_shapes, frame_skip): + img = vision_input_shapes['img'] # (1, 12, 128, 256) + n_frames = img[1] // 6 + img_buf_shape = (frame_skip * (n_frames - 1) + 1, 6, img[2], img[3]) + + fb = policy_input_shapes['features_buffer'] # (1, 25, 512) + dp = policy_input_shapes['desire_pulse'] # (1, 25, 8) + tc = policy_input_shapes['traffic_convention'] # (1, 2) + + npy = { + 'desire': np.zeros(dp[2], dtype=np.float32), + 'traffic_convention': np.zeros(tc, dtype=np.float32), + 'tfm': np.zeros((3, 3), dtype=np.float32), + 'big_tfm': np.zeros((3, 3), dtype=np.float32), + } + input_queues = { + 'img_q': Tensor.zeros(img_buf_shape, dtype='uint8').contiguous().realize(), + 'big_img_q': Tensor.zeros(img_buf_shape, dtype='uint8').contiguous().realize(), + 'feat_q': Tensor.zeros(frame_skip * (fb[1] - 1) + 1, fb[0], fb[2]).contiguous().realize(), + 'desire_q': Tensor.zeros(frame_skip * dp[1], dp[0], dp[2]).contiguous().realize(), + **{k: Tensor(v, device='NPY').realize() for k, v in npy.items()}, + } + return input_queues, npy + + +def shift_and_sample(buf, new_val, sample_fn): + buf.assign(buf[1:].cat(new_val, dim=0).contiguous()) + return sample_fn(buf) + + +def sample_skip(buf, frame_skip): + return buf[::frame_skip].contiguous().flatten(0, 1).unsqueeze(0) + + +def sample_desire(buf, frame_skip): + return buf.reshape(-1, frame_skip, *buf.shape[1:]).max(1).flatten(0, 1).unsqueeze(0) + + +def make_warp_dm(cam_w, cam_h, dm_w, dm_h): + stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) + stride_pad = stride - cam_w + + def warp_dm(input_frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT) + result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad).reshape(-1, dm_h * dm_w) + return result + return warp_dm + + +def make_run_policy(vision_runner, policy_runner, cam_w, cam_h, + vision_features_slice, frame_skip, prepare_only=False): + model_w, model_h = MEDMODEL_INPUT_SIZE + frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) + sample_skip_fn = partial(sample_skip, frame_skip=frame_skip) + sample_desire_fn = partial(sample_desire, frame_skip=frame_skip) + + def run_policy(img_q, big_img_q, feat_q, desire_q, desire, traffic_convention, tfm, big_tfm, frame, big_frame): + img = shift_and_sample(img_q, frame_prepare(frame, tfm.to(Device.DEFAULT)).unsqueeze(0), sample_skip_fn) + big_img = shift_and_sample(big_img_q, frame_prepare(big_frame, big_tfm.to(Device.DEFAULT)).unsqueeze(0), sample_skip_fn) + + if prepare_only: + return img, big_img + + vision_out = next(iter(vision_runner({'img': img, 'big_img': big_img}).values())).cast('float32') + + new_feat = vision_out[:, vision_features_slice].reshape(1, -1).unsqueeze(0) + feat_buf = shift_and_sample(feat_q, new_feat, sample_skip_fn) + desire_buf = shift_and_sample(desire_q, desire.to(Device.DEFAULT).reshape(1, 1, -1), sample_desire_fn) + + inputs = {'features_buffer': feat_buf, 'desire_pulse': desire_buf, 'traffic_convention': traffic_convention.to(Device.DEFAULT)} + policy_out = next(iter(policy_runner(inputs).values())).cast('float32') + + return vision_out, policy_out + return run_policy + + +def compile_modeld(cam_w, cam_h, prepare_only, pkl_path): + from tinygrad.nn.onnx import OnnxRunner + from openpilot.selfdrive.modeld.constants import ModelConstants + + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + print(f"Compiling combined policy JIT for {cam_w}x{cam_h}...") + + vision_runner = OnnxRunner(MODELS_DIR / 'driving_vision.onnx') + policy_runner = OnnxRunner(MODELS_DIR / 'driving_policy.onnx') + + with open(MODELS_DIR / 'driving_vision_metadata.pkl', 'rb') as f: + vision_metadata = pickle.load(f) + vision_features_slice = vision_metadata['output_slices']['hidden_state'] + vision_input_shapes = vision_metadata['input_shapes'] + with open(MODELS_DIR / 'driving_policy_metadata.pkl', 'rb') as f: + policy_input_shapes = pickle.load(f)['input_shapes'] + + frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ + + _run = make_run_policy(vision_runner, policy_runner, + cam_w, cam_h, vision_features_slice, frame_skip, prepare_only) + run_policy_jit = TinyJit(_run, prune=True) + + N_RUNS = 3 + SEED = 42 + + def random_inputs_run_fn(fn, seed, test_val=None, test_buffers=None, expect_match=True): + input_queues, npy = make_input_queues(vision_input_shapes, policy_input_shapes, frame_skip) + np.random.seed(seed) + + for i in range(N_RUNS): + frame = Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize() + big_frame = Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize() + for v in npy.values(): + v[:] = np.random.randn(*v.shape).astype(v.dtype) + Device.default.synchronize() + st = time.perf_counter() + outs = fn(**input_queues, frame=frame, big_frame=big_frame) + mt = time.perf_counter() + for o in outs: + # .realize() not needed once jitted, but needed for unjitted fn + o.realize() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/{N_RUNS}] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + val = [np.copy(v.numpy()) for v in outs] + buffers = [np.copy(v.numpy().copy()) for v in input_queues.values()] + + if test_val is not None: + match = all(np.array_equal(a, b) for a, b in zip(val, test_val, strict=True)) + assert match == expect_match, f"outputs {'differ from' if expect_match else 'match'} baseline (seed={seed})" + if test_buffers is not None: + match = all(np.array_equal(a, b) for a, b in zip(buffers, test_buffers, strict=True)) + assert match == expect_match, f"buffers {'differ from' if expect_match else 'match'} baseline (seed={seed})" + return fn, val, buffers + + print('run unjitted') + _, test_val, test_buffers = random_inputs_run_fn(_run, seed=SEED) + print('capture + replay') + run_policy_jit, _, _ = random_inputs_run_fn(run_policy_jit, SEED, test_val, test_buffers) + + print('pickle round trip') + with open(pkl_path, "wb") as f: + pickle.dump(run_policy_jit, f) + print(f" Saved to {pkl_path}") + with open(pkl_path, "rb") as f: + run_policy_jit = pickle.load(f) + random_inputs_run_fn(run_policy_jit, SEED, test_val, test_buffers, expect_match=True) + random_inputs_run_fn(run_policy_jit, SEED+1, test_val, test_buffers, expect_match=False) + + +def compile_dm_warp(cam_w, cam_h, pkl_path): + dm_w, dm_h = DM_INPUT_SIZE + _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) + + print(f"Compiling DM warp for {cam_w}x{cam_h}...") + + warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) + warp_dm_jit = TinyJit(warp_dm, prune=True) + + for i in range(10): + inputs = [Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize(), + Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] + Device.default.synchronize() + st = time.perf_counter() + warp_dm_jit(*inputs) + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + with open(pkl_path, "wb") as f: + pickle.dump(warp_dm_jit, f) + print(f" Saved to {pkl_path}") + + +def run_and_save_pickle(): + for cfg in MODELD_CONFIGS: + compile_modeld(cfg.cam_w, cfg.cam_h, cfg.prepare_only, cfg.pkl_path) + for cfg in DM_WARP_CONFIGS: + compile_dm_warp(cfg.cam_w, cfg.cam_h, cfg.pkl_path) + + +if __name__ == "__main__": + run_and_save_pickle() diff --git a/selfdrive/modeld/compile_warp.py b/selfdrive/modeld/compile_warp.py deleted file mode 100755 index 47511f2a2b6..00000000000 --- a/selfdrive/modeld/compile_warp.py +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env python3 -import time -import pickle -import numpy as np -from pathlib import Path -from tinygrad.tensor import Tensor -from tinygrad.helpers import Context -from tinygrad.device import Device -from tinygrad.engine.jit import TinyJit - -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info -from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE -from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye - -MODELS_DIR = Path(__file__).parent / 'models' - -CAMERA_CONFIGS = [ - (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 - (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 -] - -UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) -UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) - -IMG_BUFFER_SHAPE = (30, MEDMODEL_INPUT_SIZE[1] // 2, MEDMODEL_INPUT_SIZE[0] // 2) - - -def warp_pkl_path(w, h): - return MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - - -def dm_warp_pkl_path(w, h): - return MODELS_DIR / f'dm_warp_{w}x{h}_tinygrad.pkl' - - -def warp_perspective_tinygrad(src_flat, M_inv, dst_shape, src_shape, stride_pad): - w_dst, h_dst = dst_shape - h_src, w_src = src_shape - - x = Tensor.arange(w_dst).reshape(1, w_dst).expand(h_dst, w_dst).reshape(-1) - y = Tensor.arange(h_dst).reshape(h_dst, 1).expand(h_dst, w_dst).reshape(-1) - - # inline 3x3 matmul as elementwise to avoid reduce op (enables fusion with gather) - src_x = M_inv[0, 0] * x + M_inv[0, 1] * y + M_inv[0, 2] - src_y = M_inv[1, 0] * x + M_inv[1, 1] * y + M_inv[1, 2] - src_w = M_inv[2, 0] * x + M_inv[2, 1] * y + M_inv[2, 2] - - src_x = src_x / src_w - src_y = src_y / src_w - - x_nn_clipped = Tensor.round(src_x).clip(0, w_src - 1).cast('int') - y_nn_clipped = Tensor.round(src_y).clip(0, h_src - 1).cast('int') - idx = y_nn_clipped * (w_src + stride_pad) + x_nn_clipped - - return src_flat[idx] - - -def frames_to_tensor(frames, model_w, model_h): - H = (frames.shape[0] * 2) // 3 - W = frames.shape[1] - in_img1 = Tensor.cat(frames[0:H:2, 0::2], - frames[1:H:2, 0::2], - frames[0:H:2, 1::2], - frames[1:H:2, 1::2], - frames[H:H+H//4].reshape((H//2, W//2)), - frames[H+H//4:H+H//2].reshape((H//2, W//2)), dim=0).reshape((6, H//2, W//2)) - return in_img1 - - -def make_frame_prepare(cam_w, cam_h, model_w, model_h): - stride, y_height, uv_height, _ = get_nv12_info(cam_w, cam_h) - uv_offset = stride * y_height - stride_pad = stride - cam_w - - def frame_prepare_tinygrad(input_frame, M_inv): - # UV_SCALE @ M_inv @ UV_SCALE_INV simplifies to elementwise scaling - M_inv_uv = M_inv * Tensor([[1.0, 1.0, 0.5], [1.0, 1.0, 0.5], [2.0, 2.0, 1.0]]) - # deinterleave NV12 UV plane (UVUV... -> separate U, V) - uv = input_frame[uv_offset:uv_offset + uv_height * stride].reshape(uv_height, stride) - with Context(SPLIT_REDUCEOP=0): - y = warp_perspective_tinygrad(input_frame[:cam_h*stride], - M_inv, (model_w, model_h), - (cam_h, cam_w), stride_pad).realize() - u = warp_perspective_tinygrad(uv[:cam_h//2, :cam_w:2].flatten(), - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), 0).realize() - v = warp_perspective_tinygrad(uv[:cam_h//2, 1:cam_w:2].flatten(), - M_inv_uv, (model_w//2, model_h//2), - (cam_h//2, cam_w//2), 0).realize() - yuv = y.cat(u).cat(v).reshape((model_h * 3 // 2, model_w)) - tensor = frames_to_tensor(yuv, model_w, model_h) - return tensor - return frame_prepare_tinygrad - - -def make_update_img_input(frame_prepare, model_w, model_h): - def update_img_input_tinygrad(frame_buffer, frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - new_img = frame_prepare(frame, M_inv) - frame_buffer.assign(frame_buffer[6:].cat(new_img, dim=0).contiguous()) - return Tensor.cat(frame_buffer[:6], frame_buffer[-6:], dim=0).contiguous().reshape(1, 12, model_h//2, model_w//2) - return update_img_input_tinygrad - - -def make_update_both_imgs(frame_prepare, model_w, model_h): - update_img = make_update_img_input(frame_prepare, model_w, model_h) - - def update_both_imgs_tinygrad(calib_img_buffer, new_img, M_inv, - calib_big_img_buffer, new_big_img, M_inv_big): - calib_img_pair = update_img(calib_img_buffer, new_img, M_inv) - calib_big_img_pair = update_img(calib_big_img_buffer, new_big_img, M_inv_big) - return calib_img_pair, calib_big_img_pair - return update_both_imgs_tinygrad - - -def make_warp_dm(cam_w, cam_h, dm_w, dm_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - stride_pad = stride - cam_w - - def warp_dm(input_frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) - result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad).reshape(-1, dm_h * dm_w) - return result - return warp_dm - - -def compile_modeld_warp(cam_w, cam_h): - model_w, model_h = MEDMODEL_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling modeld warp for {cam_w}x{cam_h}...") - - frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) - update_both_imgs = make_update_both_imgs(frame_prepare, model_w, model_h) - update_img_jit = TinyJit(update_both_imgs, prune=True) - - full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - big_full_buffer = Tensor.zeros(IMG_BUFFER_SHAPE, dtype='uint8').contiguous().realize() - for i in range(10): - img_inputs = [full_buffer, - Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - big_img_inputs = [big_full_buffer, - Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - inputs = img_inputs + big_img_inputs - Device.default.synchronize() - - st = time.perf_counter() - _ = update_img_jit(*inputs) - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(update_img_jit, f) - print(f" Saved to {pkl_path}") - - jit = pickle.load(open(pkl_path, "rb")) - jit(*inputs) - - -def compile_dm_warp(cam_w, cam_h): - dm_w, dm_h = DM_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling DM warp for {cam_w}x{cam_h}...") - - warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) - warp_dm_jit = TinyJit(warp_dm, prune=True) - - for i in range(10): - inputs = [Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - Device.default.synchronize() - st = time.perf_counter() - warp_dm_jit(*inputs) - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - pkl_path = dm_warp_pkl_path(cam_w, cam_h) - with open(pkl_path, "wb") as f: - pickle.dump(warp_dm_jit, f) - print(f" Saved to {pkl_path}") - - -def run_and_save_pickle(): - for cam_w, cam_h in CAMERA_CONFIGS: - compile_modeld_warp(cam_w, cam_h) - compile_dm_warp(cam_w, cam_h) - - -if __name__ == "__main__": - run_and_save_pickle() diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 0c50d8beb51..7a8a442981a 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -26,6 +26,7 @@ from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser +from openpilot.selfdrive.modeld.compile_modeld import CompileConfig, make_input_queues from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan @@ -34,17 +35,13 @@ PROCESS_NAME = "selfdrive.modeld.modeld" SEND_RAW_PRED = os.getenv('SEND_RAW_PRED') -VISION_PKL_PATH = MODELS_DIR / 'driving_vision_tinygrad.pkl' VISION_METADATA_PATH = MODELS_DIR / 'driving_vision_metadata.pkl' -POLICY_PKL_PATH = MODELS_DIR / 'driving_policy_tinygrad.pkl' POLICY_METADATA_PATH = MODELS_DIR / 'driving_policy_metadata.pkl' LAT_SMOOTH_SECONDS = 0.0 LONG_SMOOTH_SECONDS = 0.3 MIN_LAT_CONTROL_SPEED = 0.3 -IMG_QUEUE_SHAPE = (6*(ModelConstants.MODEL_RUN_FREQ//ModelConstants.MODEL_CONTEXT_FREQ + 1), 128, 256) -assert IMG_QUEUE_SHAPE[0] == 30 def get_action_from_model(model_output: dict[str, np.ndarray], prev_action: log.ModelDataV2.Action, @@ -79,106 +76,36 @@ def __init__(self, vipc=None): if vipc is not None: self.frame_id, self.timestamp_sof, self.timestamp_eof = vipc.frame_id, vipc.timestamp_sof, vipc.timestamp_eof -class InputQueues: - def __init__ (self, model_fps, env_fps, n_frames_input): - assert env_fps % model_fps == 0 - assert env_fps >= model_fps - self.model_fps = model_fps - self.env_fps = env_fps - self.n_frames_input = n_frames_input - - self.dtypes = {} - self.shapes = {} - self.q = {} - - def update_dtypes_and_shapes(self, input_dtypes, input_shapes) -> None: - self.dtypes.update(input_dtypes) - if self.env_fps == self.model_fps: - self.shapes.update(input_shapes) - else: - for k in input_shapes: - shape = list(input_shapes[k]) - if 'img' in k: - n_channels = shape[1] // self.n_frames_input - shape[1] = (self.env_fps // self.model_fps + (self.n_frames_input - 1)) * n_channels - else: - shape[1] = (self.env_fps // self.model_fps) * shape[1] - self.shapes[k] = tuple(shape) - - def reset(self) -> None: - self.q = {k: np.zeros(self.shapes[k], dtype=self.dtypes[k]) for k in self.dtypes.keys()} - - def enqueue(self, inputs:dict[str, np.ndarray]) -> None: - for k in inputs.keys(): - if inputs[k].dtype != self.dtypes[k]: - raise ValueError(f'supplied input <{k}({inputs[k].dtype})> has wrong dtype, expected {self.dtypes[k]}') - input_shape = list(self.shapes[k]) - input_shape[1] = -1 - single_input = inputs[k].reshape(tuple(input_shape)) - sz = single_input.shape[1] - self.q[k][:,:-sz] = self.q[k][:,sz:] - self.q[k][:,-sz:] = single_input - - def get(self, *names) -> dict[str, np.ndarray]: - if self.env_fps == self.model_fps: - return {k: self.q[k] for k in names} - else: - out = {} - for k in names: - shape = self.shapes[k] - if 'img' in k: - n_channels = shape[1] // (self.env_fps // self.model_fps + (self.n_frames_input - 1)) - out[k] = np.concatenate([self.q[k][:, s:s+n_channels] for s in np.linspace(0, shape[1] - n_channels, self.n_frames_input, dtype=int)], axis=1) - elif 'pulse' in k: - # any pulse within interval counts - out[k] = self.q[k].reshape((shape[0], shape[1] * self.model_fps // self.env_fps, self.env_fps // self.model_fps, -1)).max(axis=2) - else: - idxs = np.arange(-1, -shape[1], -self.env_fps // self.model_fps)[::-1] - out[k] = self.q[k][:, idxs] - return out class ModelState: - inputs: dict[str, np.ndarray] - output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse - def __init__(self): + def __init__(self, cam_w: int, cam_h: int): with open(VISION_METADATA_PATH, 'rb') as f: vision_metadata = pickle.load(f) self.vision_input_shapes = vision_metadata['input_shapes'] self.vision_input_names = list(self.vision_input_shapes.keys()) self.vision_output_slices = vision_metadata['output_slices'] - vision_output_size = vision_metadata['output_shapes']['outputs'][1] with open(POLICY_METADATA_PATH, 'rb') as f: policy_metadata = pickle.load(f) self.policy_input_shapes = policy_metadata['input_shapes'] self.policy_output_slices = policy_metadata['output_slices'] - policy_output_size = policy_metadata['output_shapes']['outputs'][1] self.prev_desire = np.zeros(ModelConstants.DESIRE_LEN, dtype=np.float32) - # policy inputs - self.numpy_inputs = {k: np.zeros(self.policy_input_shapes[k], dtype=np.float32) for k in self.policy_input_shapes} - self.full_input_queues = InputQueues(ModelConstants.MODEL_CONTEXT_FREQ, ModelConstants.MODEL_RUN_FREQ, ModelConstants.N_FRAMES) - for k in ['desire_pulse', 'features_buffer']: - self.full_input_queues.update_dtypes_and_shapes({k: self.numpy_inputs[k].dtype}, {k: self.numpy_inputs[k].shape}) - self.full_input_queues.reset() - - self.img_queues = {'img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize(), - 'big_img': Tensor.zeros(IMG_QUEUE_SHAPE, dtype='uint8').contiguous().realize()} + self.frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ + self.input_queues, self.npy = make_input_queues(self.vision_input_shapes, self.policy_input_shapes, self.frame_skip) self.full_frames : dict[str, Tensor] = {} self._blob_cache : dict[int, Tensor] = {} - self.transforms_np = {k: np.zeros((3,3), dtype=np.float32) for k in self.img_queues} - self.transforms = {k: Tensor(v, device='NPY').realize() for k, v in self.transforms_np.items()} - self.vision_output = np.zeros(vision_output_size, dtype=np.float32) - self.policy_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} - self.policy_output = np.zeros(policy_output_size, dtype=np.float32) self.parser = Parser() - self.frame_buf_params : dict[str, tuple[int, int, int, int]] = {} - self.update_imgs = None - self.vision_run = pickle.loads(read_file_chunked(str(VISION_PKL_PATH))) - self.policy_run = pickle.loads(read_file_chunked(str(POLICY_PKL_PATH))) + self.frame_buf_params = {k: get_nv12_info(cam_w, cam_h) for k in ('img', 'big_img')} + self.run_policy = pickle.loads(read_file_chunked(CompileConfig(cam_w, cam_h, prefix='driving_', prepare_only=False).pkl_path)) + self.warp_enqueue = pickle.loads(read_file_chunked(CompileConfig(cam_w, cam_h, prefix='driving_', prepare_only=True).pkl_path)) + self.warp_enqueue( + **self.input_queues, + frame=Tensor.zeros(self.frame_buf_params['img'][3], dtype='uint8').contiguous().realize(), + big_frame=Tensor.zeros(self.frame_buf_params['big_img'][3], dtype='uint8').contiguous().realize()) def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slice]) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in output_slices.items()} @@ -186,18 +113,6 @@ def slice_outputs(self, model_outputs: np.ndarray, output_slices: dict[str, slic def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None: - # Model decides when action is completed, so desire input is just a pulse triggered on rising edge - inputs['desire_pulse'][0] = 0 - new_desire = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) - self.prev_desire[:] = inputs['desire_pulse'] - if self.update_imgs is None: - for key in bufs.keys(): - w, h = bufs[key].width, bufs[key].height - self.frame_buf_params[key] = get_nv12_info(w, h) - warp_path = MODELS_DIR / f'warp_{w}x{h}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.update_imgs = pickle.load(f) - for key in bufs.keys(): ptr = bufs[key].data.ctypes.data yuv_size = self.frame_buf_params[key][3] @@ -206,30 +121,31 @@ def run(self, bufs: dict[str, VisionBuf], transforms: dict[str, np.ndarray], if cache_key not in self._blob_cache: self._blob_cache[cache_key] = Tensor.from_blob(ptr, (yuv_size,), dtype='uint8') self.full_frames[key] = self._blob_cache[cache_key] - for key in bufs.keys(): - self.transforms_np[key][:,:] = transforms[key][:,:] - out = self.update_imgs(self.img_queues['img'], self.full_frames['img'], self.transforms['img'], - self.img_queues['big_img'], self.full_frames['big_img'], self.transforms['big_img']) - vision_inputs = {'img': out[0], 'big_img': out[1]} + # Model decides when action is completed, so desire input is just a pulse triggered on rising edge + inputs['desire_pulse'][0] = 0 + self.npy['desire'][:] = np.where(inputs['desire_pulse'] - self.prev_desire > .99, inputs['desire_pulse'], 0) + self.prev_desire[:] = inputs['desire_pulse'] + self.npy['traffic_convention'][:] = inputs['traffic_convention'] + self.npy['tfm'][:,:] = transforms['img'][:,:] + self.npy['big_tfm'][:,:] = transforms['big_img'][:,:] if prepare_only: + self.warp_enqueue(**self.input_queues, frame=self.full_frames['img'], big_frame=self.full_frames['big_img']) return None - self.vision_output = self.vision_run(**vision_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() - vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(self.vision_output, self.vision_output_slices)) + vision_output, policy_output = self.run_policy( + **self.input_queues, frame=self.full_frames['img'], big_frame=self.full_frames['big_img'] + ) - self.full_input_queues.enqueue({'features_buffer': vision_outputs_dict['hidden_state'], 'desire_pulse': new_desire}) - for k in ['desire_pulse', 'features_buffer']: - self.numpy_inputs[k][:] = self.full_input_queues.get(k)[k] - self.numpy_inputs['traffic_convention'][:] = inputs['traffic_convention'] - - self.policy_output = self.policy_run(**self.policy_inputs).contiguous().realize().uop.base.buffer.numpy().flatten() - policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(self.policy_output, self.policy_output_slices)) + vision_output = vision_output.numpy().flatten() + policy_output = policy_output.numpy().flatten() + vision_outputs_dict = self.parser.parse_vision_outputs(self.slice_outputs(vision_output, self.vision_output_slices)) + policy_outputs_dict = self.parser.parse_policy_outputs(self.slice_outputs(policy_output, self.policy_output_slices)) combined_outputs_dict = {**vision_outputs_dict, **policy_outputs_dict} - if SEND_RAW_PRED: - combined_outputs_dict['raw_pred'] = np.concatenate([self.vision_output.copy(), self.policy_output.copy()]) + if SEND_RAW_PRED: + combined_outputs_dict['raw_pred'] = np.concatenate([vision_output.copy(), policy_output.copy()]) return combined_outputs_dict @@ -241,11 +157,6 @@ def main(demo=False): # also need to move the aux USB interrupts for good timings config_realtime_process(7, 54) - st = time.monotonic() - cloudlog.warning("loading model") - model = ModelState() - cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") - # visionipc clients while True: available_streams = VisionIpcClient.available_streams("camerad", block=False) @@ -269,6 +180,11 @@ def main(demo=False): if use_extra_client: cloudlog.warning(f"connected extra cam with buffer size: {vipc_client_extra.buffer_len} ({vipc_client_extra.width} x {vipc_client_extra.height})") + st = time.monotonic() + cloudlog.warning("loading model") + model = ModelState(vipc_client_main.width, vipc_client_main.height) + cloudlog.warning(f"models loaded in {time.monotonic() - st:.1f}s, modeld starting") + # messaging pm = PubMaster(["modelV2", "drivingModelData", "cameraOdometry"]) sm = SubMaster(["deviceState", "carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "carControl", "liveDelay"]) diff --git a/selfdrive/modeld/tinygrad_helpers.py b/selfdrive/modeld/tinygrad_helpers.py index 49a6ed61618..9c1a0d4689f 100644 --- a/selfdrive/modeld/tinygrad_helpers.py +++ b/selfdrive/modeld/tinygrad_helpers.py @@ -2,7 +2,7 @@ import os from pathlib import Path -MODELS_DIR = Path(__file__).parent / 'models' +MODELS_DIR = Path(__file__).resolve().parent / 'models' COMPILED_FLAGS_PATH = MODELS_DIR / 'tg_compiled_flags.json' From 5624a4ccd682fdd56d9ab2420f9341e65010621a Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:41:47 -0700 Subject: [PATCH 653/685] bump teleoprtc_repo (#37848) --- teleoprtc_repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/teleoprtc_repo b/teleoprtc_repo index 389815b8ca5..22df5778218 160000 --- a/teleoprtc_repo +++ b/teleoprtc_repo @@ -1 +1 @@ -Subproject commit 389815b8ca5302ce7c1504b7841d4eb61a8cd51b +Subproject commit 22df577821862e32a011fb0cf42577599f3a79c4 From 389b639ef27a1fd5278d5ae97b9618fd562b00c2 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Fri, 17 Apr 2026 21:58:05 -0700 Subject: [PATCH 654/685] DriverMonitoringState v2 (#37799) * draft ds * better names * what is this * build new * better names2 * more * bit more cleanup * rm those * . * .2 * selfdrived * depre * hk * fix test * fix rest * 1 * fix enum * update cereal * fix rest * more * add step * fix all * imports * cant? * . * simplify * bool * fix some migrate * cleanup * fix fix * Update cereal/log.capnp Co-authored-by: Adeeb Shihadeh * touchup * what --------- Co-authored-by: Comma Device Co-authored-by: Adeeb Shihadeh --- cereal/log.capnp | 74 ++++- selfdrive/controls/controlsd.py | 2 +- selfdrive/monitoring/dmonitoringd.py | 2 +- selfdrive/monitoring/helpers.py | 286 ++++++++---------- selfdrive/monitoring/test_monitoring.py | 150 +++++---- selfdrive/selfdrived/selfdrived.py | 25 +- selfdrive/test/process_replay/migration.py | 47 ++- selfdrive/ui/mici/layouts/onboarding.py | 2 +- .../ui/mici/onroad/driver_camera_dialog.py | 36 +-- selfdrive/ui/mici/onroad/driver_state.py | 4 +- selfdrive/ui/onroad/driver_state.py | 2 +- tools/sim/lib/simulated_sensors.py | 11 +- 12 files changed, 365 insertions(+), 276 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 0f8b5470a44..2dea21ff053 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2076,7 +2076,7 @@ struct DriverStateV2 { } } -struct DriverMonitoringState @0xb83cda094a1da284 { +struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 { events @18 :List(OnroadEvent); faceDetected @1 :Bool; isDistracted @2 :Bool; @@ -2104,6 +2104,75 @@ struct DriverMonitoringState @0xb83cda094a1da284 { } } +struct DriverMonitoringState { + lockout @0 :Bool; + alertCountLockoutPercent @1 :Int8; + alertTimeLockoutPercent @2 :Int8; + + alwaysOn @3 :Bool; + alwaysOnLockout @4 :Bool; + + alertLevel @5 :AlertLevel; + activePolicy @6 :MonitoringPolicy; + isRHD @7 :Bool; + rhdCalibration @8 :CalibrationState; + + visionPolicyState @9 :VisionPolicyState; + wheeltouchPolicyState @10 :WheeltouchPolicyState; + + enum AlertLevel { + # ordinal must match the name to prevent bugs + # comparing against the raw ordinal value + none @0; + one @1; + two @2; + three @3; + } + + enum MonitoringPolicy { + wheeltouch @0; + vision @1; + } + + struct VisionPolicyState { + awarenessPercent @0 :Int8; + awarenessStep @1 :Float32; + isDistracted @2 :Bool; + distractedTypes @3 :DistractedTypes; + + faceDetected @4 :Bool; + pose @5 :Pose; + wheeltouchFallbackPercent @6 :Int8; + uncertainOffroadAlertPercent @7 :Int8; + + struct DistractedTypes { + pose @0: Bool; + eye @1: Bool; + phone @2: Bool; + } + + struct Pose { + pitch @0 :Float32; + yaw @1 :Float32; + pitchCalib @2 :CalibrationState; + yawCalib @3 :CalibrationState; + calibrated @4 :Bool; + uncertainty @5 :Float32; + } + } + + struct WheeltouchPolicyState { + awarenessPercent @0 :Int8; + awarenessStep @1 :Float32; + driverInteracting @2 :Bool; + } + + struct CalibrationState { + calibratedPercent @0 :Int8; + offset @1 :Float32; + } +} + struct Boot { wallTimeNanos @0 :UInt64; pstore @4 :Map(Text, Data); @@ -2410,7 +2479,7 @@ struct Event { thumbnail @66: Thumbnail; onroadEvents @134: List(OnroadEvent); carParams @69: Car.CarParams; - driverMonitoringState @71: DriverMonitoringState; + driverMonitoringState @151 :DriverMonitoringState; livePose @129 :LivePose; modelV2 @75 :ModelDataV2; drivingModelData @128 :DrivingModelData; @@ -2553,5 +2622,6 @@ struct Event { gyroscope2DEPRECATED @100 :SensorEventData; accelerometer2DEPRECATED @101 :SensorEventData; temperatureSensor2DEPRECATED @123 :SensorEventData; + driverMonitoringStateDEPRECATED @71 :DriverMonitoringStateDEPRECATED; } } diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index b49e46604a4..e1d9650d724 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -193,7 +193,7 @@ def publish(self, CC, lac_log): cs.upAccelCmd = float(self.LoC.pid.p) cs.uiAccelCmd = float(self.LoC.pid.i) cs.ufAccelCmd = float(self.LoC.pid.f) - cs.forceDecel = bool((self.sm['driverMonitoringState'].awarenessStatus < 0.) or + cs.forceDecel = bool((self.sm['driverMonitoringState'].alertLevel == log.DriverMonitoringState.AlertLevel.three) or (self.sm['selfdriveState'].state == State.softDisabling)) lat_tuning = self.CP.lateralTuning.which() diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 1ac2c2dcbab..2a48f744949 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -24,7 +24,7 @@ def dmonitoringd_thread(): valid = sm.all_checks() if demo_mode and sm.valid['driverStateV2']: - DM.run_step(sm, demo=demo_mode) + DM.run_step(sm, demo=True) elif valid: DM.run_step(sm, demo=demo_mode) diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index a9d8f5b11ae..37caa757c52 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -1,18 +1,20 @@ +from collections import defaultdict from math import atan2, radians import numpy as np from cereal import car, log import cereal.messaging as messaging -from openpilot.selfdrive.selfdrived.events import Events -from openpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert from openpilot.common.realtime import DT_DMON from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.params import Params from openpilot.common.stat_live import RunningStatFilter from openpilot.common.transformations.camera import DEVICE_CAMERAS -from openpilot.system.hardware import HARDWARE -EventName = log.OnroadEvent.EventName +AlertLevel = log.DriverMonitoringState.AlertLevel +MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy + +def to_perc(v): + return int(min(max(v * 100., 0.), 100.)) # ****************************************************************************************** # NOTE: To fork maintainers. @@ -21,21 +23,26 @@ # ****************************************************************************************** class DRIVER_MONITOR_SETTINGS: - def __init__(self, device_type): - self._DT_DMON = DT_DMON - # ref (page15-16): https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:42018X1947&rid=2 - self._AWARENESS_TIME = 30. # passive wheeltouch total timeout - self._AWARENESS_PRE_TIME_TILL_TERMINAL = 15. - self._AWARENESS_PROMPT_TIME_TILL_TERMINAL = 6. - self._DISTRACTED_TIME = 11. # active monitoring total timeout - self._DISTRACTED_PRE_TIME_TILL_TERMINAL = 8. - self._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6. + def __init__(self): + # https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:42018X1947&rid=2 + self._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT = 15. + self._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT = 24. + self._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT = 30. + # https://cdn.euroncap.com/cars/assets/euro_ncap_protocol_safe_driving_driver_engagement_v11_a30e874152.pdf + self._VISION_POLICY_ALERT_1_TIMEOUT = 3. + self._VISION_POLICY_ALERT_2_TIMEOUT = 5. + self._VISION_POLICY_ALERT_3_TIMEOUT = 11. + + self._TIMEOUT_RECOVERY_FACTOR_MAX = 5. + self._TIMEOUT_RECOVERY_FACTOR_MIN = 1.25 + + self._MAX_TERMINAL_ALERTS = 3 # not allowed to engage after 3 terminal alerts + self._MAX_TERMINAL_DURATION = int(30 / DT_DMON) # not allowed to engage after 30s of terminal alerts self._FACE_THRESHOLD = 0.7 self._EYE_THRESHOLD = 0.5 self._BLINK_THRESHOLD = 0.5 self._PHONE_THRESH = 0.5 - self._POSE_PITCH_THRESHOLD = 0.3133 self._POSE_PITCH_THRESHOLD_SLACK = 0.3237 self._POSE_PITCH_THRESHOLD_STRICT = self._POSE_PITCH_THRESHOLD @@ -56,36 +63,22 @@ def __init__(self, device_type): self._YAW_MIN_OFFSET = -0.0246 self._DCAM_UNCERTAIN_ALERT_THRESHOLD = 0.1 - self._DCAM_UNCERTAIN_ALERT_COUNT = int(60 / self._DT_DMON) - self._DCAM_UNCERTAIN_RESET_COUNT = int(20 / self._DT_DMON) - self._POSESTD_THRESHOLD = 0.3 - self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s + self._DCAM_UNCERTAIN_ALERT_COUNT = int(60 / DT_DMON) + self._DCAM_UNCERTAIN_RESET_COUNT = int(20 / DT_DMON) + self._HI_STD_THRESHOLD = 0.3 + self._HI_STD_FALLBACK_TIME = int(10 / DT_DMON) # fall back to wheel touch if model is uncertain for 10s self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz self._POSE_CALIB_MIN_SPEED = 13 # 30 mph - self._POSE_OFFSET_MIN_COUNT = int(60 / self._DT_DMON) # valid data counts before calibration completes, 1min cumulative - self._POSE_OFFSET_MAX_COUNT = int(360 / self._DT_DMON) # stop deweighting new data after 6 min, aka "short term memory" - + self._POSE_OFFSET_MIN_COUNT = int(60 / DT_DMON) # valid data counts before calibration completes, 1min cumulative + self._POSE_OFFSET_MAX_COUNT = int(360 / DT_DMON) # stop deweighting new data after 6 min, aka "short term memory" self._WHEELPOS_CALIB_MIN_SPEED = 11 self._WHEELPOS_THRESHOLD = 0.5 - self._WHEELPOS_FILTER_MIN_COUNT = int(15 / self._DT_DMON) # allow 15 seconds to converge wheel side + self._WHEELPOS_FILTER_MIN_COUNT = int(15 / DT_DMON) # allow 15 seconds to converge wheel side self._WHEELPOS_DATA_AVG = 0.03 self._WHEELPOS_DATA_VAR = 3*5.5e-5 self._WHEELPOS_MAX_COUNT = -1 - self._RECOVERY_FACTOR_MAX = 5. # relative to minus step change - self._RECOVERY_FACTOR_MIN = 1.25 # relative to minus step change - - self._MAX_TERMINAL_ALERTS = 3 # not allowed to engage after 3 terminal alerts - self._MAX_TERMINAL_DURATION = int(30 / self._DT_DMON) # not allowed to engage after 30s of terminal alerts - -class DistractedType: - - NOT_DISTRACTED = 0 - DISTRACTED_POSE = 1 << 0 - DISTRACTED_BLINK = 1 << 1 - DISTRACTED_PHONE = 1 << 2 - class DriverPose: def __init__(self, settings): pitch_filter_raw_priors = (settings._PITCH_NATURAL_OFFSET, settings._PITCH_NATURAL_VAR, 2) @@ -117,7 +110,7 @@ def __init__(self, raw_priors, max_trackable): W, H = (cam.dcam.width, cam.dcam.height) # corrected image has same size as raw def face_orientation_from_net(angles_desc, pos_desc, rpy_calib): - # the output of these angles are in device frame + # the output of these angles are in driver camera frame # so from driver's perspective, pitch is up and yaw is right pitch_net, yaw_net, roll_net = angles_desc @@ -138,7 +131,7 @@ def face_orientation_from_net(angles_desc, pos_desc, rpy_calib): class DriverMonitoring: def __init__(self, rhd_saved=False, settings=None, always_on=False): # init policy settings - self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type()) + self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS() # init driver status wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) @@ -147,10 +140,11 @@ def __init__(self, rhd_saved=False, settings=None, always_on=False): self.blink_prob = 0. self.phone_prob = 0. + self.alert_level = AlertLevel.none self.always_on = always_on - self.distracted_types = [] + self.distracted_types = defaultdict(bool) self.driver_distracted = False - self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, self.settings._DT_DMON) + self.driver_distraction_filter = FirstOrderFilter(0., self.settings._DISTRACTED_FILTER_TS, DT_DMON) self.wheel_on_right = False self.wheel_on_right_last = None self.wheel_on_right_default = rhd_saved @@ -159,33 +153,29 @@ def __init__(self, rhd_saved=False, settings=None, always_on=False): self.terminal_time = 0 self.step_change = 0. self.active_monitoring_mode = True + self.driver_interacting = False self.is_model_uncertain = False self.hi_stds = 0 - self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME - self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME + self.model_std_max = 0. + self.threshold_alert_1 = 0. + self.threshold_alert_2 = 0. + self.dcam_uncertain = False self.dcam_uncertain_cnt = 0 - self.dcam_uncertain_alerted = False # once per drive self.dcam_reset_cnt = 0 - - self.params = Params() - self.too_distracted = self.params.get_bool("DriverTooDistracted") + self.too_distracted = Params().get_bool("DriverTooDistracted") self._reset_awareness() self._set_timers(active_monitoring=True) - self._reset_events() def _reset_awareness(self): self.awareness = 1. - self.awareness_active = 1. - self.awareness_passive = 1. - - def _reset_events(self): - self.current_events = Events() + self.last_vision_awareness = 1. + self.last_wheeltouch_awareness = 1. def _set_timers(self, active_monitoring): - if self.active_monitoring_mode and self.awareness <= self.threshold_prompt: + if self.active_monitoring_mode and self.awareness <= self.threshold_alert_2: if active_monitoring: - self.step_change = self.settings._DT_DMON / self.settings._DISTRACTED_TIME + self.step_change = DT_DMON / self.settings._VISION_POLICY_ALERT_3_TIMEOUT else: self.step_change = 0. return # no exploit after orange alert @@ -195,24 +185,24 @@ def _set_timers(self, active_monitoring): if active_monitoring: # when falling back from passive mode to active mode, reset awareness to avoid false alert if not self.active_monitoring_mode: - self.awareness_passive = self.awareness - self.awareness = self.awareness_active + self.last_wheeltouch_awareness = self.awareness + self.awareness = self.last_vision_awareness - self.threshold_pre = self.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME - self.threshold_prompt = self.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL / self.settings._DISTRACTED_TIME - self.step_change = self.settings._DT_DMON / self.settings._DISTRACTED_TIME + self.threshold_alert_1 = 1. - self.settings._VISION_POLICY_ALERT_1_TIMEOUT / self.settings._VISION_POLICY_ALERT_3_TIMEOUT + self.threshold_alert_2 = 1. - self.settings._VISION_POLICY_ALERT_2_TIMEOUT / self.settings._VISION_POLICY_ALERT_3_TIMEOUT + self.step_change = DT_DMON / self.settings._VISION_POLICY_ALERT_3_TIMEOUT self.active_monitoring_mode = True else: if self.active_monitoring_mode: - self.awareness_active = self.awareness - self.awareness = self.awareness_passive + self.last_vision_awareness = self.awareness + self.awareness = self.last_wheeltouch_awareness - self.threshold_pre = self.settings._AWARENESS_PRE_TIME_TILL_TERMINAL / self.settings._AWARENESS_TIME - self.threshold_prompt = self.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL / self.settings._AWARENESS_TIME - self.step_change = self.settings._DT_DMON / self.settings._AWARENESS_TIME + self.threshold_alert_1 = 1. - self.settings._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + self.threshold_alert_2 = 1. - self.settings._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + self.step_change = DT_DMON / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT self.active_monitoring_mode = False - def _set_policy(self, brake_disengage_prob, car_speed): + def _set_pose_strictness(self, brake_disengage_prob, car_speed): bp = brake_disengage_prob k1 = max(-0.00156*((car_speed-16)**2)+0.6, 0.2) bp_normal = max(min(bp / k1, 0.5),0) @@ -224,7 +214,7 @@ def _set_policy(self, brake_disengage_prob, car_speed): self.settings._POSE_YAW_THRESHOLD_STRICT]) / self.settings._POSE_YAW_THRESHOLD def _get_distracted_types(self): - distracted_types = [] + self.distracted_types = defaultdict(bool) if not self.pose.calibrated: pitch_error = self.pose.pitch - self.settings._PITCH_NATURAL_OFFSET @@ -244,16 +234,9 @@ def _get_distracted_types(self): pitch_threshold = self.settings._POSE_PITCH_THRESHOLD * self.pose.cfactor_pitch if self.pose.calibrated else self.settings._PITCH_NATURAL_THRESHOLD yaw_threshold = self.settings._POSE_YAW_THRESHOLD * self.pose.cfactor_yaw - if pitch_error > pitch_threshold or yaw_error > yaw_threshold: - distracted_types.append(DistractedType.DISTRACTED_POSE) - - if self.blink_prob > self.settings._BLINK_THRESHOLD: - distracted_types.append(DistractedType.DISTRACTED_BLINK) - - if self.phone_prob > self.settings._PHONE_THRESH: - distracted_types.append(DistractedType.DISTRACTED_PHONE) - - return distracted_types + self.distracted_types['pose'] = bool((pitch_error > pitch_threshold) or (yaw_error > yaw_threshold)) + self.distracted_types['eye'] = bool(self.blink_prob > self.settings._BLINK_THRESHOLD) + self.distracted_types['phone'] = bool(self.phone_prob > self.settings._PHONE_THRESH) def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstill, demo_mode=False, steering_angle_deg=0.): rhd_pred = driver_state.wheelOnRightProb @@ -262,7 +245,7 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil driver_state.rightDriverData.faceProb > self.settings._FACE_THRESHOLD): self.wheelpos.prob_offseter.push_and_update(rhd_pred) - self.wheelpos.prob_calibrated = self.wheelpos.prob_offseter.filtered_stat.n > self.settings._WHEELPOS_FILTER_MIN_COUNT + self.wheelpos.prob_calibrated = self.wheelpos.prob_offseter.filtered_stat.n >= self.settings._WHEELPOS_FILTER_MIN_COUNT if self.wheelpos.prob_calibrated or demo_mode: self.wheel_on_right = self.wheelpos.prob_offseter.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD @@ -286,16 +269,13 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil self.wheel_on_right_last = self.wheel_on_right self.pose.pitch_std = driver_data.faceOrientationStd[0] self.pose.yaw_std = driver_data.faceOrientationStd[1] - model_std_max = max(self.pose.pitch_std, self.pose.yaw_std) - self.pose.low_std = model_std_max < self.settings._POSESTD_THRESHOLD + self.model_std_max = max(self.pose.pitch_std, self.pose.yaw_std) + self.pose.low_std = self.model_std_max < self.settings._HI_STD_THRESHOLD self.blink_prob = driver_data.eyesClosedProb * (driver_data.eyesVisibleProb > self.settings._EYE_THRESHOLD) self.phone_prob = driver_data.phoneProb - self.distracted_types = self._get_distracted_types() - self.driver_distracted = (DistractedType.DISTRACTED_PHONE in self.distracted_types - or DistractedType.DISTRACTED_POSE in self.distracted_types - or DistractedType.DISTRACTED_BLINK in self.distracted_types) \ - and driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std + self._get_distracted_types() + self.driver_distracted = any(self.distracted_types.values()) and driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std self.driver_distraction_filter.update(self.driver_distracted) # update offseter @@ -304,20 +284,20 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil self.pose.pitch_offseter.push_and_update(self.pose.pitch) self.pose.yaw_offseter.push_and_update(self.pose.yaw) - self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT and \ - self.pose.yaw_offseter.filtered_stat.n > self.settings._POSE_OFFSET_MIN_COUNT + self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT and \ + self.pose.yaw_offseter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: - if model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD: - if not standstill: - self.dcam_uncertain_cnt += 1 - self.dcam_reset_cnt = 0 + self.dcam_uncertain = self.model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD + if self.dcam_uncertain and not standstill: + self.dcam_uncertain_cnt += 1 + self.dcam_reset_cnt = 0 else: self.dcam_reset_cnt += 1 if self.dcam_reset_cnt > self.settings._DCAM_UNCERTAIN_RESET_COUNT: self.dcam_uncertain_cnt = 0 - self.is_model_uncertain = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME + self.is_model_uncertain = self.hi_stds >= self.settings._HI_STD_FALLBACK_TIME self._set_timers(self.face_detected and not self.is_model_uncertain) if self.face_detected and not self.pose.low_std and not self.driver_distracted: self.hi_stds += 1 @@ -325,20 +305,15 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil self.hi_stds = 0 def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car_speed): - self._reset_events() - # Block engaging until ignition cycle after max number or time of distractions + self.alert_level = AlertLevel.none + self.driver_interacting = driver_engaged + if self.terminal_alert_cnt >= self.settings._MAX_TERMINAL_ALERTS or \ self.terminal_time >= self.settings._MAX_TERMINAL_DURATION: - if not self.too_distracted: - self.params.put_bool_nonblocking("DriverTooDistracted", True) self.too_distracted = True - # Always-on distraction lockout is temporary - if self.too_distracted or (self.always_on and self.awareness <= self.threshold_prompt): - self.current_events.add(EventName.tooDistracted) - always_on_valid = self.always_on and not wrong_gear - if (driver_engaged and self.awareness > 0 and not self.active_monitoring_mode) or \ + if (self.driver_interacting and self.awareness > 0 and not self.active_monitoring_mode) or \ (not always_on_valid and not op_engaged) or \ (always_on_valid and not op_engaged and self.awareness <= 0): # always reset on disengage with normal mode; disengage resets only on red if always on @@ -346,83 +321,87 @@ def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car return awareness_prev = self.awareness - _reaching_pre = self.awareness - self.step_change <= self.threshold_pre - _reaching_terminal = self.awareness - self.step_change <= 0 - standstill_orange_exemption = standstill and _reaching_pre - always_on_red_exemption = always_on_valid and not op_engaged and _reaching_terminal + _reaching_alert_1 = self.awareness - self.step_change <= self.threshold_alert_1 + _reaching_alert_3 = self.awareness - self.step_change <= 0 + standstill_exemption = standstill and _reaching_alert_1 + always_on_exemption = always_on_valid and not op_engaged and _reaching_alert_3 if self.awareness > 0 and \ - ((self.driver_distraction_filter.x < 0.37 and self.face_detected and self.pose.low_std) or standstill_orange_exemption): - if driver_engaged: + ((self.driver_distraction_filter.x < 0.37 and self.face_detected and self.pose.low_std) or standstill_exemption): + if self.driver_interacting: self._reset_awareness() return # only restore awareness when paying attention and alert is not red - self.awareness = min(self.awareness + ((self.settings._RECOVERY_FACTOR_MAX-self.settings._RECOVERY_FACTOR_MIN)* - (1.-self.awareness)+self.settings._RECOVERY_FACTOR_MIN)*self.step_change, 1.) + self.awareness = min(self.awareness + ((self.settings._TIMEOUT_RECOVERY_FACTOR_MAX-self.settings._TIMEOUT_RECOVERY_FACTOR_MIN)* + (1.-self.awareness)+self.settings._TIMEOUT_RECOVERY_FACTOR_MIN)*self.step_change, 1.) if self.awareness == 1.: - self.awareness_passive = min(self.awareness_passive + self.step_change, 1.) + self.last_wheeltouch_awareness = min(self.last_wheeltouch_awareness + self.step_change, 1.) # don't display alert banner when awareness is recovering and has cleared orange - if self.awareness > self.threshold_prompt: + if self.awareness > self.threshold_alert_2: return certainly_distracted = self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected - maybe_distracted = self.hi_stds > self.settings._HI_STD_FALLBACK_TIME or not self.face_detected + maybe_distracted = self.is_model_uncertain or not self.face_detected if certainly_distracted or maybe_distracted: # should always be counting if distracted unless at standstill and reaching green # also will not be reaching 0 if DM is active when not engaged - if not (standstill_orange_exemption or always_on_red_exemption): + if not (standstill_exemption or always_on_exemption): self.awareness = max(self.awareness - self.step_change, -0.1) - alert = None if self.awareness <= 0.: - # terminal red alert: disengagement required - alert = EventName.driverDistracted3 if self.active_monitoring_mode else EventName.driverUnresponsive3 + # terminal alert: disengagement required + self.alert_level = AlertLevel.three self.terminal_time += 1 if awareness_prev > 0.: self.terminal_alert_cnt += 1 - elif self.awareness <= self.threshold_prompt: - # prompt orange alert - alert = EventName.driverDistracted2 if self.active_monitoring_mode else EventName.driverUnresponsive2 - elif self.awareness <= self.threshold_pre: - # pre green alert - alert = EventName.driverDistracted1 if self.active_monitoring_mode else EventName.driverUnresponsive1 - - if alert is not None: - self.current_events.add(alert) - - if self.dcam_uncertain_cnt > self.settings._DCAM_UNCERTAIN_ALERT_COUNT and not self.dcam_uncertain_alerted: - set_offroad_alert("Offroad_DriverMonitoringUncertain", True) - self.dcam_uncertain_alerted = True - + elif self.awareness <= self.threshold_alert_2: + self.alert_level = AlertLevel.two + elif self.awareness <= self.threshold_alert_1: + self.alert_level = AlertLevel.one def get_state_packet(self, valid=True): # build driverMonitoringState packet dat = messaging.new_message('driverMonitoringState', valid=valid) - dat.driverMonitoringState = { - "events": self.current_events.to_msg(), - "faceDetected": self.face_detected, - "isDistracted": self.driver_distracted, - "distractedType": sum(self.distracted_types), - "awarenessStatus": self.awareness, - "posePitchOffset": self.pose.pitch_offseter.filtered_stat.mean(), - "posePitchValidCount": self.pose.pitch_offseter.filtered_stat.n, - "poseYawOffset": self.pose.yaw_offseter.filtered_stat.mean(), - "poseYawValidCount": self.pose.yaw_offseter.filtered_stat.n, - "stepChange": self.step_change, - "awarenessActive": self.awareness_active, - "awarenessPassive": self.awareness_passive, - "isLowStd": self.pose.low_std, - "hiStdCount": self.hi_stds, - "isActiveMode": self.active_monitoring_mode, - "isRHD": self.wheel_on_right, - "uncertainCount": self.dcam_uncertain_cnt, - } + dm = dat.driverMonitoringState + + dm.lockout = self.too_distracted + dm.alertCountLockoutPercent = to_perc(self.terminal_alert_cnt / self.settings._MAX_TERMINAL_ALERTS) + dm.alertTimeLockoutPercent = to_perc(self.terminal_time / self.settings._MAX_TERMINAL_DURATION) + dm.alwaysOn = self.always_on + dm.alwaysOnLockout = self.always_on and self.awareness <= self.threshold_alert_2 + dm.alertLevel = self.alert_level + dm.activePolicy = MonitoringPolicy.vision if self.active_monitoring_mode else MonitoringPolicy.wheeltouch + dm.isRHD = self.wheel_on_right + dm.rhdCalibration.calibratedPercent = to_perc(self.wheelpos.prob_offseter.filtered_stat.n / self.settings._WHEELPOS_FILTER_MIN_COUNT) + dm.rhdCalibration.offset = self.wheelpos.prob_offseter.filtered_stat.M + + dm.visionPolicyState.awarenessPercent = to_perc(self.last_vision_awareness if not self.active_monitoring_mode else self.awareness) + dm.visionPolicyState.awarenessStep = self.step_change if self.active_monitoring_mode else 0. + dm.visionPolicyState.isDistracted = self.driver_distracted + dm.visionPolicyState.distractedTypes.pose = self.distracted_types['pose'] + dm.visionPolicyState.distractedTypes.eye = self.distracted_types['eye'] + dm.visionPolicyState.distractedTypes.phone = self.distracted_types['phone'] + dm.visionPolicyState.faceDetected = self.face_detected + dm.visionPolicyState.pose.pitch = self.pose.pitch + dm.visionPolicyState.pose.yaw = self.pose.yaw + dm.visionPolicyState.pose.calibrated = self.pose.calibrated + dm.visionPolicyState.pose.pitchCalib.calibratedPercent = to_perc(self.pose.pitch_offseter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) + dm.visionPolicyState.pose.pitchCalib.offset = self.pose.pitch_offseter.filtered_stat.M + dm.visionPolicyState.pose.yawCalib.calibratedPercent = to_perc(self.pose.yaw_offseter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) + dm.visionPolicyState.pose.yawCalib.offset = self.pose.yaw_offseter.filtered_stat.M + dm.visionPolicyState.pose.uncertainty = self.model_std_max + dm.visionPolicyState.wheeltouchFallbackPercent = to_perc(self.hi_stds / self.settings._HI_STD_FALLBACK_TIME) + dm.visionPolicyState.uncertainOffroadAlertPercent = to_perc(self.dcam_uncertain_cnt / self.settings._DCAM_UNCERTAIN_ALERT_COUNT) + + dm.wheeltouchPolicyState.awarenessPercent = to_perc(self.last_wheeltouch_awareness if self.active_monitoring_mode else self.awareness) + dm.wheeltouchPolicyState.awarenessStep = 0. if self.active_monitoring_mode else self.step_change + dm.wheeltouchPolicyState.driverInteracting = self.driver_interacting return dat def run_step(self, sm, demo=False): if demo: - highway_speed = 30 + car_speed = 30 enabled = True wrong_gear = False standstill = False @@ -430,23 +409,24 @@ def run_step(self, sm, demo=False): brake_disengage_prob = 1.0 rpyCalib = [0., 0., 0.] else: - highway_speed = sm['carState'].vEgo + car_speed = sm['carState'].vEgo enabled = sm['selfdriveState'].enabled wrong_gear = sm['carState'].gearShifter not in (car.CarState.GearShifter.drive, car.CarState.GearShifter.low) standstill = sm['carState'].standstill driver_engaged = sm['carState'].steeringPressed or sm['carState'].gasPressed brake_disengage_prob = sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s rpyCalib = sm['liveCalibration'].rpyCalib - self._set_policy( + + self._set_pose_strictness( brake_disengage_prob=brake_disengage_prob, - car_speed=highway_speed, + car_speed=car_speed, ) # Parse data from dmonitoringmodeld self._update_states( driver_state=sm['driverStateV2'], cal_rpy=rpyCalib, - car_speed=highway_speed, + car_speed=car_speed, op_engaged=enabled, standstill=standstill, demo_mode=demo, @@ -459,5 +439,5 @@ def run_step(self, sm, demo=False): op_engaged=enabled, standstill=standstill, wrong_gear=wrong_gear, - car_speed=highway_speed + car_speed=car_speed ) diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index d391d3f755e..ef21b3688b1 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -3,16 +3,15 @@ from cereal import log from openpilot.common.realtime import DT_DMON from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS -from openpilot.system.hardware import HARDWARE EventName = log.OnroadEvent.EventName -dm_settings = DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type()) +dm_settings = DRIVER_MONITOR_SETTINGS() TEST_TIMESPAN = 120 # seconds -DISTRACTED_SECONDS_TO_ORANGE = dm_settings._DISTRACTED_TIME - dm_settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + 1 -DISTRACTED_SECONDS_TO_RED = dm_settings._DISTRACTED_TIME + 1 -INVISIBLE_SECONDS_TO_ORANGE = dm_settings._AWARENESS_TIME - dm_settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL + 1 -INVISIBLE_SECONDS_TO_RED = dm_settings._AWARENESS_TIME + 1 +DISTRACTED_SECONDS_TO_ORANGE = dm_settings._VISION_POLICY_ALERT_2_TIMEOUT + 1 +DISTRACTED_SECONDS_TO_RED = dm_settings._VISION_POLICY_ALERT_3_TIMEOUT + 1 +INVISIBLE_SECONDS_TO_ORANGE = dm_settings._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT + 1 +INVISIBLE_SECONDS_TO_RED = dm_settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + 1 def make_msg(face_detected, distracted=False, model_uncertain=False): ds = log.DriverStateV2.new_message() @@ -34,7 +33,7 @@ def make_msg(face_detected, distracted=False, model_uncertain=False): msg_DISTRACTED = make_msg(True, distracted=True) msg_ATTENTIVE_UNCERTAIN = make_msg(True, model_uncertain=True) msg_DISTRACTED_UNCERTAIN = make_msg(True, distracted=True, model_uncertain=True) -msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN = make_msg(True, distracted=True, model_uncertain=dm_settings._POSESTD_THRESHOLD*1.5) +msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN = make_msg(True, distracted=True, model_uncertain=dm_settings._HI_STD_THRESHOLD*1.5) # driver interaction with car car_interaction_DETECTED = True @@ -50,49 +49,47 @@ def make_msg(face_detected, distracted=False, model_uncertain=False): class TestMonitoring: def _run_seq(self, msgs, interaction, engaged, standstill): DM = DriverMonitoring() - events = [] + alert_lvls = [] for idx in range(len(msgs)): DM._update_states(msgs[idx], [0, 0, 0], 0, engaged[idx], standstill[idx]) # cal_rpy and car_speed don't matter here # evaluate events at 10Hz for tests DM._update_events(interaction[idx], engaged[idx], standstill[idx], 0, 0) - events.append(DM.current_events) - assert len(events) == len(msgs), f"got {len(events)} for {len(msgs)} driverState input msgs" - return events, DM + alert_lvls.append(DM.alert_level) + assert len(alert_lvls) == len(msgs), f"got {len(alert_lvls)} for {len(msgs)} driverState input msgs" + return alert_lvls, DM - def _assert_no_events(self, events): - assert all(not len(e) for e in events) # engaged, driver is attentive all the time def test_fully_aware_driver(self): - events, _ = self._run_seq(always_attentive, always_false, always_true, always_false) - self._assert_no_events(events) + alert_lvls, _ = self._run_seq(always_attentive, always_false, always_true, always_false) + assert all(a == 0 for a in alert_lvls) # engaged, driver is distracted and does nothing def test_fully_distracted_driver(self): - events, d_status = self._run_seq(always_distracted, always_false, always_true, always_false) - assert len(events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0 - assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL + \ - ((d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \ - EventName.driverDistracted1 - assert events[int((d_status.settings._DISTRACTED_TIME-d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL + \ - ((d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.driverDistracted2 - assert events[int((d_status.settings._DISTRACTED_TIME + \ - ((TEST_TIMESPAN-10-d_status.settings._DISTRACTED_TIME)/2))/DT_DMON)].names[0] == EventName.driverDistracted3 + alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, always_false) + s = d_status.settings + assert alert_lvls[int(s._VISION_POLICY_ALERT_1_TIMEOUT / 2 / DT_DMON)] == 0 + assert alert_lvls[int((s._VISION_POLICY_ALERT_1_TIMEOUT + \ + (s._VISION_POLICY_ALERT_2_TIMEOUT - s._VISION_POLICY_ALERT_1_TIMEOUT) / 2) / DT_DMON)] == 1 + assert alert_lvls[int((s._VISION_POLICY_ALERT_2_TIMEOUT + \ + (s._VISION_POLICY_ALERT_3_TIMEOUT - s._VISION_POLICY_ALERT_2_TIMEOUT) / 2) / DT_DMON)] == 2 + assert alert_lvls[int((s._VISION_POLICY_ALERT_3_TIMEOUT + \ + (TEST_TIMESPAN - 10 - s._VISION_POLICY_ALERT_3_TIMEOUT) / 2) / DT_DMON)] == 3 assert isinstance(d_status.awareness, float) # engaged, no face detected the whole time, no action def test_fully_invisible_driver(self): - events, d_status = self._run_seq(always_no_face, always_false, always_true, always_false) - assert len(events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)]) == 0 - assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL + \ - ((d_status.settings._AWARENESS_PRE_TIME_TILL_TERMINAL-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == \ - EventName.driverUnresponsive1 - assert events[int((d_status.settings._AWARENESS_TIME-d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL + \ - ((d_status.settings._AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0] == EventName.driverUnresponsive2 - assert events[int((d_status.settings._AWARENESS_TIME + \ - ((TEST_TIMESPAN-10-d_status.settings._AWARENESS_TIME)/2))/DT_DMON)].names[0] == EventName.driverUnresponsive3 + alert_lvls, d_status = self._run_seq(always_no_face, always_false, always_true, always_false) + s = d_status.settings + assert alert_lvls[int(s._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT / 2 / DT_DMON)] == 0 + assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT + \ + (s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT - s._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT) / 2) / DT_DMON)] == 1 + assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT + \ + (s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT - s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT) / 2) / DT_DMON)] == 2 + assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + \ + (TEST_TIMESPAN - 10 - s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT) / 2) / DT_DMON)] == 3 # engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel # - should have short orange recovery time and no green afterwards; wheel touch only recovers when paying attention @@ -103,13 +100,13 @@ def test_normal_driver(self): [msg_ATTENTIVE] * (int(TEST_TIMESPAN/DT_DMON)-int((DISTRACTED_SECONDS_TO_ORANGE*3+2)/DT_DMON)) interaction_vector = [car_interaction_NOT_DETECTED] * int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON) + \ [car_interaction_DETECTED] * (int(TEST_TIMESPAN/DT_DMON)-int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON)) - events, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false) - assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 - assert events[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.driverDistracted2 - assert len(events[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)]) == 0 - assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0] == EventName.driverDistracted2 - assert events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)].names[0] == EventName.driverDistracted2 - assert len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)]) == 0 + alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false) + assert alert_lvls[int(DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)] == 0 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)] == 2 + assert alert_lvls[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)] == 0 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)] == 2 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)] == 0 # engaged, down to orange, driver dodges camera, then comes back still distracted, down to red, \ # driver dodges, and then touches wheel to no avail, disengages and reengages @@ -127,11 +124,11 @@ def test_biggest_comma_fan(self): = [True] * int(1/DT_DMON) op_vector[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+2.5)/DT_DMON):int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3)/DT_DMON)] \ = [False] * int(0.5/DT_DMON) - events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) - assert events[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)].names[0] == EventName.driverDistracted2 - assert events[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)].names[0] == EventName.driverDistracted3 - assert events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)].names[0] == EventName.driverDistracted3 - assert len(events[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)]) == 0 + alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) + assert alert_lvls[int((DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)] == 2 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)] == 3 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)] == 3 + assert alert_lvls[int((DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)] == 0 # engaged, invisible driver, down to orange, driver touches wheel; then down to orange again, driver appears # - both actions should clear the alert, but momentary appearance should not @@ -142,16 +139,16 @@ def test_sometimes_transparent_commuter(self): ds_vector[int((2*INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON):int((2*INVISIBLE_SECONDS_TO_ORANGE+1+_visible_time)/DT_DMON)] = \ [msg_ATTENTIVE] * int(_visible_time/DT_DMON) interaction_vector[int((INVISIBLE_SECONDS_TO_ORANGE)/DT_DMON):int((INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON)] = [True] * int(1/DT_DMON) - events, _ = self._run_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false) - assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 - assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2 - assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)]) == 0 + alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false) + assert alert_lvls[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)] == 0 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)] == 0 if _visible_time == 0.5: - assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2 - assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)].names[0] == EventName.driverUnresponsive1 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)] == 1 elif _visible_time == 10: - assert events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2 - assert len(events[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)]) == 0 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)] == 0 # engaged, invisible driver, down to red, driver appears and then touches wheel, then disengages/reengages # - only disengage will clear the alert @@ -163,19 +160,19 @@ def test_last_second_responder(self): ds_vector[int(INVISIBLE_SECONDS_TO_RED/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time)/DT_DMON)] = [msg_ATTENTIVE] * int(_visible_time/DT_DMON) interaction_vector[int((INVISIBLE_SECONDS_TO_RED+_visible_time)/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON)] = [True] * int(1/DT_DMON) op_vector[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON):int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] = [False] * int(0.5/DT_DMON) - events, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) - assert len(events[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)]) == 0 - assert events[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive2 - assert events[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)].names[0] == EventName.driverUnresponsive3 - assert events[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)].names[0] == EventName.driverUnresponsive3 - assert events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)].names[0] == EventName.driverUnresponsive3 - assert len(events[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)]) == 0 + alert_lvls, _ = self._run_seq(ds_vector, interaction_vector, op_vector, always_false) + assert alert_lvls[int(INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)] == 0 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)] == 3 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)] == 3 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] == 3 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)] == 0 # disengaged, always distracted driver # - dm should stay quiet when not engaged def test_pure_dashcam_user(self): - events, _ = self._run_seq(always_distracted, always_false, always_false, always_false) - assert sum(len(event) for event in events) == 0 + alert_lvls, _ = self._run_seq(always_distracted, always_false, always_false, always_false) + assert all(a == 0 for a in alert_lvls) # engaged, car stops at traffic light, down to orange, no action, then car starts moving # - should only reach green when stopped, but continues counting down on launch @@ -183,11 +180,12 @@ def test_long_traffic_light_victim(self): _redlight_time = 60 # seconds standstill_vector = always_true[:] standstill_vector[int(_redlight_time/DT_DMON):] = [False] * int((TEST_TIMESPAN-_redlight_time)/DT_DMON) - events, d_status = self._run_seq(always_distracted, always_false, always_true, standstill_vector) - assert len(events[int((_redlight_time-0.1)/DT_DMON)]) == 0 - _pre_to_prompt = d_status.settings._DISTRACTED_PRE_TIME_TILL_TERMINAL - d_status.settings._DISTRACTED_PROMPT_TIME_TILL_TERMINAL - assert events[int((_redlight_time+0.5)/DT_DMON)].names[0] == EventName.driverDistracted1 - assert events[int((_redlight_time+_pre_to_prompt+0.5)/DT_DMON)].names[0] == EventName.driverDistracted2 + alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, standstill_vector) + s = d_status.settings + assert alert_lvls[int((_redlight_time-0.1)/DT_DMON)] == 0 + _alert_1_to_2 = s._VISION_POLICY_ALERT_2_TIMEOUT - s._VISION_POLICY_ALERT_1_TIMEOUT + assert alert_lvls[int((_redlight_time+0.5)/DT_DMON)] == 1 + assert alert_lvls[int((_redlight_time+_alert_1_to_2+0.5)/DT_DMON)] == 2 # engaged, distracted while moving, then car stops after reaching orange # - should reset timer to pre green at standstill @@ -195,20 +193,18 @@ def test_distracted_then_stops(self): _stop_time = DISTRACTED_SECONDS_TO_ORANGE + 1 # stop 1 second after reaching orange standstill_vector = always_false[:] standstill_vector[int(_stop_time/DT_DMON):] = [True] * int((TEST_TIMESPAN-_stop_time)/DT_DMON) - events, _ = self._run_seq(always_distracted, always_false, always_true, standstill_vector) + alert_lvls, _ = self._run_seq(always_distracted, always_false, always_true, standstill_vector) # just before and briefly after stopping: orange alert; goes away quickly after stopped - assert events[int((_stop_time+0.1)/DT_DMON)].names[0] == EventName.driverDistracted2 - assert len(events[int((_stop_time+0.5)/DT_DMON)]) == 0 + assert alert_lvls[int((_stop_time+0.1)/DT_DMON)] == 2 + assert alert_lvls[int((_stop_time+0.5)/DT_DMON)] == 0 # engaged, model is somehow uncertain and driver is distracted # - should fall back to wheel touch after uncertain alert def test_somehow_indecisive_model(self): ds_vector = [msg_DISTRACTED_BUT_SOMEHOW_UNCERTAIN] * int(TEST_TIMESPAN/DT_DMON) interaction_vector = always_false[:] - events, d_status = self._run_seq(ds_vector, interaction_vector, always_true, always_false) - assert EventName.driverUnresponsive1 in \ - events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME-0.1)/DT_DMON)].names - assert EventName.driverUnresponsive2 in \ - events[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names - assert EventName.driverUnresponsive3 in \ - events[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*d_status.settings._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)].names + alert_lvls, d_status = self._run_seq(ds_vector, interaction_vector, always_true, always_false) + s = d_status.settings + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*s._HI_STD_FALLBACK_TIME-0.1)/DT_DMON)] == 1 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_ORANGE-1+DT_DMON*s._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)] == 2 + assert alert_lvls[int((INVISIBLE_SECONDS_TO_RED-1+DT_DMON*s._HI_STD_FALLBACK_TIME+0.1)/DT_DMON)] == 3 diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index dbc3174af74..110b0b722ec 100755 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -38,6 +38,8 @@ EventName = log.OnroadEvent.EventName ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel +AlertLevel = log.DriverMonitoringState.AlertLevel +MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput) @@ -120,6 +122,8 @@ def __init__(self, CP=None): self.experimental_mode = False self.personality = self.params.get("LongitudinalPersonality", return_default=True) self.recalibrating_seen = False + self.dm_lockout_set = False + self.dm_uncertain_alerted = False self.state_machine = StateMachine() self.rk = Ratekeeper(100, print_delay_threshold=None) @@ -182,8 +186,27 @@ def update_events(self, CS): if not self.CP.pcmCruise and CS.vCruise > 250 and resume_pressed: self.events.add(EventName.resumeBlocked) + # Handle DM if not self.CP.notCar: - self.events.add_from_msg(self.sm['driverMonitoringState'].events) + # Block engaging until ignition cycle after max number or time of distractions + if self.sm['driverMonitoringState'].lockout and not self.dm_lockout_set: + self.params.put_bool_nonblocking("DriverTooDistracted", True) + self.dm_lockout_set = True + # No entry conditions + if self.sm['driverMonitoringState'].lockout or self.sm['driverMonitoringState'].alwaysOnLockout: + self.events.add(EventName.tooDistracted) + # Alerts + vision_dm = self.sm['driverMonitoringState'].activePolicy == MonitoringPolicy.vision + if self.sm['driverMonitoringState'].alertLevel == AlertLevel.one: + self.events.add(EventName.driverDistracted1 if vision_dm else EventName.driverUnresponsive1) + elif self.sm['driverMonitoringState'].alertLevel == AlertLevel.two: + self.events.add(EventName.driverDistracted2 if vision_dm else EventName.driverUnresponsive2) + elif self.sm['driverMonitoringState'].alertLevel == AlertLevel.three: + self.events.add(EventName.driverDistracted3 if vision_dm else EventName.driverUnresponsive3) + # Warn consistent DM uncertainty + if self.sm['driverMonitoringState'].visionPolicyState.uncertainOffroadAlertPercent >= 100 and not self.dm_uncertain_alerted: + set_offroad_alert("Offroad_DriverMonitoringUncertain", True) + self.dm_uncertain_alerted = True # Add car events, ignore if CAN isn't valid if CS.canValid: diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index 03e59e53675..b6134913471 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -482,22 +482,41 @@ def migrate_onroadEvents(msgs): return ops, [], [] -@migration(inputs=["driverMonitoringState"]) +@migration(inputs=["driverMonitoringStateDEPRECATED"]) def migrate_driverMonitoringState(msgs): ops = [] for index, msg in msgs: - msg = msg.as_builder() - events = [] - for event in msg.driverMonitoringState.deprecated.events: - try: - if not str(event.name).endswith('DEPRECATED'): - migrated_event = migrate_onroad_event(event) - if migrated_event is not None: - events.append(migrated_event) - except RuntimeError: # Member was null - traceback.print_exc() - - msg.driverMonitoringState.events = events - ops.append((index, msg.as_reader())) + old = msg.driverMonitoringStateDEPRECATED + new_msg = messaging.new_message('driverMonitoringState', valid=msg.valid, logMonoTime=msg.logMonoTime) + dm = new_msg.driverMonitoringState + dm.isRHD = old.isRHD + dm.activePolicy = log.DriverMonitoringState.MonitoringPolicy.vision if old.isActiveMode else \ + log.DriverMonitoringState.MonitoringPolicy.wheeltouch + + AlertLevel = log.DriverMonitoringState.AlertLevel + event_to_alert_level = { + 'driverDistracted1': AlertLevel.one, 'driverUnresponsive1': AlertLevel.one, + 'driverDistracted2': AlertLevel.two, 'driverUnresponsive2': AlertLevel.two, + 'driverDistracted3': AlertLevel.three, 'driverUnresponsive3': AlertLevel.three, + 'tooDistracted': AlertLevel.three, + } + for event in old.events: + level = event_to_alert_level.get(str(event.name)) + if level is not None: + dm.alertLevel = level + break + + dm.visionPolicyState.awarenessPercent = int(max(0, min(100, (old.awarenessStatus if old.isActiveMode else old.awarenessActive) * 100))) + dm.visionPolicyState.awarenessStep = old.stepChange if old.isActiveMode else 0. + dm.visionPolicyState.isDistracted = old.isDistracted + dm.visionPolicyState.faceDetected = old.faceDetected + dm.visionPolicyState.pose.pitchCalib.offset = old.posePitchOffset + dm.visionPolicyState.pose.pitchCalib.calibratedPercent = int(min(100, old.posePitchValidCount / 600 * 100)) + dm.visionPolicyState.pose.yawCalib.offset = old.poseYawOffset + dm.visionPolicyState.pose.yawCalib.calibratedPercent = int(min(100, old.poseYawValidCount / 600 * 100)) + dm.visionPolicyState.pose.calibrated = old.posePitchValidCount >= 600 and old.poseYawValidCount >= 600 + dm.wheeltouchPolicyState.awarenessPercent = int(max(0, min(100, (old.awarenessPassive if old.isActiveMode else old.awarenessStatus) * 100))) + dm.wheeltouchPolicyState.awarenessStep = 0. if old.isActiveMode else old.stepChange + ops.append((index, new_msg.as_reader())) return ops, [], [] diff --git a/selfdrive/ui/mici/layouts/onboarding.py b/selfdrive/ui/mici/layouts/onboarding.py index b918bf6ef60..054d719bd6c 100644 --- a/selfdrive/ui/mici/layouts/onboarding.py +++ b/selfdrive/ui/mici/layouts/onboarding.py @@ -138,7 +138,7 @@ def _update_state(self): # stay at 100% once reached in_bad_face = gui_app.get_active_widget() == self._bad_face_page - if ((dm_state.faceDetected and looking_center) or self._progress.x > 0.99) and not in_bad_face: + if ((dm_state.visionPolicyState.faceDetected and looking_center) or self._progress.x > 0.99) and not in_bad_face: slow = self._progress.x < 0.25 duration = self.PROGRESS_DURATION * 2 if slow else self.PROGRESS_DURATION self._progress.x += 1.0 / (duration * gui_app.target_fps) diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index e8b8abb7f5c..6fa3d891835 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -1,20 +1,15 @@ import pyray as rl -from cereal import log, messaging +from cereal import car, log, messaging from msgq.visionipc import VisionStreamType from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.ui_state import ui_state, device -from openpilot.selfdrive.selfdrived.events import EVENTS, ET from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.lib.multilang import tr from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.nav_widget import NavWidget from openpilot.system.ui.widgets.label import gui_label -EventName = log.OnroadEvent.EventName - -EVENT_TO_INT = EventName.schema.enumerants - class DriverCameraView(CameraView): def _calc_frame_matrix(self, rect: rl.Rectangle): @@ -107,11 +102,14 @@ def _publish_alert_sound(self, dm_state): if self._pm is None: return + AudibleAlert = car.CarControl.HUDControl.AudibleAlert + ALERT_SOUNDS = { + log.DriverMonitoringState.AlertLevel.two: AudibleAlert.promptDistracted, + log.DriverMonitoringState.AlertLevel.three: AudibleAlert.warningImmediate, + } msg = messaging.new_message('selfdriveState') - if dm_state is not None and len(dm_state.events): - event_name = EVENT_TO_INT[dm_state.events[0].name] - if event_name is not None and event_name in EVENTS and ET.PERMANENT in EVENTS[event_name]: - msg.selfdriveState.alertSound = EVENTS[event_name][ET.PERMANENT].audible_alert + if dm_state is not None: + msg.selfdriveState.alertSound = ALERT_SOUNDS.get(dm_state.alertLevel, AudibleAlert.none) self._pm.send('selfdriveState', msg) def _render_dm_alerts(self, rect: rl.Rectangle): @@ -119,29 +117,31 @@ def _render_dm_alerts(self, rect: rl.Rectangle): dm_state = ui_state.sm["driverMonitoringState"] self._publish_alert_sound(dm_state) + is_vision = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision + awareness_pct = dm_state.visionPolicyState.awarenessPercent if is_vision else dm_state.wheeltouchPolicyState.awarenessPercent gui_label(rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height), - f"Awareness: {dm_state.awarenessStatus * 100:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, + f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, color=rl.Color(0, 0, 0, 180)) - gui_label(rect, f"Awareness: {dm_state.awarenessStatus * 100:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, + gui_label(rect, f"Awareness: {awareness_pct:.0f}%", font_size=44, font_weight=FontWeight.MEDIUM, alignment=rl.GuiTextAlignment.TEXT_ALIGN_RIGHT, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_TOP, color=rl.Color(255, 255, 255, int(255 * 0.9))) - if not dm_state.events: + if dm_state.alertLevel == log.DriverMonitoringState.AlertLevel.none: return - # Show first event (only one should be active at a time) - event_name_str = str(dm_state.events[0].name).split('.')[-1] + # Show alert level + alert_level_str = dm_state.alertLevel alignment = rl.GuiTextAlignment.TEXT_ALIGN_RIGHT if self.driver_state_renderer.is_rhd else rl.GuiTextAlignment.TEXT_ALIGN_LEFT shadow_rect = rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height) - gui_label(shadow_rect, event_name_str, font_size=40, font_weight=FontWeight.BOLD, + gui_label(shadow_rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD, alignment=alignment, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, color=rl.Color(0, 0, 0, 180)) - gui_label(rect, event_name_str, font_size=40, font_weight=FontWeight.BOLD, + gui_label(rect, alert_level_str, font_size=40, font_weight=FontWeight.BOLD, alignment=alignment, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM, color=rl.Color(255, 255, 255, int(255 * 0.9))) @@ -156,7 +156,7 @@ def _load_eye_textures(self): def _draw_face_detection(self, rect: rl.Rectangle): dm_state = ui_state.sm["driverMonitoringState"] driver_data = self.driver_state_renderer.get_driver_data() - if not dm_state.faceDetected: + if not dm_state.visionPolicyState.faceDetected: return # Get face position and orientation diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index b2be5a8e340..c51e7eca6ee 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -153,9 +153,9 @@ def get_driver_data(self): sm = ui_state.sm dm_state = sm["driverMonitoringState"] - self._is_active = dm_state.isActiveMode + self._is_active = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision self._is_rhd = dm_state.isRHD - self._face_detected = dm_state.faceDetected + self._face_detected = dm_state.visionPolicyState.faceDetected driverstate = sm["driverStateV2"] driver_data = driverstate.rightDriverData if self._is_rhd else driverstate.leftDriverData diff --git a/selfdrive/ui/onroad/driver_state.py b/selfdrive/ui/onroad/driver_state.py index 7b3181d1ac5..5b09e471b52 100644 --- a/selfdrive/ui/onroad/driver_state.py +++ b/selfdrive/ui/onroad/driver_state.py @@ -114,7 +114,7 @@ def _update_state(self): # Get monitoring state dm_state = sm["driverMonitoringState"] - self.is_active = dm_state.isActiveMode + self.is_active = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision self.is_rhd = dm_state.isRHD # Update fade state (smoother transition between active/inactive) diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py index a8374a00cdb..6ac7c4fa59a 100644 --- a/tools/sim/lib/simulated_sensors.py +++ b/tools/sim/lib/simulated_sensors.py @@ -92,11 +92,12 @@ def send_fake_driver_monitoring(self): # dmonitoringd output dat = messaging.new_message('driverMonitoringState', valid=True) - dat.driverMonitoringState = { - "faceDetected": True, - "isDistracted": False, - "awarenessStatus": 1., - } + dm = dat.driverMonitoringState + dm.alertLevel = log.DriverMonitoringState.AlertLevel.none + dm.activePolicy = log.DriverMonitoringState.MonitoringPolicy.vision + dm.visionPolicyState.faceDetected = True + dm.visionPolicyState.isDistracted = False + dm.visionPolicyState.awarenessPercent = 100 self.pm.send('driverMonitoringState', dat) def send_camera_images(self, world: 'World'): From 2c0903e45e0fbee36aa182e7d27761fff55a383d Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sat, 18 Apr 2026 15:21:47 -0400 Subject: [PATCH 655/685] tools: add retry mechanism for API requests (#36617) --- tools/lib/api.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/lib/api.py b/tools/lib/api.py index c6e2d989141..f84fe758695 100644 --- a/tools/lib/api.py +++ b/tools/lib/api.py @@ -1,5 +1,6 @@ import os import requests +from requests.adapters import HTTPAdapter, Retry API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') # TODO: this should be merged into common.api @@ -11,6 +12,9 @@ def __init__(self, token=None): if token: self.session.headers['Authorization'] = 'JWT ' + token + retries = Retry(total=5, backoff_factor=1, status_forcelist=[500, 502, 503, 504]) + self.session.mount('https://', HTTPAdapter(max_retries=retries)) + def request(self, method, endpoint, **kwargs): with self.session.request(method, API_HOST + '/' + endpoint, **kwargs) as resp: resp_json = resp.json() From b9986cae06059ef725b5ab77756bd19bb6ac9354 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Sat, 18 Apr 2026 12:38:21 -0700 Subject: [PATCH 656/685] lpa: add is_euicc() (#37847) --- system/hardware/base.py | 4 ++++ system/hardware/tici/lpa.py | 41 +++++++++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/system/hardware/base.py b/system/hardware/base.py index ef2b1460434..29c739654e0 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -94,6 +94,10 @@ def switch_profile(self, iccid: str) -> None: def process_notifications(self) -> None: pass + @abstractmethod + def is_euicc(self) -> bool: + pass + def is_comma_profile(self, iccid: str) -> bool: return any(iccid.startswith(prefix) for prefix in ('8985235',)) diff --git a/system/hardware/tici/lpa.py b/system/hardware/tici/lpa.py index e46ac005b80..63fe9ae7d53 100644 --- a/system/hardware/tici/lpa.py +++ b/system/hardware/tici/lpa.py @@ -716,22 +716,29 @@ def __init__(self): atexit.register(self._client.close) @contextmanager - def _acquire_channel(self): + def _acquire_lock(self): fd = os.open(LOCK_FILE, os.O_CREAT | os.O_RDWR) try: fcntl.flock(fd, fcntl.LOCK_EX) - self._client.open_isdr() yield finally: - if self._client.channel: - try: - self._client.query(f"AT+CCHC={self._client.channel}") - except (RuntimeError, TimeoutError): - pass - self._client.channel = None fcntl.flock(fd, fcntl.LOCK_UN) os.close(fd) + @contextmanager + def _acquire_channel(self): + with self._acquire_lock(): + try: + self._client.open_isdr() + yield + finally: + if self._client.channel: + try: + self._client.query(f"AT+CCHC={self._client.channel}") + except (RuntimeError, TimeoutError): + pass + self._client.channel = None + def list_profiles(self) -> list[Profile]: with self._acquire_channel(): return [ @@ -792,3 +799,21 @@ def switch_profile(self, iccid: str) -> None: if HARDWARE.get_device_type() == "mici": self._client.send_raw(b'AT+CFUN=0\rAT+CFUN=1\r') # mici has no SIM presence pin; raw because CFUN=0 drops serial self._client._ensure_serial(reconnect=True) + + def is_euicc(self) -> bool: + # +CCHO: -> eUICC; bare ERROR -> applet absent, non-eUICC; +CME ERROR -> applet + # exists but bus busy or modem in transient state, still eUICC. + with self._acquire_lock(): + try: + lines = self._client.query(f'AT+CCHO="{ISDR_AID}"') + except RuntimeError as e: + return "+CME ERROR" in str(e) + for line in lines: + if line.startswith("+CCHO:") and (ch := line.split(":", 1)[1].strip()): + try: + self._client.query(f"AT+CCHC={ch}") + except (RuntimeError, TimeoutError): + pass + self._client.channel = None + return True + return False From 5d7155fdda5739e7c4d2a7edd897b12daee033ed Mon Sep 17 00:00:00 2001 From: stef <19478336+stefpi@users.noreply.github.com> Date: Sat, 18 Apr 2026 13:00:05 -0700 Subject: [PATCH 657/685] body ui c3 & c4 (#37794) * c4 body ui * clean up diff * clean up * default bodyview debug with True * remove battery indicator and fix close settings bug * organize debug file * clean * clean up frame index * remove unneccessary is body check * update bodyview * remove joystick_debug_mode based events * remove debug script * apply suggestions * clean diff * clean diff * move ignition message * save a line * remove visibility set and fix tici body face when sidebar open * remove explicit textColor offroad message * remove unused imports * revert pairing dialog * apply suggestions * add body specific icon * add body icon for homescreen * set mode for state on tici after on body changed * tiny * tweak * v * tweaks * icon ratio was wrong! * same order * rm * apply suggestions * remove commented lines in animation * onroad click callback was on home bug and fix setup widget settings call back hack * fix body changed * one liner * clean up * formatting * if false * revert to master + reimplement * close sidebar on body home tici * make ignition message bigger on c3 * flip eye direction when turning --------- Co-authored-by: Nick Co-authored-by: Shane Smiskol --- selfdrive/assets/icons_mici/body.png | 3 + selfdrive/ui/body/__init__.py | 0 selfdrive/ui/body/animations.py | 278 ++++++++++++++++++++++++++ selfdrive/ui/body/layouts/__init__.py | 0 selfdrive/ui/body/layouts/onroad.py | 93 +++++++++ selfdrive/ui/layouts/main.py | 25 ++- selfdrive/ui/mici/layouts/home.py | 3 + selfdrive/ui/mici/layouts/main.py | 26 ++- selfdrive/ui/ui_state.py | 19 +- 9 files changed, 434 insertions(+), 13 deletions(-) create mode 100644 selfdrive/assets/icons_mici/body.png create mode 100644 selfdrive/ui/body/__init__.py create mode 100644 selfdrive/ui/body/animations.py create mode 100644 selfdrive/ui/body/layouts/__init__.py create mode 100644 selfdrive/ui/body/layouts/onroad.py diff --git a/selfdrive/assets/icons_mici/body.png b/selfdrive/assets/icons_mici/body.png new file mode 100644 index 00000000000..2f7bb779d18 --- /dev/null +++ b/selfdrive/assets/icons_mici/body.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b45f88128430fb50ef51c8e08b8e2a1c8fbe0b5c3a08de9f5d9d59bc1edc82e +size 4545 diff --git a/selfdrive/ui/body/__init__.py b/selfdrive/ui/body/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/selfdrive/ui/body/animations.py b/selfdrive/ui/body/animations.py new file mode 100644 index 00000000000..c40f7ecdefe --- /dev/null +++ b/selfdrive/ui/body/animations.py @@ -0,0 +1,278 @@ +from dataclasses import dataclass +from enum import Enum +import time + + +class AnimationMode(Enum): + ONCE_FORWARD = 1 + ONCE_FORWARD_BACKWARD = 2 + REPEAT_FORWARD = 3 + REPEAT_FORWARD_BACKWARD = 4 + + +@dataclass +class Animation: + frames: list[list[tuple[int, int]]] + starting_frames: list[list[tuple[int, int]]] | None = None # played once before the main loop + frame_duration: float = 0.15 # seconds each frame is shown + mode: AnimationMode = AnimationMode.REPEAT_FORWARD_BACKWARD + repeat_interval: float = 5.0 # seconds between animation restarts (only for REPEAT modes) + hold_end: float = 0.0 # seconds to hold the last frame before playing backward (only for *_BACKWARD modes) + left_turn_remove: list[tuple[int, int]] | None = None # dots to remove from frame when turning left + right_turn_remove: list[tuple[int, int]] | None = None # dots to remove from frame when turning right + + +# --- Animation Helper Functions --- + +def _mirror(dots: list[tuple[int, int]]) -> list[tuple[int, int]]: + """Mirror a component from the left side of the face to the right""" + return [(r, 15 - c) for r, c in dots] + + +def _mirror_no_flip(dots: list[tuple[int, int]]) -> list[tuple[int, int]]: + """Move a component to the mirrored position on the right half without flipping its shape.""" + min_c = min(c for _, c in dots) + max_c = max(c for _, c in dots) + return [(r, 15 - max_c - min_c + c) for r, c in dots] + + +def _shift(dots: list[tuple[int, int]], rc: tuple[int, int]) -> list[tuple[int, int]]: + dr, dc = rc + return [(r + dr, c + dc) for r, c in dots] + + +def _make_frame(left_eye: list[tuple[int, int]], right_eye: list[tuple[int, int]], + left_brow: list[tuple[int, int]], right_brow: list[tuple[int, int]], + mouth: list[tuple[int, int]]) -> list[tuple[int, int]]: + return left_eye + left_brow + right_eye + right_brow + mouth + + +# --- Animation Helper Components --- + +# Eyes (left side) +EYE_OPEN = [ + (2, 2), (2, 3), +(3, 1), (3, 2), (3, 3), (3, 4), +(4, 1), (4, 2), (4, 3), (4, 4), + (5, 2), (5, 3) +] +EYE_HALF = [ +(4, 1), (4, 2), (4, 3), (4, 4), + (5, 2), (5, 3) +] +EYE_CLOSED = [ +(4, 1), (4, 4), + (5, 2), (5, 3), +] +EYE_LEFT_LOOK = [ + (2, 2), (2, 3), +(3, 1), (3, 2), +(4, 1), (4, 2), + (5, 2), (5, 3), +] +EYE_RIGHT_LOOK = [ + (2, 2), (2, 3), + (3, 3), (3, 4), + (4, 3), (4, 4), + (5, 2), (5, 3), +] + +# Eyebrows (left side) +BROW_HIGH = [ + (0, 1), (0, 2), +(1, 0), +] +BROW_LOWERED = [ + (1, 1), (1, 2), +(2, 0) +] +BROW_STRAIGHT = [(1, 0), (1, 1), (1, 2)] +BROW_DOWN = [ +(0, 1), (0, 2), + (1, 3) +] + +# Mouths (centered, not mirrored) +MOUTH_SMILE = [ +(6, 6), (6, 9), + (7, 7), (7, 8), +] +MOUTH_NORMAL = [(7, 7), (7, 8)] +MOUTH_SAD = [ + (6, 7), (6, 8), +(7, 6), (7, 9) +] + +# --- Animations --- + +NORMAL = Animation( + frames=[ + _make_frame(EYE_OPEN, _mirror(EYE_OPEN), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(EYE_HALF, _mirror(EYE_HALF), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(EYE_CLOSED, _mirror(EYE_CLOSED), BROW_LOWERED, _mirror(BROW_LOWERED), MOUTH_SMILE), + ], + left_turn_remove=[ + (3, 3), (3, 4), + (4, 3), (4, 4), + ] + _mirror_no_flip([ + (3, 1), (3, 2), + (4, 1), (4, 2), + ]), + right_turn_remove=[ + (3, 1), (3, 2), + (4, 1), (4, 2), + ] + _mirror_no_flip([ + (3, 3), (3, 4), + (4, 3), (4, 4), + ]) +) + +ASLEEP = Animation( + frames=[ + _make_frame(EYE_CLOSED, _mirror(EYE_CLOSED), [], [], MOUTH_NORMAL), + ], +) + +SLEEPY = Animation( + frames=[ + _make_frame(EYE_CLOSED, _mirror(EYE_CLOSED), _shift(BROW_STRAIGHT, (1, 0)), [], MOUTH_NORMAL), + _make_frame(EYE_HALF, _mirror(EYE_CLOSED), BROW_LOWERED, [], MOUTH_NORMAL), + _make_frame(EYE_OPEN, _mirror(EYE_CLOSED), BROW_HIGH, [], MOUTH_NORMAL) + ], + frame_duration=0.25, + mode=AnimationMode.ONCE_FORWARD_BACKWARD, + repeat_interval=10, + hold_end=1.5, +) + +INQUISITIVE = Animation( + frames=[ + _make_frame(EYE_OPEN, _mirror(EYE_OPEN), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + + _make_frame(EYE_LEFT_LOOK, _mirror(EYE_RIGHT_LOOK), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(_shift(EYE_LEFT_LOOK, (0, -1)), _shift(_mirror(EYE_RIGHT_LOOK), (0, -1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(_shift(EYE_LEFT_LOOK, (0, -1)), _shift(_mirror(EYE_RIGHT_LOOK), (0, -1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(_shift(EYE_LEFT_LOOK, (0, -1)), _shift(_mirror(EYE_RIGHT_LOOK), (0, -1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(EYE_LEFT_LOOK, _mirror(EYE_RIGHT_LOOK), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + + _make_frame(EYE_RIGHT_LOOK, _mirror(EYE_LEFT_LOOK), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(_shift(EYE_RIGHT_LOOK, (0, 1)), _shift(_mirror(EYE_LEFT_LOOK), (0, 1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(_shift(EYE_RIGHT_LOOK, (0, 1)), _shift(_mirror(EYE_LEFT_LOOK), (0, 1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(_shift(EYE_RIGHT_LOOK, (0, 1)), _shift(_mirror(EYE_LEFT_LOOK), (0, 1)), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(EYE_RIGHT_LOOK, _mirror(EYE_LEFT_LOOK), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + + _make_frame(EYE_OPEN, _mirror(EYE_OPEN), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + ], + mode=AnimationMode.REPEAT_FORWARD, + frame_duration=0.15, + repeat_interval=10 +) + +WINK = Animation( + frames=[ + _make_frame(EYE_OPEN, _mirror(EYE_OPEN), BROW_HIGH, _mirror(BROW_HIGH), MOUTH_SMILE), + _make_frame(EYE_OPEN, _mirror(EYE_CLOSED), BROW_HIGH, _mirror(_shift(BROW_DOWN, (0, 2))), MOUTH_SMILE), + ], + mode=AnimationMode.ONCE_FORWARD_BACKWARD, + frame_duration=0.75, +) + + +# --- Face Animator Class --- + +class FaceAnimator: + def __init__(self, animation: Animation): + self._animation = animation + self._next: Animation | None = None + self._start_time = time.monotonic() + self._rewinding = False + self._rewind_start: float = 0.0 + self._rewind_from: int = 0 + self._seen_nonzero = False + + def set_animation(self, animation: Animation): + if animation is not self._animation: + self._next = animation + + def get_dots(self) -> list[tuple[int, int]]: + now = time.monotonic() + elapsed = now - self._start_time + + # Handle rewind for forward-only animations + if self._rewinding: + rewind_elapsed = now - self._rewind_start + frames_back = round(rewind_elapsed / self._animation.frame_duration) + frame_index = self._rewind_from - frames_back + if frame_index <= 0: + return self._switch_to_next(now) + return self._animation.frames[frame_index] + + # Play starting frames first (once) + starting = self._animation.starting_frames or [] + starting_duration = len(starting) * self._animation.frame_duration + if starting and elapsed < starting_duration: + frame_index = min(int(elapsed / self._animation.frame_duration), len(starting) - 1) + return starting[frame_index] + + # Main loop + loop_elapsed = elapsed - starting_duration if starting else elapsed + frame_index = _get_frame_index(self._animation, loop_elapsed, gap_first=bool(starting)) + + if frame_index != 0: + self._seen_nonzero = True + + if self._next is not None: + if frame_index == 0 and (len(self._animation.frames) == 1 or self._seen_nonzero): + return self._switch_to_next(now) + # No natural return to frame 0 — start rewinding + if self._animation.mode in (AnimationMode.ONCE_FORWARD, AnimationMode.REPEAT_FORWARD): + self._rewinding = True + self._rewind_start = now + self._rewind_from = frame_index + + return self._animation.frames[frame_index] + + def _switch_to_next(self, now: float) -> list[tuple[int, int]]: + self._animation = self._next + self._next = None + self._rewinding = False + self._seen_nonzero = False + self._start_time = now + return self._animation.frames[0] + + +def _get_frame_index(animation: Animation, elapsed: float, gap_first: bool = False) -> int: + """Get the current frame index given elapsed time and animation mode.""" + num_frames = len(animation.frames) + if num_frames == 1: + return 0 + + fd = animation.frame_duration + has_backward = animation.mode in (AnimationMode.ONCE_FORWARD_BACKWARD, AnimationMode.REPEAT_FORWARD_BACKWARD) + repeats = animation.mode in (AnimationMode.REPEAT_FORWARD, AnimationMode.REPEAT_FORWARD_BACKWARD) + + forward_duration = num_frames * fd + backward_frames = max(num_frames - 2, 0) if has_backward else 0 + hold = animation.hold_end if has_backward else 0.0 + cycle_duration = forward_duration + hold + backward_frames * fd + + if not repeats: + t = min(elapsed, cycle_duration) + else: + t = (elapsed + cycle_duration if gap_first else elapsed) % animation.repeat_interval + + # Forward phase + if t < forward_duration: + return min(int(t / fd), num_frames - 1) + t -= forward_duration + + # Hold at last frame + if t < hold: + return num_frames - 1 + t -= hold + + # Backward phase + if backward_frames and t < backward_frames * fd: + return num_frames - 2 - min(int(t / fd), backward_frames - 1) + + return 0 if has_backward else num_frames - 1 diff --git a/selfdrive/ui/body/layouts/__init__.py b/selfdrive/ui/body/layouts/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/selfdrive/ui/body/layouts/onroad.py b/selfdrive/ui/body/layouts/onroad.py new file mode 100644 index 00000000000..d7e9f419cca --- /dev/null +++ b/selfdrive/ui/body/layouts/onroad.py @@ -0,0 +1,93 @@ +import time +import pyray as rl + +from openpilot.system.ui.lib.application import gui_app, FontWeight +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.label import UnifiedLabel +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.body.animations import FaceAnimator, ASLEEP, INQUISITIVE, NORMAL, SLEEPY + +GRID_COLS = 16 +GRID_ROWS = 8 +DOT_RADIUS = 50 if gui_app.big_ui() else 10 + +IDLE_TIMEOUT = 30.0 # seconds of no joystick input before playing INQUISITIVE +IDLE_STEER_THRESH = 0.5 # degrees — below this counts as no input +IDLE_SPEED_THRESH = 0.01 # m/s — below this counts as no input + + +# This class is used both in BIG (tizi) and small (mici) UIs +class BodyLayout(Widget): + def __init__(self): + super().__init__() + self._animator = FaceAnimator(ASLEEP) + self._turning_left = False + self._turning_right = False + self._last_input_time = time.monotonic() + self._was_active = False + self._offroad_label = UnifiedLabel("turn on ignition to use", 95 if gui_app.big_ui() else 45, FontWeight.DISPLAY, + alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, + alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_MIDDLE) + + def draw_dot_grid(self, rect: rl.Rectangle, dots: list[tuple[int, int]], color: rl.Color): + spacing = min(rect.height / GRID_ROWS, rect.width / GRID_COLS) + + grid_w = (GRID_COLS - 1) * spacing + grid_h = (GRID_ROWS - 1) * spacing + + offset_x = rect.x + (rect.width - grid_w) / 2 + offset_y = rect.y + (rect.height - grid_h) / 2 + + for row, col in dots: + x = int(offset_x + col * spacing) + y = int(offset_y + row * spacing) + rl.draw_circle(x, y, DOT_RADIUS, color) + + def _update_state(self): + super()._update_state() + + sm = ui_state.sm + + if ui_state.is_onroad(): + if not self._was_active: + self._last_input_time = time.monotonic() + self._was_active = True + + cs = sm['carState'] + has_input = abs(cs.steeringAngleDeg) > IDLE_STEER_THRESH or abs(cs.vEgo) > IDLE_SPEED_THRESH + if has_input: + self._last_input_time = time.monotonic() + + if time.monotonic() - self._last_input_time > IDLE_TIMEOUT: + self._animator.set_animation(INQUISITIVE) + else: + self._animator.set_animation(NORMAL) + else: + self._was_active = False + self._animator.set_animation(ASLEEP) + + steer = sm['testJoystick'].axes[1] if len(sm['testJoystick'].axes) > 1 else 0 + self._turning_left = steer <= -0.05 + self._turning_right = steer >= 0.05 + + # play animation on screen tap + def _handle_mouse_release(self, mouse_pos): + super()._handle_mouse_release(mouse_pos) + if not self._was_active: + self._animator.set_animation(SLEEPY) + + def _render(self, rect: rl.Rectangle): + dots = self._animator.get_dots() + animation = self._animator._animation + if self._turning_left and animation.left_turn_remove: + remove_set = set(animation.left_turn_remove) + dots = [d for d in dots if d not in remove_set] + elif self._turning_right and animation.right_turn_remove: + remove_set = set(animation.right_turn_remove) + dots = [d for d in dots if d not in remove_set] + self.draw_dot_grid(rect, dots, rl.WHITE) + + if ui_state.is_offroad(): + rl.draw_rectangle(int(self.rect.x), int(self.rect.y), int(self.rect.width), int(self.rect.height), rl.Color(0, 0, 0, 175)) + upper_half = rl.Rectangle(rect.x, rect.y, rect.width, rect.height / 2) + self._offroad_label.render(upper_half) diff --git a/selfdrive/ui/layouts/main.py b/selfdrive/ui/layouts/main.py index 15d44e24da5..672f5463d97 100644 --- a/selfdrive/ui/layouts/main.py +++ b/selfdrive/ui/layouts/main.py @@ -2,13 +2,14 @@ from enum import IntEnum import cereal.messaging as messaging from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets import Widget 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 from openpilot.selfdrive.ui.onroad.augmented_road_view import AugmentedRoadView from openpilot.selfdrive.ui.ui_state import device, ui_state -from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.layouts.onboarding import OnboardingWindow +from openpilot.selfdrive.ui.body.layouts.onroad import BodyLayout class MainState(IntEnum): @@ -28,7 +29,9 @@ def __init__(self): self._prev_onroad = False # Initialize layouts - self._layouts = {MainState.HOME: HomeLayout(), MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()} + self._home_layout = HomeLayout() + self._home_body_layout = BodyLayout() + self._layouts = {MainState.HOME: self._home_layout, MainState.SETTINGS: SettingsLayout(), MainState.ONROAD: AugmentedRoadView()} self._sidebar_rect = rl.Rectangle(0, 0, 0, 0) self._content_rect = rl.Rectangle(0, 0, 0, 0) @@ -54,14 +57,18 @@ def _setup_callbacks(self): self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE)) self._layouts[MainState.HOME].set_settings_callback(lambda: self.open_settings(PanelType.TOGGLES)) self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state) - self._layouts[MainState.ONROAD].set_click_callback(self._on_onroad_clicked) + + for layout in (self._layouts[MainState.ONROAD], self._home_body_layout): + layout.set_click_callback(self._on_onroad_clicked) + device.add_interactive_timeout_callback(self._set_mode_for_state) + ui_state.add_on_body_changed_callbacks(self._on_body_changed) def _update_layout_rects(self): self._sidebar_rect = rl.Rectangle(self._rect.x, self._rect.y, SIDEBAR_WIDTH, self._rect.height) x_offset = SIDEBAR_WIDTH if self._sidebar.is_visible else 0 - self._content_rect = rl.Rectangle(self._rect.y + x_offset, self._rect.y, self._rect.width - x_offset, self._rect.height) + self._content_rect = rl.Rectangle(self._rect.x + x_offset, self._rect.y, self._rect.width - x_offset, self._rect.height) def _handle_onroad_transition(self): if ui_state.started != self._prev_onroad: @@ -70,6 +77,12 @@ def _handle_onroad_transition(self): self._set_mode_for_state() def _set_mode_for_state(self): + # Don't go onroad if body, home is onroad + if ui_state.is_body: + self._set_current_layout(MainState.HOME) + self._sidebar.set_visible(not ui_state.ignition) + return + if ui_state.started: # Don't hide sidebar from interactive timeout if self._current_mode != MainState.ONROAD: @@ -101,6 +114,10 @@ def _on_bookmark_clicked(self): def _on_onroad_clicked(self): self._sidebar.set_visible(not self._sidebar.is_visible) + def _on_body_changed(self): + self._layouts[MainState.HOME] = self._home_body_layout if ui_state.is_body else self._home_layout + self._set_mode_for_state() + def _render_main_content(self): # Render sidebar if self._sidebar.is_visible: diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 4bdff388b0d..cbe7ec29f07 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -130,6 +130,7 @@ def __init__(self): self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48)) self._mic_icon = IconWidget("icons_mici/microphone.png", (32, 46)) + self._body_icon = IconWidget("icons_mici/body.png", (54, 37)) self._alerts_pill = AlertsPill() @@ -137,6 +138,7 @@ def __init__(self): IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9), NetworkIcon(), self._experimental_icon, + self._body_icon, self._mic_icon, ], spacing=18) @@ -247,6 +249,7 @@ def _render(self, _): # ***** Center-aligned bottom section icons ***** self._experimental_icon.set_visible(self._experimental_mode) self._mic_icon.set_visible(ui_state.recording_audio) + self._body_icon.set_visible(ui_state.is_body) footer_rect = rl.Rectangle(self.rect.x + HOME_PADDING, self.rect.y + self.rect.height - 48, self.rect.width - HOME_PADDING, 48) self._status_bar_layout.render(footer_rect) diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 0d0f6308f23..badfbe315e0 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -6,6 +6,7 @@ from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView 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 from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.lib.application import gui_app @@ -29,22 +30,25 @@ def __init__(self): self._home_layout = MiciHomeLayout() self._alerts_layout = MiciOffroadAlerts() self._settings_layout = SettingsLayout() - self._onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked) + self._car_onroad_layout = AugmentedRoadView(bookmark_callback=self._on_bookmark_clicked) + self._body_onroad_layout = BodyLayout() # Initialize widget rects - for widget in (self._home_layout, self._settings_layout, self._alerts_layout, self._onroad_layout): + for widget in (self._home_layout, self._alerts_layout, self._settings_layout, + self._car_onroad_layout, self._body_onroad_layout): # TODO: set parent rect and use it if never passed rect from render (like in Scroller) widget.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height)) self._scroller.add_widgets([ self._alerts_layout, self._home_layout, - self._onroad_layout, + self._car_onroad_layout, + self._body_onroad_layout, ]) self._scroller.set_reset_scroll_at_show(False) # Disable scrolling when onroad is interacting with bookmark - self._scroller.set_scrolling_enabled(lambda: not self._onroad_layout.is_swiping_left()) + self._scroller.set_scrolling_enabled(lambda: not self._car_onroad_layout.is_swiping_left()) # Set callbacks self._setup_callbacks() @@ -57,14 +61,22 @@ def __init__(self): if not self._onboarding_window.completed: gui_app.push_widget(self._onboarding_window) + @property + def _onroad_layout(self) -> Widget: + # For scroll_to + return self._body_onroad_layout if ui_state.is_body else self._car_onroad_layout + def _setup_callbacks(self): self._home_layout.set_callbacks( on_settings=lambda: gui_app.push_widget(self._settings_layout), on_alerts=lambda: self._scroll_to(self._alerts_layout), alert_count_callback=self._alerts_layout.active_alerts, ) - self._onroad_layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) + for layout in (self._car_onroad_layout, self._body_onroad_layout): + layout.set_click_callback(lambda: self._scroll_to(self._home_layout)) + device.add_interactive_timeout_callback(self._on_interactive_timeout) + ui_state.add_on_body_changed_callbacks(self._on_body_changed) def _scroll_to(self, layout: Widget): layout_x = int(layout.rect.x) @@ -130,3 +142,7 @@ def _on_bookmark_clicked(self): user_bookmark = messaging.new_message('bookmarkButton') user_bookmark.valid = True self._pm.send('bookmarkButton', user_bookmark) + + def _on_body_changed(self): + self._car_onroad_layout.set_visible(not ui_state.is_body) + self._body_onroad_layout.set_visible(ui_state.is_body) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 30a65650955..52426f510b5 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -13,6 +13,7 @@ from openpilot.system.hardware import HARDWARE, PC BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50 +PARAM_UPDATE_TIME = 5.0 class UIStatus(Enum): @@ -54,6 +55,7 @@ def _initialize(self): "carOutput", "carControl", "liveParameters", + "testJoystick", "rawAudioData", ] ) @@ -77,15 +79,15 @@ def _initialize(self): self.panda_type: log.PandaState.PandaType = log.PandaState.PandaType.unknown self.personality: log.LongitudinalPersonality = log.LongitudinalPersonality.standard self.has_longitudinal_control: bool = False + self.is_body: bool | None = None self.CP: car.CarParams | None = None self.light_sensor: float = -1.0 - self._param_update_time: float = 0.0 + self._param_update_time: float = -PARAM_UPDATE_TIME # Callbacks self._offroad_transition_callbacks: list[Callable[[], None]] = [] self._engaged_transition_callbacks: list[Callable[[], None]] = [] - - self.update_params() + self._on_body_changed_callbacks: list[Callable[[], None]] = [] def add_offroad_transition_callback(self, callback: Callable[[], None]): self._offroad_transition_callbacks.append(callback) @@ -93,6 +95,9 @@ def add_offroad_transition_callback(self, callback: Callable[[], None]): def add_engaged_transition_callback(self, callback: Callable[[], None]): self._engaged_transition_callbacks.append(callback) + def add_on_body_changed_callbacks(self, callback: Callable[[], None]): + self._on_body_changed_callbacks.append(callback) + @property def engaged(self) -> bool: return self.started and self.sm["selfdriveState"].enabled @@ -108,7 +113,7 @@ def update(self) -> None: self.sm.update(0) self._update_state() self._update_status() - if time.monotonic() - self._param_update_time > 5.0: + if time.monotonic() - self._param_update_time >= PARAM_UPDATE_TIME: self.update_params() device.update() @@ -180,6 +185,12 @@ def update_params(self) -> None: self.has_longitudinal_control = self.params.get_bool("AlphaLongitudinalEnabled") else: self.has_longitudinal_control = self.CP.openpilotLongitudinalControl + + if self.is_body != self.CP.notCar: + self.is_body = self.CP.notCar + for callback in self._on_body_changed_callbacks: + callback() + self._param_update_time = time.monotonic() From b6432e705df0cf13719c895611179d7ff43c4385 Mon Sep 17 00:00:00 2001 From: probablyanasian <43358667+probablyanasian@users.noreply.github.com> Date: Sat, 18 Apr 2026 20:27:58 -0700 Subject: [PATCH 658/685] Fix LSM6DS3 sensors test (#37855) * fix temperature test + add std dev test for temp * loosen gyro limit, no axis on temp mean * whitespace fix * add std_max to all sensors/tests --- system/sensord/tests/test_sensord.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py index 20214e12ffa..9a66678ecf1 100644 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -30,16 +30,16 @@ }.get(HARDWARE.get_device_type(), []) Sensor = log.SensorEventData.SensorSource -SensorConfig = namedtuple('SensorConfig', ['type', 'sanity_min', 'sanity_max']) +SensorConfig = namedtuple('SensorConfig', ['type', 'sanity_min', 'sanity_max', 'std_max']) ALL_SENSORS = { Sensor.lsm6ds3trc: { - SensorConfig("acceleration", 5, 15), - SensorConfig("gyroUncalibrated", 0, .2), - SensorConfig("temperature", 10, 40), # set for max range of our office + SensorConfig("acceleration", 5, 15, 5), + SensorConfig("gyroUncalibrated", 0, .15, 0.5), + SensorConfig("temperature", 10, 40, 0.5), # set for max range of our office }, Sensor.mmc5603nj: { - SensorConfig("magneticUncalibrated", 0, 300), + SensorConfig("magneticUncalibrated", 0, 300, 10), } } ALL_SENSORS[Sensor.lsm6ds3] = ALL_SENSORS[Sensor.lsm6ds3trc] @@ -198,9 +198,16 @@ def test_sensor_values(self): continue key = (sensor, s.type) - mean_norm = np.mean(np.linalg.norm(sensor_values[key], axis=1)) - err_msg = f"Sensor '{sensor} {s.type}' failed sanity checks {mean_norm} is not between {s.sanity_min} and {s.sanity_max}" - assert s.sanity_min <= mean_norm <= s.sanity_max, err_msg + if s.type == 'temperature': + measurement_stat = np.mean(sensor_values[key]) + else: + measurement_stat = np.mean(np.linalg.norm(sensor_values[key], axis=1)) + err_msg = f"Sensor '{sensor} {s.type}' failed sanity checks {measurement_stat} is not between {s.sanity_min} and {s.sanity_max}" + assert s.sanity_min <= measurement_stat <= s.sanity_max, err_msg + + std_dev = np.std(sensor_values[key], axis=0) + err_msg = f"Sensor '{sensor} {s.type}' failed std dev test {std_dev} is not under {s.std_max}" + assert np.all(std_dev <= s.std_max), err_msg def test_sensor_verify_no_interrupts_after_stop(self): managed_processes["sensord"].start() @@ -222,4 +229,3 @@ def test_sensor_verify_no_interrupts_after_stop(self): time.sleep(1) state_two = get_irq_count(self.sensord_irq) assert state_one == state_two, "Interrupts received after sensord stop!" - From 6996e87f8d896e68fb22b97a8bcf4daee08e95f2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 20 Apr 2026 19:20:48 -0700 Subject: [PATCH 659/685] dm: helpers.py -> policy.py (#37864) --- selfdrive/monitoring/dmonitoringd.py | 2 +- selfdrive/monitoring/{helpers.py => policy.py} | 0 selfdrive/monitoring/test_monitoring.py | 2 +- selfdrive/ui/mici/onroad/driver_state.py | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename selfdrive/monitoring/{helpers.py => policy.py} (100%) diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 2a48f744949..f3a8ef34915 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -2,7 +2,7 @@ import cereal.messaging as messaging from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process -from openpilot.selfdrive.monitoring.helpers import DriverMonitoring +from openpilot.selfdrive.monitoring.policy import DriverMonitoring def dmonitoringd_thread(): diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/policy.py similarity index 100% rename from selfdrive/monitoring/helpers.py rename to selfdrive/monitoring/policy.py diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index ef21b3688b1..8b5e9bc6d46 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -2,7 +2,7 @@ from cereal import log from openpilot.common.realtime import DT_DMON -from openpilot.selfdrive.monitoring.helpers import DriverMonitoring, DRIVER_MONITOR_SETTINGS +from openpilot.selfdrive.monitoring.policy import DriverMonitoring, DRIVER_MONITOR_SETTINGS EventName = log.OnroadEvent.EventName dm_settings = DRIVER_MONITOR_SETTINGS() diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index c51e7eca6ee..f71854d099b 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -6,7 +6,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.monitoring.helpers import face_orientation_from_net +from openpilot.selfdrive.monitoring.policy import face_orientation_from_net AlertSize = log.SelfdriveState.AlertSize From 04b23ff8493c6672df33f54961ea1b4b1d133922 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Mon, 20 Apr 2026 21:13:07 -0700 Subject: [PATCH 660/685] model replay: relax driverState timing (#37868) --- selfdrive/test/process_replay/model_replay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index eb7cdbe34ac..880dc2138c2 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -35,7 +35,7 @@ EXEC_TIMINGS = [ # model, instant max, average max ("modelV2", 0.05, 0.028), - ("driverStateV2", 0.05, 0.016), + ("driverStateV2", 0.05, 0.018), ] def get_log_fn(test_route, ref="master"): From 341786acb5920fbdcf416c15f7cbfa75c7796052 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Apr 2026 09:53:17 -0700 Subject: [PATCH 661/685] jp: fix hidden plots unhiding on interaction (#37870) --- tools/jotpluggler/plot.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/jotpluggler/plot.cc b/tools/jotpluggler/plot.cc index 5d9d644808c..b1324713a74 100644 --- a/tools/jotpluggler/plot.cc +++ b/tools/jotpluggler/plot.cc @@ -848,7 +848,7 @@ void draw_plot(const AppSession &session, Pane *pane, UiState *state) { } else { for (size_t i = 0; i < prepared_curves.size(); ++i) { const PreparedCurve &curve = prepared_curves[i]; - std::string series_id = curve_legend_label(curve, has_cursor_time, max_legend_label_width) + "##curve" + std::to_string(i); + std::string series_id = curve_legend_label(curve, has_cursor_time, max_legend_label_width) + "###curve" + std::to_string(curve.pane_curve_index); ImPlotSpec spec; spec.LineColor = color_rgb(curve.color); spec.LineWeight = curve.line_weight; From 4286a64083d9bc0fbb79e45a9d2141048ba357ff Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Apr 2026 10:11:10 -0700 Subject: [PATCH 662/685] jp: reduce y padding --- tools/jotpluggler/app.cc | 2 +- tools/jotpluggler/internal.h | 1 + tools/jotpluggler/plot.cc | 4 +--- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/jotpluggler/app.cc b/tools/jotpluggler/app.cc index 4b56299ead3..f25656c7bf3 100644 --- a/tools/jotpluggler/app.cc +++ b/tools/jotpluggler/app.cc @@ -324,7 +324,7 @@ void configure_style() { plot_style.LegendInnerPadding = ImVec2(6.0f, 3.0f); plot_style.LegendSpacing = ImVec2(7.0f, 2.0f); plot_style.PlotPadding = ImVec2(4.0f, 8.0f); - plot_style.FitPadding = ImVec2(0.02f, 0.4f); + plot_style.FitPadding = ImVec2(0.02f, static_cast(PLOT_Y_PADDING_FRACTION)); ImPlot::MapInputDefault(); ImPlotInputMap &input_map = ImPlot::GetInputMap(); diff --git a/tools/jotpluggler/internal.h b/tools/jotpluggler/internal.h index 22a5c1dd959..b70aec1a535 100644 --- a/tools/jotpluggler/internal.h +++ b/tools/jotpluggler/internal.h @@ -58,6 +58,7 @@ inline constexpr float SIDEBAR_MAX_WIDTH = 520.0f; inline constexpr float TIMELINE_BAR_HEIGHT = 14.0f; inline constexpr float STATUS_BAR_HEIGHT = 52.0f; inline constexpr double MIN_HORIZONTAL_ZOOM_SECONDS = 2.0; +inline constexpr double PLOT_Y_PADDING_FRACTION = 0.05; struct UiMetrics { float width = 0.0f; diff --git a/tools/jotpluggler/plot.cc b/tools/jotpluggler/plot.cc index b1324713a74..270dc905551 100644 --- a/tools/jotpluggler/plot.cc +++ b/tools/jotpluggler/plot.cc @@ -7,8 +7,6 @@ #include #include -constexpr double PLOT_Y_PAD_FRACTION = 0.4; - struct PlotBounds { double x_min = 0.0; double x_max = 1.0; @@ -484,7 +482,7 @@ PlotBounds compute_plot_bounds(const Pane &pane, min_value = std::min(min_value, 0.0); max_value = std::max(max_value, 1.0); } - ensure_non_degenerate_range(&min_value, &max_value, PLOT_Y_PAD_FRACTION, 0.1); + ensure_non_degenerate_range(&min_value, &max_value, PLOT_Y_PADDING_FRACTION, 0.1); if (pane.range.has_y_limit_min) { min_value = pane.range.y_limit_min; } From e202bbe4aa164b22a5ec60de15cf444157119b67 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Apr 2026 11:04:46 -0700 Subject: [PATCH 663/685] monitoring: remove redundant README --- selfdrive/monitoring/README.md | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 selfdrive/monitoring/README.md diff --git a/selfdrive/monitoring/README.md b/selfdrive/monitoring/README.md deleted file mode 100644 index 2a29ea06b5a..00000000000 --- a/selfdrive/monitoring/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# driver monitoring (DM) - -Uploading driver-facing camera footage is opt-in, but it is encouraged to opt-in to improve the DM model. You can always change your preference using the "Record and Upload Driver Camera" toggle. - -## Troubleshooting - -Before creating a bug report, go through these troubleshooting steps. - -* Ensure the driver-facing camera has a good view of the driver in normal driving positions. - * This can be checked in Settings -> Device -> Preview Driver Camera (when car is off). -* If the camera can't see the driver, the device should be re-mounted. - -## Bug report - -In order for us to look into DM bug reports, we'll need the driver-facing camera footage. If you don't normally have this enabled, simply enable the toggle for a single drive. Also ensure the "Upload Raw Logs" toggle is enabled before going for a drive. From 4988a62b310b536fb5336c35151eb2df56e37d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Harald=20Sch=C3=A4fer?= Date: Tue, 21 Apr 2026 11:20:33 -0700 Subject: [PATCH 664/685] Revert "POP model (#37727)" (#37871) This reverts commit 12f1be19ccff662ebaba6b0337e9cf43d34d54db. --- selfdrive/modeld/models/driving_policy.onnx | 4 ++-- selfdrive/modeld/models/driving_vision.onnx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/modeld/models/driving_policy.onnx b/selfdrive/modeld/models/driving_policy.onnx index 7c71bc94714..611ae9fe85f 100644 --- a/selfdrive/modeld/models/driving_policy.onnx +++ b/selfdrive/modeld/models/driving_policy.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:853c6634746ff439a848349d00e4d5581cd941f13f7c1862c31b72a31cc24858 -size 14061595 +oid sha256:78477124cbf3ffe30fa951ebada8410b43c4242c6054584d656f1d329b067e15 +size 14060847 diff --git a/selfdrive/modeld/models/driving_vision.onnx b/selfdrive/modeld/models/driving_vision.onnx index afd617667c6..6c9fc4c84d3 100644 --- a/selfdrive/modeld/models/driving_vision.onnx +++ b/selfdrive/modeld/models/driving_vision.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:940e9006a25f27f0b6e85da798e6a8fd1f6dd492dd7d0b9ff1a9436460f46129 -size 46887794 +oid sha256:ee29ee5bce84d1ce23e9ff381280de9b4e4d96d2934cd751740354884e112c66 +size 46877473 From 859bd215bf9c2bda408124be960d226d71eff68a Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Tue, 21 Apr 2026 13:50:20 -0700 Subject: [PATCH 665/685] modeld: group npy -> qcom copies to avoid graph breaks (#37866) * modeld: group npy -> qcom copies to avoid graph breaks * batch realize * dm as well --- selfdrive/modeld/compile_modeld.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/selfdrive/modeld/compile_modeld.py b/selfdrive/modeld/compile_modeld.py index 469110a857c..c11f899b7f1 100755 --- a/selfdrive/modeld/compile_modeld.py +++ b/selfdrive/modeld/compile_modeld.py @@ -148,7 +148,7 @@ def make_warp_dm(cam_w, cam_h, dm_w, dm_h): stride_pad = stride - cam_w def warp_dm(input_frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT) + M_inv = M_inv.to(Device.DEFAULT).realize() result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad).reshape(-1, dm_h * dm_w) return result return warp_dm @@ -162,8 +162,14 @@ def make_run_policy(vision_runner, policy_runner, cam_w, cam_h, sample_desire_fn = partial(sample_desire, frame_skip=frame_skip) def run_policy(img_q, big_img_q, feat_q, desire_q, desire, traffic_convention, tfm, big_tfm, frame, big_frame): - img = shift_and_sample(img_q, frame_prepare(frame, tfm.to(Device.DEFAULT)).unsqueeze(0), sample_skip_fn) - big_img = shift_and_sample(big_img_q, frame_prepare(big_frame, big_tfm.to(Device.DEFAULT)).unsqueeze(0), sample_skip_fn) + tfm = tfm.to(Device.DEFAULT) + big_tfm = big_tfm.to(Device.DEFAULT) + desire = desire.to(Device.DEFAULT) + traffic_convention = traffic_convention.to(Device.DEFAULT) + Tensor.realize(tfm, big_tfm, desire, traffic_convention) + + img = shift_and_sample(img_q, frame_prepare(frame, tfm).unsqueeze(0), sample_skip_fn) + big_img = shift_and_sample(big_img_q, frame_prepare(big_frame, big_tfm).unsqueeze(0), sample_skip_fn) if prepare_only: return img, big_img @@ -172,9 +178,9 @@ def run_policy(img_q, big_img_q, feat_q, desire_q, desire, traffic_convention, t new_feat = vision_out[:, vision_features_slice].reshape(1, -1).unsqueeze(0) feat_buf = shift_and_sample(feat_q, new_feat, sample_skip_fn) - desire_buf = shift_and_sample(desire_q, desire.to(Device.DEFAULT).reshape(1, 1, -1), sample_desire_fn) + desire_buf = shift_and_sample(desire_q, desire.reshape(1, 1, -1), sample_desire_fn) - inputs = {'features_buffer': feat_buf, 'desire_pulse': desire_buf, 'traffic_convention': traffic_convention.to(Device.DEFAULT)} + inputs = {'features_buffer': feat_buf, 'desire_pulse': desire_buf, 'traffic_convention': traffic_convention} policy_out = next(iter(policy_runner(inputs).values())).cast('float32') return vision_out, policy_out @@ -210,10 +216,11 @@ def compile_modeld(cam_w, cam_h, prepare_only, pkl_path): def random_inputs_run_fn(fn, seed, test_val=None, test_buffers=None, expect_match=True): input_queues, npy = make_input_queues(vision_input_shapes, policy_input_shapes, frame_skip) np.random.seed(seed) + Tensor.manual_seed(seed) for i in range(N_RUNS): - frame = Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize() - big_frame = Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize() + frame = Tensor.randint(yuv_size, low=0, high=256, dtype='uint8').realize() + big_frame = Tensor.randint(yuv_size, low=0, high=256, dtype='uint8').realize() for v in npy.values(): v[:] = np.random.randn(*v.shape).astype(v.dtype) Device.default.synchronize() @@ -263,7 +270,7 @@ def compile_dm_warp(cam_w, cam_h, pkl_path): warp_dm_jit = TinyJit(warp_dm, prune=True) for i in range(10): - inputs = [Tensor(np.random.randint(0, 256, yuv_size, dtype=np.uint8)).realize(), + inputs = [Tensor.randint(yuv_size, low=0, high=256, dtype='uint8').realize(), Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] Device.default.synchronize() st = time.perf_counter() From b29d0a17af5cccf760bd70f624bdb4664e8c1303 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Tue, 21 Apr 2026 15:43:13 -0700 Subject: [PATCH 666/685] DM: readability, part 1 (#37872) * spellings * unused * no roll * lil more * lil more * one more * policy enum * better trans * set_timer -> set_policy * set_timer -> set_policy * no yaonet * del redundant code --------- Co-authored-by: ZwX1616 --- selfdrive/monitoring/dmonitoringd.py | 4 +- selfdrive/monitoring/policy.py | 141 ++++++++++------------- selfdrive/monitoring/test_monitoring.py | 6 +- selfdrive/ui/mici/onroad/driver_state.py | 29 ++--- 4 files changed, 76 insertions(+), 104 deletions(-) diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index f3a8ef34915..415853513ed 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -39,8 +39,8 @@ def dmonitoringd_thread(): # save rhd virtual toggle every 5 mins if (sm['driverStateV2'].frameId % 6000 == 0 and not demo_mode and - DM.wheelpos.prob_offseter.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and - DM.wheel_on_right == (DM.wheelpos.prob_offseter.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)): + DM.wheelpos_offsetter.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and + DM.wheel_on_right == (DM.wheelpos_offsetter.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)): params.put_bool_nonblocking("IsRhdDetected", DM.wheel_on_right) def main(): diff --git a/selfdrive/monitoring/policy.py b/selfdrive/monitoring/policy.py index 37caa757c52..acfbca258f3 100644 --- a/selfdrive/monitoring/policy.py +++ b/selfdrive/monitoring/policy.py @@ -13,7 +13,7 @@ AlertLevel = log.DriverMonitoringState.AlertLevel MonitoringPolicy = log.DriverMonitoringState.MonitoringPolicy -def to_perc(v): +def to_percent(v): return int(min(max(v * 100., 0.), 100.)) # ****************************************************************************************** @@ -85,47 +85,33 @@ def __init__(self, settings): yaw_filter_raw_priors = (settings._YAW_NATURAL_OFFSET, settings._YAW_NATURAL_VAR, 2) self.yaw = 0. self.pitch = 0. - self.roll = 0. - self.yaw_std = 0. - self.pitch_std = 0. - self.roll_std = 0. - self.pitch_offseter = RunningStatFilter(raw_priors=pitch_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT) - self.yaw_offseter = RunningStatFilter(raw_priors=yaw_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT) + self.pitch_offsetter = RunningStatFilter(raw_priors=pitch_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT) + self.yaw_offsetter = RunningStatFilter(raw_priors=yaw_filter_raw_priors, max_trackable=settings._POSE_OFFSET_MAX_COUNT) self.calibrated = False self.low_std = True self.cfactor_pitch = 1. self.cfactor_yaw = 1. self.steer_yaw_offset = 0. -class DriverProb: - def __init__(self, raw_priors, max_trackable): - self.prob = 0. - self.prob_offseter = RunningStatFilter(raw_priors=raw_priors, max_trackable=max_trackable) - self.prob_calibrated = False - - # model output refers to center of undistorted+leveled image -EFL = 598.0 # focal length in K -cam = DEVICE_CAMERAS[("tici", "ar0231")] # corrected image has same size as raw -W, H = (cam.dcam.width, cam.dcam.height) # corrected image has same size as raw - -def face_orientation_from_net(angles_desc, pos_desc, rpy_calib): - # the output of these angles are in driver camera frame - # so from driver's perspective, pitch is up and yaw is right +ref_undistorted_cam = DEVICE_CAMERAS[("tici", "ar0231")].dcam +dcam_undistorted_FL = 598.0 +dcam_undistorted_W, dcam_undistorted_H = (ref_undistorted_cam.width, ref_undistorted_cam.height) - pitch_net, yaw_net, roll_net = angles_desc +def face_orientation_from_model(orient_model, pos_model, rpy_calib): + pitch_model = orient_model[0] + yaw_model = orient_model[1] - face_pixel_position = ((pos_desc[0]+0.5)*W, (pos_desc[1]+0.5)*H) - yaw_focal_angle = atan2(face_pixel_position[0] - W//2, EFL) - pitch_focal_angle = atan2(face_pixel_position[1] - H//2, EFL) + face_pixel_position = ((pos_model[0]+0.5)*dcam_undistorted_W, (pos_model[1]+0.5)*dcam_undistorted_H) + yaw_focal_angle = atan2(face_pixel_position[0] - dcam_undistorted_W//2, dcam_undistorted_FL) + pitch_focal_angle = atan2(face_pixel_position[1] - dcam_undistorted_H//2, dcam_undistorted_FL) - pitch = pitch_net + pitch_focal_angle - yaw = -yaw_net + yaw_focal_angle + pitch = pitch_model + pitch_focal_angle + yaw = -yaw_model + yaw_focal_angle - # no calib for roll pitch -= rpy_calib[1] yaw -= rpy_calib[2] - return roll_net, pitch, yaw + return pitch, yaw class DriverMonitoring: @@ -135,7 +121,7 @@ def __init__(self, rhd_saved=False, settings=None, always_on=False): # init driver status wheelpos_filter_raw_priors = (self.settings._WHEELPOS_DATA_AVG, self.settings._WHEELPOS_DATA_VAR, 2) - self.wheelpos = DriverProb(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) + self.wheelpos_offsetter = RunningStatFilter(raw_priors=wheelpos_filter_raw_priors, max_trackable=self.settings._WHEELPOS_MAX_COUNT) self.pose = DriverPose(settings=self.settings) self.blink_prob = 0. self.phone_prob = 0. @@ -152,29 +138,28 @@ def __init__(self, rhd_saved=False, settings=None, always_on=False): self.terminal_alert_cnt = 0 self.terminal_time = 0 self.step_change = 0. - self.active_monitoring_mode = True + self.active_policy = MonitoringPolicy.vision self.driver_interacting = False self.is_model_uncertain = False self.hi_stds = 0 self.model_std_max = 0. self.threshold_alert_1 = 0. self.threshold_alert_2 = 0. - self.dcam_uncertain = False self.dcam_uncertain_cnt = 0 self.dcam_reset_cnt = 0 self.too_distracted = Params().get_bool("DriverTooDistracted") self._reset_awareness() - self._set_timers(active_monitoring=True) + self._set_policy(MonitoringPolicy.vision) def _reset_awareness(self): self.awareness = 1. self.last_vision_awareness = 1. self.last_wheeltouch_awareness = 1. - def _set_timers(self, active_monitoring): - if self.active_monitoring_mode and self.awareness <= self.threshold_alert_2: - if active_monitoring: + def _set_policy(self, target_policy): + if self.active_policy == MonitoringPolicy.vision and self.awareness <= self.threshold_alert_2: + if target_policy == MonitoringPolicy.vision: self.step_change = DT_DMON / self.settings._VISION_POLICY_ALERT_3_TIMEOUT else: self.step_change = 0. @@ -182,25 +167,25 @@ def _set_timers(self, active_monitoring): elif self.awareness <= 0.: return - if active_monitoring: + if target_policy == MonitoringPolicy.vision: # when falling back from passive mode to active mode, reset awareness to avoid false alert - if not self.active_monitoring_mode: + if self.active_policy != MonitoringPolicy.vision: self.last_wheeltouch_awareness = self.awareness self.awareness = self.last_vision_awareness self.threshold_alert_1 = 1. - self.settings._VISION_POLICY_ALERT_1_TIMEOUT / self.settings._VISION_POLICY_ALERT_3_TIMEOUT self.threshold_alert_2 = 1. - self.settings._VISION_POLICY_ALERT_2_TIMEOUT / self.settings._VISION_POLICY_ALERT_3_TIMEOUT self.step_change = DT_DMON / self.settings._VISION_POLICY_ALERT_3_TIMEOUT - self.active_monitoring_mode = True + self.active_policy = MonitoringPolicy.vision else: - if self.active_monitoring_mode: + if self.active_policy == MonitoringPolicy.vision: self.last_vision_awareness = self.awareness self.awareness = self.last_wheeltouch_awareness self.threshold_alert_1 = 1. - self.settings._WHEELTOUCH_POLICY_ALERT_1_TIMEOUT / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT self.threshold_alert_2 = 1. - self.settings._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT self.step_change = DT_DMON / self.settings._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT - self.active_monitoring_mode = False + self.active_policy = MonitoringPolicy.wheeltouch def _set_pose_strictness(self, brake_disengage_prob, car_speed): bp = brake_disengage_prob @@ -220,9 +205,9 @@ def _get_distracted_types(self): pitch_error = self.pose.pitch - self.settings._PITCH_NATURAL_OFFSET yaw_error = self.pose.yaw - self.settings._YAW_NATURAL_OFFSET else: - pitch_error = self.pose.pitch - min(max(self.pose.pitch_offseter.filtered_stat.mean(), + pitch_error = self.pose.pitch - min(max(self.pose.pitch_offsetter.filtered_stat.mean(), self.settings._PITCH_MIN_OFFSET), self.settings._PITCH_MAX_OFFSET) - yaw_error = self.pose.yaw - min(max(self.pose.yaw_offseter.filtered_stat.mean(), + yaw_error = self.pose.yaw - min(max(self.pose.yaw_offsetter.filtered_stat.mean(), self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET) pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit @@ -243,12 +228,12 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil # calibrates only when there's movement and either face detected if car_speed > self.settings._WHEELPOS_CALIB_MIN_SPEED and (driver_state.leftDriverData.faceProb > self.settings._FACE_THRESHOLD or driver_state.rightDriverData.faceProb > self.settings._FACE_THRESHOLD): - self.wheelpos.prob_offseter.push_and_update(rhd_pred) + self.wheelpos_offsetter.push_and_update(rhd_pred) - self.wheelpos.prob_calibrated = self.wheelpos.prob_offseter.filtered_stat.n >= self.settings._WHEELPOS_FILTER_MIN_COUNT + wheelpos_calibrated = self.wheelpos_offsetter.filtered_stat.n >= self.settings._WHEELPOS_FILTER_MIN_COUNT - if self.wheelpos.prob_calibrated or demo_mode: - self.wheel_on_right = self.wheelpos.prob_offseter.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD + if wheelpos_calibrated or demo_mode: + self.wheel_on_right = self.wheelpos_offsetter.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD else: self.wheel_on_right = self.wheel_on_right_default # use default/saved if calibration is unfinished # make sure no switching when engaged @@ -260,16 +245,14 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil return self.face_detected = driver_data.faceProb > self.settings._FACE_THRESHOLD - self.pose.roll, self.pose.pitch, self.pose.yaw = face_orientation_from_net(driver_data.faceOrientation, driver_data.facePosition, cal_rpy) + self.pose.pitch, self.pose.yaw = face_orientation_from_model(driver_data.faceOrientation, driver_data.facePosition, cal_rpy) steer_d = max(abs(steering_angle_deg) - self.settings._POSE_YAW_MIN_STEER_DEG, 0.) self.pose.steer_yaw_offset = radians(steer_d) * -np.sign(steering_angle_deg) * self.settings._POSE_YAW_STEER_FACTOR if self.wheel_on_right: self.pose.yaw *= -1 self.pose.steer_yaw_offset *= -1 self.wheel_on_right_last = self.wheel_on_right - self.pose.pitch_std = driver_data.faceOrientationStd[0] - self.pose.yaw_std = driver_data.faceOrientationStd[1] - self.model_std_max = max(self.pose.pitch_std, self.pose.yaw_std) + self.model_std_max = max(driver_data.faceOrientationStd[0], driver_data.faceOrientationStd[1]) self.pose.low_std = self.model_std_max < self.settings._HI_STD_THRESHOLD self.blink_prob = driver_data.eyesClosedProb * (driver_data.eyesVisibleProb > self.settings._EYE_THRESHOLD) self.phone_prob = driver_data.phoneProb @@ -278,18 +261,17 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil self.driver_distracted = any(self.distracted_types.values()) and driver_data.faceProb > self.settings._FACE_THRESHOLD and self.pose.low_std self.driver_distraction_filter.update(self.driver_distracted) - # update offseter - # only update when driver is actively driving the car above a certain speed + # only update offsetter when driver is actively driving the car above a certain speed if self.face_detected and car_speed > self.settings._POSE_CALIB_MIN_SPEED and self.pose.low_std and (not op_engaged or not self.driver_distracted): - self.pose.pitch_offseter.push_and_update(self.pose.pitch) - self.pose.yaw_offseter.push_and_update(self.pose.yaw) + self.pose.pitch_offsetter.push_and_update(self.pose.pitch) + self.pose.yaw_offsetter.push_and_update(self.pose.yaw) - self.pose.calibrated = self.pose.pitch_offseter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT and \ - self.pose.yaw_offseter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT + self.pose.calibrated = self.pose.pitch_offsetter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT and \ + self.pose.yaw_offsetter.filtered_stat.n >= self.settings._POSE_OFFSET_MIN_COUNT if self.face_detected and not self.driver_distracted: - self.dcam_uncertain = self.model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD - if self.dcam_uncertain and not standstill: + dcam_uncertain = self.model_std_max > self.settings._DCAM_UNCERTAIN_ALERT_THRESHOLD + if dcam_uncertain and not standstill: self.dcam_uncertain_cnt += 1 self.dcam_reset_cnt = 0 else: @@ -298,13 +280,13 @@ def _update_states(self, driver_state, cal_rpy, car_speed, op_engaged, standstil self.dcam_uncertain_cnt = 0 self.is_model_uncertain = self.hi_stds >= self.settings._HI_STD_FALLBACK_TIME - self._set_timers(self.face_detected and not self.is_model_uncertain) + self._set_policy(MonitoringPolicy.vision if self.face_detected and not self.is_model_uncertain else MonitoringPolicy.wheeltouch) if self.face_detected and not self.pose.low_std and not self.driver_distracted: self.hi_stds += 1 elif self.face_detected and self.pose.low_std: self.hi_stds = 0 - def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car_speed): + def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear): self.alert_level = AlertLevel.none self.driver_interacting = driver_engaged @@ -313,7 +295,7 @@ def _update_events(self, driver_engaged, op_engaged, standstill, wrong_gear, car self.too_distracted = True always_on_valid = self.always_on and not wrong_gear - if (self.driver_interacting and self.awareness > 0 and not self.active_monitoring_mode) or \ + if (self.driver_interacting and self.awareness > 0 and self.active_policy == MonitoringPolicy.wheeltouch) or \ (not always_on_valid and not op_engaged) or \ (always_on_valid and not op_engaged and self.awareness <= 0): # always reset on disengage with normal mode; disengage resets only on red if always on @@ -366,18 +348,18 @@ def get_state_packet(self, valid=True): dm = dat.driverMonitoringState dm.lockout = self.too_distracted - dm.alertCountLockoutPercent = to_perc(self.terminal_alert_cnt / self.settings._MAX_TERMINAL_ALERTS) - dm.alertTimeLockoutPercent = to_perc(self.terminal_time / self.settings._MAX_TERMINAL_DURATION) + dm.alertCountLockoutPercent = to_percent(self.terminal_alert_cnt / self.settings._MAX_TERMINAL_ALERTS) + dm.alertTimeLockoutPercent = to_percent(self.terminal_time / self.settings._MAX_TERMINAL_DURATION) dm.alwaysOn = self.always_on dm.alwaysOnLockout = self.always_on and self.awareness <= self.threshold_alert_2 dm.alertLevel = self.alert_level - dm.activePolicy = MonitoringPolicy.vision if self.active_monitoring_mode else MonitoringPolicy.wheeltouch + dm.activePolicy = self.active_policy dm.isRHD = self.wheel_on_right - dm.rhdCalibration.calibratedPercent = to_perc(self.wheelpos.prob_offseter.filtered_stat.n / self.settings._WHEELPOS_FILTER_MIN_COUNT) - dm.rhdCalibration.offset = self.wheelpos.prob_offseter.filtered_stat.M + dm.rhdCalibration.calibratedPercent = to_percent(self.wheelpos_offsetter.filtered_stat.n / self.settings._WHEELPOS_FILTER_MIN_COUNT) + dm.rhdCalibration.offset = self.wheelpos_offsetter.filtered_stat.M - dm.visionPolicyState.awarenessPercent = to_perc(self.last_vision_awareness if not self.active_monitoring_mode else self.awareness) - dm.visionPolicyState.awarenessStep = self.step_change if self.active_monitoring_mode else 0. + dm.visionPolicyState.awarenessPercent = to_percent(self.last_vision_awareness if self.active_policy != MonitoringPolicy.vision else self.awareness) + dm.visionPolicyState.awarenessStep = self.step_change if self.active_policy == MonitoringPolicy.vision else 0. dm.visionPolicyState.isDistracted = self.driver_distracted dm.visionPolicyState.distractedTypes.pose = self.distracted_types['pose'] dm.visionPolicyState.distractedTypes.eye = self.distracted_types['eye'] @@ -386,16 +368,16 @@ def get_state_packet(self, valid=True): dm.visionPolicyState.pose.pitch = self.pose.pitch dm.visionPolicyState.pose.yaw = self.pose.yaw dm.visionPolicyState.pose.calibrated = self.pose.calibrated - dm.visionPolicyState.pose.pitchCalib.calibratedPercent = to_perc(self.pose.pitch_offseter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) - dm.visionPolicyState.pose.pitchCalib.offset = self.pose.pitch_offseter.filtered_stat.M - dm.visionPolicyState.pose.yawCalib.calibratedPercent = to_perc(self.pose.yaw_offseter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) - dm.visionPolicyState.pose.yawCalib.offset = self.pose.yaw_offseter.filtered_stat.M + dm.visionPolicyState.pose.pitchCalib.calibratedPercent = to_percent(self.pose.pitch_offsetter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) + dm.visionPolicyState.pose.pitchCalib.offset = self.pose.pitch_offsetter.filtered_stat.M + dm.visionPolicyState.pose.yawCalib.calibratedPercent = to_percent(self.pose.yaw_offsetter.filtered_stat.n / self.settings._POSE_OFFSET_MIN_COUNT) + dm.visionPolicyState.pose.yawCalib.offset = self.pose.yaw_offsetter.filtered_stat.M dm.visionPolicyState.pose.uncertainty = self.model_std_max - dm.visionPolicyState.wheeltouchFallbackPercent = to_perc(self.hi_stds / self.settings._HI_STD_FALLBACK_TIME) - dm.visionPolicyState.uncertainOffroadAlertPercent = to_perc(self.dcam_uncertain_cnt / self.settings._DCAM_UNCERTAIN_ALERT_COUNT) + dm.visionPolicyState.wheeltouchFallbackPercent = to_percent(self.hi_stds / self.settings._HI_STD_FALLBACK_TIME) + dm.visionPolicyState.uncertainOffroadAlertPercent = to_percent(self.dcam_uncertain_cnt / self.settings._DCAM_UNCERTAIN_ALERT_COUNT) - dm.wheeltouchPolicyState.awarenessPercent = to_perc(self.last_wheeltouch_awareness if self.active_monitoring_mode else self.awareness) - dm.wheeltouchPolicyState.awarenessStep = 0. if self.active_monitoring_mode else self.step_change + dm.wheeltouchPolicyState.awarenessPercent = to_percent(self.last_wheeltouch_awareness if self.active_policy == MonitoringPolicy.vision else self.awareness) + dm.wheeltouchPolicyState.awarenessStep = 0. if self.active_policy == MonitoringPolicy.vision else self.step_change dm.wheeltouchPolicyState.driverInteracting = self.driver_interacting return dat @@ -407,6 +389,7 @@ def run_step(self, sm, demo=False): standstill = False driver_engaged = False brake_disengage_prob = 1.0 + steering_angle_deg = 0.0 rpyCalib = [0., 0., 0.] else: car_speed = sm['carState'].vEgo @@ -415,6 +398,7 @@ def run_step(self, sm, demo=False): standstill = sm['carState'].standstill driver_engaged = sm['carState'].steeringPressed or sm['carState'].gasPressed brake_disengage_prob = sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s + steering_angle_deg = sm['carState'].steeringAngleDeg rpyCalib = sm['liveCalibration'].rpyCalib self._set_pose_strictness( @@ -430,7 +414,7 @@ def run_step(self, sm, demo=False): op_engaged=enabled, standstill=standstill, demo_mode=demo, - steering_angle_deg=sm['carState'].steeringAngleDeg, + steering_angle_deg=steering_angle_deg, ) # Update distraction events @@ -439,5 +423,4 @@ def run_step(self, sm, demo=False): op_engaged=enabled, standstill=standstill, wrong_gear=wrong_gear, - car_speed=car_speed ) diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 8b5e9bc6d46..948931cb290 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -55,7 +55,7 @@ def _run_seq(self, msgs, interaction, engaged, standstill): # cal_rpy and car_speed don't matter here # evaluate events at 10Hz for tests - DM._update_events(interaction[idx], engaged[idx], standstill[idx], 0, 0) + DM._update_events(interaction[idx], engaged[idx], standstill[idx], 0) alert_lvls.append(DM.alert_level) assert len(alert_lvls) == len(msgs), f"got {len(alert_lvls)} for {len(msgs)} driverState input msgs" return alert_lvls, DM @@ -63,8 +63,9 @@ def _run_seq(self, msgs, interaction, engaged, standstill): # engaged, driver is attentive all the time def test_fully_aware_driver(self): - alert_lvls, _ = self._run_seq(always_attentive, always_false, always_true, always_false) + alert_lvls, d_status = self._run_seq(always_attentive, always_false, always_true, always_false) assert all(a == 0 for a in alert_lvls) + assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.vision # engaged, driver is distracted and does nothing def test_fully_distracted_driver(self): @@ -90,6 +91,7 @@ def test_fully_invisible_driver(self): (s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT - s._WHEELTOUCH_POLICY_ALERT_2_TIMEOUT) / 2) / DT_DMON)] == 2 assert alert_lvls[int((s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT + \ (TEST_TIMESPAN - 10 - s._WHEELTOUCH_POLICY_ALERT_3_TIMEOUT) / 2) / DT_DMON)] == 3 + assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.wheeltouch # engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel # - should have short orange recovery time and no green afterwards; wheel touch only recovers when paying attention diff --git a/selfdrive/ui/mici/onroad/driver_state.py b/selfdrive/ui/mici/onroad/driver_state.py index f71854d099b..69ada3dae99 100644 --- a/selfdrive/ui/mici/onroad/driver_state.py +++ b/selfdrive/ui/mici/onroad/driver_state.py @@ -6,7 +6,7 @@ from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget from openpilot.selfdrive.ui.ui_state import ui_state -from openpilot.selfdrive.monitoring.policy import face_orientation_from_net + AlertSize = log.SelfdriveState.AlertSize @@ -35,6 +35,8 @@ def __init__(self, lines: bool = False, inset: bool = False): self._is_active = False self._is_rhd = False self._face_detected = False + self._face_pitch = 0. + self._face_yaw = 0. self._should_draw = False self._force_active = False self._looking_center = False @@ -156,6 +158,8 @@ def get_driver_data(self): self._is_active = dm_state.activePolicy == log.DriverMonitoringState.MonitoringPolicy.vision self._is_rhd = dm_state.isRHD self._face_detected = dm_state.visionPolicyState.faceDetected + self._face_pitch = dm_state.visionPolicyState.pose.pitch + math.radians(6) # calib or DM pose is not accurate, add a fake upward pitch to bias forward + self._face_yaw = -dm_state.visionPolicyState.pose.yaw # undo sign flip in face_orientation_from_model to match UI convention driverstate = sm["driverStateV2"] driver_data = driverstate.rightDriverData if self._is_rhd else driverstate.leftDriverData @@ -163,26 +167,9 @@ def get_driver_data(self): def _update_state(self): # Get monitoring state - driver_data = self.get_driver_data() - driver_orient = driver_data.faceOrientation - driver_position = driver_data.facePosition - - if len(driver_orient) != 3: - return - - # Calibrate orientation so looking straight ahead at road (instead of at device) is (0, 0, 0) - sm = ui_state.sm - if sm.valid['liveCalibration'] and len(sm['liveCalibration'].rpyCalib) == 3: - cal_rpy = sm['liveCalibration'].rpyCalib - else: - cal_rpy = [0.0, 0.0, 0.0] - - _, pitch, yaw = face_orientation_from_net(driver_orient, driver_position, cal_rpy) - pitch += math.radians(6) # calib or DM pose is not accurate, add a fake upward pitch to bias forward - yaw = -yaw # undo sign flip in face_orientation_from_net to match UI convention - - pitch = self._pitch_filter.update(pitch) - yaw = self._yaw_filter.update(yaw) + _ = self.get_driver_data() + pitch = self._pitch_filter.update(self._face_pitch) + yaw = self._yaw_filter.update(self._face_yaw) # hysteresis on looking center if abs(pitch) < LOOKING_CENTER_THRESHOLD_LOWER and abs(yaw) < LOOKING_CENTER_THRESHOLD_LOWER: From 571a547671d3878f807ea8967f84ab3e79d2302e Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 21 Apr 2026 16:19:20 -0700 Subject: [PATCH 667/685] Fix driver preview alert text and sound (#37875) * fix type and add text * short * fix sound too --------- Co-authored-by: Comma Device --- selfdrive/ui/mici/onroad/driver_camera_dialog.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/selfdrive/ui/mici/onroad/driver_camera_dialog.py b/selfdrive/ui/mici/onroad/driver_camera_dialog.py index 6fa3d891835..58b209bdf62 100644 --- a/selfdrive/ui/mici/onroad/driver_camera_dialog.py +++ b/selfdrive/ui/mici/onroad/driver_camera_dialog.py @@ -104,12 +104,12 @@ def _publish_alert_sound(self, dm_state): AudibleAlert = car.CarControl.HUDControl.AudibleAlert ALERT_SOUNDS = { - log.DriverMonitoringState.AlertLevel.two: AudibleAlert.promptDistracted, - log.DriverMonitoringState.AlertLevel.three: AudibleAlert.warningImmediate, + 'two': AudibleAlert.promptDistracted, + 'three': AudibleAlert.warningImmediate, } msg = messaging.new_message('selfdriveState') if dm_state is not None: - msg.selfdriveState.alertSound = ALERT_SOUNDS.get(dm_state.alertLevel, AudibleAlert.none) + msg.selfdriveState.alertSound = ALERT_SOUNDS.get(str(dm_state.alertLevel), AudibleAlert.none) self._pm.send('selfdriveState', msg) def _render_dm_alerts(self, rect: rl.Rectangle): @@ -133,7 +133,7 @@ def _render_dm_alerts(self, rect: rl.Rectangle): return # Show alert level - alert_level_str = dm_state.alertLevel + alert_level_str = f"{'Pay Attention' if is_vision else 'Touch Wheel'} - level {dm_state.alertLevel}" alignment = rl.GuiTextAlignment.TEXT_ALIGN_RIGHT if self.driver_state_renderer.is_rhd else rl.GuiTextAlignment.TEXT_ALIGN_LEFT shadow_rect = rl.Rectangle(rect.x + 2, rect.y + 2, rect.width, rect.height) From ca04b70d0a7b1db2313106c0067b28ea8b7536e6 Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Tue, 21 Apr 2026 21:04:06 -0700 Subject: [PATCH 668/685] camerad: driver camera BPS magic (#37873) * update blob * fixed * didnt catch this * add back * needs BLC built in * for real --------- Co-authored-by: Comma Device --- system/camerad/cameras/bps_blobs.h | 12 +++--- system/camerad/cameras/cdm.cc | 4 +- system/camerad/cameras/cdm.h | 10 ++--- system/camerad/cameras/spectra.cc | 62 +++++++++++++++++++++++------- system/camerad/cameras/spectra.h | 1 + 5 files changed, 63 insertions(+), 26 deletions(-) diff --git a/system/camerad/cameras/bps_blobs.h b/system/camerad/cameras/bps_blobs.h index 54941b8d76a..4a0de6659d6 100644 --- a/system/camerad/cameras/bps_blobs.h +++ b/system/camerad/cameras/bps_blobs.h @@ -16,15 +16,15 @@ unsigned char bps_cfg[4][768] = { unsigned char bps_striping_output[4][0x9a0] = { { /* placeholder */ }, - {0x5, 0x0, 0x6, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xFF, 0xFF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x5B, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x5B, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x4, 0x5C, 0x2, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x70, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x89, 0x23, 0x0, 0x1, 0x0, 0x0, 0x0, 0xCC, 0x5, 0x87, 0x7, 0x0, 0x0, 0x0, 0x0, 0xD0, 0x5, 0x87, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x2, 0xA4, 0xFD, 0x50, 0xB1, 0x9, 0x0, 0x8, 0x0, 0x33, 0x0, 0x8, 0x2, 0xA4, 0xFD, 0x50, 0xB1, 0x9, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0x2, 0xA6, 0xFD, 0x68, 0xC0, 0x9, 0x0, 0x8, 0x0, 0x34, 0x0, 0xD0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0x14, 0x4, 0xD3, 0x5, 0x0, 0x0, 0x0, 0x0, 0x18, 0x4, 0xCF, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0xA6, 0xFD, 0x88, 0xA4, 0x5, 0x0, 0x8, 0x0, 0x34, 0x0, 0x18, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0x5C, 0x2, 0x1B, 0x4, 0x0, 0x0, 0x0, 0x0, 0x60, 0x2, 0x17, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x98, 0xFE, 0xA4, 0xFD, 0x50, 0x8B, 0x7, 0x0, 0x8, 0x0, 0x33, 0x0, 0x98, 0xFE, 0xA4, 0xFD, 0x50, 0x8B, 0x7, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9E, 0xFE, 0xA6, 0xFD, 0x28, 0x71, 0x7, 0x0, 0x8, 0x0, 0x34, 0x0, 0x60, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0xC0, 0x0, 0x63, 0x2, 0x0, 0x0, 0x0, 0x0, 0xC4, 0x0, 0x5F, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x9C, 0x1, 0xCE, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFC, 0xFC, 0xA4, 0xFD, 0x20, 0xA9, 0xE, 0x0, 0x8, 0x0, 0x33, 0x0, 0xFC, 0xFC, 0xA4, 0xFD, 0x20, 0xA9, 0xE, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xFD, 0xA6, 0xFD, 0xA8, 0x7B, 0xE, 0x0, 0x8, 0x0, 0x34, 0x0, 0xC4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x98, 0x7, 0x0, 0x0, 0x0, 0xC7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xC4, 0x0, 0x62, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3C, 0xFC, 0xA4, 0xFD, 0x20, 0xBF, 0x13, 0x0, 0x8, 0x0, 0x33, 0x0, 0x3C, 0xFC, 0xA4, 0xFD, 0x20, 0xBF, 0x13, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3E, 0xFC, 0xA6, 0xFD, 0xA8, 0xA6, 0x13, 0x0, 0x8, 0x0, 0x34, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE0, 0x9C, 0x3, 0x0, 0x20, 0x4, 0x1, 0x0, 0x5D, 0x59, 0xAB, 0x0, 0xC8, 0x8C, 0xFD, 0xF4, 0x3, 0x0, 0x0, 0x0, 0xB8, 0x13, 0xFD, 0xFF, 0xAC, 0x5F, 0x8C, 0xF5, 0x0, 0x20, 0x4E, 0x0, 0xAB, 0xAA, 0xAA, 0xAA, 0x40, 0x69, 0xFD, 0xF4, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x38, 0x6B, 0x8B, 0xF5, 0x11, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x9F, 0x9, 0x0, 0x0, 0xB8, 0x2B, 0x6B, 0x15, 0xC0, 0x6A, 0x8C, 0xF5, 0x0, 0x0, 0x0, 0x0, 0xB8, 0x13, 0xFD, 0xFF, 0x2A, 0x5, 0x1, 0x0, 0xC0, 0x13, 0xFD, 0xFF, 0x2C, 0x14, 0xFD, 0xFF, 0x14, 0x14, 0xFD, 0xFF, 0xCC, 0xE8, 0x89, 0xF5, 0xC0, 0x13, 0xFD, 0xFF, 0x64, 0x6A, 0x8C, 0xF5, 0x3, 0x0, 0x0, 0x0, 0xA0, 0x6B, 0x8B, 0xF5, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x8, 0x69, 0x8C, 0xF5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x6A, 0x8C, 0xF5, 0x8, 0x69, 0x8C, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0x9, 0x0, 0x0, 0xF8, 0xB2, 0xFD, 0xF4, 0x50, 0x35, 0x8C, 0xF5, 0xD0, 0x14, 0xC1, 0xF4, 0x1, 0xD0, 0x3B, 0xF5, 0x64, 0x1F, 0xFD, 0xFF, 0xD0, 0xAD, 0x4, 0xF5, 0x2C, 0x30, 0x1, 0x0, 0x0, 0x10, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC, 0x30, 0x1, 0x0, 0xD0, 0x14, 0xC1, 0xF4, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1F, 0xFD, 0xFF, 0x3C, 0x23, 0xFD, 0x34, 0x31, 0x32, 0x31, 0x36, 0xA0, 0x6B, 0x8B, 0xF5, 0x60, 0x14, 0xFD, 0xFF, 0x0, 0x30, 0x1, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1F, 0xFD, 0xFF, 0x3C, 0x23, 0xFD, 0xFF, 0xC0, 0xDC, 0x1, 0xF5, 0x4C, 0x0, 0x0, 0x0, 0x54, 0x14, 0xFD, 0xFF, 0x50, 0x1D, 0x1, 0x0, 0x30, 0x14, 0x1, 0x0, 0x98, 0x1D, 0x1, 0x0, 0x0, 0xA1, 0x0, 0x0, 0x0, 0xA1, 0x0, 0x0, 0x98, 0x1D, 0x1, 0x0, 0x6C, 0x1F, 0xFD, 0xFF, 0x64, 0x1F, 0xFD, 0xFF, 0x68, 0x1F, 0xFD, 0xFF, 0x0, 0x0, 0x0, 0x0}, - {0x5, 0x0, 0x6, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xFF, 0xFF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x5B, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x5B, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x4, 0x5C, 0x2, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x70, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x89, 0x23, 0x0, 0x1, 0x0, 0x0, 0x0, 0xCC, 0x5, 0x87, 0x7, 0x0, 0x0, 0x0, 0x0, 0xD0, 0x5, 0x87, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x2, 0xA4, 0xFD, 0x50, 0xB1, 0x9, 0x0, 0x8, 0x0, 0x33, 0x0, 0x8, 0x2, 0xA4, 0xFD, 0x50, 0xB1, 0x9, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0x2, 0xA6, 0xFD, 0x68, 0xC0, 0x9, 0x0, 0x8, 0x0, 0x34, 0x0, 0xD0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0x14, 0x4, 0xD3, 0x5, 0x0, 0x0, 0x0, 0x0, 0x18, 0x4, 0xCF, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0xA6, 0xFD, 0x88, 0xA4, 0x5, 0x0, 0x8, 0x0, 0x34, 0x0, 0x18, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0x5C, 0x2, 0x1B, 0x4, 0x0, 0x0, 0x0, 0x0, 0x60, 0x2, 0x17, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x98, 0xFE, 0xA4, 0xFD, 0x50, 0x8B, 0x7, 0x0, 0x8, 0x0, 0x33, 0x0, 0x98, 0xFE, 0xA4, 0xFD, 0x50, 0x8B, 0x7, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9E, 0xFE, 0xA6, 0xFD, 0x28, 0x71, 0x7, 0x0, 0x8, 0x0, 0x34, 0x0, 0x60, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0xC0, 0x0, 0x63, 0x2, 0x0, 0x0, 0x0, 0x0, 0xC4, 0x0, 0x5F, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x9C, 0x1, 0xCE, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFC, 0xFC, 0xA4, 0xFD, 0x20, 0xA9, 0xE, 0x0, 0x8, 0x0, 0x33, 0x0, 0xFC, 0xFC, 0xA4, 0xFD, 0x20, 0xA9, 0xE, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xFD, 0xA6, 0xFD, 0xA8, 0x7B, 0xE, 0x0, 0x8, 0x0, 0x34, 0x0, 0xC4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x98, 0x7, 0x0, 0x0, 0x0, 0xC7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xC4, 0x0, 0x62, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3C, 0xFC, 0xA4, 0xFD, 0x20, 0xBF, 0x13, 0x0, 0x8, 0x0, 0x33, 0x0, 0x3C, 0xFC, 0xA4, 0xFD, 0x20, 0xBF, 0x13, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3E, 0xFC, 0xA6, 0xFD, 0xA8, 0xA6, 0x13, 0x0, 0x8, 0x0, 0x34, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE0, 0x9C, 0x3, 0x0, 0x20, 0x4, 0x1, 0x0, 0xA0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x60, 0x5D, 0x1, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0xFC, 0x57, 0x12, 0xF5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x12, 0xF5, 0x0, 0x0, 0x0, 0x0, 0x9F, 0x9, 0x0, 0x0, 0x5E, 0x0, 0x0, 0x0, 0x5F, 0x0, 0x0, 0x0, 0x6E, 0x0, 0x0, 0x0, 0x77, 0x0, 0x0, 0x0, 0x7C, 0x0, 0x0, 0x0, 0x35, 0x1, 0x0, 0x0, 0x26, 0x0, 0x0, 0x0, 0xA4, 0x61, 0x5D, 0x1, 0x58, 0x3, 0x0, 0x0, 0xC0, 0x13, 0xFD, 0xFF, 0x64, 0x6A, 0x8C, 0xF5, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x12, 0xF5, 0x9F, 0x9, 0x0, 0x0, 0xA0, 0x9, 0x0, 0x0, 0xFC, 0x57, 0x12, 0xF5, 0x33, 0x1, 0x0, 0x0, 0xD0, 0x14, 0xC1, 0xF4, 0x1, 0xD0, 0x3B, 0xF5, 0x64, 0x1F, 0xFD, 0xFF, 0xD0, 0xAD, 0x4, 0xF5, 0x2C, 0x30, 0x1, 0x0, 0x0, 0x10, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x27, 0x5E, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x34, 0x31, 0x32, 0x31, 0x36, 0x10, 0x20, 0xFD, 0xFF, 0x60, 0x14, 0xFD, 0xFF, 0x0, 0x30, 0x1, 0x0, 0xA0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10, 0x1F, 0xFD, 0xFF, 0x3C, 0x23, 0xFD, 0xFF, 0xC0, 0xDC, 0x1, 0xF5, 0x0, 0x30, 0x1, 0x0, 0x54, 0x14, 0xFD, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x30, 0x14, 0x1, 0x0, 0x98, 0x1D, 0x1, 0x0, 0x0, 0xA1, 0x0, 0x0, 0x0, 0xA1, 0x0, 0x0, 0x98, 0x1D, 0x1, 0x0, 0x6C, 0x1F, 0xFD, 0xFF, 0x64, 0x1F, 0xFD, 0xFF, 0x68, 0x1F, 0xFD, 0xFF, 0x0, 0x0, 0x0, 0x0}, - {0x4, 0x0, 0x4, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0xFF, 0xFF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0x7B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0x7B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xF8, 0x2, 0x7C, 0x1, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2, 0x0, 0x70, 0x1, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0xA6, 0xFD, 0x88, 0xA4, 0x5, 0x0, 0x8, 0x0, 0x34, 0x0, 0x18, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x78, 0x14, 0x5E, 0x1, 0xB8, 0x5D, 0x12, 0xF5, 0xDC, 0x3B, 0x12, 0xF5, 0x24, 0x0, 0x0, 0x0, 0xFC, 0xF9, 0x3, 0xF5, 0x0, 0x96, 0xF, 0x0, 0x1, 0x5, 0x0, 0x0, 0x84, 0x3, 0x3F, 0x5, 0x0, 0x0, 0x0, 0x0, 0x88, 0x3, 0x3F, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE4, 0x0, 0x84, 0xFE, 0x20, 0xFF, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0xE4, 0x0, 0x84, 0xFE, 0x20, 0xFF, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xEA, 0x0, 0x86, 0xFE, 0x8, 0x4, 0x3, 0x0, 0x7, 0x0, 0x38, 0x0, 0x88, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1A, 0x5, 0x0, 0xCC, 0x1, 0x8B, 0x3, 0x0, 0x0, 0x0, 0x0, 0xD0, 0x1, 0x87, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2C, 0xFF, 0x84, 0xFE, 0xA0, 0xE3, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0x2C, 0xFF, 0x84, 0xFE, 0xA0, 0xE3, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0xFF, 0x86, 0xFE, 0xE8, 0xD3, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0xD0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1A, 0x5, 0x0, 0xC0, 0x0, 0xD3, 0x1, 0x0, 0x0, 0x0, 0x0, 0xC4, 0x0, 0xCF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xC, 0x1, 0x86, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0xFE, 0x84, 0xFE, 0x10, 0xB8, 0x5, 0x0, 0x7, 0x0, 0x38, 0x0, 0x20, 0xFE, 0x84, 0xFE, 0x10, 0xB8, 0x5, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26, 0xFE, 0x86, 0xFE, 0xC8, 0x9B, 0x5, 0x0, 0x7, 0x0, 0x38, 0x0, 0xC4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA0, 0x1B, 0x3, 0x0, 0x0, 0x0, 0xC7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xC4, 0x0, 0x62, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0xFD, 0x84, 0xFE, 0x10, 0x18, 0x9, 0x0, 0x7, 0x0, 0x38, 0x0, 0x60, 0xFD, 0x84, 0xFE, 0x10, 0x18, 0x9, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x62, 0xFD, 0x86, 0xFE, 0xA8, 0x7, 0x9, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE0, 0x45, 0x2, 0x0}, + {0x5, 0x0, 0x6, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xFF, 0xFF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x5B, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x5B, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x4, 0x5C, 0x2, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x70, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x89, 0x23, 0x0, 0x1, 0x0, 0x0, 0x0, 0xCC, 0x5, 0x87, 0x7, 0x0, 0x0, 0x0, 0x0, 0xD0, 0x5, 0x87, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x2, 0xA4, 0xFD, 0x50, 0xB1, 0x9, 0x0, 0x8, 0x0, 0x33, 0x0, 0x8, 0x2, 0xA4, 0xFD, 0x50, 0xB1, 0x9, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0x2, 0xA6, 0xFD, 0x68, 0xC0, 0x9, 0x0, 0x8, 0x0, 0x34, 0x0, 0xD0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0x14, 0x4, 0xD3, 0x5, 0x0, 0x0, 0x0, 0x0, 0x18, 0x4, 0xCF, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0xA6, 0xFD, 0x88, 0xA4, 0x5, 0x0, 0x8, 0x0, 0x34, 0x0, 0x18, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0x5C, 0x2, 0x1B, 0x4, 0x0, 0x0, 0x0, 0x0, 0x60, 0x2, 0x17, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x98, 0xFE, 0xA4, 0xFD, 0x50, 0x8B, 0x7, 0x0, 0x8, 0x0, 0x33, 0x0, 0x98, 0xFE, 0xA4, 0xFD, 0x50, 0x8B, 0x7, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9E, 0xFE, 0xA6, 0xFD, 0x28, 0x71, 0x7, 0x0, 0x8, 0x0, 0x34, 0x0, 0x60, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0xC0, 0x0, 0x63, 0x2, 0x0, 0x0, 0x0, 0x0, 0xC4, 0x0, 0x5F, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x9C, 0x1, 0xCE, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFC, 0xFC, 0xA4, 0xFD, 0x20, 0xA9, 0xE, 0x0, 0x8, 0x0, 0x33, 0x0, 0xFC, 0xFC, 0xA4, 0xFD, 0x20, 0xA9, 0xE, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xFD, 0xA6, 0xFD, 0xA8, 0x7B, 0xE, 0x0, 0x8, 0x0, 0x34, 0x0, 0xC4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x98, 0x7, 0x0, 0x0, 0x0, 0xC7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xC4, 0x0, 0x62, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3C, 0xFC, 0xA4, 0xFD, 0x20, 0xBF, 0x13, 0x0, 0x8, 0x0, 0x33, 0x0, 0x3C, 0xFC, 0xA4, 0xFD, 0x20, 0xBF, 0x13, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3E, 0xFC, 0xA6, 0xFD, 0xA8, 0xA6, 0x13, 0x0, 0x8, 0x0, 0x34, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE0, 0x9C, 0x3, 0x0, 0xE8, 0x3, 0x1, 0x0, 0x5D, 0x59, 0xAB, 0x0, 0xC8, 0xFC, 0xFB, 0x40, 0x3, 0x0, 0x0, 0x0, 0x8, 0xFC, 0x7F, 0x40, 0xAC, 0x1F, 0x84, 0x40, 0x0, 0x20, 0x4E, 0x0, 0xAB, 0xAA, 0xAA, 0xAA, 0x40, 0xD9, 0xFB, 0x40, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC8, 0xED, 0x80, 0x40, 0x11, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x9F, 0x9, 0x0, 0x0, 0xB8, 0x2B, 0x6B, 0x15, 0xC0, 0x2A, 0x84, 0x40, 0x0, 0x0, 0x0, 0x0, 0x8, 0xFC, 0x7F, 0x40, 0xE6, 0x4, 0x1, 0x0, 0x10, 0xFC, 0x7F, 0x40, 0x7C, 0xFC, 0x7F, 0x40, 0x64, 0xFC, 0x7F, 0x40, 0xCC, 0xA8, 0x81, 0x40, 0x10, 0xFC, 0x7F, 0x40, 0x64, 0x2A, 0x84, 0x40, 0x3, 0x0, 0x0, 0x0, 0x30, 0xEE, 0x80, 0x40, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x8, 0x29, 0x84, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x2A, 0x84, 0x40, 0x8, 0x29, 0x84, 0x40, 0xFF, 0xFF, 0xFF, 0xFF, 0xA0, 0x9, 0x0, 0x0, 0xF8, 0x22, 0xFC, 0x40, 0xB8, 0x16, 0x80, 0x40, 0xD0, 0x34, 0x84, 0x40, 0x1, 0xB0, 0xAB, 0x40, 0xB4, 0x7, 0x80, 0x40, 0xD0, 0x1D, 0x3, 0x41, 0x2C, 0x30, 0x1, 0x0, 0x0, 0x10, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC, 0x30, 0x1, 0x0, 0xD0, 0x34, 0x84, 0x40, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x7, 0x80, 0x40, 0x8C, 0xB, 0x80, 0x34, 0x31, 0x32, 0x31, 0x36, 0x30, 0xEE, 0x80, 0x40, 0xB0, 0xFC, 0x7F, 0x40, 0x0, 0x30, 0x1, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x7, 0x80, 0x40, 0x8C, 0xB, 0x80, 0x40, 0xC0, 0x4C, 0x0, 0x41, 0x4C, 0x0, 0x0, 0x0, 0xA4, 0xFC, 0x7F, 0x40, 0x28, 0x1D, 0x1, 0x0, 0xF4, 0x13, 0x1, 0x0, 0x70, 0x1D, 0x1, 0x0, 0x0, 0xA1, 0x0, 0x0, 0x0, 0xA1, 0x0, 0x0, 0x70, 0x1D, 0x1, 0x0, 0xBC, 0x7, 0x80, 0x40, 0xB4, 0x7, 0x80, 0x40, 0xB8, 0x7, 0x80, 0x40, 0x0, 0x0, 0x0, 0x0}, + {0x5, 0x0, 0x6, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xFF, 0xFF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x5B, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x5B, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x4, 0x5C, 0x2, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x5, 0x0, 0x0, 0x0, 0x70, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x89, 0x23, 0x0, 0x1, 0x0, 0x0, 0x0, 0xCC, 0x5, 0x87, 0x7, 0x0, 0x0, 0x0, 0x0, 0xD0, 0x5, 0x87, 0x7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x2, 0xA4, 0xFD, 0x50, 0xB1, 0x9, 0x0, 0x8, 0x0, 0x33, 0x0, 0x8, 0x2, 0xA4, 0xFD, 0x50, 0xB1, 0x9, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0x2, 0xA6, 0xFD, 0x68, 0xC0, 0x9, 0x0, 0x8, 0x0, 0x34, 0x0, 0xD0, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0x14, 0x4, 0xD3, 0x5, 0x0, 0x0, 0x0, 0x0, 0x18, 0x4, 0xCF, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0xA6, 0xFD, 0x88, 0xA4, 0x5, 0x0, 0x8, 0x0, 0x34, 0x0, 0x18, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0x5C, 0x2, 0x1B, 0x4, 0x0, 0x0, 0x0, 0x0, 0x60, 0x2, 0x17, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x98, 0xFE, 0xA4, 0xFD, 0x50, 0x8B, 0x7, 0x0, 0x8, 0x0, 0x33, 0x0, 0x98, 0xFE, 0xA4, 0xFD, 0x50, 0x8B, 0x7, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9E, 0xFE, 0xA6, 0xFD, 0x28, 0x71, 0x7, 0x0, 0x8, 0x0, 0x34, 0x0, 0x60, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1C, 0x8, 0x0, 0xC0, 0x0, 0x63, 0x2, 0x0, 0x0, 0x0, 0x0, 0xC4, 0x0, 0x5F, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x9B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0x9C, 0x1, 0xCE, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFC, 0xFC, 0xA4, 0xFD, 0x20, 0xA9, 0xE, 0x0, 0x8, 0x0, 0x33, 0x0, 0xFC, 0xFC, 0xA4, 0xFD, 0x20, 0xA9, 0xE, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0xFD, 0xA6, 0xFD, 0xA8, 0x7B, 0xE, 0x0, 0x8, 0x0, 0x34, 0x0, 0xC4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0x98, 0x7, 0x0, 0x0, 0x0, 0xC7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xC4, 0x0, 0x62, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3C, 0xFC, 0xA4, 0xFD, 0x20, 0xBF, 0x13, 0x0, 0x8, 0x0, 0x33, 0x0, 0x3C, 0xFC, 0xA4, 0xFD, 0x20, 0xBF, 0x13, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3E, 0xFC, 0xA6, 0xFD, 0xA8, 0xA6, 0x13, 0x0, 0x8, 0x0, 0x34, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE0, 0x9C, 0x3, 0x0, 0xE8, 0x3, 0x1, 0x0, 0xA0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x40, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x0, 0xFC, 0xC7, 0x10, 0x41, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x10, 0x41, 0x0, 0x0, 0x0, 0x0, 0x9F, 0x9, 0x0, 0x0, 0x5E, 0x0, 0x0, 0x0, 0x5F, 0x0, 0x0, 0x0, 0x6E, 0x0, 0x0, 0x0, 0x77, 0x0, 0x0, 0x0, 0x7C, 0x0, 0x0, 0x0, 0x35, 0x1, 0x0, 0x0, 0x26, 0x0, 0x0, 0x0, 0xA4, 0x41, 0x1, 0x0, 0x58, 0x3, 0x0, 0x0, 0x10, 0xFC, 0x7F, 0x40, 0x64, 0x2A, 0x84, 0x40, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x10, 0x41, 0x9F, 0x9, 0x0, 0x0, 0xA0, 0x9, 0x0, 0x0, 0xFC, 0xC7, 0x10, 0x41, 0x33, 0x1, 0x0, 0x0, 0xD0, 0x34, 0x84, 0x40, 0x1, 0xB0, 0xAB, 0x40, 0xB4, 0x7, 0x80, 0x40, 0xD0, 0x1D, 0x3, 0x41, 0x2C, 0x30, 0x1, 0x0, 0x0, 0x10, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x7, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x34, 0x31, 0x32, 0x31, 0x36, 0x60, 0x8, 0x80, 0x40, 0xB0, 0xFC, 0x7F, 0x40, 0x0, 0x30, 0x1, 0x0, 0xA0, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x7, 0x80, 0x40, 0x8C, 0xB, 0x80, 0x40, 0xC0, 0x4C, 0x0, 0x41, 0x0, 0x30, 0x1, 0x0, 0xA4, 0xFC, 0x7F, 0x40, 0x0, 0x0, 0x0, 0x0, 0xF4, 0x13, 0x1, 0x0, 0x70, 0x1D, 0x1, 0x0, 0x0, 0xA1, 0x0, 0x0, 0x0, 0xA1, 0x0, 0x0, 0x70, 0x1D, 0x1, 0x0, 0xBC, 0x7, 0x80, 0x40, 0xB4, 0x7, 0x80, 0x40, 0xB8, 0x7, 0x80, 0x40, 0x0, 0x0, 0x0, 0x0}, + {0x4, 0x0, 0x4, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0xFF, 0xFF, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0x7B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0x7B, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xF7, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xF8, 0x2, 0x7C, 0x1, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2, 0x0, 0x70, 0x1, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x50, 0x0, 0xA4, 0xFD, 0x10, 0xAA, 0x5, 0x0, 0x8, 0x0, 0x33, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x56, 0x0, 0xA6, 0xFD, 0x88, 0xA4, 0x5, 0x0, 0x8, 0x0, 0x34, 0x0, 0x18, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x24, 0x0, 0x0, 0x0, 0x78, 0xF4, 0x1, 0x0, 0xB8, 0xCD, 0x10, 0x41, 0xDC, 0xAB, 0x10, 0x41, 0x24, 0x0, 0x0, 0x0, 0xFC, 0x69, 0x2, 0x41, 0x0, 0x96, 0xF, 0x0, 0x1, 0x5, 0x0, 0x0, 0x84, 0x3, 0x3F, 0x5, 0x0, 0x0, 0x0, 0x0, 0x88, 0x3, 0x3F, 0x5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE4, 0x0, 0x84, 0xFE, 0x20, 0xFF, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0xE4, 0x0, 0x84, 0xFE, 0x20, 0xFF, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xEA, 0x0, 0x86, 0xFE, 0x8, 0x4, 0x3, 0x0, 0x7, 0x0, 0x38, 0x0, 0x88, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1A, 0x5, 0x0, 0xCC, 0x1, 0x8B, 0x3, 0x0, 0x0, 0x0, 0x0, 0xD0, 0x1, 0x87, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0xB7, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xB8, 0x1, 0xDC, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2C, 0xFF, 0x84, 0xFE, 0xA0, 0xE3, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0x2C, 0xFF, 0x84, 0xFE, 0xA0, 0xE3, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x32, 0xFF, 0x86, 0xFE, 0xE8, 0xD3, 0x2, 0x0, 0x7, 0x0, 0x38, 0x0, 0xD0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x1A, 0x5, 0x0, 0xC0, 0x0, 0xD3, 0x1, 0x0, 0x0, 0x0, 0x0, 0xC4, 0x0, 0xCF, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0xB, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xC, 0x1, 0x86, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x20, 0xFE, 0x84, 0xFE, 0x10, 0xB8, 0x5, 0x0, 0x7, 0x0, 0x38, 0x0, 0x20, 0xFE, 0x84, 0xFE, 0x10, 0xB8, 0x5, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x26, 0xFE, 0x86, 0xFE, 0xC8, 0x9B, 0x5, 0x0, 0x7, 0x0, 0x38, 0x0, 0xC4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xA0, 0x1B, 0x3, 0x0, 0x0, 0x0, 0xC7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0xB, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0xC3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x40, 0x0, 0x1, 0x0, 0xC4, 0x0, 0x62, 0x0, 0x0, 0x0, 0x3, 0x0, 0x2, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x41, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0xFD, 0x84, 0xFE, 0x10, 0x18, 0x9, 0x0, 0x7, 0x0, 0x38, 0x0, 0x60, 0xFD, 0x84, 0xFE, 0x10, 0x18, 0x9, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x62, 0xFD, 0x86, 0xFE, 0xA8, 0x7, 0x9, 0x0, 0x7, 0x0, 0x38, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xE0, 0x45, 0x2, 0x0}, }; unsigned char bps_settings[4][684] = { { /* placeholder */ }, - {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, }; diff --git a/system/camerad/cameras/cdm.cc b/system/camerad/cameras/cdm.cc index d4ef20c48cd..8a070fa3aad 100644 --- a/system/camerad/cameras/cdm.cc +++ b/system/camerad/cameras/cdm.cc @@ -1,9 +1,9 @@ #include "cdm.h" #include "stddef.h" -int write_dmi(uint8_t *dst, uint64_t *addr, uint32_t length, uint32_t dmi_addr, uint8_t sel) { +int write_dmi(uint8_t *dst, uint64_t *addr, uint32_t length, uint32_t dmi_addr, uint8_t sel, uint8_t opcode) { struct cdm_dmi_cmd *cmd = (struct cdm_dmi_cmd*)dst; - cmd->cmd = CAM_CDM_CMD_DMI_32; + cmd->cmd = opcode; cmd->length = length - 1; cmd->reserved = 0; cmd->addr = 0; // gets patched in diff --git a/system/camerad/cameras/cdm.h b/system/camerad/cameras/cdm.h index adda6004006..d9640cee3e5 100644 --- a/system/camerad/cameras/cdm.h +++ b/system/camerad/cameras/cdm.h @@ -6,11 +6,6 @@ #include #include -// our helpers -int write_random(uint8_t *dst, const std::vector &vals); -int write_cont(uint8_t *dst, uint32_t reg, const std::vector &vals); -int write_dmi(uint8_t *dst, uint64_t *addr, uint32_t length, uint32_t dmi_addr, uint8_t sel); - // from drivers/media/platform/msm/camera/cam_cdm/cam_cdm_util.{c,h} enum cam_cdm_command { @@ -32,6 +27,11 @@ enum cam_cdm_command { CAM_CDM_CMD_PRIVATE_BASE_MAX = 0x7F }; +// our helpers +int write_random(uint8_t *dst, const std::vector &vals); +int write_cont(uint8_t *dst, uint32_t reg, const std::vector &vals); +int write_dmi(uint8_t *dst, uint64_t *addr, uint32_t length, uint32_t dmi_addr, uint8_t sel, uint8_t opcode = CAM_CDM_CMD_DMI_32); + /** * struct cdm_regrandom_cmd - Definition for CDM random register command. * @count: Number of register writes diff --git a/system/camerad/cameras/spectra.cc b/system/camerad/cameras/spectra.cc index ab9d8e069af..23c58c1ec90 100644 --- a/system/camerad/cameras/spectra.cc +++ b/system/camerad/cameras/spectra.cc @@ -467,7 +467,7 @@ void SpectraCamera::config_bps(int idx, int request_id) { */ int size = sizeof(struct cam_packet) + sizeof(struct cam_cmd_buf_desc)*2 + sizeof(struct cam_buf_io_cfg)*2; - size += sizeof(struct cam_patch_desc)*9; + size += sizeof(struct cam_patch_desc)*12; uint32_t cam_packet_handle = 0; auto pkt = m->mem_mgr.alloc(size, &cam_packet_handle); @@ -545,8 +545,15 @@ void SpectraCamera::config_bps(int idx, int request_id) { int cdm_len = 0; if (bps_lin_reg.size() == 0) { + // set first knee pt to do BLC + uint32_t new_knee[8]; + new_knee[0] = sensor->black_level << (14 - sensor->bits_per_pixel); + for (int i = 0; i < 7; i++) { + uint32_t pts = sensor->linearization_pts[i / 2]; + new_knee[i + 1] = (i % 2 == 0) ? (pts >> 16) : (pts & 0xffff); + } for (int i = 0; i < 4; i++) { - bps_lin_reg.push_back(((sensor->linearization_pts[i] & 0xffff) << 0x10) | (sensor->linearization_pts[i] >> 0x10)); + bps_lin_reg.push_back((new_knee[2*i + 1] << 16) | new_knee[2*i]); } } @@ -569,20 +576,24 @@ void SpectraCamera::config_bps(int idx, int request_id) { 0x00000080, 0x00800066, }); - // linearization, EN=0 + // linearization cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x1868, bps_lin_reg); cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x1878, bps_lin_reg); cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x1888, bps_lin_reg); cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x1898, bps_lin_reg); - /* - uint8_t *start = (unsigned char *)bps_cdm_program_array.ptr + cdm_len; uint64_t addr; - cdm_len += write_dmi((unsigned char *)bps_cdm_program_array.ptr + cdm_len, &addr, sensor->linearization_lut.size()*sizeof(uint32_t), 0x1808, 1); - patches.push_back(addr - (uint64_t)start); - */ + cdm_len += write_dmi((unsigned char *)bps_cdm_program_array.ptr + cdm_len, &addr, sensor->linearization_lut.size()*sizeof(uint32_t), 0x1808, 1, CAM_CDM_CMD_DMI); + patches.push_back(addr - (uint64_t)bps_cdm_program_array.ptr); + // color correction cdm_len += write_cont((unsigned char *)bps_cdm_program_array.ptr + cdm_len, 0x2e68, bps_ccm_reg); + // gamma + for (uint8_t ch = 1; ch <= 3; ch++) { + cdm_len += write_dmi((unsigned char *)bps_cdm_program_array.ptr + cdm_len, &addr, sensor->gamma_lut_rgb.size()*sizeof(uint32_t), 0x3208, ch, CAM_CDM_CMD_DMI); + patches.push_back(addr - (uint64_t)bps_cdm_program_array.ptr); + } + cdm_len += build_common_ife_bps((unsigned char *)bps_cdm_program_array.ptr + cdm_len, cc, sensor.get(), patches, false); pa->length = cdm_len - 1; @@ -664,11 +675,16 @@ void SpectraCamera::config_bps(int idx, int request_id) { // *** patches *** { - assert(patches.size() == 0 | patches.size() == 1); + assert(patches.size() == 0 || patches.size() == 4); pkt->patch_offset = sizeof(struct cam_cmd_buf_desc)*pkt->num_cmd_buf + sizeof(struct cam_buf_io_cfg)*pkt->num_io_configs; if (patches.size() > 0) { - add_patch(pkt.get(), bps_cmd.handle, patches[0], bps_linearization_lut.handle, 0); + // linearization LUT + add_patch(pkt.get(), bps_cdm_program_array.handle, patches[0], bps_linearization_lut.handle, 0); + // gamma LUTs + for (int i = 0; i < 3; i++) { + add_patch(pkt.get(), bps_cdm_program_array.handle, patches[i+1], bps_gamma_lut.handle, 0); + } } // input frame @@ -1170,10 +1186,30 @@ void SpectraCamera::configICP() { bps_cdm_striping_bl.init(m, 0xa100, 0x20, true, m->icp_device_iommu); // LUTs - /* + assert(sensor->linearization_lut.size() == 36); bps_linearization_lut.init(m, sensor->linearization_lut.size()*sizeof(uint32_t), 0x20, true, m->icp_device_iommu); - memcpy(bps_linearization_lut.ptr, sensor->linearization_lut.data(), bps_linearization_lut.size); - */ + + // bit shift linearization_lut to bps specs, also compensate for black level here + uint32_t bl = sensor->black_level << (14 - sensor->bits_per_pixel); + uint32_t* bps_lut = (uint32_t*)bps_linearization_lut.ptr; + for (size_t i = 0; i < sensor->linearization_lut.size(); i++) { + size_t seg = i / 4; + size_t ch = i % 4; + if (seg == 0) { + bps_lut[i] = 0; + continue; + } + uint32_t e = sensor->linearization_lut[(seg - 1) * 4 + ch]; + uint32_t base = e & 0x3fff; + uint32_t slope_q11 = (e >> 14) & 0x3fff; + uint32_t slope_q12 = std::min(slope_q11 << 1, 0x3fff); + base = (base > bl) ? (base - bl) : 0; + bps_lut[i] = base | (slope_q12 << 14); + } + + assert(sensor->gamma_lut_rgb.size() == 64); + bps_gamma_lut.init(m, sensor->gamma_lut_rgb.size()*sizeof(uint32_t), 0x20, true, m->icp_device_iommu); + memcpy(bps_gamma_lut.ptr, sensor->gamma_lut_rgb.data(), bps_gamma_lut.size); } void SpectraCamera::configCSIPHY() { diff --git a/system/camerad/cameras/spectra.h b/system/camerad/cameras/spectra.h index 7dd11352540..741e8ad276e 100644 --- a/system/camerad/cameras/spectra.h +++ b/system/camerad/cameras/spectra.h @@ -173,6 +173,7 @@ class SpectraCamera { SpectraBuf bps_iq; SpectraBuf bps_striping; SpectraBuf bps_linearization_lut; + SpectraBuf bps_gamma_lut; std::vector bps_lin_reg; std::vector bps_ccm_reg; From f8c45d307c3da61e49d6f57e26ecccbde1bc0d13 Mon Sep 17 00:00:00 2001 From: Trey Moen <50057480+greatgitsby@users.noreply.github.com> Date: Wed, 22 Apr 2026 08:48:02 -0700 Subject: [PATCH 669/685] esim: skip listing profiles on mutation ops (#37878) --- system/hardware/esim.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/system/hardware/esim.py b/system/hardware/esim.py index 40600d26b56..54407693b31 100755 --- a/system/hardware/esim.py +++ b/system/hardware/esim.py @@ -28,8 +28,7 @@ lpa.nickname_profile(args.nickname[0], args.nickname[1]) else: parser.print_help() - - profiles = lpa.list_profiles() - print(f'\n{len(profiles)} profile{"s" if len(profiles) > 1 else ""}:') - for p in profiles: - print(f'- {p.iccid} (nickname: {p.nickname or ""}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}') + profiles = lpa.list_profiles() + print(f'\n{len(profiles)} profile{"s" if len(profiles) > 1 else ""}:') + for p in profiles: + print(f'- {p.iccid} (nickname: {p.nickname or ""}) (provider: {p.provider}) - {"enabled" if p.enabled else "disabled"}') From f8372ccc4df0b5b0e49572f8817ce7d5b14c6db9 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Apr 2026 12:53:07 -0700 Subject: [PATCH 670/685] sensord: remove mmc5603nj support (#37881) * sensord: remove mmc5603nj support * lil more * lil more --- cereal/services.py | 2 - system/sensord/sensord.py | 6 - system/sensord/sensors/mmc5603nj_magn.py | 76 ------------ system/sensord/tests/test_sensord.py | 146 ++++++++--------------- 4 files changed, 51 insertions(+), 179 deletions(-) delete mode 100644 system/sensord/sensors/mmc5603nj_magn.py diff --git a/cereal/services.py b/cereal/services.py index c8525c0dd30..c43d3cb9b6c 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -24,8 +24,6 @@ def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] # note: the "EncodeIdx" packets will still be in the log "gyroscope": (True, 104., 104), "accelerometer": (True, 104., 104), - "magnetometer": (True, 25.), - "lightSensor": (True, 100., 100), "temperatureSensor": (True, 2., 200), "gpsNMEA": (True, 9.), "deviceState": (True, 2., 1), diff --git a/system/sensord/sensord.py b/system/sensord/sensord.py index 62908c6f185..d17d296958b 100755 --- a/system/sensord/sensord.py +++ b/system/sensord/sensord.py @@ -11,13 +11,11 @@ from openpilot.common.realtime import config_realtime_process, Ratekeeper from openpilot.common.swaglog import cloudlog from openpilot.common.gpio import gpiochip_get_ro_value_fd, gpioevent_data -from openpilot.system.hardware import HARDWARE from openpilot.system.sensord.sensors.i2c_sensor import Sensor from openpilot.system.sensord.sensors.lsm6ds3_accel import LSM6DS3_Accel from openpilot.system.sensord.sensors.lsm6ds3_gyro import LSM6DS3_Gyro from openpilot.system.sensord.sensors.lsm6ds3_temp import LSM6DS3_Temp -from openpilot.system.sensord.sensors.mmc5603nj_magn import MMC5603NJ_Magn I2C_BUS_IMU = 1 @@ -97,10 +95,6 @@ def main() -> None: (LSM6DS3_Gyro(I2C_BUS_IMU), "gyroscope", True), (LSM6DS3_Temp(I2C_BUS_IMU), "temperatureSensor", False), ] - if HARDWARE.get_device_type() == "tizi": - sensors_cfg.append( - (MMC5603NJ_Magn(I2C_BUS_IMU), "magnetometer", False), - ) # Reset sensors for sensor, _, _ in sensors_cfg: diff --git a/system/sensord/sensors/mmc5603nj_magn.py b/system/sensord/sensors/mmc5603nj_magn.py deleted file mode 100644 index 255e99eb3e3..00000000000 --- a/system/sensord/sensors/mmc5603nj_magn.py +++ /dev/null @@ -1,76 +0,0 @@ -import time - -from cereal import log -from openpilot.system.sensord.sensors.i2c_sensor import Sensor - -# https://www.mouser.com/datasheet/2/821/Memsic_09102019_Datasheet_Rev.B-1635324.pdf - -# Register addresses -REG_ODR = 0x1A -REG_INTERNAL_0 = 0x1B -REG_INTERNAL_1 = 0x1C - -# Control register settings -CMM_FREQ_EN = (1 << 7) -AUTO_SR_EN = (1 << 5) -SET = (1 << 3) -RESET = (1 << 4) - -class MMC5603NJ_Magn(Sensor): - @property - def device_address(self) -> int: - return 0x30 - - def init(self): - self.verify_chip_id(0x39, [0x10, ]) - self.writes(( - (REG_ODR, 0), - - # Set BW to 0b01 for 1-150 Hz operation - (REG_INTERNAL_1, 0b01), - )) - - def _read_data(self, cycle) -> list[float]: - # start measurement - self.write(REG_INTERNAL_0, cycle) - self.wait() - - # read out XYZ - scale = 1.0 / 16384.0 - b = self.read(0x00, 9) - return [ - (self.parse_20bit(b[6], b[1], b[0]) * scale) - 32.0, - (self.parse_20bit(b[7], b[3], b[2]) * scale) - 32.0, - (self.parse_20bit(b[8], b[5], b[4]) * scale) - 32.0, - ] - - def get_event(self, ts: int | None = None) -> log.SensorEventData: - ts = time.monotonic_ns() - - # SET - RESET cycle - xyz = self._read_data(SET) - reset_xyz = self._read_data(RESET) - vals = [*xyz, *reset_xyz] - - event = log.SensorEventData.new_message() - event.timestamp = ts - event.version = 1 - event.sensor = 3 # SENSOR_MAGNETOMETER_UNCALIBRATED - event.type = 14 # SENSOR_TYPE_MAGNETIC_FIELD_UNCALIBRATED - event.source = log.SensorEventData.SensorSource.mmc5603nj - - m = event.init('magneticUncalibrated') - m.v = vals - m.status = int(all(int(v) != -32 for v in vals)) - - return event - - def shutdown(self) -> None: - v = self.read(REG_INTERNAL_0, 1)[0] - self.writes(( - # disable auto-reset of measurements - (REG_INTERNAL_0, (v & (~(CMM_FREQ_EN | AUTO_SR_EN)))), - - # disable continuous mode - (REG_ODR, 0), - )) diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py index 9a66678ecf1..c82e5956034 100644 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -5,44 +5,19 @@ from collections import namedtuple, defaultdict import cereal.messaging as messaging -from cereal import log from cereal.services import SERVICE_LIST from openpilot.common.gpio import get_irqs_for_action from openpilot.common.timeout import Timeout -from openpilot.system.hardware import HARDWARE from openpilot.system.manager.process_config import managed_processes -LSM = { - ('lsm6ds3', 'acceleration'), - ('lsm6ds3', 'gyroUncalibrated'), - ('lsm6ds3', 'temperature'), -} -LSM_C = {(x[0]+'trc', x[1]) for x in LSM} - -MMC = { - ('mmc5603nj', 'magneticUncalibrated'), -} - -SENSOR_CONFIGURATIONS: list[set] = { - "mici": [LSM, LSM_C], - "tizi": [MMC | LSM, MMC | LSM_C], - "tici": [LSM, LSM_C, MMC | LSM, MMC | LSM_C], -}.get(HARDWARE.get_device_type(), []) - -Sensor = log.SensorEventData.SensorSource -SensorConfig = namedtuple('SensorConfig', ['type', 'sanity_min', 'sanity_max', 'std_max']) -ALL_SENSORS = { - Sensor.lsm6ds3trc: { - SensorConfig("acceleration", 5, 15, 5), - SensorConfig("gyroUncalibrated", 0, .15, 0.5), - SensorConfig("temperature", 10, 40, 0.5), # set for max range of our office - }, - - Sensor.mmc5603nj: { - SensorConfig("magneticUncalibrated", 0, 300, 10), - } -} -ALL_SENSORS[Sensor.lsm6ds3] = ALL_SENSORS[Sensor.lsm6ds3trc] +SensorConfig = namedtuple('SensorConfig', ['service', 'measurement', 'sanity_min', 'sanity_max', 'std_max']) + +SENSOR_CONFIGS = ( + SensorConfig("accelerometer", "acceleration", 5, 15, 5), + SensorConfig("gyroscope", "gyroUncalibrated", 0, .15, 0.5), + SensorConfig("temperatureSensor", "temperature", 10, 40, 0.5), # set for max range of our office +) +SENSOR_CONFIGS_BY_MEASUREMENT = {config.measurement: config for config in SENSOR_CONFIGS} def get_irq_count(irq: int): with open(f"/sys/kernel/irq/{irq}/per_cpu_count") as f: @@ -50,12 +25,11 @@ def get_irq_count(irq: int): return sum(per_cpu) def read_sensor_events(duration_sec): - sensor_types = ['accelerometer', 'gyroscope', 'magnetometer', 'temperatureSensor',] socks = {} poller = messaging.Poller() events = defaultdict(list) - for stype in sensor_types: - socks[stype] = messaging.sub_sock(stype, poller=poller, timeout=100) + for config in SENSOR_CONFIGS: + socks[config.service] = messaging.sub_sock(config.service, poller=poller, timeout=100) # wait for sensors to come up with Timeout(int(os.environ.get("SENSOR_WAIT", "5")), "sensors didn't come up"): @@ -70,11 +44,15 @@ def read_sensor_events(duration_sec): for s in socks: events[s] += messaging.drain_sock(socks[s]) time.sleep(0.1) - assert sum(map(len, events.values())) != 0, "No sensor events collected!" return {k: v for k, v in events.items() if len(v) > 0} +def iter_measurements(events): + for msgs in events.values(): + for measurement in msgs: + yield measurement, getattr(measurement, measurement.which()) + @pytest.mark.tici class TestSensord: @classmethod @@ -102,15 +80,9 @@ def teardown_class(cls): def teardown_method(self): managed_processes["sensord"].stop() - def test_sensors_present(self): - # verify correct sensors configuration - seen = set() - for etype in self.events: - for measurement in self.events[etype]: - m = getattr(measurement, measurement.which()) - seen.add((str(m.source), m.which())) - - assert seen in SENSOR_CONFIGURATIONS + def test_all_sensors_present(self): + missing = [config.service for config in SENSOR_CONFIGS if config.service not in self.events] + assert len(missing) == 0, f"missing sensors: {missing}" def test_lsm6ds3_timing(self, subtests): # verify measurements are sampled and published at 104Hz @@ -120,13 +92,10 @@ def test_lsm6ds3_timing(self, subtests): 5: [], # gyro } - for measurement in self.events['accelerometer']: - m = getattr(measurement, measurement.which()) - sensor_t[m.sensor].append(m.timestamp) - - for measurement in self.events['gyroscope']: - m = getattr(measurement, measurement.which()) - sensor_t[m.sensor].append(m.timestamp) + for service in ('accelerometer', 'gyroscope'): + for measurement in self.events.get(service, []): + m = getattr(measurement, measurement.which()) + sensor_t[m.sensor].append(m.timestamp) for s, vals in sensor_t.items(): with subtests.test(sensor=s): @@ -153,19 +122,16 @@ def test_sensor_frequency(self, subtests): def test_logmonottime_timestamp_diff(self): # ensure diff between the message logMonotime and sample timestamp is small - tdiffs = list() - for etype in self.events: - for measurement in self.events[etype]: - m = getattr(measurement, measurement.which()) - - # check if gyro and accel timestamps are before logMonoTime - if str(m.source).startswith("lsm6ds3") and m.which() != 'temperature': - err_msg = f"Timestamp after logMonoTime: {m.timestamp} > {measurement.logMonoTime}" - assert m.timestamp < measurement.logMonoTime, err_msg + tdiffs = [] + for measurement, m in iter_measurements(self.events): + # check if gyro and accel timestamps are before logMonoTime + if str(m.source).startswith("lsm6ds3") and m.which() != 'temperature': + err_msg = f"Timestamp after logMonoTime: {m.timestamp} > {measurement.logMonoTime}" + assert m.timestamp < measurement.logMonoTime, err_msg - # negative values might occur, as non interrupt packages created - # before the sensor is read - tdiffs.append(abs(measurement.logMonoTime - m.timestamp) / 1e6) + # negative values might occur, as non interrupt packages created + # before the sensor is read + tdiffs.append(abs(measurement.logMonoTime - m.timestamp) / 1e6) # some sensors have a read procedure that will introduce an expected diff on the order of 20ms high_delay_diffs = set(filter(lambda d: d >= 25., tdiffs)) @@ -175,39 +141,29 @@ def test_logmonottime_timestamp_diff(self): assert avg_diff < 4, f"Avg packet diff: {avg_diff:.1f}ms" def test_sensor_values(self): - sensor_values = dict() - for etype in self.events: - for measurement in self.events[etype]: - m = getattr(measurement, measurement.which()) - key = (m.source.raw, m.which()) - values = getattr(m, m.which()) - - if hasattr(values, 'v'): - values = values.v - values = np.atleast_1d(values) + sensor_values = defaultdict(list) + for _, m in iter_measurements(self.events): + key = (m.source.raw, m.which()) + values = getattr(m, m.which()) - if key in sensor_values: - sensor_values[key].append(values) - else: - sensor_values[key] = [values] + if hasattr(values, 'v'): + values = values.v + sensor_values[key].append(np.atleast_1d(values)) # Sanity check sensor values - for sensor, stype in sensor_values: - for s in ALL_SENSORS[sensor]: - if s.type != stype: - continue - - key = (sensor, s.type) - if s.type == 'temperature': - measurement_stat = np.mean(sensor_values[key]) - else: - measurement_stat = np.mean(np.linalg.norm(sensor_values[key], axis=1)) - err_msg = f"Sensor '{sensor} {s.type}' failed sanity checks {measurement_stat} is not between {s.sanity_min} and {s.sanity_max}" - assert s.sanity_min <= measurement_stat <= s.sanity_max, err_msg - - std_dev = np.std(sensor_values[key], axis=0) - err_msg = f"Sensor '{sensor} {s.type}' failed std dev test {std_dev} is not under {s.std_max}" - assert np.all(std_dev <= s.std_max), err_msg + for (sensor, stype), values in sensor_values.items(): + config = SENSOR_CONFIGS_BY_MEASUREMENT[stype] + + if config.measurement == 'temperature': + measurement_stat = np.mean(values) + else: + measurement_stat = np.mean(np.linalg.norm(values, axis=1)) + err_msg = f"Sensor '{sensor} {config.measurement}' failed sanity checks {measurement_stat} is not between {config.sanity_min} and {config.sanity_max}" + assert config.sanity_min <= measurement_stat <= config.sanity_max, err_msg + + std_dev = np.std(values, axis=0) + err_msg = f"Sensor '{sensor} {config.measurement}' failed std dev test {std_dev} is not under {config.std_max}" + assert np.all(std_dev <= config.std_max), err_msg def test_sensor_verify_no_interrupts_after_stop(self): managed_processes["sensord"].start() From 2266a9dd9cc1327736ed3b93528c8caae92f965c Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Apr 2026 13:13:39 -0700 Subject: [PATCH 671/685] sensord: clean up SensorEventData struct (#37883) --- cereal/log.capnp | 13 ++++++++----- selfdrive/test/process_replay/migration.py | 3 --- system/sensord/sensors/lsm6ds3_accel.py | 4 ---- system/sensord/sensors/lsm6ds3_gyro.py | 4 ---- system/sensord/sensors/lsm6ds3_temp.py | 1 - system/sensord/tests/test_sensord.py | 9 +++------ tools/sim/lib/simulated_sensors.py | 5 ----- 7 files changed, 11 insertions(+), 28 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 2dea21ff053..618aad1831b 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -273,11 +273,7 @@ struct GPSNMEAData { nmea @2 :Text; } -# android sensor_event_t struct SensorEventData { - version @0 :Int32; - sensor @1 :Int32; - type @2 :Int32; timestamp @3 :Int64; union { @@ -296,7 +292,10 @@ struct SensorEventData { struct SensorVec { v @0 :List(Float32); - status @1 :Int8; + + deprecated :group { + status @1 :Int8; + } } enum SensorSource { @@ -314,7 +313,11 @@ struct SensorEventData { mmc5603nj @11; } + # formerly based on android sensor_event_t deprecated :group { + version @0 :Int32; + sensor @1 :Int32; + type @2 :Int32; uncalibrated @10 :Bool; } } diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index b6134913471..12a9664a54e 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -447,9 +447,6 @@ def migrate_sensorEvents(msgs): m.logMonoTime = msg.logMonoTime m_dat = getattr(m, sensor_service) - m_dat.version = evt.version - m_dat.sensor = evt.sensor - m_dat.type = evt.type m_dat.source = evt.source m_dat.timestamp = evt.timestamp setattr(m_dat, evt.which(), getattr(evt, evt.which())) diff --git a/system/sensord/sensors/lsm6ds3_accel.py b/system/sensord/sensors/lsm6ds3_accel.py index 43863daa936..761ae828bbd 100644 --- a/system/sensord/sensors/lsm6ds3_accel.py +++ b/system/sensord/sensors/lsm6ds3_accel.py @@ -77,13 +77,9 @@ def get_event(self, ts: int | None = None) -> log.SensorEventData: event = log.SensorEventData.new_message() event.timestamp = ts - event.version = 1 - event.sensor = 1 # SENSOR_ACCELEROMETER - event.type = 1 # SENSOR_TYPE_ACCELEROMETER event.source = self.source a = event.init('acceleration') a.v = [y, -x, z] - a.status = 1 return event def shutdown(self) -> None: diff --git a/system/sensord/sensors/lsm6ds3_gyro.py b/system/sensord/sensors/lsm6ds3_gyro.py index 60de2bbe02e..654cff9da8d 100644 --- a/system/sensord/sensors/lsm6ds3_gyro.py +++ b/system/sensord/sensors/lsm6ds3_gyro.py @@ -73,13 +73,9 @@ def get_event(self, ts: int | None = None) -> log.SensorEventData: event = log.SensorEventData.new_message() event.timestamp = ts - event.version = 2 - event.sensor = 5 # SENSOR_GYRO_UNCALIBRATED - event.type = 16 # SENSOR_TYPE_GYROSCOPE_UNCALIBRATED event.source = self.source g = event.init('gyroUncalibrated') g.v = xyz - g.status = 1 return event def shutdown(self) -> None: diff --git a/system/sensord/sensors/lsm6ds3_temp.py b/system/sensord/sensors/lsm6ds3_temp.py index b9bb9fe3da5..ffe970c22cb 100644 --- a/system/sensord/sensors/lsm6ds3_temp.py +++ b/system/sensord/sensors/lsm6ds3_temp.py @@ -23,7 +23,6 @@ def init(self): def get_event(self, ts: int | None = None) -> log.SensorEventData: event = log.SensorEventData.new_message() - event.version = 1 event.timestamp = int(time.monotonic() * 1e9) event.source = self.source event.temperature = self._read_temperature() diff --git a/system/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py index c82e5956034..3d7d26f9faf 100644 --- a/system/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -87,15 +87,12 @@ def test_all_sensors_present(self): def test_lsm6ds3_timing(self, subtests): # verify measurements are sampled and published at 104Hz - sensor_t = { - 1: [], # accel - 5: [], # gyro - } + sensor_t = {service: [] for service in ('accelerometer', 'gyroscope')} - for service in ('accelerometer', 'gyroscope'): + for service in sensor_t: for measurement in self.events.get(service, []): m = getattr(measurement, measurement.which()) - sensor_t[m.sensor].append(m.timestamp) + sensor_t[service].append(m.timestamp) for s, vals in sensor_t.items(): with subtests.test(sensor=s): diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py index 6ac7c4fa59a..d6d822c582b 100644 --- a/tools/sim/lib/simulated_sensors.py +++ b/tools/sim/lib/simulated_sensors.py @@ -23,17 +23,12 @@ def __init__(self, dual_camera=False): def send_imu_message(self, simulator_state: 'SimulatorState'): for _ in range(5): dat = messaging.new_message('accelerometer', valid=True) - dat.accelerometer.sensor = 4 - dat.accelerometer.type = 0x10 dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp dat.accelerometer.init('acceleration') dat.accelerometer.acceleration.v = [simulator_state.imu.accelerometer.x, simulator_state.imu.accelerometer.y, simulator_state.imu.accelerometer.z] self.pm.send('accelerometer', dat) - # copied these numbers from locationd dat = messaging.new_message('gyroscope', valid=True) - dat.gyroscope.sensor = 5 - dat.gyroscope.type = 0x10 dat.gyroscope.timestamp = dat.logMonoTime # TODO: use the IMU timestamp dat.gyroscope.init('gyroUncalibrated') dat.gyroscope.gyroUncalibrated.v = [simulator_state.imu.gyroscope.x, simulator_state.imu.gyroscope.y, simulator_state.imu.gyroscope.z] From 39d1eec575336a63219c06bc1300773d469b7e97 Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 22 Apr 2026 15:05:07 -0700 Subject: [PATCH 672/685] Fix Tesla route spam (#37884) bump --- panda | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panda b/panda index ac89f0c92d0..b19b66a6f0a 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit ac89f0c92d02405c66498b8e3086a310c547e7ea +Subproject commit b19b66a6f0a246729953cb19390dbee1e93610b2 From df6d34e52bc8490914a4fe177bd63001761c7b2b Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Apr 2026 15:09:56 -0700 Subject: [PATCH 673/685] remove enhancement issue template --- .github/ISSUE_TEMPLATE/enhancement.md | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/enhancement.md diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md deleted file mode 100644 index 330b9b11015..00000000000 --- a/.github/ISSUE_TEMPLATE/enhancement.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: Enhancement -about: For openpilot enhancement suggestions -title: '' -labels: 'enhancement' -assignees: '' ---- - From 736cf6d9dffd0db6c6a5a2fb532bea018a3e3ead Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Apr 2026 16:01:35 -0700 Subject: [PATCH 674/685] clean up deprecated services (#37885) * clean up deprecated services * lil more --- cereal/log.capnp | 16 +++++++--------- cereal/services.py | 9 --------- system/qcomgpsd/tests/test_qcomgpsd.py | 2 +- tools/jotpluggler/runtime.cc | 1 - 4 files changed, 8 insertions(+), 20 deletions(-) diff --git a/cereal/log.capnp b/cereal/log.capnp index 618aad1831b..c0ee33743af 100644 --- a/cereal/log.capnp +++ b/cereal/log.capnp @@ -2449,7 +2449,6 @@ struct Event { boot @60 :Boot; # ********** openpilot daemon msgs ********** - gpsNMEA @3 :GPSNMEAData; can @5 :List(CanData); controlsState @7 :ControlsState; selfdriveState @130 :SelfdriveState; @@ -2474,7 +2473,6 @@ struct Event { qcomGnss @31 :QcomGnss; gpsLocationExternal @48 :GpsLocationData; gpsLocation @21 :GpsLocationData; - gnssMeasurements @91 :GnssMeasurements; liveParameters @61 :LiveParametersData; liveTorqueParameters @94 :LiveTorqueParametersData; liveDelay @146 : LiveDelayData; @@ -2508,7 +2506,6 @@ struct Event { # systems stuff androidLog @20 :AndroidLogEntry; managerState @78 :ManagerState; - uploaderState @79 :UploaderState; procLog @33 :ProcLog; clocks @35 :Clocks; deviceState @6 :DeviceState; @@ -2518,12 +2515,6 @@ struct Event { # touch frame touch @135 :List(Touch); - # navigation - navInstruction @82 :NavInstruction; - navRoute @83 :NavRoute; - navThumbnail @84: Thumbnail; - mapRenderState @105: MapRenderState; - # UI services uiDebug @102 :UIDebug; @@ -2626,5 +2617,12 @@ struct Event { accelerometer2DEPRECATED @101 :SensorEventData; temperatureSensor2DEPRECATED @123 :SensorEventData; driverMonitoringStateDEPRECATED @71 :DriverMonitoringStateDEPRECATED; + gpsNMEADEPRECATED @3 :GPSNMEAData; + uploaderStateDEPRECATED @79 :UploaderState; + navInstructionDEPRECATED @82 :NavInstruction; + navRouteDEPRECATED @83 :NavRoute; + navThumbnailDEPRECATED @84 :Thumbnail; + gnssMeasurementsDEPRECATED @91 :GnssMeasurements; + mapRenderStateDEPRECATED @105: MapRenderState; } } diff --git a/cereal/services.py b/cereal/services.py index c43d3cb9b6c..8343874dedf 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -25,7 +25,6 @@ def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] "gyroscope": (True, 104., 104), "accelerometer": (True, 104., 104), "temperatureSensor": (True, 2., 200), - "gpsNMEA": (True, 9.), "deviceState": (True, 2., 1), "touch": (True, 20., 1), "can": (True, 100., 2053, QueueSize.BIG), # decimation gives ~3 msgs in a full segment @@ -54,7 +53,6 @@ def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] "gpsLocation": (True, 1., 1), "ubloxGnss": (True, 10.), "qcomGnss": (True, 2.), - "gnssMeasurements": (True, 10., 10), "clocks": (True, 0.1, 1), "ubloxRaw": (True, 20.), "livePose": (True, 20., 4), @@ -73,10 +71,6 @@ def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] "drivingModelData": (True, 20., 10), "modelV2": (True, 20., None, QueueSize.BIG), "managerState": (True, 2., 1), - "uploaderState": (True, 0., 1), - "navInstruction": (True, 1., 10), - "navRoute": (True, 0.), - "navThumbnail": (True, 0.), "qRoadEncodeIdx": (False, 20.), "userBookmark": (True, 0., 1), "soundPressure": (True, 10., 10), @@ -98,9 +92,6 @@ def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] "livestreamWideRoadEncodeData": (False, 20., None, QueueSize.MEDIUM), "livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM), "livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM), - "customReservedRawData0": (True, 0.), - "customReservedRawData1": (True, 0.), - "customReservedRawData2": (True, 0.), } SERVICE_LIST = {name: Service(*vals) for idx, (name, vals) in enumerate(_services.items())} diff --git a/system/qcomgpsd/tests/test_qcomgpsd.py b/system/qcomgpsd/tests/test_qcomgpsd.py index 75e87075910..9b0644734e7 100644 --- a/system/qcomgpsd/tests/test_qcomgpsd.py +++ b/system/qcomgpsd/tests/test_qcomgpsd.py @@ -26,7 +26,7 @@ def teardown_class(cls): os.system("sudo systemctl restart ModemManager lte") def setup_method(self): - self.sm = messaging.SubMaster(['qcomGnss', 'gpsLocation', 'gnssMeasurements']) + self.sm = messaging.SubMaster(['qcomGnss', 'gpsLocation']) def teardown_method(self): managed_processes['qcomgpsd'].stop() diff --git a/tools/jotpluggler/runtime.cc b/tools/jotpluggler/runtime.cc index 6eb2be80e8e..3247a5d01da 100644 --- a/tools/jotpluggler/runtime.cc +++ b/tools/jotpluggler/runtime.cc @@ -71,7 +71,6 @@ bool should_subscribe_stream_service(const std::string &name) { "livestreamRoadEncodeIdx", "livestreamDriverEncodeIdx", "thumbnail", - "navThumbnail", }}; if (name == "rawAudioData") return false; for (std::string_view skipped : kSkippedServices) { From 0be0d7fa9443c32aa261612bda222ebef7d7b2c5 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Apr 2026 16:40:42 -0700 Subject: [PATCH 675/685] add that back, it's used in a test --- cereal/services.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cereal/services.py b/cereal/services.py index 8343874dedf..c2d38d852db 100755 --- a/cereal/services.py +++ b/cereal/services.py @@ -92,6 +92,7 @@ def __init__(self, should_log: bool, frequency: float, decimation: Optional[int] "livestreamWideRoadEncodeData": (False, 20., None, QueueSize.MEDIUM), "livestreamRoadEncodeData": (False, 20., None, QueueSize.MEDIUM), "livestreamDriverEncodeData": (False, 20., None, QueueSize.MEDIUM), + "customReservedRawData0": (True, 0.), } SERVICE_LIST = {name: Service(*vals) for idx, (name, vals) in enumerate(_services.items())} From 0eacf34e15cd65146e1122006bd77b7f669ef9d1 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Apr 2026 16:49:29 -0700 Subject: [PATCH 676/685] sensord: add note about shared IRQ --- system/sensord/sensord.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/system/sensord/sensord.py b/system/sensord/sensord.py index d17d296958b..ce6fb9ccb24 100755 --- a/system/sensord/sensord.py +++ b/system/sensord/sensord.py @@ -22,6 +22,10 @@ def interrupt_loop(sensors: list[tuple[Sensor, str, bool]], event) -> None: pm = messaging.PubMaster([service for sensor, service, interrupt in sensors if interrupt]) + # NOTE: the gyro and accelerometer share an IRQ due to the comma three + # routing only one GPIO from the LSM to the SOC, but comma 3X and four + # have two. if we want better timestamps in the future, we can use both. + # Requesting both edges as the data ready pulse from the lsm6ds sensor is # very short (75us) and is mostly detected as falling edge instead of rising. # So if it is detected as rising the following falling edge is skipped. From 49685fc2bcb4c57ed1c81c532a921542e5b089c4 Mon Sep 17 00:00:00 2001 From: Daniel Koepping Date: Wed, 22 Apr 2026 17:59:34 -0700 Subject: [PATCH 677/685] ui: fix long maneuver toggle (#37622) --- selfdrive/ui/layouts/settings/developer.py | 6 ------ selfdrive/ui/mici/layouts/settings/developer.py | 6 ------ 2 files changed, 12 deletions(-) diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py index c61a4068586..2a823b57d71 100644 --- a/selfdrive/ui/layouts/settings/developer.py +++ b/selfdrive/ui/layouts/settings/developer.py @@ -131,12 +131,6 @@ def _update_toggles(self): long_man_enabled = ui_state.has_longitudinal_control and ui_state.is_offroad() self._long_maneuver_toggle.action_item.set_enabled(long_man_enabled) - if not long_man_enabled: - self._long_maneuver_toggle.action_item.set_state(False) - self._params.put_bool("LongitudinalManeuverMode", False) - - lat_man_enabled = ui_state.is_offroad() - self._lat_maneuver_toggle.action_item.set_enabled(lat_man_enabled) else: self._long_maneuver_toggle.action_item.set_enabled(False) self._lat_maneuver_toggle.action_item.set_enabled(False) diff --git a/selfdrive/ui/mici/layouts/settings/developer.py b/selfdrive/ui/mici/layouts/settings/developer.py index f47614c073e..3a587f12421 100644 --- a/selfdrive/ui/mici/layouts/settings/developer.py +++ b/selfdrive/ui/mici/layouts/settings/developer.py @@ -131,12 +131,6 @@ def _update_toggles(self): long_man_enabled = ui_state.has_longitudinal_control and ui_state.is_offroad() self._long_maneuver_toggle.set_enabled(long_man_enabled) - if not long_man_enabled: - self._long_maneuver_toggle.set_checked(False) - ui_state.params.put_bool("LongitudinalManeuverMode", False) - - lat_man_enabled = ui_state.is_offroad() - self._lat_maneuver_toggle.set_enabled(lat_man_enabled) else: self._long_maneuver_toggle.set_enabled(False) self._lat_maneuver_toggle.set_enabled(False) From 7d71354fd02ad6a3eea941ad10f73e666a9ac3b2 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Wed, 22 Apr 2026 19:57:59 -0700 Subject: [PATCH 678/685] ui: remove firehose count (#37886) --- selfdrive/ui/layouts/settings/firehose.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/selfdrive/ui/layouts/settings/firehose.py b/selfdrive/ui/layouts/settings/firehose.py index ea83e962e61..f28f4e3386b 100644 --- a/selfdrive/ui/layouts/settings/firehose.py +++ b/selfdrive/ui/layouts/settings/firehose.py @@ -1,7 +1,7 @@ import pyray as rl from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE -from openpilot.system.ui.lib.multilang import tr, trn, tr_noop +from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.lib.scroll_panel import GuiScrollPanel from openpilot.system.ui.lib.wrap_text import wrap_text @@ -65,12 +65,13 @@ def _render_content(self, rect: rl.Rectangle, scroll_offset: float) -> int: y = self._draw_wrapped_text(x, y, w, status_text, gui_app.font(FontWeight.BOLD), 60, status_color) y += 20 + 20 + # TODO: add back once reliable # Contribution count (if available) - if self._segment_count > 0: - contrib_text = trn("{} segment of your driving is in the training dataset so far.", - "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) - y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE) - y += 20 + 20 + #if self._segment_count > 0: + # contrib_text = trn("{} segment of your driving is in the training dataset so far.", + # "{} segments of your driving is in the training dataset so far.", self._segment_count).format(self._segment_count) + # y = self._draw_wrapped_text(x, y, w, contrib_text, gui_app.font(FontWeight.BOLD), 52, rl.WHITE) + # y += 20 + 20 # Separator rl.draw_rectangle(x, y, w, 2, self.GRAY) From bb4b96e05de9d7fe52d00221ea58f4a91d8d33f4 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Apr 2026 10:20:09 -0700 Subject: [PATCH 679/685] qcomgpsd: rm XTRA assistance (#37893) * qcomgpsd: rm XTRA assistance * lil more * lil more --- system/qcomgpsd/qcomgpsd.py | 82 +------------------------- system/qcomgpsd/tests/test_qcomgpsd.py | 74 +---------------------- 2 files changed, 5 insertions(+), 151 deletions(-) diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 47d64ff4e9f..e7a8d55d6c5 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -5,11 +5,8 @@ import itertools import math import time -import requests -import shutil from serial import Serial import datetime -from multiprocessing import Process, Event from typing import NoReturn from struct import unpack_from, calcsize, pack @@ -30,9 +27,6 @@ LOG_GNSS_OEMDRE_SVPOLY_REPORT) DEBUG = int(os.getenv("DEBUG", "0"))==1 -ASSIST_DATA_FILE = '/tmp/xtra3grc.bin' -ASSIST_DATA_FILE_DOWNLOAD = ASSIST_DATA_FILE + '.download' -ASSISTANCE_URL = 'http://xtrapath3.izatcloud.net/xtra3grc.bin' LOG_TYPES = [ LOG_GNSS_GPS_MEASUREMENT_REPORT, @@ -112,49 +106,8 @@ def at_cmd(cmd: str) -> str: def gps_enabled() -> bool: return "QGPS: 1" in at_cmd("AT+QGPS?") -def download_assistance(): - try: - response = requests.get(ASSISTANCE_URL, timeout=5, stream=True) - - with open(ASSIST_DATA_FILE_DOWNLOAD, 'wb') as fp: - for chunk in response.iter_content(chunk_size=8192): - fp.write(chunk) - if fp.tell() > 1e5: - cloudlog.error("Qcom assistance data larger than expected") - return - - os.rename(ASSIST_DATA_FILE_DOWNLOAD, ASSIST_DATA_FILE) - - except requests.exceptions.RequestException: - cloudlog.exception("Failed to download assistance file") - return - -def downloader_loop(event): - if os.path.exists(ASSIST_DATA_FILE): - os.remove(ASSIST_DATA_FILE) - - alt_path = os.getenv("QCOM_ALT_ASSISTANCE_PATH", None) - if alt_path is not None and os.path.exists(alt_path): - shutil.copyfile(alt_path, ASSIST_DATA_FILE) - - try: - while not os.path.exists(ASSIST_DATA_FILE) and not event.is_set(): - download_assistance() - event.wait(timeout=10) - except KeyboardInterrupt: - pass - -@retry(attempts=5, delay=0.2, ignore_failure=True) -def inject_assistance(): - import subprocess - cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}" - subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) - cloudlog.info("successfully loaded assistance data") - @retry(attempts=5, delay=1.0) -def setup_quectel(diag: ModemDiag) -> bool: - ret = False - +def setup_quectel(diag: ModemDiag): # enable OEMDRE in the NV # TODO: it has to reboot for this to take effect DIAG_NV_READ_F = 38 @@ -168,26 +121,11 @@ def setup_quectel(diag: ModemDiag) -> bool: if gps_enabled(): at_cmd("AT+QGPSEND") - if "GPS_COLD_START" in os.environ: - # deletes all assistance - at_cmd("AT+QGPSDEL=0") - else: - # allow module to perform hot start - at_cmd("AT+QGPSDEL=1") - # disable DPO power savings for more accuracy at_cmd("AT+QGPSCFG=\"dpoenable\",0") # don't automatically turn on GNSS on powerup at_cmd("AT+QGPSCFG=\"autogps\",0") - # Do internet assistance - at_cmd("AT+QGPSXTRA=1") - at_cmd("AT+QGPSSUPLURL=\"NULL\"") - if os.path.exists(ASSIST_DATA_FILE): - ret = True - inject_assistance() - os.remove(ASSIST_DATA_FILE) - #at_cmd("AT+QGPSXTRADATA?") if system_time_valid(): time_str = datetime.datetime.now(datetime.UTC).replace(tzinfo=None).strftime("%Y/%m/%d,%H:%M:%S") at_cmd(f"AT+QGPSXTRATIME=0,\"{time_str}\",1,1,1000") @@ -214,7 +152,6 @@ def setup_quectel(diag: ModemDiag) -> bool: 0,0 )) - return ret def teardown_quectel(diag): at_cmd("AT+QGPSCFG=\"outport\",\"none\"") @@ -255,9 +192,6 @@ def main() -> NoReturn: wait_for_modem() - stop_download_event = Event() - assist_fetch_proc = Process(target=downloader_loop, args=(stop_download_event,)) - assist_fetch_proc.start() def cleanup(sig, frame): cloudlog.warning("caught sig disabling quectel gps") @@ -268,18 +202,13 @@ def cleanup(sig, frame): except NameError: cloudlog.warning('quectel not yet setup') - stop_download_event.set() - assist_fetch_proc.kill() - assist_fetch_proc.join() - sys.exit(0) signal.signal(signal.SIGINT, cleanup) signal.signal(signal.SIGTERM, cleanup) # connect to modem diag = ModemDiag() - r = setup_quectel(diag) - want_assistance = not r + setup_quectel(diag) cloudlog.warning("quectel setup done") gpio_init(GPIO.GNSS_PWR_EN, True) gpio_set(GPIO.GNSS_PWR_EN, True) @@ -287,10 +216,6 @@ def cleanup(sig, frame): pm = messaging.PubMaster(['qcomGnss', 'gpsLocation']) while 1: - if os.path.exists(ASSIST_DATA_FILE) and want_assistance: - setup_quectel(diag) - want_assistance = False - opcode, payload = diag.recv() if opcode != DIAG_LOG_F: cloudlog.error(f"Unhandled opcode: {opcode}") @@ -383,9 +308,6 @@ def cleanup(sig, frame): gps.speedAccuracy = math.sqrt(sum([x**2 for x in vNEDsigma])) # quectel gps verticalAccuracy is clipped to 500, set invalid if so gps.hasFix = gps.verticalAccuracy != 500 - if gps.hasFix: - want_assistance = False - stop_download_event.set() pm.send('gpsLocation', msg) elif log_type == LOG_GNSS_OEMDRE_SVPOLY_REPORT: diff --git a/system/qcomgpsd/tests/test_qcomgpsd.py b/system/qcomgpsd/tests/test_qcomgpsd.py index 9b0644734e7..f697811ae9b 100644 --- a/system/qcomgpsd/tests/test_qcomgpsd.py +++ b/system/qcomgpsd/tests/test_qcomgpsd.py @@ -1,28 +1,22 @@ import os import pytest import time -import datetime import cereal.messaging as messaging from openpilot.system.qcomgpsd.qcomgpsd import at_cmd, wait_for_modem from openpilot.system.manager.process_config import managed_processes -GOOD_SIGNAL = bool(int(os.getenv("GOOD_SIGNAL", '0'))) - @pytest.mark.tici -class TestRawgpsd: +class TestQcomgpsd: @classmethod def setup_class(cls): - os.environ['GPS_COLD_START'] = '1' - os.system("sudo systemctl start systemd-resolved") os.system("sudo systemctl restart ModemManager lte") wait_for_modem() @classmethod def teardown_class(cls): managed_processes['qcomgpsd'].stop() - os.system("sudo systemctl restart systemd-resolved") os.system("sudo systemctl restart ModemManager lte") def setup_method(self): @@ -30,7 +24,6 @@ def setup_method(self): def teardown_method(self): managed_processes['qcomgpsd'].stop() - os.system("sudo systemctl restart systemd-resolved") def _wait_for_output(self, t): dt = 0.1 @@ -41,24 +34,10 @@ def _wait_for_output(self, t): time.sleep(dt) return self.sm.updated['qcomGnss'] - def test_no_crash_double_command(self): - wait_for_modem() - at_cmd("AT+QGPSDEL=0") - at_cmd("AT+QGPSDEL=0") - - def test_wait_for_modem(self): - os.system("sudo systemctl stop ModemManager") + def test_startup_time(self): managed_processes['qcomgpsd'].start() assert self._wait_for_output(30) - - def test_startup_time(self, subtests): - for internet in (True, False): - if not internet: - os.system("sudo systemctl stop systemd-resolved") - with subtests.test(internet=internet): - managed_processes['qcomgpsd'].start() - assert self._wait_for_output(30) - managed_processes['qcomgpsd'].stop() + managed_processes['qcomgpsd'].stop() def test_turns_off_gnss(self, subtests): for s in (0.1, 1, 5): @@ -70,50 +49,3 @@ def test_turns_off_gnss(self, subtests): wait_for_modem() resp = at_cmd("AT+QGPS?") assert "+QGPS: 0" in resp - - - def check_assistance(self, should_be_loaded): - # after QGPSDEL: '+QGPSXTRADATA: 0,"1980/01/05,19:00:00"' - # after loading: '+QGPSXTRADATA: 10080,"2023/06/24,19:00:00"' - wait_for_modem() - out = at_cmd("AT+QGPSXTRADATA?") - out = out.split("+QGPSXTRADATA:")[1].split("'")[0].strip() - valid_duration, injected_time_str = out.split(",", 1) - if should_be_loaded: - assert valid_duration == "10080" # should be max time - injected_time = datetime.datetime.strptime(injected_time_str.replace("\"", ""), "%Y/%m/%d,%H:%M:%S") - assert abs((datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - injected_time).total_seconds()) < 60*60*12 - else: - valid_duration, injected_time_str = out.split(",", 1) - injected_time_str = injected_time_str.replace('\"', '').replace('\'', '') - assert injected_time_str[:] == '1980/01/05,19:00:00'[:] - assert valid_duration == '0' - - @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") - def test_assistance_loading(self): - managed_processes['qcomgpsd'].start() - assert self._wait_for_output(30) - managed_processes['qcomgpsd'].stop() - self.check_assistance(True) - - @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") - def test_no_assistance_loading(self): - os.system("sudo systemctl stop systemd-resolved") - - managed_processes['qcomgpsd'].start() - assert self._wait_for_output(30) - managed_processes['qcomgpsd'].stop() - self.check_assistance(False) - - @pytest.mark.skip(reason="XTRA injection via QMI needs debugging on AGNOS 17") - def test_late_assistance_loading(self): - os.system("sudo systemctl stop systemd-resolved") - - managed_processes['qcomgpsd'].start() - self._wait_for_output(17) - assert self.sm.updated['qcomGnss'] - - os.system("sudo systemctl restart systemd-resolved") - time.sleep(15) - managed_processes['qcomgpsd'].stop() - self.check_assistance(True) From ad04c6a038dd407f36b133158315077f549a4055 Mon Sep 17 00:00:00 2001 From: Andi Radulescu Date: Thu, 23 Apr 2026 21:52:12 +0300 Subject: [PATCH 680/685] cruise: fix test_cruise_speed assertion (#37802) --- selfdrive/car/tests/test_cruise_speed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selfdrive/car/tests/test_cruise_speed.py b/selfdrive/car/tests/test_cruise_speed.py index 05fef93b4ef..bfb060874d1 100644 --- a/selfdrive/car/tests/test_cruise_speed.py +++ b/selfdrive/car/tests/test_cruise_speed.py @@ -93,7 +93,7 @@ def test_rising_edge_enable(self): self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False) # Expected diff on enabling. Speed should not change on falling edge of pressed - assert not pressed == self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last + assert (not pressed) == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) def test_resume_in_standstill(self): """ From 551e2f77bfd616e55511b3ad283b5859cdddf7e4 Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Thu, 23 Apr 2026 11:55:07 -0700 Subject: [PATCH 681/685] modeld: standalone compile script (#37851) * modeld: standalone compile script * cleanup * frame skip * rm last op import * dm warp * no graph break * +x compile_dm_warp.py * don't import tg before setting device * compile_modeld exports metadata * update help * namedtuple * lint * Revert "compile_modeld exports metadata" This reverts commit 93c3c223567b4d4a074c9071d7f734c56f5aedcc. * import --- selfdrive/modeld/SConscript | 49 +++++++--- selfdrive/modeld/compile_dm_warp.py | 54 +++++++++++ selfdrive/modeld/compile_modeld.py | 129 +++++++++---------------- selfdrive/modeld/dmonitoringmodeld.py | 2 +- selfdrive/modeld/get_model_metadata.py | 6 +- selfdrive/modeld/helpers.py | 31 ++++++ selfdrive/modeld/modeld.py | 4 +- selfdrive/modeld/tinygrad_helpers.py | 12 --- 8 files changed, 173 insertions(+), 114 deletions(-) create mode 100755 selfdrive/modeld/compile_dm_warp.py create mode 100644 selfdrive/modeld/helpers.py delete mode 100644 selfdrive/modeld/tinygrad_helpers.py diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 053eb157b6d..76fef75bae0 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -1,10 +1,23 @@ import glob import json import os +from itertools import product from SCons.Script import Value from openpilot.common.file_chunker import chunk_file, get_chunk_paths +from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye +from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE +from openpilot.selfdrive.modeld.constants import ModelConstants +from openpilot.selfdrive.modeld.helpers import CompileConfig from tinygrad import Device +CAMERA_CONFIGS = [ + (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 + (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 +] +MODELD_CONFIGS = [CompileConfig(cam_w, cam_h, prepare_only, 'driving_') + for (cam_w, cam_h), prepare_only in product(CAMERA_CONFIGS, [True, False])] +DM_WARP_CONFIGS = [CompileConfig(cam_w, cam_h, True, 'dm_') for cam_w, cam_h in CAMERA_CONFIGS] + Import('env', 'arch') chunker_file = File("#common/file_chunker.py") lenv = env.Clone() @@ -53,23 +66,35 @@ for model_name in ['driving_vision', 'driving_policy', 'dmonitoring_model']: image_flag = { 'larch64': 'IMAGE=2', }.get(arch, 'IMAGE=0') -script_files = [File(Dir("#selfdrive/modeld").File("compile_modeld.py").abspath)] -compile_modeld_cmd = f'{tg_flags} {mac_brew_string} {image_flag} python3 {Dir("#selfdrive/modeld").abspath}/compile_modeld.py ' +modeld_dir = Dir("#selfdrive/modeld").abspath +compile_modeld_script = [File(f"{modeld_dir}/compile_modeld.py")] +compile_dm_warp_script = [File(f"{modeld_dir}/compile_dm_warp.py")] driving_onnx_deps = [File(f"models/{m}.onnx").abspath for m in ['driving_vision', 'driving_policy']] driving_metadata_deps = [File(f"models/{m}_metadata.pkl").abspath for m in ['driving_vision', 'driving_policy']] -from openpilot.selfdrive.modeld.compile_modeld import MODELD_CONFIGS, DM_WARP_CONFIGS -policy_pkls = [File(cfg.pkl_path).abspath for cfg in MODELD_CONFIGS] -modeld_targets = policy_pkls + [File(cfg.pkl_path).abspath for cfg in DM_WARP_CONFIGS] -compile_node = lenv.Command(modeld_targets, tinygrad_files + script_files + driving_onnx_deps + driving_metadata_deps + [chunker_file, compiled_flags_node], compile_modeld_cmd) - -# chunk the combined policy pkls -for policy_pkl in policy_pkls: +model_w, model_h = MEDMODEL_INPUT_SIZE +frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ +for cfg in MODELD_CONFIGS: + cmd = (f'{tg_flags} {mac_brew_string} {image_flag} python3 {modeld_dir}/compile_modeld.py ' + f'--model-size {model_w}x{model_h} ' + f'--nv12 {",".join(str(x) for x in cfg.nv12)} ' + f'--vision-onnx {File("models/driving_vision.onnx").abspath} ' + f'--policy-onnx {File("models/driving_policy.onnx").abspath} ' + f'--output {cfg.pkl_path} --frame-skip {frame_skip}' + + (' --prepare-only' if cfg.prepare_only else '')) + node = lenv.Command(cfg.pkl_path, tinygrad_files + compile_modeld_script + driving_onnx_deps + driving_metadata_deps + [chunker_file, compiled_flags_node], cmd) onnx_sizes_sum = sum(os.path.getsize(f) for f in driving_onnx_deps) - chunk_targets = get_chunk_paths(policy_pkl, estimate_pickle_max_size(onnx_sizes_sum)) - def do_chunk(target, source, env, pkl=policy_pkl, chunks=chunk_targets): + chunk_targets = get_chunk_paths(cfg.pkl_path, estimate_pickle_max_size(onnx_sizes_sum)) + def do_chunk(target, source, env, pkl=cfg.pkl_path, chunks=chunk_targets): chunk_file(pkl, chunks) - lenv.Command(chunk_targets, compile_node, do_chunk) + lenv.Command(chunk_targets, node, do_chunk) + +dm_w, dm_h = DM_INPUT_SIZE +for cfg in DM_WARP_CONFIGS: + cmd = (f'{tg_flags} {mac_brew_string} {image_flag} python3 {modeld_dir}/compile_dm_warp.py ' + f'--nv12 {",".join(str(x) for x in cfg.nv12)} --warp-to {dm_w}x{dm_h} ' + f'--output {cfg.pkl_path}') + lenv.Command(cfg.pkl_path, tinygrad_files + compile_dm_warp_script + compile_modeld_script + [compiled_flags_node], cmd) def tg_compile(flags, model_name): pythonpath_string = 'PYTHONPATH="${PYTHONPATH}:' + env.Dir("#tinygrad_repo").abspath + '"' diff --git a/selfdrive/modeld/compile_dm_warp.py b/selfdrive/modeld/compile_dm_warp.py new file mode 100755 index 00000000000..2713cccf413 --- /dev/null +++ b/selfdrive/modeld/compile_dm_warp.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +import argparse +import pickle +import time + +from tinygrad.tensor import Tensor +from tinygrad.device import Device +from tinygrad.engine.jit import TinyJit + +from openpilot.selfdrive.modeld.compile_modeld import NV12Frame, warp_perspective_tinygrad, _parse_size, _parse_nv12 + + +def make_warp_dm(nv12: NV12Frame, dm_w, dm_h): + cam_w, cam_h, stride, _, _, _ = nv12 + stride_pad = stride - cam_w + + def warp_dm(input_frame, M_inv): + M_inv = M_inv.to(Device.DEFAULT).realize() + return warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, + (dm_w, dm_h), (cam_h, cam_w), stride_pad).reshape(-1, dm_h * dm_w) + return warp_dm + + +def compile_dm_warp(nv12: NV12Frame, dm_w, dm_h, pkl_path): + print(f"Compiling DM warp for {nv12.width}x{nv12.height} -> {dm_w}x{dm_h}...") + + warp_dm_jit = TinyJit(make_warp_dm(nv12, dm_w, dm_h), prune=True) + + for i in range(10): + frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize() + M_inv = Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY') + Device.default.synchronize() + st = time.perf_counter() + warp_dm_jit(frame, M_inv).realize() + mt = time.perf_counter() + Device.default.synchronize() + et = time.perf_counter() + print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") + + with open(pkl_path, "wb") as f: + pickle.dump(warp_dm_jit, f) + print(f" Saved to {pkl_path}") + + +if __name__ == "__main__": + p = argparse.ArgumentParser() + p.add_argument('--nv12', type=_parse_nv12, required=True, + help=f'NV12 frame layout: {",".join(NV12Frame._fields)}') + p.add_argument('--warp-to', type=_parse_size, required=True, help='DM input WxH') + p.add_argument('--output', required=True) + args = p.parse_args() + + dm_w, dm_h = args.warp_to + compile_dm_warp(args.nv12, dm_w, dm_h, args.output) diff --git a/selfdrive/modeld/compile_modeld.py b/selfdrive/modeld/compile_modeld.py index c11f899b7f1..1e05ae30a48 100755 --- a/selfdrive/modeld/compile_modeld.py +++ b/selfdrive/modeld/compile_modeld.py @@ -1,20 +1,16 @@ #!/usr/bin/env python3 -import time +import argparse import pickle -from dataclasses import dataclass -from itertools import product +import time from functools import partial +from collections import namedtuple import numpy as np from tinygrad.tensor import Tensor from tinygrad.helpers import Context from tinygrad.device import Device from tinygrad.engine.jit import TinyJit - -from openpilot.selfdrive.modeld.tinygrad_helpers import MODELS_DIR -from openpilot.system.camerad.cameras.nv12_info import get_nv12_info -from openpilot.common.transformations.model import MEDMODEL_INPUT_SIZE, DM_INPUT_SIZE -from openpilot.common.transformations.camera import _ar_ox_fisheye, _os_fisheye +from tinygrad.nn.onnx import OnnxRunner # https://github.com/tinygrad/tinygrad/issues/15682 from tinygrad.uop.ops import UOp, Ops @@ -22,24 +18,7 @@ UOp.__reduce__ = lambda self: (UOp.unique, ()) if self.op is Ops.UNIQUE else _orig(self) -@dataclass -class CompileConfig: - cam_w: int - cam_h: int - prepare_only: bool - prefix: str - - @property - def pkl_path(self): - return str(MODELS_DIR / f'{self.prefix}{"warp_" if self.prepare_only else ""}{self.cam_w}x{self.cam_h}_tinygrad.pkl') - -CAMERA_CONFIGS = [ - (_ar_ox_fisheye.width, _ar_ox_fisheye.height), # tici: 1928x1208 - (_os_fisheye.width, _os_fisheye.height), # mici: 1344x760 -] -MODELD_CONFIGS = [CompileConfig(cam_w, cam_h, prepare_only, 'driving_') for (cam_w, cam_h), prepare_only in product(CAMERA_CONFIGS, [True, False])] -DM_WARP_CONFIGS = [CompileConfig(cam_w, cam_h, True, 'dm_') for cam_w, cam_h in CAMERA_CONFIGS] - +NV12Frame = namedtuple("NV12Frame", ['width', 'height', 'stride', 'y_height', 'uv_height', 'size']) UV_SCALE_MATRIX = np.array([[0.5, 0, 0], [0, 0.5, 0], [0, 0, 1]], dtype=np.float32) UV_SCALE_MATRIX_INV = np.linalg.inv(UV_SCALE_MATRIX) @@ -79,8 +58,8 @@ def frames_to_tensor(frames): return in_img1 -def make_frame_prepare(cam_w, cam_h, model_w, model_h): - stride, y_height, uv_height, _ = get_nv12_info(cam_w, cam_h) +def make_frame_prepare(nv12: NV12Frame, model_w, model_h): + cam_w, cam_h, stride, y_height, uv_height, _ = nv12 uv_offset = stride * y_height stride_pad = stride - cam_w @@ -143,21 +122,9 @@ def sample_desire(buf, frame_skip): return buf.reshape(-1, frame_skip, *buf.shape[1:]).max(1).flatten(0, 1).unsqueeze(0) -def make_warp_dm(cam_w, cam_h, dm_w, dm_h): - stride, y_height, _, _ = get_nv12_info(cam_w, cam_h) - stride_pad = stride - cam_w - - def warp_dm(input_frame, M_inv): - M_inv = M_inv.to(Device.DEFAULT).realize() - result = warp_perspective_tinygrad(input_frame[:cam_h*stride], M_inv, (dm_w, dm_h), (cam_h, cam_w), stride_pad).reshape(-1, dm_h * dm_w) - return result - return warp_dm - - -def make_run_policy(vision_runner, policy_runner, cam_w, cam_h, +def make_run_policy(vision_runner, policy_runner, nv12: NV12Frame, model_w, model_h, vision_features_slice, frame_skip, prepare_only=False): - model_w, model_h = MEDMODEL_INPUT_SIZE - frame_prepare = make_frame_prepare(cam_w, cam_h, model_w, model_h) + frame_prepare = make_frame_prepare(nv12, model_w, model_h) sample_skip_fn = partial(sample_skip, frame_skip=frame_skip) sample_desire_fn = partial(sample_desire, frame_skip=frame_skip) @@ -187,27 +154,24 @@ def run_policy(img_q, big_img_q, feat_q, desire_q, desire, traffic_convention, t return run_policy -def compile_modeld(cam_w, cam_h, prepare_only, pkl_path): - from tinygrad.nn.onnx import OnnxRunner - from openpilot.selfdrive.modeld.constants import ModelConstants +def compile_modeld(nv12: NV12Frame, model_w, model_h, prepare_only, frame_skip, + vision_onnx, policy_onnx, pkl_path): + from get_model_metadata import metadata_path_for - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - print(f"Compiling combined policy JIT for {cam_w}x{cam_h}...") + print(f"Compiling combined policy JIT for {nv12.width}x{nv12.height} (prepare_only={prepare_only})...") - vision_runner = OnnxRunner(MODELS_DIR / 'driving_vision.onnx') - policy_runner = OnnxRunner(MODELS_DIR / 'driving_policy.onnx') + vision_runner = OnnxRunner(vision_onnx) + policy_runner = OnnxRunner(policy_onnx) - with open(MODELS_DIR / 'driving_vision_metadata.pkl', 'rb') as f: + with open(metadata_path_for(vision_onnx), 'rb') as f: vision_metadata = pickle.load(f) vision_features_slice = vision_metadata['output_slices']['hidden_state'] vision_input_shapes = vision_metadata['input_shapes'] - with open(MODELS_DIR / 'driving_policy_metadata.pkl', 'rb') as f: + with open(metadata_path_for(policy_onnx), 'rb') as f: policy_input_shapes = pickle.load(f)['input_shapes'] - frame_skip = ModelConstants.MODEL_RUN_FREQ // ModelConstants.MODEL_CONTEXT_FREQ - - _run = make_run_policy(vision_runner, policy_runner, - cam_w, cam_h, vision_features_slice, frame_skip, prepare_only) + _run = make_run_policy(vision_runner, policy_runner, nv12, model_w, model_h, + vision_features_slice, frame_skip, prepare_only) run_policy_jit = TinyJit(_run, prune=True) N_RUNS = 3 @@ -219,8 +183,8 @@ def random_inputs_run_fn(fn, seed, test_val=None, test_buffers=None, expect_matc Tensor.manual_seed(seed) for i in range(N_RUNS): - frame = Tensor.randint(yuv_size, low=0, high=256, dtype='uint8').realize() - big_frame = Tensor.randint(yuv_size, low=0, high=256, dtype='uint8').realize() + frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize() + big_frame = Tensor.randint(nv12.size, low=0, high=256, dtype='uint8').realize() for v in npy.values(): v[:] = np.random.randn(*v.shape).astype(v.dtype) Device.default.synchronize() @@ -260,37 +224,30 @@ def random_inputs_run_fn(fn, seed, test_val=None, test_buffers=None, expect_matc random_inputs_run_fn(run_policy_jit, SEED+1, test_val, test_buffers, expect_match=False) -def compile_dm_warp(cam_w, cam_h, pkl_path): - dm_w, dm_h = DM_INPUT_SIZE - _, _, _, yuv_size = get_nv12_info(cam_w, cam_h) - - print(f"Compiling DM warp for {cam_w}x{cam_h}...") - - warp_dm = make_warp_dm(cam_w, cam_h, dm_w, dm_h) - warp_dm_jit = TinyJit(warp_dm, prune=True) - - for i in range(10): - inputs = [Tensor.randint(yuv_size, low=0, high=256, dtype='uint8').realize(), - Tensor(Tensor.randn(3, 3).mul(8).realize().numpy(), device='NPY')] - Device.default.synchronize() - st = time.perf_counter() - warp_dm_jit(*inputs) - mt = time.perf_counter() - Device.default.synchronize() - et = time.perf_counter() - print(f" [{i+1}/10] enqueue {(mt-st)*1e3:6.2f} ms -- total {(et-st)*1e3:6.2f} ms") - - with open(pkl_path, "wb") as f: - pickle.dump(warp_dm_jit, f) - print(f" Saved to {pkl_path}") +def _parse_size(s): + w, h = s.lower().split('x') + return int(w), int(h) -def run_and_save_pickle(): - for cfg in MODELD_CONFIGS: - compile_modeld(cfg.cam_w, cfg.cam_h, cfg.prepare_only, cfg.pkl_path) - for cfg in DM_WARP_CONFIGS: - compile_dm_warp(cfg.cam_w, cfg.cam_h, cfg.pkl_path) +def _parse_nv12(s): + parts = s.split(',') + assert len(parts) == len(NV12Frame._fields), \ + f"--nv12 expects {','.join(NV12Frame._fields)} (got {s!r})" + return NV12Frame(*(int(x) for x in parts)) if __name__ == "__main__": - run_and_save_pickle() + p = argparse.ArgumentParser() + p.add_argument('--model-size', type=_parse_size, required=True, help='model input WxH') + p.add_argument('--nv12', type=_parse_nv12, required=True, + help=f'NV12 frame layout: {",".join(NV12Frame._fields)}') + p.add_argument('--vision-onnx', required=True) + p.add_argument('--policy-onnx', required=True) + p.add_argument('--output', required=True) + p.add_argument('--prepare-only', action='store_true') + p.add_argument('--frame-skip', type=int, required=True) + args = p.parse_args() + + model_w, model_h = args.model_size + compile_modeld(args.nv12, model_w, model_h, args.prepare_only, args.frame_skip, + args.vision_onnx, args.policy_onnx, args.output) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index d5901e89439..9aaf6aa9e08 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import os -from openpilot.selfdrive.modeld.tinygrad_helpers import MODELS_DIR, set_tinygrad_backend_from_compiled_flags +from openpilot.selfdrive.modeld.helpers import MODELS_DIR, set_tinygrad_backend_from_compiled_flags set_tinygrad_backend_from_compiled_flags() from tinygrad.tensor import Tensor diff --git a/selfdrive/modeld/get_model_metadata.py b/selfdrive/modeld/get_model_metadata.py index 838b1e9f404..ee08e9fb145 100755 --- a/selfdrive/modeld/get_model_metadata.py +++ b/selfdrive/modeld/get_model_metadata.py @@ -7,6 +7,10 @@ from tinygrad.nn.onnx import OnnxPBParser +def metadata_path_for(onnx_path) -> pathlib.Path: + p = pathlib.Path(onnx_path) + return p.parent / (p.stem + '_metadata.pkl') + class MetadataOnnxPBParser(OnnxPBParser): def _parse_ModelProto(self) -> dict: @@ -48,7 +52,7 @@ def get_metadata_value_by_name(model: dict[str, Any], name: str) -> str | Any: 'output_shapes': dict(get_name_and_shape(x) for x in model["graph"]["output"]), } - metadata_path = model_path.parent / (model_path.stem + '_metadata.pkl') + metadata_path = metadata_path_for(model_path) with open(metadata_path, 'wb') as f: pickle.dump(metadata, f) diff --git a/selfdrive/modeld/helpers.py b/selfdrive/modeld/helpers.py new file mode 100644 index 00000000000..e5d731f34ba --- /dev/null +++ b/selfdrive/modeld/helpers.py @@ -0,0 +1,31 @@ +import json +import os +from dataclasses import dataclass +from pathlib import Path + +from openpilot.system.camerad.cameras.nv12_info import get_nv12_info + +MODELS_DIR = Path(__file__).resolve().parent / 'models' +COMPILED_FLAGS_PATH = MODELS_DIR / 'tg_compiled_flags.json' + + +def set_tinygrad_backend_from_compiled_flags() -> None: + if os.path.isfile(COMPILED_FLAGS_PATH): + with open(COMPILED_FLAGS_PATH) as f: + os.environ['DEV'] = str(json.load(f)['DEV']) + + +@dataclass +class CompileConfig: + cam_w: int + cam_h: int + prepare_only: bool + prefix: str + + @property + def pkl_path(self): + return str(MODELS_DIR / f'{self.prefix}{"warp_" if self.prepare_only else ""}{self.cam_w}x{self.cam_h}_tinygrad.pkl') + + @property + def nv12(self): + return (self.cam_w, self.cam_h, *get_nv12_info(self.cam_w, self.cam_h)) diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 7a8a442981a..73ed19ec943 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import os -from openpilot.selfdrive.modeld.tinygrad_helpers import MODELS_DIR, set_tinygrad_backend_from_compiled_flags +from openpilot.selfdrive.modeld.helpers import MODELS_DIR, CompileConfig, set_tinygrad_backend_from_compiled_flags set_tinygrad_backend_from_compiled_flags() USBGPU = "USBGPU" in os.environ @@ -26,7 +26,7 @@ from openpilot.selfdrive.controls.lib.desire_helper import DesireHelper from openpilot.selfdrive.controls.lib.drive_helpers import get_accel_from_plan, smooth_value, get_curvature_from_plan from openpilot.selfdrive.modeld.parse_model_outputs import Parser -from openpilot.selfdrive.modeld.compile_modeld import CompileConfig, make_input_queues +from openpilot.selfdrive.modeld.compile_modeld import make_input_queues from openpilot.selfdrive.modeld.fill_model_msg import fill_model_msg, fill_pose_msg, PublishState from openpilot.common.file_chunker import read_file_chunked from openpilot.selfdrive.modeld.constants import ModelConstants, Plan diff --git a/selfdrive/modeld/tinygrad_helpers.py b/selfdrive/modeld/tinygrad_helpers.py deleted file mode 100644 index 9c1a0d4689f..00000000000 --- a/selfdrive/modeld/tinygrad_helpers.py +++ /dev/null @@ -1,12 +0,0 @@ -import json -import os -from pathlib import Path - -MODELS_DIR = Path(__file__).resolve().parent / 'models' -COMPILED_FLAGS_PATH = MODELS_DIR / 'tg_compiled_flags.json' - - -def set_tinygrad_backend_from_compiled_flags() -> None: - if os.path.isfile(COMPILED_FLAGS_PATH): - with open(COMPILED_FLAGS_PATH) as f: - os.environ['DEV'] = str(json.load(f)['DEV']) From 1c69770c5376bb72966f19e89e680f1916503ec8 Mon Sep 17 00:00:00 2001 From: Adeeb Shihadeh Date: Thu, 23 Apr 2026 13:15:08 -0700 Subject: [PATCH 682/685] tools/setup: support all common Linux distros (#37765) * shorter ubuntu * tools/setup: support all common Linux distros --- tools/setup_dependencies.sh | 92 ++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 52 deletions(-) diff --git a/tools/setup_dependencies.sh b/tools/setup_dependencies.sh index 8132cd16dc1..73cee5569b7 100755 --- a/tools/setup_dependencies.sh +++ b/tools/setup_dependencies.sh @@ -19,7 +19,7 @@ function retry() { return 1 } -function install_ubuntu_deps() { +function install_linux_deps() { SUDO="" if [[ ! $(id -u) -eq 0 ]]; then @@ -30,61 +30,49 @@ function install_ubuntu_deps() { SUDO="sudo" fi - # Detect OS using /etc/os-release file - if [ -f "/etc/os-release" ]; then - source /etc/os-release - case "$VERSION_CODENAME" in - "jammy" | "kinetic" | "noble") - ;; - *) - echo "$ID $VERSION_ID is unsupported. This setup script is written for Ubuntu 24.04." - read -p "Would you like to attempt installation anyway? " -n 1 -r - echo "" - if [[ ! $REPLY =~ ^[Yy]$ ]]; then - exit 1 - fi - ;; - esac + # normal stuff, this mostly for bare docker images + if command -v apt-get > /dev/null 2>&1; then + $SUDO apt-get update + $SUDO apt-get install -y --no-install-recommends ca-certificates build-essential curl libcurl4-openssl-dev locales git xvfb + elif command -v dnf > /dev/null 2>&1; then + $SUDO dnf install -y ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-langpack-en git xorg-x11-server-Xvfb + elif command -v yum > /dev/null 2>&1; then + $SUDO yum install -y ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-langpack-en git xorg-x11-server-Xvfb + elif command -v pacman > /dev/null 2>&1; then + $SUDO pacman -Syu --noconfirm --needed base-devel ca-certificates curl git xorg-server-xvfb + elif command -v zypper > /dev/null 2>&1; then + $SUDO zypper --non-interactive refresh + $SUDO zypper --non-interactive install ca-certificates gcc gcc-c++ make curl libcurl-devel glibc-locale git xorg-x11-server + elif command -v apk > /dev/null 2>&1; then + $SUDO apk add --no-cache ca-certificates build-base curl curl-dev musl-locales git xvfb + elif command -v xbps-install > /dev/null 2>&1; then + $SUDO xbps-install -Syu base-devel ca-certificates curl git libcurl-devel glibc-locales xorg-server-xvfb else - echo "No /etc/os-release in the system. Make sure you're running on Ubuntu, or similar." + echo "Unsupported Linux distribution. Supported package managers: apt-get, dnf, yum, pacman, zypper, apk, xbps-install." exit 1 fi - $SUDO apt-get update - - # normal stuff, mostly for the bare docker image - $SUDO apt-get install -y --no-install-recommends \ - ca-certificates \ - build-essential \ - curl \ - libcurl4-openssl-dev \ - locales \ - git \ - xvfb - if [[ -d "/etc/udev/rules.d/" ]]; then - # Setup jungle udev rules - $SUDO tee /etc/udev/rules.d/12-panda_jungle.rules > /dev/null < /dev/null < /dev/null < /dev/null <<-EOF + # Panda Jungle devices + SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcf", MODE="0666" + SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddef", MODE="0666" + SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcf", MODE="0666" + SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddef", MODE="0666" + + # Panda devices + SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE="0666" + SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddcc", MODE="0666" + SUBSYSTEM=="usb", ATTRS{idVendor}=="3801", ATTRS{idProduct}=="ddee", MODE="0666" + SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddcc", MODE="0666" + SUBSYSTEM=="usb", ATTRS{idVendor}=="bbaa", ATTRS{idProduct}=="ddee", MODE="0666" + + # comma devices over ADB + SUBSYSTEM=="usb", ATTR{idVendor}=="04d8", ATTR{idProduct}=="1234", ENV{adb_user}="yes" + EOF + + # delete the old ones + $SUDO rm -f /etc/udev/rules.d/11-panda.rules /etc/udev/rules.d/12-panda_jungle.rules /etc/udev/rules.d/50-comma-adb.rules $SUDO udevadm control --reload-rules && $SUDO udevadm trigger || true fi @@ -116,7 +104,7 @@ function install_python_deps() { # --- Main --- if [[ "$OSTYPE" == "linux-gnu"* ]]; then - install_ubuntu_deps + install_linux_deps echo "[ ] installed system dependencies t=$SECONDS" elif [[ "$OSTYPE" == "darwin"* ]]; then if [[ $SHELL == "/bin/zsh" ]]; then From c3b0f0d11a68836ad59b4a47df8145520ae96d50 Mon Sep 17 00:00:00 2001 From: Armand du Parc Locmaria Date: Thu, 23 Apr 2026 15:23:31 -0700 Subject: [PATCH 683/685] dmonitoringmodeld: get frame size from vipc (#37897) --- selfdrive/modeld/dmonitoringmodeld.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 9aaf6aa9e08..18a79136ae6 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import os -from openpilot.selfdrive.modeld.helpers import MODELS_DIR, set_tinygrad_backend_from_compiled_flags +from openpilot.selfdrive.modeld.helpers import MODELS_DIR, CompileConfig, set_tinygrad_backend_from_compiled_flags set_tinygrad_backend_from_compiled_flags() from tinygrad.tensor import Tensor @@ -28,7 +28,7 @@ class ModelState: inputs: dict[str, np.ndarray] output: np.ndarray - def __init__(self): + def __init__(self, cam_w: int, cam_h: int): with open(METADATA_PATH, 'rb') as f: model_metadata = pickle.load(f) self.input_shapes = model_metadata['input_shapes'] @@ -40,22 +40,18 @@ def __init__(self): self.warp_inputs_np = {'transform': np.zeros((3,3), dtype=np.float32)} self.warp_inputs = {k: Tensor(v, device='NPY') for k,v in self.warp_inputs_np.items()} - self.frame_buf_params = None + self.frame_buf_params = get_nv12_info(cam_w, cam_h) self.tensor_inputs = {k: Tensor(v, device='NPY').realize() for k,v in self.numpy_inputs.items()} self._blob_cache : dict[int, Tensor] = {} - self.image_warp = None self.model_run = pickle.loads(read_file_chunked(str(MODEL_PKL_PATH))) + with open(CompileConfig(cam_w, cam_h, prefix='dm_', prepare_only=True).pkl_path, "rb") as f: + self.image_warp = pickle.load(f) def run(self, buf: VisionBuf, calib: np.ndarray, transform: np.ndarray) -> tuple[np.ndarray, float]: self.numpy_inputs['calib'][0,:] = calib t1 = time.perf_counter() - if self.image_warp is None: - self.frame_buf_params = get_nv12_info(buf.width, buf.height) - warp_path = MODELS_DIR / f'dm_warp_{buf.width}x{buf.height}_tinygrad.pkl' - with open(warp_path, "rb") as f: - self.image_warp = pickle.load(f) ptr = buf.data.ctypes.data # There is a ringbuffer of imgs, just cache tensors pointing to all of them if ptr not in self._blob_cache: @@ -109,9 +105,6 @@ def get_driverstate_packet(model_output, frame_id: int, location_ts: int, exec_t def main(): config_realtime_process(7, 5) - model = ModelState() - cloudlog.warning("models loaded, dmonitoringmodeld starting") - cloudlog.warning("connecting to driver stream") vipc_client = VisionIpcClient("camerad", VisionStreamType.VISION_STREAM_DRIVER, True) while not vipc_client.connect(False): @@ -119,6 +112,9 @@ def main(): assert vipc_client.is_connected() cloudlog.warning(f"connected with buffer size: {vipc_client.buffer_len}") + model = ModelState(vipc_client.width, vipc_client.height) + cloudlog.warning("models loaded, dmonitoringmodeld starting") + sm = SubMaster(["liveCalibration"]) pm = PubMaster(["driverStateV2"]) From 27bc70405b71de158392b31723eba3c83f8f1ac1 Mon Sep 17 00:00:00 2001 From: elkoled Date: Thu, 23 Apr 2026 15:45:17 -0700 Subject: [PATCH 684/685] test: trigger docs on any branch push (temporary) --- .github/workflows/docs.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index f618c1bdf89..d9e35de214a 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -2,8 +2,6 @@ name: docs on: push: - branches: - - master pull_request: workflow_call: inputs: From e587394aee1d359979de66f83732643ccda386ec Mon Sep 17 00:00:00 2001 From: elkoled Date: Thu, 23 Apr 2026 15:52:54 -0700 Subject: [PATCH 685/685] docs: rename LinksProcessor -> LinksTreeprocessor for newer zensical Upstream zensical renamed the class; the old name no longer exists, causing a Rust worker panic that hangs the docs build. --- docs/ext/glossary.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ext/glossary.py b/docs/ext/glossary.py index 35c92add10b..9bbf3c78d71 100644 --- a/docs/ext/glossary.py +++ b/docs/ext/glossary.py @@ -8,7 +8,7 @@ from markdown.preprocessors import Preprocessor from markdown.treeprocessors import Treeprocessor -from zensical.extensions.links import LinksProcessor +from zensical.extensions.links import LinksTreeprocessor GlossaryTerm = tuple[str, re.Pattern[str], str] @@ -78,7 +78,7 @@ def __init__(self, md, glossary: list[GlossaryTerm]): def run(self, root: ET.Element) -> None: at = self.md.treeprocessors.get_index_for_name("zrelpath") processor = self.md.treeprocessors[at] - if not isinstance(processor, LinksProcessor): + if not isinstance(processor, LinksTreeprocessor): raise TypeError("Links processor not registered") if processor.path == GLOSSARY_PAGE: return